feat(asm): Phase A.0 — add kw_asm keyword + lex test
`asm` now lexes as a dedicated `kw_asm` keyword (Token.Tag + keyword map entry).
`volatile` and `clobbers` stay out of the global keyword table — they are
recognized contextually only inside an `asm { … }` body (PLAN-ASM Deviation 4).
- token.zig: kw_asm tag + `.{ "asm", .kw_asm }` map entry.
- lsp/server.zig: classifyToken exhaustive switch gained the .kw_asm arm
(the new enum value forced coverage — intended tripwire).
- lexer.test.zig (new, wired into root.zig barrel): locks `asm`->kw_asm and
`volatile`/`clobbers`->identifier.
Lock commit (behavior-locking passing test). zig build test green (445 unit).
This commit is contained in:
@@ -6,13 +6,18 @@ commit, one step at a time per the cadence rule (no commit may both add a test
|
|||||||
and make it pass).
|
and make it pass).
|
||||||
|
|
||||||
## Last completed step
|
## Last completed step
|
||||||
**0.2** — docs: CLAUDE.md §Testing + §Test-layout now document the
|
**A.0** — `kw_asm` keyword (first compiler code). Added the `kw_asm` `Token.Tag`
|
||||||
`<name>.build` JSON sidecar (`aot` + `target` keys, ir-only arch-gating,
|
variant + `.{ "asm", .kw_asm }` keyword-map entry in `src/token.zig`; `volatile` /
|
||||||
unknown-key-is-error) and list it alongside the other `expected/` files,
|
`clobbers` deliberately stay OUT of the global table (contextual). New exhaustive
|
||||||
replacing the stale standalone `.aot` marker prose. Docs-only — no build impact.
|
`Tag` switch in `src/lsp/server.zig` `classifyToken` flagged the missing arm (the
|
||||||
**Phase 0 COMPLETE.**
|
intended coverage tripwire) — added `.kw_asm` to the keyword group. Lock test in
|
||||||
|
new `src/lexer.test.zig` (`asm`→`kw_asm`, `volatile`/`clobbers`→`identifier`),
|
||||||
|
wired into the `src/root.zig` barrel as `lexer_tests`. `zig build test` green (648
|
||||||
|
corpus, 0 failed; 445 unit, 0 failed — +1). Files: `src/token.zig`,
|
||||||
|
`src/lexer.test.zig`, `src/root.zig`, `src/lsp/server.zig`.
|
||||||
|
|
||||||
Prior: **0.1** — corpus runner **ir-only branch** for cross-target examples. Replaced
|
Prior: **0.2** — CLAUDE.md docs for `<name>.build`; **Phase 0 COMPLETE**.
|
||||||
|
**0.1** — corpus runner **ir-only branch** for cross-target examples. Replaced
|
||||||
0.0's loud placeholder bail: when `cfg.target` doesn't match the host (`ir_only`),
|
0.0's loud placeholder bail: when `cfg.target` doesn't match the host (`ir_only`),
|
||||||
`sweepRoot` skips run/build/exec and verifies via `sx ir --target` only —
|
`sweepRoot` skips run/build/exec and verifies via `sx ir --target` only —
|
||||||
asserting `.exit` (ir cmd) + `.ir` (normalized stdout) + `.stderr`, never
|
asserting `.exit` (ir cmd) + `.ir` (normalized stdout) + `.stderr`, never
|
||||||
@@ -26,21 +31,21 @@ guards fire: corrupting the `.ir` → IR mismatch; deleting it → the require-f
|
|||||||
`src/corpus_run.test.zig`, `examples/1639-*`.
|
`src/corpus_run.test.zig`, `examples/1639-*`.
|
||||||
|
|
||||||
## Current state
|
## Current state
|
||||||
Phase 0 steps 0.0 + 0.1 landed (test-infra only — no compiler code). The corpus
|
Phase 0 complete (corpus target-gating + `.build` JSON). Phase A underway: `asm`
|
||||||
runner reads `expected/<name>.build` (JSON `{ aot, target }`), threads `--target`,
|
now lexes as `kw_asm` (A.0). No parsing/AST yet — `asm` in source would reach
|
||||||
and gates on host arch+os: a matching target **executes** (full exit/stdout/stderr
|
`parsePrimary` and fall through to the existing "unexpected token" error until
|
||||||
+ optional `.ir`); a mismatch runs **ir-only** (`sx ir --target`, asserting
|
A.1. Phase B–E feasibility already confirmed against the live tree
|
||||||
exit+ir+stderr, `.ir` required). Phase A–E feasibility already confirmed against
|
(`LLVMGetInlineAsm` / `LLVMBuildCall2` / `LLVMAppendModuleInlineAsm` in LLVM@19
|
||||||
the live tree (`LLVMGetInlineAsm` / `LLVMBuildCall2` / `LLVMAppendModuleInlineAsm`
|
`Core.h`; ERR-stream `extractvalue`→tuple in `emit_llvm.zig:726-927`; lib-less
|
||||||
in LLVM@19 `Core.h`; ERR-stream `extractvalue`→tuple in `emit_llvm.zig:726-927`;
|
`extern`, 60 sites; `--target` a global CLI flag).
|
||||||
lib-less `extern`, 60 sites; `--target` a global CLI flag).
|
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
**A.0** (Phase A — first compiler code) — add the `kw_asm` keyword: `Token.Tag`
|
**A.1** (xfail) — parse `asm { … }` → `AsmExpr` / `AsmOperand` in `parsePrimary`
|
||||||
entry + keyword `StaticStringMap` in `src/token.zig`, plus a unit lex test
|
(`src/parser.zig`); add the `asm_expr` arm to `Node.Data` + the `AsmExpr` /
|
||||||
(`asm → kw_asm`). `volatile`/`clobbers` stay out of the global table
|
`AsmOperand` structs in `src/ast.zig` (per design §II.3); lowering still
|
||||||
(contextual). This is a **lock** commit (behavior-locking passing test). Then A.1
|
`bailDetail("inline asm codegen unimplemented")` in `src/ir/interp.zig` (or the
|
||||||
(parse `asm { … }` → `AsmExpr`, lowering bails loudly). See `PLAN-ASM.md` Phase A.
|
lower dispatch). Pin a parse-shape snapshot (`sx ir` or AST). The unimplemented
|
||||||
|
bail must be loud + named. See `PLAN-ASM.md` Phase A (A.1) + design §II.3–II.4.
|
||||||
|
|
||||||
## Log
|
## Log
|
||||||
- (init) Plan + design doc written; ASM stream opened.
|
- (init) Plan + design doc written; ASM stream opened.
|
||||||
@@ -53,6 +58,9 @@ entry + keyword `StaticStringMap` in `src/token.zig`, plus a unit lex test
|
|||||||
corrupt-.ir → mismatch and missing-.ir → loud failure. `zig build test` green.
|
corrupt-.ir → mismatch and missing-.ir → loud failure. `zig build test` green.
|
||||||
- (0.2) docs: CLAUDE.md documents `<name>.build` JSON sidecar (aot + target +
|
- (0.2) docs: CLAUDE.md documents `<name>.build` JSON sidecar (aot + target +
|
||||||
ir-only gating), replacing stale `.aot` marker prose. **Phase 0 COMPLETE.**
|
ir-only gating), replacing stale `.aot` marker prose. **Phase 0 COMPLETE.**
|
||||||
|
- (A.0) `kw_asm` keyword in token.zig (+ map entry); LSP `classifyToken` switch
|
||||||
|
coverage; lock test in new `lexer.test.zig` (wired via root.zig). `volatile` /
|
||||||
|
`clobbers` stay contextual identifiers. `zig build test` green (445 unit, +1).
|
||||||
|
|
||||||
## Known issues
|
## Known issues
|
||||||
None yet.
|
None yet.
|
||||||
|
|||||||
14
src/lexer.test.zig
Normal file
14
src/lexer.test.zig
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Lexer = @import("lexer.zig").Lexer;
|
||||||
|
const Tag = @import("token.zig").Tag;
|
||||||
|
|
||||||
|
// ASM stream Phase A.0: `asm` lexes as the dedicated `kw_asm` keyword, while
|
||||||
|
// `volatile` / `clobbers` deliberately stay plain identifiers (recognized
|
||||||
|
// contextually inside an `asm { … }` body, never reserved globally).
|
||||||
|
test "lex asm keyword; volatile/clobbers stay identifiers" {
|
||||||
|
var lex = Lexer.init("asm volatile clobbers");
|
||||||
|
const expected = [_]Tag{ .kw_asm, .identifier, .identifier };
|
||||||
|
for (expected) |exp| {
|
||||||
|
try std.testing.expectEqual(exp, lex.next().tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1685,6 +1685,7 @@ pub const Server = struct {
|
|||||||
.kw_callconv,
|
.kw_callconv,
|
||||||
.kw_extern,
|
.kw_extern,
|
||||||
.kw_export,
|
.kw_export,
|
||||||
|
.kw_asm,
|
||||||
.hash_run,
|
.hash_run,
|
||||||
.hash_import,
|
.hash_import,
|
||||||
.hash_insert,
|
.hash_insert,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
pub const llvm_api = @import("llvm_api.zig");
|
pub const llvm_api = @import("llvm_api.zig");
|
||||||
pub const token = @import("token.zig");
|
pub const token = @import("token.zig");
|
||||||
pub const lexer = @import("lexer.zig");
|
pub const lexer = @import("lexer.zig");
|
||||||
|
pub const lexer_tests = @import("lexer.test.zig");
|
||||||
pub const ast = @import("ast.zig");
|
pub const ast = @import("ast.zig");
|
||||||
pub const parser = @import("parser.zig");
|
pub const parser = @import("parser.zig");
|
||||||
pub const print = @import("print.zig");
|
pub const print = @import("print.zig");
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ pub const Tag = enum {
|
|||||||
kw_callconv, // callconv (calling convention annotation)
|
kw_callconv, // callconv (calling convention annotation)
|
||||||
kw_extern, // extern (import: external linkage, C ABI, no body)
|
kw_extern, // extern (import: external linkage, C ABI, no body)
|
||||||
kw_export, // export (define + expose: external linkage, C ABI)
|
kw_export, // export (define + expose: external linkage, C ABI)
|
||||||
|
kw_asm, // asm (inline assembly expression / global asm decl)
|
||||||
|
|
||||||
// Symbols
|
// Symbols
|
||||||
colon, // :
|
colon, // :
|
||||||
@@ -283,6 +284,9 @@ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
|
|||||||
.{ "callconv", .kw_callconv },
|
.{ "callconv", .kw_callconv },
|
||||||
.{ "extern", .kw_extern },
|
.{ "extern", .kw_extern },
|
||||||
.{ "export", .kw_export },
|
.{ "export", .kw_export },
|
||||||
|
// `asm` is a real keyword; `volatile` / `clobbers` stay OUT of this table
|
||||||
|
// (recognized contextually only inside an `asm { … }` body — see PLAN-ASM).
|
||||||
|
.{ "asm", .kw_asm },
|
||||||
});
|
});
|
||||||
|
|
||||||
pub fn getKeyword(bytes: []const u8) ?Tag {
|
pub fn getKeyword(bytes: []const u8) ?Tag {
|
||||||
|
|||||||
Reference in New Issue
Block a user