diff options
| author | Brad Lewis <22850972+BradLewis@users.noreply.github.com> | 2025-08-24 10:19:02 -0400 |
|---|---|---|
| committer | Brad Lewis <22850972+BradLewis@users.noreply.github.com> | 2025-08-24 15:31:47 -0400 |
| commit | 9949af4debee31d6bc182056aa8436bbfefe8be2 (patch) | |
| tree | 880e169a70369a357d1ab60fbc9263703ef279e9 /src/server | |
| parent | db53fc4924383e41b1631ef35a80377446a99c4e (diff) | |
Move position context code into it's own file
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/analysis.odin | 844 | ||||
| -rw-r--r-- | src/server/locals.odin | 13 | ||||
| -rw-r--r-- | src/server/position_context.odin | 842 |
3 files changed, 855 insertions, 844 deletions
diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 053273a..adf7a8b 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -5,7 +5,6 @@ import "core:fmt" import "core:log" import "core:mem" import "core:odin/ast" -import "core:odin/parser" import "core:odin/tokenizer" import "core:path/filepath" import path "core:path/slashpath" @@ -18,72 +17,8 @@ import "core:unicode/utf8" import "src:common" -DocumentPositionContextHint :: enum { - Completion, - SignatureHelp, - Definition, - Hover, -} - -DocumentPositionContext :: struct { - file: ast.File, - position: common.AbsolutePosition, - nested_position: common.AbsolutePosition, //When doing the non-mutable local gathering we still need to know where in the nested block the position is. - line: int, - function: ^ast.Proc_Lit, //used to help with type resolving in function scope - functions: [dynamic]^ast.Proc_Lit, //stores all the functions that have been iterated through to find the position - selector: ^ast.Expr, //used for completion - selector_expr: ^ast.Node, - identifier: ^ast.Node, - label: ^ast.Ident, - implicit_context: ^ast.Implicit, - index: ^ast.Index_Expr, - previous_index: ^ast.Index_Expr, - tag: ^ast.Node, - field: ^ast.Expr, //used for completion - call: ^ast.Expr, //used for signature help - returns: ^ast.Return_Stmt, //used for completion - comp_lit: ^ast.Comp_Lit, //used for completion - parent_comp_lit: ^ast.Comp_Lit, //used for completion - basic_lit: ^ast.Basic_Lit, - struct_type: ^ast.Struct_Type, - union_type: ^ast.Union_Type, - bitset_type: ^ast.Bit_Set_Type, - enum_type: ^ast.Enum_Type, - field_value: ^ast.Field_Value, - bit_field_type: ^ast.Bit_Field_Type, - implicit: bool, //used for completion - arrow: bool, - binary: ^ast.Binary_Expr, //used for completion - parent_binary: ^ast.Binary_Expr, //used for completion - assign: ^ast.Assign_Stmt, //used for completion - switch_stmt: ^ast.Switch_Stmt, //used for completion - switch_type_stmt: ^ast.Type_Switch_Stmt, //used for completion - case_clause: ^ast.Case_Clause, //used for completion - value_decl: ^ast.Value_Decl, //used for completion - implicit_selector_expr: ^ast.Implicit_Selector_Expr, - abort_completion: bool, - hint: DocumentPositionContextHint, - global_lhs_stmt: bool, - import_stmt: ^ast.Import_Decl, - call_commas: []int, -} - -DocumentLocal :: struct { - lhs: ^ast.Expr, - rhs: ^ast.Expr, - offset: int, - resolved_global: bool, //Some locals have already been resolved and are now in global space - local_global: bool, //Some locals act like globals, i.e. functions defined inside functions. - pkg: string, - variable: bool, - parameter: bool, -} - DeferredDepth :: 35 -LocalGroup :: map[string][dynamic]DocumentLocal - UsingStatement :: struct { alias: string, pkg_name: string, @@ -3715,782 +3650,3 @@ type_to_string :: proc(ast_context: ^AstContext, expr: ^ast.Expr) -> string { return node_to_string(expr) } - -get_document_position_decls :: proc(decls: []^ast.Stmt, position_context: ^DocumentPositionContext) -> bool { - exists_in_decl := false - for decl in decls { - if position_in_node(decl, position_context.position) { - get_document_position(decl, position_context) - exists_in_decl = true - #partial switch v in decl.derived { - case ^ast.Expr_Stmt: - position_context.global_lhs_stmt = true - } - break - } - } - return exists_in_decl -} - -/* - Figure out what exactly is at the given position and whether it is in a function, struct, etc. -*/ -get_document_position_context :: proc( - document: ^Document, - position: common.Position, - hint: DocumentPositionContextHint, -) -> ( - DocumentPositionContext, - bool, -) { - position_context: DocumentPositionContext - - position_context.hint = hint - position_context.file = document.ast - position_context.line = position.line - - position_context.functions = make([dynamic]^ast.Proc_Lit, context.temp_allocator) - - absolute_position, ok := common.get_absolute_position(position, document.text) - - if !ok { - log.error("failed to get absolute position") - return position_context, false - } - - position_context.position = absolute_position - - exists_in_decl := get_document_position_decls(document.ast.decls[:], &position_context) - - for import_stmt in document.ast.imports { - if position_in_node(import_stmt, position_context.position) { - position_context.import_stmt = import_stmt - break - } - } - - if !exists_in_decl && position_context.import_stmt == nil { - position_context.abort_completion = true - } - - if !position_in_node(position_context.comp_lit, position_context.position) { - position_context.comp_lit = nil - } - - if !position_in_node(position_context.parent_comp_lit, position_context.position) { - position_context.parent_comp_lit = nil - } - - if !position_in_node(position_context.assign, position_context.position) { - position_context.assign = nil - } - - if !position_in_node(position_context.binary, position_context.position) { - position_context.binary = nil - } - - if !position_in_node(position_context.parent_binary, position_context.position) { - position_context.parent_binary = nil - } - - if hint == .Completion && position_context.selector == nil && position_context.field == nil { - fallback_position_context_completion(document, position, &position_context) - } - - if (hint == .SignatureHelp || hint == .Completion) && position_context.call == nil { - fallback_position_context_signature(document, position, &position_context) - } - - if hint == .SignatureHelp { - get_call_commas(&position_context, document) - } - - return position_context, true -} - -//terrible fallback code -fallback_position_context_completion :: proc( - document: ^Document, - position: common.Position, - position_context: ^DocumentPositionContext, -) { - paren_count: int - bracket_count: int - end: int - start: int - empty_dot: bool - empty_arrow: bool - last_dot: bool - last_arrow: bool - dots_seen: int - partial_arrow: bool - - i := position_context.position - 1 - - end = i - - for i > 0 { - c := position_context.file.src[i] - - if c == '(' && paren_count == 0 { - start = i + 1 - break - } else if c == '[' && bracket_count == 0 { - start = i + 1 - break - } else if c == ']' && !last_dot && !last_arrow { - start = i + 1 - break - } else if c == ')' && !last_dot && !last_arrow { - start = i + 1 - break - } else if c == ')' { - paren_count -= 1 - } else if c == '(' { - paren_count += 1 - } else if c == '[' { - bracket_count += 1 - } else if c == ']' { - bracket_count -= 1 - } else if c == '.' { - dots_seen += 1 - last_dot = true - i -= 1 - continue - } else if position_context.file.src[max(0, i - 1)] == '-' && c == '>' { - last_arrow = true - i -= 2 - continue - } - - //ignore everything in the bracket - if bracket_count != 0 || paren_count != 0 { - i -= 1 - continue - } - - //yeah.. - if c == ' ' || - c == '{' || - c == ',' || - c == '}' || - c == '^' || - c == ':' || - c == '\n' || - c == '\r' || - c == '\t' || - c == '=' || - c == '<' || - c == '-' || - c == '!' || - c == '+' || - c == '&' || - c == '|' { - start = i + 1 - break - } else if c == '>' { - partial_arrow = true - } - - last_dot = false - last_arrow = false - - i -= 1 - } - - if i >= 0 && position_context.file.src[end] == '.' { - empty_dot = true - end -= 1 - } else if i >= 0 && position_context.file.src[max(0, end - 1)] == '-' && position_context.file.src[end] == '>' { - empty_arrow = true - end -= 2 - position_context.arrow = true - } - - begin_offset := max(0, start) - end_offset := max(start, end + 1) - line_offset := begin_offset - - if line_offset < len(position_context.file.src) { - for line_offset > 0 { - c := position_context.file.src[line_offset] - if c == '\n' || c == '\r' { - line_offset += 1 - break - } - line_offset -= 1 - } - } - - str := position_context.file.src[0:end_offset] - - if empty_dot && end_offset - begin_offset == 0 { - position_context.implicit = true - return - } - - s := string(position_context.file.src[begin_offset:end_offset]) - - if !partial_arrow { - only_whitespaces := true - - for r in s { - if !strings.is_space(r) { - only_whitespaces = false - } - } - - if only_whitespaces { - return - } - } - - p := parser.Parser { - err = common.parser_warning_handler, //empty - warn = common.parser_warning_handler, //empty - flags = {.Optional_Semicolons}, - file = &position_context.file, - } - - tokenizer.init(&p.tok, str, position_context.file.fullpath, common.parser_warning_handler) - - p.tok.ch = ' ' - p.tok.line_count = position.line + 1 - p.tok.line_offset = line_offset - p.tok.offset = begin_offset - p.tok.read_offset = begin_offset - - tokenizer.advance_rune(&p.tok) - - if p.tok.ch == utf8.RUNE_BOM { - tokenizer.advance_rune(&p.tok) - } - - parser.advance_token(&p) - - context.allocator = context.temp_allocator - - e := parser.parse_expr(&p, true) - - if empty_dot || empty_arrow { - position_context.selector = e - } else if s, ok := e.derived.(^ast.Selector_Expr); ok { - position_context.selector = s.expr - position_context.field = s.field - } else if s, ok := e.derived.(^ast.Implicit_Selector_Expr); ok { - position_context.implicit = true - position_context.implicit_selector_expr = s - } else if s, ok := e.derived.(^ast.Tag_Expr); ok { - position_context.tag = s.expr - } else if bad_expr, ok := e.derived.(^ast.Bad_Expr); ok { - //this is most likely because of use of 'in', 'context', etc. - //try to go back one dot. - - src_with_dot := string(position_context.file.src[0:min(len(position_context.file.src), end_offset + 1)]) - last_dot := strings.last_index(src_with_dot, ".") - - if last_dot == -1 { - return - } - - tokenizer.init( - &p.tok, - position_context.file.src[0:last_dot], - position_context.file.fullpath, - common.parser_warning_handler, - ) - - p.tok.ch = ' ' - p.tok.line_count = position.line + 1 - p.tok.line_offset = line_offset - p.tok.offset = begin_offset - p.tok.read_offset = begin_offset - - tokenizer.advance_rune(&p.tok) - - if p.tok.ch == utf8.RUNE_BOM { - tokenizer.advance_rune(&p.tok) - } - - parser.advance_token(&p) - - e := parser.parse_expr(&p, true) - - if e == nil { - position_context.abort_completion = true - return - } else if e, ok := e.derived.(^ast.Bad_Expr); ok { - position_context.abort_completion = true - return - } - - position_context.selector = e - - ident := new_type(ast.Ident, e.pos, e.end, context.temp_allocator) - ident.name = string(position_context.file.src[last_dot + 1:end_offset]) - - if ident.name != "" { - position_context.field = ident - } - } else { - position_context.identifier = e - } -} - -fallback_position_context_signature :: proc( - document: ^Document, - position: common.Position, - position_context: ^DocumentPositionContext, -) { - end: int - start: int - i := position_context.position - 1 - end = i - - for i > 0 { - - c := position_context.file.src[i] - - if c == ' ' || c == '\n' || c == '\r' { - start = i + 1 - break - } - - i -= 1 - } - - if end < 0 { - return - } - - if position_context.file.src[end] != '(' { - return - } - - end -= 1 - - begin_offset := max(0, start) - end_offset := max(start, end + 1) - - if end_offset - begin_offset <= 1 { - return - } - - str := position_context.file.src[0:end_offset] - - p := parser.Parser { - err = common.parser_warning_handler, //empty - warn = common.parser_warning_handler, //empty - file = &position_context.file, - } - - tokenizer.init(&p.tok, str, position_context.file.fullpath, common.parser_warning_handler) - - p.tok.ch = ' ' - p.tok.line_count = position.line - p.tok.offset = begin_offset - p.tok.read_offset = begin_offset - - tokenizer.advance_rune(&p.tok) - - if p.tok.ch == utf8.RUNE_BOM { - tokenizer.advance_rune(&p.tok) - } - - parser.advance_token(&p) - - context.allocator = context.temp_allocator - - position_context.call = parser.parse_expr(&p, true) - - if _, ok := position_context.call.derived.(^ast.Proc_Type); ok { - position_context.call = nil - } - - //log.error(string(position_context.file.src[begin_offset:end_offset])); -} - -// Used to find which sub-expr is desired by the position. -// Eg. for map[Key]Value, do we want 'map', 'Key' or 'Value' -get_desired_expr :: proc(node: ^ast.Expr, position: common.AbsolutePosition) -> ^ast.Expr { - #partial switch n in node.derived { - case ^ast.Array_Type: - if position_in_node(n.tag, position) { - return n.tag - } - if position_in_node(n.elem, position) { - return n.elem - } - if position_in_node(n.len, position) { - return n.len - } - case ^ast.Map_Type: - if position_in_node(n.key, position) { - return n.key - } - if position_in_node(n.value, position) { - return n.key - } - case ^ast.Dynamic_Array_Type: - if position_in_node(n.tag, position) { - return n.tag - } - if position_in_node(n.elem, position) { - return n.elem - } - case ^ast.Bit_Set_Type: - if position_in_node(n.elem, position) { - return n.elem - } - } - - return node -} - -/* - All these fallback functions are not perfect and should be fixed. A lot of weird use of the odin tokenizer and parser. -*/ - -get_document_position :: proc { - get_document_position_array, - get_document_position_dynamic_array, - get_document_position_node, -} - -get_document_position_array :: proc(array: $A/[]^$T, position_context: ^DocumentPositionContext) { - for elem, i in array { - get_document_position(elem, position_context) - } -} - -get_document_position_dynamic_array :: proc(array: $A/[dynamic]^$T, position_context: ^DocumentPositionContext) { - for elem, i in array { - get_document_position(elem, position_context) - } -} - -position_in_node :: proc(node: ^ast.Node, position: common.AbsolutePosition) -> bool { - return node != nil && node.pos.offset <= position && position <= node.end.offset -} - -position_in_exprs :: proc(nodes: []^ast.Expr, position: common.AbsolutePosition) -> bool { - for node in nodes { - if node != nil && node.pos.offset <= position && position <= node.end.offset { - return true - } - } - - return false -} - -get_document_position_label :: proc(label: ^ast.Expr, position_context: ^DocumentPositionContext) { - if label == nil { - return - } - - if ident, ok := label.derived.(^ast.Ident); ok { - position_context.label = ident - } -} - -get_document_position_node :: proc(node: ^ast.Node, position_context: ^DocumentPositionContext) { - using ast - - if node == nil { - return - } - - if !position_in_node(node, position_context.position) { - return - } - - #partial switch n in node.derived { - case ^Bad_Expr: - case ^Ident: - position_context.identifier = node - case ^Implicit: - if n.tok.text == "context" { - position_context.implicit_context = n - } - case ^Undef: - case ^Basic_Lit: - position_context.basic_lit = cast(^Basic_Lit)node - case ^Matrix_Index_Expr: - get_document_position(n.expr, position_context) - get_document_position(n.row_index, position_context) - get_document_position(n.column_index, position_context) - case ^Matrix_Type: - get_document_position(n.row_count, position_context) - get_document_position(n.column_count, position_context) - get_document_position(n.elem, position_context) - case ^Ellipsis: - get_document_position(n.expr, position_context) - case ^Proc_Lit: - if position_in_node(n.body, position_context.position) { - get_document_position(n.type, position_context) - position_context.function = cast(^Proc_Lit)node - append(&position_context.functions, position_context.function) - get_document_position(n.body, position_context) - } else if position_in_node(n.type, position_context.position) { - position_context.function = cast(^Proc_Lit)node - get_document_position(n.type, position_context) - } else { - for clause in n.where_clauses { - if position_in_node(clause, position_context.position) { - position_context.function = cast(^Proc_Lit)node - get_document_position(clause, position_context) - } - } - } - case ^Comp_Lit: - //only set this for the parent comp literal, since we will need to walk through it to infer types. - if position_context.parent_comp_lit == nil { - position_context.parent_comp_lit = cast(^Comp_Lit)node - } - - position_context.comp_lit = cast(^Comp_Lit)node - - get_document_position(n.type, position_context) - get_document_position(n.elems, position_context) - case ^Tag_Expr: - get_document_position(n.expr, position_context) - case ^Unary_Expr: - get_document_position(n.expr, position_context) - case ^Binary_Expr: - if position_context.parent_binary == nil { - position_context.parent_binary = n - } - position_context.binary = n - get_document_position(n.left, position_context) - get_document_position(n.right, position_context) - case ^Paren_Expr: - get_document_position(n.expr, position_context) - case ^Call_Expr: - position_context.call = n - get_document_position(n.expr, position_context) - get_document_position(n.args, position_context) - case ^Selector_Call_Expr: - if position_context.hint == .Definition || - position_context.hint == .Hover || - position_context.hint == .SignatureHelp || - position_context.hint == .Completion { - position_context.selector = n.expr - position_context.field = n.call - position_context.selector_expr = node - - if _, ok := n.call.derived.(^ast.Call_Expr); ok { - position_context.call = n.call - } - - get_document_position(n.expr, position_context) - get_document_position(n.call, position_context) - - if position_context.hint == .SignatureHelp { - position_context.arrow = true - } - } - case ^Selector_Expr: - if position_context.hint == .Definition || position_context.hint == .Hover && n.field != nil { - position_context.selector = n.expr - position_context.field = n.field - position_context.selector_expr = node - get_document_position(n.expr, position_context) - get_document_position(n.field, position_context) - } else { - get_document_position(n.expr, position_context) - get_document_position(n.field, position_context) - } - case ^Index_Expr: - position_context.previous_index = position_context.index - position_context.index = n - get_document_position(n.expr, position_context) - get_document_position(n.index, position_context) - case ^Deref_Expr: - get_document_position(n.expr, position_context) - case ^Slice_Expr: - get_document_position(n.expr, position_context) - get_document_position(n.low, position_context) - get_document_position(n.high, position_context) - case ^Field_Value: - position_context.field_value = n - get_document_position(n.field, position_context) - get_document_position(n.value, position_context) - case ^Ternary_If_Expr: - get_document_position(n.x, position_context) - get_document_position(n.cond, position_context) - get_document_position(n.y, position_context) - case ^Ternary_When_Expr: - get_document_position(n.x, position_context) - get_document_position(n.cond, position_context) - get_document_position(n.y, position_context) - case ^Type_Assertion: - get_document_position(n.expr, position_context) - get_document_position(n.type, position_context) - case ^Type_Cast: - get_document_position(n.type, position_context) - get_document_position(n.expr, position_context) - case ^Auto_Cast: - get_document_position(n.expr, position_context) - case ^Bad_Stmt: - case ^Empty_Stmt: - case ^Expr_Stmt: - get_document_position(n.expr, position_context) - case ^Tag_Stmt: - r := n - get_document_position(r.stmt, position_context) - case ^Assign_Stmt: - position_context.assign = n - get_document_position(n.lhs, position_context) - get_document_position(n.rhs, position_context) - case ^Block_Stmt: - get_document_position_label(n.label, position_context) - get_document_position(n.stmts, position_context) - case ^If_Stmt: - get_document_position_label(n.label, position_context) - get_document_position(n.init, position_context) - get_document_position(n.cond, position_context) - get_document_position(n.body, position_context) - get_document_position(n.else_stmt, position_context) - case ^When_Stmt: - get_document_position(n.cond, position_context) - get_document_position(n.body, position_context) - get_document_position(n.else_stmt, position_context) - case ^Return_Stmt: - position_context.returns = n - get_document_position(n.results, position_context) - case ^Defer_Stmt: - get_document_position(n.stmt, position_context) - case ^For_Stmt: - get_document_position_label(n.label, position_context) - get_document_position(n.init, position_context) - get_document_position(n.cond, position_context) - get_document_position(n.post, position_context) - get_document_position(n.body, position_context) - case ^Range_Stmt: - get_document_position_label(n.label, position_context) - get_document_position(n.vals, position_context) - get_document_position(n.expr, position_context) - get_document_position(n.body, position_context) - case ^Case_Clause: - for elem in n.list { - if position_in_node(elem, position_context.position) { - position_context.case_clause = cast(^Case_Clause)node - break - } - } - - get_document_position(n.list, position_context) - get_document_position(n.body, position_context) - case ^Switch_Stmt: - position_context.switch_stmt = cast(^Switch_Stmt)node - get_document_position_label(n.label, position_context) - get_document_position(n.init, position_context) - get_document_position(n.cond, position_context) - get_document_position(n.body, position_context) - case ^Type_Switch_Stmt: - position_context.switch_type_stmt = cast(^Type_Switch_Stmt)node - get_document_position_label(n.label, position_context) - get_document_position(n.tag, position_context) - get_document_position(n.expr, position_context) - get_document_position(n.body, position_context) - case ^Branch_Stmt: - get_document_position_label(n.label, position_context) - case ^Using_Stmt: - get_document_position(n.list, position_context) - case ^Bad_Decl: - case ^Value_Decl: - position_context.value_decl = cast(^Value_Decl)node - get_document_position(n.attributes, position_context) - - for name in n.names { - if position_in_node(name, position_context.position) && n.end.line - 1 == position_context.line { - position_context.abort_completion = true - break - } - } - get_document_position(n.names, position_context) - get_document_position(n.type, position_context) - get_document_position(n.values, position_context) - case ^Package_Decl: - case ^Import_Decl: - case ^Foreign_Block_Decl: - get_document_position(n.attributes, position_context) - get_document_position(n.foreign_library, position_context) - get_document_position(n.body, position_context) - case ^Foreign_Import_Decl: - get_document_position(n.name, position_context) - case ^Proc_Group: - get_document_position(n.args, position_context) - case ^Attribute: - get_document_position(n.elems, position_context) - case ^Field: - get_document_position(n.names, position_context) - get_document_position(n.type, position_context) - get_document_position(n.default_value, position_context) - case ^Field_List: - get_document_position(n.list, position_context) - case ^Typeid_Type: - get_document_position(n.specialization, position_context) - case ^Helper_Type: - get_document_position(n.type, position_context) - case ^Distinct_Type: - get_document_position(n.type, position_context) - case ^Poly_Type: - get_document_position(n.type, position_context) - get_document_position(n.specialization, position_context) - case ^Proc_Type: - get_document_position(n.params, position_context) - get_document_position(n.results, position_context) - case ^Pointer_Type: - get_document_position(n.elem, position_context) - case ^Array_Type: - get_document_position(n.len, position_context) - get_document_position(n.elem, position_context) - case ^Dynamic_Array_Type: - get_document_position(n.elem, position_context) - case ^Multi_Pointer_Type: - get_document_position(n.elem, position_context) - case ^Struct_Type: - position_context.struct_type = n - get_document_position(n.poly_params, position_context) - get_document_position(n.align, position_context) - get_document_position(n.fields, position_context) - case ^Union_Type: - position_context.union_type = n - get_document_position(n.poly_params, position_context) - get_document_position(n.align, position_context) - get_document_position(n.variants, position_context) - case ^Enum_Type: - position_context.enum_type = n - get_document_position(n.base_type, position_context) - get_document_position(n.fields, position_context) - case ^Bit_Set_Type: - position_context.bitset_type = n - get_document_position(n.elem, position_context) - get_document_position(n.underlying, position_context) - case ^Map_Type: - get_document_position(n.key, position_context) - get_document_position(n.value, position_context) - case ^Implicit_Selector_Expr: - position_context.implicit = true - position_context.implicit_selector_expr = n - get_document_position(n.field, position_context) - case ^Or_Else_Expr: - get_document_position(n.x, position_context) - get_document_position(n.y, position_context) - case ^Or_Return_Expr: - get_document_position(n.expr, position_context) - case ^Or_Branch_Expr: - get_document_position_label(n.label, position_context) - get_document_position(n.expr, position_context) - case ^Bit_Field_Type: - position_context.bit_field_type = n - get_document_position(n.backing_type, position_context) - get_document_position(n.fields, position_context) - case ^Bit_Field_Field: - get_document_position(n.name, position_context) - get_document_position(n.type, position_context) - get_document_position(n.bit_size, position_context) - case: - } -} diff --git a/src/server/locals.odin b/src/server/locals.odin index 38b52c0..c913ab1 100644 --- a/src/server/locals.odin +++ b/src/server/locals.odin @@ -4,6 +4,19 @@ import "core:fmt" import "core:log" import "core:odin/ast" +DocumentLocal :: struct { + lhs: ^ast.Expr, + rhs: ^ast.Expr, + offset: int, + resolved_global: bool, //Some locals have already been resolved and are now in global space + local_global: bool, //Some locals act like globals, i.e. functions defined inside functions. + pkg: string, + variable: bool, + parameter: bool, +} + +LocalGroup :: map[string][dynamic]DocumentLocal + store_local :: proc( ast_context: ^AstContext, lhs: ^ast.Expr, diff --git a/src/server/position_context.odin b/src/server/position_context.odin new file mode 100644 index 0000000..aad3d0e --- /dev/null +++ b/src/server/position_context.odin @@ -0,0 +1,842 @@ +package server + +import "core:log" +import "core:odin/ast" +import "core:odin/parser" +import "core:odin/tokenizer" +import "core:strings" +import "core:unicode/utf8" + +import "src:common" + +DocumentPositionContextHint :: enum { + Completion, + SignatureHelp, + Definition, + Hover, +} + +DocumentPositionContext :: struct { + file: ast.File, + position: common.AbsolutePosition, + nested_position: common.AbsolutePosition, //When doing the non-mutable local gathering we still need to know where in the nested block the position is. + line: int, + function: ^ast.Proc_Lit, //used to help with type resolving in function scope + functions: [dynamic]^ast.Proc_Lit, //stores all the functions that have been iterated through to find the position + selector: ^ast.Expr, //used for completion + selector_expr: ^ast.Node, + identifier: ^ast.Node, + label: ^ast.Ident, + implicit_context: ^ast.Implicit, + index: ^ast.Index_Expr, + previous_index: ^ast.Index_Expr, + tag: ^ast.Node, + field: ^ast.Expr, //used for completion + call: ^ast.Expr, //used for signature help + returns: ^ast.Return_Stmt, //used for completion + comp_lit: ^ast.Comp_Lit, //used for completion + parent_comp_lit: ^ast.Comp_Lit, //used for completion + basic_lit: ^ast.Basic_Lit, + struct_type: ^ast.Struct_Type, + union_type: ^ast.Union_Type, + bitset_type: ^ast.Bit_Set_Type, + enum_type: ^ast.Enum_Type, + field_value: ^ast.Field_Value, + bit_field_type: ^ast.Bit_Field_Type, + implicit: bool, //used for completion + arrow: bool, + binary: ^ast.Binary_Expr, //used for completion + parent_binary: ^ast.Binary_Expr, //used for completion + assign: ^ast.Assign_Stmt, //used for completion + switch_stmt: ^ast.Switch_Stmt, //used for completion + switch_type_stmt: ^ast.Type_Switch_Stmt, //used for completion + case_clause: ^ast.Case_Clause, //used for completion + value_decl: ^ast.Value_Decl, //used for completion + implicit_selector_expr: ^ast.Implicit_Selector_Expr, + abort_completion: bool, + hint: DocumentPositionContextHint, + global_lhs_stmt: bool, + import_stmt: ^ast.Import_Decl, + type_cast: ^ast.Type_Cast, + call_commas: []int, +} + +get_document_position_decls :: proc(decls: []^ast.Stmt, position_context: ^DocumentPositionContext) -> bool { + exists_in_decl := false + for decl in decls { + if position_in_node(decl, position_context.position) { + get_document_position(decl, position_context) + exists_in_decl = true + #partial switch v in decl.derived { + case ^ast.Expr_Stmt: + position_context.global_lhs_stmt = true + } + break + } + } + return exists_in_decl +} + +/* + Figure out what exactly is at the given position and whether it is in a function, struct, etc. +*/ +get_document_position_context :: proc( + document: ^Document, + position: common.Position, + hint: DocumentPositionContextHint, +) -> ( + DocumentPositionContext, + bool, +) { + position_context: DocumentPositionContext + + position_context.hint = hint + position_context.file = document.ast + position_context.line = position.line + + position_context.functions = make([dynamic]^ast.Proc_Lit, context.temp_allocator) + + absolute_position, ok := common.get_absolute_position(position, document.text) + + if !ok { + log.error("failed to get absolute position") + return position_context, false + } + + position_context.position = absolute_position + + exists_in_decl := get_document_position_decls(document.ast.decls[:], &position_context) + + for import_stmt in document.ast.imports { + if position_in_node(import_stmt, position_context.position) { + position_context.import_stmt = import_stmt + break + } + } + + if !exists_in_decl && position_context.import_stmt == nil { + position_context.abort_completion = true + } + + if !position_in_node(position_context.comp_lit, position_context.position) { + position_context.comp_lit = nil + } + + if !position_in_node(position_context.parent_comp_lit, position_context.position) { + position_context.parent_comp_lit = nil + } + + if !position_in_node(position_context.assign, position_context.position) { + position_context.assign = nil + } + + if !position_in_node(position_context.binary, position_context.position) { + position_context.binary = nil + } + + if !position_in_node(position_context.parent_binary, position_context.position) { + position_context.parent_binary = nil + } + + if hint == .Completion && position_context.selector == nil && position_context.field == nil { + fallback_position_context_completion(document, position, &position_context) + } + + if (hint == .SignatureHelp || hint == .Completion) && position_context.call == nil { + fallback_position_context_signature(document, position, &position_context) + } + + if hint == .SignatureHelp { + get_call_commas(&position_context, document) + } + + return position_context, true +} + +//terrible fallback code +fallback_position_context_completion :: proc( + document: ^Document, + position: common.Position, + position_context: ^DocumentPositionContext, +) { + paren_count: int + bracket_count: int + end: int + start: int + empty_dot: bool + empty_arrow: bool + last_dot: bool + last_arrow: bool + dots_seen: int + partial_arrow: bool + + i := position_context.position - 1 + + end = i + + for i > 0 { + c := position_context.file.src[i] + + if c == '(' && paren_count == 0 { + start = i + 1 + break + } else if c == '[' && bracket_count == 0 { + start = i + 1 + break + } else if c == ']' && !last_dot && !last_arrow { + start = i + 1 + break + } else if c == ')' && !last_dot && !last_arrow { + start = i + 1 + break + } else if c == ')' { + paren_count -= 1 + } else if c == '(' { + paren_count += 1 + } else if c == '[' { + bracket_count += 1 + } else if c == ']' { + bracket_count -= 1 + } else if c == '.' { + dots_seen += 1 + last_dot = true + i -= 1 + continue + } else if position_context.file.src[max(0, i - 1)] == '-' && c == '>' { + last_arrow = true + i -= 2 + continue + } + + //ignore everything in the bracket + if bracket_count != 0 || paren_count != 0 { + i -= 1 + continue + } + + //yeah.. + if c == ' ' || + c == '{' || + c == ',' || + c == '}' || + c == '^' || + c == ':' || + c == '\n' || + c == '\r' || + c == '\t' || + c == '=' || + c == '<' || + c == '-' || + c == '!' || + c == '+' || + c == '&' || + c == '|' { + start = i + 1 + break + } else if c == '>' { + partial_arrow = true + } + + last_dot = false + last_arrow = false + + i -= 1 + } + + if i >= 0 && position_context.file.src[end] == '.' { + empty_dot = true + end -= 1 + } else if i >= 0 && position_context.file.src[max(0, end - 1)] == '-' && position_context.file.src[end] == '>' { + empty_arrow = true + end -= 2 + position_context.arrow = true + } + + begin_offset := max(0, start) + end_offset := max(start, end + 1) + line_offset := begin_offset + + if line_offset < len(position_context.file.src) { + for line_offset > 0 { + c := position_context.file.src[line_offset] + if c == '\n' || c == '\r' { + line_offset += 1 + break + } + line_offset -= 1 + } + } + + str := position_context.file.src[0:end_offset] + + if empty_dot && end_offset - begin_offset == 0 { + position_context.implicit = true + return + } + + s := string(position_context.file.src[begin_offset:end_offset]) + + if !partial_arrow { + only_whitespaces := true + + for r in s { + if !strings.is_space(r) { + only_whitespaces = false + } + } + + if only_whitespaces { + return + } + } + + p := parser.Parser { + err = common.parser_warning_handler, //empty + warn = common.parser_warning_handler, //empty + flags = {.Optional_Semicolons}, + file = &position_context.file, + } + + tokenizer.init(&p.tok, str, position_context.file.fullpath, common.parser_warning_handler) + + p.tok.ch = ' ' + p.tok.line_count = position.line + 1 + p.tok.line_offset = line_offset + p.tok.offset = begin_offset + p.tok.read_offset = begin_offset + + tokenizer.advance_rune(&p.tok) + + if p.tok.ch == utf8.RUNE_BOM { + tokenizer.advance_rune(&p.tok) + } + + parser.advance_token(&p) + + context.allocator = context.temp_allocator + + e := parser.parse_expr(&p, true) + + if empty_dot || empty_arrow { + position_context.selector = e + } else if s, ok := e.derived.(^ast.Selector_Expr); ok { + position_context.selector = s.expr + position_context.field = s.field + } else if s, ok := e.derived.(^ast.Implicit_Selector_Expr); ok { + position_context.implicit = true + position_context.implicit_selector_expr = s + } else if s, ok := e.derived.(^ast.Tag_Expr); ok { + position_context.tag = s.expr + } else if bad_expr, ok := e.derived.(^ast.Bad_Expr); ok { + //this is most likely because of use of 'in', 'context', etc. + //try to go back one dot. + + src_with_dot := string(position_context.file.src[0:min(len(position_context.file.src), end_offset + 1)]) + last_dot := strings.last_index(src_with_dot, ".") + + if last_dot == -1 { + return + } + + tokenizer.init( + &p.tok, + position_context.file.src[0:last_dot], + position_context.file.fullpath, + common.parser_warning_handler, + ) + + p.tok.ch = ' ' + p.tok.line_count = position.line + 1 + p.tok.line_offset = line_offset + p.tok.offset = begin_offset + p.tok.read_offset = begin_offset + + tokenizer.advance_rune(&p.tok) + + if p.tok.ch == utf8.RUNE_BOM { + tokenizer.advance_rune(&p.tok) + } + + parser.advance_token(&p) + + e := parser.parse_expr(&p, true) + + if e == nil { + position_context.abort_completion = true + return + } else if e, ok := e.derived.(^ast.Bad_Expr); ok { + position_context.abort_completion = true + return + } + + position_context.selector = e + + ident := new_type(ast.Ident, e.pos, e.end, context.temp_allocator) + ident.name = string(position_context.file.src[last_dot + 1:end_offset]) + + if ident.name != "" { + position_context.field = ident + } + } else { + position_context.identifier = e + } +} + +fallback_position_context_signature :: proc( + document: ^Document, + position: common.Position, + position_context: ^DocumentPositionContext, +) { + end: int + start: int + i := position_context.position - 1 + end = i + + for i > 0 { + + c := position_context.file.src[i] + + if c == ' ' || c == '\n' || c == '\r' { + start = i + 1 + break + } + + i -= 1 + } + + if end < 0 { + return + } + + if position_context.file.src[end] != '(' { + return + } + + end -= 1 + + begin_offset := max(0, start) + end_offset := max(start, end + 1) + + if end_offset - begin_offset <= 1 { + return + } + + str := position_context.file.src[0:end_offset] + + p := parser.Parser { + err = common.parser_warning_handler, //empty + warn = common.parser_warning_handler, //empty + file = &position_context.file, + } + + tokenizer.init(&p.tok, str, position_context.file.fullpath, common.parser_warning_handler) + + p.tok.ch = ' ' + p.tok.line_count = position.line + p.tok.offset = begin_offset + p.tok.read_offset = begin_offset + + tokenizer.advance_rune(&p.tok) + + if p.tok.ch == utf8.RUNE_BOM { + tokenizer.advance_rune(&p.tok) + } + + parser.advance_token(&p) + + context.allocator = context.temp_allocator + + position_context.call = parser.parse_expr(&p, true) + + if _, ok := position_context.call.derived.(^ast.Proc_Type); ok { + position_context.call = nil + } + + //log.error(string(position_context.file.src[begin_offset:end_offset])); +} + +// Used to find which sub-expr is desired by the position. +// Eg. for map[Key]Value, do we want 'map', 'Key' or 'Value' +get_desired_expr :: proc(node: ^ast.Expr, position: common.AbsolutePosition) -> ^ast.Expr { + #partial switch n in node.derived { + case ^ast.Array_Type: + if position_in_node(n.tag, position) { + return n.tag + } + if position_in_node(n.elem, position) { + return n.elem + } + if position_in_node(n.len, position) { + return n.len + } + case ^ast.Map_Type: + if position_in_node(n.key, position) { + return n.key + } + if position_in_node(n.value, position) { + return n.key + } + case ^ast.Dynamic_Array_Type: + if position_in_node(n.tag, position) { + return n.tag + } + if position_in_node(n.elem, position) { + return n.elem + } + case ^ast.Bit_Set_Type: + if position_in_node(n.elem, position) { + return n.elem + } + } + + return node +} + +/* + All these fallback functions are not perfect and should be fixed. A lot of weird use of the odin tokenizer and parser. +*/ + +get_document_position :: proc { + get_document_position_array, + get_document_position_dynamic_array, + get_document_position_node, +} + +get_document_position_array :: proc(array: $A/[]^$T, position_context: ^DocumentPositionContext) { + for elem, i in array { + get_document_position(elem, position_context) + } +} + +get_document_position_dynamic_array :: proc(array: $A/[dynamic]^$T, position_context: ^DocumentPositionContext) { + for elem, i in array { + get_document_position(elem, position_context) + } +} + +position_in_node :: proc(node: ^ast.Node, position: common.AbsolutePosition) -> bool { + return node != nil && node.pos.offset <= position && position <= node.end.offset +} + +position_in_exprs :: proc(nodes: []^ast.Expr, position: common.AbsolutePosition) -> bool { + for node in nodes { + if node != nil && node.pos.offset <= position && position <= node.end.offset { + return true + } + } + + return false +} + +get_document_position_label :: proc(label: ^ast.Expr, position_context: ^DocumentPositionContext) { + if label == nil { + return + } + + if ident, ok := label.derived.(^ast.Ident); ok { + position_context.label = ident + } +} + +get_document_position_node :: proc(node: ^ast.Node, position_context: ^DocumentPositionContext) { + using ast + + if node == nil { + return + } + + if !position_in_node(node, position_context.position) { + return + } + + #partial switch n in node.derived { + case ^Bad_Expr: + case ^Ident: + position_context.identifier = node + case ^Implicit: + if n.tok.text == "context" { + position_context.implicit_context = n + } + case ^Undef: + case ^Basic_Lit: + position_context.basic_lit = cast(^Basic_Lit)node + case ^Matrix_Index_Expr: + get_document_position(n.expr, position_context) + get_document_position(n.row_index, position_context) + get_document_position(n.column_index, position_context) + case ^Matrix_Type: + get_document_position(n.row_count, position_context) + get_document_position(n.column_count, position_context) + get_document_position(n.elem, position_context) + case ^Ellipsis: + get_document_position(n.expr, position_context) + case ^Proc_Lit: + if position_in_node(n.body, position_context.position) { + get_document_position(n.type, position_context) + position_context.function = cast(^Proc_Lit)node + append(&position_context.functions, position_context.function) + get_document_position(n.body, position_context) + } else if position_in_node(n.type, position_context.position) { + position_context.function = cast(^Proc_Lit)node + get_document_position(n.type, position_context) + } else { + for clause in n.where_clauses { + if position_in_node(clause, position_context.position) { + position_context.function = cast(^Proc_Lit)node + get_document_position(clause, position_context) + } + } + } + case ^Comp_Lit: + //only set this for the parent comp literal, since we will need to walk through it to infer types. + if position_context.parent_comp_lit == nil { + position_context.parent_comp_lit = cast(^Comp_Lit)node + } + + position_context.comp_lit = cast(^Comp_Lit)node + + get_document_position(n.type, position_context) + get_document_position(n.elems, position_context) + case ^Tag_Expr: + get_document_position(n.expr, position_context) + case ^Unary_Expr: + get_document_position(n.expr, position_context) + case ^Binary_Expr: + if position_context.parent_binary == nil { + position_context.parent_binary = n + } + position_context.binary = n + get_document_position(n.left, position_context) + get_document_position(n.right, position_context) + case ^Paren_Expr: + get_document_position(n.expr, position_context) + case ^Call_Expr: + position_context.call = n + get_document_position(n.expr, position_context) + get_document_position(n.args, position_context) + case ^Selector_Call_Expr: + if position_context.hint == .Definition || + position_context.hint == .Hover || + position_context.hint == .SignatureHelp || + position_context.hint == .Completion { + position_context.selector = n.expr + position_context.field = n.call + position_context.selector_expr = node + + if _, ok := n.call.derived.(^ast.Call_Expr); ok { + position_context.call = n.call + } + + get_document_position(n.expr, position_context) + get_document_position(n.call, position_context) + + if position_context.hint == .SignatureHelp { + position_context.arrow = true + } + } + case ^Selector_Expr: + if position_context.hint == .Definition || position_context.hint == .Hover && n.field != nil { + position_context.selector = n.expr + position_context.field = n.field + position_context.selector_expr = node + get_document_position(n.expr, position_context) + get_document_position(n.field, position_context) + } else { + get_document_position(n.expr, position_context) + get_document_position(n.field, position_context) + } + case ^Index_Expr: + position_context.previous_index = position_context.index + position_context.index = n + get_document_position(n.expr, position_context) + get_document_position(n.index, position_context) + case ^Deref_Expr: + get_document_position(n.expr, position_context) + case ^Slice_Expr: + get_document_position(n.expr, position_context) + get_document_position(n.low, position_context) + get_document_position(n.high, position_context) + case ^Field_Value: + position_context.field_value = n + get_document_position(n.field, position_context) + get_document_position(n.value, position_context) + case ^Ternary_If_Expr: + get_document_position(n.x, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.y, position_context) + case ^Ternary_When_Expr: + get_document_position(n.x, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.y, position_context) + case ^Type_Assertion: + get_document_position(n.expr, position_context) + get_document_position(n.type, position_context) + case ^Type_Cast: + position_context.type_cast = cast(^Type_Cast)node + get_document_position(n.type, position_context) + get_document_position(n.expr, position_context) + case ^Auto_Cast: + get_document_position(n.expr, position_context) + case ^Bad_Stmt: + case ^Empty_Stmt: + case ^Expr_Stmt: + get_document_position(n.expr, position_context) + case ^Tag_Stmt: + r := n + get_document_position(r.stmt, position_context) + case ^Assign_Stmt: + position_context.assign = n + get_document_position(n.lhs, position_context) + get_document_position(n.rhs, position_context) + case ^Block_Stmt: + get_document_position_label(n.label, position_context) + get_document_position(n.stmts, position_context) + case ^If_Stmt: + get_document_position_label(n.label, position_context) + get_document_position(n.init, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.body, position_context) + get_document_position(n.else_stmt, position_context) + case ^When_Stmt: + get_document_position(n.cond, position_context) + get_document_position(n.body, position_context) + get_document_position(n.else_stmt, position_context) + case ^Return_Stmt: + position_context.returns = n + get_document_position(n.results, position_context) + case ^Defer_Stmt: + get_document_position(n.stmt, position_context) + case ^For_Stmt: + get_document_position_label(n.label, position_context) + get_document_position(n.init, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.post, position_context) + get_document_position(n.body, position_context) + case ^Range_Stmt: + get_document_position_label(n.label, position_context) + get_document_position(n.vals, position_context) + get_document_position(n.expr, position_context) + get_document_position(n.body, position_context) + case ^Case_Clause: + for elem in n.list { + if position_in_node(elem, position_context.position) { + position_context.case_clause = cast(^Case_Clause)node + break + } + } + + get_document_position(n.list, position_context) + get_document_position(n.body, position_context) + case ^Switch_Stmt: + position_context.switch_stmt = cast(^Switch_Stmt)node + get_document_position_label(n.label, position_context) + get_document_position(n.init, position_context) + get_document_position(n.cond, position_context) + get_document_position(n.body, position_context) + case ^Type_Switch_Stmt: + position_context.switch_type_stmt = cast(^Type_Switch_Stmt)node + get_document_position_label(n.label, position_context) + get_document_position(n.tag, position_context) + get_document_position(n.expr, position_context) + get_document_position(n.body, position_context) + case ^Branch_Stmt: + get_document_position_label(n.label, position_context) + case ^Using_Stmt: + get_document_position(n.list, position_context) + case ^Bad_Decl: + case ^Value_Decl: + position_context.value_decl = cast(^Value_Decl)node + get_document_position(n.attributes, position_context) + + for name in n.names { + if position_in_node(name, position_context.position) && n.end.line - 1 == position_context.line { + position_context.abort_completion = true + break + } + } + get_document_position(n.names, position_context) + get_document_position(n.type, position_context) + get_document_position(n.values, position_context) + case ^Package_Decl: + case ^Import_Decl: + case ^Foreign_Block_Decl: + get_document_position(n.attributes, position_context) + get_document_position(n.foreign_library, position_context) + get_document_position(n.body, position_context) + case ^Foreign_Import_Decl: + get_document_position(n.name, position_context) + case ^Proc_Group: + get_document_position(n.args, position_context) + case ^Attribute: + get_document_position(n.elems, position_context) + case ^Field: + get_document_position(n.names, position_context) + get_document_position(n.type, position_context) + get_document_position(n.default_value, position_context) + case ^Field_List: + get_document_position(n.list, position_context) + case ^Typeid_Type: + get_document_position(n.specialization, position_context) + case ^Helper_Type: + get_document_position(n.type, position_context) + case ^Distinct_Type: + get_document_position(n.type, position_context) + case ^Poly_Type: + get_document_position(n.type, position_context) + get_document_position(n.specialization, position_context) + case ^Proc_Type: + get_document_position(n.params, position_context) + get_document_position(n.results, position_context) + case ^Pointer_Type: + get_document_position(n.elem, position_context) + case ^Array_Type: + get_document_position(n.len, position_context) + get_document_position(n.elem, position_context) + case ^Dynamic_Array_Type: + get_document_position(n.elem, position_context) + case ^Multi_Pointer_Type: + get_document_position(n.elem, position_context) + case ^Struct_Type: + position_context.struct_type = n + get_document_position(n.poly_params, position_context) + get_document_position(n.align, position_context) + get_document_position(n.fields, position_context) + case ^Union_Type: + position_context.union_type = n + get_document_position(n.poly_params, position_context) + get_document_position(n.align, position_context) + get_document_position(n.variants, position_context) + case ^Enum_Type: + position_context.enum_type = n + get_document_position(n.base_type, position_context) + get_document_position(n.fields, position_context) + case ^Bit_Set_Type: + position_context.bitset_type = n + get_document_position(n.elem, position_context) + get_document_position(n.underlying, position_context) + case ^Map_Type: + get_document_position(n.key, position_context) + get_document_position(n.value, position_context) + case ^Implicit_Selector_Expr: + position_context.implicit = true + position_context.implicit_selector_expr = n + get_document_position(n.field, position_context) + case ^Or_Else_Expr: + get_document_position(n.x, position_context) + get_document_position(n.y, position_context) + case ^Or_Return_Expr: + get_document_position(n.expr, position_context) + case ^Or_Branch_Expr: + get_document_position_label(n.label, position_context) + get_document_position(n.expr, position_context) + case ^Bit_Field_Type: + position_context.bit_field_type = n + get_document_position(n.backing_type, position_context) + get_document_position(n.fields, position_context) + case ^Bit_Field_Field: + get_document_position(n.name, position_context) + get_document_position(n.type, position_context) + get_document_position(n.bit_size, position_context) + case: + } +} |