plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
be16cd8
— Peter John
2026-07-24T11:09:03+05:30
feat(plum-core): parse bracket generics, drop ReturnType in favor of Type
- plum-core/src/ast.rs +2 -8
- 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<
|
|
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<
|
|
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,* ")")?
|
|
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[
|
|
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: "
|
|
103
|
+
// generics: "[" generic_type,* "]" where generic_type: generic (":" sep1(type_identifier, "+"))?
|
|
101
104
|
//
|
|
102
|
-
//
|
|
105
|
+
// `generic_type` is `inline`d in the grammar, so the `generics` node has NO
|
|
103
|
-
// `
|
|
106
|
+
// `generic_type` children — its named children are the single-uppercase-letter
|
|
107
|
+
// `generic` nodes, each optionally followed by their bound `type_identifier`
|
|
104
|
-
//
|
|
108
|
+
// nodes, all flattened together. Reconstruct each `GenericParam` by starting a
|
|
105
|
-
// followed by their bound `type_identifier` nodes, all flattened together.
|
|
106
|
-
//
|
|
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
|
|
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
|
-
"
|
|
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 ")" ("->"
|
|
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
|
-
|
|
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 ("
|
|
171
|
+
// enum_field (aliased to field): "|" type_identifier ("[" (type_identifier | generic),* "]")?
|
|
173
|
-
// named children: type_identifier (name), then each field type inside "
|
|
172
|
+
// named children: type_identifier (name), then each field type inside "[]" — a
|
|
174
|
-
// `type_identifier` (concrete, e.g. `Int`) or
|
|
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" | "
|
|
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
|
-
|
|
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"
|
|
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 {
|