feat: parse function calls
This commit is contained in:
@@ -9,3 +9,5 @@ bro build
|
|||||||
```
|
```
|
||||||
|
|
||||||
The parser supports arithmetic expressions with nested parenthesized groups, such as `a + (b - c) * 2 / 3`. Groups control precedence without adding AST nodes; inner expression spans exclude the grouping parentheses.
|
The parser supports arithmetic expressions with nested parenthesized groups, such as `a + (b - c) * 2 / 3`. Groups control precedence without adding AST nodes; inner expression spans exclude the grouping parentheses.
|
||||||
|
|
||||||
|
Function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. Calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. Argument lists allow a trailing comma; newlines remain statement boundaries. Name resolution and argument/type checking belong to semantic analysis.
|
||||||
|
|||||||
+94
-8
@@ -2,9 +2,10 @@
|
|||||||
#! * type ..... -> identifier
|
#! * type ..... -> identifier
|
||||||
#! * assignment -> :: | :=
|
#! * assignment -> :: | :=
|
||||||
#! * literal .. -> int | float | string
|
#! * literal .. -> int | float | string
|
||||||
#! * expr ..... -> identifier | literal
|
#! * op ....... -> '+' | '-' | '*' | '/'
|
||||||
|
#! * expr ..... -> identifier | literal | '(' expr ')' | '-' expr | expr op expr | expr '(' [expr {',' expr} [',']] ')'
|
||||||
#! * decl ..... -> identifier [type] assignment expr
|
#! * decl ..... -> identifier [type] assignment expr
|
||||||
#! * stmt ..... -> decl
|
#! * stmt ..... -> decl | expr
|
||||||
|
|
||||||
import "@std/debug"
|
import "@std/debug"
|
||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
@@ -20,13 +21,38 @@ NodeId :: distinct u32
|
|||||||
@hide:file
|
@hide:file
|
||||||
NO_NODE :: maxval!(NodeId)
|
NO_NODE :: maxval!(NodeId)
|
||||||
|
|
||||||
LiteralKind :: enum { int, float, string }
|
LiteralKind :: enum {
|
||||||
ExprKind :: enum { literal_int, literal_float, literal_string, identifier, unary, binary, invalid }
|
int, float, string
|
||||||
DeclKind :: enum { statement, function, invalid }
|
}
|
||||||
StmtKind :: enum { declaration, invalid }
|
|
||||||
Mutability :: enum { constant, variable }
|
ExprKind :: enum {
|
||||||
|
literal_int
|
||||||
|
literal_float
|
||||||
|
literal_string
|
||||||
|
identifier
|
||||||
|
unary
|
||||||
|
binary
|
||||||
|
call
|
||||||
|
invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
DeclKind :: enum {
|
||||||
|
statement, function, invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
StmtKind :: enum {
|
||||||
|
declaration, expression, invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
Mutability :: enum {
|
||||||
|
constant, variable
|
||||||
|
}
|
||||||
|
|
||||||
UnaryOperator :: enum { negate }
|
UnaryOperator :: enum { negate }
|
||||||
BinaryOperator :: enum { add, subtract, multiply, divide }
|
|
||||||
|
BinaryOperator :: enum {
|
||||||
|
add, subtract, multiply, divide
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
NodeKind :: enum {
|
NodeKind :: enum {
|
||||||
@@ -36,6 +62,8 @@ NodeKind :: enum {
|
|||||||
expr_identifier,
|
expr_identifier,
|
||||||
expr_unary,
|
expr_unary,
|
||||||
expr_binary,
|
expr_binary,
|
||||||
|
expr_call,
|
||||||
|
stmt_expr,
|
||||||
stmt_decl,
|
stmt_decl,
|
||||||
func_decl,
|
func_decl,
|
||||||
param_decl,
|
param_decl,
|
||||||
@@ -116,6 +144,11 @@ BinaryExprView :: struct {
|
|||||||
right NodeId
|
right NodeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CallExprView :: struct {
|
||||||
|
span Span
|
||||||
|
callee NodeId
|
||||||
|
}
|
||||||
|
|
||||||
InvalidView :: struct {
|
InvalidView :: struct {
|
||||||
span Span
|
span Span
|
||||||
diagnostic DiagnosticId
|
diagnostic DiagnosticId
|
||||||
@@ -197,6 +230,29 @@ add_binary proc(
|
|||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_call proc(
|
||||||
|
builder @mut Builder,
|
||||||
|
span Span,
|
||||||
|
callee NodeId,
|
||||||
|
arguments []NodeId,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
|
start :: builder.storage.extra.items.len
|
||||||
|
for (arguments) |argument| _ = try add_extra(builder, u32(argument))
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_call,
|
||||||
|
main = NodeData{ node_id = callee },
|
||||||
|
data0 = NodeData{ extra = u32(start) },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_expression_stmt proc(builder @mut Builder, expression NodeId) NodeId ! mem.AllocError {
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .stmt_expr,
|
||||||
|
main = NodeData{ node_id = expression },
|
||||||
|
}, builder_span(builder, expression))
|
||||||
|
}
|
||||||
|
|
||||||
add_statement_decl proc(
|
add_statement_decl proc(
|
||||||
builder @mut Builder,
|
builder @mut Builder,
|
||||||
span Span,
|
span Span,
|
||||||
@@ -314,11 +370,18 @@ decl_kind proc(tree @Tree, id NodeId) DeclKind {
|
|||||||
stmt_kind proc(tree @Tree, id NodeId) StmtKind {
|
stmt_kind proc(tree @Tree, id NodeId) StmtKind {
|
||||||
return match get_node(&tree.storage, id).kind {
|
return match get_node(&tree.storage, id).kind {
|
||||||
.stmt_decl: .declaration
|
.stmt_decl: .declaration
|
||||||
|
.stmt_expr: .expression
|
||||||
.invalid_stmt: .invalid
|
.invalid_stmt: .invalid
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
statement_expr proc(tree @Tree, id NodeId) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .stmt_expr)
|
||||||
|
return node.main.node_id
|
||||||
|
}
|
||||||
|
|
||||||
statement_decl proc(tree @Tree, id NodeId) StatementDeclView {
|
statement_decl proc(tree @Tree, id NodeId) StatementDeclView {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .stmt_decl)
|
debug.assert(node.kind == .stmt_decl)
|
||||||
@@ -389,6 +452,7 @@ expr_kind proc(tree @Tree, id NodeId) ExprKind {
|
|||||||
.expr_identifier: .identifier
|
.expr_identifier: .identifier
|
||||||
.expr_unary: .unary
|
.expr_unary: .unary
|
||||||
.expr_binary: .binary
|
.expr_binary: .binary
|
||||||
|
.expr_call: .call
|
||||||
.invalid_expr: .invalid
|
.invalid_expr: .invalid
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
@@ -415,6 +479,28 @@ binary_expr proc(tree @Tree, id NodeId) BinaryExprView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
call_expr proc(tree @Tree, id NodeId) CallExprView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_call)
|
||||||
|
return CallExprView{
|
||||||
|
span = get_span(&tree.storage, id),
|
||||||
|
callee = node.main.node_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
call_arg_count proc(tree @Tree, id NodeId) usize {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_call)
|
||||||
|
return usize(node.data1.extra - node.data0.extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
call_arg proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_call)
|
||||||
|
debug.assert(index < usize(node.data1.extra - node.data0.extra))
|
||||||
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
|
}
|
||||||
|
|
||||||
node_span proc(tree @Tree, id NodeId) Span { return get_span(&tree.storage, id) }
|
node_span proc(tree @Tree, id NodeId) Span { return get_span(&tree.storage, id) }
|
||||||
|
|
||||||
invalid proc(tree @Tree, id NodeId) InvalidView {
|
invalid proc(tree @Tree, id NodeId) InvalidView {
|
||||||
|
|||||||
@@ -41,6 +41,12 @@ render_function proc(
|
|||||||
statement :: ast.block_statement(tree, function.body, index)
|
statement :: ast.block_statement(tree, function.body, index)
|
||||||
match ast.stmt_kind(tree, statement) {
|
match ast.stmt_kind(tree, statement) {
|
||||||
.declaration: try render_statement(renderer, tree, statement, " ")
|
.declaration: try render_statement(renderer, tree, statement, " ")
|
||||||
|
.expression: {
|
||||||
|
expression :: ast.statement_expr(tree, statement)
|
||||||
|
try append_text(renderer, " expression ")
|
||||||
|
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, expression)))
|
||||||
|
try append_text(renderer, "\n")
|
||||||
|
}
|
||||||
.invalid: try append_text(renderer, " invalid statement\n")
|
.invalid: try append_text(renderer, " invalid statement\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ program ::
|
|||||||
`empty proc() void {}
|
`empty proc() void {}
|
||||||
`grouped proc(a, b T, c U) void {
|
`grouped proc(a, b T, c U) void {
|
||||||
` value int :: 1
|
` value int :: 1
|
||||||
|
` empty()
|
||||||
`}
|
`}
|
||||||
|
|
||||||
main proc() void! {
|
main proc() void! {
|
||||||
|
|||||||
+43
-6
@@ -167,8 +167,18 @@ parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_statement proc(state @mut State) ast.NodeId ! ParseError {
|
parse_statement proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
if next_is(state, .ident) return try parse_statement_decl(state)
|
if (next_is(state, .ident) and (
|
||||||
return .unexpected_token
|
next_n_is(state, 1, .ident) or
|
||||||
|
next_n_is(state, 1, .double_colon) or
|
||||||
|
next_n_is(state, 1, .colon_equal)
|
||||||
|
)) return try parse_statement_decl(state)
|
||||||
|
match current(state).kind {
|
||||||
|
.ident, .int, .float, .string, .minus, .open_paren: {
|
||||||
|
expression :: try parse_expr(state)
|
||||||
|
return try ast.add_expression_stmt(&state.builder, expression)
|
||||||
|
}
|
||||||
|
else: return .unexpected_token
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -202,12 +212,12 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_expr proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
parse_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
return try parse_expr_bp(state, BindingPowerScalar(0))
|
return try parse_expr_bp(state, BindingPowerScalar(0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! ParseError {
|
||||||
token := current(state)
|
token := current(state)
|
||||||
left := if (prefix_binding_power(token.kind)) |right_bp| {
|
left := if (prefix_binding_power(token.kind)) |right_bp| {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
@@ -225,6 +235,10 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! (me
|
|||||||
|
|
||||||
while true {
|
while true {
|
||||||
token = current(state)
|
token = current(state)
|
||||||
|
if (token.kind == .open_paren) {
|
||||||
|
left = try parse_call(state, left)
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (infix_binding_power(token.kind)) |power| {
|
if (infix_binding_power(token.kind)) |power| {
|
||||||
if (power.left < min_bp) break
|
if (power.left < min_bp) break
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
@@ -246,7 +260,30 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! (me
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_primary proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
||||||
|
_ = try consume(state, .open_paren)
|
||||||
|
arguments arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
|
defer arraylist.deinit(&arguments)
|
||||||
|
|
||||||
|
while !next_is(state, .close_paren) and !next_is(state, .newline) and
|
||||||
|
!next_is(state, .close_curly) and !next_is(state, .eof)
|
||||||
|
{
|
||||||
|
argument :: try parse_expr(state)
|
||||||
|
try arraylist.append(&arguments, argument)
|
||||||
|
if (!next_is(state, .comma)) break
|
||||||
|
state.next_token += 1
|
||||||
|
}
|
||||||
|
close :: try consume(state, .close_paren)
|
||||||
|
return try ast.add_call(
|
||||||
|
&state.builder,
|
||||||
|
ast.Span{ start = ast.builder_span(&state.builder, callee).start, end = close.span.end },
|
||||||
|
callee,
|
||||||
|
arguments.items,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
token :: current(state)
|
token :: current(state)
|
||||||
match token.kind {
|
match token.kind {
|
||||||
.open_paren: {
|
.open_paren: {
|
||||||
@@ -284,7 +321,7 @@ parse_primary proc(state @mut State) ast.NodeId ! (mem.AllocError | diagnostics.
|
|||||||
else
|
else
|
||||||
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||||
|
|
||||||
if (token.kind != .newline and token.kind != .close_paren and token.kind != .close_curly and token.kind != .eof) {
|
if (token.kind != .newline and token.kind != .comma and token.kind != .close_paren and token.kind != .close_curly and token.kind != .eof) {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -159,3 +159,103 @@ recovers_malformed_groups_and_preserves_following_statements test {
|
|||||||
try testing.expect_equal("after", ast.source_text(&tree, after.name))
|
try testing.expect_equal("after", ast.source_text(&tree, after.name))
|
||||||
try testing.expect_equal("4", ast.source_text(&tree, ast.node_span(&tree, after.initializer)))
|
try testing.expect_equal("4", ast.source_text(&tree, ast.node_span(&tree, after.initializer)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parses_call_statements_and_postfix_precedence test {
|
||||||
|
strpool.STRINGS = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.STRINGS)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`main proc() void {
|
||||||
|
` ping()
|
||||||
|
` result int := -factory()(add(1, 2 * 3), (4 + 5),) + 6
|
||||||
|
` (handler)(result)
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||||
|
try testing.expect_equal(3, ast.block_statement_count(&tree, function.body))
|
||||||
|
ping_stmt :: ast.block_statement(&tree, function.body, 0)
|
||||||
|
try testing.expect_equal(ast.StmtKind.expression, ast.stmt_kind(&tree, ping_stmt))
|
||||||
|
ping :: ast.statement_expr(&tree, ping_stmt)
|
||||||
|
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, ping))
|
||||||
|
try testing.expect_equal(0, ast.call_arg_count(&tree, ping))
|
||||||
|
try testing.expect_equal("ping", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, ping).callee)))
|
||||||
|
try testing.expect_equal("ping()", ast.source_text(&tree, ast.call_expr(&tree, ping).span))
|
||||||
|
|
||||||
|
result :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1))
|
||||||
|
try testing.expect_equal(ast.Mutability.variable, result.mutability)
|
||||||
|
add :: ast.binary_expr(&tree, result.initializer)
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.add, add.operator)
|
||||||
|
try testing.expect_equal("6", ast.source_text(&tree, ast.node_span(&tree, add.right)))
|
||||||
|
negate :: ast.unary_expr(&tree, add.left)
|
||||||
|
call :: negate.operand
|
||||||
|
try testing.expect_equal(2, ast.call_arg_count(&tree, call))
|
||||||
|
factory :: ast.call_expr(&tree, call).callee
|
||||||
|
try testing.expect_equal(0, ast.call_arg_count(&tree, factory))
|
||||||
|
try testing.expect_equal("factory", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, factory).callee)))
|
||||||
|
nested :: ast.call_arg(&tree, call, 0)
|
||||||
|
try testing.expect_equal(2, ast.call_arg_count(&tree, nested))
|
||||||
|
try testing.expect_equal("add", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, nested).callee)))
|
||||||
|
try testing.expect_equal("1", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, nested, 0))))
|
||||||
|
multiply :: ast.binary_expr(&tree, ast.call_arg(&tree, nested, 1))
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator)
|
||||||
|
try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, multiply.left)))
|
||||||
|
try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, multiply.right)))
|
||||||
|
grouped :: ast.binary_expr(&tree, ast.call_arg(&tree, call, 1))
|
||||||
|
try testing.expect_equal(ast.BinaryOperator.add, grouped.operator)
|
||||||
|
try testing.expect_equal("4", ast.source_text(&tree, ast.node_span(&tree, grouped.left)))
|
||||||
|
try testing.expect_equal("5", ast.source_text(&tree, ast.node_span(&tree, grouped.right)))
|
||||||
|
|
||||||
|
handler :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 2))
|
||||||
|
try testing.expect_equal("handler", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, handler).callee)))
|
||||||
|
try testing.expect_equal(1, ast.call_arg_count(&tree, handler))
|
||||||
|
try testing.expect_equal("result", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, handler, 0))))
|
||||||
|
}
|
||||||
|
|
||||||
|
recovers_malformed_calls_and_preserves_siblings test {
|
||||||
|
strpool.STRINGS = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.STRINGS)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`main proc() void {
|
||||||
|
` bad(1 2)
|
||||||
|
` good()
|
||||||
|
` missing(1
|
||||||
|
` after :: 3
|
||||||
|
` holes(, 2)
|
||||||
|
`}
|
||||||
|
`tail :: f()
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parse(scan_state.tokens.items, source, &diagnostic_store, mem.c_allocator)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
|
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
||||||
|
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||||
|
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||||
|
try testing.expect_equal(5, ast.block_statement_count(&tree, function.body))
|
||||||
|
for ([0, 2]) |index| {
|
||||||
|
statement :: ast.block_statement(&tree, function.body, usize(index))
|
||||||
|
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, statement))
|
||||||
|
try testing.expect(diagnostics.get(&diagnostic_store, ast.invalid(&tree, statement).diagnostic) != null)
|
||||||
|
}
|
||||||
|
good :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 1))
|
||||||
|
try testing.expect_equal("good", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, good).callee)))
|
||||||
|
after :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 3))
|
||||||
|
try testing.expect_equal("after", ast.source_text(&tree, after.name))
|
||||||
|
holes :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 4))
|
||||||
|
try testing.expect_equal(2, ast.call_arg_count(&tree, holes))
|
||||||
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.call_arg(&tree, holes, 0)))
|
||||||
|
try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, holes, 1))))
|
||||||
|
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
||||||
|
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
||||||
|
try testing.expect_equal(0, ast.call_arg_count(&tree, tail.initializer))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user