| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922 |
- package odin_printer
- import "core:odin/ast"
- import "core:odin/tokenizer"
- import "core:strings"
- import "core:fmt"
- import "core:mem"
- Type_Enum :: enum {Line_Comment, Value_Decl, Switch_Stmt, Struct, Assign, Call, Enum, If, For, Proc_Lit}
- Line_Type :: bit_set[Type_Enum]
- /*
- Represents an unwrapped line
- */
- Line :: struct {
- format_tokens: [dynamic]Format_Token,
- finalized: bool,
- used: bool,
- depth: int,
- types: Line_Type, //for performance, so you don't have to verify what types are in it by going through the tokens - might give problems when adding linebreaking
- }
- /*
- Represents a singular token in a unwrapped line
- */
- Format_Token :: struct {
- kind: tokenizer.Token_Kind,
- text: string,
- type: Type_Enum,
- spaces_before: int,
- parameter_count: int,
- }
- Printer :: struct {
- string_builder: strings.Builder,
- config: Config,
- depth: int, //the identation depth
- comments: [dynamic]^ast.Comment_Group,
- latest_comment_index: int,
- allocator: mem.Allocator,
- file: ^ast.File,
- source_position: tokenizer.Pos,
- last_source_position: tokenizer.Pos,
- lines: [dynamic]Line, //need to look into a better data structure, one that can handle inserting lines rather than appending
- skip_semicolon: bool,
- current_line: ^Line,
- current_line_index: int,
- last_line_index: int,
- last_token: ^Format_Token,
- merge_next_token: bool,
- space_next_token: bool,
- debug: bool,
- }
- Config :: struct {
- spaces: int, //Spaces per indentation
- newline_limit: int, //The limit of newlines between statements and declarations.
- tabs: bool, //Enable or disable tabs
- convert_do: bool, //Convert all do statements to brace blocks
- semicolons: bool, //Enable semicolons
- split_multiple_stmts: bool,
- align_switch: bool,
- brace_style: Brace_Style,
- align_assignments: bool,
- align_structs: bool,
- align_style: Alignment_Style,
- align_enums: bool,
- align_length_break: int,
- indent_cases: bool,
- newline_style: Newline_Style,
- }
- Brace_Style :: enum {
- _1TBS,
- Allman,
- Stroustrup,
- K_And_R,
- }
- Block_Type :: enum {
- None,
- If_Stmt,
- Proc,
- Generic,
- Comp_Lit,
- Switch_Stmt,
- }
- Alignment_Style :: enum {
- Align_On_Type_And_Equals,
- Align_On_Colon_And_Equals,
- }
- Newline_Style :: enum {
- CRLF,
- LF,
- }
- default_style := Config {
- spaces = 4,
- newline_limit = 2,
- convert_do = false,
- semicolons = false,
- tabs = true,
- brace_style = ._1TBS,
- split_multiple_stmts = true,
- align_assignments = true,
- align_style = .Align_On_Type_And_Equals,
- indent_cases = false,
- align_switch = true,
- align_structs = true,
- align_enums = true,
- newline_style = .CRLF,
- align_length_break = 9,
- }
- make_printer :: proc(config: Config, allocator := context.allocator) -> Printer {
- return {
- config = config,
- allocator = allocator,
- debug = false,
- }
- }
- print :: proc(p: ^Printer, file: ^ast.File) -> string {
- p.comments = file.comments
- if len(file.decls) > 0 {
- p.lines = make([dynamic]Line, 0, (file.decls[len(file.decls) - 1].end.line - file.decls[0].pos.line) * 2, context.temp_allocator)
- }
- set_source_position(p, file.pkg_token.pos)
- p.last_source_position.line = 1
- set_line(p, 0)
- push_generic_token(p, .Package, 0)
- push_ident_token(p, file.pkg_name, 1)
- for decl in file.decls {
- visit_decl(p, cast(^ast.Decl)decl)
- }
- if len(p.comments) > 0 {
- infinite := p.comments[len(p.comments) - 1].end
- infinite.offset = 9999999
- push_comments(p, infinite)
- }
- fix_lines(p)
- builder := strings.builder_make(0, 5 * mem.Megabyte, p.allocator)
- last_line := 0
- newline: string
- if p.config.newline_style == .LF {
- newline = "\n"
- } else {
- newline = "\r\n"
- }
- for line, line_index in p.lines {
- diff_line := line_index - last_line
- for i := 0; i < diff_line; i += 1 {
- strings.write_string(&builder, newline)
- }
- if p.config.tabs {
- for i := 0; i < line.depth; i += 1 {
- strings.write_byte(&builder, '\t')
- }
- } else {
- for i := 0; i < line.depth * p.config.spaces; i += 1 {
- strings.write_byte(&builder, ' ')
- }
- }
- if p.debug {
- strings.write_string(&builder, fmt.tprintf("line %v: ", line_index))
- }
- for format_token in line.format_tokens {
- for i := 0; i < format_token.spaces_before; i += 1 {
- strings.write_byte(&builder, ' ')
- }
- strings.write_string(&builder, format_token.text)
- }
- last_line = line_index
- }
- strings.write_string(&builder, newline)
- return strings.to_string(builder)
- }
- fix_lines :: proc(p: ^Printer) {
- align_var_decls(p)
- format_generic(p)
- align_comments(p) //align them last since they rely on the other alignments
- }
- format_value_decl :: proc(p: ^Printer, index: int) {
- eq_found := false
- eq_token: Format_Token
- eq_line: int
- largest := 0
- found_eq: for line, line_index in p.lines[index:] {
- for format_token in line.format_tokens {
- largest += len(format_token.text) + format_token.spaces_before
- if format_token.kind == .Eq {
- eq_token = format_token
- eq_line = line_index + index
- eq_found = true
- break found_eq
- }
- }
- }
- if !eq_found {
- return
- }
- align_next := false
- //check to see if there is a binary operator in the last token(this is guaranteed by the ast visit), otherwise it's not multilined
- for line in p.lines[eq_line:] {
- if len(line.format_tokens) == 0 {
- break
- }
- if align_next {
- line.format_tokens[0].spaces_before = largest + 1
- align_next = false
- }
- kind := find_last_token(line.format_tokens).kind
- if tokenizer.Token_Kind.B_Operator_Begin < kind && kind <= tokenizer.Token_Kind.Cmp_Or {
- align_next = true
- }
- if !align_next {
- break
- }
- }
- }
- find_last_token :: proc(format_tokens: [dynamic]Format_Token) -> Format_Token {
- for i := len(format_tokens) - 1; i >= 0; i -= 1 {
- if format_tokens[i].kind != .Comment {
- return format_tokens[i]
- }
- }
- panic("not possible")
- }
- format_assignment :: proc(p: ^Printer, index: int) {
- }
- format_call :: proc(p: ^Printer, line_index: int, format_index: int) {
- paren_found := false
- paren_token: Format_Token
- paren_line: int
- paren_token_index: int
- largest := 0
- found_paren: for line, i in p.lines[line_index:] {
- for format_token, j in line.format_tokens {
- largest += len(format_token.text) + format_token.spaces_before
- if i == 0 && j < format_index {
- continue
- }
- if format_token.kind == .Open_Paren && format_token.type == .Call {
- paren_token = format_token
- paren_line = line_index + i
- paren_found = true
- paren_token_index = j
- break found_paren
- }
- }
- }
- if !paren_found {
- panic("Should not be possible")
- }
- paren_count := 1
- done := false
- for line in p.lines[paren_line:] {
- if len(line.format_tokens) == 0 {
- continue
- }
- for format_token, i in line.format_tokens {
- if format_token.kind == .Comment {
- continue
- }
- if line_index == 0 && i <= paren_token_index {
- continue
- }
- if format_token.kind == .Open_Paren {
- paren_count += 1
- } else if format_token.kind == .Close_Paren {
- paren_count -= 1
- }
- if paren_count == 0 {
- done = true
- }
- }
- if line_index != 0 {
- line.format_tokens[0].spaces_before = largest
- }
- if done {
- return
- }
- }
- }
- format_keyword_to_brace :: proc(p: ^Printer, line_index: int, format_index: int, keyword: tokenizer.Token_Kind) {
- keyword_found := false
- keyword_token: Format_Token
- keyword_line: int
- largest := 0
- brace_count := 0
- done := false
- found_keyword: for line, i in p.lines[line_index:] {
- for format_token in line.format_tokens {
- largest += len(format_token.text) + format_token.spaces_before
- if format_token.kind == keyword {
- keyword_token = format_token
- keyword_line = line_index + i
- keyword_found = true
- break found_keyword
- }
- }
- }
- if !keyword_found {
- panic("Should not be possible")
- }
- for line, line_idx in p.lines[keyword_line:] {
- if len(line.format_tokens) == 0 {
- continue
- }
- for format_token, i in line.format_tokens {
- if format_token.kind == .Comment {
- break
- } else if format_token.kind == .Undef {
- return
- }
- if line_idx == 0 && i <= format_index {
- continue
- }
- if format_token.kind == .Open_Brace {
- brace_count += 1
- } else if format_token.kind == .Close_Brace {
- brace_count -= 1
- }
- if brace_count == 1 {
- done = true
- }
- }
- if line_idx != 0 {
- line.format_tokens[0].spaces_before = largest + 1
- }
- if done {
- return
- }
- }
- }
- format_generic :: proc(p: ^Printer) {
- next_struct_line := 0
- for line, line_index in p.lines {
- if len(line.format_tokens) <= 0 {
- continue
- }
- for format_token, token_index in line.format_tokens {
- #partial switch format_token.kind {
- case .For, .If, .When, .Switch:
- format_keyword_to_brace(p, line_index, token_index, format_token.kind)
- case .Proc:
- if format_token.type == .Proc_Lit {
- format_keyword_to_brace(p, line_index, token_index, format_token.kind)
- }
- case:
- if format_token.type == .Call {
- format_call(p, line_index, token_index)
- }
- }
- }
- if .Switch_Stmt in line.types && p.config.align_switch {
- align_switch_stmt(p, line_index)
- }
- if .Enum in line.types && p.config.align_enums {
- align_enum(p, line_index)
- }
- if .Struct in line.types && p.config.align_structs && next_struct_line <= 0 {
- next_struct_line = align_struct(p, line_index)
- }
- if .Value_Decl in line.types {
- format_value_decl(p, line_index)
- }
- if .Assign in line.types {
- format_assignment(p, line_index)
- }
- next_struct_line -= 1
- }
- }
- align_var_decls :: proc(p: ^Printer) {
- current_line: int
- current_typed: bool
- current_not_mutable: bool
- largest_lhs := 0
- largest_rhs := 0
- TokenAndLength :: struct {
- format_token: ^Format_Token,
- length: int,
- }
- colon_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator)
- type_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator)
- equal_tokens := make([dynamic]TokenAndLength, 0, 10, context.temp_allocator)
- for line, line_index in p.lines {
- //It is only possible to align value decls that are one one line, otherwise just ignore them
- if .Value_Decl not_in line.types {
- continue
- }
- typed := true
- not_mutable := false
- continue_flag := false
- for i := 0; i < len(line.format_tokens); i += 1 {
- if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Eq {
- typed = false
- }
- if line.format_tokens[i].kind == .Colon && line.format_tokens[min(i + 1, len(line.format_tokens) - 1)].kind == .Colon {
- not_mutable = true
- }
- if line.format_tokens[i].kind == .Union ||
- line.format_tokens[i].kind == .Enum ||
- line.format_tokens[i].kind == .Struct ||
- line.format_tokens[i].kind == .For ||
- line.format_tokens[i].kind == .If ||
- line.format_tokens[i].kind == .Comment {
- continue_flag = true
- }
- //enforced undef is always on the last line, if it exists
- if line.format_tokens[i].kind == .Proc && line.format_tokens[len(line.format_tokens)-1].kind != .Undef {
- continue_flag = true
- }
- }
- if continue_flag {
- continue
- }
- if line_index != current_line + 1 || typed != current_typed || not_mutable != current_not_mutable {
- if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable {
- for colon_token in colon_tokens {
- colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1
- }
- } else if p.config.align_style == .Align_On_Type_And_Equals {
- for type_token in type_tokens {
- type_token.format_token.spaces_before = largest_lhs - type_token.length + 1
- }
- }
- if current_typed {
- for equal_token in equal_tokens {
- equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1
- }
- } else {
- for equal_token in equal_tokens {
- equal_token.format_token.spaces_before = 0
- }
- }
- clear(&colon_tokens)
- clear(&type_tokens)
- clear(&equal_tokens)
- largest_rhs = 0
- largest_lhs = 0
- current_typed = typed
- current_not_mutable = not_mutable
- }
- current_line = line_index
- current_token_index := 0
- lhs_length := 0
- rhs_length := 0
- //calcuate the length of lhs of a value decl i.e. `a, b:`
- for; current_token_index < len(line.format_tokens); current_token_index += 1 {
- lhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before
- if line.format_tokens[current_token_index].kind == .Colon {
- append(&colon_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = lhs_length})
- if len(line.format_tokens) > current_token_index && line.format_tokens[current_token_index + 1].kind != .Eq {
- append(&type_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index + 1], length = lhs_length})
- }
- current_token_index += 1
- largest_lhs = max(largest_lhs, lhs_length)
- break
- }
- }
- //calcuate the length of the rhs i.e. `[dynamic]int = 123123`
- for; current_token_index < len(line.format_tokens); current_token_index += 1 {
- rhs_length += len(line.format_tokens[current_token_index].text) + line.format_tokens[current_token_index].spaces_before
- if line.format_tokens[current_token_index].kind == .Eq {
- append(&equal_tokens, TokenAndLength {format_token = &line.format_tokens[current_token_index], length = rhs_length})
- largest_rhs = max(largest_rhs, rhs_length)
- break
- }
- }
- }
- //repeating myself, move to sub procedure
- if p.config.align_style == .Align_On_Colon_And_Equals || !current_typed || current_not_mutable {
- for colon_token in colon_tokens {
- colon_token.format_token.spaces_before = largest_lhs - colon_token.length + 1
- }
- } else if p.config.align_style == .Align_On_Type_And_Equals {
- for type_token in type_tokens {
- type_token.format_token.spaces_before = largest_lhs - type_token.length + 1
- }
- }
- if current_typed {
- for equal_token in equal_tokens {
- equal_token.format_token.spaces_before = largest_rhs - equal_token.length + 1
- }
- } else {
- for equal_token in equal_tokens {
- equal_token.format_token.spaces_before = 0
- }
- }
- }
- align_switch_stmt :: proc(p: ^Printer, index: int) {
- switch_found := false
- brace_token: Format_Token
- brace_line: int
- found_switch_brace: for line, line_index in p.lines[index:] {
- for format_token in line.format_tokens {
- if format_token.kind == .Open_Brace && switch_found {
- brace_token = format_token
- brace_line = line_index + index
- break found_switch_brace
- } else if format_token.kind == .Open_Brace {
- break
- } else if format_token.kind == .Switch {
- switch_found = true
- }
- }
- }
- if !switch_found {
- return
- }
- largest := 0
- case_count := 0
- TokenAndLength :: struct {
- format_token: ^Format_Token,
- length: int,
- }
- format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator)
- //find all the switch cases that are one lined
- for line, line_index in p.lines[brace_line + 1:] {
- case_found := false
- colon_found := false
- length := 0
- for format_token, i in line.format_tokens {
- if format_token.kind == .Comment {
- break
- }
- //this will only happen if the case is one lined
- if case_found && colon_found {
- append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length})
- largest = max(length, largest)
- break
- }
- if format_token.kind == .Case {
- case_found = true
- case_count += 1
- } else if format_token.kind == .Colon {
- colon_found = true
- }
- length += len(format_token.text) + format_token.spaces_before
- }
- if case_count >= brace_token.parameter_count {
- break
- }
- }
- for token in format_tokens {
- token.format_token.spaces_before = largest - token.length + 1
- }
- }
- align_enum :: proc(p: ^Printer, index: int) {
- enum_found := false
- brace_token: Format_Token
- brace_line: int
- found_enum_brace: for line, line_index in p.lines[index:] {
- for format_token in line.format_tokens {
- if format_token.kind == .Open_Brace && enum_found {
- brace_token = format_token
- brace_line = line_index + index
- break found_enum_brace
- } else if format_token.kind == .Open_Brace {
- break
- } else if format_token.kind == .Enum {
- enum_found = true
- }
- }
- }
- if !enum_found {
- return
- }
- largest := 0
- comma_count := 0
- TokenAndLength :: struct {
- format_token: ^Format_Token,
- length: int,
- }
- format_tokens := make([dynamic]TokenAndLength, 0, brace_token.parameter_count, context.temp_allocator)
- for line, line_index in p.lines[brace_line + 1:] {
- length := 0
- for format_token, i in line.format_tokens {
- if format_token.kind == .Comment {
- break
- }
- if format_token.kind == .Eq {
- append(&format_tokens, TokenAndLength {format_token = &line.format_tokens[i], length = length})
- largest = max(length, largest)
- break
- } else if format_token.kind == .Comma {
- comma_count += 1
- }
- length += len(format_token.text) + format_token.spaces_before
- }
- if comma_count >= brace_token.parameter_count {
- break
- }
- }
- for token in format_tokens {
- token.format_token.spaces_before = largest - token.length + 1
- }
- }
- align_struct :: proc(p: ^Printer, index: int) -> int {
- struct_found := false
- brace_token: Format_Token
- brace_line: int
- found_struct_brace: for line, line_index in p.lines[index:] {
- for format_token in line.format_tokens {
- if format_token.kind == .Open_Brace && struct_found {
- brace_token = format_token
- brace_line = line_index + index
- break found_struct_brace
- } else if format_token.kind == .Open_Brace {
- break
- } else if format_token.kind == .Struct {
- struct_found = true
- }
- }
- }
- if !struct_found {
- return 0
- }
- largest := 0
- colon_count := 0
- nested := false
- seen_brace := false
- TokenAndLength :: struct {
- format_token: ^Format_Token,
- length: int,
- }
- format_tokens := make([]TokenAndLength, brace_token.parameter_count, context.temp_allocator)
- if brace_token.parameter_count == 0 {
- return 0
- }
- end_line_index := 0
- for line, line_index in p.lines[brace_line + 1:] {
- length := 0
- for format_token, i in line.format_tokens {
- //give up on nested structs
- if format_token.kind == .Comment {
- break
- } else if format_token.kind == .Open_Paren {
- break
- } else if format_token.kind == .Open_Brace {
- seen_brace = true
- } else if format_token.kind == .Close_Brace {
- seen_brace = false
- } else if seen_brace {
- continue
- }
- if format_token.kind == .Colon {
- format_tokens[colon_count] = {format_token = &line.format_tokens[i + 1], length = length}
- if format_tokens[colon_count].format_token.kind == .Struct {
- nested = true
- }
- colon_count += 1
- largest = max(length, largest)
- }
- length += len(format_token.text) + format_token.spaces_before
- }
- if nested {
- end_line_index = line_index + brace_line + 1
- }
- if colon_count >= brace_token.parameter_count {
- break
- }
- }
- //give up aligning nested, it never looks good
- if nested {
- for line, line_index in p.lines[end_line_index:] {
- for format_token in line.format_tokens {
- if format_token.kind == .Close_Brace {
- return end_line_index + line_index - index
- }
- }
- }
- }
- for token in format_tokens {
- token.format_token.spaces_before = largest - token.length + 1
- }
- return 0
- }
- align_comments :: proc(p: ^Printer) {
- Comment_Align_Info :: struct {
- length: int,
- begin: int,
- end: int,
- depth: int,
- }
- comment_infos := make([dynamic]Comment_Align_Info, 0, context.temp_allocator)
- current_info: Comment_Align_Info
- for line, line_index in p.lines {
- if len(line.format_tokens) <= 0 {
- continue
- }
- if .Line_Comment in line.types {
- if current_info.end + 1 != line_index || current_info.depth != line.depth ||
- (current_info.begin == current_info.end && current_info.length == 0) {
- if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 {
- append(&comment_infos, current_info)
- }
- current_info.begin = line_index
- current_info.end = line_index
- current_info.depth = line.depth
- current_info.length = 0
- }
- length := 0
- for format_token, i in line.format_tokens {
- if format_token.kind == .Comment {
- current_info.length = max(current_info.length, length)
- current_info.end = line_index
- }
- length += format_token.spaces_before + len(format_token.text)
- }
- }
- }
- if (current_info.begin != 0 && current_info.end != 0) || current_info.length > 0 {
- append(&comment_infos, current_info)
- }
- for info in comment_infos {
- if info.begin == info.end || info.length == 0 {
- continue
- }
- for i := info.begin; i <= info.end; i += 1 {
- l := p.lines[i]
- length := 0
- for format_token in l.format_tokens {
- if format_token.kind == .Comment {
- if len(l.format_tokens) == 1 {
- l.format_tokens[i].spaces_before = info.length + 1
- } else {
- l.format_tokens[i].spaces_before = info.length - length + 1
- }
- }
- length += format_token.spaces_before + len(format_token.text)
- }
- }
- }
- }
|