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:
parent
9c102cb68e
commit
225d40cea5
12 changed files with 464 additions and 3 deletions
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue