diff --git a/Cargo.lock b/Cargo.lock index 00e8991..a9af070 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -730,6 +730,7 @@ version = "0.1.0" dependencies = [ "ariadne", "datatest-stable", + "indexmap", "kerolox-ir", "lsp-types", "ropey", diff --git a/Cargo.toml b/Cargo.toml index 66e1774..c37feac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ ariadne = { version = "0.5", features = ["auto-color"] } datatest-stable = "0.3" derive-where = "1.6" futures-util = { version = "0.3", default-features = false } +indexmap = "2" lsp-types = "0.94" ordered-float = { version = "4", features = ["serde"] } ropey = "1.6" diff --git a/crates/frontend/Cargo.toml b/crates/frontend/Cargo.toml index ef85680..0846b25 100644 --- a/crates/frontend/Cargo.toml +++ b/crates/frontend/Cargo.toml @@ -9,6 +9,7 @@ rust-version.workspace = true [dependencies] ariadne.workspace = true +indexmap.workspace = true kerolox-ir = { workspace = true, features = ["salsa"] } lsp-types.workspace = true ropey.workspace = true diff --git a/crates/frontend/src/resolve.rs b/crates/frontend/src/resolve.rs index c3300c5..b5bd367 100644 --- a/crates/frontend/src/resolve.rs +++ b/crates/frontend/src/resolve.rs @@ -19,8 +19,9 @@ //! The "resolve" stage of the frontend: match up names of objects with the //! semantic objects they refer to. -use std::collections::BTreeMap; +use std::{collections::BTreeMap, convert::Infallible, marker::PhantomData, sync::Arc}; +use indexmap::IndexSet; use salsa::Update; use crate::prelude::*; @@ -64,3 +65,248 @@ impl<'db> NamespaceItem<'db> { } } } + +/// Resolves all references in an [ast::Rule]. +#[salsa::tracked] +pub fn rule<'db>(db: &'db dyn Database, ast: ast::Rule<'db>) -> Rule<'db> { + // resolve the body + let body = match ast.body(db) { + // if a body is present, resolve separately + Some(body) => rule_body(db, body), + + // if a body is absent, create an explicit rule body by hand + None => RuleBody { + meta: ast.ast(db), + vars: Default::default(), + loaded: Default::default(), + body: Expr { + meta: ast.ast(db), + kind: ir::ExprKind::Extra(Arc::new(ExprExtra::Implicit)), + }, + }, + }; + + // the head is empty for now because it is untyped + let head = Default::default(); + + // resolve the name of the target relation + let relation = relation_name(db, ast.relation(db)); + + // assemble the finished rule + Rule { + meta: ast, + relation, + head, + body, + } +} + +/// Resolves all references in an [ast::Assumption]. +#[salsa::tracked] +pub fn assumption<'db>(db: &'db dyn Database, ast: ast::Assumption<'db>) -> Assumption<'db> { + todo!() +} + +/// Resolves all references in an [ast::RuleBody]. +#[salsa::tracked] +pub fn rule_body<'db>(db: &'db dyn Database, ast: ast::RuleBody<'db>) -> RuleBody<'db> { + // initialize conversion tables + let mut vars = IndexSet::new(); + let mut loaded = IndexSet::new(); + + // convert AST clauses to resolved clauses + let clauses = ast + .clauses(db) + .into_iter() + .map(|ast| expr(db, &mut vars, &mut loaded, &ast)); + + // reduce each clause to a single "and" operation + let body = clauses.reduce(|lhs, rhs| Expr { + meta: ast.ast(db), + kind: ir::ExprKind::BinaryOp { + op: ir::BinaryOpKind::And.into(), + lhs: Arc::new(lhs), + rhs: Arc::new(rhs), + }, + }); + + // if the body has no clauses at all, mark it specially + let body = body.unwrap_or(Expr { + meta: ast.ast(db), + kind: ir::ExprKind::Extra(Arc::new(ExprExtra::Implicit)), + }); + + // assemble completed rule body + RuleBody { + loaded: loaded.into_iter().collect(), + vars: vars.into_iter().collect(), + meta: ast.ast(db), + body, + } +} + +/// Internal conversion tool to resolved expressions, with side-effects. +pub fn expr<'db>( + db: &'db dyn Database, + vars: &mut IndexSet, + loaded: &mut IndexSet>, + expr: &ast::Expr, +) -> Expr<'db> { + // alias recursive call with curried side-effects + let mut resolve = |ast| crate::resolve::expr(db, vars, loaded, ast); + + // convert inner expression variant + use ir::ExprKind::*; + let kind = match &expr.kind { + // directly translate existing value definitions + // TODO: symbols are unresolved, handle unreachability better? + Value(value) => Value(value.clone()), + + // recursively descend into unary operations + UnaryOp { op, term } => UnaryOp { + op: *op, + term: Arc::new(resolve(term.as_ref())), + }, + + // recursively descend into binary operations + BinaryOp { op, lhs, rhs } => BinaryOp { + op: *op, + lhs: Arc::new(resolve(lhs.as_ref())), + rhs: Arc::new(resolve(rhs.as_ref())), + }, + + // do deeper conversion for AST-specific expression variants + Extra(extra) => match extra.as_ref() { + // insert a fresh variable identified by this expression's AST node + ast::ExprExtra::Hole => Variable(vars.insert_full(VariableMeta::Hole(expr.meta)).0), + + // recursively convert elements of tuples + ast::ExprExtra::Tuple(exprs) => Extra(Arc::new(ExprExtra::Tuple( + exprs.iter().map(resolve).collect(), + ))), + + ast::ExprExtra::SymbolName(name) => todo!(), + + // resolve common variable name to index + ast::ExprExtra::VariableName(name) => { + Variable(vars.insert_full(VariableMeta::Named(name.clone())).0) + } + + ast::ExprExtra::Apply(apply) => todo!(), + + ast::ExprExtra::Aggregate(aggregate) => todo!(), + }, + + // TODO: handle unreachability better? + Variable(idx) => Variable(*idx), + + // TODO: handle unreachability better? + Load { relation, query } => Load { + relation: *relation, + query: query.clone(), + }, + }; + + // construct converted expression + Expr { + meta: expr.meta, + kind, + } +} + +/// Resolve a reference to a relation by name. +// TODO: track incrementally? +pub fn relation_name<'db>(db: &'db dyn Database, name: ast::Name) -> RelationLabel<'db> { + todo!() +} + +/// A type alias for a resolve stage [ir::Rule]. +pub type Rule<'db> = ir::Rule>; + +/// A type alias for a resolve stage [ir::Assumption]. +pub type Assumption<'db> = ir::Assumption>; + +/// A type alias for a resolve stage [ir::RuleBody]. +pub type RuleBody<'db> = ir::RuleBody>; + +/// A type alias for a resolve stage [ir::Expr]. +pub type Expr<'db> = ir::Expr>; + +/// Resolve stage-specific extensions to [ir::ProgramInfo]. +pub struct ProgramInfo<'db>(PhantomData<&'db ()>); + +impl<'db> ir::ProgramInfo for ProgramInfo<'db> { + type SymbolMeta = Infallible; + + /// [RelationLabel] fallibly identifies relations after the resolution stage. + type RelationLabel = RelationLabel<'db>; + + type AssumptionMeta = Infallible; + + type RuleMeta = ast::Rule<'db>; + + /// Resolved rule bodies point only to their AST origins. + /// + /// Rule bodies may be implicit, so there is not always an associated + /// [ast::RuleBody]. In this case, the AST node is the rule, not its body. + type RuleBodyMeta = AstNode; + + type VariableMeta = VariableMeta; + + /// Information about the source location is preserved from the AST stage. + type ExprMeta = AstNode; + + type BinaryOp = ast::BinaryOpKind; + + type UnaryOp = ir::UnaryOpKind; + + type ExprExtra = Arc>; +} + +/// Extra [ExprKind] variants in the resolve stage. +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum ExprExtra<'db> { + /// If a rule body has no clauses, this is its body's expression variant. + Implicit, + + /// A structured tuple of sub-expressions. + Tuple(Vec>), + + /// A resolved symbol. + /// + /// Not yet aggregated globally, so no index as [ir::Value::Symbol] yet. + Symbol(WithAst), + + /// A resolved application expression. + Apply(Apply<'db>), +} + +/// Identifiers for relations in the resolve stage. +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum RelationLabel<'db> { + /// The relation has been resolved to a concrete relation object. + Relation(ast::Relation<'db>), + + /// The relation could not be resolved. + Unknown(ast::Name), +} + +/// Resolved identification of a variable within a rule body. +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum VariableMeta { + /// The variable has a name. + Named(String), + + /// The variable is unnamed (a hole) but is unique to its AST node. + Hole(AstNode), +} + +/// A high-level application operation. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Apply<'db> { + /// The source relation's index in the parent [RuleBody]. + pub relation: usize, + + /// The parameters to the function or query. + pub body: Expr<'db>, +}