Skip to content

Commit 83bd3b9

Browse files
olwangclaude
andcommitted
feat(selfhost): AST producer — param defaults, &-types, unclosed body → 592/606
- Param default values (`height: Int = 2`) now emit a `default` child (dump_param). - Type names skip non-ident prefix tokens like a leading `&` (`&Bytes` → name=Bytes), matching parse_type_ref's name_index; bounded by a type terminator. - Unclosed fn body (`fn f() { …<EOF>`) → program-level malformed-declaration via a proper fn_malformed predicate (no name / unclosed params / unclosed body). - next_decl_skip now mirrors skip_unknown_top_level: an unclosed `{` on the decl line skips to EOF (not just the line end), so leftover tokens aren't re-parsed as spurious unknown-top-level items. Corpus reach: 581 -> 592 / 606 byte-exact (+11), still 0 run-failures. Floor -> 592. 55 samples green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 51c2141 commit 83bd3b9

5 files changed

Lines changed: 80 additions & 10 deletions

File tree

crates/rsscript/src/selfhost_parity.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1522,7 +1522,7 @@ const AST_SAMPLE_MIN: usize = 6;
15221522
/// Floor for `ast_parity_corpus` — the number of corpus files whose rss AST dump
15231523
/// already matches the oracle byte-for-byte. Ratchets up as the producer's
15241524
/// coverage grows; a drop signals a regression. (Full parity = files.len().)
1525-
const AST_CORPUS_PARITY_FLOOR: usize = 581;
1525+
const AST_CORPUS_PARITY_FLOOR: usize = 592;
15261526

15271527
/// Step-2 measurement gate (ignored by default): how many corpus files the rss
15281528
/// producer reproduces byte-for-byte. Not full parity yet — this ratchets a floor

selfhost/astdump.rss

Lines changed: 58 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,11 @@ fn is_type_prefix(toks: read List<Tok>, j: read Int) -> Bool {
969969
return is_kw_text(toks: read toks, i: read j, word: read "read") || is_kw_text(toks: read toks, i: read j, word: read "mut") || is_kw_text(toks: read toks, i: read j, word: read "take") || is_kw_text(toks: read toks, i: read j, word: read "fresh") || is_kw_text(toks: read toks, i: read j, word: read "handle") || is_kw_text(toks: read toks, i: read j, word: read "weak") || is_kw_text(toks: read toks, i: read j, word: read "noescape") || is_kw_text(toks: read toks, i: read j, word: read "owned")
970970
}
971971

972+
// Tokens that end a type ref (used to bound the name search).
973+
fn is_type_terminator(toks: read List<Tok>, j: read Int) -> Bool {
974+
return tk_kind(toks: read toks, i: read j) == TOK_EOF || at_symbol(toks: read toks, i: read j, code: read SYM_COMMA) || at_symbol(toks: read toks, i: read j, code: read SYM_RPAREN) || at_symbol(toks: read toks, i: read j, code: read SYM_RANGLE) || at_symbol(toks: read toks, i: read j, code: read SYM_RBRACE) || at_symbol(toks: read toks, i: read j, code: read SYM_LBRACE) || at_symbol(toks: read toks, i: read j, code: read SYM_SEMI) || at_symbol(toks: read toks, i: read j, code: read SYM_EQ)
975+
}
976+
972977
fn emit_type_ref(toks: read List<Tok>, i: read Int, tag: read String, eff: read String, d: read Int) -> Int {
973978
// Prefix flags reflect ONLY the token at `i` (matching parse_type_ref, which
974979
// reads is_fresh/is_noescape/is_owned from tokens[start]).
@@ -978,9 +983,20 @@ fn emit_type_ref(toks: read List<Tok>, i: read Int, tag: read String, eff: read
978983
if at_ident(toks: read toks, i: read i, wid: read WORD_FRESH) { isFresh = true }
979984
else if is_kw_text(toks: read toks, i: read i, word: read "noescape") { isNoescape = true }
980985
else if is_kw_text(toks: read toks, i: read i, word: read "owned") { isOwned = true }
981-
// Name is the first non-prefix token.
986+
// Name is the first non-prefix IDENT (parse_type_ref's name_index skips both
987+
// prefix keywords AND non-ident tokens like a leading `&`), bounded by a type
988+
// terminator so a malformed type never scans away.
982989
let mut j = i
983-
while is_type_prefix(toks: read toks, j: read j) { j = j + 1 }
990+
let mut scanning = true
991+
while scanning {
992+
if is_ident_tok(toks: read toks, i: read j) && is_type_prefix(toks: read toks, j: read j) == false {
993+
scanning = false
994+
} else if is_type_terminator(toks: read toks, j: read j) {
995+
scanning = false
996+
} else {
997+
j = j + 1
998+
}
999+
}
9841000
let name = tk_text(toks: read toks, i: read j)
9851001
j = j + 1
9861002
local sb = StringBuilder.new()
@@ -1825,7 +1841,12 @@ fn emit_params(toks: read List<Tok>, open: read Int, close: read Int, d: read In
18251841
line = String.concat(left: read l2, right: read effStr)
18261842
}
18271843
emit(depth: read d, s: read line)
1828-
emit_type_ref(toks: read toks, i: read k, tag: read "type", eff: read "", d: read (d + 1))
1844+
let afterType = emit_type_ref(toks: read toks, i: read k, tag: read "type", eff: read "", d: read (d + 1))
1845+
// Default value: `name: Type = <expr>` (dump_param emits it after type).
1846+
if at_symbol(toks: read toks, i: read afterType, code: read SYM_EQ) {
1847+
emit(depth: read (d + 1), s: read "default")
1848+
emit_expr(toks: read toks, start: read (afterType + 1), end: read segEnd, d: read (d + 2))
1849+
}
18291850
}
18301851
}
18311852
if comma < 0 { i = close } else { i = comma + 1 }
@@ -2615,8 +2636,7 @@ fn main() -> Unit {
26152636
// A fn whose name won't parse (`fn (…)`) is a program-level malformed
26162637
// declaration (parse_function_decl returns None) — counted for the
26172638
// section-5 marker, not emitted as a fn.
2618-
let ns = fn_name_start(toks: read toks, start: read i)
2619-
if ns >= 0 && take_function_name(toks: read toks, i: read ns) < 0 {
2639+
if fn_malformed(toks: read toks, start: read i) {
26202640
malDecl = malDecl + 1
26212641
i = advance(toks: read toks, i: read i, r: read next_decl_skip(toks: read toks, start: read i))
26222642
} else {
@@ -2659,6 +2679,29 @@ fn fn_name_start(toks: read List<Tok>, start: read Int) -> Int {
26592679
return j + 1
26602680
}
26612681

2682+
// True if a fn head at `start` fails to parse (parse_function_decl returns None):
2683+
// no name, unclosed params `(`, or a body `{` with no matching `}`.
2684+
fn fn_malformed(toks: read List<Tok>, start: read Int) -> Bool {
2685+
let ns = fn_name_start(toks: read toks, start: read start)
2686+
if ns < 0 { return false }
2687+
let nameEnd = take_function_name(toks: read toks, i: read ns)
2688+
if nameEnd < 0 { return true }
2689+
let popen = parse_generic_params(toks: read toks, i: read nameEnd)
2690+
let mut sigStart = popen
2691+
if at_symbol(toks: read toks, i: read popen, code: read SYM_LPAREN) {
2692+
let pclose = find_matching(toks: read toks, open: read popen, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
2693+
if pclose < 0 { return true }
2694+
sigStart = pclose + 1
2695+
}
2696+
let sigEnd = function_signature_end(toks: read toks, start: read sigStart)
2697+
if at_symbol(toks: read toks, i: read sigEnd, code: read SYM_LBRACE) {
2698+
if find_matching(toks: read toks, open: read sigEnd, openSym: read SYM_LBRACE, closeSym: read SYM_RBRACE) < 0 {
2699+
return true
2700+
}
2701+
}
2702+
return false
2703+
}
2704+
26622705
// True if token i begins a top-level `features:` header (depth checked by caller).
26632706
fn is_features_header(toks: read List<Tok>, i: read Int) -> Bool {
26642707
return is_kw_text(toks: read toks, i: read i, word: read "features") && at_symbol(toks: read toks, i: read (i + 1), code: read SYM_COLON)
@@ -2760,12 +2803,18 @@ fn is_feature_word(toks: read List<Tok>, i: read Int) -> Bool {
27602803
}
27612804

27622805
// Skip an unsupported top-level item: to its block end if it has one, else its line end.
2806+
// Skip an unknown/malformed top-level declaration (mirrors skip_unknown_top_level):
2807+
// if the declaration line opens a `{`, skip to its matching `}` (or to EOF when it
2808+
// never closes); otherwise skip to the declaration line end.
27632809
fn next_decl_skip(toks: read List<Tok>, start: read Int) -> Int {
27642810
let n = List.len(list: read toks)
2765-
let openb = find_block_open(toks: read toks, start: read start, limit: read n)
2766-
if openb >= 0 && tk_line(toks: read toks, i: read openb) == tk_line(toks: read toks, i: read start) {
2811+
let lineEnd = declaration_line_end(toks: read toks, start: read start)
2812+
let openb = find_block_open(toks: read toks, start: read start, limit: read lineEnd)
2813+
let mut r = lineEnd
2814+
if openb >= 0 {
27672815
let close = find_matching(toks: read toks, open: read openb, openSym: read SYM_LBRACE, closeSym: read SYM_RBRACE)
2768-
if close > openb { return close + 1 }
2816+
if close < 0 { r = n } else { r = close + 1 }
27692817
}
2770-
return declaration_line_end(toks: read toks, start: read start)
2818+
if r > start { return r }
2819+
return start + 1
27712820
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Default parameter values: a call may omit a trailing parameter that declares a
2+
// default (`name: Type = <expr>`); the default is filled in at lowering so every
3+
// backend sees a complete call.
4+
fn box_volume(width: Int, height: Int = 2, depth: Int = 3) -> Int {
5+
return width * height * depth
6+
}
7+
8+
fn main() -> Unit {
9+
Log.write(message: read String.from_int(value: box_volume(width: 5)))
10+
Log.write(message: read String.from_int(value: box_volume(width: 5, height: 4)))
11+
Log.write(message: read String.from_int(value: box_volume(width: 5, height: 4, depth: 6)))
12+
return Unit
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// expect: RS1004
2+
3+
fn hash(data: &Bytes) -> UInt64 {
4+
return Bytes.hash(data: read data)
5+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// expect: RS0015
2+
fn main() -> Unit {
3+
let x = 1

0 commit comments

Comments
 (0)