plum

#treesitter#compiler#wasm

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

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


be16cd8Peter John 2026-07-24T11:09:03+05:30
feat(plum-core): parse bracket generics, drop ReturnType in favor of Type
Files changed (2) hide show
  1. plum-core/src/ast.rs +2 -8
  2. plum-core/src/parser.rs +29 -36
plum-core/src/ast.rs CHANGED
@@ -57,7 +57,7 @@ pub struct Trait {
57
57
  pub struct TraitMethod {
58
58
  pub name: String,
59
59
  pub params: Vec<Param>,
60
- pub returns: Option<ReturnType>,
60
+ pub returns: Option<Type>,
61
61
  }
62
62
 
63
63
  #[derive(Debug, Clone, PartialEq)]
@@ -80,7 +80,7 @@ pub struct Fn {
80
80
  /// Type parameter for method dispatch, e.g. `<Cat>` in `toStr<Cat>()`
81
81
  pub type_param: Option<String>,
82
82
  pub params: Vec<Param>,
83
- pub returns: Option<ReturnType>,
83
+ pub returns: Option<Type>,
84
84
  pub body: FnBody,
85
85
  }
86
86
 
@@ -112,12 +112,6 @@ pub struct Type {
112
112
  pub generics: Vec<Type>,
113
113
  }
114
114
 
115
- #[derive(Debug, Clone, PartialEq)]
116
- pub struct ReturnType {
117
- pub name: String,
118
- pub generics: Vec<GenericParam>,
119
- }
120
-
121
115
  #[derive(Debug, Clone, PartialEq)]
122
116
  pub enum FnBody {
123
117
  Expr(Expr),
plum-core/src/parser.rs CHANGED
@@ -71,15 +71,18 @@ impl<'a> AstParser<'a> {
71
71
  // ---- class / trait / enum ---------------------------------------------
72
72
 
73
73
  fn parse_class(&self, node: Node) -> Class {
74
- // class: "type" type_identifier ("(" type_identifier,* ")")? generics? "=" body
74
+ // class: "type" type_identifier generics? ("(" type_identifier,* ")")? "=" body
75
- // Named children in order: type_identifier (name), type_identifier* (implements), field*
75
+ // Named children in order: type_identifier (name), generics? (declaration), type_identifier* (implements), field*
76
76
  let mut cursor = node.walk();
77
77
  let named: Vec<Node> = node.named_children(&mut cursor).collect();
78
78
 
79
79
  let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
80
80
 
81
+ // Skip the optional `generics` declaration node before looking for implements.
82
+ let after_generics = if named.get(1).map(|n| n.kind()) == Some("generics") { 2 } else { 1 };
83
+
81
84
  // implements = type_identifiers that appear before any `field` node
82
- let implements: Vec<String> = named[1..]
85
+ let implements: Vec<String> = named[after_generics..]
83
86
  .iter()
84
87
  .take_while(|n| n.kind() == "type_identifier")
85
88
  .map(|n| self.text(*n))
@@ -97,15 +100,14 @@ impl<'a> AstParser<'a> {
97
100
  }
98
101
 
99
102
  fn parse_generics_field(&self, node: Node) -> Vec<GenericParam> {
100
- // generics: "(" generic_type,* ")" where generic_type: generic (":" sep1(type_identifier, "+"))?
103
+ // generics: "[" generic_type,* "]" where generic_type: generic (":" sep1(type_identifier, "+"))?
101
104
  //
102
- // Both `generic_type` and `generic` are `inline`d in the grammar, so the
105
+ // `generic_type` is `inline`d in the grammar, so the `generics` node has NO
103
- // `generics` node has NO `generic_type` children — its named children are
106
+ // `generic_type` children — its named children are the single-uppercase-letter
107
+ // `generic` nodes, each optionally followed by their bound `type_identifier`
104
- // the single-letter generic nodes (`a`/`b`/`c`/`d`) each optionally
108
+ // nodes, all flattened together. Reconstruct each `GenericParam` by starting a
105
- // followed by their bound `type_identifier` nodes, all flattened together.
106
- // Reconstruct each `GenericParam` by starting a new one at every generic
109
+ // new one at every `generic` node and attaching any following
107
- // letter and attaching any following `type_identifier`s as its bounds
108
- // until the next generic letter.
110
+ // `type_identifier`s as its bounds until the next `generic` node.
109
111
  let Some(generics_node) = self.children_of_kind(node, "generics").into_iter().next() else {
110
112
  return Vec::new();
111
113
  };
@@ -113,7 +115,7 @@ impl<'a> AstParser<'a> {
113
115
  let mut params: Vec<GenericParam> = Vec::new();
114
116
  for child in generics_node.named_children(&mut cursor) {
115
117
  match child.kind() {
116
- "a" | "b" | "c" | "d" => {
118
+ "generic" => {
117
119
  params.push(GenericParam { name: self.text(child), bounds: Vec::new() });
118
120
  }
119
121
  "type_identifier" => {
@@ -149,13 +151,10 @@ impl<'a> AstParser<'a> {
149
151
  }
150
152
 
151
153
  fn parse_trait_method(&self, node: Node) -> TraitMethod {
152
- // trait_field (aliased to field): fn_identifier "(" params ")" ("->" return_type)?
154
+ // trait_field (aliased to field): fn_identifier "(" params ")" ("->" type)?
153
155
  let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
154
156
  let params = self.collect_params_from(node);
155
- let returns = node
156
- .named_children(&mut node.walk())
157
- .find(|n| n.kind() == "return_type")
157
+ let returns = node.child_by_field_name("returns").map(|n| self.parse_type(n));
158
- .map(|n| self.parse_return_type(n));
159
158
  TraitMethod { name, params, returns }
160
159
  }
161
160
 
@@ -169,14 +168,13 @@ impl<'a> AstParser<'a> {
169
168
  }
170
169
 
171
170
  fn parse_enum_variant(&self, node: Node) -> EnumVariant {
172
- // enum_field (aliased to field): "|" type_identifier ("(" (type_identifier | generic),* ")")?
171
+ // enum_field (aliased to field): "|" type_identifier ("[" (type_identifier | generic),* "]")?
173
- // named children: type_identifier (name), then each field type inside "()" — a
172
+ // named children: type_identifier (name), then each field type inside "[]" — a
174
- // `type_identifier` (concrete, e.g. `Int`) or an inlined generic letter node
173
+ // `type_identifier` (concrete, e.g. `Int`) or a `generic` node (single uppercase letter).
175
- // (`a`/`b`/`c`/`d`, since `generic` is inlined in the grammar).
176
174
  let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
177
175
  let fields: Vec<String> = (1..node.named_child_count())
178
176
  .filter_map(|i| node.named_child(i as u32))
179
- .filter(|n| matches!(n.kind(), "type_identifier" | "a" | "b" | "c" | "d"))
177
+ .filter(|n| matches!(n.kind(), "type_identifier" | "generic"))
180
178
  .map(|n| self.text(n))
181
179
  .collect();
182
180
  EnumVariant { name, fields }
@@ -205,17 +203,20 @@ impl<'a> AstParser<'a> {
205
203
  .map(|n| self.parse_param(*n))
206
204
  .collect();
207
205
 
208
- let returns = named
209
- .iter()
210
- .find(|n| n.kind() == "return_type")
206
+ let returns = node.child_by_field_name("returns").map(|n| self.parse_type(n));
211
- .map(|n| self.parse_return_type(*n));
212
207
 
213
208
  // body is the last named child — it is either a `body` node (block)
214
- // or an expression node when the body is a single expression.
209
+ // or an expression node when the body is a single expression. The
210
+ // `<Cat>` receiver annotation and the `returns` type both have kind
211
+ // "type" now (return_type no longer exists as a separate node kind),
212
+ // but that's fine: neither can ever be the LAST named child when a
213
+ // body is present, since `body`/the trailing expression always comes
214
+ // after them in the grammar — so this match doesn't need to
215
+ // distinguish the two "type" cases from each other, only from `body`.
215
216
  let body = named.last().and_then(|last| {
216
217
  match last.kind() {
217
218
  // Skip non-body trailing nodes
218
- "fn_identifier" | "type" | "param" | "self" | "return_type" => None,
219
+ "fn_identifier" | "type" | "param" | "self" => None,
219
220
  "body" => Some(FnBody::Block(self.parse_block(*last))),
220
221
  _ => {
221
222
  let unwrapped = self.unwrap_expr_node(*last);
@@ -300,14 +301,6 @@ impl<'a> AstParser<'a> {
300
301
  Type { name, generics }
301
302
  }
302
303
 
303
- fn parse_return_type(&self, node: Node) -> ReturnType {
304
- // return_type: type_identifier generics?
305
- // named_child(0) = type_identifier
306
- let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
307
- let generics = self.parse_generics_field(node);
308
- ReturnType { name, generics }
309
- }
310
-
311
304
  // ---- statements -------------------------------------------------------
312
305
 
313
306
  fn parse_block(&self, node: Node) -> Block {