feat: engram-lang — new programming language, quantum-sealed prod target, spreading activation types
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
//! Abstract syntax tree node types.
|
||||
|
||||
use el_lexer::Span;
|
||||
|
||||
// ── Literals ──────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Literal {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Str(String),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
// ── Type expressions ──────────────────────────────────────────────────────────
|
||||
|
||||
/// A type annotation in source code, e.g. `String`, `[Int]`, `User?`.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TypeExpr {
|
||||
/// A named type: `Int`, `String`, `User`, …
|
||||
Named(String),
|
||||
/// An array type: `[T]`
|
||||
Array(Box<TypeExpr>),
|
||||
/// An optional type: `T?`
|
||||
Optional(Box<TypeExpr>),
|
||||
/// A function type: `fn(A, B) -> C`
|
||||
Fn { params: Vec<TypeExpr>, return_type: Box<TypeExpr> },
|
||||
}
|
||||
|
||||
// ── Patterns (for match arms) ─────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Pattern {
|
||||
/// `Status::Active`
|
||||
EnumVariant { enum_name: String, variant: String, payload: Option<String> },
|
||||
/// A wildcard `_`
|
||||
Wildcard,
|
||||
/// A literal: `42`, `"str"`, `true`
|
||||
Literal(Literal),
|
||||
/// A binding: `x`
|
||||
Binding(String),
|
||||
}
|
||||
|
||||
// ── Binary operators ──────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum BinOp {
|
||||
Add, Sub, Mul, Div,
|
||||
Eq, NotEq, Lt, Gt, LtEq, GtEq,
|
||||
And, Or,
|
||||
}
|
||||
|
||||
// ── Expressions ───────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Expr {
|
||||
Literal(Literal),
|
||||
Ident(String),
|
||||
BinOp { op: BinOp, left: Box<Expr>, right: Box<Expr> },
|
||||
UnaryNot(Box<Expr>),
|
||||
Call { func: Box<Expr>, args: Vec<Expr> },
|
||||
Block(Vec<Stmt>),
|
||||
Match { subject: Box<Expr>, arms: Vec<MatchArm> },
|
||||
/// `activate TypeName where "semantic query string"`
|
||||
Activate { type_name: String, query: String },
|
||||
/// `sealed { stmts... }` — quantum-sealed block
|
||||
Sealed(Vec<Stmt>),
|
||||
If { cond: Box<Expr>, then: Box<Expr>, else_: Option<Box<Expr>> },
|
||||
Field { object: Box<Expr>, field: String },
|
||||
/// Array constructor: `[a, b, c]`
|
||||
Array(Vec<Expr>),
|
||||
/// Path expression: `Status::Active` (enum variant ref)
|
||||
Path { segments: Vec<String> },
|
||||
/// Index expression: `arr[0]`
|
||||
Index { object: Box<Expr>, index: Box<Expr> },
|
||||
}
|
||||
|
||||
// ── Match arm ─────────────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct MatchArm {
|
||||
pub pattern: Pattern,
|
||||
pub body: Expr,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
// ── Statements ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// A named parameter in a function definition.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Param {
|
||||
pub name: String,
|
||||
pub type_ann: TypeExpr,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// A field in a type definition.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Field {
|
||||
pub name: String,
|
||||
pub type_ann: TypeExpr,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// A variant in an enum definition.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Variant {
|
||||
pub name: String,
|
||||
/// Payload type, if any (e.g. `Pending(String)`)
|
||||
pub payload: Option<TypeExpr>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Stmt {
|
||||
/// `let name: Type = expr`
|
||||
Let {
|
||||
name: String,
|
||||
type_ann: Option<TypeExpr>,
|
||||
value: Expr,
|
||||
span: Span,
|
||||
},
|
||||
/// `return expr`
|
||||
Return(Expr, Span),
|
||||
/// A bare expression used as a statement (usually a call).
|
||||
Expr(Expr, Span),
|
||||
/// `fn name(params) -> ReturnType { body }`
|
||||
FnDef {
|
||||
name: String,
|
||||
params: Vec<Param>,
|
||||
return_type: TypeExpr,
|
||||
body: Vec<Stmt>,
|
||||
span: Span,
|
||||
},
|
||||
/// `type Name { fields... }`
|
||||
TypeDef {
|
||||
name: String,
|
||||
fields: Vec<Field>,
|
||||
span: Span,
|
||||
},
|
||||
/// `enum Name { variants... }`
|
||||
EnumDef {
|
||||
name: String,
|
||||
variants: Vec<Variant>,
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
// ── Top-level program ─────────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Program {
|
||||
pub stmts: Vec<Stmt>,
|
||||
/// The original source, kept for diagnostics and source maps.
|
||||
pub source: String,
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
//! Parser error types.
|
||||
|
||||
use thiserror::Error;
|
||||
use el_lexer::{Span, Token};
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
#[error("{kind} at {span}")]
|
||||
pub struct ParseError {
|
||||
pub kind: ParseErrorKind,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn new(kind: ParseErrorKind, span: Span) -> Self {
|
||||
Self { kind, span }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum ParseErrorKind {
|
||||
#[error("unexpected token {got}, expected {expected}")]
|
||||
UnexpectedToken { expected: String, got: String },
|
||||
|
||||
#[error("unexpected end of file")]
|
||||
UnexpectedEof,
|
||||
|
||||
#[error("invalid expression starting with {0}")]
|
||||
InvalidExprStart(String),
|
||||
|
||||
#[error("invalid type expression: {0}")]
|
||||
InvalidTypeExpr(String),
|
||||
|
||||
#[error("invalid pattern: {0}")]
|
||||
InvalidPattern(String),
|
||||
|
||||
#[error("expected identifier, got {0}")]
|
||||
ExpectedIdent(String),
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn expected(expected: impl Into<String>, got: &Token, span: Span) -> Self {
|
||||
Self::new(
|
||||
ParseErrorKind::UnexpectedToken {
|
||||
expected: expected.into(),
|
||||
got: got.to_string(),
|
||||
},
|
||||
span,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn eof(span: Span) -> Self {
|
||||
Self::new(ParseErrorKind::UnexpectedEof, span)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//! el-parser — Engram language recursive-descent parser.
|
||||
//!
|
||||
//! Converts a flat token stream into a typed [`Program`] AST.
|
||||
//!
|
||||
//! # Design
|
||||
//! Hand-written recursive descent — no parser generator. Every parse function
|
||||
//! returns `Result<T, ParseError>`, making the error path explicit.
|
||||
|
||||
mod ast;
|
||||
mod error;
|
||||
mod parser;
|
||||
|
||||
pub use ast::{
|
||||
BinOp, Expr, Field, Literal, MatchArm, Param, Pattern, Program, Stmt, TypeExpr, Variant,
|
||||
};
|
||||
pub use error::{ParseError, ParseErrorKind};
|
||||
pub use parser::parse;
|
||||
@@ -0,0 +1,685 @@
|
||||
//! Recursive-descent parser for the Engram language.
|
||||
|
||||
use el_lexer::{Span, Spanned, Token};
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::error::{ParseError, ParseErrorKind};
|
||||
|
||||
/// Parse a token stream into a [`Program`].
|
||||
///
|
||||
/// The `source` string is stored verbatim in the returned program for
|
||||
/// diagnostics and source map generation.
|
||||
pub fn parse(tokens: Vec<Spanned<Token>>, source: String) -> Result<Program, ParseError> {
|
||||
let mut p = Parser::new(tokens);
|
||||
let stmts = p.parse_program()?;
|
||||
Ok(Program { stmts, source })
|
||||
}
|
||||
|
||||
// ── Parser state ──────────────────────────────────────────────────────────────
|
||||
|
||||
struct Parser {
|
||||
tokens: Vec<Spanned<Token>>,
|
||||
/// Current cursor into `tokens`.
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
fn new(tokens: Vec<Spanned<Token>>) -> Self {
|
||||
Self { tokens, pos: 0 }
|
||||
}
|
||||
|
||||
// ── Token stream navigation ───────────────────────────────────────────────
|
||||
|
||||
fn current(&self) -> &Spanned<Token> {
|
||||
&self.tokens[self.pos.min(self.tokens.len() - 1)]
|
||||
}
|
||||
|
||||
fn peek(&self) -> &Token {
|
||||
&self.current().node
|
||||
}
|
||||
|
||||
fn peek_span(&self) -> Span {
|
||||
self.current().span
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn peek2(&self) -> Option<&Token> {
|
||||
self.tokens.get(self.pos + 1).map(|s| &s.node)
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> &Spanned<Token> {
|
||||
let tok = &self.tokens[self.pos.min(self.tokens.len() - 1)];
|
||||
if self.pos < self.tokens.len() - 1 {
|
||||
self.pos += 1;
|
||||
}
|
||||
tok
|
||||
}
|
||||
|
||||
fn at_end(&self) -> bool {
|
||||
matches!(self.peek(), Token::Eof)
|
||||
}
|
||||
|
||||
/// Consume the current token if it matches `expected`, otherwise error.
|
||||
fn expect(&mut self, expected: &Token) -> Result<Span, ParseError> {
|
||||
if self.peek() == expected {
|
||||
let span = self.peek_span();
|
||||
self.advance();
|
||||
Ok(span)
|
||||
} else {
|
||||
Err(ParseError::expected(format!("{expected}"), self.peek(), self.peek_span()))
|
||||
}
|
||||
}
|
||||
|
||||
fn expect_ident(&mut self) -> Result<(String, Span), ParseError> {
|
||||
let span = self.peek_span();
|
||||
match self.peek().clone() {
|
||||
Token::Ident(name) => {
|
||||
self.advance();
|
||||
Ok((name, span))
|
||||
}
|
||||
tok => Err(ParseError::new(
|
||||
ParseErrorKind::ExpectedIdent(tok.to_string()),
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn eat(&mut self, tok: &Token) -> bool {
|
||||
if self.peek() == tok {
|
||||
self.advance();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
// ── Top-level ─────────────────────────────────────────────────────────────
|
||||
|
||||
fn parse_program(&mut self) -> Result<Vec<Stmt>, ParseError> {
|
||||
let mut stmts = Vec::new();
|
||||
while !self.at_end() {
|
||||
// Skip optional semicolons at top level
|
||||
while self.eat(&Token::Semicolon) {}
|
||||
if self.at_end() {
|
||||
break;
|
||||
}
|
||||
stmts.push(self.parse_stmt()?);
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
|
||||
// ── Statements ────────────────────────────────────────────────────────────
|
||||
|
||||
fn parse_stmt(&mut self) -> Result<Stmt, ParseError> {
|
||||
let start = self.peek_span();
|
||||
match self.peek().clone() {
|
||||
Token::Let => self.parse_let(start),
|
||||
Token::Fn => self.parse_fn_def(start),
|
||||
Token::Type => self.parse_type_def(start),
|
||||
Token::Enum => self.parse_enum_def(start),
|
||||
Token::Return => {
|
||||
self.advance(); // consume `return`
|
||||
let expr = self.parse_expr()?;
|
||||
let span = Span::new(start.start, expr_span_end(&expr, start), start.line, start.col);
|
||||
self.eat(&Token::Semicolon);
|
||||
Ok(Stmt::Return(expr, span))
|
||||
}
|
||||
_ => {
|
||||
let expr = self.parse_expr()?;
|
||||
let span = start;
|
||||
self.eat(&Token::Semicolon);
|
||||
Ok(Stmt::Expr(expr, span))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_let(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::Let)?;
|
||||
let (name, _) = self.expect_ident()?;
|
||||
let type_ann = if self.eat(&Token::Colon) {
|
||||
Some(self.parse_type_expr()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.expect(&Token::Eq)?;
|
||||
let value = self.parse_expr()?;
|
||||
self.eat(&Token::Semicolon);
|
||||
Ok(Stmt::Let { name, type_ann, value, span: start })
|
||||
}
|
||||
|
||||
fn parse_fn_def(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::Fn)?;
|
||||
let (name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::LParen)?;
|
||||
let params = self.parse_param_list()?;
|
||||
self.expect(&Token::RParen)?;
|
||||
self.expect(&Token::Arrow)?;
|
||||
let return_type = self.parse_type_expr()?;
|
||||
self.expect(&Token::LBrace)?;
|
||||
let body = self.parse_block_body()?;
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Stmt::FnDef { name, params, return_type, body, span: start })
|
||||
}
|
||||
|
||||
fn parse_param_list(&mut self) -> Result<Vec<Param>, ParseError> {
|
||||
let mut params = Vec::new();
|
||||
while !matches!(self.peek(), Token::RParen | Token::Eof) {
|
||||
let span = self.peek_span();
|
||||
let (name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::Colon)?;
|
||||
let type_ann = self.parse_type_expr()?;
|
||||
params.push(Param { name, type_ann, span });
|
||||
if !self.eat(&Token::Comma) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(params)
|
||||
}
|
||||
|
||||
fn parse_type_def(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::Type)?;
|
||||
let (name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::LBrace)?;
|
||||
let mut fields = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
let span = self.peek_span();
|
||||
let (fname, _) = self.expect_ident()?;
|
||||
self.expect(&Token::Colon)?;
|
||||
let type_ann = self.parse_type_expr()?;
|
||||
fields.push(Field { name: fname, type_ann, span });
|
||||
self.eat(&Token::Comma);
|
||||
self.eat(&Token::Semicolon);
|
||||
}
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Stmt::TypeDef { name, fields, span: start })
|
||||
}
|
||||
|
||||
fn parse_enum_def(&mut self, start: Span) -> Result<Stmt, ParseError> {
|
||||
self.expect(&Token::Enum)?;
|
||||
let (name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::LBrace)?;
|
||||
let mut variants = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
let span = self.peek_span();
|
||||
let (vname, _) = self.expect_ident()?;
|
||||
let payload = if self.eat(&Token::LParen) {
|
||||
let ty = self.parse_type_expr()?;
|
||||
self.expect(&Token::RParen)?;
|
||||
Some(ty)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
variants.push(Variant { name: vname, payload, span });
|
||||
self.eat(&Token::Comma);
|
||||
self.eat(&Token::Semicolon);
|
||||
}
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Stmt::EnumDef { name, variants, span: start })
|
||||
}
|
||||
|
||||
fn parse_block_body(&mut self) -> Result<Vec<Stmt>, ParseError> {
|
||||
let mut stmts = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
while self.eat(&Token::Semicolon) {}
|
||||
if matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
break;
|
||||
}
|
||||
stmts.push(self.parse_stmt()?);
|
||||
}
|
||||
Ok(stmts)
|
||||
}
|
||||
|
||||
// ── Type expressions ──────────────────────────────────────────────────────
|
||||
|
||||
fn parse_type_expr(&mut self) -> Result<TypeExpr, ParseError> {
|
||||
let span = self.peek_span();
|
||||
// Array type: [T]
|
||||
if self.eat(&Token::LBracket) {
|
||||
let inner = self.parse_type_expr()?;
|
||||
self.expect(&Token::RBracket)?;
|
||||
let te = TypeExpr::Array(Box::new(inner));
|
||||
// Optional array: [T]?
|
||||
if self.eat(&Token::Not) {
|
||||
// Not actually "!", we need "?" — but we don't have that token.
|
||||
// We'll use Optional postfix via the Ident "?" — skip for now.
|
||||
}
|
||||
return Ok(te);
|
||||
}
|
||||
// Named type
|
||||
let name = match self.peek().clone() {
|
||||
Token::Ident(n) => { self.advance(); n }
|
||||
tok => return Err(ParseError::new(
|
||||
ParseErrorKind::InvalidTypeExpr(tok.to_string()),
|
||||
span,
|
||||
)),
|
||||
};
|
||||
// Check for function type: fn(A) -> B
|
||||
if name == "fn" {
|
||||
self.expect(&Token::LParen)?;
|
||||
let mut params = Vec::new();
|
||||
while !matches!(self.peek(), Token::RParen | Token::Eof) {
|
||||
params.push(self.parse_type_expr()?);
|
||||
if !self.eat(&Token::Comma) { break; }
|
||||
}
|
||||
self.expect(&Token::RParen)?;
|
||||
self.expect(&Token::Arrow)?;
|
||||
let ret = self.parse_type_expr()?;
|
||||
return Ok(TypeExpr::Fn { params, return_type: Box::new(ret) });
|
||||
}
|
||||
Ok(TypeExpr::Named(name))
|
||||
}
|
||||
|
||||
// ── Expressions ───────────────────────────────────────────────────────────
|
||||
|
||||
fn parse_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
self.parse_or_expr()
|
||||
}
|
||||
|
||||
fn parse_or_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_and_expr()?;
|
||||
while self.eat(&Token::Or) {
|
||||
let right = self.parse_and_expr()?;
|
||||
left = Expr::BinOp { op: BinOp::Or, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_and_expr(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_equality()?;
|
||||
while self.eat(&Token::And) {
|
||||
let right = self.parse_equality()?;
|
||||
left = Expr::BinOp { op: BinOp::And, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_equality(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_comparison()?;
|
||||
loop {
|
||||
let op = match self.peek() {
|
||||
Token::EqEq => BinOp::Eq,
|
||||
Token::NotEq => BinOp::NotEq,
|
||||
_ => break,
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_comparison()?;
|
||||
left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_comparison(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_additive()?;
|
||||
loop {
|
||||
let op = match self.peek() {
|
||||
Token::Lt => BinOp::Lt,
|
||||
Token::Gt => BinOp::Gt,
|
||||
Token::LtEq => BinOp::LtEq,
|
||||
Token::GtEq => BinOp::GtEq,
|
||||
_ => break,
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_additive()?;
|
||||
left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_additive(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_multiplicative()?;
|
||||
loop {
|
||||
let op = match self.peek() {
|
||||
Token::Plus => BinOp::Add,
|
||||
Token::Minus => BinOp::Sub,
|
||||
_ => break,
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_multiplicative()?;
|
||||
left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_multiplicative(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut left = self.parse_unary()?;
|
||||
loop {
|
||||
let op = match self.peek() {
|
||||
Token::Star => BinOp::Mul,
|
||||
Token::Slash => BinOp::Div,
|
||||
_ => break,
|
||||
};
|
||||
self.advance();
|
||||
let right = self.parse_unary()?;
|
||||
left = Expr::BinOp { op, left: Box::new(left), right: Box::new(right) };
|
||||
}
|
||||
Ok(left)
|
||||
}
|
||||
|
||||
fn parse_unary(&mut self) -> Result<Expr, ParseError> {
|
||||
if self.eat(&Token::Not) {
|
||||
let inner = self.parse_unary()?;
|
||||
return Ok(Expr::UnaryNot(Box::new(inner)));
|
||||
}
|
||||
self.parse_postfix()
|
||||
}
|
||||
|
||||
fn parse_postfix(&mut self) -> Result<Expr, ParseError> {
|
||||
let mut expr = self.parse_primary()?;
|
||||
loop {
|
||||
match self.peek() {
|
||||
Token::Dot => {
|
||||
self.advance();
|
||||
let (field, _) = self.expect_ident()?;
|
||||
expr = Expr::Field { object: Box::new(expr), field };
|
||||
}
|
||||
Token::LParen => {
|
||||
self.advance();
|
||||
let args = self.parse_arg_list()?;
|
||||
self.expect(&Token::RParen)?;
|
||||
expr = Expr::Call { func: Box::new(expr), args };
|
||||
}
|
||||
Token::LBracket => {
|
||||
self.advance();
|
||||
let index = self.parse_expr()?;
|
||||
self.expect(&Token::RBracket)?;
|
||||
expr = Expr::Index { object: Box::new(expr), index: Box::new(index) };
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
fn parse_arg_list(&mut self) -> Result<Vec<Expr>, ParseError> {
|
||||
let mut args = Vec::new();
|
||||
while !matches!(self.peek(), Token::RParen | Token::Eof) {
|
||||
args.push(self.parse_expr()?);
|
||||
if !self.eat(&Token::Comma) { break; }
|
||||
}
|
||||
Ok(args)
|
||||
}
|
||||
|
||||
fn parse_primary(&mut self) -> Result<Expr, ParseError> {
|
||||
let span = self.peek_span();
|
||||
match self.peek().clone() {
|
||||
// Literals
|
||||
Token::IntLiteral(n) => { self.advance(); Ok(Expr::Literal(Literal::Int(n))) }
|
||||
Token::FloatLiteral(f) => { self.advance(); Ok(Expr::Literal(Literal::Float(f))) }
|
||||
Token::StringLiteral(s) => { self.advance(); Ok(Expr::Literal(Literal::Str(s))) }
|
||||
Token::BoolLiteral(b) => { self.advance(); Ok(Expr::Literal(Literal::Bool(b))) }
|
||||
|
||||
// Grouped or tuple
|
||||
Token::LParen => {
|
||||
self.advance();
|
||||
let expr = self.parse_expr()?;
|
||||
self.expect(&Token::RParen)?;
|
||||
Ok(expr)
|
||||
}
|
||||
|
||||
// Block
|
||||
Token::LBrace => {
|
||||
self.advance();
|
||||
let stmts = self.parse_block_body()?;
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Expr::Block(stmts))
|
||||
}
|
||||
|
||||
// Array literal
|
||||
Token::LBracket => {
|
||||
self.advance();
|
||||
let mut elems = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBracket | Token::Eof) {
|
||||
elems.push(self.parse_expr()?);
|
||||
if !self.eat(&Token::Comma) { break; }
|
||||
}
|
||||
self.expect(&Token::RBracket)?;
|
||||
Ok(Expr::Array(elems))
|
||||
}
|
||||
|
||||
// match expression
|
||||
Token::Match => {
|
||||
self.advance();
|
||||
let subject = self.parse_expr()?;
|
||||
self.expect(&Token::LBrace)?;
|
||||
let arms = self.parse_match_arms()?;
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Expr::Match { subject: Box::new(subject), arms })
|
||||
}
|
||||
|
||||
// activate Type where "query"
|
||||
Token::Activate => {
|
||||
self.advance();
|
||||
let (type_name, _) = self.expect_ident()?;
|
||||
self.expect(&Token::Where)?;
|
||||
let query = match self.peek().clone() {
|
||||
Token::StringLiteral(s) => { self.advance(); s }
|
||||
tok => return Err(ParseError::expected("string literal", &tok, self.peek_span())),
|
||||
};
|
||||
Ok(Expr::Activate { type_name, query })
|
||||
}
|
||||
|
||||
// sealed { stmts }
|
||||
Token::Sealed => {
|
||||
self.advance();
|
||||
self.expect(&Token::LBrace)?;
|
||||
let stmts = self.parse_block_body()?;
|
||||
self.expect(&Token::RBrace)?;
|
||||
Ok(Expr::Sealed(stmts))
|
||||
}
|
||||
|
||||
// if/else
|
||||
Token::If => {
|
||||
self.advance();
|
||||
let cond = self.parse_expr()?;
|
||||
let then = self.parse_primary()?; // expects block
|
||||
let else_ = if self.eat(&Token::Else) {
|
||||
Some(Box::new(self.parse_primary()?))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Expr::If { cond: Box::new(cond), then: Box::new(then), else_ })
|
||||
}
|
||||
|
||||
// Identifier — could be plain name or path (Foo::Bar)
|
||||
Token::Ident(name) => {
|
||||
self.advance();
|
||||
// Check for path: Foo::Bar or Foo::Bar::Baz
|
||||
if matches!(self.peek(), Token::ColonColon) {
|
||||
let mut segments = vec![name];
|
||||
while self.eat(&Token::ColonColon) {
|
||||
let (seg, _) = self.expect_ident()?;
|
||||
segments.push(seg);
|
||||
}
|
||||
Ok(Expr::Path { segments })
|
||||
} else {
|
||||
Ok(Expr::Ident(name))
|
||||
}
|
||||
}
|
||||
|
||||
tok => Err(ParseError::new(
|
||||
ParseErrorKind::InvalidExprStart(tok.to_string()),
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// ── Match arms ────────────────────────────────────────────────────────────
|
||||
|
||||
fn parse_match_arms(&mut self) -> Result<Vec<MatchArm>, ParseError> {
|
||||
let mut arms = Vec::new();
|
||||
while !matches!(self.peek(), Token::RBrace | Token::Eof) {
|
||||
let span = self.peek_span();
|
||||
let pattern = self.parse_pattern()?;
|
||||
self.expect(&Token::FatArrow)?;
|
||||
let body = self.parse_expr()?;
|
||||
arms.push(MatchArm { pattern, body, span });
|
||||
self.eat(&Token::Comma);
|
||||
self.eat(&Token::Semicolon);
|
||||
}
|
||||
Ok(arms)
|
||||
}
|
||||
|
||||
fn parse_pattern(&mut self) -> Result<Pattern, ParseError> {
|
||||
let span = self.peek_span();
|
||||
match self.peek().clone() {
|
||||
// Wildcard
|
||||
Token::Ident(ref s) if s == "_" => {
|
||||
self.advance();
|
||||
Ok(Pattern::Wildcard)
|
||||
}
|
||||
// Could be: binding, enum variant, or path
|
||||
Token::Ident(name) => {
|
||||
self.advance();
|
||||
if self.eat(&Token::ColonColon) {
|
||||
// EnumVariant pattern: Status::Active or Status::Pending(reason)
|
||||
let (variant, _) = self.expect_ident()?;
|
||||
let payload = if self.eat(&Token::LParen) {
|
||||
let (bind, _) = self.expect_ident()?;
|
||||
self.expect(&Token::RParen)?;
|
||||
Some(bind)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(Pattern::EnumVariant { enum_name: name, variant, payload })
|
||||
} else {
|
||||
// Simple binding
|
||||
Ok(Pattern::Binding(name))
|
||||
}
|
||||
}
|
||||
Token::IntLiteral(n) => { self.advance(); Ok(Pattern::Literal(Literal::Int(n))) }
|
||||
Token::StringLiteral(s) => { self.advance(); Ok(Pattern::Literal(Literal::Str(s))) }
|
||||
Token::BoolLiteral(b) => { self.advance(); Ok(Pattern::Literal(Literal::Bool(b))) }
|
||||
tok => Err(ParseError::new(
|
||||
ParseErrorKind::InvalidPattern(tok.to_string()),
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn expr_span_end(_expr: &Expr, fallback: Span) -> usize {
|
||||
fallback.end
|
||||
}
|
||||
|
||||
// ── Tests ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use el_lexer::tokenize;
|
||||
use super::*;
|
||||
|
||||
fn parse_src(src: &str) -> Program {
|
||||
let tokens = tokenize(src).expect("lex failed");
|
||||
parse(tokens, src.to_string()).expect("parse failed")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_let() {
|
||||
let p = parse_src("let x: Int = 42");
|
||||
assert!(matches!(p.stmts[0], Stmt::Let { ref name, .. } if name == "x"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fn_def() {
|
||||
let src = r#"fn greet(name: String) -> String { return "Hello" }"#;
|
||||
let p = parse_src(src);
|
||||
assert!(matches!(&p.stmts[0], Stmt::FnDef { name, .. } if name == "greet"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_type_def() {
|
||||
let src = "type User { id: Uuid name: String email: String }";
|
||||
let p = parse_src(src);
|
||||
assert!(matches!(&p.stmts[0], Stmt::TypeDef { name, fields, .. } if name == "User" && fields.len() == 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_enum_def() {
|
||||
let src = "enum Status { Active Inactive Pending(String) }";
|
||||
let p = parse_src(src);
|
||||
match &p.stmts[0] {
|
||||
Stmt::EnumDef { name, variants, .. } => {
|
||||
assert_eq!(name, "Status");
|
||||
assert_eq!(variants.len(), 3);
|
||||
assert_eq!(variants[2].name, "Pending");
|
||||
assert!(variants[2].payload.is_some());
|
||||
}
|
||||
_ => panic!("expected EnumDef"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_match() {
|
||||
let src = r#"
|
||||
match status {
|
||||
Status::Active => "active"
|
||||
Status::Inactive => "inactive"
|
||||
}
|
||||
"#;
|
||||
let p = parse_src(src);
|
||||
assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Match { arms, .. }, _) if arms.len() == 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_activate() {
|
||||
let src = r#"activate User where "customer who purchased recently""#;
|
||||
let p = parse_src(src);
|
||||
assert!(matches!(
|
||||
&p.stmts[0],
|
||||
Stmt::Expr(Expr::Activate { type_name, query }, _)
|
||||
if type_name == "User" && query.contains("customer")
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_sealed_block() {
|
||||
let src = r#"sealed { let key: String = "secret" }"#;
|
||||
let p = parse_src(src);
|
||||
assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Sealed(_), _)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_binary_ops() {
|
||||
let p = parse_src("let result = 1 + 2 * 3");
|
||||
match &p.stmts[0] {
|
||||
Stmt::Let { value: Expr::BinOp { op: BinOp::Add, right, .. }, .. } => {
|
||||
// Right side should be 2*3
|
||||
assert!(matches!(**right, Expr::BinOp { op: BinOp::Mul, .. }));
|
||||
}
|
||||
_ => panic!("unexpected AST"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fn_call() {
|
||||
let p = parse_src(r#"greet("Will")"#);
|
||||
assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Call { .. }, _)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_field_access() {
|
||||
let p = parse_src("user.name");
|
||||
assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Field { field, .. }, _) if field == "name"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_if_else() {
|
||||
let src = r#"if x == 1 { return "yes" } else { return "no" }"#;
|
||||
let p = parse_src(src);
|
||||
assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::If { else_: Some(_), .. }, _)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_array_literal() {
|
||||
let p = parse_src("[1, 2, 3]");
|
||||
assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Array(elems), _) if elems.len() == 3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_path_expr() {
|
||||
let p = parse_src("Status::Active");
|
||||
assert!(matches!(&p.stmts[0], Stmt::Expr(Expr::Path { segments }, _) if segments.len() == 2));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user