mod atoms;
mod body;
mod clause;
mod facts;
mod lower;
mod predicate;
mod program;
mod term_emit;
pub use program::codegen_program;
use plg_frontend::{CgClause, SourceMap};
use plg_shared::{AtomId, Span, StringInterner};
use std::collections::{BTreeMap, HashMap};
pub const NO_SITE: u32 = u32::MAX;
pub struct CgSource {
pub path: String,
pub text: String,
}
#[derive(Clone, Copy, PartialEq)]
pub enum GoalTarget {
Defined,
DynamicFail,
Undefined,
}
pub struct CodeGen<'a> {
pub interner: &'a StringInterner,
pub predicates: BTreeMap<(AtomId, u32), Vec<CgClause>>,
pub dynamic_only: Vec<(AtomId, u32)>,
pub out: String,
tmp: u32,
label: u32,
sources: &'a [CgSource],
srcmaps: Vec<SourceMap<'a>>,
srcmap: Vec<(u32, u32, u32)>,
site_cache: HashMap<(u32, u32, u32), u32>,
files: Vec<String>,
}
impl<'a> CodeGen<'a> {
pub fn new(interner: &'a StringInterner, sources: &'a [CgSource]) -> Self {
CodeGen {
interner,
predicates: BTreeMap::new(),
dynamic_only: Vec::new(),
out: String::new(),
tmp: 0,
label: 0,
sources,
srcmaps: sources.iter().map(|s| SourceMap::new(&s.text)).collect(),
srcmap: Vec::new(),
site_cache: HashMap::new(),
files: Vec::new(),
}
}
pub fn site_id(&mut self, span: Span) -> u32 {
let fid = span.file as usize;
if fid >= self.srcmaps.len() {
return NO_SITE;
}
let (line, col) = self.srcmaps[fid].line_col(span.lo);
let path = self.sources[fid].path.clone();
let file_idx = match self.files.iter().position(|p| *p == path) {
Some(i) => i as u32,
None => {
self.files.push(path);
(self.files.len() - 1) as u32
}
};
let key = (file_idx, line as u32, col as u32);
if let Some(&id) = self.site_cache.get(&key) {
return id; }
let id = self.srcmap.len() as u32;
self.srcmap.push(key);
self.site_cache.insert(key, id);
id
}
pub fn fresh(&mut self) -> String {
self.tmp += 1;
format!("%t{}", self.tmp)
}
pub fn fresh_label(&mut self) -> String {
self.label += 1;
format!("L{}", self.label)
}
pub fn reset_temps(&mut self) {
self.tmp = 0;
self.label = 0;
}
pub fn pred_symbol(&self, functor: AtomId, arity: u32) -> String {
format!(
"plg_pred_{functor}_{arity}__{}",
sanitize(self.interner.resolve(functor))
)
}
pub fn how_to_call(&self, functor: AtomId, arity: u32) -> GoalTarget {
if self.predicates.contains_key(&(functor, arity)) {
GoalTarget::Defined
} else if self.dynamic_only.contains(&(functor, arity)) {
GoalTarget::DynamicFail
} else {
GoalTarget::Undefined
}
}
}
pub fn sanitize(name: &str) -> String {
let mut out = String::new();
for c in name.chars().take(24) {
if c.is_ascii_alphanumeric() || c == '_' {
out.push(c);
} else {
out.push_str(&format!("x{:02x}", c as u32 & 0xff));
}
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sanitize_escapes_symbols() {
assert_eq!(sanitize("foo_bar9"), "foo_bar9");
assert_eq!(sanitize("=.."), "x3dx2ex2e");
}
#[test]
fn no_site_sentinel_value_is_pinned() {
assert_eq!(super::NO_SITE, u32::MAX);
}
}