618 lines
21 KiB
Rust
618 lines
21 KiB
Rust
use crate::atom_table::*;
|
|
use crate::codegen::CodeGenSettings;
|
|
use crate::forms::*;
|
|
use crate::instructions::*;
|
|
use crate::machine::disjuncts::*;
|
|
use crate::machine::loader::*;
|
|
use crate::machine::machine_errors::*;
|
|
use crate::parser::ast::*;
|
|
|
|
use indexmap::IndexSet;
|
|
|
|
use std::cell::Cell;
|
|
use std::convert::TryFrom;
|
|
pub(crate) fn to_op_decl(prec: u16, spec: OpDeclSpec, name: Atom) -> OpDecl {
|
|
OpDecl::new(OpDesc::build_with(prec, spec), name)
|
|
}
|
|
|
|
pub(crate) fn to_op_decl_spec(spec: Atom) -> Result<OpDeclSpec, CompilationError> {
|
|
OpDeclSpec::try_from(spec).map_err(|_err| {
|
|
CompilationError::InvalidDirective(DirectiveError::InvalidOpDeclSpecValue(spec))
|
|
})
|
|
}
|
|
|
|
fn setup_op_decl(mut terms: Vec<Term>) -> Result<OpDecl, CompilationError> {
|
|
// should allow non-partial lists?
|
|
let name = match terms.pop().unwrap() {
|
|
Term::Literal(_, Literal::Atom(name)) => name,
|
|
other => {
|
|
return Err(CompilationError::InvalidDirective(
|
|
DirectiveError::InvalidOpDeclNameType(other),
|
|
));
|
|
}
|
|
};
|
|
|
|
let spec = match terms.pop().unwrap() {
|
|
Term::Literal(_, Literal::Atom(name)) => name,
|
|
other => {
|
|
return Err(CompilationError::InvalidDirective(
|
|
DirectiveError::InvalidOpDeclSpecDomain(other),
|
|
))
|
|
}
|
|
};
|
|
|
|
let spec = to_op_decl_spec(spec)?;
|
|
|
|
let prec = match terms.pop().unwrap() {
|
|
Term::Literal(_, Literal::Fixnum(bi)) => match u16::try_from(bi.get_num()) {
|
|
Ok(n) if n <= 1200 => n,
|
|
_ => {
|
|
return Err(CompilationError::InvalidDirective(
|
|
DirectiveError::InvalidOpDeclPrecDomain(bi),
|
|
));
|
|
}
|
|
},
|
|
other => {
|
|
return Err(CompilationError::InvalidDirective(
|
|
DirectiveError::InvalidOpDeclPrecType(other),
|
|
));
|
|
}
|
|
};
|
|
|
|
if name == "[]" || name == "{}" {
|
|
return Err(CompilationError::InvalidDirective(
|
|
DirectiveError::ShallNotCreate(name),
|
|
));
|
|
}
|
|
|
|
if name == "," {
|
|
return Err(CompilationError::InvalidDirective(
|
|
DirectiveError::ShallNotModify(name),
|
|
));
|
|
}
|
|
|
|
if name == "|" && (prec < 1001 || !spec.is_infix()) {
|
|
return Err(CompilationError::InvalidDirective(
|
|
DirectiveError::ShallNotCreate(name),
|
|
));
|
|
}
|
|
|
|
Ok(to_op_decl(prec, spec, name))
|
|
}
|
|
|
|
fn setup_predicate_indicator(term: &mut Term) -> Result<PredicateKey, CompilationError> {
|
|
match term {
|
|
Term::Clause(_, slash, ref mut terms)
|
|
if (*slash == atom!("/") || *slash == atom!("//")) && terms.len() == 2 =>
|
|
{
|
|
let arity = terms.pop().unwrap();
|
|
let name = terms.pop().unwrap();
|
|
|
|
let arity = match arity {
|
|
Term::Literal(_, Literal::Integer(n)) => (&*n).try_into().ok(),
|
|
Term::Literal(_, Literal::Fixnum(n)) => usize::try_from(n.get_num()).ok(),
|
|
_ => None,
|
|
}
|
|
.ok_or(CompilationError::InvalidModuleExport)?;
|
|
|
|
let name = match name {
|
|
Term::Literal(_, Literal::Atom(name)) => Some(name),
|
|
_ => None,
|
|
}
|
|
.ok_or(CompilationError::InvalidModuleExport)?;
|
|
|
|
if *slash == atom!("/") {
|
|
Ok((name, arity))
|
|
} else {
|
|
Ok((name, arity + 2))
|
|
}
|
|
}
|
|
_ => Err(CompilationError::InvalidModuleExport),
|
|
}
|
|
}
|
|
|
|
fn setup_module_export(mut term: Term) -> Result<ModuleExport, CompilationError> {
|
|
setup_predicate_indicator(&mut term)
|
|
.map(ModuleExport::PredicateKey)
|
|
.or_else(|_| {
|
|
if let Term::Clause(_, name, terms) = term {
|
|
if terms.len() == 3 && name == atom!("op") {
|
|
Ok(ModuleExport::OpDecl(setup_op_decl(terms)?))
|
|
} else {
|
|
Err(CompilationError::InvalidModuleDecl)
|
|
}
|
|
} else {
|
|
Err(CompilationError::InvalidModuleDecl)
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn build_rule_body(vars: &[Term], body_term: Term) -> Term {
|
|
let head_term = Term::Clause(Cell::default(), atom!(""), vars.to_vec());
|
|
let rule = vec![head_term, body_term];
|
|
|
|
Term::Clause(Cell::default(), atom!(":-"), rule)
|
|
}
|
|
|
|
pub(super) fn setup_module_export_list(
|
|
mut export_list: Term,
|
|
) -> Result<Vec<ModuleExport>, CompilationError> {
|
|
let mut exports = vec![];
|
|
|
|
while let Term::Cons(_, t1, t2) = export_list {
|
|
let module_export = setup_module_export(*t1)?;
|
|
|
|
exports.push(module_export);
|
|
export_list = *t2;
|
|
}
|
|
|
|
if let Term::Literal(_, Literal::Atom(atom!("[]"))) = export_list {
|
|
Ok(exports)
|
|
} else {
|
|
Err(CompilationError::InvalidModuleDecl)
|
|
}
|
|
}
|
|
|
|
fn setup_module_decl(mut terms: Vec<Term>) -> Result<ModuleDecl, CompilationError> {
|
|
let export_list = terms.pop().unwrap();
|
|
let name = terms.pop().unwrap();
|
|
|
|
let name = match name {
|
|
Term::Literal(_, Literal::Atom(name)) => Some(name),
|
|
_ => None,
|
|
}
|
|
.ok_or(CompilationError::InvalidModuleDecl)?;
|
|
|
|
let exports = setup_module_export_list(export_list)?;
|
|
Ok(ModuleDecl { name, exports })
|
|
}
|
|
|
|
fn setup_use_module_decl(mut terms: Vec<Term>) -> Result<ModuleSource, CompilationError> {
|
|
match terms.pop().unwrap() {
|
|
Term::Clause(_, name, mut terms) if name == atom!("library") && terms.len() == 1 => {
|
|
match terms.pop().unwrap() {
|
|
Term::Literal(_, Literal::Atom(name)) => Ok(ModuleSource::Library(name)),
|
|
_ => Err(CompilationError::InvalidModuleDecl),
|
|
}
|
|
}
|
|
Term::Literal(_, Literal::Atom(name)) => Ok(ModuleSource::File(name)),
|
|
_ => Err(CompilationError::InvalidUseModuleDecl),
|
|
}
|
|
}
|
|
|
|
type UseModuleExport = (ModuleSource, IndexSet<ModuleExport>);
|
|
|
|
fn setup_qualified_import(mut terms: Vec<Term>) -> Result<UseModuleExport, CompilationError> {
|
|
let mut export_list = terms.pop().unwrap();
|
|
let module_src = match terms.pop().unwrap() {
|
|
Term::Clause(_, name, mut terms) if name == atom!("library") && terms.len() == 1 => {
|
|
match terms.pop().unwrap() {
|
|
Term::Literal(_, Literal::Atom(name)) => Ok(ModuleSource::Library(name)),
|
|
_ => Err(CompilationError::InvalidModuleDecl),
|
|
}
|
|
}
|
|
Term::Literal(_, Literal::Atom(name)) => Ok(ModuleSource::File(name)),
|
|
_ => Err(CompilationError::InvalidUseModuleDecl),
|
|
}?;
|
|
|
|
let mut exports = IndexSet::new();
|
|
|
|
while let Term::Cons(_, t1, t2) = export_list {
|
|
exports.insert(setup_module_export(*t1)?);
|
|
export_list = *t2;
|
|
}
|
|
|
|
if let Term::Literal(_, Literal::Atom(atom!("[]"))) = export_list {
|
|
Ok((module_src, exports))
|
|
} else {
|
|
Err(CompilationError::InvalidModuleDecl)
|
|
}
|
|
}
|
|
|
|
/*
|
|
* setup_meta_predicate tries to extract meta-predicate information
|
|
* from an appropriately formed declaration
|
|
*
|
|
* :- meta_predicate(maplist(:, ?, ?)).
|
|
*
|
|
* indicating that, for each QueryTerm call to maplist/3, the first
|
|
* argument is to be expanded with the call resolution ((:)/2)
|
|
* operator, the first argument of which is the name of the host
|
|
* module, as an atom. For example,
|
|
*
|
|
* p(X) :- maplist(X, [a,b,c], Result).
|
|
*
|
|
* If p/2 is defined in a module named "mod", the call is expanded to
|
|
*
|
|
* maplist(mod:X, [a,b,c], Result).
|
|
*
|
|
* before the predicate is compiled to WAM instructions.
|
|
*
|
|
* If the term bound to X -- the predicate to be called -- is
|
|
* qualified with (:)/2 already, the innermost qualifier is used for
|
|
* call resolution.
|
|
*
|
|
* The three arguments returned by a successful call are the module name,
|
|
* predicate name, and the list of meta-specs, one for each predicate argument.
|
|
*
|
|
* The module name might be used to specify intra-module meta-predicates whose
|
|
* module is not yet defined. There are several examples of this
|
|
* contained in src/lib/ops_and_meta_predicates.pl, which is loaded before
|
|
* src/lib/builtins.pl.
|
|
*
|
|
* Meta-specs have four forms:
|
|
*
|
|
* (:) (the argument should be expanded with (:)/2 as described above)
|
|
* + (mode declarations under the mode syntax, which currently have no effect)
|
|
* -
|
|
* ?
|
|
*/
|
|
|
|
fn setup_meta_predicate<'a, LS: LoadState<'a>>(
|
|
mut terms: Vec<Term>,
|
|
loader: &mut Loader<'a, LS>,
|
|
) -> Result<(Atom, Atom, Vec<MetaSpec>), CompilationError> {
|
|
fn get_name_and_meta_specs(
|
|
name: Atom,
|
|
terms: &mut [Term],
|
|
) -> Result<(Atom, Vec<MetaSpec>), CompilationError> {
|
|
let mut meta_specs = vec![];
|
|
|
|
for meta_spec in terms.iter_mut() {
|
|
match meta_spec {
|
|
Term::Literal(_, Literal::Atom(meta_spec)) => {
|
|
let meta_spec = match meta_spec {
|
|
atom!("+") => MetaSpec::Plus,
|
|
atom!("-") => MetaSpec::Minus,
|
|
atom!("?") => MetaSpec::Either,
|
|
atom!(":") => MetaSpec::Colon,
|
|
_ => return Err(CompilationError::InvalidMetaPredicateDecl),
|
|
};
|
|
|
|
meta_specs.push(meta_spec);
|
|
}
|
|
Term::Literal(_, Literal::Fixnum(n)) => match usize::try_from(n.get_num()) {
|
|
Ok(n) if n <= MAX_ARITY => {
|
|
meta_specs.push(MetaSpec::RequiresExpansionWithArgument(n));
|
|
}
|
|
_ => {
|
|
return Err(CompilationError::InvalidMetaPredicateDecl);
|
|
}
|
|
},
|
|
_ => {
|
|
return Err(CompilationError::InvalidMetaPredicateDecl);
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok((name, meta_specs))
|
|
}
|
|
|
|
match terms.pop().unwrap() {
|
|
Term::Clause(_, name, mut terms) if name == atom!(":") && terms.len() == 2 => {
|
|
let spec = terms.pop().unwrap();
|
|
let module_name = terms.pop().unwrap();
|
|
|
|
match module_name {
|
|
Term::Literal(_, Literal::Atom(module_name)) => match spec {
|
|
Term::Clause(_, name, mut terms) => {
|
|
let (name, meta_specs) = get_name_and_meta_specs(name, &mut terms)?;
|
|
Ok((module_name, name, meta_specs))
|
|
}
|
|
_ => Err(CompilationError::InvalidMetaPredicateDecl),
|
|
},
|
|
_ => Err(CompilationError::InvalidMetaPredicateDecl),
|
|
}
|
|
}
|
|
Term::Clause(_, name, mut terms) => {
|
|
let (name, meta_specs) = get_name_and_meta_specs(name, &mut terms)?;
|
|
Ok((
|
|
loader.payload.compilation_target.module_name(),
|
|
name,
|
|
meta_specs,
|
|
))
|
|
}
|
|
_ => Err(CompilationError::InvalidMetaPredicateDecl),
|
|
}
|
|
}
|
|
|
|
pub(super) fn setup_declaration<'a, LS: LoadState<'a>>(
|
|
loader: &mut Loader<'a, LS>,
|
|
mut terms: Vec<Term>,
|
|
) -> Result<Declaration, CompilationError> {
|
|
let term = terms.pop().unwrap();
|
|
|
|
match term {
|
|
Term::Clause(_, name, mut terms) => match (name, terms.len()) {
|
|
(atom!("dynamic"), 1) => {
|
|
let (name, arity) = setup_predicate_indicator(&mut terms.pop().unwrap())?;
|
|
Ok(Declaration::Dynamic(name, arity))
|
|
}
|
|
(atom!("module"), 2) => Ok(Declaration::Module(setup_module_decl(terms)?)),
|
|
(atom!("op"), 3) => Ok(Declaration::Op(setup_op_decl(terms)?)),
|
|
(atom!("non_counted_backtracking"), 1) => {
|
|
let (name, arity) = setup_predicate_indicator(&mut terms.pop().unwrap())?;
|
|
Ok(Declaration::NonCountedBacktracking(name, arity))
|
|
}
|
|
(atom!("use_module"), 1) => Ok(Declaration::UseModule(setup_use_module_decl(terms)?)),
|
|
(atom!("use_module"), 2) => {
|
|
let (name, exports) = setup_qualified_import(terms)?;
|
|
Ok(Declaration::UseQualifiedModule(name, exports))
|
|
}
|
|
(atom!("meta_predicate"), 1) => {
|
|
let (module_name, name, meta_specs) = setup_meta_predicate(terms, loader)?;
|
|
Ok(Declaration::MetaPredicate(module_name, name, meta_specs))
|
|
}
|
|
_ => Err(CompilationError::InvalidDirective(
|
|
DirectiveError::InvalidDirective(name, terms.len()),
|
|
)),
|
|
},
|
|
other => Err(CompilationError::InvalidDirective(
|
|
DirectiveError::ExpectedDirective(other),
|
|
)),
|
|
}
|
|
}
|
|
|
|
fn build_meta_predicate_clause<'a, LS: LoadState<'a>>(
|
|
loader: &mut Loader<'a, LS>,
|
|
module_name: Atom,
|
|
terms: Vec<Term>,
|
|
meta_specs: Vec<MetaSpec>,
|
|
) -> Vec<Term> {
|
|
let mut arg_terms = Vec::with_capacity(terms.len());
|
|
|
|
for (term, meta_spec) in terms.into_iter().zip(meta_specs.iter()) {
|
|
if let MetaSpec::RequiresExpansionWithArgument(supp_args) = meta_spec {
|
|
if let Some(name) = term.name() {
|
|
if name == atom!("$call") {
|
|
arg_terms.push(term);
|
|
continue;
|
|
}
|
|
|
|
let arity = term.arity();
|
|
|
|
fn get_qualified_name(
|
|
module_term: &Term,
|
|
qualified_term: &Term,
|
|
) -> Option<(Atom, Atom)> {
|
|
if let Term::Literal(_, Literal::Atom(module_name)) = module_term {
|
|
if let Some(name) = qualified_term.name() {
|
|
return Some((*module_name, name));
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn identity_fn(_module_name: Atom, term: Term) -> Term {
|
|
term
|
|
}
|
|
|
|
fn tag_with_module_name(module_name: Atom, term: Term) -> Term {
|
|
Term::Clause(
|
|
Cell::default(),
|
|
atom!(":"),
|
|
vec![
|
|
Term::Literal(Cell::default(), Literal::Atom(module_name)),
|
|
term,
|
|
],
|
|
)
|
|
}
|
|
|
|
let process_term: fn(Atom, Term) -> Term;
|
|
|
|
let (module_name, key, term) = match term {
|
|
Term::Clause(cell, atom!(":"), mut terms) if terms.len() == 2 => {
|
|
if let Some((module_name, name)) = get_qualified_name(&terms[0], &terms[1])
|
|
{
|
|
process_term = tag_with_module_name;
|
|
(
|
|
module_name,
|
|
(name, terms[1].arity() + supp_args),
|
|
terms.pop().unwrap(),
|
|
)
|
|
} else {
|
|
arg_terms.push(Term::Clause(cell, atom!(":"), terms));
|
|
continue;
|
|
}
|
|
}
|
|
term => {
|
|
process_term = identity_fn;
|
|
(module_name, (name, arity + supp_args), term)
|
|
}
|
|
};
|
|
|
|
let term = match term {
|
|
Term::Clause(cell, name, mut terms) => {
|
|
if let Some(Term::Literal(_, Literal::CodeIndexOffset(_))) = terms.last() {
|
|
arg_terms
|
|
.push(process_term(module_name, Term::Clause(cell, name, terms)));
|
|
|
|
continue;
|
|
}
|
|
|
|
let idx = loader.get_or_insert_qualified_code_index(module_name, key);
|
|
|
|
terms.push(Term::Literal(
|
|
Cell::default(),
|
|
Literal::CodeIndexOffset(idx.into()),
|
|
));
|
|
process_term(module_name, Term::Clause(cell, name, terms))
|
|
}
|
|
Term::Literal(cell, Literal::Atom(name)) => {
|
|
let idx = loader.get_or_insert_qualified_code_index(module_name, key);
|
|
|
|
process_term(
|
|
module_name,
|
|
Term::Clause(
|
|
cell,
|
|
name,
|
|
vec![Term::Literal(
|
|
Cell::default(),
|
|
Literal::CodeIndexOffset(idx.into()),
|
|
)],
|
|
),
|
|
)
|
|
}
|
|
term => term,
|
|
};
|
|
|
|
arg_terms.push(term);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
arg_terms.push(term);
|
|
}
|
|
|
|
arg_terms
|
|
}
|
|
|
|
#[inline]
|
|
pub(super) fn clause_to_query_term<'a, LS: LoadState<'a>>(
|
|
loader: &mut Loader<'a, LS>,
|
|
name: Atom,
|
|
mut terms: Vec<Term>,
|
|
call_policy: CallPolicy,
|
|
) -> QueryTerm {
|
|
if let Some(Term::Literal(_, Literal::CodeIndexOffset(_))) = terms.last() {
|
|
// supplementary code vector indices are unnecessary for
|
|
// root-level clauses.
|
|
terms.pop();
|
|
}
|
|
|
|
let mut ct = loader.get_clause_type(name, terms.len());
|
|
|
|
if let ClauseType::Named(arity, name, idx) = ct {
|
|
if let Some(meta_specs) = loader.get_meta_specs(name, arity).cloned() {
|
|
let module_name = loader.payload.compilation_target.module_name();
|
|
let terms = build_meta_predicate_clause(loader, module_name, terms, meta_specs);
|
|
|
|
return QueryTerm::Clause(
|
|
Cell::default(),
|
|
ClauseType::Named(arity, name, idx),
|
|
terms,
|
|
call_policy,
|
|
);
|
|
}
|
|
|
|
ct = ClauseType::Named(arity, name, idx);
|
|
}
|
|
|
|
QueryTerm::Clause(Cell::default(), ct, terms, call_policy)
|
|
}
|
|
|
|
#[inline]
|
|
pub(super) fn qualified_clause_to_query_term<'a, LS: LoadState<'a>>(
|
|
loader: &mut Loader<'a, LS>,
|
|
module_name: Atom,
|
|
name: Atom,
|
|
mut terms: Vec<Term>,
|
|
call_policy: CallPolicy,
|
|
) -> QueryTerm {
|
|
if let Some(Term::Literal(_, Literal::CodeIndexOffset(_))) = terms.last() {
|
|
// supplementary code vector indices are unnecessary for
|
|
// root-level clauses.
|
|
terms.pop();
|
|
}
|
|
|
|
let mut ct = loader.get_qualified_clause_type(module_name, name, terms.len());
|
|
|
|
if let ClauseType::Named(arity, name, idx) = ct {
|
|
if let Some(meta_specs) = loader.get_meta_specs(name, arity).cloned() {
|
|
let terms = build_meta_predicate_clause(loader, module_name, terms, meta_specs);
|
|
|
|
return QueryTerm::Clause(
|
|
Cell::default(),
|
|
ClauseType::Named(arity, name, idx),
|
|
terms,
|
|
call_policy,
|
|
);
|
|
}
|
|
|
|
ct = ClauseType::Named(arity, name, idx);
|
|
}
|
|
|
|
QueryTerm::Clause(Cell::default(), ct, terms, call_policy)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct Preprocessor {
|
|
settings: CodeGenSettings,
|
|
}
|
|
|
|
impl Preprocessor {
|
|
pub(super) fn new(settings: CodeGenSettings) -> Self {
|
|
Preprocessor { settings }
|
|
}
|
|
|
|
fn setup_fact(&mut self, term: Term) -> Result<(Fact, VarData), CompilationError> {
|
|
match term {
|
|
Term::Clause(..) | Term::Literal(_, Literal::Atom(..)) => {
|
|
let classifier = VariableClassifier::new(self.settings.default_call_policy());
|
|
|
|
let (head, var_data) = classifier.classify_fact(term)?;
|
|
Ok((Fact { head }, var_data))
|
|
}
|
|
_ => Err(CompilationError::InadmissibleFact),
|
|
}
|
|
}
|
|
|
|
fn setup_rule<'a, LS: LoadState<'a>>(
|
|
&mut self,
|
|
loader: &mut Loader<'a, LS>,
|
|
head: Term,
|
|
body: Term,
|
|
) -> Result<(Rule, VarData), CompilationError> {
|
|
let classifier = VariableClassifier::new(self.settings.default_call_policy());
|
|
|
|
let (head, clauses, var_data) = classifier.classify_rule(loader, head, body)?;
|
|
|
|
match head {
|
|
Term::Clause(_, name, terms) => Ok((
|
|
Rule {
|
|
head: (name, terms),
|
|
clauses,
|
|
},
|
|
var_data,
|
|
)),
|
|
Term::Literal(_, Literal::Atom(name)) => Ok((
|
|
Rule {
|
|
head: (name, vec![]),
|
|
clauses,
|
|
},
|
|
var_data,
|
|
)),
|
|
_ => Err(CompilationError::InvalidRuleHead),
|
|
}
|
|
}
|
|
|
|
pub(super) fn try_term_to_tl<'a, LS: LoadState<'a>>(
|
|
&mut self,
|
|
loader: &mut Loader<'a, LS>,
|
|
term: Term,
|
|
) -> Result<PredicateClause, CompilationError> {
|
|
match term {
|
|
Term::Clause(r, name, mut terms) => {
|
|
let is_rule = name == atom!(":-") && terms.len() == 2;
|
|
|
|
if is_rule {
|
|
let tail = terms.pop().unwrap();
|
|
let head = terms.pop().unwrap();
|
|
|
|
let (rule, var_data) = self.setup_rule(loader, head, tail)?;
|
|
Ok(PredicateClause::Rule(rule, var_data))
|
|
} else {
|
|
let term = Term::Clause(r, name, terms);
|
|
let (fact, var_data) = self.setup_fact(term)?;
|
|
Ok(PredicateClause::Fact(fact, var_data))
|
|
}
|
|
}
|
|
term => {
|
|
let (fact, var_data) = self.setup_fact(term)?;
|
|
Ok(PredicateClause::Fact(fact, var_data))
|
|
}
|
|
}
|
|
}
|
|
}
|