feat: add record spread syntax { ...base, field: val }

Adds spread operator for records, allowing concise record updates:
  let p2 = { ...p, x: 5.0 }

Changes across the full pipeline:
- Lexer: new DotDotDot (...) token
- AST: optional spread field on Record variant
- Parser: detect ... at start of record expression
- Typechecker: merge spread record fields with explicit overrides
- Interpreter: evaluate spread, overlay explicit fields
- JS backend: emit native JS spread syntax
- C backend: copy spread into temp, assign overrides
- Formatter, linter, LSP, symbol table: propagate spread

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-18 23:05:27 -05:00
parent 7c3bfa9301
commit 3d706cb32b
8 changed files with 116 additions and 20 deletions

View File

@@ -90,6 +90,7 @@ pub enum TokenKind {
Arrow, // =>
ThinArrow, // ->
Dot, // .
DotDotDot, // ...
Colon, // :
ColonColon, // ::
Comma, // ,
@@ -181,6 +182,7 @@ impl fmt::Display for TokenKind {
TokenKind::Arrow => write!(f, "=>"),
TokenKind::ThinArrow => write!(f, "->"),
TokenKind::Dot => write!(f, "."),
TokenKind::DotDotDot => write!(f, "..."),
TokenKind::Colon => write!(f, ":"),
TokenKind::ColonColon => write!(f, "::"),
TokenKind::Comma => write!(f, ","),
@@ -373,7 +375,22 @@ impl<'a> Lexer<'a> {
TokenKind::Pipe
}
}
'.' => TokenKind::Dot,
'.' => {
if self.peek() == Some('.') {
// Check for ... (need to peek past second dot)
// We look at source directly since we can only peek one ahead
let next_next = self.source[self.pos..].chars().nth(1);
if next_next == Some('.') {
self.advance(); // consume second '.'
self.advance(); // consume third '.'
TokenKind::DotDotDot
} else {
TokenKind::Dot
}
} else {
TokenKind::Dot
}
}
':' => {
if self.peek() == Some(':') {
self.advance();