adapt code generation

This commit is contained in:
Mark Thom
2022-10-17 22:56:08 -06:00
committed by Mark
parent b9c9de5222
commit e41d1b319b
8 changed files with 700 additions and 427 deletions

View File

@@ -2,7 +2,7 @@ use crate::atom_table::*;
use crate::codegen::CodeGenSettings;
use crate::forms::*;
use crate::instructions::*;
use crate::iterators::*;
use crate::machine::disjuncts::*;
use crate::machine::loader::*;
use crate::machine::machine_errors::*;
use crate::parser::ast::*;
@@ -13,21 +13,6 @@ use std::cell::Cell;
use std::collections::VecDeque;
use std::convert::TryFrom;
/*
* The preprocessor fabricates if-then-else ( .. -> ... ; ...)
* clauses into nameless standalone predicates, which it queues for
* later preprocessing and compilation. Fabricated predicates inherit
* explicit "cut variables" from the handwritten predicate
* surrounding their source if-then-else. They must be specially
* handled.
*/
#[derive(Clone, Copy, Debug)]
pub(crate) enum CutContext {
BlocksCuts,
HasCutVariable,
}
pub(crate) fn fold_by_str<I>(terms: I, mut term: Term, sym: Atom) -> Term
where
I: DoubleEndedIterator<Item = Term>,
@@ -131,6 +116,13 @@ fn setup_module_export(
})
}
pub(crate) fn build_rule_body(vars: &[Term], body_term: Term) -> Term {
let head_term = Term::Clause(Cell::default(), atom!(""), vars.iter().cloned().collect());
let rule = vec![head_term, body_term];
Term::Clause(Cell::default(), atom!(":-"), rule)
}
pub(super) fn setup_module_export_list(
mut export_list: Term,
atom_tbl: &mut AtomTable,
@@ -324,110 +316,6 @@ fn setup_meta_predicate<'a, LS: LoadState<'a>>(
}
}
fn merge_clauses(tls: &mut VecDeque<TopLevel>) -> Result<TopLevel, CompilationError> {
let mut clauses = vec![];
while let Some(tl) = tls.pop_front() {
match tl {
TopLevel::Query(_) if clauses.is_empty() && tls.is_empty() => {
return Ok(tl);
}
TopLevel::Query(_) => {
return Err(CompilationError::InconsistentEntry);
}
TopLevel::Fact(fact) => {
let clause = PredicateClause::Fact(fact);
clauses.push(clause);
}
TopLevel::Rule(rule) => {
let clause = PredicateClause::Rule(rule);
clauses.push(clause);
}
TopLevel::Predicate(predicate) => clauses.extend(predicate.into_iter()),
}
}
if clauses.is_empty() {
Err(CompilationError::InconsistentEntry)
} else {
Ok(TopLevel::Predicate(clauses))
}
}
fn mark_cut_variables_as(terms: &mut Vec<Term>, name: Atom) {
for term in terms.iter_mut() {
match term {
&mut Term::Literal(_, Literal::Atom(ref mut var)) if *var == atom!("!") => {
*var = name;
}
_ => {}
}
}
}
fn mark_cut_variable(term: &mut Term) -> bool {
let cut_var_found = match term {
&mut Term::Literal(_, Literal::Atom(ref var)) if *var == atom!("!") => true,
_ => false,
};
if cut_var_found {
*term = Term::Var(Cell::default(), Var::from("!"));
true
} else {
false
}
}
fn mark_cut_variables(terms: &mut Vec<Term>) -> bool {
let mut found_cut_var = false;
for item in terms.iter_mut() {
found_cut_var = mark_cut_variable(item) || found_cut_var;
}
found_cut_var
}
// terms is a list of goals composing one clause in a (;) functor. it
// checks that the first (and only) of these clauses is a ->. if so,
// it expands its terms using a blocked_!.
fn check_for_internal_if_then(terms: &mut Vec<Term>) {
if terms.len() != 1 {
return;
}
if let Some(Term::Clause(_, name, ref subterms)) = terms.last() {
if *name != atom!("->") || source_arity(subterms) != 2 {
return;
}
} else {
return;
}
if let Some(Term::Clause(_, _, mut subterms)) = terms.pop() {
let mut conq_terms = VecDeque::from(unfold_by_str(subterms.pop().unwrap(), atom!(",")));
let mut pre_cut_terms = VecDeque::from(unfold_by_str(subterms.pop().unwrap(), atom!(",")));
conq_terms.push_front(Term::Literal(
Cell::default(),
Literal::Atom(atom!("blocked_!")),
));
while let Some(term) = pre_cut_terms.pop_back() {
conq_terms.push_front(term);
}
let tail_term = conq_terms.pop_back().unwrap();
terms.push(fold_by_str(
conq_terms.into_iter(),
tail_term,
atom!(","),
));
}
}
pub(super) fn setup_declaration<'a, LS: LoadState<'a>>(
loader: &mut Loader<'a, LS>,
mut terms: Vec<Term>,
@@ -569,7 +457,7 @@ fn build_meta_predicate_clause<'a, LS: LoadState<'a>>(
}
#[inline]
fn clause_to_query_term<'a, LS: LoadState<'a>>(
pub(super) fn clause_to_query_term<'a, LS: LoadState<'a>>(
loader: &mut Loader<'a, LS>,
name: Atom,
mut terms: Vec<Term>,
@@ -608,7 +496,7 @@ fn clause_to_query_term<'a, LS: LoadState<'a>>(
}
#[inline]
fn qualified_clause_to_query_term<'a, LS: LoadState<'a>>(
pub(super) fn qualified_clause_to_query_term<'a, LS: LoadState<'a>>(
loader: &mut Loader<'a, LS>,
module_name: Atom,
name: Atom,
@@ -646,308 +534,65 @@ fn qualified_clause_to_query_term<'a, LS: LoadState<'a>>(
QueryTerm::Clause(Cell::default(), ct, terms, call_policy)
}
fn compute_head(term: &Term) -> Vec<Term> {
let mut vars = IndexSet::new();
for term in post_order_iter(term) {
if let TermRef::Var(_, _, v) = term {
vars.insert(v.clone());
}
}
vars.insert(Var::from("!"));
vars.into_iter()
.map(|v| Term::Var(Cell::default(), v))
.collect()
}
pub(crate) fn build_rule_body(vars: &[Term], body_term: Term) -> Term {
let head_term = Term::Clause(Cell::default(), atom!(""), vars.iter().cloned().collect());
let rule = vec![head_term, body_term];
Term::Clause(Cell::default(), atom!(":-"), rule)
}
// the terms form the body of the rule. We create a head, by
// gathering variables from the body of terms and recording them
// in the head clause.
fn build_rule(body_term: Term) -> (JumpStub, VecDeque<Term>) {
// collect the vars of body_term into a head, return the num_vars
// (the arity) as well.
let vars = compute_head(&body_term);
let rule = build_rule_body(&vars, body_term);
(vars, VecDeque::from(vec![rule]))
}
fn build_disjunct(body_term: Term) -> (JumpStub, VecDeque<Term>) {
let vars = compute_head(&body_term);
let results = unfold_by_str(body_term, atom!(";"))
.into_iter()
.map(|term| {
let mut subterms = unfold_by_str(term, atom!(","));
mark_cut_variables(&mut subterms);
check_for_internal_if_then(&mut subterms);
let term = subterms.pop().unwrap();
let clause = fold_by_str(subterms.into_iter(), term, atom!(","));
build_rule_body(&vars, clause)
})
.collect();
(vars, results)
}
fn build_if_then(prec: Term, conq: Term) -> (JumpStub, VecDeque<Term>) {
let mut prec_seq = unfold_by_str(prec, atom!(","));
let comma_sym = atom!(",");
let cut_sym = Literal::Atom(atom!("!"));
prec_seq.push(Term::Literal(Cell::default(), cut_sym));
mark_cut_variables_as(&mut prec_seq, atom!("blocked_!"));
let mut conq_seq = unfold_by_str(conq, atom!(","));
mark_cut_variables(&mut conq_seq);
prec_seq.extend(conq_seq.into_iter());
let back_term = prec_seq.pop().unwrap();
let front_term = prec_seq.pop().unwrap();
let body_term = Term::Clause(
Cell::default(),
comma_sym,
vec![front_term, back_term],
);
build_rule(fold_by_str(prec_seq.into_iter(), body_term, comma_sym))
}
#[derive(Debug)]
pub(crate) struct Preprocessor {
queue: VecDeque<VecDeque<Term>>,
settings: CodeGenSettings,
}
impl Preprocessor {
pub(super) fn new(settings: CodeGenSettings) -> Self {
Preprocessor {
queue: VecDeque::new(),
settings,
}
}
fn setup_fact(&mut self, term: Term) -> Result<Term, CompilationError> {
fn setup_fact(&mut self, term: Term) -> Result<Fact, CompilationError> {
match term {
Term::Clause(..) | Term::Literal(_, Literal::Atom(..)) => Ok(term),
Term::Clause(..) | Term::Literal(_, Literal::Atom(..)) => {
let mut classifier = VariableClassifier::new(
self.settings.default_call_policy(),
);
let (head, var_records) = classifier.classify_fact(term)?;
Ok(Fact { head, var_records })
}
_ => Err(CompilationError::InadmissibleFact),
}
}
fn to_query_term<'a, LS: LoadState<'a>>(
&mut self,
loader: &mut Loader<'a, LS>,
term: Term,
) -> Result<QueryTerm, CompilationError> {
match term {
Term::Literal(_, Literal::Atom(name)) => {
if name == atom!("!") || name == atom!("blocked_!") {
Ok(QueryTerm::BlockedCut)
} else {
Ok(clause_to_query_term(
loader,
name,
vec![],
self.settings.default_call_policy(),
))
}
}
Term::Literal(_, Literal::Char('!')) => Ok(QueryTerm::BlockedCut),
Term::Var(_, ref v) if v.as_str() == Some("!") => {
Ok(QueryTerm::UnblockedCut(Cell::default()))
}
Term::Clause(r, name, mut terms) => match (name, source_arity(&terms)) {
(atom!(";"), 2) => {
let term = Term::Clause(r, name, terms);
let (stub, clauses) = build_disjunct(term);
self.queue.push_back(clauses);
Ok(QueryTerm::Jump(stub))
}
(atom!("->"), 2) => {
let conq = terms.pop().unwrap();
let prec = terms.pop().unwrap();
let (stub, clauses) = build_if_then(prec, conq);
self.queue.push_back(clauses);
Ok(QueryTerm::Jump(stub))
}
(atom!("\\+"), 1) => {
terms.push(Term::Literal(
Cell::default(),
Literal::Atom(atom!("$fail")),
));
let conq = Term::Literal(Cell::default(), Literal::Atom(atom!("true")));
let prec = Term::Clause(Cell::default(), atom!("->"), terms);
let terms = vec![prec, conq];
let term = Term::Clause(Cell::default(), atom!(";"), terms);
let (stub, clauses) = build_disjunct(term);
debug_assert!(clauses.len() > 0);
self.queue.push_back(clauses);
Ok(QueryTerm::Jump(stub))
}
(atom!("$get_level"), 1) => {
if let Term::Var(_, ref var) = &terms[0] {
Ok(QueryTerm::GetLevelAndUnify(Cell::default(), var.clone()))
} else {
Err(CompilationError::InadmissibleQueryTerm)
}
}
(atom!(":"), 2) => {
let predicate_name = terms.pop().unwrap();
let module_name = terms.pop().unwrap();
match (module_name, predicate_name) {
(
Term::Literal(_, Literal::Atom(module_name)),
Term::Literal(_, Literal::Atom(predicate_name)),
) => Ok(qualified_clause_to_query_term(
loader,
module_name,
predicate_name,
vec![],
self.settings.default_call_policy(),
)),
(
Term::Literal(_, Literal::Atom(module_name)),
Term::Clause(_, name, terms),
) => Ok(qualified_clause_to_query_term(
loader,
module_name,
name,
terms,
self.settings.default_call_policy()
)),
(module_name, predicate_name) => {
terms.push(module_name);
terms.push(predicate_name);
Ok(clause_to_query_term(
loader,
atom!("call"),
vec![Term::Clause(r, name, terms)],
self.settings.default_call_policy(),
))
}
}
}
_ => Ok(clause_to_query_term(loader, name, terms,
self.settings.default_call_policy())),
},
Term::Var(..) => Ok(QueryTerm::Clause(
Cell::default(),
ClauseType::CallN(1),
vec![term],
self.settings.default_call_policy(),
)),
_ => Err(CompilationError::InadmissibleQueryTerm),
}
}
fn pre_query_term<'a, LS: LoadState<'a>>(
&mut self,
loader: &mut Loader<'a, LS>,
term: Term,
) -> Result<QueryTerm, CompilationError> {
match term {
Term::Clause(r, name, mut subterms) => {
if subterms.len() == 1 && name == atom!("$call_with_inference_counting") {
self.to_query_term(loader, subterms.pop().unwrap())
.map(|mut query_term| {
query_term.set_call_policy(CallPolicy::Counted);
query_term
})
} else {
let clause = Term::Clause(r, name, subterms);
self.to_query_term(loader, clause)
}
}
_ => self.to_query_term(loader, term),
}
}
fn setup_query<'a, LS: LoadState<'a>>(
&mut self,
loader: &mut Loader<'a, LS>,
terms: Vec<Term>,
cut_context: CutContext,
) -> Result<Vec<QueryTerm>, CompilationError> {
let mut query_terms = vec![];
let mut work_queue = VecDeque::from(terms);
while let Some(term) = work_queue.pop_front() {
let mut term = term;
if let Term::Clause(cell, name, terms) = term {
if name == atom!(",") && source_arity(&terms) == 2 {
let term = Term::Clause(cell, name, terms);
let mut subterms = unfold_by_str(term, atom!(","));
while let Some(subterm) = subterms.pop() {
work_queue.push_front(subterm);
}
continue;
} else {
term = Term::Clause(cell, name, terms);
}
}
if let CutContext::HasCutVariable = cut_context {
mark_cut_variable(&mut term);
}
query_terms.push(self.pre_query_term(loader, term)?);
}
Ok(query_terms)
}
fn setup_rule<'a, LS: LoadState<'a>>(
&mut self,
loader: &mut Loader<'a, LS>,
mut terms: Vec<Term>,
cut_context: CutContext,
head: Term,
body: Term,
) -> Result<Rule, CompilationError> {
let post_head_terms: Vec<_> = terms.drain(1..).collect();
let mut query_terms = self.setup_query(loader, post_head_terms, cut_context)?;
let mut classifier = VariableClassifier::new(
self.settings.default_call_policy(),
);
let (head, mut query_terms, var_records) =
classifier.classify_rule(loader, head, body)?;
let clauses = query_terms.drain(1..).collect();
let qt = query_terms.pop().unwrap();
match terms.pop().unwrap() {
match head {
Term::Clause(_, name, terms) => Ok(Rule {
head: (name, terms, qt),
clauses,
var_records,
}),
Term::Literal(_, Literal::Atom(name)) => Ok(Rule {
head: (name, vec![], qt),
clauses,
var_records,
}),
_ => Err(CompilationError::InvalidRuleHead),
}
}
/*
fn try_term_to_query<'a, LS: LoadState<'a>>(
&mut self,
loader: &mut Loader<'a, LS>,
@@ -960,23 +605,19 @@ impl Preprocessor {
cut_context,
)?))
}
*/
pub(super) fn try_term_to_tl<'a, LS: LoadState<'a>>(
&mut self,
loader: &mut Loader<'a, LS>,
term: Term,
cut_context: CutContext,
) -> Result<TopLevel, CompilationError> {
match term {
Term::Clause(r, name, terms) => {
if name == atom!("?-") {
self.try_term_to_query(loader, terms, cut_context)
} else if name == atom!(":-") && terms.len() == 2 {
Ok(TopLevel::Rule(self.setup_rule(
loader,
terms,
cut_context,
)?))
let is_rule = name == atom!(":-") && terms.len() == 2;
if is_rule {
Ok(TopLevel::Rule(self.setup_rule(loader, terms[0], terms[1])?))
} else {
let term = Term::Clause(r, name, terms);
Ok(TopLevel::Fact(self.setup_fact(term)?))
@@ -990,33 +631,13 @@ impl Preprocessor {
&mut self,
loader: &mut Loader<'a, LS>,
terms: I,
cut_context: CutContext,
) -> Result<VecDeque<TopLevel>, CompilationError> {
let mut results = VecDeque::new();
for term in terms.into_iter() {
results.push_back(self.try_term_to_tl(loader, term, cut_context)?);
results.push_back(self.try_term_to_tl(loader, term)?);
}
Ok(results)
}
pub(super) fn parse_queue<'a, LS: LoadState<'a>>(
&mut self,
loader: &mut Loader<'a, LS>,
) -> Result<VecDeque<TopLevel>, CompilationError> {
let mut queue = VecDeque::new();
while let Some(terms) = self.queue.pop_front() {
let clauses = merge_clauses(&mut self.try_terms_to_tls(
loader,
terms,
CutContext::HasCutVariable,
)?)?;
queue.push_back(clauses);
}
Ok(queue)
}
}