diff options
| author | DanielGavin <danielgavin5@hotmail.com> | 2025-07-31 22:11:52 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-07-31 22:11:52 +0200 |
| commit | 2d72faebe069f32f98c10382da8f09eb1d8d7445 (patch) | |
| tree | 1b746a3414195d880712fc29a2fb4072eeb94d3a /src | |
| parent | 9cfe56a78b45546dd569f3604d41ff9db60a3fa0 (diff) | |
| parent | 27e2caa6363f9afa6910930fbaf4c00aa67dfbfb (diff) | |
Merge pull request #807 from BradLewis/feat/builtin-proc-improvements
Improve builtin proc resolution, complex and quaternion procs and add missing basic type keywords
Diffstat (limited to 'src')
| -rw-r--r-- | src/server/analysis.odin | 132 | ||||
| -rw-r--r-- | src/server/ast.odin | 23 | ||||
| -rw-r--r-- | src/server/build.odin | 17 | ||||
| -rw-r--r-- | src/server/builtins.odin | 269 | ||||
| -rw-r--r-- | src/server/documentation.odin | 26 |
5 files changed, 305 insertions, 162 deletions
diff --git a/src/server/analysis.odin b/src/server/analysis.odin index 86289f5..250e118 100644 --- a/src/server/analysis.odin +++ b/src/server/analysis.odin @@ -373,7 +373,7 @@ are_symbol_untyped_basic_same_typed :: proc(a, b: Symbol) -> (bool, bool) { } case .Bool: switch basic.ident.name { - case "bool", "b32", "b64": + case "bool", "b8", "b16", "b32", "b64": return true, true case: return false, true @@ -387,7 +387,7 @@ are_symbol_untyped_basic_same_typed :: proc(a, b: Symbol) -> (bool, bool) { } case .Float: switch basic.ident.name { - case "f32", "f64": + case "f16", "f32", "f64": return true, true case: return false, true @@ -931,7 +931,8 @@ get_proc_return_types :: proc( is_mutable: bool, ) -> []^ast.Expr { return_types := make([dynamic]^ast.Expr, context.temp_allocator) - if ret, ok := check_builtin_proc_return_type(symbol, call, is_mutable); ok { + if ret, ok := check_builtin_proc_return_type(ast_context, symbol, call, is_mutable); ok { + appended := false if call, ok := ret.derived.(^ast.Call_Expr); ok { if symbol, ok := internal_resolve_type_expression(ast_context, call.expr); ok { return get_proc_return_types(ast_context, symbol, call, true) @@ -951,131 +952,6 @@ get_proc_return_types :: proc( return return_types[:] } -// Attempts to resolve the type of the builtin proc by following the rules of the odin type checker -// defined in `check_builtin.cpp`. -// We don't need to worry about whether the inputs to the procs are valid which eliminates most edge cases. -// The basic rules are as follows: -// - For values not known at compile time (eg values return from procs), just return that type. -// The correct value will either be that type or a compiler error. -// - If all values are known at compile time, then we essentially compute the relevant value -// and return that type. -// There is a difference in the returned types between constants and variables. Constants will use an untyped -// value whereas variables will be typed (as either `int` or `f64`). -check_builtin_proc_return_type :: proc(symbol: Symbol, call: ^ast.Call_Expr, is_mutable: bool) -> (^ast.Expr, bool) { - convert_candidate :: proc(candidate: ^ast.Basic_Lit, is_mutable: bool) -> ^ast.Expr { - if is_mutable { - ident := ast.new(ast.Ident, candidate.pos, candidate.end) - if candidate.tok.kind == .Integer { - ident.name = "int" - } else { - ident.name = "f64" - } - return ident - } - - return candidate - } - - compare_basic_lit_value :: proc(a, b: f64, name: string) -> bool { - if name == "max" { - return a > b - } else if name == "min" { - return a < b - } - return a > b - } - - get_basic_lit_value :: proc(n: ^ast.Expr) -> (^ast.Basic_Lit, f64, bool) { - n := n - - op := "" - if u, ok := n.derived.(^ast.Unary_Expr); ok { - op = u.op.text - n = u.expr - } - - if lit, ok := n.derived.(^ast.Basic_Lit); ok { - text := lit.tok.text - if op != "" { - text = fmt.tprintf("%s%s", op, text) - } - value, ok := strconv.parse_f64(text) - if !ok { - return nil, 0, false - } - - return lit, value, true - } - - return nil, 0, false - } - - if symbol.pkg == "$builtin" { - switch symbol.name { - case "max", "min": - curr_candidate: ^ast.Basic_Lit - curr_value := 0.0 - for arg, i in call.args { - if lit, value, ok := get_basic_lit_value(arg); ok { - if i != 0 { - if compare_basic_lit_value(value, curr_value, symbol.name) { - curr_candidate = lit - curr_value = value - } - } else { - curr_candidate = lit - curr_value = value - } - } - if lit, ok := arg.derived.(^ast.Basic_Lit); ok { - } else { - return arg, true - } - } - if curr_candidate != nil { - return convert_candidate(curr_candidate, is_mutable), true - } - case "abs": - for arg in call.args { - if lit, _, ok := get_basic_lit_value(arg); ok { - return convert_candidate(lit, is_mutable), true - } - return arg, true - } - case "clamp": - if len(call.args) == 3 { - - value_lit, value_value, value_ok := get_basic_lit_value(call.args[0]) - if !value_ok { - return call.args[0], true - } - - minimum_lit, minimum_value, minimum_ok := get_basic_lit_value(call.args[1]) - if !minimum_ok { - return call.args[1], true - } - - maximum_lit, maximum_value, maximum_ok := get_basic_lit_value(call.args[2]) - if !maximum_ok { - return call.args[2], true - } - - if value_value < minimum_value { - return convert_candidate(minimum_lit, is_mutable), true - } - if value_value > maximum_value { - return convert_candidate(maximum_lit, is_mutable), true - } - - return convert_candidate(value_lit, is_mutable), true - } - } - - } - - return nil, false -} - check_node_recursion :: proc(ast_context: ^AstContext, node: ^ast.Node) -> bool { raw := cast(rawptr)node diff --git a/src/server/ast.odin b/src/server/ast.odin index 79f14f6..71e17f6 100644 --- a/src/server/ast.odin +++ b/src/server/ast.odin @@ -11,23 +11,26 @@ import "core:strings" keyword_map: map[string]bool = { "typeid" = true, - "int" = true, - "uint" = true, "string" = true, "cstring" = true, + "int" = true, + "uint" = true, + "u8" = true, + "i8" = true, + "u16" = true, + "i16" = true, + "u32" = true, + "i32" = true, "u64" = true, - "f32" = true, - "f64" = true, "i64" = true, + "u128" = true, "i128" = true, - "i32" = true, - "i16" = true, - "u16" = true, + "f16" = true, + "f32" = true, + "f64" = true, "bool" = true, "rawptr" = true, "any" = true, - "u32" = true, - "u128" = true, "b8" = true, "b16" = true, "b32" = true, @@ -36,8 +39,6 @@ keyword_map: map[string]bool = { "false" = true, "nil" = true, "byte" = true, - "u8" = true, - "i8" = true, "rune" = true, "f16be" = true, "f16le" = true, diff --git a/src/server/build.odin b/src/server/build.odin index 3cbb6f5..373b5d3 100644 --- a/src/server/build.odin +++ b/src/server/build.odin @@ -282,20 +282,15 @@ setup_index :: proc() { dir_exe := common.get_executable_path(context.temp_allocator) builtin_path := path.join({dir_exe, "builtin"}, context.temp_allocator) - if os.exists(builtin_path) { - try_build_package(builtin_path) + if !os.exists(builtin_path) { + log.errorf( + "Failed to find the builtin folder at `%v`.\nPlease ensure the `builtin` folder that ships with `ols` is located next to the `ols` binary as it is required for ols to work with builtins", + builtin_path, + ) return } - root_path := os.get_env("ODIN_ROOT", context.temp_allocator) - root_builtin_path := path.join({root_path, "/base/builtin"}, context.temp_allocator) - - if !os.exists(root_builtin_path) { - log.errorf("Failed to find the builtin folder at `%v` or `%v`", builtin_path, root_builtin_path) - return - } - - try_build_package(root_builtin_path) + try_build_package(builtin_path) } free_index :: proc() { diff --git a/src/server/builtins.odin b/src/server/builtins.odin new file mode 100644 index 0000000..e00e000 --- /dev/null +++ b/src/server/builtins.odin @@ -0,0 +1,269 @@ +package server + +import "core:fmt" +import "core:log" +import "core:odin/ast" +import "core:strconv" + +// Attempts to resolve the type of the builtin proc by following the rules of the odin type checker +// defined in `check_builtin.cpp`. +// We don't need to worry about whether the inputs to the procs are valid which eliminates most edge cases. +// The basic rules are as follows: +// - For values not known at compile time (eg values return from procs), just return that type. +// The correct value will either be that type or a compiler error. +// - If all values are known at compile time, then we essentially compute the relevant value +// and return that type. +// There is a difference in the returned types between constants and variables. Constants will use an untyped +// value whereas variables will be typed. +check_builtin_proc_return_type :: proc( + ast_context: ^AstContext, + symbol: Symbol, + call: ^ast.Call_Expr, + is_mutable: bool, +) -> (^ast.Expr, bool) { + if symbol.pkg == "$builtin" { + switch symbol.name { + case "max", "min": + curr_candidate: ^ast.Basic_Lit + curr_value := 0.0 + for arg, i in call.args { + if lit, value, ok := get_basic_lit_value(arg); ok { + if i != 0 { + if compare_basic_lit_value(value, curr_value, symbol.name) { + curr_candidate = lit + curr_value = value + } + } else { + curr_candidate = lit + curr_value = value + } + } else { + return get_return_expr(ast_context, arg, is_mutable), true + } + } + if curr_candidate != nil { + return convert_candidate(curr_candidate, is_mutable), true + } + case "abs": + for arg in call.args { + if lit, _, ok := get_basic_lit_value(arg); ok { + return convert_candidate(lit, is_mutable), true + } + return get_return_expr(ast_context, arg, is_mutable), true + } + case "clamp": + if len(call.args) == 3 { + value_lit, value_value, value_ok := get_basic_lit_value(call.args[0]) + if !value_ok { + return get_return_expr(ast_context, call.args[0], is_mutable), true + } + + minimum_lit, minimum_value, minimum_ok := get_basic_lit_value(call.args[1]) + if !minimum_ok { + return get_return_expr(ast_context, call.args[1], is_mutable), true + } + + maximum_lit, maximum_value, maximum_ok := get_basic_lit_value(call.args[2]) + if !maximum_ok { + return get_return_expr(ast_context, call.args[2], is_mutable), true + } + + if value_value < minimum_value { + return convert_candidate(minimum_lit, is_mutable), true + } + if value_value > maximum_value { + return convert_candidate(maximum_lit, is_mutable), true + } + + return convert_candidate(value_lit, is_mutable), true + } + case "complex": + candidate: ^ast.Basic_Lit + for arg in call.args { + if lit, _, ok := get_basic_lit_value(arg); ok { + candidate = lit + } else { + expr := get_complex_return_expr(ast_context, arg) + if ident, ok := expr.derived.(^ast.Ident); ok { + switch ident.name { + case "f16": + ident.name = "complex32" + return ident, true + case "f32": + ident.name = "complex64" + return ident, true + case "f64": + ident.name = "complex128" + return ident, true + } + } + } + } + if candidate != nil { + return convert_complex_candidate(candidate, is_mutable), true + } + case "quaternion": + candidate: ^ast.Basic_Lit + for arg in call.args { + if lit, _, ok := get_basic_lit_value(arg); ok { + candidate = lit + } else { + expr := get_quaternion_return_expr(ast_context, arg) + if ident, ok := expr.derived.(^ast.Ident); ok { + switch ident.name { + case "f16": + ident.name = "quaternion64" + return ident, true + case "f32": + ident.name = "quaternion128" + return ident, true + case "f64": + ident.name = "quaternion256" + return ident, true + } + } + } + } + if candidate != nil { + return convert_quaternion_candidate(candidate, is_mutable), true + } + } + } + + return nil, false +} + +@(private="file") +get_return_expr :: proc(ast_context: ^AstContext, expr: ^ast.Expr, is_mutable: bool) -> ^ast.Expr { + if v, ok := expr.derived.(^ast.Field_Value); ok { + return get_return_expr(ast_context, v.value, is_mutable) + } + if ident, ok := expr.derived.(^ast.Ident); ok { + if symbol, ok := internal_resolve_type_expression(ast_context, ident); ok { + if v, ok := symbol.value.(SymbolBasicValue); ok { + return v.ident + } else if v, ok := symbol.value.(SymbolUntypedValue); ok { + lit := ast.new(ast.Basic_Lit, expr.pos, expr.end) + lit.tok = v.tok + return convert_candidate(lit, is_mutable) + } + } + } + return expr +} + +@(private="file") +convert_candidate :: proc(candidate: ^ast.Basic_Lit, is_mutable: bool) -> ^ast.Expr { + if is_mutable { + ident := ast.new(ast.Ident, candidate.pos, candidate.end) + if candidate.tok.kind == .Integer { + ident.name = "int" + } else { + ident.name = "f64" + } + return ident + } + + return candidate +} + +@(private="file") +get_complex_return_expr :: proc(ast_context: ^AstContext, expr: ^ast.Expr) -> ^ast.Expr { + if v, ok := expr.derived.(^ast.Field_Value); ok { + return get_complex_return_expr(ast_context, v.value) + } + if ident, ok := expr.derived.(^ast.Ident); ok { + if symbol, ok := internal_resolve_type_expression(ast_context, ident); ok { + if v, ok := symbol.value.(SymbolBasicValue); ok { + return v.ident + } else if v, ok := symbol.value.(SymbolUntypedValue); ok { + // There isn't a token for `Complex` so we just set it to `f64` instead + ident := ast.new(ast.Ident, expr.pos, expr.end) + ident.name = "f64" + return ident + } + } + } + return expr +} + +@(private="file") +convert_complex_candidate :: proc(candidate: ^ast.Basic_Lit, is_mutable: bool) -> ^ast.Expr { + if is_mutable { + ident := ast.new(ast.Ident, candidate.pos, candidate.end) + ident.name = "complex128" + return ident + } + + return candidate +} + +@(private="file") +get_quaternion_return_expr :: proc(ast_context: ^AstContext, expr: ^ast.Expr) -> ^ast.Expr { + if v, ok := expr.derived.(^ast.Field_Value); ok { + return get_quaternion_return_expr(ast_context, v.value) + } + if ident, ok := expr.derived.(^ast.Ident); ok { + if symbol, ok := internal_resolve_type_expression(ast_context, ident); ok { + if v, ok := symbol.value.(SymbolBasicValue); ok { + return v.ident + } else if v, ok := symbol.value.(SymbolUntypedValue); ok { + // There isn't a token for `Quaternion` so we just set it to `quaternion256` instead + ident := ast.new(ast.Ident, expr.pos, expr.end) + ident.name = "f64" + return ident + } + } + } + return expr +} + +@(private="file") +convert_quaternion_candidate :: proc(candidate: ^ast.Basic_Lit, is_mutable: bool) -> ^ast.Expr { + if is_mutable { + ident := ast.new(ast.Ident, candidate.pos, candidate.end) + ident.name = "quaternion256" + return ident + } + + return candidate +} + +@(private="file") +get_basic_lit_value :: proc(n: ^ast.Expr) -> (^ast.Basic_Lit, f64, bool) { + n := n + if v, ok := n.derived.(^ast.Field_Value); ok { + return get_basic_lit_value(v.value) + } + + op := "" + if u, ok := n.derived.(^ast.Unary_Expr); ok { + op = u.op.text + n = u.expr + } + + if lit, ok := n.derived.(^ast.Basic_Lit); ok { + text := lit.tok.text + if op != "" { + text = fmt.tprintf("%s%s", op, text) + } + value, ok := strconv.parse_f64(text) + if !ok { + return nil, 0, false + } + + return lit, value, true + } + + return nil, 0, false +} + +@(private="file") +compare_basic_lit_value :: proc(a, b: f64, name: string) -> bool { + if name == "max" { + return a > b + } else if name == "min" { + return a < b + } + return a > b +} diff --git a/src/server/documentation.odin b/src/server/documentation.odin index 4e1cf9f..7dbf5d9 100644 --- a/src/server/documentation.odin +++ b/src/server/documentation.odin @@ -8,23 +8,27 @@ import path "core:path/slashpath" import "core:strings" keywords_docs: map[string]bool = { - "int" = true, - "uint" = true, + "typeid" = true, "string" = true, "cstring" = true, + "int" = true, + "uint" = true, + "u8" = true, + "i8" = true, + "u16" = true, + "i16" = true, + "u32" = true, + "i32" = true, "u64" = true, - "f32" = true, - "f64" = true, "i64" = true, + "u128" = true, "i128" = true, - "i32" = true, - "i16" = true, - "u16" = true, + "f16" = true, + "f32" = true, + "f64" = true, "bool" = true, "rawptr" = true, "any" = true, - "u32" = true, - "u128" = true, "b8" = true, "b16" = true, "b32" = true, @@ -33,8 +37,6 @@ keywords_docs: map[string]bool = { "false" = true, "nil" = true, "byte" = true, - "u8" = true, - "i8" = true, "rune" = true, "f16be" = true, "f16le" = true, @@ -683,7 +685,7 @@ concatenate_raw_symbol_information :: proc(ast_context: ^AstContext, symbol: Sym if symbol.signature != "" { fmt.sbprintf(&sb, ": %v", symbol.signature) } - return strings.to_string(sb) + return strings.to_string(sb) } return concatenate_raw_string_information( |