use super::{Deserialize, Serialize};
use crate::node::{self, Node};
use std::{fmt, str::FromStr};
use steel::{
parser::{ast::ExprKind, lexer::TokenStream},
steel_vm::engine::Engine,
};
use thiserror::Error;
#[derive(Clone, Debug, Eq, Hash, PartialEq, Deserialize, Serialize)]
pub struct Expr {
src: String,
n_inputs: usize,
n_outputs: usize,
}
#[derive(Debug, Error)]
pub enum ExprError {
#[error("failed to parse a valid expr: {err}")]
InvalidExpr {
#[from]
err: steel::rerrs::SteelErr,
},
#[error("parsed result contains no expression")]
Empty,
}
impl Expr {
pub fn new(src: impl Into<String>) -> Result<Self, ExprError> {
let src: String = src.into();
let skip_comments = true;
let source_id = None;
let tts = TokenStream::new(&src, skip_comments, source_id);
let n_inputs = count_dollars(tts);
let exprs = Engine::emit_ast(&src)?;
let n_outputs = exprs.len();
Ok(Expr {
src,
n_inputs,
n_outputs,
})
}
pub fn src(&self) -> &str {
&self.src
}
}
fn count_dollars(tts: TokenStream) -> usize {
tts.filter(|token| token.source().starts_with("$")).count()
}
fn interpolate_tokens(tts: TokenStream, inputs: &[Option<String>]) -> String {
let mut inputs = inputs.iter();
let tokens = tts.map(move |token| {
let mut tts = vec![];
let in_src;
if token.source().starts_with("$") {
let input = inputs.next().unwrap();
match input.as_ref() {
None => {
tts.extend(TokenStream::new("'()", true, None));
}
Some(in_expr) => {
in_src = format!("{in_expr}");
let in_tts = TokenStream::new(&in_src, true, None);
tts.extend(in_tts);
}
}
} else {
tts.push(token);
}
tts.iter()
.map(|t| format!("{}", t.source()))
.collect::<Vec<_>>()
.join(" ")
});
tokens.collect::<Vec<_>>().join(" ")
}
impl Node for Expr {
fn n_inputs(&self) -> usize {
self.n_inputs
}
fn n_outputs(&self) -> usize {
self.n_outputs
}
fn expr(&self, ctx: node::ExprCtx) -> ExprKind {
let skip_comments = true;
let source_id = None;
let tts = TokenStream::new(&self.src, skip_comments, source_id);
let new_src = interpolate_tokens(tts, ctx.inputs());
let exprs = Engine::emit_ast(&new_src).expect("failed to emit AST");
if exprs.len() == 1 {
exprs.into_iter().next().unwrap()
} else {
let exprs = exprs
.iter()
.map(|expr| format!("{expr}"))
.collect::<Vec<_>>()
.join(" ");
let out_src = format!("(begin {})", exprs);
Engine::emit_ast(&out_src)
.expect("failed to emit AST")
.into_iter()
.next()
.unwrap()
}
}
fn stateful(&self) -> bool {
self.src().contains("state")
}
fn register(&self, path: &[super::Id], vm: &mut Engine) {
node::state::update_value(vm, path, steel::SteelVal::Void).unwrap();
}
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.src)
}
}
impl FromStr for Expr {
type Err = ExprError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}
#[test]
fn test_count_dollars() {
let expr = TokenStream::new("(+ $l $r)", true, None);
assert_eq!(count_dollars(expr), 2);
let expr = TokenStream::new("(* (sin $freq) $amp)", true, None);
assert_eq!(count_dollars(expr), 2);
let expr = TokenStream::new("$foo", true, None);
assert_eq!(count_dollars(expr), 1);
let expr = TokenStream::new("($a, $b, $c, $d, $e)", true, None);
assert_eq!(count_dollars(expr), 5);
let expr = TokenStream::new("()", true, None);
assert_eq!(count_dollars(expr), 0);
}