aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/config.odin7
-rw-r--r--src/odin/printer/document.odin295
-rw-r--r--src/odin/printer/printer.odin150
-rw-r--r--src/odin/printer/visit.odin1239
-rw-r--r--src/server/format.odin11
-rw-r--r--src/server/requests.odin15
-rw-r--r--src/server/types.odin4
7 files changed, 1714 insertions, 7 deletions
diff --git a/src/common/config.odin b/src/common/config.odin
index df12845..89edb06 100644
--- a/src/common/config.odin
+++ b/src/common/config.odin
@@ -15,6 +15,13 @@ Config :: struct {
enable_semantic_tokens: bool,
enable_procedure_context: bool,
thread_count: int,
+ file_log: bool,
+ formatter: Format_Config,
+}
+
+Format_Config :: struct {
+ tabs: bool,
+ characters: int,
}
config: Config; \ No newline at end of file
diff --git a/src/odin/printer/document.odin b/src/odin/printer/document.odin
new file mode 100644
index 0000000..8a563d8
--- /dev/null
+++ b/src/odin/printer/document.odin
@@ -0,0 +1,295 @@
+package odin_printer
+
+import "core:strings"
+
+Document :: union {
+ Document_Nil,
+ Document_Newline,
+ Document_Text,
+ Document_Nest,
+ Document_Break,
+ Document_Group,
+ Document_Cons,
+ Document_If_Break,
+}
+
+Document_Nil :: struct {
+
+}
+
+Document_Newline :: struct {
+ amount: int,
+}
+
+Document_Text :: struct {
+ value: string,
+}
+
+Document_Nest :: struct {
+ level: int,
+ document: ^Document,
+}
+
+Document_Break :: struct {
+ value: string,
+ newline: bool,
+}
+
+Document_If_Break :: struct {
+ value: string,
+}
+
+Document_Group :: struct {
+ document: ^Document,
+ fill: bool,
+}
+
+Document_Cons :: struct {
+ lhs: ^Document,
+ rhs: ^Document,
+}
+
+Document_Group_Mode :: enum {
+ Flat,
+ Break,
+ Fill,
+}
+
+empty :: proc(allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_Nil {}
+ return document
+}
+
+text :: proc(value: string, allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_Text {
+ value = value,
+ }
+ return document
+}
+
+newline :: proc(amount: int, allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_Newline {
+ amount = amount,
+ }
+ return document
+}
+
+nest :: proc(level: int, nested_document: ^Document, allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_Nest {
+ level = level,
+ document = nested_document,
+ }
+ return document
+}
+
+if_break :: proc(value: string, allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_If_Break {
+ value = value,
+ }
+ return document
+}
+
+break_with :: proc(value: string, newline := true, allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_Break {
+ value = value,
+ newline = newline,
+ }
+ return document
+}
+
+break_with_space :: proc(allocator := context.allocator) -> ^Document {
+ return break_with(" ", true, allocator)
+}
+
+break_with_no_newline :: proc(allocator := context.allocator) -> ^Document {
+ return break_with(" ", false, allocator)
+}
+
+group :: proc(grouped_document: ^Document, allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_Group {
+ document = grouped_document,
+ }
+ return document
+}
+
+fill_group :: proc(grouped_document: ^Document, allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_Group {
+ document = grouped_document,
+ fill = true,
+ }
+ return document
+}
+
+cons :: proc(lhs: ^Document, rhs: ^Document, allocator := context.allocator) -> ^Document {
+ document := new(Document, allocator)
+ document^ = Document_Cons {
+ lhs = lhs,
+ rhs = rhs,
+ }
+ return document
+}
+
+cons_with_opl :: proc(lhs: ^Document, rhs: ^Document, allocator := context.allocator) -> ^Document {
+
+ if _, ok := lhs.(Document_Nil); ok {
+ return rhs
+ }
+
+ if _, ok := rhs.(Document_Nil); ok {
+ return lhs
+ }
+
+ return cons(lhs, cons(break_with_space(allocator), rhs), allocator)
+}
+
+cons_with_nopl:: proc(lhs: ^Document, rhs: ^Document, allocator := context.allocator) -> ^Document {
+
+ if _, ok := lhs.(Document_Nil); ok {
+ return rhs
+ }
+
+ if _, ok := rhs.(Document_Nil); ok {
+ return lhs
+ }
+
+ return cons(lhs, cons(break_with_no_newline(allocator), rhs), allocator)
+}
+
+Tuple :: struct {
+ indentation: int,
+ mode: Document_Group_Mode,
+ document: ^Document,
+}
+
+fits :: proc(width: int, list: ^[dynamic]Tuple) -> bool {
+ assert(list != nil)
+
+ width := width
+
+ if len(list) == 0 {
+ return true
+ } else if width < 0 {
+ return false
+ }
+
+ for len(list) != 0 {
+ data: Tuple = pop(list)
+
+ if width < 0 {
+ return false
+ }
+
+ switch v in data.document {
+ case Document_Nil:
+ case Document_Newline:
+ return true
+ case Document_Cons:
+ append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.rhs})
+ append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.lhs})
+ case Document_Nest:
+ append(list, Tuple {indentation = data.indentation + v.level, mode = data.mode, document = v.document})
+ case Document_Text:
+ width -= len(v.value)
+ case Document_Break:
+ if data.mode == .Break && v.newline {
+ return true
+ } else {
+ width -= len(v.value)
+ }
+ case Document_If_Break:
+ if data.mode == .Break {
+ width -= len(v.value)
+ }
+ case Document_Group:
+ append(list, Tuple {indentation = data.indentation, mode = .Flat, document = v.document})
+ }
+ }
+
+ return true
+}
+
+format_newline :: proc(indentation: int, consumed: ^int, builder: ^strings.Builder, p: ^Printer) {
+ strings.write_string(builder, p.newline)
+ for i := 0; i < indentation; i += 1 {
+ strings.write_string(builder, p.indentation)
+ }
+ consumed^ = indentation
+}
+
+format :: proc(width: int, list: ^[dynamic]Tuple, builder: ^strings.Builder, p: ^Printer) {
+ assert(list != nil)
+ assert(builder != nil)
+
+ consumed := 0
+
+ for len(list) != 0 {
+
+ data: Tuple = pop(list)
+
+ switch v in data.document {
+ case Document_Nil:
+ case Document_Newline:
+ if v.amount > 0 {
+ for i := 0; i < v.amount; i += 1 {
+ strings.write_string(builder, p.newline)
+ }
+ for i := 0; i < data.indentation; i += 1 {
+ strings.write_string(builder, p.indentation)
+ }
+ consumed = data.indentation
+ }
+ case Document_Cons:
+ append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.rhs})
+ append(list, Tuple {indentation = data.indentation, mode = data.mode, document = v.lhs})
+ case Document_Nest:
+ append(list, Tuple {indentation = data.indentation + v.level, mode = data.mode, document = v.document})
+ case Document_Text:
+ strings.write_string(builder, v.value)
+ consumed += len(v.value)
+ case Document_Break:
+ if data.mode == .Break && v.newline {
+ format_newline(data.indentation, &consumed, builder, p)
+ } else if data.mode == .Fill && consumed < width {
+ strings.write_string(builder, v.value)
+ consumed += len(v.value)
+ } else if data.mode == .Fill && v.newline {
+ format_newline(data.indentation, &consumed, builder, p)
+ } else {
+ strings.write_string(builder, v.value)
+ consumed += len(v.value)
+ }
+ case Document_If_Break:
+ if data.mode == .Break {
+ strings.write_string(builder, v.value)
+ consumed += len(v.value)
+ }
+ case Document_Group:
+ l := make([dynamic]Tuple, 0, len(list))
+
+ for element in list {
+ append(&l, element)
+ }
+
+ append(&l, Tuple {indentation = data.indentation, mode = .Flat, document = v.document})
+
+ if fits(width-consumed, &l) {
+ append(list, Tuple {indentation = data.indentation, mode = .Flat, document = v.document})
+ } else {
+ if v.fill || data.mode == .Fill {
+ append(list, Tuple {indentation = data.indentation, mode = .Fill, document = v.document})
+ } else {
+ append(list, Tuple {indentation = data.indentation, mode = .Break, document = v.document})
+ }
+ }
+ }
+ }
+}
+
diff --git a/src/odin/printer/printer.odin b/src/odin/printer/printer.odin
new file mode 100644
index 0000000..e0776fc
--- /dev/null
+++ b/src/odin/printer/printer.odin
@@ -0,0 +1,150 @@
+package odin_printer
+
+import "core:odin/ast"
+import "core:odin/tokenizer"
+import "core:strings"
+import "core:fmt"
+import "core:mem"
+
+Printer :: struct {
+ string_builder: strings.Builder,
+ config: Config,
+ comments: [dynamic]^ast.Comment_Group,
+ latest_comment_index: int,
+ allocator: mem.Allocator,
+ file: ^ast.File,
+ source_position: tokenizer.Pos,
+ last_source_position: tokenizer.Pos,
+ skip_semicolon: bool,
+ current_line_index: int,
+ last_line_index: int,
+ document: ^Document,
+ indentation: string,
+ newline: string,
+ indentation_count: int,
+ disabled_lines: map[int]string,
+ src: string,
+}
+
+Config :: struct {
+ max_characters: int,
+ 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
+ brace_style: Brace_Style,
+ 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,
+}
+
+Newline_Style :: enum {
+ CRLF,
+ LF,
+}
+
+default_style := Config {
+ spaces = 4,
+ newline_limit = 2,
+ convert_do = false,
+ tabs = false,
+ brace_style = ._1TBS,
+ indent_cases = false,
+ newline_style = .CRLF,
+ max_characters = 120,
+}
+
+make_printer :: proc(config: Config, allocator := context.allocator) -> Printer {
+ return {
+ config = config,
+ allocator = allocator,
+ }
+}
+
+
+build_disabled_lines_info :: proc(p: ^Printer) {
+ found_disable := false
+ disable_position: tokenizer.Pos
+
+ for group in p.comments {
+ for comment in group.list {
+ if strings.contains(comment.text[:], "//odinfmt: disable") {
+ found_disable = true
+ disable_position = comment.pos
+ } else if strings.contains(comment.text[:], "//odinfmt: enable") && found_disable {
+ for line := disable_position.line; line <= comment.pos.line; line += 1 {
+ p.disabled_lines[line] = p.src[disable_position.offset:comment.pos.offset+len(comment.text)]
+ }
+ found_disable = false
+ }
+ }
+ }
+
+}
+
+print :: proc(p: ^Printer, file: ^ast.File) -> string {
+ p.comments = file.comments
+ p.string_builder = strings.make_builder(p.allocator)
+ p.src = file.src
+ context.allocator = p.allocator
+
+ if p.config.tabs {
+ p.indentation = "\t"
+ p.indentation_count = 1
+ } else {
+ p.indentation_count = p.config.spaces
+ p.indentation = " "
+ }
+
+ if p.config.newline_style == .CRLF {
+ p.newline = "\r\n"
+ } else {
+ p.newline = "\n"
+ }
+
+ set_source_position(p, file.pkg_token.pos)
+
+ p.last_source_position.line = 1
+
+ build_disabled_lines_info(p)
+
+ p.document = cons_with_nopl(text("package"), text(file.pkg_name))
+
+ for decl in file.decls {
+ p.document = cons(p.document, visit_decl(p, cast(^ast.Decl)decl))
+ }
+
+ if len(p.comments) > 0 {
+ infinite := p.comments[len(p.comments) - 1].end
+ infinite.offset = 9999999
+ document, _ := visit_comments(p, infinite)
+ p.document = cons(p.document, document)
+ }
+
+ list := make([dynamic]Tuple, p.allocator)
+
+ append(&list, Tuple {
+ document = p.document,
+ indentation = 0,
+ })
+
+ format(p.config.max_characters, &list, &p.string_builder, p)
+
+ return strings.to_string(p.string_builder)
+}
+
diff --git a/src/odin/printer/visit.odin b/src/odin/printer/visit.odin
new file mode 100644
index 0000000..44aea2b
--- /dev/null
+++ b/src/odin/printer/visit.odin
@@ -0,0 +1,1239 @@
+package odin_printer
+
+import "core:odin/ast"
+import "core:odin/tokenizer"
+import "core:strings"
+import "core:fmt"
+import "core:sort"
+
+//right now the attribute order is not linearly parsed(bug?)
+@(private)
+sort_attribute :: proc(s: ^[dynamic]^ast.Attribute) -> sort.Interface {
+ return sort.Interface {
+ collection = rawptr(s),
+ len = proc(it: sort.Interface) -> int {
+ s := (^[dynamic]^ast.Attribute)(it.collection)
+ return len(s^)
+ },
+ less = proc(it: sort.Interface, i, j: int) -> bool {
+ s := (^[dynamic]^ast.Attribute)(it.collection)
+ return s[i].pos.offset < s[j].pos.offset
+ },
+ swap = proc(it: sort.Interface, i, j: int) {
+ s := (^[dynamic]^ast.Attribute)(it.collection)
+ s[i], s[j] = s[j], s[i]
+ },
+ }
+}
+
+@(private)
+comment_before_position :: proc(p: ^Printer, pos: tokenizer.Pos) -> bool {
+ if len(p.comments) <= p.latest_comment_index {
+ return false
+ }
+
+ comment := p.comments[p.latest_comment_index]
+
+ return comment.pos.offset < pos.offset
+}
+
+@(private)
+comment_before_or_in_line :: proc(p: ^Printer, line: int) -> bool {
+ if len(p.comments) <= p.latest_comment_index {
+ return false
+ }
+
+ comment := p.comments[p.latest_comment_index]
+
+ return comment.pos.line < line
+}
+
+@(private)
+next_comment_group :: proc(p: ^Printer) {
+ p.latest_comment_index += 1
+}
+
+@(private)
+text_token :: proc(p: ^Printer, token: tokenizer.Token) -> ^Document {
+ document, _ := visit_comments(p, token.pos)
+ return cons(document, text(token.text))
+}
+
+text_position :: proc(p: ^Printer, value: string, pos: tokenizer.Pos) -> ^Document {
+ document, _ := visit_comments(p, pos)
+ return cons(document, text(value))
+}
+
+newline_position :: proc(p: ^Printer, amount: int, pos: tokenizer.Pos) -> ^Document {
+ document, _ := visit_comments(p, pos)
+ return cons(document, newline(amount))
+}
+
+@(private)
+set_source_position :: proc(p: ^Printer, pos: tokenizer.Pos) {
+ p.source_position = pos
+}
+
+@(private)
+move_line :: proc(p: ^Printer, pos: tokenizer.Pos) -> ^Document {
+ l, _ := move_line_limit(p, pos, p.config.newline_limit+1)
+ return l
+}
+
+@(private)
+move_line_limit :: proc(p: ^Printer, pos: tokenizer.Pos, limit: int) -> (^Document, bool) {
+ lines := pos.line - p.source_position.line
+
+ if lines < 0 {
+ return empty(), false
+ }
+
+ document, comments_newlined := visit_comments(p, pos)
+
+ p.source_position = pos
+
+ return cons(document, newline(max(min(lines-comments_newlined, limit), 0))), lines > 0
+}
+
+@(private)
+visit_comment :: proc(p: ^Printer, comment: tokenizer.Token, end_newline := true) -> (int, ^Document) {
+ document := empty()
+ if len(comment.text) == 0 {
+ return 0, document
+ }
+
+ newlines_before_comment := min(comment.pos.line - p.source_position.line, p.config.newline_limit + 1)
+
+ document = cons(document, newline(newlines_before_comment))
+
+ if comment.text[:2] != "/*" {
+ if comment.pos.line in p.disabled_lines {
+ p.source_position = comment.pos
+ return 1, empty()
+ } else if comment.pos.line == p.source_position.line {
+ p.source_position = comment.pos
+ return newlines_before_comment, cons_with_opl(document, text(comment.text))
+ } else {
+ p.source_position = comment.pos
+ return newlines_before_comment, cons(document, text(comment.text))
+ }
+ /*
+ if comment.pos.line in p.disabled_lines {
+ p.source_position = comment.pos
+ return 1, empty()
+ } else if !end_newline || comment.pos.line == p.source_position.line {
+ p.source_position = comment.pos
+ return newlines_before_comment, cons_with_opl(document, text(comment.text))
+ } else {
+ p.source_position = comment.pos
+ p.source_position.line += 1
+ return newlines_before_comment+1, cons(document, cons(text(comment.text), newline(1)))
+ }
+ */
+ } else {
+ newlines := strings.count(comment.text, "\n")
+
+ if comment.pos.line in p.disabled_lines {
+ p.source_position = comment.pos
+ p.source_position.line += newlines
+ return 1, empty()
+ } else if comment.pos.line == p.source_position.line {
+ p.source_position = comment.pos
+ p.source_position.line += newlines
+ return newlines_before_comment+newlines, cons_with_opl(document, text(comment.text))
+ } else {
+ p.source_position = comment.pos
+ p.source_position.line += newlines
+ return newlines_before_comment+newlines, cons(document, text(comment.text))
+ }
+
+ /*
+ if comment.pos.line in p.disabled_lines {
+ p.source_position = comment.pos
+ p.source_position.line += newlines
+ return 1, empty()
+ } else if comment.pos.line == p.source_position.line {
+ p.source_position = comment.pos
+ p.source_position.line += newlines
+ return newlines_before_comment+newlines, cons_with_opl(document, text(comment.text))
+ } else {
+ p.source_position = comment.pos
+ p.source_position.line += newlines
+ p.source_position.line += 1
+ return newlines_before_comment+newlines+1, cons(document, cons(text(comment.text), newline(1)))
+ }
+ */
+
+ return 0, document
+ }
+
+
+}
+
+@(private)
+visit_comments :: proc(p: ^Printer, pos: tokenizer.Pos, end_newline := true) -> (^Document, int) {
+ document := empty()
+ lines := 0
+
+ for comment_before_position(p, pos) {
+ comment_group := p.comments[p.latest_comment_index]
+
+ for comment, i in comment_group.list {
+ newlined, tmp_document := visit_comment(p, comment, end_newline)
+ lines += newlined
+ document = cons(document, tmp_document)
+ }
+
+ next_comment_group(p)
+ }
+
+ return document, lines
+}
+
+visit_disabled_decl :: proc(p: ^Printer, decl: ^ast.Decl) -> ^Document {
+ disabled_text := p.disabled_lines[decl.pos.line]
+ move := move_line(p, decl.pos)
+
+ for comment_before_or_in_line(p, decl.end.line + 1) {
+ next_comment_group(p)
+ }
+
+ p.source_position = decl.end
+
+ return cons(move, text(disabled_text))
+}
+
+@(private)
+visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) -> ^Document {
+ using ast
+
+ if decl == nil {
+ return empty()
+ }
+
+ if decl.pos.line in p.disabled_lines {
+ return visit_disabled_decl(p, decl)
+ }
+
+ switch v in &decl.derived {
+ case Expr_Stmt:
+ document := move_line(p, decl.pos)
+ return cons(document, visit_expr(p, v.expr))
+ case When_Stmt:
+ return visit_stmt(p, cast(^Stmt)decl)
+ case Foreign_Import_Decl:
+ document := empty()
+ if len(v.attributes) > 0 {
+ document = cons(document, visit_attributes(p, &v.attributes, v.pos))
+ }
+
+ document = cons(document, move_line(p, decl.pos))
+ document = cons(document, cons_with_opl(text(v.foreign_tok.text), text(v.import_tok.text)))
+
+ if v.name != nil {
+ document = cons_with_opl(document, text_position(p, v.name.name, v.pos))
+ }
+
+ for path in v.fullpaths {
+ document = cons_with_opl(document, text(path))
+ }
+ return document
+ case Foreign_Block_Decl:
+ document := empty()
+ if len(v.attributes) > 0 {
+ document = cons(document, visit_attributes(p, &v.attributes, v.pos))
+ }
+
+ document = cons(document, move_line(p, decl.pos))
+ document = cons(document, cons_with_opl(text("foreign"), visit_expr(p, v.foreign_library)))
+ document = cons_with_nopl(document, visit_stmt(p, v.body))
+ return document
+ case Import_Decl:
+ document := move_line(p, decl.pos)
+
+ if v.name.text != "" {
+ document = cons(document, text_token(p, v.import_tok))
+ document = cons(document, break_with_space())
+ document = cons(document, text_token(p, v.name))
+ document = cons(document, break_with_space())
+ document = cons(document, text(v.fullpath))
+ } else {
+ document = cons(document, text_token(p, v.import_tok))
+ document = cons(document, break_with_space())
+ document = cons(document, text(v.fullpath))
+ }
+ return document
+ case Value_Decl:
+ document := empty()
+ if len(v.attributes) > 0 {
+ document = cons(document, visit_attributes(p, &v.attributes, v.pos))
+ }
+
+ document = cons(document, move_line(p, decl.pos))
+
+ if v.is_using {
+ document = cons(document, cons(text("using"), break_with_no_newline()))
+ }
+
+ document = cons(document, visit_exprs(p, v.names, {.Add_Comma}))
+
+ if v.type != nil {
+ document = group(cons(document, text(":")))
+ document = cons_with_nopl(document, visit_expr(p, v.type))
+ } else {
+ if !v.is_mutable {
+ document = cons_with_nopl(document, cons(text(":"), text(":")))
+ } else {
+ document = cons_with_nopl(document, text(":"))
+ }
+ }
+
+ if len(v.values) > 0 && v.is_mutable {
+ if v.type != nil {
+ document = cons_with_nopl(document, text("="))
+ } else {
+ document = cons(document, text("="))
+ }
+ document = cons_with_nopl(document, visit_exprs(p, v.values, {.Add_Comma}))
+ } else if len(v.values) > 0 && v.type != nil {
+ document = cons_with_nopl(document, cons_with_nopl(text(":"), visit_exprs(p, v.values, {.Add_Comma})))
+ } else {
+ document = cons_with_nopl(document, visit_exprs(p, v.values, {.Add_Comma}))
+ }
+
+ return document
+ case:
+ panic(fmt.aprint(decl.derived))
+ }
+
+ return empty()
+}
+
+@(private)
+visit_exprs :: proc(p: ^Printer, list: []^ast.Expr, options := List_Options{}) -> ^Document {
+ if len(list) == 0 {
+ return empty()
+ }
+
+ document := empty()
+
+ for expr, i in list {
+
+ if (.Enforce_Newline in options) {
+ document = group(cons(document, visit_expr(p, expr, options)))
+ } else {
+ document = group(cons_with_opl(document, visit_expr(p, expr, options)))
+ }
+
+ if (i != len(list) - 1 || .Trailing in options) && .Add_Comma in options {
+ document = cons(document, text(","))
+ }
+
+ if (i != len(list) - 1 && .Enforce_Newline in options) {
+ comment, ok := visit_comments(p, list[i+1].pos, false)
+ document = cons(document, cons(comment, newline(1)))
+ }
+ }
+
+ return document
+}
+
+@(private)
+visit_attributes :: proc(p: ^Printer, attributes: ^[dynamic]^ast.Attribute, pos: tokenizer.Pos) -> ^Document {
+ document := empty()
+ if len(attributes) == 0 {
+ return document
+ }
+
+ sort.sort(sort_attribute(attributes))
+ document = cons(document, move_line(p, attributes[0].pos))
+
+ for attribute, i in attributes {
+ document = cons(document, cons(text("@"), text("(")))
+ document = cons(document, visit_exprs(p, attribute.elems, {.Add_Comma}))
+ document = cons(document, text(")"))
+
+ if i != len(attributes) - 1 {
+ document = cons(document, newline(1))
+ } else if pos.line == attributes[0].pos.line {
+ document = cons(document, newline(1))
+ }
+ }
+
+ return document
+}
+
+@(private)
+visit_stmt :: proc(p: ^Printer, stmt: ^ast.Stmt, block_type: Block_Type = .Generic, empty_block := false, block_stmt := false) -> ^Document {
+ using ast
+
+ if stmt == nil {
+ return empty()
+ }
+
+ switch v in stmt.derived {
+ case Import_Decl:
+ return visit_decl(p, cast(^Decl)stmt, true)
+ case Value_Decl:
+ return visit_decl(p, cast(^Decl)stmt, true)
+ case Foreign_Import_Decl:
+ return visit_decl(p, cast(^Decl)stmt, true)
+ case Foreign_Block_Decl:
+ return visit_decl(p, cast(^Decl)stmt, true)
+ }
+
+ switch v in stmt.derived {
+ case Using_Stmt:
+ document := move_line(p, v.pos)
+ document = cons(document, cons_with_nopl(text("using"), visit_exprs(p, v.list, {.Add_Comma})))
+ return document
+ case Block_Stmt:
+ document := move_line(p, v.pos)
+
+ if !empty_block {
+ document = cons(document, visit_begin_brace(p, v.pos, block_type, len(v.stmts)))
+ }
+
+ set_source_position(p, v.pos)
+
+ block := group(visit_block_stmts(p, v.stmts, len(v.stmts) > 1))
+ comment_end, _ := visit_comments(p, tokenizer.Pos {line = v.end.line, offset = v.end.offset}, false)
+
+ if block_type == .Switch_Stmt && !p.config.indent_cases {
+ document = cons(document, cons(block, comment_end))
+ } else {
+ document = cons(document, nest(p.indentation_count, cons(block, comment_end)))
+ }
+
+ if !empty_block {
+ document = cons(document, visit_end_brace(p, v.end))
+ }
+ return document
+ case If_Stmt:
+ document := move_line(p, v.pos)
+
+ if v.label != nil {
+ document = cons(document, cons(visit_expr(p, v.label), cons(text(":"), break_with_space())))
+ }
+
+ document = cons(document, text("if"))
+
+ if v.init != nil {
+ document = cons_with_nopl(document, cons(visit_stmt(p, v.init), text(";")))
+ }
+
+ document = cons_with_opl(document, visit_expr(p, v.cond))
+
+ uses_do := false
+
+ if check_stmt, ok := v.body.derived.(Block_Stmt); ok && check_stmt.uses_do {
+ uses_do = true
+ }
+
+ if uses_do && !p.config.convert_do {
+ document = cons(document, cons_with_opl(text("do"), visit_stmt(p, v.body, .If_Stmt, true)))
+ } else {
+ if uses_do {
+ document = cons(document, newline(1))
+ }
+
+ set_source_position(p, v.body.pos)
+
+ document = cons_with_nopl(document, visit_stmt(p, v.body, .If_Stmt))
+
+ set_source_position(p, v.body.end)
+ }
+
+ if v.else_stmt != nil {
+
+ if p.config.brace_style == .Allman || p.config.brace_style == .Stroustrup {
+ document = cons(document, newline(1))
+ }
+
+ set_source_position(p, v.else_stmt.pos)
+
+ if _, ok := v.else_stmt.derived.(ast.If_Stmt); ok {
+ document = cons_with_opl(document, cons_with_opl(text("else"), visit_stmt(p, v.else_stmt)))
+ } else {
+ document = cons_with_opl(document, cons(text("else"), visit_stmt(p, v.else_stmt)))
+ }
+ }
+ return document
+ case Switch_Stmt:
+ document := move_line(p, v.pos)
+
+ if v.label != nil {
+ document = cons(document, cons(visit_expr(p, v.label), cons(text(":"), break_with_space())))
+ }
+
+ if v.partial {
+ document = cons(document, cons(text("#partial"), break_with_space()))
+ }
+
+ document = cons(document, text("switch"))
+
+ if v.init != nil {
+ document = cons_with_opl(document, visit_stmt(p, v.init))
+ }
+
+ if v.init != nil && v.cond != nil {
+ document = cons(document, text(";"))
+ }
+
+ document = cons_with_opl(document, visit_expr(p, v.cond))
+ document = cons_with_nopl(document, visit_stmt(p, v.body, .Switch_Stmt))
+ return document
+ case Case_Clause:
+ document := move_line(p, v.pos)
+ document = cons(document, text("case"))
+
+ if v.list != nil {
+ document = cons_with_nopl(document, visit_exprs(p, v.list, {.Add_Comma}))
+ }
+
+ document = cons(document, text(v.terminator.text))
+
+ if len(v.body) != 0 {
+ set_source_position(p, v.body[0].pos)
+ document = cons(document, nest(p.indentation_count, cons(newline(1), visit_block_stmts(p, v.body))))
+ }
+
+ return document
+ case Type_Switch_Stmt:
+ document := move_line(p, v.pos)
+
+ if v.label != nil {
+ document = cons(document, cons(visit_expr(p, v.label), cons(text(":"), break_with_space())))
+ }
+
+ if v.partial {
+ document = cons(document, cons(text("#partial"), break_with_space()))
+ }
+
+ document = cons(document, text("switch"))
+
+ document = cons_with_nopl(document, visit_stmt(p, v.tag))
+ document = cons_with_nopl(document, visit_stmt(p, v.body, .Switch_Stmt))
+ return document
+ case Assign_Stmt:
+ document := move_line(p, v.pos)
+
+ document = cons(document, visit_exprs(p, v.lhs, {.Add_Comma}))
+ document = cons_with_opl(document, cons_with_opl(text(v.op.text), visit_exprs(p, v.rhs, {.Add_Comma})))
+
+ return document
+ case Expr_Stmt:
+ document := move_line(p, v.pos)
+ document = cons(document, visit_expr(p, v.expr))
+ return document
+ case For_Stmt:
+ document := move_line(p, v.pos)
+
+ if v.label != nil {
+ document = cons(document, cons(visit_expr(p, v.label), cons(text(":"), break_with_space())))
+ }
+
+ document = cons(document, text("for"))
+
+ if v.init != nil {
+ document = cons_with_nopl(document, cons(visit_stmt(p, v.init), text(";")))
+ } else if v.post != nil {
+ document = cons_with_nopl(document, text(";"))
+ }
+
+ if v.cond != nil {
+ document = cons_with_nopl(document, visit_expr(p, v.cond))
+ }
+
+ if v.post != nil {
+ document = cons(document, text(";"))
+ document = cons_with_nopl(document, visit_stmt(p, v.post))
+ } else if v.post == nil && v.cond != nil && v.init != nil {
+ document = cons(document, text(";"))
+ }
+
+ document = cons_with_nopl(document, visit_stmt(p, v.body))
+ return document
+ case Inline_Range_Stmt:
+ document := move_line(p, v.pos)
+
+ if v.label != nil {
+ document = cons(document, cons(visit_expr(p, v.label), cons(text(":"), break_with_space())))
+ }
+
+ document = cons(document, text("#unroll"))
+ document = cons_with_nopl(document, text("for"))
+
+ document = cons_with_nopl(document, visit_expr(p, v.val0))
+
+ if v.val1 != nil {
+ document = cons_with_nopl(document, cons_with_opl(text(","), visit_expr(p, v.val1)))
+ }
+
+ document = cons_with_nopl(document, text("in"))
+
+ document = cons_with_nopl(document, visit_expr(p, v.expr))
+ document = cons_with_nopl(document, visit_stmt(p, v.body))
+ return document
+ case Range_Stmt:
+ document := move_line(p, v.pos)
+
+ if v.label != nil {
+ document = cons(document, cons(visit_expr(p, v.label), cons(text(":"), break_with_space())))
+ }
+
+ document = cons(document, text("for"))
+
+ if len(v.vals) >= 1 {
+ document = cons_with_opl(document, visit_expr(p, v.vals[0]))
+ }
+
+ if len(v.vals) >= 2 {
+ document = cons(document, cons_with_opl(text(","), visit_expr(p, v.vals[1])))
+ }
+
+ document = cons_with_opl(document, text("in"))
+
+ document = cons_with_opl(document, visit_expr(p, v.expr))
+ document = cons_with_nopl(document, visit_stmt(p, v.body))
+ return document
+ case Return_Stmt:
+ document := move_line(p, v.pos)
+
+ document = cons(document, text("return"))
+
+ if v.results != nil {
+ document = cons_with_opl(document, visit_exprs(p, v.results, {.Add_Comma}))
+ }
+
+ return document
+ case Defer_Stmt:
+ document := move_line(p, v.pos)
+ document = cons(document, text("defer"))
+ document = cons_with_opl(document, visit_stmt(p, v.stmt))
+ return document
+ case When_Stmt:
+ document := move_line(p, v.pos)
+
+ document = cons(document, cons_with_opl(text("when"), visit_expr(p, v.cond)))
+
+ document = cons_with_nopl(document, visit_stmt(p, v.body))
+
+ if v.else_stmt != nil {
+ if p.config.brace_style == .Allman {
+ document = cons(document, newline(1))
+ }
+
+ set_source_position(p, v.else_stmt.pos)
+
+ document = cons(document, cons_with_opl(text("else"), visit_stmt(p, v.else_stmt)))
+ }
+ return document
+ case Branch_Stmt:
+ document := move_line(p, v.pos)
+
+ document = cons(document, text(v.tok.text))
+
+ if v.label != nil {
+ document = cons_with_nopl(document, visit_expr(p, v.label))
+ }
+ return document
+ case:
+ panic(fmt.aprint(stmt.derived))
+ }
+
+ set_source_position(p, stmt.end)
+
+ return empty()
+}
+
+@(private)
+push_where_clauses :: proc(p: ^Printer, clauses: []^ast.Expr) -> ^Document {
+ if len(clauses) == 0 {
+ return empty()
+ }
+
+ return nest(p.indentation_count, cons(newline(1), cons_with_opl(text("where"), visit_exprs(p, clauses, {.Add_Comma, .Enforce_Newline}))))
+}
+
+@(private)
+visit_poly_params :: proc(p: ^Printer, poly_params: ^ast.Field_List) -> ^Document {
+ if poly_params != nil {
+ return cons(text("("), cons(visit_field_list(p, poly_params, {.Add_Comma, .Enforce_Poly_Names}), text(")")))
+ } else {
+ return empty()
+ }
+}
+
+
+@(private)
+visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) -> ^Document {
+ using ast
+
+ if expr == nil {
+ return empty()
+ }
+
+ set_source_position(p, expr.pos)
+
+ switch v in expr.derived {
+ case Inline_Asm_Expr:
+ document := cons(text_token(p, v.tok), text("("))
+ document = cons(document, visit_exprs(p, v.param_types, {.Add_Comma}))
+ document = cons(document, text(")"))
+ document = cons_with_opl(document, cons(text("-"), text(">")))
+ document = cons_with_opl(document, visit_expr(p, v.return_type))
+
+ document = cons(document, text("{"))
+ document = cons(document, visit_expr(p, v.asm_string))
+ document = cons(document, text(","))
+ document = cons(document, visit_expr(p, v.constraints_string))
+ document = cons(document, text("}"))
+ case Undef:
+ return text("---")
+ case Auto_Cast:
+ return cons(text_token(p, v.op), visit_expr(p, v.expr))
+ case Ternary_If_Expr:
+ document := visit_expr(p, v.x)
+ document = cons_with_opl(document, text_token(p, v.op1))
+ document = cons_with_opl(document, visit_expr(p, v.cond))
+ document = cons_with_opl(document, text_token(p, v.op2))
+ document = cons_with_opl(document, visit_expr(p, v.y))
+ return document
+ case Ternary_When_Expr:
+ document := visit_expr(p, v.x)
+ document = cons_with_opl(document, text_token(p, v.op1))
+ document = cons_with_opl(document, visit_expr(p, v.cond))
+ document = cons_with_opl(document, text_token(p, v.op2))
+ document = cons_with_opl(document, visit_expr(p, v.y))
+ return document
+ case Or_Else_Expr:
+ document := visit_expr(p, v.x)
+ document = cons_with_opl(document, text_token(p, v.token))
+ document = cons_with_opl(document, visit_expr(p, v.y))
+ return document
+ case Or_Return_Expr:
+ return cons_with_opl(visit_expr(p, v.expr), text_token(p, v.token))
+ case Selector_Call_Expr:
+ document := visit_expr(p, v.call.expr)
+ document = cons(document, text("("))
+ document = cons(document, visit_exprs(p, v.call.args, {.Add_Comma}))
+ document = cons(document, text(")"))
+ return document
+ case Ellipsis:
+ return cons_with_opl(text("..."), visit_expr(p, v.expr))
+ case Relative_Type:
+ return cons_with_opl(visit_expr(p, v.tag), visit_expr(p, v.type))
+ case Slice_Expr:
+ document := visit_expr(p, v.expr)
+ document = cons(document, text("["))
+ document = cons(document, visit_expr(p, v.low))
+ document = cons(document, text(v.interval.text))
+
+ if v.high != nil {
+ document = cons(document, visit_expr(p, v.high))
+ }
+ document = cons(document, text("]"))
+ return document
+ case Ident:
+ if .Enforce_Poly_Names in options {
+ return cons(text("$"), text(v.name))
+ } else {
+ return text_position(p, v.name, v.pos)
+ }
+ case Deref_Expr:
+ return cons(visit_expr(p, v.expr), text_token(p, v.op))
+ case Type_Cast:
+ document := cons(text_token(p, v.tok), text("("))
+ document = cons(document, visit_expr(p, v.type))
+ document = cons(document, text(")"))
+ document = cons(document, visit_expr(p, v.expr))
+ return document
+ case Basic_Directive:
+ return cons(text_token(p, v.tok), text_position(p, v.name, v.pos))
+ case Distinct_Type:
+ return cons_with_opl(text_position(p, "distinct", v.pos), visit_expr(p, v.type))
+ case Dynamic_Array_Type:
+ document := visit_expr(p, v.tag)
+ document = cons(document, text("["))
+ document = cons(document, text("dynamic"))
+ document = cons(document, text("]"))
+ document = cons(document, visit_expr(p, v.elem))
+ return document
+ case Bit_Set_Type:
+ document := text_position(p, "bit_set", v.pos)
+ document = cons(document, text("["))
+ document = cons(document, visit_expr(p, v.elem))
+
+ if v.underlying != nil {
+ document = cons(document, cons(text(";"), visit_expr(p, v.underlying)))
+ }
+
+ document = cons(document, text("]"))
+ return document
+ case Union_Type:
+ document := text_position(p, "union", v.pos)
+
+ document = cons(document, visit_poly_params(p, v.poly_params))
+
+ if v.is_maybe {
+ document = cons_with_opl(document, text("#maybe"))
+ }
+
+ document = cons_with_opl(document, push_where_clauses(p, v.where_clauses))
+
+ if v.variants != nil && (len(v.variants) == 0 || v.pos.line == v.end.line) {
+ document = cons_with_nopl(document, text("{"))
+ document = cons(document, visit_exprs(p, v.variants, {.Add_Comma}))
+ document = cons(document, text("}"))
+ } else if v.variants != nil {
+ document = cons_with_opl(document, visit_begin_brace(p, v.pos, .Generic))
+
+ set_source_position(p, v.variants[0].pos)
+
+ document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.variants[0].pos), visit_exprs(p, v.variants, {.Add_Comma, .Trailing, .Enforce_Newline}))))
+ document = cons(document, visit_end_brace(p, v.end))
+ }
+ return document
+ case Enum_Type:
+ document := text_position(p, "enum", v.pos)
+
+ if v.base_type != nil {
+ document = cons(document, visit_expr(p, v.base_type))
+ }
+
+ if v.fields != nil && (len(v.fields) == 0 || v.pos.line == v.end.line) {
+ document = cons_with_nopl(document, text("{"))
+ document = cons(document, visit_exprs(p, v.fields, {.Add_Comma}))
+ document = cons(document, text("}"))
+ } else {
+ document = cons(document, cons(break_with_space(), visit_begin_brace(p, v.pos, .Generic)))
+
+ set_source_position(p, v.fields[0].pos)
+
+ document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.fields[0].pos), visit_exprs(p, v.fields, {.Add_Comma, .Trailing, .Enforce_Newline}))))
+ document = cons(document, visit_end_brace(p, v.end))
+ }
+
+ set_source_position(p, v.end)
+ return document
+ case Struct_Type:
+ document := text_position(p, "struct", v.pos)
+
+ document = cons(document, visit_poly_params(p, v.poly_params))
+
+ if v.is_packed {
+ document = cons_with_nopl(document, text("#packed"))
+ }
+
+ if v.is_raw_union {
+ document = cons_with_nopl(document, text("#raw_union"))
+ }
+
+ if v.align != nil {
+ document = cons_with_nopl(document, text("#align"))
+ document = cons_with_nopl(document, visit_expr(p, v.align))
+ }
+
+ document = cons_with_opl(document, push_where_clauses(p, v.where_clauses))
+
+ if v.fields != nil && (len(v.fields.list) == 0 || v.pos.line == v.end.line) {
+ document = cons_with_nopl(document, text("{"))
+ document = cons(document, visit_field_list(p, v.fields, {.Add_Comma}))
+ document = cons(document, text("}"))
+ } else if v.fields != nil {
+ document = cons(document, cons(break_with_space(), visit_begin_brace(p, v.pos, .Generic)))
+
+ set_source_position(p, v.fields.pos)
+
+ document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.fields.pos), visit_field_list(p, v.fields, {.Add_Comma, .Trailing, .Enforce_Newline}))))
+ document = cons(document, visit_end_brace(p, v.end))
+ }
+
+ set_source_position(p, v.end)
+ return document
+ case Proc_Lit:
+ document := empty()
+
+ switch v.inlining {
+ case .None:
+ case .Inline:
+ document = cons(document, text("#force_inline"))
+ case .No_Inline:
+ document = cons(document, text("#force_no_inline"))
+ }
+
+ document = cons(document, visit_proc_type(p, v.type^))
+ document = cons_with_opl(document, push_where_clauses(p, v.where_clauses))
+
+ if v.body != nil {
+ set_source_position(p, v.body.pos)
+ document = cons_with_nopl(document, group(visit_stmt(p, v.body, .Proc)))
+ } else {
+ document = cons_with_opl(document, text("---"))
+ }
+
+ return document
+ case Proc_Type:
+ return group(visit_proc_type(p, v))
+ case Basic_Lit:
+ return text_token(p, v.tok)
+ case Binary_Expr:
+ return nest(p.indentation_count, fill_group(visit_binary_expr(p, v)))
+ case Implicit_Selector_Expr:
+ return cons(text("."), text_position(p, v.field.name, v.field.pos))
+ case Call_Expr:
+ document := group(visit_expr(p, v.expr))
+ document = cons(document, text("("))
+ document = cons(document, nest(p.indentation_count, fill_group(visit_call_exprs(p, v.args, v.ellipsis.kind == .Ellipsis))))
+ document = cons(document, text(")"))
+ return document
+ case Typeid_Type:
+ document := text("typeid")
+
+ if v.specialization != nil {
+ document = cons(document, cons(text("/"), visit_expr(p, v.specialization)))
+ }
+ return document
+ case Selector_Expr:
+ return cons(visit_expr(p, v.expr), cons(text_token(p, v.op), visit_expr(p, v.field)))
+ case Paren_Expr:
+ return cons(text("("), cons(visit_expr(p, v.expr), text(")")))
+ case Index_Expr:
+ document := visit_expr(p, v.expr)
+ document = cons(document, text("["))
+ document = cons(document, visit_expr(p, v.index))
+ document = cons(document, text("]"))
+ return document
+ case Proc_Group:
+ document := text_token(p, v.tok)
+
+ if len(v.args) != 0 && v.pos.line != v.args[len(v.args) - 1].pos.line {
+ document = cons(document, cons(break_with_space(), visit_begin_brace(p, v.pos, .Generic)))
+ set_source_position(p, v.args[0].pos)
+ document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.args[0].pos), visit_exprs(p, v.args, {.Add_Comma, .Trailing, .Enforce_Newline}))))
+ document = cons(document, visit_end_brace(p, v.end))
+ } else {
+ document = cons(document, text("{"))
+ document = cons(document, visit_exprs(p, v.args, {.Add_Comma}))
+ document = cons(document, text("}"))
+ }
+ return document
+ case Comp_Lit:
+ document := empty()
+ if v.type != nil {
+ document = cons(document, visit_expr(p, v.type))
+ }
+
+ if len(v.elems) != 0 && v.pos.line != v.elems[len(v.elems) - 1].pos.line {
+ document = cons(document, cons(break_with_space(), visit_begin_brace(p, v.pos, .Generic)))
+ set_source_position(p, v.elems[0].pos)
+ document = cons(document, nest(p.indentation_count, cons(newline_position(p, 1, v.elems[0].pos), visit_exprs(p, v.elems, {.Add_Comma, .Trailing, .Enforce_Newline}))))
+ document = cons(document, visit_end_brace(p, v.end))
+ } else {
+ document = cons(document, text("{"))
+ document = cons(document, visit_exprs(p, v.elems, {.Add_Comma}))
+ document = cons(document, text("}"))
+ }
+ return document
+ case Unary_Expr:
+ return cons(text_token(p, v.op), visit_expr(p, v.expr))
+ case Field_Value:
+ document := cons_with_opl(visit_expr(p, v.field), cons_with_nopl(text_position(p, "=", v.sep), visit_expr(p, v.value)))
+ return document
+ case Type_Assertion:
+ document := visit_expr(p, v.expr)
+
+ if unary, ok := v.type.derived.(Unary_Expr); ok && unary.op.text == "?" {
+ document = cons(document, cons(text("."), visit_expr(p, v.type)))
+ } else {
+ document = cons(document, text("."))
+ document = cons(document, text("("))
+ document = cons(document, visit_expr(p, v.type))
+ document = cons(document, text(")"))
+ }
+ return document
+ case Pointer_Type:
+ return cons(text("^"), visit_expr(p, v.elem))
+ case Implicit:
+ return text_token(p, v.tok)
+ case Poly_Type:
+ document := cons(text("$"), visit_expr(p, v.type))
+
+ if v.specialization != nil {
+ document = cons(document, text("/"))
+ document = cons(document, visit_expr(p, v.specialization))
+ }
+ return document
+ case Array_Type:
+ document := visit_expr(p, v.tag)
+ document = cons(document, text("["))
+ document = cons(document, visit_expr(p, v.len))
+ document = cons(document, text("]"))
+ document = cons(document, visit_expr(p, v.elem))
+ return document
+ case Map_Type:
+ document := cons(text("map"), text("["))
+ document = cons(document, visit_expr(p, v.key))
+ document = cons(document, text("]"))
+ document = cons(document, visit_expr(p, v.value))
+ return document
+ case Helper_Type:
+ return visit_expr(p, v.type)
+ case Matrix_Type:
+ document := text_position(p, "matrix", v.pos)
+ document = cons(document, text("["))
+ document = cons(document, visit_expr(p, v.row_count))
+ document = cons(document, text(","))
+ document = cons_with_opl(document, visit_expr(p, v.column_count))
+ document = cons(document, text("]"))
+ document = cons(document, visit_expr(p, v.elem))
+ return document
+ case Matrix_Index_Expr:
+ document := visit_expr(p, v.expr)
+ document = cons(document, text("["))
+ document = cons(document, visit_expr(p, v.row_index))
+ document = cons(document, text(","))
+ document = cons_with_opl(document, visit_expr(p, v.column_index))
+ document = cons(document, text("]"))
+ return document
+ case:
+ panic(fmt.aprint(expr.derived))
+ }
+
+ return empty()
+}
+
+visit_begin_brace :: proc(p: ^Printer, begin: tokenizer.Pos, type: Block_Type, count := 0, same_line_spaces_before := 1) -> ^Document {
+ set_source_position(p, begin)
+
+ newline_braced := p.config.brace_style == .Allman
+ newline_braced |= p.config.brace_style == .K_And_R && type == .Proc
+ newline_braced &= p.config.brace_style != ._1TBS
+
+ if newline_braced {
+ document := newline(1)
+ document = cons(document, text("{"))
+ return document
+ } else {
+ return text("{")
+ }
+}
+
+visit_end_brace :: proc(p: ^Printer, end: tokenizer.Pos) -> ^Document {
+ return cons(move_line(p, end), text("}"))
+}
+
+visit_block_stmts :: proc(p: ^Printer, stmts: []^ast.Stmt, split := false) -> ^Document {
+ document := empty()
+
+ for stmt, i in stmts {
+ document = cons(document, visit_stmt(p, stmt, .Generic, false, true))
+ }
+
+ return document
+}
+
+List_Option :: enum u8 {
+ Add_Comma,
+ Trailing,
+ Enforce_Newline,
+ Enforce_Poly_Names,
+}
+
+List_Options :: distinct bit_set[List_Option]
+
+visit_field_list :: proc(p: ^Printer, list: ^ast.Field_List, options := List_Options{}) -> ^Document {
+ document := empty()
+ if list.list == nil {
+ return document
+ }
+
+ for field, i in list.list {
+
+ p.source_position = field.pos
+
+ if .Using in field.flags {
+ document = cons(document, cons(text("using"), break_with_no_newline()))
+ }
+
+ name_options := List_Options{.Add_Comma}
+ if .Enforce_Poly_Names in options {
+ name_options += {.Enforce_Poly_Names}
+ }
+
+ if (.Enforce_Newline in options) {
+ document = cons(document, visit_exprs(p, field.names, name_options))
+ } else {
+ document = cons_with_opl(document, visit_exprs(p, field.names, name_options))
+ }
+
+ if field.type != nil {
+ if len(field.names) != 0 {
+ document = cons(document, text(":"))
+ }
+ document = cons_with_opl(document, visit_expr(p, field.type))
+ } else {
+ document = cons(document, cons(text(":"), text("=")))
+ document = cons_with_opl(document, visit_expr(p, field.default_value))
+ }
+
+ if field.tag.text != "" {
+ document = cons_with_nopl(document, text_token(p, field.tag))
+ }
+
+ if (i != len(list.list) - 1 || .Trailing in options) && .Add_Comma in options {
+ document = cons(document, text(","))
+ }
+
+ if i != len(list.list) - 1 && .Enforce_Newline in options {
+ comment, ok := visit_comments(p, list.list[i+1].pos, false)
+ document = cons(document, cons(comment, newline(1)))
+ }
+ }
+ return document
+}
+
+visit_proc_type :: proc(p: ^Printer, proc_type: ast.Proc_Type) -> ^Document {
+
+ document := text("proc")
+
+ explicit_calling := false
+
+ if v, ok := proc_type.calling_convention.(string); ok {
+ explicit_calling = true
+ document = cons_with_opl(document, text(v))
+ }
+
+ if explicit_calling {
+ document = cons_with_opl(document, text("("))
+ } else {
+ document = cons(document, text("("))
+ }
+
+ document = cons(document, nest(p.indentation_count, cons(break_with(""), visit_signature_list(p, proc_type.params, false))))
+ document = group(cons(document, cons(break_with(""), text(")"))))
+
+ if proc_type.results != nil {
+ document = cons_with_nopl(document, text("-"))
+ document = cons(document, text(">"))
+
+ use_parens := false
+
+ if len(proc_type.results.list) > 1 {
+ use_parens = true
+ } else if len(proc_type.results.list) == 1 {
+
+ for name in proc_type.results.list[0].names {
+ if ident, ok := name.derived.(ast.Ident); ok {
+ if ident.name != "_" {
+ use_parens = true
+ }
+ }
+ }
+ }
+
+ if use_parens {
+ document = cons_with_nopl(document, text("("))
+ document = cons(document, nest(p.indentation_count, cons(break_with(""), visit_signature_list(p, proc_type.results))))
+ document = group(cons(document, cons(break_with(""), text(")"))))
+ } else {
+ document = cons_with_opl(document, nest(p.indentation_count, cons(break_with(""), visit_signature_list(p, proc_type.results))))
+ }
+ }
+
+ return document
+}
+
+visit_binary_expr :: proc(p: ^Printer, binary: ast.Binary_Expr) -> ^Document {
+ lhs: ^Document
+ rhs: ^Document
+
+ if v, ok := binary.left.derived.(ast.Binary_Expr); ok {
+ lhs = visit_binary_expr(p, v)
+ } else {
+ lhs = visit_expr(p, binary.left)
+ }
+
+ if v, ok := binary.right.derived.(ast.Binary_Expr); ok {
+ rhs = visit_binary_expr(p, v)
+ } else {
+ rhs = visit_expr(p, binary.right)
+ }
+
+ op := text(binary.op.text)
+
+ return nest(0, cons_with_nopl(lhs, cons_with_opl(op, rhs)))
+}
+
+visit_call_exprs :: proc(p: ^Printer, list: []^ast.Expr, ellipsis := false) -> ^Document {
+ document := empty()
+
+ for expr, i in list {
+ if i == len(list) - 1 && ellipsis {
+ document = cons(document, text("..."))
+ }
+ document = cons(document, visit_expr(p, expr))
+
+ if i != len(list) - 1 {
+ document = cons(document, text(","))
+ document = cons(document, break_with_space())
+ }
+
+ }
+ return document
+}
+
+visit_signature_list :: proc(p: ^Printer, list: ^ast.Field_List, remove_blank := true) -> ^Document {
+ document := empty()
+
+ for field, i in list.list {
+
+ if .Using in field.flags {
+ document = cons(document, cons(text("using"), break_with_no_newline()))
+ }
+
+ named := false
+
+ for name in field.names {
+ if ident, ok := name.derived.(ast.Ident); ok {
+ //for some reason the parser uses _ to mean empty
+ if ident.name != "_" || !remove_blank {
+ named = true
+ }
+ } else {
+ //alternative is poly names
+ named = true
+ }
+ }
+
+ if named {
+ document = cons(document, visit_exprs(p, field.names, {.Add_Comma}))
+
+ if len(field.names) != 0 && field.type != nil {
+ document = cons(document, cons(text(":"), break_with_no_newline()))
+ }
+ }
+
+ if field.type != nil && field.default_value != nil {
+ document = cons(document, visit_expr(p, field.type))
+ document = cons_with_nopl(document, text("="))
+ document = cons_with_nopl(document, visit_expr(p, field.default_value))
+ } else if field.type != nil {
+ document = cons(document, visit_expr(p, field.type))
+ } else {
+ document = cons_with_nopl(document, text(":"))
+ document = cons(document, text("="))
+ document = cons_with_nopl(document, visit_expr(p, field.default_value))
+ }
+
+ if i != len(list.list) - 1 {
+ document = cons(document, cons(text(","), break_with_space()))
+ } else {
+ document = cons(document, if_break(","))
+ }
+ }
+
+
+ return document
+}
diff --git a/src/server/format.odin b/src/server/format.odin
index 82857dc..6eb83de 100644
--- a/src/server/format.odin
+++ b/src/server/format.odin
@@ -2,7 +2,7 @@ package server
import "shared:common"
-import "core:odin/printer"
+import "shared:odin/printer"
FormattingOptions :: struct {
tabSize: uint,
@@ -22,8 +22,13 @@ TextEdit :: struct {
newText: string,
}
-get_complete_format :: proc(document: ^common.Document) -> ([]TextEdit, bool) {
- prnt := printer.make_printer(printer.default_style, context.temp_allocator);
+get_complete_format :: proc(document: ^common.Document, config: ^common.Config) -> ([]TextEdit, bool) {
+
+ style := printer.default_style;
+ style.max_characters = config.formatter.characters;
+ style.tabs = config.formatter.tabs;
+
+ prnt := printer.make_printer(style, context.temp_allocator);
if document.ast.syntax_error_count > 0 {
return {}, true;
diff --git a/src/server/requests.odin b/src/server/requests.odin
index 1d48887..f78053e 100644
--- a/src/server/requests.odin
+++ b/src/server/requests.odin
@@ -399,17 +399,24 @@ request_initialize :: proc (task: ^common.Task) {
if value, err := json.parse(data = data, allocator = context.temp_allocator, parse_integers = true); err == .None {
- ols_config: OlsConfig;
+ ols_config := OlsConfig {
+ formatter = {
+ characters = 90,
+ tabs = true,
+ },
+ }
if unmarshal(value, ols_config, context.temp_allocator) == .None {
config.thread_count = ols_config.thread_pool_count;
config.enable_document_symbols = ols_config.enable_document_symbols;
config.enable_hover = ols_config.enable_hover;
- config.enable_format = ols_config.enable_format;
+ config.enable_format = true // ols_config.enable_format;
config.enable_semantic_tokens = ols_config.enable_semantic_tokens;
config.enable_procedure_context = ols_config.enable_procedure_context;
config.verbose = ols_config.verbose;
+ config.file_log = ols_config.file_log;
+ config.formatter = ols_config.formatter;
for p in ols_config.collections {
@@ -723,7 +730,7 @@ request_format_document :: proc (task: ^common.Task) {
}
edit: []TextEdit;
- edit, ok = get_complete_format(document);
+ edit, ok = get_complete_format(document, config);
if !ok {
handle_error(.InternalError, id, writer);
@@ -913,6 +920,8 @@ notification_did_save :: proc (task: ^common.Task) {
if ret := index.collect_symbols(&index.indexer.dynamic_index.collection, file, uri.uri); ret != .None {
log.errorf("failed to collect symbols on save %v", ret);
}
+
+ check(uri, writer);
}
request_semantic_token_full :: proc (task: ^common.Task) {
diff --git a/src/server/types.odin b/src/server/types.odin
index 03da6f4..53a54b7 100644
--- a/src/server/types.odin
+++ b/src/server/types.odin
@@ -269,6 +269,8 @@ OlsConfig :: struct {
enable_format: bool,
enable_procedure_context: bool,
verbose: bool,
+ file_log: bool,
+ formatter: common.Format_Config,
}
OlsConfigCollection :: struct {
@@ -327,4 +329,4 @@ Command :: struct {
title: string,
command: string,
arguments: []string,
-}
+} \ No newline at end of file