aufbau 0.3.1

Generalized prefix parsing for a class of context-dependent languages
use crate::debug_trace;
use crate::engine::grammar::SPG;
use crate::engine::parse::TypedParser;
use crate::engine::structure::ast::FusionAST;
use crate::semantics::TypingRuntime;
use crate::typing::{Context, TypingDomain};

#[cfg(test)]
mod tests;

pub struct Synthesizer {
    spg: SPG,
    runtime: TypingRuntime,
    parser: TypedParser,

    input: String,
    ctx: Context,
    tree: Option<FusionAST>,
}

impl Synthesizer {
    pub fn new(spg: SPG, input: impl Into<String>) -> Self {
        let input = input.into();
        debug_trace!("synth", "new: input='{}'", input);
        let runtime = TypingRuntime::new(TypingDomain::default(), spg.clone());
        let parser = TypedParser::new(spg.clone(), runtime.clone());

        Self {
            spg,
            runtime,
            parser,
            ctx: Context::new(),
            input,
            tree: None,
        }
    }

    pub fn grammar(&self) -> &SPG {
        &self.spg
    }

    pub fn runtime(&self) -> &TypingRuntime {
        &self.runtime
    }

    pub fn ctx(&self) -> &Context {
        &self.ctx
    }

    pub fn with_ctx(&mut self, ctx: Context) {
        self.ctx = ctx;
        self.tree = None;
        let _ = self.ast();
    }

    pub fn input(&self) -> &str {
        &self.input
    }

    pub fn set_input(&mut self, input: impl Into<String>) {
        self.input = input.into();
        self.tree = None;
        let _ = self.ast();
    }

    pub fn ast(&mut self) -> Result<FusionAST, String> {
        if let Some(ast) = &self.tree {
            Ok(ast.clone())
        } else {
            let ctx_id = self.runtime.intern_context(self.ctx.clone());
            match self.parser.parse(&self.input, ctx_id) {
                Ok(ast) => {
                    debug_trace!("synth", "ast: input='{}' parsed successfully", self.input);
                    self.tree = Some(ast.clone());
                    Ok(ast)
                }
                Err(err) => {
                    debug_trace!("synth", "ast: input='{}' parse failed: {}", self.input, err);
                    Err(format!("Parse error: {err}"))
                }
            }
        }
    }

    pub fn parse_with(&mut self, ctx: &Context) -> Result<FusionAST, String> {
        self.with_ctx(ctx.clone());
        self.ast()
    }

    pub fn feed_with(&mut self, token: &str, ctx: &Context) -> Result<FusionAST, String> {
        self.with_ctx(ctx.clone());
        self.feed(token)
    }

    pub fn feed(&mut self, token: &str) -> Result<FusionAST, String> {
        debug_trace!("synth", "feed: input='{}' token='{}'", self.input, token);
        let extended = format!("{}{}", self.input, token);
        self.set_input(extended);
        self.ast()
    }

    #[must_use = "discarding try_feed result hides parse failures"]
    pub fn try_feed(&mut self, token: &str) -> Result<FusionAST, String> {
        debug_trace!("synth", "try: input='{}' token='{}'", self.input, token);
        let extended = format!("{}{}", self.input, token);
        let mut p = self.parser.fork();
        let ctx_id = self.runtime.intern_context(self.ctx.clone());
        match p.parse(&extended, ctx_id) {
            Ok(ast) => Ok(ast),
            Err(err) => Err(format!("try_feed failed: {err}")),
        }
    }
}