aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorDanielGavin <danielgavin5@hotmail.com>2025-07-31 22:11:52 +0200
committerGitHub <noreply@github.com>2025-07-31 22:11:52 +0200
commit2d72faebe069f32f98c10382da8f09eb1d8d7445 (patch)
tree1b746a3414195d880712fc29a2fb4072eeb94d3a /src/server
parent9cfe56a78b45546dd569f3604d41ff9db60a3fa0 (diff)
parent27e2caa6363f9afa6910930fbaf4c00aa67dfbfb (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/server')
-rw-r--r--src/server/analysis.odin132
-rw-r--r--src/server/ast.odin23
-rw-r--r--src/server/build.odin17
-rw-r--r--src/server/builtins.odin269
-rw-r--r--src/server/documentation.odin26
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(