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 = [
|
dependencies = [
|
||||||
"ariadne",
|
"ariadne",
|
||||||
"datatest-stable",
|
"datatest-stable",
|
||||||
|
"indexmap",
|
||||||
"kerolox-ir",
|
"kerolox-ir",
|
||||||
"lsp-types",
|
"lsp-types",
|
||||||
"ropey",
|
"ropey",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ ariadne = { version = "0.5", features = ["auto-color"] }
|
|||||||
datatest-stable = "0.3"
|
datatest-stable = "0.3"
|
||||||
derive-where = "1.6"
|
derive-where = "1.6"
|
||||||
futures-util = { version = "0.3", default-features = false }
|
futures-util = { version = "0.3", default-features = false }
|
||||||
|
indexmap = "2"
|
||||||
lsp-types = "0.94"
|
lsp-types = "0.94"
|
||||||
ordered-float = { version = "4", features = ["serde"] }
|
ordered-float = { version = "4", features = ["serde"] }
|
||||||
ropey = "1.6"
|
ropey = "1.6"
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ rust-version.workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ariadne.workspace = true
|
ariadne.workspace = true
|
||||||
|
indexmap.workspace = true
|
||||||
kerolox-ir = { workspace = true, features = ["salsa"] }
|
kerolox-ir = { workspace = true, features = ["salsa"] }
|
||||||
lsp-types.workspace = true
|
lsp-types.workspace = true
|
||||||
ropey.workspace = true
|
ropey.workspace = true
|
||||||
|
|||||||
@@ -19,8 +19,9 @@
|
|||||||
//! The "resolve" stage of the frontend: match up names of objects with the
|
//! The "resolve" stage of the frontend: match up names of objects with the
|
||||||
//! semantic objects they refer to.
|
//! 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 salsa::Update;
|
||||||
|
|
||||||
use crate::prelude::*;
|
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