1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-18 06:36:57 +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

@ -393,6 +393,38 @@ impl LoweringContext {
self.emit(IrOp::ArrayStore(var_id, idx, result));
}
}
LValue::Field(name, field) => {
// The analyzer synthesizes a variable named
// `"struct.field"` for each struct field, so we can
// treat field assignment as a regular variable
// assignment to that synthetic name.
let full_name = format!("{name}.{field}");
let var_id = self.get_or_create_var(&full_name);
match op {
AssignOp::Assign => {
let val = self.lower_expr(expr);
self.emit(IrOp::StoreVar(var_id, val));
}
_ => {
let current = self.fresh_temp();
self.emit(IrOp::LoadVar(current, var_id));
let rhs = self.lower_expr(expr);
let result = self.fresh_temp();
let ir_op = match op {
AssignOp::PlusAssign => IrOp::Add(result, current, rhs),
AssignOp::MinusAssign => IrOp::Sub(result, current, rhs),
AssignOp::AmpAssign => IrOp::And(result, current, rhs),
AssignOp::PipeAssign => IrOp::Or(result, current, rhs),
AssignOp::CaretAssign => IrOp::Xor(result, current, rhs),
AssignOp::ShiftLeftAssign => IrOp::ShiftLeft(result, current, 1),
AssignOp::ShiftRightAssign => IrOp::ShiftRight(result, current, 1),
AssignOp::Assign => unreachable!(),
};
self.emit(ir_op);
self.emit(IrOp::StoreVar(var_id, result));
}
}
}
}
}
@ -537,6 +569,16 @@ impl LoweringContext {
self.emit(IrOp::ArrayLoad(t, var_id, idx));
t
}
Expr::FieldAccess(name, field, _) => {
// Field access lowers to a plain load of the
// synthetic `"struct.field"` variable produced by the
// analyzer.
let full_name = format!("{name}.{field}");
let var_id = self.get_or_create_var(&full_name);
let t = self.fresh_temp();
self.emit(IrOp::LoadVar(t, var_id));
t
}
Expr::BinaryOp(left, op, right, _) => self.lower_binop(left, *op, right),
Expr::UnaryOp(op, inner, _) => {
let val = self.lower_expr(inner);
@ -697,6 +739,10 @@ fn type_size(t: &NesType) -> u16 {
NesType::U8 | NesType::I8 | NesType::Bool => 1,
NesType::U16 => 2,
NesType::Array(elem, count) => type_size(elem) * count,
// Struct sizes are resolved in the analyzer. IR lowering only
// sees struct types on `var` declarations, which are skipped
// below via the analyzer's synthetic field allocations.
NesType::Struct(_) => 0,
}
}