plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


3d79c9fPeter John 2026-07-23T18:45:42+05:30
feat(plum-core): parse obj.field assignment targets into AssignTarget::Field
Files changed (2) hide show
  1. plum-core/src/ast.rs +9 -1
  2. plum-core/src/parser.rs +27 -5
plum-core/src/ast.rs CHANGED
@@ -152,9 +152,17 @@ pub enum Stmt {
152
152
  Expr(Expr),
153
153
  }
154
154
 
155
+ #[derive(Debug, Clone, PartialEq)]
156
+ pub enum AssignTarget {
157
+ Var(String),
158
+ /// `object.field = value` — `object`'s evaluated type must be a class; `field`
159
+ /// is that class's field name being written.
160
+ Field(Box<Expr>, String),
161
+ }
162
+
155
163
  #[derive(Debug, Clone, PartialEq)]
156
164
  pub struct Assign {
157
- pub targets: Vec<String>,
165
+ pub targets: Vec<AssignTarget>,
158
166
  pub values: Vec<Expr>,
159
167
  }
160
168
 
plum-core/src/parser.rs CHANGED
@@ -349,13 +349,19 @@ impl<'a> AstParser<'a> {
349
349
  }
350
350
 
351
351
  fn parse_assign(&self, node: Node) -> Assign {
352
- // assign: commaSep1(var_identifier) "=" commaSep1(expression)
352
+ // assign: commaSep1(choice(var_identifier, field_target)) "=" commaSep1(expression)
353
- // Named children are all var_identifiers then all expressions.
353
+ // Named children are all targets (var_identifier | field_target) then all
354
- // We split at the first non-var_identifier.
354
+ // expressions. We split at the first child that is neither.
355
355
  let mut cursor = node.walk();
356
356
  let named: Vec<Node> = node.named_children(&mut cursor).collect();
357
+ let split = named
358
+ .iter()
357
- let split = named.iter().position(|n| n.kind() != "var_identifier").unwrap_or(named.len());
359
+ .position(|n| n.kind() != "var_identifier" && n.kind() != "field_target")
360
+ .unwrap_or(named.len());
358
- let targets = named[..split].iter().map(|n| self.text(*n)).collect();
361
+ let targets = named[..split]
362
+ .iter()
363
+ .map(|n| self.parse_assign_target(*n))
364
+ .collect();
359
365
  let values = named[split..]
360
366
  .iter()
361
367
  .map(|n| { let u = self.unwrap_expr_node(*n); self.parse_expression(u) })
@@ -363,6 +369,22 @@ impl<'a> AstParser<'a> {
363
369
  Assign { targets, values }
364
370
  }
365
371
 
372
+ fn parse_assign_target(&self, node: Node) -> AssignTarget {
373
+ match node.kind() {
374
+ "field_target" => {
375
+ // field_target: object: primary_expression "." member: fn_identifier
376
+ let object_node = node.child_by_field_name("object").expect("field_target has an object");
377
+ let member = node
378
+ .child_by_field_name("member")
379
+ .map(|n| self.text(n))
380
+ .unwrap_or_default();
381
+ let object = self.parse_primary_expression(self.unwrap_expr_node(object_node));
382
+ AssignTarget::Field(Box::new(object), member)
383
+ }
384
+ _ => AssignTarget::Var(self.text(node)),
385
+ }
386
+ }
387
+
366
388
  fn parse_for(&self, node: Node) -> For {
367
389
  // for: "for" commaSep1(var_identifier) "in" primary_expression body
368
390
  // Named children: var_identifier+, primary_expression (iter), body