1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00

Language: struct types

Adds composite \`struct\` types with field access:

    struct Vec2 { x: u8, y: u8 }

    var pos: Vec2
    pos.x = 100
    pos.y = pos.x + 5

- Lexer: \`struct\` keyword
- AST: \`StructDecl\` with \`StructField\` list; \`NesType::Struct(name)\`
  for struct-typed variable declarations; \`Expr::FieldAccess\` and
  \`LValue::Field\` for reads/writes
- Parser: top-level \`struct Name { field: type, ... }\` (optional
  trailing comma) and \`ident.field\` syntax in both expression and
  lvalue position
- Analyzer: \`register_struct\` computes contiguous field offsets
  (no padding) and stores them in \`struct_layouts\`. Struct variables
  synthesize a \`VarAllocation\` per field under the name
  \`"struct_var.field"\`, and \`Expr::FieldAccess\` / \`LValue::Field\`
  resolve against those. Unknown struct types and unknown fields
  emit E0201.
- IR lowering + AST codegen: treat struct field access as ordinary
  variable access against the synthetic per-field symbols. No new IR
  ops are needed.

v1 structs only support primitive fields (u8/i8/bool). Nested structs,
u16 fields, and array fields are not yet allowed.

https://claude.ai/code/session_01W6eQFStA66EuMKHUFo2rx3
This commit is contained in:
Claude 2026-04-12 16:18:05 +00:00
parent 9c102cb68e
commit 225d40cea5
No known key found for this signature in database
12 changed files with 464 additions and 3 deletions

View file

@ -117,6 +117,7 @@ impl Parser {
let mut globals = Vec::new();
let mut constants = Vec::new();
let mut enums: Vec<EnumDecl> = Vec::new();
let mut structs: Vec<StructDecl> = Vec::new();
let mut functions = Vec::new();
let mut states = Vec::new();
let mut sprites = Vec::new();
@ -147,6 +148,9 @@ impl Parser {
TokenKind::KwEnum => {
enums.push(self.parse_enum_decl()?);
}
TokenKind::KwStruct => {
structs.push(self.parse_struct_decl()?);
}
TokenKind::KwState => {
states.push(self.parse_state_decl()?);
}
@ -221,6 +225,7 @@ impl Parser {
globals,
constants,
enums,
structs,
functions,
states,
sprites,
@ -232,6 +237,40 @@ impl Parser {
})
}
fn parse_struct_decl(&mut self) -> Result<StructDecl, Diagnostic> {
let start = self.current_span();
self.expect(&TokenKind::KwStruct)?;
let (name, _) = self.expect_ident()?;
self.expect(&TokenKind::LBrace)?;
let mut fields = Vec::new();
while *self.peek() != TokenKind::RBrace && *self.peek() != TokenKind::Eof {
let field_span = self.current_span();
let (field_name, _) = self.expect_ident()?;
self.expect(&TokenKind::Colon)?;
let field_type = self.parse_type()?;
fields.push(StructField {
name: field_name,
field_type,
span: field_span,
});
if *self.peek() == TokenKind::Comma {
self.advance();
} else if *self.peek() != TokenKind::RBrace {
return Err(Diagnostic::error(
ErrorCode::E0201,
"expected ',' or '}' in struct body",
self.current_span(),
));
}
}
self.expect(&TokenKind::RBrace)?;
Ok(StructDecl {
name,
fields,
span: Span::new(start.file_id, start.start, self.current_span().end),
})
}
fn parse_enum_decl(&mut self) -> Result<EnumDecl, Diagnostic> {
let start = self.current_span();
self.expect(&TokenKind::KwEnum)?;
@ -1121,6 +1160,19 @@ impl Parser {
start,
))
}
TokenKind::Dot => {
// Field assignment: name.field = value
self.advance();
let (field, _) = self.expect_ident()?;
let op = self.parse_assign_op()?;
let value = self.parse_expr()?;
Ok(Statement::Assign(
LValue::Field(name, field),
op,
value,
start,
))
}
TokenKind::LParen => {
// Function call
self.advance();
@ -1207,6 +1259,12 @@ impl Parser {
self.advance();
NesType::Bool
}
TokenKind::Ident(name) => {
// User-declared struct types are referenced by name.
// The analyzer validates that the name exists.
self.advance();
NesType::Struct(name)
}
_ => {
return Err(Diagnostic::error(
ErrorCode::E0201,
@ -1464,6 +1522,13 @@ impl Parser {
return Ok(Expr::Call(name, args, span));
}
// Check for field access: `name.field`
if *self.peek() == TokenKind::Dot {
self.advance();
let (field, _) = self.expect_ident()?;
return Ok(Expr::FieldAccess(name, field, span));
}
Ok(Expr::Ident(name, span))
}
TokenKind::LBracket => {