Skip to content

Commit ca14e9d

Browse files
olwangclaude
andcommitted
feat(selfhost): AST producer — unary !/~ + negative number literals → 245/569
- Negative number `-<num>` (exactly two tokens) emits `number -N`, checked before binary/closure so it doesn't split as subtraction (matches parse_expr). - Prefix `!x` desugars to `x == false` (binary equal, right `ident false`); `~x` to `Int.bit_not(value: x)`; both after binary split, with paren-stripped operand. Corpus reach: 242 -> 245 / 569 byte-exact, still 0 run-failures. Floor -> 245. Diminishing returns confirmed: remaining mismatches are benchmark/selfhost files that each stack several still-unsupported constructs (interpolation, effect- receiver calls, fn attributes, protocols/impls). New sample unary.rss; 13 samples byte-exact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 6ad0633 commit ca14e9d

3 files changed

Lines changed: 41 additions & 1 deletion

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 = 242;
1525+
const AST_CORPUS_PARITY_FLOOR: usize = 245;
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: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,12 @@ fn emit_expr(toks: read List<Tok>, start: read Int, end: read Int, d: read Int)
234234
emit(depth: read d, s: read "unknown-expr")
235235
return Unit
236236
}
237+
// Negative number literal: `-` <number> exactly (before binary/closure, as in
238+
// parse_expr) so it doesn't split as subtraction.
239+
if e - s == 2 && at_symbol(toks: read toks, i: read s, code: read SYM_MINUS) && tk_kind(toks: read toks, i: read (s + 1)) == TOK_NUMBER {
240+
emit(depth: read d, s: read String.concat(left: read "number -", right: read escape(s: read tk_text(toks: read toks, i: read (s + 1)))))
241+
return Unit
242+
}
237243
// Closure `|params| body` — checked before binary split so its `|` pipes are
238244
// not read as bit-or (matches parse_expr's ordering).
239245
if at_symbol(toks: read toks, i: read s, code: read SYM_PIPE) {
@@ -250,6 +256,20 @@ fn emit_expr(toks: read List<Tok>, start: read Int, end: read Int, d: read Int)
250256
emit_expr(toks: read toks, start: read (opIdx + w), end: read e, d: read (d + 1))
251257
return Unit
252258
}
259+
// Prefix `!x` desugars to `x == false`; `~x` to `Int.bit_not(value: x)`.
260+
if at_symbol(toks: read toks, i: read s, code: read SYM_BANG) {
261+
emit(depth: read d, s: read "binary op=equal")
262+
emit_unary_operand(toks: read toks, start: read (s + 1), end: read e, d: read (d + 1))
263+
emit(depth: read (d + 1), s: read "ident false")
264+
return Unit
265+
}
266+
if at_symbol(toks: read toks, i: read s, code: read SYM_TILDE) {
267+
emit(depth: read d, s: read "call")
268+
emit(depth: read (d + 1), s: read "callee-qualified namespace=Int name=bit_not")
269+
emit(depth: read (d + 1), s: read "arg name=value")
270+
emit_unary_operand(toks: read toks, start: read (s + 1), end: read e, d: read (d + 2))
271+
return Unit
272+
}
253273
// Trailing top-level `?` => try.
254274
if at_symbol(toks: read toks, i: read (e - 1), code: read SYM_QUESTION) {
255275
emit(depth: read d, s: read "try")
@@ -315,6 +335,19 @@ fn emit_closure(toks: read List<Tok>, start: read Int, end: read Int, d: read In
315335
return Unit
316336
}
317337

338+
// A unary operand [start, end): strip parens wrapping the whole operand (matches
339+
// unary_operand_range), then emit the expression.
340+
fn emit_unary_operand(toks: read List<Tok>, start: read Int, end: read Int, d: read Int) -> Unit {
341+
let mut s = start
342+
let mut e = end
343+
if s < e && at_symbol(toks: read toks, i: read s, code: read SYM_LPAREN) {
344+
let close = find_matching(toks: read toks, open: read s, openSym: read SYM_LPAREN, closeSym: read SYM_RPAREN)
345+
if close == e - 1 { s = s + 1; e = close }
346+
}
347+
emit_expr(toks: read toks, start: read s, end: read e, d: read d)
348+
return Unit
349+
}
350+
318351
// "read"/"mut"/"take" if token i is that effect keyword, else "".
319352
fn effect_kw(toks: read List<Tok>, i: read Int) -> String {
320353
if is_kw_text(toks: read toks, i: read i, word: read "read") { return "read" }

selfhost/samples/ast/unary.rss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn checks(a: read Int, done: read Bool) -> Int {
2+
let neg = -5
3+
if !done {
4+
return neg
5+
}
6+
return ~a
7+
}

0 commit comments

Comments
 (0)