WIP resolve stage
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -730,6 +730,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"ariadne",
|
||||
"datatest-stable",
|
||||
"indexmap",
|
||||
"kerolox-ir",
|
||||
"lsp-types",
|
||||
"ropey",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<VariableMeta>,
|
||||
loaded: &mut IndexSet<RelationLabel<'db>>,
|
||||
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<ProgramInfo<'db>>;
|
||||
|
||||
/// A type alias for a resolve stage [ir::Assumption].
|
||||
pub type Assumption<'db> = ir::Assumption<ProgramInfo<'db>>;
|
||||
|
||||
/// A type alias for a resolve stage [ir::RuleBody].
|
||||
pub type RuleBody<'db> = ir::RuleBody<ProgramInfo<'db>>;
|
||||
|
||||
/// A type alias for a resolve stage [ir::Expr].
|
||||
pub type Expr<'db> = ir::Expr<ProgramInfo<'db>>;
|
||||
|
||||
/// 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<ExprExtra<'db>>;
|
||||
}
|
||||
|
||||
/// 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<Expr<'db>>),
|
||||
|
||||
/// A resolved symbol.
|
||||
///
|
||||
/// Not yet aggregated globally, so no index as [ir::Value::Symbol] yet.
|
||||
Symbol(WithAst<ast::Name>),
|
||||
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user