booleans, comparisons, and if/else
This commit is contained in:
@@ -81,10 +81,14 @@
|
||||
- static inline functions (implemented)
|
||||
|
||||
- 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`, `!`
|
||||
- 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 with guard clause: `if val |v : v >= 10| { ... } else { ... }` - unwrap `val` into `v` if it is not `none`
|
||||
- multi-unwrap (see section below)
|
||||
|
||||
@@ -68,6 +68,7 @@ Expr_Kind :: enum u8 {
|
||||
Integer,
|
||||
Float,
|
||||
String,
|
||||
Bool,
|
||||
Array,
|
||||
None,
|
||||
Name,
|
||||
@@ -81,7 +82,16 @@ Expr_Kind :: enum u8 {
|
||||
Struct_Literal,
|
||||
Keyed,
|
||||
Negate,
|
||||
Not,
|
||||
Add,
|
||||
Eq,
|
||||
Ne,
|
||||
Lt,
|
||||
Le,
|
||||
Gt,
|
||||
Ge,
|
||||
And,
|
||||
Or,
|
||||
Call,
|
||||
}
|
||||
|
||||
@@ -109,6 +119,7 @@ Stmt_Kind :: enum u8 {
|
||||
Assignment,
|
||||
Return,
|
||||
Expression,
|
||||
If,
|
||||
}
|
||||
|
||||
Stmt :: struct {
|
||||
@@ -119,6 +130,11 @@ Stmt :: struct {
|
||||
immutable: bool,
|
||||
target: 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,
|
||||
}
|
||||
|
||||
@@ -234,6 +250,10 @@ destroy_module :: proc(module: ^Module) {
|
||||
for expr in module.exprs {
|
||||
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 {
|
||||
delete(function.params, module.allocator)
|
||||
delete(function.body, module.allocator)
|
||||
|
||||
+421
-383
@@ -44,6 +44,23 @@ Build_Local :: struct {
|
||||
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 {
|
||||
Unknown,
|
||||
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 {
|
||||
append(&stack, expr.left)
|
||||
}
|
||||
case .Negate, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
||||
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Keyed:
|
||||
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)
|
||||
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 {
|
||||
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:
|
||||
}
|
||||
}
|
||||
mark_block_imports_used(checker, function.body, function.file)
|
||||
delete(locals)
|
||||
}
|
||||
for function, function_id in checker.ast_module.functions {
|
||||
@@ -988,6 +1013,15 @@ infer_compound_expr :: proc(
|
||||
) -> types.Type {
|
||||
store := &checker.module.types
|
||||
#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:
|
||||
return string_literal_type(checker, expr.integer)
|
||||
case .Array:
|
||||
@@ -1124,7 +1158,8 @@ infer_expr :: proc(
|
||||
last = types.F64
|
||||
_ = pop(&stack)
|
||||
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)
|
||||
_ = pop(&stack)
|
||||
case .Name:
|
||||
@@ -1342,6 +1377,49 @@ infer_expr :: proc(
|
||||
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 {
|
||||
spec := checker.specs[id]
|
||||
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
|
||||
for statement_id in function.body {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
infer_statements(checker, function.body, &locals, function.pkg, function.file, demanded, &result)
|
||||
if types.is_constraint(declared) {
|
||||
return result
|
||||
}
|
||||
@@ -2116,6 +2169,90 @@ build_compound_expr :: proc(
|
||||
kind=.Orelse, span=expr.span, type=child, left=optional, right=fallback,
|
||||
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:
|
||||
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
|
||||
@@ -2230,7 +2367,8 @@ build_expr :: proc(
|
||||
}
|
||||
switch expr.kind {
|
||||
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(
|
||||
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)
|
||||
}
|
||||
|
||||
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) {
|
||||
spec := checker.specs[id]
|
||||
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 {
|
||||
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,
|
||||
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
|
||||
}
|
||||
ctx := Build_Ctx{
|
||||
checker = checker,
|
||||
pkg = function.pkg,
|
||||
file = function.file,
|
||||
result = spec.result,
|
||||
locals = &locals,
|
||||
hir_locals = &hir_locals,
|
||||
global_reads = &global_reads,
|
||||
calls = &calls,
|
||||
problematic = &problematic,
|
||||
has_return = &has_return,
|
||||
}
|
||||
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 {
|
||||
id := source.addf(
|
||||
|
||||
@@ -77,6 +77,7 @@ Expr_Kind :: enum u8 {
|
||||
Integer,
|
||||
Float,
|
||||
String,
|
||||
Bool,
|
||||
Array,
|
||||
Struct,
|
||||
None,
|
||||
@@ -99,8 +100,17 @@ Expr_Kind :: enum u8 {
|
||||
Weaken_Slice,
|
||||
Decay_Array_Pointer,
|
||||
Negate,
|
||||
Not,
|
||||
Add,
|
||||
Pointer_Add,
|
||||
Eq,
|
||||
Ne,
|
||||
Lt,
|
||||
Le,
|
||||
Gt,
|
||||
Ge,
|
||||
And,
|
||||
Or,
|
||||
Call,
|
||||
}
|
||||
|
||||
@@ -130,6 +140,7 @@ Stmt_Kind :: enum u8 {
|
||||
Expression,
|
||||
Sink,
|
||||
Trap,
|
||||
If,
|
||||
}
|
||||
|
||||
Stmt :: struct {
|
||||
@@ -138,6 +149,10 @@ Stmt :: struct {
|
||||
local: Local_Id,
|
||||
target: 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,
|
||||
}
|
||||
|
||||
@@ -204,6 +219,10 @@ destroy_module :: proc(module: ^Module) {
|
||||
for expr in module.exprs {
|
||||
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 {
|
||||
delete(function.link_name, module.allocator)
|
||||
delete(function.params, module.allocator)
|
||||
|
||||
@@ -95,12 +95,27 @@ Opcode :: enum u8 {
|
||||
Neg_Checked,
|
||||
Add_Checked,
|
||||
Pointer_Add,
|
||||
Not,
|
||||
Compare,
|
||||
Label,
|
||||
Br,
|
||||
Cond_Br,
|
||||
Call,
|
||||
Trap,
|
||||
Return,
|
||||
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 {
|
||||
span: source.Span,
|
||||
type: types.Type,
|
||||
|
||||
@@ -23,7 +23,14 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
||||
case "mut": return .Keyword_Mut
|
||||
case "none": return .Keyword_None
|
||||
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 "bool": return .Keyword_Bool
|
||||
case "int": return .Keyword_Int
|
||||
case "i8": return .Keyword_I8
|
||||
case "i16": return .Keyword_I16
|
||||
@@ -106,8 +113,41 @@ lex :: proc(
|
||||
append_token(&stream, source_file, .Invalid, start, cursor, diagnostic=id)
|
||||
}
|
||||
case '=':
|
||||
append_token(&stream, source_file, .Equal, cursor, cursor+1)
|
||||
start := cursor
|
||||
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 '+':
|
||||
append_token(&stream, source_file, .Plus, cursor, 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) {
|
||||
return "void"
|
||||
}
|
||||
if types.is_bool(value) {
|
||||
return "i1"
|
||||
}
|
||||
#partial switch types.kind(value, store) {
|
||||
case .Pointer:
|
||||
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 {
|
||||
if types.is_bool(value_type) {
|
||||
return 0
|
||||
}
|
||||
switch types.bits(value_type, selected) {
|
||||
case 8: return -86
|
||||
case 16: return -21846
|
||||
@@ -228,10 +234,10 @@ valid_value :: proc(
|
||||
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
||||
.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
|
||||
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
|
||||
@@ -262,6 +268,10 @@ write_constant :: proc(builder: ^strings.Builder, value: i64, value_type: types.
|
||||
strings.write_string(builder, "zeroinitializer")
|
||||
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
|
||||
if types.is_float(value_type, selected) {
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
emitter: ^Emitter,
|
||||
instructions: []ir.Instruction,
|
||||
@@ -1335,6 +1369,44 @@ emit_instruction_stream :: proc(
|
||||
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:
|
||||
message := diagnostic_message(emitter, instruction.diagnostic, instruction.span, "invalid recovered source")
|
||||
emit_trap_call(emitter, message)
|
||||
|
||||
+229
-100
@@ -13,10 +13,19 @@ State :: struct {
|
||||
instructions: [dynamic]ir.Instruction,
|
||||
local_values: []ir.Instruction_Id,
|
||||
local_slots: []ir.Instruction_Id,
|
||||
func_locals: []hir.Local,
|
||||
func_result: types.Type,
|
||||
expr_stack: [dynamic]Lower_Expr_Frame,
|
||||
next_label: i64,
|
||||
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 {
|
||||
id := ir.instruction_id(len(state.instructions))
|
||||
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,
|
||||
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)
|
||||
}
|
||||
@@ -295,14 +375,15 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
case .Invalid:
|
||||
last = append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||
_ = pop(&stack)
|
||||
case .Integer, .Float:
|
||||
case .Integer, .Float, .Bool:
|
||||
last = append_instruction(state, ir.Instruction{
|
||||
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,
|
||||
})
|
||||
_ = pop(&stack)
|
||||
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)
|
||||
_ = pop(&stack)
|
||||
case .Local:
|
||||
@@ -453,10 +534,155 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
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 {
|
||||
state := State{
|
||||
hir_module=hir_module,
|
||||
allocator=allocator,
|
||||
func_locals=function.locals,
|
||||
func_result=function.result,
|
||||
local_values=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
|
||||
}
|
||||
|
||||
for statement_id in 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,
|
||||
})
|
||||
}
|
||||
}
|
||||
lower_statements(&state, function.body)
|
||||
if len(state.instructions) == 0 ||
|
||||
(state.instructions[len(state.instructions)-1].op != .Return &&
|
||||
state.instructions[len(state.instructions)-1].op != .Return_Void) {
|
||||
|
||||
+124
-30
@@ -20,6 +20,11 @@ Parser :: struct {
|
||||
file: ast.File_Id,
|
||||
cursor: 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
|
||||
@@ -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_Long, .Keyword_C_Ulong, .Keyword_C_Longlong, .Keyword_C_Ulonglong,
|
||||
.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 false
|
||||
@@ -291,6 +296,9 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax {
|
||||
case .Keyword_Void:
|
||||
advance(parser)
|
||||
return types.VOID
|
||||
case .Keyword_Bool:
|
||||
advance(parser)
|
||||
return types.BOOL
|
||||
case .Keyword_C_Func:
|
||||
advance(parser)
|
||||
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,
|
||||
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:
|
||||
return parse_array_literal(parser, nesting)
|
||||
case .Identifier:
|
||||
@@ -583,7 +601,7 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
||||
if current(parser).kind == .Left_Paren {
|
||||
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 add_expr(parser, ast.Expr{
|
||||
@@ -633,15 +651,36 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
|
||||
#partial switch kind {
|
||||
case .Keyword_Orelse:
|
||||
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:
|
||||
return 10, 11, true
|
||||
}
|
||||
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) {
|
||||
#partial switch kind {
|
||||
case .Minus, .Ampersand:
|
||||
case .Minus, .Ampersand, .Bang:
|
||||
return 20, true
|
||||
}
|
||||
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_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{
|
||||
kind=.Address if operator.kind == .Ampersand else .Negate,
|
||||
kind=prefix_kind,
|
||||
span=span_from(operator.span, operand_expr.span),
|
||||
left=operand,
|
||||
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]
|
||||
right_expr := parser.module.exprs[right]
|
||||
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),
|
||||
left=left,
|
||||
right=right,
|
||||
@@ -881,6 +925,9 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||
if current(parser).kind == .Keyword_Return {
|
||||
return parse_return(parser)
|
||||
}
|
||||
if current(parser).kind == .Keyword_If {
|
||||
return parse_if(parser)
|
||||
}
|
||||
|
||||
if current(parser).kind == .Identifier || current(parser).kind == .Underscore {
|
||||
start_cursor := parser.cursor
|
||||
@@ -995,8 +1042,76 @@ parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
|
||||
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) {
|
||||
func_token := advance(parser)
|
||||
advance(parser)
|
||||
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||
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
|
||||
}
|
||||
advance(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
|
||||
}
|
||||
body := parse_block(parser)
|
||||
end = previous(parser)
|
||||
_ = ast.function_id(len(parser.module.functions))
|
||||
append(&parser.module.functions, ast.Function{
|
||||
span=span_from(name.span, end.span),
|
||||
@@ -1064,7 +1158,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
||||
variadic=variadic,
|
||||
params=params,
|
||||
result=result,
|
||||
body=body[:],
|
||||
body=body,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,6 +15,13 @@ Kind :: enum u8 {
|
||||
Underscore,
|
||||
Colon_Colon,
|
||||
Equal,
|
||||
Equal_Equal,
|
||||
Bang,
|
||||
Bang_Equal,
|
||||
Less,
|
||||
Less_Equal,
|
||||
Greater,
|
||||
Greater_Equal,
|
||||
Plus,
|
||||
Minus,
|
||||
Dot,
|
||||
@@ -42,7 +49,14 @@ Kind :: enum u8 {
|
||||
Keyword_Mut,
|
||||
Keyword_None,
|
||||
Keyword_Orelse,
|
||||
Keyword_And,
|
||||
Keyword_Or,
|
||||
Keyword_If,
|
||||
Keyword_Else,
|
||||
Keyword_True,
|
||||
Keyword_False,
|
||||
Keyword_Void,
|
||||
Keyword_Bool,
|
||||
Keyword_Int,
|
||||
Keyword_I8,
|
||||
Keyword_I16,
|
||||
|
||||
@@ -40,6 +40,8 @@ C_FLOAT :: Type(26)
|
||||
C_DOUBLE :: Type(27)
|
||||
C_LONGDOUBLE :: Type(28)
|
||||
|
||||
BOOL :: Type(29)
|
||||
|
||||
DYNAMIC_START :: Type(64)
|
||||
|
||||
Numeric_Category :: enum u8 {
|
||||
@@ -236,6 +238,8 @@ kind :: proc(value: Type, store: ^Store = nil) -> Kind {
|
||||
return .Void
|
||||
case INT:
|
||||
return .Int_Constraint
|
||||
case BOOL:
|
||||
return .Scalar
|
||||
}
|
||||
if value >= I8 && value <= C_LONGDOUBLE {
|
||||
return .Scalar
|
||||
@@ -268,6 +272,10 @@ is_void :: proc(value: Type) -> bool {
|
||||
return value == VOID
|
||||
}
|
||||
|
||||
is_bool :: proc(value: Type) -> bool {
|
||||
return value == BOOL
|
||||
}
|
||||
|
||||
is_constraint :: proc(value: Type) -> bool {
|
||||
return value == INT
|
||||
}
|
||||
@@ -320,6 +328,8 @@ category :: proc(value: Type, selected := target.DEFAULT) -> Numeric_Category {
|
||||
|
||||
bits :: proc(value: Type, selected := target.DEFAULT) -> int {
|
||||
switch value {
|
||||
case BOOL:
|
||||
return 1
|
||||
case I8, U8:
|
||||
return 8
|
||||
case I16, U16:
|
||||
@@ -945,6 +955,7 @@ name :: proc(value: Type) -> string {
|
||||
switch value {
|
||||
case INVALID: return "<invalid>"
|
||||
case VOID: return "void"
|
||||
case BOOL: return "bool"
|
||||
case INT: return "int"
|
||||
case I8: return "i8"
|
||||
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)
|
||||
}
|
||||
|
||||
@(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)
|
||||
foreign_function_links_from_c_source :: proc(t: ^testing.T) {
|
||||
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