booleans, comparisons, and if/else
This commit is contained in:
@@ -81,10 +81,14 @@
|
|||||||
- static inline functions (implemented)
|
- static inline functions (implemented)
|
||||||
|
|
||||||
- 5. control flow
|
- 5. control flow
|
||||||
- boolean expressions
|
- boolean expressions (implemented)
|
||||||
|
- `bool` type with `true` / `false` literals
|
||||||
|
- comparison operators: `==`, `!=`, `<`, `<=`, `>`, `>=` (numeric operands widen; `bool` supports only `==` / `!=`)
|
||||||
- operators: `and`, `or`, `!`
|
- operators: `and`, `or`, `!`
|
||||||
- lazy evaluation / short-circuit evaluation
|
- lazy evaluation / short-circuit evaluation
|
||||||
- if statements. example: `if condition { ... } else if { ... } else { ... }`
|
- if statements (implemented). example: `if condition { ... } else if { ... } else { ... }`
|
||||||
|
- conditions must be `bool`; block-scoped locals with shadowing across blocks
|
||||||
|
- lowered through new `Label` / `Br` / `Cond_Br` IR opcodes (alloca-backed locals, no phi nodes)
|
||||||
- conditional unwrapping for optionals (`?T`): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
- conditional unwrapping for optionals (`?T`): `if val |v| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
||||||
- conditional unwrapping with guard clause: `if val |v : v >= 10| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
- conditional unwrapping with guard clause: `if val |v : v >= 10| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
||||||
- multi-unwrap (see section below)
|
- multi-unwrap (see section below)
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Integer,
|
Integer,
|
||||||
Float,
|
Float,
|
||||||
String,
|
String,
|
||||||
|
Bool,
|
||||||
Array,
|
Array,
|
||||||
None,
|
None,
|
||||||
Name,
|
Name,
|
||||||
@@ -81,7 +82,16 @@ Expr_Kind :: enum u8 {
|
|||||||
Struct_Literal,
|
Struct_Literal,
|
||||||
Keyed,
|
Keyed,
|
||||||
Negate,
|
Negate,
|
||||||
|
Not,
|
||||||
Add,
|
Add,
|
||||||
|
Eq,
|
||||||
|
Ne,
|
||||||
|
Lt,
|
||||||
|
Le,
|
||||||
|
Gt,
|
||||||
|
Ge,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
Call,
|
Call,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,6 +119,7 @@ Stmt_Kind :: enum u8 {
|
|||||||
Assignment,
|
Assignment,
|
||||||
Return,
|
Return,
|
||||||
Expression,
|
Expression,
|
||||||
|
If,
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt :: struct {
|
Stmt :: struct {
|
||||||
@@ -119,6 +130,11 @@ Stmt :: struct {
|
|||||||
immutable: bool,
|
immutable: bool,
|
||||||
target: Expr_Id,
|
target: Expr_Id,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
|
// `If` statements use `expr` as the condition, `body` as the then-block, and
|
||||||
|
// `else_body` as the else-block. An `else if` chain is represented as an
|
||||||
|
// `else_body` holding a single nested `If` statement.
|
||||||
|
body: []Stmt_Id,
|
||||||
|
else_body: []Stmt_Id,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +250,10 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
for expr in module.exprs {
|
for expr in module.exprs {
|
||||||
delete(expr.args, module.allocator)
|
delete(expr.args, module.allocator)
|
||||||
}
|
}
|
||||||
|
for statement in module.statements {
|
||||||
|
delete(statement.body, module.allocator)
|
||||||
|
delete(statement.else_body, module.allocator)
|
||||||
|
}
|
||||||
for function in module.functions {
|
for function in module.functions {
|
||||||
delete(function.params, module.allocator)
|
delete(function.params, module.allocator)
|
||||||
delete(function.body, module.allocator)
|
delete(function.body, module.allocator)
|
||||||
|
|||||||
+420
-382
@@ -44,6 +44,23 @@ Build_Local :: struct {
|
|||||||
id: hir.Local_Id,
|
id: hir.Local_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build_Ctx threads the per-function accumulators through build_block so that
|
||||||
|
// nested control-flow blocks (if/else) can be built recursively. `locals` is a
|
||||||
|
// scope stack: each block records its entry length and truncates back to it on
|
||||||
|
// exit, while `hir_locals` keeps every allocated slot for the function.
|
||||||
|
Build_Ctx :: struct {
|
||||||
|
checker: ^Checker,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
result: types.Type,
|
||||||
|
locals: ^[dynamic]Build_Local,
|
||||||
|
hir_locals: ^[dynamic]hir.Local,
|
||||||
|
global_reads: ^[dynamic]hir.Global_Id,
|
||||||
|
calls: ^[dynamic]hir.Function_Id,
|
||||||
|
problematic: ^bool,
|
||||||
|
has_return: ^bool,
|
||||||
|
}
|
||||||
|
|
||||||
Constant_Kind :: enum {
|
Constant_Kind :: enum {
|
||||||
Unknown,
|
Unknown,
|
||||||
Not_Constant,
|
Not_Constant,
|
||||||
@@ -593,11 +610,29 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
|
|||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
}
|
}
|
||||||
case .Negate, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
||||||
append(&stack, expr.left)
|
append(&stack, expr.left)
|
||||||
case .Add, .Index, .Orelse:
|
case .Add, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||||
append(&stack, expr.left, expr.right)
|
append(&stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .None, .Name:
|
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Name:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mark_block_imports_used :: proc(checker: ^Checker, statements: []ast.Stmt_Id, file: ast.File_Id) {
|
||||||
|
for statement_id in statements {
|
||||||
|
statement := checker.ast_module.statements[statement_id]
|
||||||
|
switch statement.kind {
|
||||||
|
case .Declaration, .Assignment, .Return, .Expression:
|
||||||
|
mark_expr_imports_used(checker, statement.expr, file)
|
||||||
|
if statement.target != ast.INVALID_EXPR {
|
||||||
|
mark_expr_imports_used(checker, statement.target, file)
|
||||||
|
}
|
||||||
|
case .If:
|
||||||
|
mark_expr_imports_used(checker, statement.expr, file)
|
||||||
|
mark_block_imports_used(checker, statement.body, file)
|
||||||
|
mark_block_imports_used(checker, statement.else_body, file)
|
||||||
|
case .Invalid:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -772,17 +807,7 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for statement_id in function.body {
|
mark_block_imports_used(checker, function.body, function.file)
|
||||||
statement := checker.ast_module.statements[statement_id]
|
|
||||||
switch statement.kind {
|
|
||||||
case .Declaration, .Assignment, .Return, .Expression:
|
|
||||||
mark_expr_imports_used(checker, statement.expr, function.file)
|
|
||||||
if statement.target != ast.INVALID_EXPR {
|
|
||||||
mark_expr_imports_used(checker, statement.target, function.file)
|
|
||||||
}
|
|
||||||
case .Invalid:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(locals)
|
delete(locals)
|
||||||
}
|
}
|
||||||
for function, function_id in checker.ast_module.functions {
|
for function, function_id in checker.ast_module.functions {
|
||||||
@@ -988,6 +1013,15 @@ infer_compound_expr :: proc(
|
|||||||
) -> types.Type {
|
) -> types.Type {
|
||||||
store := &checker.module.types
|
store := &checker.module.types
|
||||||
#partial switch expr.kind {
|
#partial switch expr.kind {
|
||||||
|
case .Bool:
|
||||||
|
return types.BOOL
|
||||||
|
case .Not:
|
||||||
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
||||||
|
return types.BOOL
|
||||||
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||||
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded)
|
||||||
|
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded)
|
||||||
|
return types.BOOL
|
||||||
case .String:
|
case .String:
|
||||||
return string_literal_type(checker, expr.integer)
|
return string_literal_type(checker, expr.integer)
|
||||||
case .Array:
|
case .Array:
|
||||||
@@ -1124,7 +1158,8 @@ infer_expr :: proc(
|
|||||||
last = types.F64
|
last = types.F64
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed:
|
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
|
||||||
|
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||||
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
|
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .Name:
|
case .Name:
|
||||||
@@ -1342,6 +1377,49 @@ infer_expr :: proc(
|
|||||||
return last
|
return last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infer_statements :: proc(
|
||||||
|
checker: ^Checker,
|
||||||
|
statements: []ast.Stmt_Id,
|
||||||
|
locals: ^[dynamic]Infer_Local,
|
||||||
|
pkg: ast.Package_Id,
|
||||||
|
file: ast.File_Id,
|
||||||
|
demanded: ^[dynamic]Spec_Id,
|
||||||
|
result: ^types.Type,
|
||||||
|
) {
|
||||||
|
scope_start := len(locals^)
|
||||||
|
for statement_id in statements {
|
||||||
|
statement := checker.ast_module.statements[statement_id]
|
||||||
|
#partial switch statement.kind {
|
||||||
|
case .Declaration:
|
||||||
|
value_type := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||||
|
declared_local := type_from_syntax(statement.type)
|
||||||
|
if is_runtime_type(checker, declared_local) {
|
||||||
|
value_type = declared_local
|
||||||
|
}
|
||||||
|
append(locals, Infer_Local{name = statement.name, type = value_type})
|
||||||
|
case .Assignment, .Expression:
|
||||||
|
if statement.target != ast.INVALID_EXPR {
|
||||||
|
_ = infer_expr(checker, statement.target, locals^[:], pkg, file, demanded)
|
||||||
|
}
|
||||||
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||||
|
case .Return:
|
||||||
|
if statement.expr != ast.INVALID_EXPR {
|
||||||
|
returned := infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||||
|
if !types.is_valid(result^) {
|
||||||
|
result^ = returned
|
||||||
|
} else {
|
||||||
|
result^ = types.widest(result^, returned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .If:
|
||||||
|
_ = infer_expr(checker, statement.expr, locals^[:], pkg, file, demanded)
|
||||||
|
infer_statements(checker, statement.body, locals, pkg, file, demanded, result)
|
||||||
|
infer_statements(checker, statement.else_body, locals, pkg, file, demanded, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resize(locals, scope_start)
|
||||||
|
}
|
||||||
|
|
||||||
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
|
infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Spec_Id = nil) -> types.Type {
|
||||||
spec := checker.specs[id]
|
spec := checker.specs[id]
|
||||||
function := checker.ast_module.functions[spec.template]
|
function := checker.ast_module.functions[spec.template]
|
||||||
@@ -1362,32 +1440,7 @@ infer_spec_result :: proc(checker: ^Checker, id: Spec_Id, demanded: ^[dynamic]Sp
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := types.INVALID
|
result := types.INVALID
|
||||||
for statement_id in function.body {
|
infer_statements(checker, function.body, &locals, function.pkg, function.file, demanded, &result)
|
||||||
statement := checker.ast_module.statements[statement_id]
|
|
||||||
#partial switch statement.kind {
|
|
||||||
case .Declaration:
|
|
||||||
value_type := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
|
|
||||||
declared_local := type_from_syntax(statement.type)
|
|
||||||
if is_runtime_type(checker, declared_local) {
|
|
||||||
value_type = declared_local
|
|
||||||
}
|
|
||||||
append(&locals, Infer_Local{name = statement.name, type = value_type})
|
|
||||||
case .Assignment, .Expression:
|
|
||||||
if statement.target != ast.INVALID_EXPR {
|
|
||||||
_ = infer_expr(checker, statement.target, locals[:], function.pkg, function.file, demanded)
|
|
||||||
}
|
|
||||||
_ = infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
|
|
||||||
case .Return:
|
|
||||||
if statement.expr != ast.INVALID_EXPR {
|
|
||||||
returned := infer_expr(checker, statement.expr, locals[:], function.pkg, function.file, demanded)
|
|
||||||
if !types.is_valid(result) {
|
|
||||||
result = returned
|
|
||||||
} else {
|
|
||||||
result = types.widest(result, returned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if types.is_constraint(declared) {
|
if types.is_constraint(declared) {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -2116,6 +2169,90 @@ build_compound_expr :: proc(
|
|||||||
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
|
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
|
||||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
case .Bool:
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Bool, span=expr.span, type=types.BOOL, integer=i64(expr.integer),
|
||||||
|
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .Not:
|
||||||
|
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
||||||
|
operand_type := checker.module.exprs[operand].type
|
||||||
|
if checker.module.exprs[operand].kind != .Invalid && !types.is_bool(operand_type) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'!' requires a bool operand")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
||||||
|
}
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
|
||||||
|
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .And, .Or:
|
||||||
|
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
||||||
|
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
|
||||||
|
left_type := checker.module.exprs[left].type
|
||||||
|
right_type := checker.module.exprs[right].type
|
||||||
|
left_ok := checker.module.exprs[left].kind == .Invalid || types.is_bool(left_type)
|
||||||
|
right_ok := checker.module.exprs[right].kind == .Invalid || types.is_bool(right_type)
|
||||||
|
if !left_ok || !right_ok {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "'and'/'or' require bool operands")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
||||||
|
}
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=.And if expr.kind == .And else .Or, span=expr.span, type=types.BOOL,
|
||||||
|
left=left, right=right, target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||||
|
// Contextualize a bare integer-literal operand to the other operand's type
|
||||||
|
// so comparisons like `count > 0` or `0 < count` type-check.
|
||||||
|
left_const := eval_constant(checker, expr.left)
|
||||||
|
right_const := eval_constant(checker, expr.right)
|
||||||
|
left, right: hir.Expr_Id
|
||||||
|
if right_const.kind == .Value && left_const.kind != .Value {
|
||||||
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
hint := checker.module.exprs[left].type
|
||||||
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, hint, pkg, file)
|
||||||
|
} else if left_const.kind == .Value && right_const.kind != .Value {
|
||||||
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
hint := checker.module.exprs[right].type
|
||||||
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
|
||||||
|
} else {
|
||||||
|
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
|
||||||
|
}
|
||||||
|
left_type := checker.module.exprs[left].type
|
||||||
|
right_type := checker.module.exprs[right].type
|
||||||
|
if checker.module.exprs[left].kind == .Invalid || checker.module.exprs[right].kind == .Invalid {
|
||||||
|
return invalid_hir_expr(checker, expr.span, expr.diagnostic, types.BOOL)
|
||||||
|
}
|
||||||
|
operand_type := types.INVALID
|
||||||
|
if types.is_bool(left_type) && types.is_bool(right_type) {
|
||||||
|
if expr.kind != .Eq && expr.kind != .Ne {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "bool values only support '==' and '!='")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
||||||
|
}
|
||||||
|
operand_type = types.BOOL
|
||||||
|
} else {
|
||||||
|
operand_type = types.widest(left_type, right_type)
|
||||||
|
if !types.is_concrete_scalar(operand_type) || types.is_bool(operand_type) {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "comparison requires compatible numeric operands")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id, types.BOOL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
left = coerce_expr(checker, left, operand_type, checker.module.exprs[left].span)
|
||||||
|
right = coerce_expr(checker, right, operand_type, checker.module.exprs[right].span)
|
||||||
|
compare_kind := hir.Expr_Kind.Eq
|
||||||
|
#partial switch expr.kind {
|
||||||
|
case .Eq: compare_kind = .Eq
|
||||||
|
case .Ne: compare_kind = .Ne
|
||||||
|
case .Lt: compare_kind = .Lt
|
||||||
|
case .Le: compare_kind = .Le
|
||||||
|
case .Gt: compare_kind = .Gt
|
||||||
|
case .Ge: compare_kind = .Ge
|
||||||
|
}
|
||||||
|
return add_hir_expr(checker, hir.Expr{
|
||||||
|
kind=compare_kind, span=expr.span, type=types.BOOL, left=left, right=right,
|
||||||
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Struct_Literal:
|
case .Struct_Literal:
|
||||||
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
target_pkg, available := expr_package(checker, expr, pkg, file, true)
|
||||||
struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
|
struct_type := types.find_named(store, u32(target_pkg), u32(expr.name)) if available else types.INVALID
|
||||||
@@ -2230,7 +2367,8 @@ build_expr :: proc(
|
|||||||
}
|
}
|
||||||
switch expr.kind {
|
switch expr.kind {
|
||||||
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed:
|
.Field, .Unwrap, .Orelse, .Struct_Literal, .Keyed,
|
||||||
|
.Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||||
last = build_compound_expr(
|
last = build_compound_expr(
|
||||||
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
||||||
)
|
)
|
||||||
@@ -2676,6 +2814,231 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
|||||||
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
|
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
build_block :: proc(ctx: ^Build_Ctx, statements: []ast.Stmt_Id) -> []hir.Stmt_Id {
|
||||||
|
checker := ctx.checker
|
||||||
|
body: [dynamic]hir.Stmt_Id
|
||||||
|
body.allocator = checker.allocator
|
||||||
|
scope_start := len(ctx.locals^)
|
||||||
|
for statement_id in statements {
|
||||||
|
statement := checker.ast_module.statements[statement_id]
|
||||||
|
switch statement.kind {
|
||||||
|
case .Declaration:
|
||||||
|
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
|
||||||
|
expected := types.INVALID
|
||||||
|
if is_runtime_type(checker, declared) {
|
||||||
|
expected = declared
|
||||||
|
}
|
||||||
|
value := build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
expected, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
value_type := checker.module.exprs[value].type
|
||||||
|
if is_runtime_type(checker, declared) {
|
||||||
|
value = coerce_expr(checker, value, declared, statement.span)
|
||||||
|
value_type = checker.module.exprs[value].type
|
||||||
|
} else if types.is_void(declared) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "locals cannot have type void")
|
||||||
|
value = invalid_hir_expr(checker, statement.span, id)
|
||||||
|
value_type = types.INVALID
|
||||||
|
}
|
||||||
|
if _, found := find_build_local(ctx.locals^[scope_start:], statement.name); found {
|
||||||
|
id := source.addf(
|
||||||
|
checker.diagnostics, statement.span,
|
||||||
|
"duplicate local '%s'", symbol_text(checker, statement.name),
|
||||||
|
)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
local_id := hir.local_id(len(ctx.hir_locals^))
|
||||||
|
append(ctx.hir_locals, hir.Local{
|
||||||
|
name = statement.name, type = value_type, mutable = !statement.immutable,
|
||||||
|
})
|
||||||
|
append(ctx.locals, Build_Local{
|
||||||
|
name = statement.name, type = value_type, mutable = !statement.immutable, id = local_id,
|
||||||
|
})
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Declaration, span = statement.span, local = local_id, expr = value,
|
||||||
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||||
|
case .Assignment:
|
||||||
|
if statement.target != ast.INVALID_EXPR {
|
||||||
|
target_expr := build_expr(
|
||||||
|
checker, statement.target, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
types.INVALID, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
target_type := checker.module.exprs[target_expr].type
|
||||||
|
if !hir_location_writable(checker, target_expr, ctx.locals^[:]) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
|
||||||
|
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value := build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
target_type, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
value = coerce_expr(checker, value, target_type, statement.span)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
|
||||||
|
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if statement.name == checker.sink_symbol {
|
||||||
|
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
||||||
|
if types.is_void(checker.module.exprs[value].type) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "cannot assign a void expression to '_'")
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
} else {
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Sink, span = statement.span, expr = value,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
local, found := find_build_local(ctx.locals^[:], statement.name)
|
||||||
|
if !found {
|
||||||
|
id := source.addf(checker.diagnostics, statement.span, "cannot assign unresolved local '%s'", symbol_text(checker, statement.name))
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !local.mutable {
|
||||||
|
id := source.addf(checker.diagnostics, statement.span, "cannot assign immutable local '%s'", symbol_text(checker, statement.name))
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value := build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
local.type, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
value = coerce_expr(checker, value, local.type, statement.span)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Assignment, span = statement.span, expr = value, local = local.id,
|
||||||
|
target = hir.INVALID_EXPR, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||||
|
case .Return:
|
||||||
|
ctx.has_return^ = true
|
||||||
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
|
if !types.is_void(ctx.result) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "'return _' is only valid in a void function")
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
} else {
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Return, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if types.is_void(ctx.result) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "void function cannot return a value")
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value := build_expr(
|
||||||
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
|
ctx.result, ctx.pkg, ctx.file,
|
||||||
|
)
|
||||||
|
value = coerce_expr(checker, value, ctx.result, statement.span)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Return, span = statement.span, expr = value,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[value].kind == .Invalid
|
||||||
|
case .Expression:
|
||||||
|
value := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.INVALID, ctx.pkg, ctx.file)
|
||||||
|
if !types.is_void(checker.module.exprs[value].type) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "non-void expression result must be consumed or assigned to '_'")
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
} else {
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Expression, span = statement.span, expr = value,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case .If:
|
||||||
|
condition := build_expr(checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls, types.BOOL, ctx.pkg, ctx.file)
|
||||||
|
if checker.module.exprs[condition].kind != .Invalid && !types.is_bool(checker.module.exprs[condition].type) {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "'if' condition must be a bool")
|
||||||
|
condition = invalid_hir_expr(checker, statement.span, id, types.BOOL)
|
||||||
|
ctx.problematic^ = true
|
||||||
|
}
|
||||||
|
then_body := build_block(ctx, statement.body)
|
||||||
|
else_body: []hir.Stmt_Id = nil
|
||||||
|
if statement.else_body != nil {
|
||||||
|
else_body = build_block(ctx, statement.else_body)
|
||||||
|
}
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .If, span = statement.span, expr = condition,
|
||||||
|
then_body = then_body, else_body = else_body,
|
||||||
|
local = hir.INVALID_LOCAL, target = hir.INVALID_EXPR,
|
||||||
|
diagnostic = source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = ctx.problematic^ || checker.module.exprs[condition].kind == .Invalid
|
||||||
|
case .Invalid:
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind = .Trap, span = statement.span, expr = hir.INVALID_EXPR,
|
||||||
|
local = hir.INVALID_LOCAL, diagnostic = statement.diagnostic,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resize(ctx.locals, scope_start)
|
||||||
|
return body[:]
|
||||||
|
}
|
||||||
|
|
||||||
build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
||||||
spec := checker.specs[id]
|
spec := checker.specs[id]
|
||||||
function := checker.ast_module.functions[spec.template]
|
function := checker.ast_module.functions[spec.template]
|
||||||
@@ -2769,348 +3132,23 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for statement_id in function.body {
|
ctx := Build_Ctx{
|
||||||
statement := checker.ast_module.statements[statement_id]
|
checker = checker,
|
||||||
switch statement.kind {
|
pkg = function.pkg,
|
||||||
case .Declaration:
|
file = function.file,
|
||||||
declared := resolve_inferred_array(checker, type_from_syntax(statement.type), statement.expr)
|
result = spec.result,
|
||||||
expected := types.INVALID
|
locals = &locals,
|
||||||
if is_runtime_type(checker, declared) {
|
hir_locals = &hir_locals,
|
||||||
expected = declared
|
global_reads = &global_reads,
|
||||||
}
|
calls = &calls,
|
||||||
value := build_expr(
|
problematic = &problematic,
|
||||||
checker,
|
has_return = &has_return,
|
||||||
statement.expr,
|
|
||||||
locals[:],
|
|
||||||
&global_reads,
|
|
||||||
&calls,
|
|
||||||
expected,
|
|
||||||
function.pkg,
|
|
||||||
function.file,
|
|
||||||
)
|
|
||||||
value_type := checker.module.exprs[value].type
|
|
||||||
if is_runtime_type(checker, declared) {
|
|
||||||
value = coerce_expr(checker, value, declared, statement.span)
|
|
||||||
value_type = checker.module.exprs[value].type
|
|
||||||
} else if types.is_void(declared) {
|
|
||||||
id := source.add(
|
|
||||||
checker.diagnostics,
|
|
||||||
statement.span,
|
|
||||||
"locals cannot have type void",
|
|
||||||
)
|
|
||||||
value = invalid_hir_expr(checker, statement.span, id)
|
|
||||||
value_type = types.INVALID
|
|
||||||
}
|
|
||||||
if _, found := find_build_local(locals[:], statement.name); found {
|
|
||||||
id := source.addf(
|
|
||||||
checker.diagnostics,
|
|
||||||
statement.span,
|
|
||||||
"duplicate local '%s'",
|
|
||||||
symbol_text(checker, statement.name),
|
|
||||||
)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Trap,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
local_id := hir.local_id(len(hir_locals))
|
|
||||||
append(
|
|
||||||
&hir_locals,
|
|
||||||
hir.Local {
|
|
||||||
name = statement.name,
|
|
||||||
type = value_type,
|
|
||||||
mutable = !statement.immutable,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
append(
|
|
||||||
&locals,
|
|
||||||
Build_Local {
|
|
||||||
name = statement.name,
|
|
||||||
type = value_type,
|
|
||||||
mutable = !statement.immutable,
|
|
||||||
id = local_id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Declaration,
|
|
||||||
span = statement.span,
|
|
||||||
local = local_id,
|
|
||||||
expr = value,
|
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
||||||
case .Assignment:
|
|
||||||
if statement.target != ast.INVALID_EXPR {
|
|
||||||
target_expr := build_expr(
|
|
||||||
checker, statement.target, locals[:], &global_reads, &calls,
|
|
||||||
types.INVALID, function.pkg, function.file,
|
|
||||||
)
|
|
||||||
target_type := checker.module.exprs[target_expr].type
|
|
||||||
if !hir_location_writable(checker, target_expr, locals[:]) {
|
|
||||||
id := source.add(checker.diagnostics, statement.span, "assignment target is not writable")
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(&checker.module.statements, hir.Stmt{
|
|
||||||
kind=.Trap, span=statement.span, local=hir.INVALID_LOCAL,
|
|
||||||
target=hir.INVALID_EXPR, expr=hir.INVALID_EXPR, diagnostic=id,
|
|
||||||
})
|
|
||||||
problematic = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value := build_expr(
|
|
||||||
checker, statement.expr, locals[:], &global_reads, &calls,
|
|
||||||
target_type, function.pkg, function.file,
|
|
||||||
)
|
|
||||||
value = coerce_expr(checker, value, target_type, statement.span)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(&checker.module.statements, hir.Stmt{
|
|
||||||
kind=.Assignment, span=statement.span, local=hir.INVALID_LOCAL,
|
|
||||||
target=target_expr, expr=value, diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if statement.name == checker.sink_symbol {
|
|
||||||
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file)
|
|
||||||
if types.is_void(checker.module.exprs[value].type) {
|
|
||||||
id := source.add(
|
|
||||||
checker.diagnostics,
|
|
||||||
statement.span,
|
|
||||||
"cannot assign a void expression to '_'",
|
|
||||||
)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Trap,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = true
|
|
||||||
} else {
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Sink,
|
|
||||||
span = statement.span,
|
|
||||||
expr = value,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
local, found := find_build_local(locals[:], statement.name)
|
|
||||||
if !found {
|
|
||||||
id := source.addf(
|
|
||||||
checker.diagnostics,
|
|
||||||
statement.span,
|
|
||||||
"cannot assign unresolved local '%s'",
|
|
||||||
symbol_text(checker, statement.name),
|
|
||||||
)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Trap,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if !local.mutable {
|
|
||||||
id := source.addf(
|
|
||||||
checker.diagnostics,
|
|
||||||
statement.span,
|
|
||||||
"cannot assign immutable local '%s'",
|
|
||||||
symbol_text(checker, statement.name),
|
|
||||||
)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Trap,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value := build_expr(
|
|
||||||
checker,
|
|
||||||
statement.expr,
|
|
||||||
locals[:],
|
|
||||||
&global_reads,
|
|
||||||
&calls,
|
|
||||||
local.type,
|
|
||||||
function.pkg,
|
|
||||||
function.file,
|
|
||||||
)
|
|
||||||
value = coerce_expr(checker, value, local.type, statement.span)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Assignment,
|
|
||||||
span = statement.span,
|
|
||||||
expr = value,
|
|
||||||
local = local.id,
|
|
||||||
target = hir.INVALID_EXPR,
|
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
||||||
case .Return:
|
|
||||||
has_return = true
|
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
|
||||||
if !types.is_void(spec.result) {
|
|
||||||
id := source.add(
|
|
||||||
checker.diagnostics,
|
|
||||||
statement.span,
|
|
||||||
"'return _' is only valid in a void function",
|
|
||||||
)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Trap,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = true
|
|
||||||
} else {
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Return,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if types.is_void(spec.result) {
|
|
||||||
id := source.add(
|
|
||||||
checker.diagnostics,
|
|
||||||
statement.span,
|
|
||||||
"void function cannot return a value",
|
|
||||||
)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Trap,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value := build_expr(
|
|
||||||
checker,
|
|
||||||
statement.expr,
|
|
||||||
locals[:],
|
|
||||||
&global_reads,
|
|
||||||
&calls,
|
|
||||||
spec.result,
|
|
||||||
function.pkg,
|
|
||||||
function.file,
|
|
||||||
)
|
|
||||||
value = coerce_expr(checker, value, spec.result, statement.span)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Return,
|
|
||||||
span = statement.span,
|
|
||||||
expr = value,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = problematic || checker.module.exprs[value].kind == .Invalid
|
|
||||||
case .Expression:
|
|
||||||
value := build_expr(checker, statement.expr, locals[:], &global_reads, &calls, types.INVALID, function.pkg, function.file)
|
|
||||||
if !types.is_void(checker.module.exprs[value].type) {
|
|
||||||
id := source.add(
|
|
||||||
checker.diagnostics,
|
|
||||||
statement.span,
|
|
||||||
"non-void expression result must be consumed or assigned to '_'",
|
|
||||||
)
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Trap,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = id,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = true
|
|
||||||
} else {
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Expression,
|
|
||||||
span = statement.span,
|
|
||||||
expr = value,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = source.INVALID_DIAGNOSTIC,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case .Invalid:
|
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
|
||||||
append(
|
|
||||||
&checker.module.statements,
|
|
||||||
hir.Stmt {
|
|
||||||
kind = .Trap,
|
|
||||||
span = statement.span,
|
|
||||||
expr = hir.INVALID_EXPR,
|
|
||||||
local = hir.INVALID_LOCAL,
|
|
||||||
diagnostic = statement.diagnostic,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
problematic = true
|
|
||||||
}
|
}
|
||||||
|
block := build_block(&ctx, function.body)
|
||||||
|
for block_stmt in block {
|
||||||
|
append(&body, block_stmt)
|
||||||
}
|
}
|
||||||
|
delete(block, checker.allocator)
|
||||||
|
|
||||||
if !types.is_void(spec.result) && !has_return {
|
if !types.is_void(spec.result) && !has_return {
|
||||||
id := source.addf(
|
id := source.addf(
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Integer,
|
Integer,
|
||||||
Float,
|
Float,
|
||||||
String,
|
String,
|
||||||
|
Bool,
|
||||||
Array,
|
Array,
|
||||||
Struct,
|
Struct,
|
||||||
None,
|
None,
|
||||||
@@ -99,8 +100,17 @@ Expr_Kind :: enum u8 {
|
|||||||
Weaken_Slice,
|
Weaken_Slice,
|
||||||
Decay_Array_Pointer,
|
Decay_Array_Pointer,
|
||||||
Negate,
|
Negate,
|
||||||
|
Not,
|
||||||
Add,
|
Add,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
|
Eq,
|
||||||
|
Ne,
|
||||||
|
Lt,
|
||||||
|
Le,
|
||||||
|
Gt,
|
||||||
|
Ge,
|
||||||
|
And,
|
||||||
|
Or,
|
||||||
Call,
|
Call,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,6 +140,7 @@ Stmt_Kind :: enum u8 {
|
|||||||
Expression,
|
Expression,
|
||||||
Sink,
|
Sink,
|
||||||
Trap,
|
Trap,
|
||||||
|
If,
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt :: struct {
|
Stmt :: struct {
|
||||||
@@ -138,6 +149,10 @@ Stmt :: struct {
|
|||||||
local: Local_Id,
|
local: Local_Id,
|
||||||
target: Expr_Id,
|
target: Expr_Id,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
|
// `If` statements use `expr` as the condition and `then_body`/`else_body` as
|
||||||
|
// the branch statement lists.
|
||||||
|
then_body: []Stmt_Id,
|
||||||
|
else_body: []Stmt_Id,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +219,10 @@ destroy_module :: proc(module: ^Module) {
|
|||||||
for expr in module.exprs {
|
for expr in module.exprs {
|
||||||
delete(expr.args, module.allocator)
|
delete(expr.args, module.allocator)
|
||||||
}
|
}
|
||||||
|
for statement in module.statements {
|
||||||
|
delete(statement.then_body, module.allocator)
|
||||||
|
delete(statement.else_body, module.allocator)
|
||||||
|
}
|
||||||
for function in module.functions {
|
for function in module.functions {
|
||||||
delete(function.link_name, module.allocator)
|
delete(function.link_name, module.allocator)
|
||||||
delete(function.params, module.allocator)
|
delete(function.params, module.allocator)
|
||||||
|
|||||||
@@ -95,12 +95,27 @@ Opcode :: enum u8 {
|
|||||||
Neg_Checked,
|
Neg_Checked,
|
||||||
Add_Checked,
|
Add_Checked,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
|
Not,
|
||||||
|
Compare,
|
||||||
|
Label,
|
||||||
|
Br,
|
||||||
|
Cond_Br,
|
||||||
Call,
|
Call,
|
||||||
Trap,
|
Trap,
|
||||||
Return,
|
Return,
|
||||||
Return_Void,
|
Return_Void,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compare_Predicate is stored in Instruction.integer for the Compare opcode.
|
||||||
|
Compare_Predicate :: enum u8 {
|
||||||
|
Eq,
|
||||||
|
Ne,
|
||||||
|
Lt,
|
||||||
|
Le,
|
||||||
|
Gt,
|
||||||
|
Ge,
|
||||||
|
}
|
||||||
|
|
||||||
Instruction :: struct {
|
Instruction :: struct {
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
type: types.Type,
|
type: types.Type,
|
||||||
|
|||||||
@@ -23,7 +23,14 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "mut": return .Keyword_Mut
|
case "mut": return .Keyword_Mut
|
||||||
case "none": return .Keyword_None
|
case "none": return .Keyword_None
|
||||||
case "orelse": return .Keyword_Orelse
|
case "orelse": return .Keyword_Orelse
|
||||||
|
case "and": return .Keyword_And
|
||||||
|
case "or": return .Keyword_Or
|
||||||
|
case "if": return .Keyword_If
|
||||||
|
case "else": return .Keyword_Else
|
||||||
|
case "true": return .Keyword_True
|
||||||
|
case "false": return .Keyword_False
|
||||||
case "void": return .Keyword_Void
|
case "void": return .Keyword_Void
|
||||||
|
case "bool": return .Keyword_Bool
|
||||||
case "int": return .Keyword_Int
|
case "int": return .Keyword_Int
|
||||||
case "i8": return .Keyword_I8
|
case "i8": return .Keyword_I8
|
||||||
case "i16": return .Keyword_I16
|
case "i16": return .Keyword_I16
|
||||||
@@ -106,8 +113,41 @@ lex :: proc(
|
|||||||
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
|
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
|
||||||
}
|
}
|
||||||
case '=':
|
case '=':
|
||||||
append_token(&stream, source_file, .Equal, cursor, cursor+1)
|
start := cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Equal_Equal, start, cursor)
|
||||||
|
} else {
|
||||||
|
append_token(&stream, source_file, .Equal, start, cursor)
|
||||||
|
}
|
||||||
|
case '!':
|
||||||
|
start := cursor
|
||||||
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Bang_Equal, start, cursor)
|
||||||
|
} else {
|
||||||
|
append_token(&stream, source_file, .Bang, start, cursor)
|
||||||
|
}
|
||||||
|
case '<':
|
||||||
|
start := cursor
|
||||||
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Less_Equal, start, cursor)
|
||||||
|
} else {
|
||||||
|
append_token(&stream, source_file, .Less, start, cursor)
|
||||||
|
}
|
||||||
|
case '>':
|
||||||
|
start := cursor
|
||||||
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
append_token(&stream, source_file, .Greater_Equal, start, cursor)
|
||||||
|
} else {
|
||||||
|
append_token(&stream, source_file, .Greater, start, cursor)
|
||||||
|
}
|
||||||
case '+':
|
case '+':
|
||||||
append_token(&stream, source_file, .Plus, cursor, cursor+1)
|
append_token(&stream, source_file, .Plus, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
|||||||
+74
-2
@@ -138,6 +138,9 @@ llvm_type :: proc(value: types.Type, store: ^types.Store = nil) -> string {
|
|||||||
if types.is_void(value) {
|
if types.is_void(value) {
|
||||||
return "void"
|
return "void"
|
||||||
}
|
}
|
||||||
|
if types.is_bool(value) {
|
||||||
|
return "i1"
|
||||||
|
}
|
||||||
#partial switch types.kind(value, store) {
|
#partial switch types.kind(value, store) {
|
||||||
case .Pointer:
|
case .Pointer:
|
||||||
return "ptr"
|
return "ptr"
|
||||||
@@ -201,6 +204,9 @@ emit_function_result :: proc(builder: ^strings.Builder, function: ir.Function, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 {
|
sentinel :: proc(value_type: types.Type, selected := target.DEFAULT) -> i64 {
|
||||||
|
if types.is_bool(value_type) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
switch types.bits(value_type, selected) {
|
switch types.bits(value_type, selected) {
|
||||||
case 8: return -86
|
case 8: return -86
|
||||||
case 16: return -21846
|
case 16: return -21846
|
||||||
@@ -228,10 +234,10 @@ valid_value :: proc(
|
|||||||
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
||||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
.Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
||||||
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
|
||||||
.Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
|
.Neg_Checked, .Add_Checked, .Pointer_Add, .Not, .Compare, .Call:
|
||||||
return true
|
return true
|
||||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||||
.Store, .Trap, .Return, .Return_Void:
|
.Store, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -262,6 +268,10 @@ write_constant :: proc(builder: ^strings.Builder, value: i64, value_type: types.
|
|||||||
strings.write_string(builder, "zeroinitializer")
|
strings.write_string(builder, "zeroinitializer")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if types.is_bool(value_type) {
|
||||||
|
strings.write_string(builder, "true" if value != 0 else "false")
|
||||||
|
return
|
||||||
|
}
|
||||||
selected := store.selected if store != nil else target.DEFAULT
|
selected := store.selected if store != nil else target.DEFAULT
|
||||||
if types.is_float(value_type, selected) {
|
if types.is_float(value_type, selected) {
|
||||||
text := ""
|
text := ""
|
||||||
@@ -462,6 +472,30 @@ emit_unpack_c_record :: proc(
|
|||||||
fmt.sbprintf(&emitter.builder, " %s = load %s, ptr %%abi_unpack_slot%d\n", result_name, llvm_type(value_type, &emitter.module.types), tag)
|
fmt.sbprintf(&emitter.builder, " %s = load %s, ptr %%abi_unpack_slot%d\n", result_name, llvm_type(value_type, &emitter.module.types), tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
integer_predicate :: proc(predicate: ir.Compare_Predicate, signed: bool) -> string {
|
||||||
|
switch predicate {
|
||||||
|
case .Eq: return "eq"
|
||||||
|
case .Ne: return "ne"
|
||||||
|
case .Lt: return "slt" if signed else "ult"
|
||||||
|
case .Le: return "sle" if signed else "ule"
|
||||||
|
case .Gt: return "sgt" if signed else "ugt"
|
||||||
|
case .Ge: return "sge" if signed else "uge"
|
||||||
|
}
|
||||||
|
return "eq"
|
||||||
|
}
|
||||||
|
|
||||||
|
float_predicate :: proc(predicate: ir.Compare_Predicate) -> string {
|
||||||
|
switch predicate {
|
||||||
|
case .Eq: return "oeq"
|
||||||
|
case .Ne: return "une"
|
||||||
|
case .Lt: return "olt"
|
||||||
|
case .Le: return "ole"
|
||||||
|
case .Gt: return "ogt"
|
||||||
|
case .Ge: return "oge"
|
||||||
|
}
|
||||||
|
return "oeq"
|
||||||
|
}
|
||||||
|
|
||||||
emit_instruction_stream :: proc(
|
emit_instruction_stream :: proc(
|
||||||
emitter: ^Emitter,
|
emitter: ^Emitter,
|
||||||
instructions: []ir.Instruction,
|
instructions: []ir.Instruction,
|
||||||
@@ -1335,6 +1369,44 @@ emit_instruction_stream :: proc(
|
|||||||
100000+instruction_index,
|
100000+instruction_index,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case .Not:
|
||||||
|
if !valid_value(instructions, instruction.a, types.BOOL, &emitter.module.types) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid '!' operand")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = xor i1 ", instruction_index)
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, types.BOOL, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ", true\n")
|
||||||
|
case .Compare:
|
||||||
|
operand_type := instructions[instruction.a].type if valid_instruction(instructions, instruction.a) else types.INVALID
|
||||||
|
if !valid_value(instructions, instruction.a, operand_type, &emitter.module.types) ||
|
||||||
|
!valid_value(instructions, instruction.b, operand_type, &emitter.module.types) {
|
||||||
|
emit_recovery_value(emitter, instruction_index, instruction, "invalid comparison operand")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
predicate := ir.Compare_Predicate(instruction.integer)
|
||||||
|
type_name := llvm_type(operand_type, &emitter.module.types)
|
||||||
|
if types.is_float(operand_type, emitter.module.target) {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = fcmp %s %s ", instruction_index, float_predicate(predicate), type_name)
|
||||||
|
} else {
|
||||||
|
fmt.sbprintf(&emitter.builder, " %%v%d = icmp %s %s ", instruction_index, integer_predicate(predicate, types.is_signed(operand_type, emitter.module.target)), type_name)
|
||||||
|
}
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, operand_type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, ", ")
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.b, operand_type, &emitter.module.types)
|
||||||
|
strings.write_string(&emitter.builder, "\n")
|
||||||
|
case .Label:
|
||||||
|
fmt.sbprintf(&emitter.builder, "bro_block_%d:\n", instruction.integer)
|
||||||
|
case .Br:
|
||||||
|
fmt.sbprintf(&emitter.builder, " br label %%bro_block_%d\n", instruction.integer)
|
||||||
|
case .Cond_Br:
|
||||||
|
if !valid_value(instructions, instruction.a, types.BOOL, &emitter.module.types) {
|
||||||
|
fmt.sbprintf(&emitter.builder, " br label %%bro_block_%d\n", u32(instruction.target))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
strings.write_string(&emitter.builder, " br i1 ")
|
||||||
|
write_operand(&emitter.builder, instructions, instruction.a, types.BOOL, &emitter.module.types)
|
||||||
|
fmt.sbprintf(&emitter.builder, ", label %%bro_block_%d, label %%bro_block_%d\n", instruction.integer, u32(instruction.target))
|
||||||
case .Trap:
|
case .Trap:
|
||||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
||||||
emit_trap_call(emitter, message)
|
emit_trap_call(emitter, message)
|
||||||
|
|||||||
+229
-100
@@ -13,10 +13,19 @@ State :: struct {
|
|||||||
instructions: [dynamic]ir.Instruction,
|
instructions: [dynamic]ir.Instruction,
|
||||||
local_values: []ir.Instruction_Id,
|
local_values: []ir.Instruction_Id,
|
||||||
local_slots: []ir.Instruction_Id,
|
local_slots: []ir.Instruction_Id,
|
||||||
|
func_locals: []hir.Local,
|
||||||
|
func_result: types.Type,
|
||||||
expr_stack: [dynamic]Lower_Expr_Frame,
|
expr_stack: [dynamic]Lower_Expr_Frame,
|
||||||
|
next_label: i64,
|
||||||
allocator: mem.Allocator,
|
allocator: mem.Allocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fresh_label :: proc(state: ^State) -> i64 {
|
||||||
|
id := state.next_label
|
||||||
|
state.next_label += 1
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
append_instruction :: proc(state: ^State, instruction: ir.Instruction) -> ir.Instruction_Id {
|
append_instruction :: proc(state: ^State, instruction: ir.Instruction) -> ir.Instruction_Id {
|
||||||
id := ir.instruction_id(len(state.instructions))
|
id := ir.instruction_id(len(state.instructions))
|
||||||
append(&state.instructions, instruction)
|
append(&state.instructions, instruction)
|
||||||
@@ -265,6 +274,77 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
target=ir.INVALID_REF, a=begin, b=fallback,
|
target=ir.INVALID_REF, a=begin, b=fallback,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
case .Not:
|
||||||
|
value := lower_nested_expr(state, expr.left)
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Not, span=expr.span, type=types.BOOL,
|
||||||
|
target=ir.INVALID_REF, a=value, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
|
||||||
|
left := lower_nested_expr(state, expr.left)
|
||||||
|
right := lower_nested_expr(state, expr.right)
|
||||||
|
predicate := ir.Compare_Predicate.Eq
|
||||||
|
#partial switch expr.kind {
|
||||||
|
case .Eq: predicate = .Eq
|
||||||
|
case .Ne: predicate = .Ne
|
||||||
|
case .Lt: predicate = .Lt
|
||||||
|
case .Le: predicate = .Le
|
||||||
|
case .Gt: predicate = .Gt
|
||||||
|
case .Ge: predicate = .Ge
|
||||||
|
}
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Compare, span=expr.span, type=types.BOOL, integer=i64(predicate),
|
||||||
|
target=ir.INVALID_REF, a=left, b=right,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .And, .Or:
|
||||||
|
// Short-circuit via a bool slot: store the left operand, branch on it, and
|
||||||
|
// only evaluate/store the right operand when needed. Avoids phi nodes.
|
||||||
|
slot := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Alloca, span=expr.span, type=types.BOOL,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
left := lower_nested_expr(state, expr.left)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=expr.span, type=types.BOOL,
|
||||||
|
target=ir.INVALID_REF, a=slot, b=left, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
rhs_lbl := fresh_label(state)
|
||||||
|
done_lbl := fresh_label(state)
|
||||||
|
true_target := rhs_lbl if expr.kind == .And else done_lbl
|
||||||
|
false_target := done_lbl if expr.kind == .And else rhs_lbl
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Cond_Br, span=expr.span, type=types.VOID,
|
||||||
|
a=left, integer=true_target, target=ir.Ref(u32(false_target)),
|
||||||
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=expr.span, type=types.VOID, integer=rhs_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
right := lower_nested_expr(state, expr.right)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=expr.span, type=types.BOOL,
|
||||||
|
target=ir.INVALID_REF, a=slot, b=right, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Br, span=expr.span, type=types.VOID, integer=done_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=expr.span, type=types.VOID, integer=done_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return append_instruction(state, ir.Instruction{
|
||||||
|
op=.Load, span=expr.span, type=types.BOOL,
|
||||||
|
target=ir.INVALID_REF, a=slot, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||||
}
|
}
|
||||||
@@ -295,14 +375,15 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
case .Invalid:
|
case .Invalid:
|
||||||
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .Integer, .Float:
|
case .Integer, .Float, .Bool:
|
||||||
last = append_instruction(state, ir.Instruction{
|
last = append_instruction(state, ir.Instruction{
|
||||||
op=.Const, span=expr.span, type=expr.type, integer=expr.integer,
|
op=.Const, span=expr.span, type=expr.type, integer=expr.integer,
|
||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .String, .Array, .Struct, .None, .Optional_Some, .Address, .Deref,
|
case .String, .Array, .Struct, .None, .Optional_Some, .Address, .Deref,
|
||||||
.Index, .Slice, .Field, .Length, .Slice_Ptr, .Unwrap, .Orelse:
|
.Index, .Slice, .Field, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
||||||
|
.Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||||
last = lower_compound_expr(state, frame.expr)
|
last = lower_compound_expr(state, frame.expr)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .Local:
|
case .Local:
|
||||||
@@ -453,10 +534,155 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
return last
|
return last
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
||||||
|
hir_module := state.hir_module
|
||||||
|
for statement_id in statements {
|
||||||
|
statement := hir_module.statements[statement_id]
|
||||||
|
switch statement.kind {
|
||||||
|
case .Declaration:
|
||||||
|
value := lower_expr(state, statement.expr)
|
||||||
|
if statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(state.func_locals) {
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Trap, span=statement.span, type=types.VOID,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
local := state.func_locals[statement.local]
|
||||||
|
slot := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Alloca,
|
||||||
|
span=statement.span,
|
||||||
|
type=local.type,
|
||||||
|
target=ir.local_ref(ir.Local_Id(statement.local)),
|
||||||
|
a=ir.INVALID_INSTRUCTION,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
state.local_slots[statement.local] = slot
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store,
|
||||||
|
span=statement.span,
|
||||||
|
type=local.type,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=slot,
|
||||||
|
b=value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .Assignment:
|
||||||
|
value := lower_expr(state, statement.expr)
|
||||||
|
slot := ir.INVALID_INSTRUCTION
|
||||||
|
value_type := types.INVALID
|
||||||
|
if statement.target != hir.INVALID_EXPR {
|
||||||
|
slot = lower_location(state, statement.target, true)
|
||||||
|
if int(statement.target) < len(hir_module.exprs) {
|
||||||
|
value_type = hir_module.exprs[statement.target].type
|
||||||
|
}
|
||||||
|
} else if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) {
|
||||||
|
slot = state.local_slots[statement.local]
|
||||||
|
value_type = state.func_locals[statement.local].type
|
||||||
|
}
|
||||||
|
if slot == ir.INVALID_INSTRUCTION || !types.is_valid(value_type) {
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Trap, span=statement.span, type=types.VOID,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store,
|
||||||
|
span=statement.span,
|
||||||
|
type=value_type,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=slot,
|
||||||
|
b=value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
case .Return:
|
||||||
|
if statement.expr == hir.INVALID_EXPR {
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Return_Void,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.VOID,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=ir.INVALID_INSTRUCTION,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
value := lower_expr(state, statement.expr)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Return,
|
||||||
|
span=statement.span,
|
||||||
|
type=state.func_result,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=value,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case .Expression, .Sink:
|
||||||
|
_ = lower_expr(state, statement.expr)
|
||||||
|
case .Trap:
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Trap,
|
||||||
|
span=statement.span,
|
||||||
|
type=types.VOID,
|
||||||
|
target=ir.INVALID_REF,
|
||||||
|
a=ir.INVALID_INSTRUCTION,
|
||||||
|
b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=statement.diagnostic,
|
||||||
|
})
|
||||||
|
case .If:
|
||||||
|
cond := lower_expr(state, statement.expr)
|
||||||
|
has_else := statement.else_body != nil
|
||||||
|
then_lbl := fresh_label(state)
|
||||||
|
else_lbl := fresh_label(state) if has_else else then_lbl
|
||||||
|
merge_lbl := fresh_label(state)
|
||||||
|
false_target := else_lbl if has_else else merge_lbl
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Cond_Br, span=statement.span, type=types.VOID,
|
||||||
|
a=cond, integer=then_lbl, target=ir.Ref(u32(false_target)),
|
||||||
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=statement.span, type=types.VOID, integer=then_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
lower_statements(state, statement.then_body)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Br, span=statement.span, type=types.VOID, integer=merge_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
if has_else {
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=statement.span, type=types.VOID, integer=else_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
lower_statements(state, statement.else_body)
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Br, span=statement.span, type=types.VOID, integer=merge_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Label, span=statement.span, type=types.VOID, integer=merge_lbl,
|
||||||
|
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: mem.Allocator) -> []ir.Instruction {
|
lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: mem.Allocator) -> []ir.Instruction {
|
||||||
state := State{
|
state := State{
|
||||||
hir_module=hir_module,
|
hir_module=hir_module,
|
||||||
allocator=allocator,
|
allocator=allocator,
|
||||||
|
func_locals=function.locals,
|
||||||
|
func_result=function.result,
|
||||||
local_values=make([]ir.Instruction_Id, len(function.locals), allocator),
|
local_values=make([]ir.Instruction_Id, len(function.locals), allocator),
|
||||||
local_slots=make([]ir.Instruction_Id, len(function.locals), allocator),
|
local_slots=make([]ir.Instruction_Id, len(function.locals), allocator),
|
||||||
}
|
}
|
||||||
@@ -497,104 +723,7 @@ lower_body :: proc(hir_module: ^hir.Module, function: hir.Function, allocator: m
|
|||||||
state.local_slots[local_id] = slot
|
state.local_slots[local_id] = slot
|
||||||
}
|
}
|
||||||
|
|
||||||
for statement_id in function.body {
|
lower_statements(&state, function.body)
|
||||||
statement := hir_module.statements[statement_id]
|
|
||||||
switch statement.kind {
|
|
||||||
case .Declaration:
|
|
||||||
value := lower_expr(&state, statement.expr)
|
|
||||||
if statement.local == hir.INVALID_LOCAL || int(statement.local) >= len(function.locals) {
|
|
||||||
append_instruction(&state, ir.Instruction{
|
|
||||||
op=.Trap, span=statement.span, type=types.VOID,
|
|
||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
local := function.locals[statement.local]
|
|
||||||
slot := append_instruction(&state, ir.Instruction{
|
|
||||||
op=.Alloca,
|
|
||||||
span=statement.span,
|
|
||||||
type=local.type,
|
|
||||||
target=ir.local_ref(ir.Local_Id(statement.local)),
|
|
||||||
a=ir.INVALID_INSTRUCTION,
|
|
||||||
b=ir.INVALID_INSTRUCTION,
|
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
state.local_slots[statement.local] = slot
|
|
||||||
append_instruction(&state, ir.Instruction{
|
|
||||||
op=.Store,
|
|
||||||
span=statement.span,
|
|
||||||
type=local.type,
|
|
||||||
target=ir.INVALID_REF,
|
|
||||||
a=slot,
|
|
||||||
b=value,
|
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
case .Assignment:
|
|
||||||
value := lower_expr(&state, statement.expr)
|
|
||||||
slot := ir.INVALID_INSTRUCTION
|
|
||||||
value_type := types.INVALID
|
|
||||||
if statement.target != hir.INVALID_EXPR {
|
|
||||||
slot = lower_location(&state, statement.target, true)
|
|
||||||
if int(statement.target) < len(hir_module.exprs) {
|
|
||||||
value_type = hir_module.exprs[statement.target].type
|
|
||||||
}
|
|
||||||
} else if statement.local != hir.INVALID_LOCAL && int(statement.local) < len(state.local_slots) {
|
|
||||||
slot = state.local_slots[statement.local]
|
|
||||||
value_type = function.locals[statement.local].type
|
|
||||||
}
|
|
||||||
if slot == ir.INVALID_INSTRUCTION || !types.is_valid(value_type) {
|
|
||||||
append_instruction(&state, ir.Instruction{
|
|
||||||
op=.Trap, span=statement.span, type=types.VOID,
|
|
||||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=statement.diagnostic,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
append_instruction(&state, ir.Instruction{
|
|
||||||
op=.Store,
|
|
||||||
span=statement.span,
|
|
||||||
type=value_type,
|
|
||||||
target=ir.INVALID_REF,
|
|
||||||
a=slot,
|
|
||||||
b=value,
|
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
case .Return:
|
|
||||||
if statement.expr == hir.INVALID_EXPR {
|
|
||||||
append_instruction(&state, ir.Instruction{
|
|
||||||
op=.Return_Void,
|
|
||||||
span=statement.span,
|
|
||||||
type=types.VOID,
|
|
||||||
target=ir.INVALID_REF,
|
|
||||||
a=ir.INVALID_INSTRUCTION,
|
|
||||||
b=ir.INVALID_INSTRUCTION,
|
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
value := lower_expr(&state, statement.expr)
|
|
||||||
append_instruction(&state, ir.Instruction{
|
|
||||||
op=.Return,
|
|
||||||
span=statement.span,
|
|
||||||
type=function.result,
|
|
||||||
target=ir.INVALID_REF,
|
|
||||||
a=value,
|
|
||||||
b=ir.INVALID_INSTRUCTION,
|
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
case .Expression, .Sink:
|
|
||||||
_ = lower_expr(&state, statement.expr)
|
|
||||||
case .Trap:
|
|
||||||
append_instruction(&state, ir.Instruction{
|
|
||||||
op=.Trap,
|
|
||||||
span=statement.span,
|
|
||||||
type=types.VOID,
|
|
||||||
target=ir.INVALID_REF,
|
|
||||||
a=ir.INVALID_INSTRUCTION,
|
|
||||||
b=ir.INVALID_INSTRUCTION,
|
|
||||||
diagnostic=statement.diagnostic,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(state.instructions) == 0 ||
|
if len(state.instructions) == 0 ||
|
||||||
(state.instructions[len(state.instructions)-1].op != .Return &&
|
(state.instructions[len(state.instructions)-1].op != .Return &&
|
||||||
state.instructions[len(state.instructions)-1].op != .Return_Void) {
|
state.instructions[len(state.instructions)-1].op != .Return_Void) {
|
||||||
|
|||||||
+124
-30
@@ -20,6 +20,11 @@ Parser :: struct {
|
|||||||
file: ast.File_Id,
|
file: ast.File_Id,
|
||||||
cursor: int,
|
cursor: int,
|
||||||
delimiter_depth: int,
|
delimiter_depth: int,
|
||||||
|
// Suppresses `Name { ... }` struct-literal parsing at delimiter depth 0 so a
|
||||||
|
// control-flow condition like `if foo { ... }` does not swallow the block as a
|
||||||
|
// struct literal. Nested `(`/`[`/call-arg contexts (delimiter_depth > 0) still
|
||||||
|
// allow struct literals.
|
||||||
|
no_struct_literal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_EXPRESSION_NESTING :: 256
|
MAX_EXPRESSION_NESTING :: 256
|
||||||
@@ -90,7 +95,7 @@ is_type_token :: proc(kind: token.Kind) -> bool {
|
|||||||
.Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
|
.Keyword_C_Short, .Keyword_C_Ushort, .Keyword_C_Int, .Keyword_C_Uint,
|
||||||
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
|
.Keyword_C_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
|
||||||
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble,
|
.Keyword_C_Float, .Keyword_C_Double, .Keyword_C_Longdouble,
|
||||||
.Keyword_Void, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket:
|
.Keyword_Void, .Keyword_Bool, .Keyword_C_Func, .Identifier, .Question, .At, .Star, .Left_Bracket:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@@ -291,6 +296,9 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
|||||||
case .Keyword_Void:
|
case .Keyword_Void:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
return types.VOID
|
return types.VOID
|
||||||
|
case .Keyword_Bool:
|
||||||
|
advance(parser)
|
||||||
|
return types.BOOL
|
||||||
case .Keyword_C_Func:
|
case .Keyword_C_Func:
|
||||||
advance(parser)
|
advance(parser)
|
||||||
if _, ok := allow(parser, .Left_Paren); !ok {
|
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||||
@@ -567,6 +575,16 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
right=ast.INVALID_EXPR,
|
right=ast.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
|
case .Keyword_True, .Keyword_False:
|
||||||
|
advance(parser)
|
||||||
|
return add_expr(parser, ast.Expr{
|
||||||
|
kind=.Bool,
|
||||||
|
span=tok.span,
|
||||||
|
integer=1 if tok.kind == .Keyword_True else 0,
|
||||||
|
left=ast.INVALID_EXPR,
|
||||||
|
right=ast.INVALID_EXPR,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
case .Left_Bracket:
|
case .Left_Bracket:
|
||||||
return parse_array_literal(parser, nesting)
|
return parse_array_literal(parser, nesting)
|
||||||
case .Identifier:
|
case .Identifier:
|
||||||
@@ -583,7 +601,7 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
if current(parser).kind == .Left_Paren {
|
if current(parser).kind == .Left_Paren {
|
||||||
return parse_call(parser, qualifier, first, name, nesting)
|
return parse_call(parser, qualifier, first, name, nesting)
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Left_Brace {
|
if current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
|
||||||
return parse_struct_literal(parser, qualifier, first, name, nesting)
|
return parse_struct_literal(parser, qualifier, first, name, nesting)
|
||||||
}
|
}
|
||||||
return add_expr(parser, ast.Expr{
|
return add_expr(parser, ast.Expr{
|
||||||
@@ -633,15 +651,36 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
|
|||||||
#partial switch kind {
|
#partial switch kind {
|
||||||
case .Keyword_Orelse:
|
case .Keyword_Orelse:
|
||||||
return 2, 3, true
|
return 2, 3, true
|
||||||
|
case .Keyword_Or:
|
||||||
|
return 4, 5, true
|
||||||
|
case .Keyword_And:
|
||||||
|
return 6, 7, true
|
||||||
|
case .Equal_Equal, .Bang_Equal, .Less, .Less_Equal, .Greater, .Greater_Equal:
|
||||||
|
return 8, 9, true
|
||||||
case .Plus:
|
case .Plus:
|
||||||
return 10, 11, true
|
return 10, 11, true
|
||||||
}
|
}
|
||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
|
||||||
|
#partial switch kind {
|
||||||
|
case .Keyword_Orelse: return .Orelse
|
||||||
|
case .Keyword_Or: return .Or
|
||||||
|
case .Keyword_And: return .And
|
||||||
|
case .Equal_Equal: return .Eq
|
||||||
|
case .Bang_Equal: return .Ne
|
||||||
|
case .Less: return .Lt
|
||||||
|
case .Less_Equal: return .Le
|
||||||
|
case .Greater: return .Gt
|
||||||
|
case .Greater_Equal: return .Ge
|
||||||
|
case: return .Add
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
|
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
|
||||||
#partial switch kind {
|
#partial switch kind {
|
||||||
case .Minus, .Ampersand:
|
case .Minus, .Ampersand, .Bang:
|
||||||
return 20, true
|
return 20, true
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
@@ -663,8 +702,13 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
}
|
}
|
||||||
operand := parse_expression_bp(parser, right_power, nesting+1)
|
operand := parse_expression_bp(parser, right_power, nesting+1)
|
||||||
operand_expr := parser.module.exprs[operand]
|
operand_expr := parser.module.exprs[operand]
|
||||||
|
prefix_kind := ast.Expr_Kind.Negate
|
||||||
|
#partial switch operator.kind {
|
||||||
|
case .Ampersand: prefix_kind = .Address
|
||||||
|
case .Bang: prefix_kind = .Not
|
||||||
|
}
|
||||||
left = add_expr(parser, ast.Expr{
|
left = add_expr(parser, ast.Expr{
|
||||||
kind=.Address if operator.kind == .Ampersand else .Negate,
|
kind=prefix_kind,
|
||||||
span=span_from(operator.span, operand_expr.span),
|
span=span_from(operator.span, operand_expr.span),
|
||||||
left=operand,
|
left=operand,
|
||||||
right=ast.INVALID_EXPR,
|
right=ast.INVALID_EXPR,
|
||||||
@@ -787,7 +831,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
left_expr := parser.module.exprs[left]
|
left_expr := parser.module.exprs[left]
|
||||||
right_expr := parser.module.exprs[right]
|
right_expr := parser.module.exprs[right]
|
||||||
left = add_expr(parser, ast.Expr{
|
left = add_expr(parser, ast.Expr{
|
||||||
kind=.Orelse if operator.kind == .Keyword_Orelse else .Add,
|
kind=infix_expr_kind(operator.kind),
|
||||||
span=span_from(left_expr.span, right_expr.span),
|
span=span_from(left_expr.span, right_expr.span),
|
||||||
left=left,
|
left=left,
|
||||||
right=right,
|
right=right,
|
||||||
@@ -881,6 +925,9 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
if current(parser).kind == .Keyword_Return {
|
if current(parser).kind == .Keyword_Return {
|
||||||
return parse_return(parser)
|
return parse_return(parser)
|
||||||
}
|
}
|
||||||
|
if current(parser).kind == .Keyword_If {
|
||||||
|
return parse_if(parser)
|
||||||
|
}
|
||||||
|
|
||||||
if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
|
if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
|
||||||
start_cursor := parser.cursor
|
start_cursor := parser.cursor
|
||||||
@@ -995,8 +1042,76 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
|
|||||||
return params[:], variadic
|
return params[:], variadic
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parse_block parses a brace-delimited statement sequence `{ ... }`, consuming
|
||||||
|
// both braces, and returns the contained statement ids. Shared by function
|
||||||
|
// bodies and control-flow blocks.
|
||||||
|
parse_block :: proc(parser: ^Parser) -> []ast.Stmt_Id {
|
||||||
|
body: [dynamic]ast.Stmt_Id
|
||||||
|
body.allocator = parser.module.allocator
|
||||||
|
if _, ok := allow(parser, .Left_Brace); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '{' to open a block")
|
||||||
|
return body[:]
|
||||||
|
}
|
||||||
|
skip_newlines(parser)
|
||||||
|
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
||||||
|
append(&body, parse_statement(parser))
|
||||||
|
if diagnostic := finish_statement(parser, true); diagnostic != source.INVALID_DIAGNOSTIC {
|
||||||
|
statement_id := ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.Invalid,
|
||||||
|
span=current(parser).span,
|
||||||
|
expr=ast.INVALID_EXPR,
|
||||||
|
diagnostic=diagnostic,
|
||||||
|
})
|
||||||
|
append(&body, statement_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, ok := allow(parser, .Right_Brace); !ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '}' to close a block")
|
||||||
|
}
|
||||||
|
return body[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||||
|
start := advance(parser) // consume 'if'
|
||||||
|
skip_newlines(parser)
|
||||||
|
saved := parser.no_struct_literal
|
||||||
|
parser.no_struct_literal = true
|
||||||
|
condition := parse_expression(parser)
|
||||||
|
parser.no_struct_literal = saved
|
||||||
|
skip_newlines(parser)
|
||||||
|
then_body := parse_block(parser)
|
||||||
|
else_body: []ast.Stmt_Id = nil
|
||||||
|
saved_cursor := parser.cursor
|
||||||
|
skip_newlines(parser)
|
||||||
|
if current(parser).kind == .Keyword_Else {
|
||||||
|
advance(parser)
|
||||||
|
skip_newlines(parser)
|
||||||
|
if current(parser).kind == .Keyword_If {
|
||||||
|
nested := parse_if(parser)
|
||||||
|
single := make([]ast.Stmt_Id, 1, parser.module.allocator)
|
||||||
|
single[0] = nested
|
||||||
|
else_body = single
|
||||||
|
} else {
|
||||||
|
else_body = parse_block(parser)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parser.cursor = saved_cursor
|
||||||
|
}
|
||||||
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
|
append(&parser.module.statements, ast.Stmt{
|
||||||
|
kind=.If,
|
||||||
|
span=span_from(start.span, previous(parser).span),
|
||||||
|
expr=condition,
|
||||||
|
body=then_body,
|
||||||
|
else_body=else_body,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||||
func_token := advance(parser)
|
advance(parser)
|
||||||
if _, ok := allow(parser, .Left_Paren); !ok {
|
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
|
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
|
||||||
}
|
}
|
||||||
@@ -1030,29 +1145,8 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
advance(parser)
|
body := parse_block(parser)
|
||||||
|
end = previous(parser)
|
||||||
body: [dynamic]ast.Stmt_Id
|
|
||||||
body.allocator = parser.module.allocator
|
|
||||||
skip_newlines(parser)
|
|
||||||
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
|
||||||
append(&body, parse_statement(parser))
|
|
||||||
if diagnostic := finish_statement(parser, true); diagnostic != source.INVALID_DIAGNOSTIC {
|
|
||||||
statement_id := ast.stmt_id(len(parser.module.statements))
|
|
||||||
append(&parser.module.statements, ast.Stmt{
|
|
||||||
kind=.Invalid,
|
|
||||||
span=current(parser).span,
|
|
||||||
expr=ast.INVALID_EXPR,
|
|
||||||
diagnostic=diagnostic,
|
|
||||||
})
|
|
||||||
append(&body, statement_id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end = current(parser)
|
|
||||||
if _, ok := allow(parser, .Right_Brace); !ok {
|
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '}' after function body")
|
|
||||||
end = func_token
|
|
||||||
}
|
|
||||||
_ = ast.function_id(len(parser.module.functions))
|
_ = ast.function_id(len(parser.module.functions))
|
||||||
append(&parser.module.functions, ast.Function{
|
append(&parser.module.functions, ast.Function{
|
||||||
span=span_from(name.span, end.span),
|
span=span_from(name.span, end.span),
|
||||||
@@ -1064,7 +1158,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
variadic=variadic,
|
variadic=variadic,
|
||||||
params=params,
|
params=params,
|
||||||
result=result,
|
result=result,
|
||||||
body=body[:],
|
body=body,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,13 @@ Kind :: enum u8 {
|
|||||||
Underscore,
|
Underscore,
|
||||||
Colon_Colon,
|
Colon_Colon,
|
||||||
Equal,
|
Equal,
|
||||||
|
Equal_Equal,
|
||||||
|
Bang,
|
||||||
|
Bang_Equal,
|
||||||
|
Less,
|
||||||
|
Less_Equal,
|
||||||
|
Greater,
|
||||||
|
Greater_Equal,
|
||||||
Plus,
|
Plus,
|
||||||
Minus,
|
Minus,
|
||||||
Dot,
|
Dot,
|
||||||
@@ -42,7 +49,14 @@ Kind :: enum u8 {
|
|||||||
Keyword_Mut,
|
Keyword_Mut,
|
||||||
Keyword_None,
|
Keyword_None,
|
||||||
Keyword_Orelse,
|
Keyword_Orelse,
|
||||||
|
Keyword_And,
|
||||||
|
Keyword_Or,
|
||||||
|
Keyword_If,
|
||||||
|
Keyword_Else,
|
||||||
|
Keyword_True,
|
||||||
|
Keyword_False,
|
||||||
Keyword_Void,
|
Keyword_Void,
|
||||||
|
Keyword_Bool,
|
||||||
Keyword_Int,
|
Keyword_Int,
|
||||||
Keyword_I8,
|
Keyword_I8,
|
||||||
Keyword_I16,
|
Keyword_I16,
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ C_FLOAT :: Type(26)
|
|||||||
C_DOUBLE :: Type(27)
|
C_DOUBLE :: Type(27)
|
||||||
C_LONGDOUBLE :: Type(28)
|
C_LONGDOUBLE :: Type(28)
|
||||||
|
|
||||||
|
BOOL :: Type(29)
|
||||||
|
|
||||||
DYNAMIC_START :: Type(64)
|
DYNAMIC_START :: Type(64)
|
||||||
|
|
||||||
Numeric_Category :: enum u8 {
|
Numeric_Category :: enum u8 {
|
||||||
@@ -236,6 +238,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
|||||||
return .Void
|
return .Void
|
||||||
case INT:
|
case INT:
|
||||||
return .Int_Constraint
|
return .Int_Constraint
|
||||||
|
case BOOL:
|
||||||
|
return .Scalar
|
||||||
}
|
}
|
||||||
if value >= I8 && value <= C_LONGDOUBLE {
|
if value >= I8 && value <= C_LONGDOUBLE {
|
||||||
return .Scalar
|
return .Scalar
|
||||||
@@ -268,6 +272,10 @@ is_void :: proc(value: Type) -> bool {
|
|||||||
return value == VOID
|
return value == VOID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_bool :: proc(value: Type) -> bool {
|
||||||
|
return value == BOOL
|
||||||
|
}
|
||||||
|
|
||||||
is_constraint :: proc(value: Type) -> bool {
|
is_constraint :: proc(value: Type) -> bool {
|
||||||
return value == INT
|
return value == INT
|
||||||
}
|
}
|
||||||
@@ -320,6 +328,8 @@ category :: proc(value: Type, selected := target.DEFAULT) -> Numeric_Category {
|
|||||||
|
|
||||||
bits :: proc(value: Type, selected := target.DEFAULT) -> int {
|
bits :: proc(value: Type, selected := target.DEFAULT) -> int {
|
||||||
switch value {
|
switch value {
|
||||||
|
case BOOL:
|
||||||
|
return 1
|
||||||
case I8, U8:
|
case I8, U8:
|
||||||
return 8
|
return 8
|
||||||
case I16, U16:
|
case I16, U16:
|
||||||
@@ -945,6 +955,7 @@ name :: proc(value: Type) -> string {
|
|||||||
switch value {
|
switch value {
|
||||||
case INVALID: return "<invalid>"
|
case INVALID: return "<invalid>"
|
||||||
case VOID: return "void"
|
case VOID: return "void"
|
||||||
|
case BOOL: return "bool"
|
||||||
case INT: return "int"
|
case INT: return "int"
|
||||||
case I8: return "i8"
|
case I8: return "i8"
|
||||||
case I16: return "i16"
|
case I16: return "i16"
|
||||||
|
|||||||
@@ -1793,6 +1793,27 @@ sentinel_pointer_views_compile_and_run :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 303)
|
testing.expect_value(t, state.exit_code, 303)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
control_flow_compiles_and_runs :: proc(t: ^testing.T) {
|
||||||
|
output := "/tmp/brolang-test-control-flow"
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
status := compiler_core.compile_package("examples/programs/control_flow", output)
|
||||||
|
testing.expect_value(t, status, 0)
|
||||||
|
state, stdout, stderr, _ := os2.process_exec(
|
||||||
|
os2.Process_Desc{command=[]string{output}},
|
||||||
|
context.allocator,
|
||||||
|
)
|
||||||
|
defer delete(stdout)
|
||||||
|
defer delete(stderr)
|
||||||
|
// if / else if / else, comparisons, logical and/or/not, bool locals, and
|
||||||
|
// block scoping together produce 42.
|
||||||
|
testing.expect_value(t, state.exit_code, 42)
|
||||||
|
// Short-circuit: `noisy()` is never reached, so its output must be absent,
|
||||||
|
// while the taken or-branch must print.
|
||||||
|
testing.expect(t, !strings.contains(string(stdout), "rhs-evaluated"))
|
||||||
|
testing.expect(t, strings.contains(string(stdout), "or-taken"))
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
foreign_function_links_from_c_source :: proc(t: ^testing.T) {
|
foreign_function_links_from_c_source :: proc(t: ^testing.T) {
|
||||||
output := "/tmp/brolang-test-foreign-source"
|
output := "/tmp/brolang-test-foreign-source"
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# Milestone 5 foundation: booleans, comparisons, logical ops, if/else if/else.
|
||||||
|
|
||||||
|
printf :: c_func(format *c_char, ...) c_int
|
||||||
|
|
||||||
|
# Returns a distinct code per range using if / else if / else and comparisons.
|
||||||
|
classify :: func(n i32) i32 {
|
||||||
|
if n < 0 {
|
||||||
|
return 1
|
||||||
|
} else if n == 0 {
|
||||||
|
return 2
|
||||||
|
} else if n >= 100 {
|
||||||
|
return 3
|
||||||
|
} else {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# A bool-returning function with a visible side effect, used to prove
|
||||||
|
# short-circuit evaluation: it must only print when actually evaluated.
|
||||||
|
noisy :: func() bool {
|
||||||
|
_ = printf("rhs-evaluated\n")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
main :: func() i32 {
|
||||||
|
total i32 = 0
|
||||||
|
|
||||||
|
# comparisons drive if / else if / else
|
||||||
|
total = total + classify(-5) # 1
|
||||||
|
total = total + classify(0) # 2
|
||||||
|
total = total + classify(250) # 3
|
||||||
|
total = total + classify(42) # 4 -> 10
|
||||||
|
|
||||||
|
# bool variables and logical and / or / not
|
||||||
|
a :: true
|
||||||
|
b :: false
|
||||||
|
if a and !b {
|
||||||
|
total = total + 10 # 20
|
||||||
|
}
|
||||||
|
if b or a {
|
||||||
|
total = total + 10 # 30
|
||||||
|
}
|
||||||
|
if !(a and b) {
|
||||||
|
total = total + 5 # 35
|
||||||
|
}
|
||||||
|
|
||||||
|
# block scoping: inner x shadows outer x, outer is unchanged after the block
|
||||||
|
x i32 = 1
|
||||||
|
if x == 1 {
|
||||||
|
x i32 = 100
|
||||||
|
if x == 100 {
|
||||||
|
total = total + 5 # 40
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if x == 1 {
|
||||||
|
total = total + 2 # 42
|
||||||
|
}
|
||||||
|
|
||||||
|
# short-circuit: `false and noisy()` must NOT call noisy()
|
||||||
|
if false and noisy() {
|
||||||
|
_ = printf("unreachable-and\n")
|
||||||
|
}
|
||||||
|
# short-circuit: `true or noisy()` must NOT call noisy()
|
||||||
|
if true or noisy() {
|
||||||
|
_ = printf("or-taken\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
return total # expect exit code 42
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user