plum
git clone https://git.pyrossh.dev/plum
A statically typed, imperative programming language inspired by rust, python
plum-core/src/parser.rs
use tree_sitter::Node;
use crate::ast::*;
pub struct AstParser<'a> {
source: &'a [u8],
}
impl<'a> AstParser<'a> {
pub fn new(source: &'a str) -> Self {
AstParser { source: source.as_bytes() }
}
fn text(&self, node: Node) -> String {
node.utf8_text(self.source).unwrap_or("").to_string()
}
/// Peel transparent `expression` / `primary_expression` wrapper nodes.
fn unwrapExprNode<'b>(&self, node: Node<'b>) -> Node<'b> {
match node.kind() {
"expression" | "primary_expression" => {
node.named_child(0).map(|c| self.unwrapExprNode(c)).unwrap_or(node)
}
_ => node,
}
}
/// Collect named children of `node` that have the given `kind`.
fn childrenOfKind(&self, node: Node<'a>, kind: &str) -> Vec<Node<'a>> {
let mut cursor = node.walk();
node.named_children(&mut cursor)
.filter(|n| n.kind() == kind)
.collect()
}
// ---- top level --------------------------------------------------------
pub fn parseSource(&self, node: Node) -> Source {
assert_eq!(node.kind(), "source");
let mut module = None;
let mut imports = Vec::new();
let mut items = Vec::new();
let mut cursor = node.walk();
for child in node.named_children(&mut cursor) {
match child.kind() {
"module" => module = Some(self.parseModule(child)),
"import" => imports.push(self.parseImport(child)),
"class" => {
let c = self.parseClass(child);
let nested = self.collectNestedFns(child, &c.name);
items.push(Item::Class(c));
items.extend(nested.into_iter().map(Item::Fn));
}
"trait" => items.push(Item::Trait(self.parseTrait(child))),
"enum" => {
let e = self.parseEnum(child);
let nested = self.collectNestedFns(child, &e.name);
items.push(Item::Enum(e));
items.extend(nested.into_iter().map(Item::Fn));
}
"fn" => items.push(Item::Fn(self.parseFn(child))),
"const" => items.push(Item::Const(self.parseConst(child))),
_ => {}
}
}
Source { module, imports, items }
}
fn parseModule(&self, node: Node) -> Module {
// module: "module" mod_identifier
// named_child(0) = mod_identifier
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
Module { name }
}
fn parseImport(&self, node: Node) -> Import {
// import: "import" url — url is the only named child
let path = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
Import { path }
}
// ---- class / trait / enum ---------------------------------------------
/// Collects any `fn` named children nested directly inside a class/enum body and
/// parses each as an ordinary top-level `Fn`, with `type_param` forced to `owner`
/// regardless of whatever the nested `fn` itself parsed (a nested method's receiver
/// is implicit from its enclosing declaration; if it also carries its own explicit,
/// redundant `<Receiver>` annotation, that's simply overridden, not treated as a
/// conflict/error).
fn collectNestedFns(&self, node: Node, owner: &str) -> Vec<Fn> {
self.childrenOfKind(node, "fn")
.into_iter()
.map(|n| {
let mut f = self.parseFn(n);
f.type_param = Some(owner.to_string());
f
})
.collect()
}
fn parseClass(&self, node: Node) -> Class {
// class: "type" type_identifier generics? ("(" type_identifier,* ")")? "=" body
// Named children in order: type_identifier (name), generics? (declaration), type_identifier* (implements), field*
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
// Skip the optional `generics` declaration node before looking for implements.
let after_generics = if named.get(1).map(|n| n.kind()) == Some("generics") { 2 } else { 1 };
// implements = type_identifiers that appear before any `field` node
let implements: Vec<String> = named[after_generics..]
.iter()
.take_while(|n| n.kind() == "type_identifier")
.map(|n| self.text(*n))
.collect();
let generics = self.parseGenericsField(node);
let fields: Vec<Field> = named
.iter()
.filter(|n| n.kind() == "field")
.map(|n| self.parseField(*n))
.collect();
Class { name, implements, generics, fields }
}
fn parseGenericsField(&self, node: Node) -> Vec<GenericParam> {
// generics: "[" generic_type,* "]" where generic_type: generic (":" sep1(type_identifier, "+"))?
//
// `generic_type` is `inline`d in the grammar, so the `generics` node has NO
// `generic_type` children — its named children are the single-uppercase-letter
// `generic` nodes, each optionally followed by their bound `type_identifier`
// nodes, all flattened together. Reconstruct each `GenericParam` by starting a
// new one at every `generic` node and attaching any following
// `type_identifier`s as its bounds until the next `generic` node.
let Some(generics_node) = self.childrenOfKind(node, "generics").into_iter().next() else {
return Vec::new();
};
let mut cursor = generics_node.walk();
let mut params: Vec<GenericParam> = Vec::new();
for child in generics_node.named_children(&mut cursor) {
match child.kind() {
"generic" => {
params.push(GenericParam { name: self.text(child), bounds: Vec::new() });
}
"type_identifier" => {
if let Some(last) = params.last_mut() {
last.bounds.push(self.text(child));
}
}
_ => {}
}
}
params
}
fn parseField(&self, node: Node) -> Field {
// class_field (aliased to field): var_identifier ":" type
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let ty = node
.named_child(1)
.map(|n| self.parseType(n))
.unwrap_or(Type { name: String::new(), generics: vec![] });
Field { name, ty }
}
fn parseTrait(&self, node: Node) -> Trait {
// trait: "trait" type_identifier generics? "=" body
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let generics = self.parseGenericsField(node);
let methods = self.childrenOfKind(node, "field")
.into_iter()
.map(|f| self.parseTraitMethod(f))
.collect();
Trait { name, generics, methods }
}
fn parseTraitMethod(&self, node: Node) -> TraitMethod {
// trait_field (aliased to field): fn_identifier "(" params ")" ("->" type)?
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let params = self.collectParamsFrom(node);
// As with `parseFn`'s `returns` field, the grammar's `returns` field
// wraps the whole `optional(seq("->", $.type))`, so
// `child_by_field_name("returns")` resolves to the anonymous "->"
// token, not the `type` node. Unlike `parseFn`, a trait method has
// no receiver annotation, so there's at most one `type`-kind named
// child here, and it's unambiguously the return type when present.
let returns = node
.named_children(&mut node.walk())
.find(|n| n.kind() == "type")
.map(|n| self.parseType(n));
TraitMethod { name, params, returns }
}
fn parseEnum(&self, node: Node) -> Enum {
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let params = self.childrenOfKind(node, "enum_param")
.into_iter()
.map(|n| self.parseEnumParam(n))
.collect();
let variants = self.childrenOfKind(node, "field")
.into_iter()
.map(|f| self.parseEnumVariant(f))
.collect();
Enum { name, params, variants }
}
fn parseEnumParam(&self, node: Node) -> EnumParam {
// enum_param: var_identifier ":" type
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let ty = node
.named_child(1)
.map(|n| self.parseType(n))
.unwrap_or(Type { name: String::new(), generics: vec![] });
EnumParam { name, ty }
}
fn parseEnumVariant(&self, node: Node) -> EnumVariant {
// enum_field (aliased to field): "|" type_identifier
// ("[" (type_identifier | generic),* "]")? -- existing: generic type payload
// | ("(" expression,* ")")? -- new: discriminant value literals
// named children after the name: either type_identifier/generic (fields) or
// expression (values) — the two are disjoint child-kind sets, never mixed.
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let rest: Vec<Node> = (1..node.named_child_count())
.filter_map(|i| node.named_child(i as u32))
.collect();
let fields: Vec<String> = rest.iter()
.filter(|n| matches!(n.kind(), "type_identifier" | "generic"))
.map(|n| self.text(*n))
.collect();
let values: Vec<Expr> = rest.iter()
.filter(|n| n.kind() == "expression")
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.collect();
EnumVariant { name, fields, values }
}
// ---- functions --------------------------------------------------------
fn parseFn(&self, node: Node) -> Fn {
// fn: "fun" fn_identifier "(" param,* ")" ("->" type)? "=" body_or_expr
// Named children: fn_identifier, param*, type?, body/expr
//
// `type_param` (the method's receiver, e.g. `Cat` in a method nested inside
// `type Cat = ...`) is never set here — a bare `fn` node has no receiver of
// its own; `parseSource`'s `collectNestedFns` forces it afterward for any
// `fn` nested inside a `class`/`enum` body. There is no top-level
// `<Receiver>` annotation syntax to parse.
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let name = named.first().map(|n| self.text(*n)).unwrap_or_default();
let params: Vec<Param> = named
.iter()
.filter(|n| n.kind() == "param")
.map(|n| self.parseParam(*n))
.collect();
// The grammar's `returns` field wraps the whole `optional(seq("->", $.type))`,
// so `child_by_field_name("returns")` resolves to the anonymous `"->"` token,
// not the `type` node — find the `type`-kind named child instead.
let returns = named
.iter()
.find(|n| n.kind() == "type")
.map(|n| self.parseType(*n));
// body is the last named child — it is either a `body` node (block)
// or an expression node when the body is a single expression. Genuinely
// absent (`extern fun foo(...)` with no `=`) parses as `FnBody::Extern`;
// whether that's actually valid here is `plum-checker`'s job, not the
// parser's — it must agree with `is_extern` below.
let body = named.last().and_then(|last| {
match last.kind() {
// Skip non-body trailing nodes
"fn_identifier" | "type" | "param" | "self" => None,
"body" => Some(FnBody::Block(self.parseBlock(*last))),
_ => {
let unwrapped = self.unwrapExprNode(*last);
Some(FnBody::Expr(self.parseExpression(unwrapped)))
}
}
}).unwrap_or(FnBody::Extern);
let is_extern = node.child_by_field_name("externKw").is_some();
Fn { name, type_param: None, is_extern, params, returns, body }
}
fn parseConst(&self, node: Node) -> Const {
// const: const_identifier "=" expression
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let value = node
.named_child(1)
.map(|n| {
let unwrapped = self.unwrapExprNode(n);
self.parseExpression(unwrapped)
})
.unwrap_or(Expr::Int(0));
Const { name, value }
}
// ---- params / return type ---------------------------------------------
/// Collect `param` named children from any node that has them.
fn collectParamsFrom(&self, node: Node) -> Vec<Param> {
self.childrenOfKind(node, "param")
.into_iter()
.map(|n| self.parseParam(n))
.collect()
}
fn parseParam(&self, node: Node) -> Param {
// param: var_identifier ":" (type | variadic_type | fn_value_type) ("=" expression)?
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let ty = node.named_child(1).map(|n| match n.kind() {
"variadic_type" => {
let inner = n.named_child(0)
.map(|t| self.parseType(t))
.unwrap_or(Type { name: String::new(), generics: vec![] });
ParamType::Variadic(inner)
}
"fn_value_type" => self.parseFnValueType(n),
_ => ParamType::Type(self.parseType(n)),
}).unwrap_or(ParamType::Type(Type { name: String::new(), generics: vec![] }));
let default = node.named_child(2).map(|n| {
let unwrapped = self.unwrapExprNode(n);
self.parseExpression(unwrapped)
});
Param { name, ty, default }
}
fn parseFnValueType(&self, node: Node) -> ParamType {
// fn_value_type: "fn" "(" field("params", type,*) ")" ("->" field("returns", type))?
// The "returns" field (if present) is a distinct field from "params", so the
// two are disambiguated unambiguously by field name, not by counting/position
// among same-kind "type" children — the same idiom `fn`'s own `returns` field
// already uses.
let returns_node = node.child_by_field_name("returns");
let param_types: Vec<Type> = self.childrenOfKind(node, "type")
.into_iter()
.filter(|n| Some(*n) != returns_node)
.map(|n| self.parseType(n))
.collect();
let ret = returns_node.map(|n| Box::new(self.parseType(n)));
ParamType::Fn(param_types, ret)
}
fn parseType(&self, node: Node) -> Type {
// type: type_identifier ("[" type,* "]")? | generic | "[" "]" element:type
// A slice type (e.g. `[]Byte`) is flattened to a single reserved name
// `"[]" + element_name` rather than a real generics list — the checker
// treats it as a fixed builtin (only `[]Byte` is accepted), not a
// monomorphized generic, so there's no template to carry args for.
if let Some(element) = node.child_by_field_name("element") {
let elem = self.parseType(element);
return Type { name: format!("[]{}", elem.name), generics: vec![] };
}
// named_child(0) = type_identifier
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_else(|| self.text(node));
let mut cursor = node.walk();
let generics: Vec<Type> = node
.named_children(&mut cursor)
.skip(1)
.filter(|n| n.kind() == "type")
.map(|n| self.parseType(n))
.collect();
Type { name, generics }
}
// ---- statements -------------------------------------------------------
fn parseBlock(&self, node: Node) -> Block {
let mut cursor = node.walk();
let stmts = node
.named_children(&mut cursor)
.filter_map(|n| self.parseStmt(n))
.collect();
Block { stmts }
}
fn parseStmt(&self, node: Node) -> Option<Stmt> {
let node = self.unwrapExprNode(node);
Some(match node.kind() {
"assign" => Stmt::Assign(self.parseAssign(node)),
"break" => Stmt::Break,
"continue" => Stmt::Continue,
"return" => {
let expr = node.named_child(0).map(|n| {
let u = self.unwrapExprNode(n);
self.parseExpression(u)
});
Stmt::Return(expr)
}
"todo" => Stmt::Todo,
"assert" => {
let expr = node.named_child(0)
.map(|n| { let u = self.unwrapExprNode(n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
Stmt::Assert(expr)
}
"for" => Stmt::For(self.parseFor(node)),
"while" => Stmt::While(self.parseWhile(node)),
"if" => Stmt::If(self.parseIf(node)),
"match" => Stmt::Match(self.parseMatch(node)),
kind if isExpressionKind(kind) => Stmt::Expr(self.parseExpression(node)),
_ => return None,
})
}
fn parseAssign(&self, node: Node) -> Assign {
// assign: commaSep1(choice(var_identifier, field_target)) "=" commaSep1(expression)
// Named children are all targets (var_identifier | field_target) then all
// expressions. We split at the first child that is neither.
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let split = named
.iter()
.position(|n| n.kind() != "var_identifier" && n.kind() != "field_target")
.unwrap_or(named.len());
let targets = named[..split]
.iter()
.map(|n| self.parseAssignTarget(*n))
.collect();
let values = named[split..]
.iter()
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.collect();
let declare = node.child_by_field_name("op").map(|n| self.text(n) == ":=").unwrap_or(false);
Assign { targets, values, declare }
}
fn parseAssignTarget(&self, node: Node) -> AssignTarget {
match node.kind() {
"field_target" => {
// field_target: object: primary_expression "." member: fn_identifier
let object_node = node.child_by_field_name("object").expect("field_target has an object");
let member = node
.child_by_field_name("member")
.map(|n| self.text(n))
.unwrap_or_default();
let object = self.parsePrimaryExpression(self.unwrapExprNode(object_node));
AssignTarget::Field(Box::new(object), member)
}
_ => AssignTarget::Var(self.text(node)),
}
}
fn parseFor(&self, node: Node) -> For {
// for: "for" commaSep1(var_identifier) "in" primary_expression body
// Named children: var_identifier+, primary_expression (iter), body
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let split = named.iter().position(|n| n.kind() != "var_identifier").unwrap_or(0);
let vars = named[..split].iter().map(|n| self.text(*n)).collect();
let iter = named.get(split)
.map(|n| { let u = self.unwrapExprNode(*n); self.parsePrimaryExpression(u) })
.unwrap_or(Expr::Int(0));
let body = named.last()
.filter(|n| n.kind() == "body")
.map(|n| self.parseBlock(*n))
.unwrap_or(Block { stmts: vec![] });
For { vars, iter, body }
}
fn parseWhile(&self, node: Node) -> While {
// while: "while" expression body
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let condition = named.first()
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
let body = named.last()
.filter(|n| n.kind() == "body")
.map(|n| self.parseBlock(*n))
.unwrap_or(Block { stmts: vec![] });
While { condition, body }
}
fn parseIf(&self, node: Node) -> If {
// if: "if" expression body else_if* else?
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let condition = named.first()
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
let body = named.get(1)
.filter(|n| n.kind() == "body")
.map(|n| self.parseBlock(*n))
.unwrap_or(Block { stmts: vec![] });
let else_ifs = named.iter()
.filter(|n| n.kind() == "else_if")
.map(|n| self.parseElseIf(*n))
.collect();
let else_ = named.iter()
.find(|n| n.kind() == "else")
.and_then(|n| n.named_child(0))
.map(|n| self.parseBlock(n));
If { condition, body, else_ifs, else_ }
}
fn parseElseIf(&self, node: Node) -> ElseIf {
// else_if: "else if" expression body
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let condition = named.first()
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
let body = named.last()
.filter(|n| n.kind() == "body")
.map(|n| self.parseBlock(*n))
.unwrap_or(Block { stmts: vec![] });
ElseIf { condition, body }
}
fn parseMatch(&self, node: Node) -> Match {
// match: "match" commaSep1(expression) "is" case+
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let split = named.iter().position(|n| n.kind() == "case").unwrap_or(named.len());
let subjects = named[..split]
.iter()
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.collect();
let cases = named[split..]
.iter()
.filter(|n| n.kind() == "case")
.map(|n| self.parseCase(*n))
.collect();
Match { subjects, cases }
}
fn parseCase(&self, node: Node) -> Case {
// case: commaSep1(case_pattern) "=>" (expression | body)
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let patterns = named.iter()
.filter(|n| n.kind() == "case_pattern")
.map(|n| self.parseCasePattern(*n))
.collect();
let body = named.iter()
.find(|n| n.kind() != "case_pattern")
.map(|n| {
if n.kind() == "body" {
self.parseBlock(*n)
} else {
let unwrapped = self.unwrapExprNode(*n);
Block { stmts: vec![Stmt::Expr(self.parseExpression(unwrapped))] }
}
})
.unwrap_or(Block { stmts: vec![] });
Case { patterns, body }
}
fn parseCasePattern(&self, node: Node) -> CasePattern {
// case_pattern wraps: class_pattern | string | integer | float | dotted_name | "_"
let inner = node.named_child(0).unwrap_or(node);
match inner.kind() {
"class_pattern" => {
// class_pattern: dotted_name "(" case_pattern,* ")"
let name = inner.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let fields = (1..inner.named_child_count())
.filter_map(|i| inner.named_child(i as u32))
.filter(|n| n.kind() == "case_pattern")
.map(|n| self.parseCasePattern(n))
.collect();
CasePattern::Class { name, fields }
}
"string" => CasePattern::String(self.parseStringRaw(inner)),
"integer" => CasePattern::Int(self.parseInteger(inner)),
"float" => CasePattern::Float(self.parseFloat(inner)),
"dotted_name" => CasePattern::Name(self.text(inner)),
_ => {
let t = self.text(inner);
if t == "_" { CasePattern::Wildcard } else { CasePattern::Name(t) }
}
}
}
// ---- expressions ------------------------------------------------------
pub fn parseExpression(&self, node: Node) -> Expr {
let node = self.unwrapExprNode(node);
match node.kind() {
"comparison_operator" => self.parseCompare(node),
"not_operator" => {
let arg = node.named_child(0)
.map(|n| { let u = self.unwrapExprNode(n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
Expr::Not(Box::new(arg))
}
"boolean_operator" => self.parseBoolOp(node),
"ternary_expression" => self.parseTernary(node),
"closure" => Expr::Closure(Box::new(self.parseClosure(node))),
_ => self.parsePrimaryExpression(node),
}
}
fn parseClosure(&self, node: Node) -> Closure {
// closure: "|" var_identifier,* "|" (expression | body)
let params: Vec<String> = self.childrenOfKind(node, "var_identifier")
.into_iter()
.map(|n| self.text(n))
.collect();
// The body is either an indented `body` block or a single inline expression
// (`|v| v`); normalize the inline form into a one-statement block so codegen and
// the checker only ever see a `Block`.
let body = match self.childrenOfKind(node, "body").into_iter().next() {
Some(block_node) => self.parseBlock(block_node),
None => match node.child_by_field_name("body") {
Some(expr_node) => {
let unwrapped = self.unwrapExprNode(expr_node);
Block { stmts: vec![Stmt::Expr(self.parseExpression(unwrapped))] }
}
None => Block { stmts: vec![] },
},
};
Closure { params, body }
}
pub fn parsePrimaryExpression(&self, node: Node) -> Expr {
let node = self.unwrapExprNode(node);
match node.kind() {
"binary_operator" => self.parseBinary(node),
"unary_operator" => self.parseUnary(node),
"attribute" => self.parseAttribute(node),
"fn_call" => Expr::FnCall(self.parseFnCall(node)),
"class_call" => Expr::ClassCall(self.parseClassCall(node)),
"parenthesized_expression" => {
// parenthesized_expression: "{" expression "}"
let inner = node.named_child(0)
.map(|n| { let u = self.unwrapExprNode(n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
Expr::Paren(Box::new(inner))
}
"string" => Expr::String(self.parseString(node)),
"integer" => Expr::Int(self.parseInteger(node)),
"float" => Expr::Float(self.parseFloat(node)),
"self" => Expr::Self_,
"var_identifier" => Expr::Var(self.text(node)),
"type_identifier" => Expr::TypeName(self.text(node)),
// A SCREAMING_CASE const reference (e.g. `MAX_FLOAT_VALUE`) — reuses the
// `TypeName` path, which already resolves a matching top-level const's
// real type/value (see plum-checker's `inferExpr` and
// plum-wasm-codegen's `CURRENT_CONSTS`).
"const_identifier" => Expr::TypeName(self.text(node)),
_ => Expr::Var(self.text(node)),
}
}
fn parseBinary(&self, node: Node) -> Expr {
// binary_operator: primary_expression op primary_expression
// "operator" is an unnamed child; left/right are field-named
let op_text = self.findUnnamedOperator(node);
let op = match op_text.as_str() {
"+" => BinOp::Add,
"-" => BinOp::Sub,
"*" => BinOp::Mul,
"/" => BinOp::Div,
"%" => BinOp::Mod,
"|" => BinOp::BitOr,
"&" => BinOp::BitAnd,
"^" => BinOp::Xor,
"<<" => BinOp::Shl,
">>" => BinOp::Shr,
_ => BinOp::Add,
};
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let left = named.first()
.map(|n| { let u = self.unwrapExprNode(*n); self.parsePrimaryExpression(u) })
.unwrap_or(Expr::Int(0));
let right = named.last()
.map(|n| { let u = self.unwrapExprNode(*n); self.parsePrimaryExpression(u) })
.unwrap_or(Expr::Int(0));
Expr::Binary(Box::new(BinaryExpr { op, left, right }))
}
fn parseUnary(&self, node: Node) -> Expr {
let op_text = self.findUnnamedOperator(node);
let op = if op_text == "-" { UnOp::Neg } else { UnOp::Pos };
let operand = node.named_child(0)
.map(|n| { let u = self.unwrapExprNode(n); self.parsePrimaryExpression(u) })
.unwrap_or(Expr::Int(0));
Expr::Unary(Box::new(UnaryExpr { op, operand }))
}
fn parseBoolOp(&self, node: Node) -> Expr {
let op_text = self.findUnnamedOperator(node);
let op = if op_text == "&&" { BoolOp::And } else { BoolOp::Or };
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let left = named.first()
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
let right = named.last()
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
Expr::Bool(Box::new(BoolExpr { op, left, right }))
}
fn parseCompare(&self, node: Node) -> Expr {
let op_text = self.findUnnamedOperator(node);
let op = match op_text.as_str() {
"<" => CmpOp::Lt,
"<=" => CmpOp::Lte,
"==" => CmpOp::Eq,
"!=" => CmpOp::Neq,
">=" => CmpOp::Gte,
">" => CmpOp::Gt,
"<>" => CmpOp::NotEq2,
_ => CmpOp::Eq,
};
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let left = named.first()
.map(|n| { let u = self.unwrapExprNode(*n); self.parsePrimaryExpression(u) })
.unwrap_or(Expr::Int(0));
let right = named.last()
.map(|n| { let u = self.unwrapExprNode(*n); self.parsePrimaryExpression(u) })
.unwrap_or(Expr::Int(0));
Expr::Compare(Box::new(CompareExpr { op, left, right }))
}
fn parseTernary(&self, node: Node) -> Expr {
// ternary_expression: expression "?" expression ":" expression
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let condition = named.first()
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
let then = named.get(1)
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
let else_ = named.get(2)
.map(|n| { let u = self.unwrapExprNode(*n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
Expr::Ternary(Box::new(TernaryExpr { condition, then, else_ }))
}
fn parseAttribute(&self, node: Node) -> Expr {
// attribute: primary_expression "." fn_identifier fn_argument_list?
// The member name is always fn_identifier (a superset of var_identifier); an
// optional trailing argument list distinguishes a method call from field access.
let mut cursor = node.walk();
let named: Vec<Node> = node.named_children(&mut cursor).collect();
let object = named.first()
.map(|n| { let u = self.unwrapExprNode(*n); self.parsePrimaryExpression(u) })
.unwrap_or(Expr::Int(0));
let member = named.get(1).map(|n| self.text(*n)).unwrap_or_default();
let attr = match named.get(2) {
Some(args_node) => {
let mut acursor = args_node.walk();
let args = args_node.named_children(&mut acursor)
.map(|n| self.parseArg(n))
.collect();
AttrKind::Method(FnCall { name: member, args })
}
None => AttrKind::Field(member),
};
Expr::Attribute(Box::new(AttributeExpr { object, attr }))
}
fn parseFnCall(&self, node: Node) -> FnCall {
// fn_call: var_identifier fn_argument_list (the callee lexes as var_identifier
// to avoid an identifier-token tie with all-lowercase, no-underscore names)
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let args = node.named_child(1)
.map(|args_node| {
let mut cursor = args_node.walk();
args_node.named_children(&mut cursor)
.map(|n| self.parseArg(n))
.collect()
})
.unwrap_or_default();
FnCall { name, args }
}
fn parseArg(&self, node: Node) -> Arg {
match node.kind() {
"keyword_argument" => {
// keyword_argument: var_identifier "=" expression
let name = node.named_child(0).map(|n| self.text(n)).unwrap_or_default();
let value = node.named_child(1)
.map(|n| { let u = self.unwrapExprNode(n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
Arg::Keyword { name, value }
}
"pair_argument" => {
// pair_argument: string "=>" expression
let key = node.named_child(0).map(|n| self.parseStringRaw(n)).unwrap_or_default();
let value = node.named_child(1)
.map(|n| { let u = self.unwrapExprNode(n); self.parseExpression(u) })
.unwrap_or(Expr::Int(0));
Arg::Pair { key, value }
}
_ => {
let u = self.unwrapExprNode(node);
Arg::Positional(self.parseExpression(u))
}
}
}
fn parseClassCall(&self, node: Node) -> ClassCall {
// class_call: type_identifier ("[" type,* "]")? class_argument_list
let type_name = node.child_by_field_name("type").map(|n| self.text(n)).unwrap_or_default();
// Filtering by NODE KIND (rather than the "generics" field, which
// tree-sitter attaches once per repeated element, not as a single
// group) — mirrors `parseType`'s own handling of a type's nested
// generics list, and sidesteps the optional bracket entirely: if it
// wasn't written, there are simply no "type" children to find.
let generics: Vec<Type> = {
let mut cursor = node.walk();
node.named_children(&mut cursor)
.filter(|n| n.kind() == "type")
.map(|n| self.parseType(n))
.collect()
};
let fields = node.child_by_field_name("arguments")
.map(|args_node| {
// class_argument_list: "(" (var_identifier ":" expression),* ")"
// Named children alternate: var_identifier, expression, ...
let mut cursor = args_node.walk();
let named: Vec<Node> = args_node.named_children(&mut cursor).collect();
named.chunks(2).filter_map(|chunk| {
if chunk.len() == 2 {
let name = self.text(chunk[0]);
let u = self.unwrapExprNode(chunk[1]);
Some(FieldArg { name, value: self.parseExpression(u) })
} else {
None
}
}).collect()
})
.unwrap_or_default();
ClassCall { type_name, fields, generics }
}
// ---- string literals --------------------------------------------------
fn parseString(&self, node: Node) -> StringExpr {
let mut cursor = node.walk();
let parts = node
.named_children(&mut cursor)
.filter_map(|n| match n.kind() {
"string_content" => Some(StringPart::Text(decodeEscapes(&self.text(n)))),
"interpolation" => {
n.named_child(0).map(|e| {
let u = self.unwrapExprNode(e);
StringPart::Interp(self.parsePrimaryExpression(u))
})
}
_ => None,
})
.collect();
StringExpr { parts }
}
fn parseStringRaw(&self, node: Node) -> String {
let full = self.text(node);
full.trim_matches('"').to_string()
}
// ---- numeric literals -------------------------------------------------
fn parseInteger(&self, node: Node) -> i64 {
let s = self.text(node).replace('_', "");
if s.starts_with("0x") || s.starts_with("0X") {
i64::from_str_radix(&s[2..], 16).unwrap_or(0)
} else if s.starts_with("0b") || s.starts_with("0B") {
i64::from_str_radix(&s[2..], 2).unwrap_or(0)
} else {
s.parse().unwrap_or(0)
}
}
fn parseFloat(&self, node: Node) -> f64 {
let s = self.text(node).trim_end_matches(['f', 'F']).replace('_', "");
s.parse().unwrap_or(0.0)
}
// ---- helpers ----------------------------------------------------------
/// Find the text of the first unnamed (punctuation/operator) non-whitespace child.
fn findUnnamedOperator(&self, node: Node) -> String {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if !child.is_named() {
let t = self.text(child);
if !t.trim().is_empty() {
return t;
}
}
}
String::new()
}
}
/// Decodes a string literal's raw source text (the grammar's `escape_sequence`
/// is matched at the lexer level but never actually interpreted anywhere — the
/// parser just handed back the literal source bytes, backslashes and all)
/// into its real content: `\n`/`\t`/`\\`/`\"`/etc single-char escapes, `\NNN`
/// (1-3 decimal digits), `\xXX`, `\uXXXX`, `\UXXXXXXXX`. An unrecognized escape
/// (including `\N{...}`) is passed through unchanged rather than erroring —
/// this only ever runs on text the grammar already accepted as a valid
/// `escape_sequence`, so "unrecognized" only means "not decoded yet."
fn decodeEscapes(s: &str) -> String {
let chars: Vec<char> = s.chars().collect();
let mut out = String::with_capacity(chars.len());
let mut i = 0;
while i < chars.len() {
if chars[i] != '\\' || i + 1 >= chars.len() {
out.push(chars[i]);
i += 1;
continue;
}
let next = chars[i + 1];
match next {
'n' => { out.push('\n'); i += 2; }
't' => { out.push('\t'); i += 2; }
'r' => { out.push('\r'); i += 2; }
'a' => { out.push('\u{07}'); i += 2; }
'b' => { out.push('\u{08}'); i += 2; }
'f' => { out.push('\u{0C}'); i += 2; }
'v' => { out.push('\u{0B}'); i += 2; }
'\\' => { out.push('\\'); i += 2; }
'\'' => { out.push('\''); i += 2; }
'"' => { out.push('"'); i += 2; }
'\n' => { i += 2; } // escaped literal newline: line continuation, emits nothing
'x' => match decodeHexEscape(&chars, i + 2, 2) {
Some((ch, consumed)) => { out.push(ch); i += 2 + consumed; }
None => { out.push(chars[i]); i += 1; }
},
'u' => match decodeHexEscape(&chars, i + 2, 4) {
Some((ch, consumed)) => { out.push(ch); i += 2 + consumed; }
None => { out.push(chars[i]); i += 1; }
},
'U' => match decodeHexEscape(&chars, i + 2, 8) {
Some((ch, consumed)) => { out.push(ch); i += 2 + consumed; }
None => { out.push(chars[i]); i += 1; }
},
d if d.is_ascii_digit() => {
let mut j = i + 1;
while j < chars.len() && j < i + 4 && chars[j].is_ascii_digit() {
j += 1;
}
let digits: String = chars[i + 1..j].iter().collect();
match digits.parse::<u32>().ok().and_then(char::from_u32) {
Some(ch) => { out.push(ch); i = j; }
None => { out.push(chars[i]); i += 1; }
}
}
_ => { out.push(chars[i]); i += 1; } // e.g. `\N{...}` — pass through raw
}
}
out
}
/// Decodes exactly `width` hex digits starting at `start` into a `char`, if
/// `start..start+width` are all present and form a valid codepoint. Returns
/// `(decoded_char, width)` on success so the caller advances past all of them.
fn decodeHexEscape(chars: &[char], start: usize, width: usize) -> Option<(char, usize)> {
if start + width > chars.len() {
return None;
}
let hex: String = chars[start..start + width].iter().collect();
u32::from_str_radix(&hex, 16).ok().and_then(char::from_u32).map(|ch| (ch, width))
}
fn isExpressionKind(kind: &str) -> bool {
matches!(
kind,
"binary_operator"
| "unary_operator"
| "boolean_operator"
| "not_operator"
| "comparison_operator"
| "ternary_expression"
| "attribute"
| "fn_call"
| "class_call"
| "parenthesized_expression"
| "string"
| "integer"
| "float"
| "self"
| "var_identifier"
| "type_identifier"
)
}