use pyo3::exceptions::{PyRuntimeError, PyValueError};
use pyo3::prelude::*;
use super::grammar::PyGrammar;
use super::parse::PyAst;
use crate::engine::grammar::SPG;
use crate::typing::{Context, Term, Type, TypingRule, TypingSynth};
#[pyclass(unsendable, name = "TypingRule")]
pub struct PyTypingRule {
inner: TypingRule,
}
#[pymethods]
impl PyTypingRule {
#[getter]
fn name(&self) -> &str {
&self.inner.name
}
fn premise_count(&self) -> usize {
self.inner.premises.len()
}
fn pretty(&self, indent: usize) -> String {
self.inner.pretty(indent)
}
fn bindings(&self) -> Vec<String> {
self.inner
.used_bindings()
.into_iter()
.map(|s| s.to_string())
.collect()
}
fn __repr__(&self) -> String {
format!("TypingRule('{}')", self.inner.name)
}
}
#[pyclass(unsendable, name = "Term")]
#[derive(Clone)]
pub struct PyTerm {
pub(crate) inner: Term,
}
#[pymethods]
impl PyTerm {
fn __repr__(&self) -> String {
format!("Term({})", self.inner)
}
fn __str__(&self) -> String {
self.inner.to_string()
}
fn label(&self) -> Option<String> {
match &self.inner {
Term::Con(l, _) => Some(l.clone()),
_ => None,
}
}
fn children(&self) -> Vec<PyTerm> {
match &self.inner {
Term::Con(_, kids) => kids.iter().cloned().map(|inner| PyTerm { inner }).collect(),
_ => vec![],
}
}
fn is_var(&self) -> bool {
matches!(self.inner, Term::Var(_))
}
fn is_leaf(&self) -> bool {
matches!(self.inner, Term::Leaf(_))
}
fn is_con(&self) -> bool {
matches!(self.inner, Term::Con(..))
}
fn is_ground(&self) -> bool {
self.inner.is_ground()
}
}
#[pyclass(unsendable, name = "Synthesizer")]
pub struct PySynthesizer {
synth: TypingSynth,
ctx: Context,
}
#[pymethods]
impl PySynthesizer {
#[new]
#[pyo3(signature = (spec_source, input = ""))]
fn new(spec_source: String, input: &str) -> PyResult<Self> {
let grammar = SPG::load(&spec_source)
.map_err(|e| PyValueError::new_err(format!("failed to load grammar: {e}")))?;
Ok(Self {
synth: TypingSynth::new(grammar, input),
ctx: Context::new(),
})
}
#[staticmethod]
#[pyo3(signature = (grammar, input = ""))]
fn from_grammar(grammar: &PyGrammar, input: &str) -> Self {
Self {
synth: TypingSynth::new(grammar.inner.clone(), input),
ctx: Context::new(),
}
}
fn set_input(&mut self, input: &str) {
self.synth.set_input(input);
}
fn input(&self) -> String {
self.synth.input().to_string()
}
fn parse(&mut self) -> PyResult<String> {
self.synth
.parse_with(&self.ctx)
.map(|ast| ast.to_string())
.map_err(PyRuntimeError::new_err)
}
fn feed(&mut self, token: &str) -> PyResult<String> {
self.synth
.feed_with(token, &self.ctx)
.map(|ast| ast.to_string())
.map_err(PyRuntimeError::new_err)
}
fn try_feed(&mut self, token: &str) -> PyResult<String> {
self.synth
.try_feed(token)
.map(|ast| ast.to_string())
.map_err(PyRuntimeError::new_err)
}
fn mask(&mut self, candidates: Vec<String>) -> Vec<bool> {
candidates
.iter()
.map(|t| self.synth.try_feed(t).is_ok())
.collect()
}
#[pyo3(signature = (expected = None))]
fn in_scope(&self, expected: Option<&str>) -> PyResult<Vec<String>> {
let g = self.synth.grammar();
let want = match expected {
Some(s) => Some(
Term::parse(g, s)
.map_err(|e| PyValueError::new_err(format!("invalid type '{s}': {e}")))?,
),
None => None,
};
let norm = crate::typing::loader::normalizer(g);
let mut names: Vec<String> = self
.ctx
.bindings
.iter()
.filter(|(_, ty)| match &want {
None => true,
Some(w) => {
let mut s = crate::typing::Subst::new();
crate::typing::unify_modulo(&norm, w, ty, &mut s, true)
}
})
.map(|(name, _)| name.clone())
.collect();
names.sort();
Ok(names)
}
fn status(&mut self) -> &'static str {
match self.synth.parse_with(&self.ctx) {
Ok(ast) if ast.is_complete() => "typed",
Ok(_) => "live",
Err(_) => "dead",
}
}
fn root_type(&mut self) -> Option<PyTerm> {
let ast = self.synth.parse_with(&self.ctx).ok()?;
let rt = self.synth.runtime().clone();
ast.roots()
.filter(crate::engine::structure::FusionNode::is_complete)
.find_map(|r| rt.evidence_of(r.evidence()))
.map(|inner| PyTerm { inner })
}
fn add_to_ctx(&mut self, name: &str, ty: &str) -> PyResult<()> {
let ty = Type::parse(self.synth.grammar(), ty)
.map_err(|e| PyValueError::new_err(format!("invalid type '{ty}': {e}")))?;
self.ctx.add(name.to_string(), ty);
Ok(())
}
fn clear_ctx(&mut self) {
self.ctx = Context::new();
}
fn is_complete(&mut self) -> bool {
match self.synth.parse_with(&self.ctx) {
Ok(ast) => ast.is_complete(),
Err(_) => false,
}
}
fn grammar(&self) -> PyGrammar {
PyGrammar {
inner: self.synth.grammar().clone(),
}
}
fn get_rule(&self, name: &str) -> Option<PyTypingRule> {
self.synth
.grammar()
.rules
.get(name)
.cloned()
.map(|inner| PyTypingRule { inner })
}
fn ast(&mut self) -> PyResult<PyAst> {
let fusion = self
.synth
.parse_with(&self.ctx)
.map_err(|e| PyRuntimeError::new_err(format!("parse error: {e}")))?;
let runtime = self.synth.runtime().clone();
Ok(PyAst::from_fusion(&fusion, runtime))
}
}