diff --git a/crates/frontend/src/ast.rs b/crates/frontend/src/ast.rs index 16d93a7..64e2978 100644 --- a/crates/frontend/src/ast.rs +++ b/crates/frontend/src/ast.rs @@ -23,6 +23,8 @@ use strum::{EnumDiscriminants, EnumIter}; use crate::prelude::*; +// TODO: rename all `atom` references to `apply` + /// Retrieves, but does not parse, all top-level items in a file. // TODO: is `Vec` the most efficient to incrementally update here? #[salsa::tracked] @@ -242,11 +244,10 @@ pub fn expr(db: &dyn Database, ast: AstNode) -> Expr { let op = unary.expect_field(db, "op").expect_parse(db); let term = Arc::new(expr(db, unary.expect_field(db, "term"))); ExprKind::UnaryOp { op, term } - } else if let Some(atom) = ast.get_field(db, "atom") { - let head = atom.expect_field(db, "head"); - let head = WithAst::new(head, head.contents(db).to_string()); - let body = expr(db, atom.expect_field(db, "body")); - ExprKind::Extra(Arc::new(ExprExtra::Atom { head, body })) + } else if let Some(ast) = ast.get_field(db, "atom") { + ExprKind::Extra(Arc::new(ExprExtra::Apply(apply(db, ast)))) + } else if let Some(ast) = ast.get_field(db, "aggregate") { + ExprKind::Extra(Arc::new(ExprExtra::Aggregate(aggregate(db, ast)))) } else if let Some(parens) = ast.get_field(db, "parens") { return expr(db, parens); } else { @@ -278,6 +279,36 @@ fn value(db: &dyn Database, ast: AstNode) -> ir::Value { } } +/// Parses an aggregate operator. +pub fn aggregate(db: &dyn Database, ast: AstNode) -> Aggregate { + let kind = ast.expect_field(db, "op").with_contents(db); + + let witnesses = ast + .get_fields(db, "witness") + .map(|ast| ast.with_contents(db)) + .collect(); + + let body = if let Some(ast) = ast.get_field(db, "atom") { + AggregateBody::Apply(apply(db, ast)) + } else { + let clauses = ast.get_fields(db, "clause").map(|ast| expr(db, ast)); + AggregateBody::Body(clauses.collect()) + }; + + Aggregate { + kind, + witnesses, + body, + } +} + +/// Parses an application operation. +pub fn apply(db: &dyn Database, ast: AstNode) -> Apply { + let head = ast.expect_field(db, "head").with_contents(db); + let body = expr(db, ast.expect_field(db, "body")); + Apply { head, body } +} + /// A type alias for parse stage [Expr]. pub type Expr = ir::Expr; @@ -308,8 +339,44 @@ pub enum ExprExtra { /// An unresolved variable name. VariableName(String), - /// A raw atom expression with no lower-level querying logic. - Atom { head: WithAst, body: Expr }, + /// An abstract application expression. + Apply(Apply), + + /// An aggregate operator. + Aggregate(Aggregate), +} + +/// An abstract aggregate operator. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Aggregate { + /// The unresolved kind of aggregate operator. + pub kind: WithAst, + + /// The list of witnesses to the body. + pub witnesses: Vec>, + + /// The body of the aggregate. + pub body: AggregateBody, +} + +/// An aggregate operator's body. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum AggregateBody { + /// A single application operation. + Apply(Apply), + + /// A list of clauses, like in a [RuleBody]. + Body(Vec), +} + +/// An application operation with no lower-level querying logic. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct Apply { + /// The name of the relation to query or function to invoke. + pub head: WithAst, + + /// The parameters to the function or query. + pub body: Expr, } /// Extends [ir::BinaryOpKind] with the omitted operators to preserve syntax. diff --git a/tree-sitter-kerolox/grammar.js b/tree-sitter-kerolox/grammar.js index bdc6ff1..b269f4e 100644 --- a/tree-sitter-kerolox/grammar.js +++ b/tree-sitter-kerolox/grammar.js @@ -118,6 +118,7 @@ export default grammar({ seq('(', field("parens", $.expr), ')'), ), + // TODO: rename to "apply" atom: $ => prec.right(2, seq(field("head", $.symbol), field("body", $.expr))), tuple: $ => parenListComma(field("el", $.expr)),