natives: parse native fn bodies and native { } blocks, with a parser gate test (step 2 of #53) #467
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
Step 2 of #53 (natives:
native fnbodies as IR andnative { }blocks). Step 1 (#410) landed the design recorddocs/design/NATIVE-BODIES.md(PR #412). This is its slice 1: the syntax and the parser, with a gate test. Nothing is checked or lowered yet.Goal
The parser reads the two new forms of section 2.1 and 5 of the record, and a parser gate test pins them:
native fnwith a body:native-body ::= "{" native-statement* native-result "}", attached toFunctionDeclaration.bodyas a new nodeNativeBody(statements, result).native { ... }at the start of an expression: a newExpressionKind.NativeBlock. Anativefollowed by anything else in an expression position stays the error it is today.intrinsicis a contextual word, read only where a native statement or result expects an operation; it stays usable as an ordinary name everywhere else. The parser joinsadd.i64.unchecked(identifier plus member accesses) into the intrinsic name; the lexer does not change.const name (: type)? = intrinsic ...; result: an operation or an operand (name path, integer, float,true,false); operands separated by commas.The checker answers every parsed native body or block with the single error
a `native` body is not supported yet(no rules N1-N10 yet; that is step 3). No lowering.Acceptance criteria
NativeBodyandExpressionKind.NativeBlockexist in the syntax tree; the parser builds them for every example of the record (sections 2.3 and 2.4:Int64.add,shiftedLeft,convert.i8.i64, theisOddblock, the multi-statement block withconst).const,nativefollowed by something other than{in an expression, an operation with a trailing comma, a missing operand separator.intrinsicis still a valid identifier outside native bodies (test: a variable, a function and a member namedintrinsic).native fnwithout a body parses and checks exactly as before (existing tests unchanged); the manifest (natives.trb) and everystd/file are untouched.a `native` body is not supported yetat thenativekeyword for a parsed body or block; a test pins the text.torb formatformats the new forms idempotently (formatter test for the examples), or, if the formatter walks the tree generically, a test says so.sh tools/gates.sh agreen in forge CI; the compiler's own sources do not use the form, so the current seed builds the commit.Design record point
docs/design/NATIVE-BODIES.mdsections 2 (source form), 5 (parsing) and 8 (slice 1).Seed impact
Yes, new syntax. This is the first commit of the two-commit rule ("teach both forms, refresh the seed, then migrate",
CLAUDE.md"Seed and breaking changes"). Nothing incompiler/orstd/uses the form yet, so the published seed still builds it. The seed refresh stays Torben's and happens after slice 3 (lowering), before thestdmigration.Size
One PR, one or two Conventional Commits, e.g.
feat(syntax): parse native fn bodies and native blocksand, if split,test(syntax): parser gate test for IR bodies of natives.Dependencies
Step 1 (#410, landed). Part of #53; blocks the checker step (rules N1-N10), the lowering step and the
stdmigration.[loop:torbscript] decided by the loop: step 2 of #53 is the parser slice with a gate test, as slice 1 of docs/design/NATIVE-BODIES.md cuts it. The checker answers a parsed body with a single "not supported yet" error so the slice is self-contained and cannot leak half-checked IR into std. It is seed-relevant (new syntax, first commit of the two-commit rule) but touches no seed file and nothing in compiler/ or std/ uses the form, so the current seed builds it; the refresh stays Torben's, after the lowering step. No owner question: the record decides the syntax and reserves nothing for the owner.
native fnbodies as IR andnative { }blocks #53[loop:torbscript] claim implement by session:loop-torbscript-20261024 until 2026-10-24T13:15:00Z
[loop:torbscript] implement: checkpoint, no PR yet (context guard at ~85k tokens), next: continue implement in a fresh worker. Tokens used: about 85k
Done: read LOOP.md, CLAUDE.md, CONTRIBUTING.md, NATIVE-BODIES.md; worktree
code/wt/torbscript-language-467on branchloop/467-native-body-parserfrom main, no commits yet.Findings for the next worker:
FunctionDeclaration.bodyisBlock?(compiler/src/syntax/ast.trb); the record wants aNativeBody(statements, result)node attached there. Least invasive: keepbody: Block?and add a defaulted fieldnativeBody: NativeBody? = None, or turn it into a sum type, then fix every reader of.body(grep\.bodyon FunctionDeclaration in checker, lower, canon, format, highlight, lint, language-server).ExpressionKind.NativeBlock(block: NativeBody)needs a.NativeBlockarm in every exhaustive match overExpressionKind:syntax/nodes.trb,canon/walk.trb,canon/calls.trb,documentation/canon.trb,highlight/scope.trb,highlight/resolve.trb,semantics/scripts.trb,semantics/resolve-types.trb,semantics/checker/{expression,close,place,spelling,constant,quote}.trb,ir/lower/{expression,closure}.trb,language-server/{completion,test-tree}.trb,lint/{unread-binding,redundant-wrap}.trb, plusformat/width.trb. The checker arm inchecker/expression.trbreportsa `native` body is not supported yetat thenativekeyword; the same error for anative fnbody in the function-shape check.parseModifiers/ function body parse insyntax/parser/declarations.trb(native modifier isKeyword.Native),syntax/parser/expressions.trbfor the primary expression.intrinsicis read withparser.isAtWord("intrinsic")likedeprecated/protected; the nameadd.i64.uncheckedis joined from identifier and member tokens (keywords allowed after.vianameOrKeyword).compiler/tests/parser.test.trb(gate test),compiler/tests/format.test.trb/canon.test.trb(formatter idempotence), a checker test for the error text. Docs:docs/change only if a page names the grammar; then regenerateskills/.[loop:torbscript] claim implement by session:loop-torbscript-20261024 until 2026-10-24T14:30:00Z
[loop:torbscript] implement: checkpoint 2, no PR yet (context guard), next: continue implement in a fresh worker. Tokens used: about 85k
Branch
loop/467-native-body-parseris pushed with one WIP commit (67e4422e), worktreecode/wt/torbscript-language-467. Nothing compiled or run locally (CI only).Done
compiler/src/syntax/ast.trb:Modifiers.nativeSpan: Span? = None;FunctionDeclaration.nativeBody: NativeBody? = None;ExpressionKind.NativeBlock(body: NativeBody); new typesNativeBody(keyword, statements, result, span),NativeStatement,NativeResult {Operation, Operand},NativeOperation(name, nameSpan, operands, span),NativeOperand(kind, span),NativeOperandKind {Path, IntegerLiteral, FloatLiteral, BoolLiteral}.compiler/src/syntax/parser/natives.trb(new):parseNativeBody(parser, keyword: Span)with the grammar of record section 2.1.intrinsicis an operation only when followed by an identifier (result) or afterconst x =(statement); otherwise an ordinary operand name. Errors: a body ending inconstor empty ("Anativebody ends with a result, not with aconst"), missing operand, trailing comma (the lexer joins the line, so}is "Expected an operand"), missing comma between operands ("Expected,or the end of the operation").To do
syntax/parser/declarations.trb): setnativeSpaninparseModifiers(.Reserved(.Native)arm, span of the token);parseFunctiontakesnative: Span? = Noneand, when set and at{, buildsnativeBody = Some parseNativeBody(parser, keyword)withbody = None; passmodifiers.nativeSpanfromparseDeclaration(line ~28),parseMethodandparseStaticMember(native static fn from, needs the modifiers passed in fromparseMember). Expression:parsePrimaryExpressionarm.Reserved(.Native) if parser.kindAt(1) == TokenKind.BraceOpen->ExpressionKind.NativeBlock parseNativeBody(parser, start)after bumpingnative; any othernativekeeps "Expected an expression".statements.trbline ~85: the.Reserved(.Public | ... | .Native ...)declaration arm must not takenative {: add an earlier arm or guard sonative {falls to the expression arm.startsCommandArgumentmay need.Reserved(.Native)only if a command argument may start withnative {(not required).NativeBlockarms in every exhaustive match overExpressionKind(grep.Block(/ExpressionKind.Block): the files in the first checkpoint. Syntax walkers (nodes.trb,canon/walk.trb, highlight, lint, language-server) treat it as a leaf with no sub-expressions (operands are names, notExpressions);format/width.trband the formatter print it (see 4). Checkerexpression.trb: reporta `native` body is not supported yetatbody.keyword, resultInvalid/absorbing type. Function shape (checker/declaration.trbnear line 760, before thefunction.body.isSome() || ...return): the same error atnativeBody.keywordwhenfunction.nativeBodyisSome. Lowering (ir/lower/function.trb:50,expression.trb,closure.trb): unreachable arm like the other checker-rejected nodes.syntax/dump.trb) if it matches onExpressionKindexhaustively.format/prints a function body and expressions; printnative fn ... {+ one native statement per line + result, andnative {...}withconst x = intrinsic name a, b/ operands joined by,. Idempotence test incompiler/tests/format.test.trbfor the record's examples (sections 2.3, 2.4).compiler/tests/parser.test.trbgate test (every record example:Int64.add,shiftedLeft,convert.i8.i64,isOddblock, multi-statement block withconst; rejects: block without result, body ending inconst,nativenot followed by{in an expression, trailing comma, missing separator;intrinsicas variable, function and member name outside native bodies); a checker test pinning the error text for anative fnbody and a block (notenativeis only allowed instd/packages,requireStandardLibrary: the test must use a std-package fixture the way existing checker tests ofnativedo, or assert both messages).tools/check-commits.sh,torb format --checkon touched files (no localtorbbinary found; CI runs it), amend the WIP commit message into the final Conventional Commits, PR withCloses #467, ci-wait.sh.[loop:torbscript] claim implement by session:loop-torbscript-20261024 until 2026-10-24T13:30:00Z (continuation 3 of 3, from checkpoint 5993)
[loop:torbscript] implement: checkpoint 3, no PR yet (context guard), next: implement in a fresh worker. Tokens used: about 80k
Branch
loop/467-native-body-parserpushed, two WIP commits (67e4422e,ee101b4d); worktreecode/wt/torbscript-language-467. Nothing compiled or run locally (CI only).mainhad not moved at the time of the push.Done in
ee101b4dparseModifierssetsnativeSpan;parseFunction(parser, isMember, native: Span? = None)reads a{body withparseNativeBodywhennativeis set (body = None,nativeBody = Some(...));nativepassed fromparseDeclaration,parseMember->parseMethod(..., native:)(bothfnandvar fn) andparseStaticMember(parser, native)(native static fn from).parsePrimaryExpression:.Reserved(.Native) if kindAt(1) == BraceOpen->ExpressionKind.NativeBlock; any othernativekeeps "Expected an expression".parseStatement: an earlier arm sendsnative {toStatementKind.Evaluate parseCommandExpression(parser)instead of the declaration arm.NativeBlockarms in every exhaustiveExpressionKindmatch (found by script: no wildcard arm):syntax/nodes.trb,canon/walk.trb,documentation/canon.trb,semantics/checker/quote.trb,language-server/test-tree.trb,lint/unread-binding.trb(leaf arms),semantics/checker/expression.trb(error). All other matches overExpressionKindhave a wildcard;ir/lower/expression.trbfalls tounsupportedSlot("an expression the front end rejected").nativebody is not supported yet" (capitalised like every diagnostic) atbody.keyword, inevaluateExpressionand at the top ofcheckFunctionShape(declaration.trb) forfunction.nativeBody. Outsidestd/thenativemodifier error ofrequireStandardLibrarycomes as well.format/layout.trb), no change expected for the new forms; the idempotence test is still to be written.natives.trbshortened to the 120 column width.To do
compiler/tests/parser.test.trbgate test with every example of docs/design/NATIVE-BODIES.md sections 2.3 and 2.4 (Int64.add,shiftedLeft,native static fn fromwithconvert.i8.i64,isOddblock, two-statement block) and the rejects (body without result / ending inconst,nativenot followed by{in an expression, trailing comma, missing separator,const x = notIntrinsic);intrinsicas variable, function and member name. Formatter idempotence incompiler/tests/format.test.trbfor the same examples. Checker test pinning the error text (see the existing tests ofnativefor the std-package fixture).tools/check-commits.sh; squash the two WIP commits into one Conventional Commit (feat(syntax): parse native fn bodies and native blocks, bodyCloses #467), rebase on main, force-push, PR,ci-wait.sh.[loop:torbscript] release implement: continuation budget used up, checkpoint 6000, next: implement
[loop:torbscript] claim implement by session:loop-torbscript-20261024-1000 until 2026-10-24T14:10:28Z
[loop:torbscript] implement: checkpoint 4, no PR yet (context guard), next: continue implement in a fresh worker. Tokens used: about 85k
Branch
loop/467-native-body-parserrebased onto currentmain(57085dce, the tls commit) and force-pushed; two WIP commits (c864ae3f,d0755639), worktreecode/wt/torbscript-language-467. Nothing compiled or run locally (CI only). The source side from checkpoint 3 is complete and re-read this round (AST,parser/natives.trb, wiring indeclarations.trb/expressions.trb/statements.trb,NativeBlockarms, the checker error incheckFunctionShapeandevaluateExpression); no source change was needed.To do (tests only, then PR)
compiler/tests/parser.test.trb: newgroup(raw"""strings need no brace escapes). Helper that parsesnative fn .../extend Int64 with Add { native fn ... }(ExtendDeclaration.members -> MemberKind.Function ->nativeBody) and a block (const remainder = native { ... }->Bind(binding).value.kindis.NativeBlock(body)), plus adescribe(NativeBody): Stringrenderer (const sum = add.i64.unchecked(left, right); less.i64(sum, limit)). Cases: record 2.3/2.4 examples (Int64.add,shiftedLeft,native static fn fromwithconvert.i8.i64,isOddblock, two-statement block); rejects: no result / ends inconst("Anativebody ends with a result, not with aconst"),native+ non-{in an expression ("Expected an expression"), trailing comma ("Expected an operand ..."), missing separator ("Expected,or the end of the operation ..."),const x = notIntrinsic;intrinsicas variable, function and member name outside bodies;native fnwithout body hasnativeBody == None.nativebody is not supported yet" incompiler/tests/program-shape.test.trbnext to the group "nativebelongs to the standard library" (problemsfrom./harness; an ordinary package also gets thenative is only allowederror, so use.contains). Block case:fn probe(): Int { native { intrinsic add.i64 1, 2 } }.compiler/tests/format.test.trbkeeps/formatsfor the record examples (the formatter is token based; if CI showsdroppedor a changed layout inside a body, fixformat/layout.trb).feat(syntax): parse native fn bodies and native blocks(bodyCloses #467; the test commit may stay separate astest(syntax): ...),tools/check-commits.sh origin/main, push, open PRCloses #467,ci-wait.sh; fix red CI (first CI run is the first compile of all of this, expect type errors to fix).[loop:torbscript] implement: checkpoint 4 above is the current state, next: continue implement in a fresh worker. CONTINUE: torbscript/language#467
[loop:torbscript] claim implement by session:loop-torbscript-20261024-1000 until 2026-10-24T14:12:30Z (continuation 2 of 3, from checkpoint 4)
[loop:torbscript] release implement: worker run ended (timeout) after opening PR #472; branch
loop/467-native-body-parserat5587ddd7, forge CI pending, review requested from argus. next: review (wait for CI and the merge gate; fix round on the same branch if red).native fnbodies as IR andnative { }blocks #53native fnbodies andnative { }blocks, kernel test (step 4 of #53) #484native fnbodies as IR andnative { }blocks #53native fnbodies as IR andnative { }blocks #53native fnbodies as IR andnative { }blocks #53