fix comptime yield targeting
This commit is contained in:
+496
-42
@@ -318,6 +318,7 @@ Ct_State :: struct {
|
|||||||
bindings: [dynamic]Ct_Binding,
|
bindings: [dynamic]Ct_Binding,
|
||||||
error_refinements: [dynamic]Ct_Error_Refinement,
|
error_refinements: [dynamic]Ct_Error_Refinement,
|
||||||
defers: [dynamic]Ct_Defer,
|
defers: [dynamic]Ct_Defer,
|
||||||
|
yield_targets: [dynamic]symbol.Id,
|
||||||
defer_depth: int,
|
defer_depth: int,
|
||||||
value_return_depth: int,
|
value_return_depth: int,
|
||||||
steps: int,
|
steps: int,
|
||||||
@@ -361,6 +362,7 @@ ct_state_make :: proc(
|
|||||||
state.bindings.allocator = checker.allocator
|
state.bindings.allocator = checker.allocator
|
||||||
state.error_refinements.allocator = checker.allocator
|
state.error_refinements.allocator = checker.allocator
|
||||||
state.defers.allocator = checker.allocator
|
state.defers.allocator = checker.allocator
|
||||||
|
state.yield_targets.allocator = checker.allocator
|
||||||
state.promoted_cells.allocator = checker.allocator
|
state.promoted_cells.allocator = checker.allocator
|
||||||
state.promoted_globals.allocator = checker.allocator
|
state.promoted_globals.allocator = checker.allocator
|
||||||
for value in values {
|
for value in values {
|
||||||
@@ -401,6 +403,7 @@ ct_state_destroy :: proc(state: ^Ct_State) {
|
|||||||
delete(state.bindings)
|
delete(state.bindings)
|
||||||
delete(state.error_refinements)
|
delete(state.error_refinements)
|
||||||
delete(state.defers)
|
delete(state.defers)
|
||||||
|
delete(state.yield_targets)
|
||||||
delete(state.promoted_cells)
|
delete(state.promoted_cells)
|
||||||
delete(state.promoted_globals)
|
delete(state.promoted_globals)
|
||||||
}
|
}
|
||||||
@@ -1271,7 +1274,7 @@ ct_eval_expr :: proc(
|
|||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
return ct_eval_expr(state, expr.left, expected, depth+1)
|
return ct_eval_expr(state, expr.left, expected, depth+1)
|
||||||
}
|
}
|
||||||
flow, ok := ct_exec_statements(state, expr.body, true, depth+1)
|
flow, ok := ct_exec_value_source(state, expr.body, symbol.INVALID, false, false, depth+1)
|
||||||
if ok && flow.kind == .Yield {
|
if ok && flow.kind == .Yield {
|
||||||
return flow.value, ct_flow(.Normal), true
|
return flow.value, ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
@@ -3934,7 +3937,9 @@ ct_eval_catch_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Typ
|
|||||||
return result, result_flow, result_ok
|
return result, result_flow, result_ok
|
||||||
}
|
}
|
||||||
state.value_return_depth += 1
|
state.value_return_depth += 1
|
||||||
handler, handler_ok := ct_exec_statements(state, expr.body, true, depth+1)
|
handler, handler_ok := ct_exec_value_source(
|
||||||
|
state, expr.body, symbol.INVALID, expr.integer != 0, true, depth+1,
|
||||||
|
)
|
||||||
state.value_return_depth -= 1
|
state.value_return_depth -= 1
|
||||||
ct_pop_bindings(state, scope_start)
|
ct_pop_bindings(state, scope_start)
|
||||||
if !handler_ok {
|
if !handler_ok {
|
||||||
@@ -4123,6 +4128,397 @@ ct_return_value :: proc(state: ^Ct_State, expr_id: ast.Expr_Id, span: source.Spa
|
|||||||
return ct_flow(.Return, value), true
|
return ct_flow(.Return, value), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct_has_yield_target :: proc(state: ^Ct_State, label: symbol.Id) -> bool {
|
||||||
|
for index := len(state.yield_targets) - 1; index >= 0; index -= 1 {
|
||||||
|
if state.yield_targets[index] == label {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_statements_exit_value_source :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
||||||
|
checker := state.checker
|
||||||
|
for statement_id in statements {
|
||||||
|
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
statement := checker.ast_module.statements[statement_id]
|
||||||
|
#partial switch statement.kind {
|
||||||
|
case .Return, .Break, .Continue:
|
||||||
|
return true
|
||||||
|
case .Yield:
|
||||||
|
if symbol.is_valid(statement.label) && ct_has_yield_target(state, statement.label) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .If:
|
||||||
|
if statement.else_body != nil &&
|
||||||
|
ct_statements_exit_value_source(state, statement.body) &&
|
||||||
|
ct_statements_exit_value_source(state, statement.else_body) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .Block:
|
||||||
|
if ct_statements_exit_value_source(state, statement.body) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
case .Match:
|
||||||
|
exits := len(statement.body) > 0
|
||||||
|
for arm_id in statement.body {
|
||||||
|
arm := checker.ast_module.statements[arm_id]
|
||||||
|
exits = exits && arm.kind == .Match_Arm && ct_statements_exit_value_source(state, arm.body)
|
||||||
|
}
|
||||||
|
if exits {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_validate_ordinary_statements :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
||||||
|
checker := state.checker
|
||||||
|
for statement_id in statements {
|
||||||
|
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
statement := checker.ast_module.statements[statement_id]
|
||||||
|
#partial switch statement.kind {
|
||||||
|
case .Declaration, .Assignment:
|
||||||
|
if statement.expr == ast.INVALID_EXPR && !ct_validate_value_source_structure(
|
||||||
|
state, statement.body, statement.label, statement.value_control_flow, false,
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .Return:
|
||||||
|
if statement.value_control_flow && !ct_validate_value_source_structure(
|
||||||
|
state, statement.body, symbol.INVALID, true, false,
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .Yield:
|
||||||
|
if !symbol.is_valid(statement.label) {
|
||||||
|
return ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"'yield' is only valid as the final statement of a value block",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !ct_has_yield_target(state, statement.label) {
|
||||||
|
return ct_failf(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"no enclosing value loop or block is labeled '%s'",
|
||||||
|
symbol_text(checker, statement.label),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if statement.value_control_flow && !ct_validate_value_source_structure(
|
||||||
|
state, statement.body, symbol.INVALID, true, false,
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .If:
|
||||||
|
if !ct_validate_ordinary_statements(state, statement.body) ||
|
||||||
|
!ct_validate_ordinary_statements(state, statement.else_body) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .While, .For:
|
||||||
|
if !ct_validate_ordinary_statements(state, statement.body) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if statement.update != ast.INVALID_STMT {
|
||||||
|
update := [1]ast.Stmt_Id{statement.update}
|
||||||
|
if !ct_validate_ordinary_statements(state, update[:]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .Block:
|
||||||
|
if !ct_validate_ordinary_statements(state, statement.body) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
case .Defer:
|
||||||
|
if statement.update != ast.INVALID_STMT {
|
||||||
|
deferred := [1]ast.Stmt_Id{statement.update}
|
||||||
|
if !ct_validate_ordinary_statements(state, deferred[:]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .Match:
|
||||||
|
for arm_id in statement.body {
|
||||||
|
arm := checker.ast_module.statements[arm_id]
|
||||||
|
if arm.kind == .Match_Arm && !ct_validate_ordinary_statements(state, arm.body) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_validate_value_branch_structure :: proc(state: ^Ct_State, statements: []ast.Stmt_Id) -> bool {
|
||||||
|
checker := state.checker
|
||||||
|
if len(statements) == 1 && checker.ast_module.statements[statements[0]].kind == .Expression {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
n := len(statements)
|
||||||
|
if n > 0 {
|
||||||
|
last := checker.ast_module.statements[statements[n-1]]
|
||||||
|
if last.kind == .Yield && !symbol.is_valid(last.label) {
|
||||||
|
if !ct_validate_ordinary_statements(state, statements[:n-1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !last.value_control_flow || ct_validate_value_source_structure(
|
||||||
|
state, last.body, symbol.INVALID, true, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ct_statements_exit_value_source(state, statements) {
|
||||||
|
return ct_validate_ordinary_statements(state, statements)
|
||||||
|
}
|
||||||
|
return ct_fail(
|
||||||
|
state, .Not_Comptime, source.Span{},
|
||||||
|
"a value branch must end with 'yield' or exit on every path (return/break/continue)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_validate_value_if_structure :: proc(state: ^Ct_State, statement: ast.Stmt) -> bool {
|
||||||
|
checker := state.checker
|
||||||
|
if statement.else_body == nil {
|
||||||
|
return ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"an 'if' used as a value must have an 'else' so every path yields",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if !ct_validate_value_branch_structure(state, statement.body) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(statement.else_body) == 1 {
|
||||||
|
else_statement := checker.ast_module.statements[statement.else_body[0]]
|
||||||
|
if else_statement.kind == .If {
|
||||||
|
return ct_validate_value_if_structure(state, else_statement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ct_validate_value_branch_structure(state, statement.else_body)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_validate_value_source_structure :: proc(
|
||||||
|
state: ^Ct_State,
|
||||||
|
statements: []ast.Stmt_Id,
|
||||||
|
label: symbol.Id,
|
||||||
|
value_control_flow: bool,
|
||||||
|
allow_exit: bool,
|
||||||
|
) -> bool {
|
||||||
|
checker := state.checker
|
||||||
|
if symbol.is_valid(label) {
|
||||||
|
target_start := len(state.yield_targets)
|
||||||
|
append(&state.yield_targets, label)
|
||||||
|
valid := ct_validate_ordinary_statements(state, statements) &&
|
||||||
|
ct_statements_exit_value_source(state, statements)
|
||||||
|
resize(&state.yield_targets, target_start)
|
||||||
|
if valid {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if state.error == .None {
|
||||||
|
return ct_fail(state, .Not_Comptime, source.Span{}, "a labeled value block must 'yield' on every path")
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if value_control_flow && len(statements) == 1 {
|
||||||
|
statement := checker.ast_module.statements[statements[0]]
|
||||||
|
#partial switch statement.kind {
|
||||||
|
case .If:
|
||||||
|
return ct_validate_value_if_structure(state, statement)
|
||||||
|
case .For, .While:
|
||||||
|
n := len(statement.body)
|
||||||
|
if !symbol.is_valid(statement.label) {
|
||||||
|
return ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"a value loop must label its body (e.g. 'blk:') so a 'yield :blk' can exit it",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"a value loop's body must end with a 'yield' for when the loop completes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
last := checker.ast_module.statements[statement.body[n-1]]
|
||||||
|
if last.kind != .Yield || symbol.is_valid(last.label) {
|
||||||
|
return ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"a value loop's body must end with a 'yield' for when the loop completes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
target_start := len(state.yield_targets)
|
||||||
|
append(&state.yield_targets, statement.label)
|
||||||
|
valid := ct_validate_ordinary_statements(state, statement.body[:n-1])
|
||||||
|
if valid && statement.update != ast.INVALID_STMT {
|
||||||
|
update := [1]ast.Stmt_Id{statement.update}
|
||||||
|
valid = ct_validate_ordinary_statements(state, update[:])
|
||||||
|
}
|
||||||
|
resize(&state.yield_targets, target_start)
|
||||||
|
if valid && last.value_control_flow {
|
||||||
|
valid = ct_validate_value_source_structure(
|
||||||
|
state, last.body, symbol.INVALID, true, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return valid
|
||||||
|
case .Match:
|
||||||
|
for arm_id in statement.body {
|
||||||
|
arm := checker.ast_module.statements[arm_id]
|
||||||
|
if arm.kind == .Match_Arm && !ct_validate_value_branch_structure(state, arm.body) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n := len(statements)
|
||||||
|
if n > 0 {
|
||||||
|
last := checker.ast_module.statements[statements[n-1]]
|
||||||
|
if last.kind == .Yield && !symbol.is_valid(last.label) {
|
||||||
|
if !ct_validate_ordinary_statements(state, statements[:n-1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !last.value_control_flow || ct_validate_value_source_structure(
|
||||||
|
state, last.body, symbol.INVALID, true, false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allow_exit {
|
||||||
|
return ct_validate_ordinary_statements(state, statements)
|
||||||
|
}
|
||||||
|
return ct_fail(state, .Not_Comptime, source.Span{}, "a value block must end with an explicit 'yield'")
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_exec_value_branch :: proc(state: ^Ct_State, statements: []ast.Stmt_Id, depth: int) -> (Ct_Flow, bool) {
|
||||||
|
checker := state.checker
|
||||||
|
if len(statements) == 1 {
|
||||||
|
statement := checker.ast_module.statements[statements[0]]
|
||||||
|
if statement.kind == .Expression {
|
||||||
|
value, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||||
|
if !ok || flow.kind != .Normal {
|
||||||
|
return flow, ok
|
||||||
|
}
|
||||||
|
return ct_flow(.Yield, value), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flow, ok := ct_exec_statements(state, statements, true, depth+1)
|
||||||
|
if ok && flow.kind == .Normal {
|
||||||
|
return flow, ct_fail(
|
||||||
|
state, .Not_Comptime, source.Span{},
|
||||||
|
"a value branch must end with 'yield' or exit on every path (return/break/continue)",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return flow, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_exec_value_loop :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) -> (Ct_Flow, bool) {
|
||||||
|
checker := state.checker
|
||||||
|
n := len(statement.body)
|
||||||
|
if !symbol.is_valid(statement.label) {
|
||||||
|
return ct_flow(.Normal), ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"a value loop must label its body (e.g. 'blk:') so a 'yield :blk' can exit it",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if n == 0 {
|
||||||
|
return ct_flow(.Normal), ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"a value loop's body must end with a 'yield' for when the loop completes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fall_stmt := checker.ast_module.statements[statement.body[n-1]]
|
||||||
|
if fall_stmt.kind != .Yield || symbol.is_valid(fall_stmt.label) {
|
||||||
|
return ct_flow(.Normal), ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"a value loop's body must end with a 'yield' for when the loop completes",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fallback := ct_flow(.Normal)
|
||||||
|
fallback_ok := true
|
||||||
|
if fall_stmt.value_control_flow {
|
||||||
|
fallback, fallback_ok = ct_exec_value_source(
|
||||||
|
state, fall_stmt.body, symbol.INVALID, true, false, depth+1,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
value, flow, ok := ct_eval_expr(state, fall_stmt.expr, types.INVALID, depth+1)
|
||||||
|
fallback_ok = ok
|
||||||
|
fallback = flow
|
||||||
|
if ok && flow.kind == .Normal {
|
||||||
|
fallback = ct_flow(.Yield, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fallback_ok || fallback.kind != .Yield {
|
||||||
|
return fallback, fallback_ok
|
||||||
|
}
|
||||||
|
peeled := statement
|
||||||
|
peeled.body = statement.body[:n-1]
|
||||||
|
target_start := len(state.yield_targets)
|
||||||
|
append(&state.yield_targets, statement.label)
|
||||||
|
defer resize(&state.yield_targets, target_start)
|
||||||
|
flow := ct_flow(.Normal)
|
||||||
|
ok := false
|
||||||
|
if statement.kind == .For {
|
||||||
|
flow, ok = ct_exec_for(state, peeled, false, depth+1)
|
||||||
|
} else {
|
||||||
|
flow, ok = ct_exec_while(state, peeled, false, depth+1)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return flow, false
|
||||||
|
}
|
||||||
|
if flow.kind == .Yield && flow.label == statement.label {
|
||||||
|
return ct_flow(.Yield, flow.value), true
|
||||||
|
}
|
||||||
|
if flow.kind == .Normal {
|
||||||
|
return fallback, true
|
||||||
|
}
|
||||||
|
return flow, true
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_exec_value_source :: proc(
|
||||||
|
state: ^Ct_State,
|
||||||
|
statements: []ast.Stmt_Id,
|
||||||
|
label: symbol.Id,
|
||||||
|
value_control_flow: bool,
|
||||||
|
allow_exit: bool,
|
||||||
|
depth: int,
|
||||||
|
) -> (Ct_Flow, bool) {
|
||||||
|
checker := state.checker
|
||||||
|
if !ct_validate_value_source_structure(state, statements, label, value_control_flow, allow_exit) {
|
||||||
|
return ct_flow(.Normal), false
|
||||||
|
}
|
||||||
|
if symbol.is_valid(label) {
|
||||||
|
target_start := len(state.yield_targets)
|
||||||
|
append(&state.yield_targets, label)
|
||||||
|
flow, ok := ct_exec_statements(state, statements, false, depth+1)
|
||||||
|
resize(&state.yield_targets, target_start)
|
||||||
|
if !ok {
|
||||||
|
return flow, false
|
||||||
|
}
|
||||||
|
if flow.kind == .Yield && flow.label == label {
|
||||||
|
return ct_flow(.Yield, flow.value), true
|
||||||
|
}
|
||||||
|
if flow.kind == .Normal {
|
||||||
|
return flow, ct_fail(state, .Not_Comptime, source.Span{}, "a labeled value block must 'yield' on every path")
|
||||||
|
}
|
||||||
|
return flow, true
|
||||||
|
}
|
||||||
|
if value_control_flow && len(statements) == 1 {
|
||||||
|
statement := checker.ast_module.statements[statements[0]]
|
||||||
|
#partial switch statement.kind {
|
||||||
|
case .If:
|
||||||
|
return ct_exec_if(state, statement, true, depth+1)
|
||||||
|
case .For, .While:
|
||||||
|
return ct_exec_value_loop(state, statement, depth+1)
|
||||||
|
case .Match:
|
||||||
|
return ct_exec_match(state, statement, true, depth+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flow, ok := ct_exec_statements(state, statements, true, depth+1)
|
||||||
|
if ok && flow.kind == .Normal && !allow_exit {
|
||||||
|
return flow, ct_fail(state, .Not_Comptime, source.Span{}, "a value block must end with an explicit 'yield'")
|
||||||
|
}
|
||||||
|
return flow, ok
|
||||||
|
}
|
||||||
|
|
||||||
ct_exec_statements :: proc(
|
ct_exec_statements :: proc(
|
||||||
state: ^Ct_State,
|
state: ^Ct_State,
|
||||||
statements: []ast.Stmt_Id,
|
statements: []ast.Stmt_Id,
|
||||||
@@ -4139,7 +4535,7 @@ ct_exec_statements :: proc(
|
|||||||
ct_pop_bindings(state, scope_start)
|
ct_pop_bindings(state, scope_start)
|
||||||
resize(&state.defers, defer_start)
|
resize(&state.defers, defer_start)
|
||||||
}
|
}
|
||||||
for statement_id in statements {
|
for statement_id, statement_index in statements {
|
||||||
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
if statement_id == ast.INVALID_STMT || int(statement_id) >= len(checker.ast_module.statements) {
|
||||||
return ct_flow(.Normal), false
|
return ct_flow(.Normal), false
|
||||||
}
|
}
|
||||||
@@ -4152,7 +4548,9 @@ ct_exec_statements :: proc(
|
|||||||
#partial switch statement.kind {
|
#partial switch statement.kind {
|
||||||
case .Declaration:
|
case .Declaration:
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
value_flow, value_ok := ct_exec_statements(state, statement.body, true, depth+1)
|
value_flow, value_ok := ct_exec_value_source(
|
||||||
|
state, statement.body, statement.label, statement.value_control_flow, false, depth+1,
|
||||||
|
)
|
||||||
ok = value_ok
|
ok = value_ok
|
||||||
if ok && value_flow.kind == .Yield {
|
if ok && value_flow.kind == .Yield {
|
||||||
value := value_flow.value
|
value := value_flow.value
|
||||||
@@ -4163,6 +4561,8 @@ ct_exec_statements :: proc(
|
|||||||
if ok && statement.name != checker.sink_symbol {
|
if ok && statement.name != checker.sink_symbol {
|
||||||
ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable)
|
ct_bind_value(state, statement.name, state.values[value].type, value, !statement.immutable)
|
||||||
}
|
}
|
||||||
|
} else if ok && value_flow.kind != .Normal {
|
||||||
|
flow = value_flow
|
||||||
} else if ok {
|
} else if ok {
|
||||||
ok = ct_fail(state, .Not_Comptime, statement.span, "comptime value block must yield")
|
ok = ct_fail(state, .Not_Comptime, statement.span, "comptime value block must yield")
|
||||||
}
|
}
|
||||||
@@ -4189,7 +4589,9 @@ ct_exec_statements :: proc(
|
|||||||
if yield_returns && state.value_return_depth == 0 {
|
if yield_returns && state.value_return_depth == 0 {
|
||||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'return' is not valid in this comptime block")
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'return' is not valid in this comptime block")
|
||||||
} else if statement.value_control_flow {
|
} else if statement.value_control_flow {
|
||||||
value_flow, value_ok := ct_exec_statements(state, statement.body, true, depth+1)
|
value_flow, value_ok := ct_exec_value_source(
|
||||||
|
state, statement.body, symbol.INVALID, true, false, depth+1,
|
||||||
|
)
|
||||||
ok = value_ok
|
ok = value_ok
|
||||||
if ok && value_flow.kind == .Yield {
|
if ok && value_flow.kind == .Yield {
|
||||||
value := value_flow.value
|
value := value_flow.value
|
||||||
@@ -4205,42 +4607,62 @@ ct_exec_statements :: proc(
|
|||||||
flow = ct_flow(.Return, value)
|
flow = ct_flow(.Return, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if ok {
|
||||||
|
flow = value_flow
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
flow, ok = ct_return_value(state, statement.expr, statement.span, depth+1)
|
flow, ok = ct_return_value(state, statement.expr, statement.span, depth+1)
|
||||||
}
|
}
|
||||||
case .Yield:
|
case .Yield:
|
||||||
if !yield_returns {
|
if symbol.is_valid(statement.label) {
|
||||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid in a comptime value block")
|
if !ct_has_yield_target(state, statement.label) {
|
||||||
} else if statement.value_control_flow {
|
ok = ct_failf(
|
||||||
flow, ok = ct_exec_statements(state, statement.body, true, depth+1)
|
state, .Not_Comptime, statement.span,
|
||||||
if ok && flow.kind == .Yield && types.is_void(state.values[flow.value].type) {
|
"no enclosing value loop or block is labeled '%s'",
|
||||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
symbol_text(checker, statement.label),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else if !yield_returns || statement_index != len(statements)-1 {
|
||||||
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' is only valid as the final statement of a value block")
|
||||||
ok = expr_ok
|
}
|
||||||
flow = expr_flow
|
if ok {
|
||||||
if ok && flow.kind == .Normal {
|
if statement.value_control_flow {
|
||||||
if types.is_void(state.values[value].type) {
|
value_flow, value_ok := ct_exec_value_source(
|
||||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
state, statement.body, symbol.INVALID, true, false, depth+1,
|
||||||
} else {
|
)
|
||||||
flow = ct_flow(.Yield, value, statement.label)
|
ok = value_ok
|
||||||
|
flow = value_flow
|
||||||
|
if ok && value_flow.kind == .Yield {
|
||||||
|
flow.label = statement.label
|
||||||
|
if types.is_void(state.values[flow.value].type) {
|
||||||
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value, expr_flow, expr_ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||||
|
ok = expr_ok
|
||||||
|
flow = expr_flow
|
||||||
|
if ok && flow.kind == .Normal {
|
||||||
|
if types.is_void(state.values[value].type) {
|
||||||
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'yield' expression must produce a non-void value")
|
||||||
|
} else {
|
||||||
|
flow = ct_flow(.Yield, value, statement.label)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .If:
|
case .If:
|
||||||
flow, ok = ct_exec_if(state, statement, yield_returns, depth+1)
|
flow, ok = ct_exec_if(state, statement, false, depth+1)
|
||||||
case .While:
|
case .While:
|
||||||
flow, ok = ct_exec_while(state, statement, yield_returns, depth+1)
|
flow, ok = ct_exec_while(state, statement, false, depth+1)
|
||||||
case .For:
|
case .For:
|
||||||
flow, ok = ct_exec_for(state, statement, yield_returns, depth+1)
|
flow, ok = ct_exec_for(state, statement, false, depth+1)
|
||||||
case .Break:
|
case .Break:
|
||||||
flow = ct_flow(.Break, INVALID_CT_VALUE, statement.label)
|
flow = ct_flow(.Break, INVALID_CT_VALUE, statement.label)
|
||||||
case .Continue:
|
case .Continue:
|
||||||
flow = ct_flow(.Continue, INVALID_CT_VALUE, statement.label)
|
flow = ct_flow(.Continue, INVALID_CT_VALUE, statement.label)
|
||||||
case .Block:
|
case .Block:
|
||||||
flow, ok = ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
flow, ok = ct_exec_statements(state, statement.body, false, depth+1)
|
||||||
case .Defer:
|
case .Defer:
|
||||||
if statement.error_only && types.kind(state.result, &checker.module.types) != .Fallible {
|
if statement.error_only && types.kind(state.result, &checker.module.types) != .Fallible {
|
||||||
ok = ct_fail(state, .Not_Comptime, statement.span, "'errdefer' requires an enclosing fallible function")
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'errdefer' requires an enclosing fallible function")
|
||||||
@@ -4254,7 +4676,7 @@ ct_exec_statements :: proc(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
case .Match:
|
case .Match:
|
||||||
flow, ok = ct_exec_match(state, statement, yield_returns, depth+1)
|
flow, ok = ct_exec_match(state, statement, false, depth+1)
|
||||||
case .Match_Arm:
|
case .Match_Arm:
|
||||||
ok = ct_fail(state, .Not_Comptime, statement.span, "unexpected match arm outside 'match'")
|
ok = ct_fail(state, .Not_Comptime, statement.span, "unexpected match arm outside 'match'")
|
||||||
case .Invalid:
|
case .Invalid:
|
||||||
@@ -4327,8 +4749,13 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
|||||||
}
|
}
|
||||||
if name == checker.sink_symbol {
|
if name == checker.sink_symbol {
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
flow, ok := ct_exec_statements(state, statement.body, true, depth+1)
|
flow, ok := ct_exec_value_source(
|
||||||
return ct_flow(.Normal), ok && (flow.kind == .Yield || flow.kind == .Normal)
|
state, statement.body, statement.label, statement.value_control_flow, false, depth+1,
|
||||||
|
)
|
||||||
|
if ok && flow.kind == .Yield {
|
||||||
|
return ct_flow(.Normal), true
|
||||||
|
}
|
||||||
|
return flow, ok
|
||||||
}
|
}
|
||||||
_, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
_, flow, ok := ct_eval_expr(state, statement.expr, types.INVALID, depth+1)
|
||||||
return flow, ok
|
return flow, ok
|
||||||
@@ -4359,7 +4786,9 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
|||||||
flow := ct_flow(.Normal)
|
flow := ct_flow(.Normal)
|
||||||
ok := true
|
ok := true
|
||||||
if statement.expr == ast.INVALID_EXPR {
|
if statement.expr == ast.INVALID_EXPR {
|
||||||
flow, ok = ct_exec_statements(state, statement.body, true, depth+1)
|
flow, ok = ct_exec_value_source(
|
||||||
|
state, statement.body, statement.label, statement.value_control_flow, false, depth+1,
|
||||||
|
)
|
||||||
if ok && flow.kind == .Yield {
|
if ok && flow.kind == .Yield {
|
||||||
value = flow.value
|
value = flow.value
|
||||||
flow = ct_flow(.Normal)
|
flow = ct_flow(.Normal)
|
||||||
@@ -4411,6 +4840,12 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
|||||||
|
|
||||||
ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, depth: int) -> (Ct_Flow, bool) {
|
||||||
checker := state.checker
|
checker := state.checker
|
||||||
|
if yield_returns && statement.else_body == nil {
|
||||||
|
return ct_flow(.Normal), ct_fail(
|
||||||
|
state, .Not_Comptime, statement.span,
|
||||||
|
"an 'if' used as a value must have an 'else' so every path yields",
|
||||||
|
)
|
||||||
|
}
|
||||||
if len(statement.captures) == 0 {
|
if len(statement.captures) == 0 {
|
||||||
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1)
|
condition, flow, ok := ct_eval_expr(state, statement.expr, types.BOOL, depth+1)
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
@@ -4421,7 +4856,13 @@ ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, d
|
|||||||
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' condition must be a bool")
|
return ct_flow(.Normal), ct_fail(state, .Not_Comptime, statement.span, "'if' condition must be a bool")
|
||||||
}
|
}
|
||||||
body := statement.body if value else statement.else_body
|
body := statement.body if value else statement.else_body
|
||||||
return ct_exec_statements(state, body, yield_returns, depth+1)
|
if yield_returns {
|
||||||
|
if !value && len(body) == 1 && checker.ast_module.statements[body[0]].kind == .If {
|
||||||
|
return ct_exec_if(state, checker.ast_module.statements[body[0]], true, depth+1)
|
||||||
|
}
|
||||||
|
return ct_exec_value_branch(state, body, depth+1)
|
||||||
|
}
|
||||||
|
return ct_exec_statements(state, body, false, depth+1)
|
||||||
}
|
}
|
||||||
operands: [dynamic]ast.Expr_Id
|
operands: [dynamic]ast.Expr_Id
|
||||||
operands.allocator = checker.allocator
|
operands.allocator = checker.allocator
|
||||||
@@ -4466,7 +4907,17 @@ ct_exec_if :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool, d
|
|||||||
matched = guard_value
|
matched = guard_value
|
||||||
}
|
}
|
||||||
body := statement.body if matched else statement.else_body
|
body := statement.body if matched else statement.else_body
|
||||||
flow, ok := ct_exec_statements(state, body, yield_returns, depth+1)
|
flow := ct_flow(.Normal)
|
||||||
|
ok := false
|
||||||
|
if yield_returns {
|
||||||
|
if !matched && len(body) == 1 && checker.ast_module.statements[body[0]].kind == .If {
|
||||||
|
flow, ok = ct_exec_if(state, checker.ast_module.statements[body[0]], true, depth+1)
|
||||||
|
} else {
|
||||||
|
flow, ok = ct_exec_value_branch(state, body, depth+1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flow, ok = ct_exec_statements(state, body, false, depth+1)
|
||||||
|
}
|
||||||
ct_pop_bindings(state, scope_start)
|
ct_pop_bindings(state, scope_start)
|
||||||
return flow, ok
|
return flow, ok
|
||||||
}
|
}
|
||||||
@@ -4918,16 +5369,13 @@ ct_exec_match :: proc(state: ^Ct_State, statement: ast.Stmt, yield_returns: bool
|
|||||||
selection.tag != INVALID_CT_VALUE {
|
selection.tag != INVALID_CT_VALUE {
|
||||||
ct_bind_value(state, arm.captures[1], state.values[selection.tag].type, selection.tag, false)
|
ct_bind_value(state, arm.captures[1], state.values[selection.tag].type, selection.tag, false)
|
||||||
}
|
}
|
||||||
if yield_returns && len(arm.body) == 1 && checker.ast_module.statements[arm.body[0]].kind == .Expression {
|
arm_flow := ct_flow(.Normal)
|
||||||
expr_stmt := checker.ast_module.statements[arm.body[0]]
|
arm_ok := false
|
||||||
value, expr_flow, expr_ok := ct_eval_expr(state, expr_stmt.expr, types.INVALID, depth+1)
|
if yield_returns {
|
||||||
ct_pop_bindings(state, scope_start)
|
arm_flow, arm_ok = ct_exec_value_branch(state, arm.body, depth+1)
|
||||||
if !expr_ok || expr_flow.kind != .Normal {
|
} else {
|
||||||
return expr_flow, expr_ok
|
arm_flow, arm_ok = ct_exec_statements(state, arm.body, false, depth+1)
|
||||||
}
|
|
||||||
return ct_flow(.Yield, value), true
|
|
||||||
}
|
}
|
||||||
arm_flow, arm_ok := ct_exec_statements(state, arm.body, yield_returns, depth+1)
|
|
||||||
ct_pop_bindings(state, scope_start)
|
ct_pop_bindings(state, scope_start)
|
||||||
return arm_flow, arm_ok
|
return arm_flow, arm_ok
|
||||||
}
|
}
|
||||||
@@ -5049,7 +5497,13 @@ eval_comptime_statements :: proc(
|
|||||||
) -> (Constant, bool, bool) {
|
) -> (Constant, bool, bool) {
|
||||||
state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false)
|
state := ct_state_make(checker, pkg, file, types.INVALID, values, diagnose=false)
|
||||||
defer ct_state_destroy(&state)
|
defer ct_state_destroy(&state)
|
||||||
flow, ok := ct_exec_statements(&state, statements, yield_returns, depth)
|
flow := ct_flow(.Normal)
|
||||||
|
ok := false
|
||||||
|
if yield_returns {
|
||||||
|
flow, ok = ct_exec_value_source(&state, statements, symbol.INVALID, false, false, depth)
|
||||||
|
} else {
|
||||||
|
flow, ok = ct_exec_statements(&state, statements, false, depth)
|
||||||
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
#partial switch state.error {
|
#partial switch state.error {
|
||||||
case .Overflow:
|
case .Overflow:
|
||||||
@@ -5120,7 +5574,7 @@ infer_comptime_expr_type :: proc(
|
|||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
value, flow, ok = ct_eval_expr(&state, expr.left, types.INVALID)
|
value, flow, ok = ct_eval_expr(&state, expr.left, types.INVALID)
|
||||||
} else {
|
} else {
|
||||||
flow, ok = ct_exec_statements(&state, expr.body, true)
|
flow, ok = ct_exec_value_source(&state, expr.body, symbol.INVALID, false, false, 0)
|
||||||
if ok && flow.kind == .Yield {
|
if ok && flow.kind == .Yield {
|
||||||
value = flow.value
|
value = flow.value
|
||||||
flow = ct_flow(.Normal)
|
flow = ct_flow(.Normal)
|
||||||
@@ -5156,7 +5610,7 @@ build_comptime_expr :: proc(
|
|||||||
if expr.left != ast.INVALID_EXPR {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
value, flow, ok = ct_eval_expr(&state, expr.left, expected)
|
value, flow, ok = ct_eval_expr(&state, expr.left, expected)
|
||||||
} else {
|
} else {
|
||||||
flow, ok = ct_exec_statements(&state, expr.body, true)
|
flow, ok = ct_exec_value_source(&state, expr.body, symbol.INVALID, false, false, 0)
|
||||||
if ok && flow.kind == .Yield {
|
if ok && flow.kind == .Yield {
|
||||||
value = flow.value
|
value = flow.value
|
||||||
flow = ct_flow(.Normal)
|
flow = ct_flow(.Normal)
|
||||||
|
|||||||
+59
-1
@@ -5064,7 +5064,7 @@ main func() void {
|
|||||||
found_expired = found_expired || strings.contains(message, "expired storage")
|
found_expired = found_expired || strings.contains(message, "expired storage")
|
||||||
found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota")
|
found_quota = found_quota || strings.contains(message, "comptime evaluation exceeded the step quota")
|
||||||
found_missing = found_missing || strings.contains(message, "did not return a value")
|
found_missing = found_missing || strings.contains(message, "did not return a value")
|
||||||
found_yield = found_yield || strings.contains(message, "comptime block must yield a value")
|
found_yield = found_yield || strings.contains(message, "a value block must end with an explicit 'yield'")
|
||||||
}
|
}
|
||||||
testing.expect(t, found_runtime)
|
testing.expect(t, found_runtime)
|
||||||
testing.expect(t, runtime_only_count >= 2)
|
testing.expect(t, runtime_only_count >= 2)
|
||||||
@@ -7428,6 +7428,64 @@ yield_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
testing.expect(t, misplaced_yield)
|
testing.expect(t, misplaced_yield)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
comptime_yield_targets_match_value_source_semantics :: proc(t: ^testing.T) {
|
||||||
|
directory := "/tmp/brolang-test-comptime-yield-targets"
|
||||||
|
main_path := "/tmp/brolang-test-comptime-yield-targets/main.bro"
|
||||||
|
output := "/tmp/brolang-test-comptime-yield-targets-output"
|
||||||
|
valid_text := `selected :: ${
|
||||||
|
via_if :: if true {
|
||||||
|
yield 1
|
||||||
|
} else {
|
||||||
|
yield 2
|
||||||
|
}
|
||||||
|
via_label :: done: {
|
||||||
|
if true {
|
||||||
|
yield :done 3
|
||||||
|
}
|
||||||
|
yield :done 4
|
||||||
|
}
|
||||||
|
yield via_if + via_label
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() i32 { return selected - 4 }
|
||||||
|
`
|
||||||
|
_ = os2.remove_all(directory)
|
||||||
|
defer _ = os2.remove_all(directory)
|
||||||
|
defer _ = os.remove(output)
|
||||||
|
testing.expect(t, os.make_directory(directory) == nil)
|
||||||
|
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)valid_text))
|
||||||
|
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||||
|
state := run_executable(output)
|
||||||
|
testing.expect_value(t, state.exit_code, 0)
|
||||||
|
|
||||||
|
invalid_text := `invalid :: ${
|
||||||
|
if false {
|
||||||
|
yield 1
|
||||||
|
}
|
||||||
|
yield 2
|
||||||
|
}
|
||||||
|
|
||||||
|
main func() void {}
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="invalid_comptime_yield.bro", text=invalid_text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&ast_module)
|
||||||
|
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||||
|
defer hir.destroy_module(&hir_module)
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, "'yield' is only valid as the final statement of a value block")
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
yield_control_flow_is_diagnosed :: proc(t: ^testing.T) {
|
||||||
// Value if/loop/block misuse: an `if` value without an `else`; a branch that neither
|
// Value if/loop/block misuse: an `if` value without an `else`; a branch that neither
|
||||||
|
|||||||
@@ -170,10 +170,8 @@ ct_errdefer_check func() i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recover func() i32 {
|
recover func() i32 {
|
||||||
return may_fail(true) catch |e| {
|
return may_fail(true) catch |e| match e {
|
||||||
match e {
|
.bad: 5
|
||||||
.bad: yield 5
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,48 +35,51 @@ init func($V type, $N usize, $entries [N]Pair(V)) StaticStringMap(V) {
|
|||||||
values[i] = entry.1
|
values[i] = entry.1
|
||||||
}
|
}
|
||||||
|
|
||||||
if N == 0 {
|
result :: done: {
|
||||||
len_indexes [0]mut u32 = undefined
|
if N == 0 {
|
||||||
yield StaticStringMap(V) {
|
len_indexes [0]mut u32 = undefined
|
||||||
|
yield :done StaticStringMap(V) {
|
||||||
|
keys = keys[..],
|
||||||
|
values = values[..],
|
||||||
|
len_indexes = len_indexes[..],
|
||||||
|
min_len = 0,
|
||||||
|
max_len = 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ponytail: insertion sort is compile-time O(N²); replace if large maps affect builds.
|
||||||
|
i usize = 1
|
||||||
|
while i < N : i += 1 {
|
||||||
|
key :: keys[i]
|
||||||
|
value :: values[i]
|
||||||
|
j usize = i
|
||||||
|
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
||||||
|
keys[j] = keys[j - 1]
|
||||||
|
values[j] = values[j - 1]
|
||||||
|
}
|
||||||
|
keys[j] = key
|
||||||
|
values[j] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
min_len u32 :: u32(keys[0].len)
|
||||||
|
max_len u32 :: u32(keys[N - 1].len)
|
||||||
|
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
||||||
|
entry_index usize = 0
|
||||||
|
length usize = 0
|
||||||
|
while length <= usize(max_len) : length += 1 {
|
||||||
|
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
||||||
|
len_indexes[length] = u32(entry_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
yield :done StaticStringMap(V) {
|
||||||
keys = keys[..],
|
keys = keys[..],
|
||||||
values = values[..],
|
values = values[..],
|
||||||
len_indexes = len_indexes[..],
|
len_indexes = len_indexes[..],
|
||||||
min_len = 0,
|
min_len = min_len,
|
||||||
max_len = 0,
|
max_len = max_len,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
yield result
|
||||||
# ponytail: insertion sort is compile-time O(N²); replace if large maps affect builds.
|
|
||||||
i usize = 1
|
|
||||||
while i < N : i += 1 {
|
|
||||||
key :: keys[i]
|
|
||||||
value :: values[i]
|
|
||||||
j usize = i
|
|
||||||
while j > 0 and keys[j - 1].len > key.len : j -= 1 {
|
|
||||||
keys[j] = keys[j - 1]
|
|
||||||
values[j] = values[j - 1]
|
|
||||||
}
|
|
||||||
keys[j] = key
|
|
||||||
values[j] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
min_len u32 :: u32(keys[0].len)
|
|
||||||
max_len u32 :: u32(keys[N - 1].len)
|
|
||||||
len_indexes [usize(max_len) + 1]mut u32 = undefined
|
|
||||||
entry_index usize = 0
|
|
||||||
length usize = 0
|
|
||||||
while length <= usize(max_len) : length += 1 {
|
|
||||||
while entry_index < N and keys[entry_index].len < length : entry_index += 1 {}
|
|
||||||
len_indexes[length] = u32(entry_index)
|
|
||||||
}
|
|
||||||
|
|
||||||
yield StaticStringMap(V) {
|
|
||||||
keys = keys[..],
|
|
||||||
values = values[..],
|
|
||||||
len_indexes = len_indexes[..],
|
|
||||||
min_len = min_len,
|
|
||||||
max_len = max_len,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user