893 lines
32 KiB
Rust
893 lines
32 KiB
Rust
use preserves_schema::Codec;
|
|
|
|
use std::io;
|
|
use std::borrow::Cow;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use syndicate::actor::*;
|
|
use syndicate::dataspace::Dataspace;
|
|
use syndicate::during;
|
|
use syndicate::enclose;
|
|
use syndicate::pattern::{lift_literal, drop_literal};
|
|
use syndicate::schemas::dataspace;
|
|
use syndicate::schemas::dataspace_patterns as P;
|
|
use syndicate::schemas::sturdy;
|
|
use syndicate::value::Map;
|
|
use syndicate::value::NestedValue;
|
|
use syndicate::value::NoEmbeddedDomainCodec;
|
|
use syndicate::value::Record;
|
|
use syndicate::value::Set;
|
|
use syndicate::value::TextWriter;
|
|
use syndicate::value::Value;
|
|
|
|
use crate::language::language;
|
|
|
|
#[derive(Debug)]
|
|
struct PatternInstantiator<'env> {
|
|
env: &'env Env,
|
|
binding_names: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct Env {
|
|
pub path: PathBuf,
|
|
bindings: Map<String, AnyValue>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Parser<'t> {
|
|
tokens: &'t [AnyValue],
|
|
errors: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum Parsed<T> {
|
|
Value(T),
|
|
Skip,
|
|
Eof,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Instruction {
|
|
Assert {
|
|
target: String,
|
|
template: AnyValue,
|
|
},
|
|
Message {
|
|
target: String,
|
|
template: AnyValue,
|
|
},
|
|
During {
|
|
target: String,
|
|
pattern_template: AnyValue,
|
|
body: Box<Instruction>,
|
|
},
|
|
OnMessage {
|
|
target: String,
|
|
pattern_template: AnyValue,
|
|
body: Box<Instruction>,
|
|
},
|
|
OnStop {
|
|
body: Box<Instruction>,
|
|
},
|
|
Sequence {
|
|
instructions: Vec<Instruction>,
|
|
},
|
|
Let {
|
|
pattern_template: AnyValue,
|
|
expr: Expr,
|
|
},
|
|
Cond {
|
|
value_var: String,
|
|
pattern_template: AnyValue,
|
|
on_match: Box<Instruction>,
|
|
on_nomatch: Box<Instruction>,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub enum Expr {
|
|
Template {
|
|
template: AnyValue,
|
|
},
|
|
Dataspace,
|
|
Timestamp,
|
|
Facet,
|
|
Stringify {
|
|
expr: Box<Expr>,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum RewriteTemplate {
|
|
Filter {
|
|
pattern_template: AnyValue,
|
|
},
|
|
Rewrite {
|
|
pattern_template: AnyValue,
|
|
template_template: AnyValue,
|
|
},
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
enum Symbolic {
|
|
Reference(String),
|
|
Binder(String),
|
|
Discard,
|
|
Literal(String),
|
|
Bare(String),
|
|
}
|
|
|
|
struct FacetHandle;
|
|
|
|
impl<T> Default for Parsed<T> {
|
|
fn default() -> Self {
|
|
Parsed::Skip
|
|
}
|
|
}
|
|
|
|
impl FacetHandle {
|
|
fn new() -> Self {
|
|
FacetHandle
|
|
}
|
|
}
|
|
|
|
impl Entity<AnyValue> for FacetHandle {
|
|
fn message(&mut self, t: &mut Activation, body: AnyValue) -> ActorResult {
|
|
if let Some("stop") = body.value().as_symbol().map(|s| s.as_str()) {
|
|
t.stop();
|
|
return Ok(())
|
|
}
|
|
tracing::warn!(?body, "Unrecognised message sent to FacetHandle");
|
|
return Ok(())
|
|
}
|
|
}
|
|
|
|
fn analyze(s: &str) -> Symbolic {
|
|
if s == "_" {
|
|
Symbolic::Discard
|
|
} else if s.starts_with("?") {
|
|
Symbolic::Binder(s[1..].to_owned())
|
|
} else if s.starts_with("$") {
|
|
Symbolic::Reference(s[1..].to_owned())
|
|
} else if s.starts_with("=") {
|
|
Symbolic::Literal(s[1..].to_owned())
|
|
} else {
|
|
Symbolic::Bare(s.to_owned())
|
|
}
|
|
}
|
|
|
|
fn bad_instruction(message: &str) -> io::Error {
|
|
io::Error::new(io::ErrorKind::InvalidData, message)
|
|
}
|
|
|
|
fn discard() -> P::Pattern {
|
|
P::Pattern::DDiscard(Box::new(P::DDiscard))
|
|
}
|
|
|
|
fn dlit(value: AnyValue) -> P::Pattern {
|
|
lift_literal(&value)
|
|
}
|
|
|
|
fn tlit(value: AnyValue) -> sturdy::Template {
|
|
sturdy::Template::Lit(Box::new(sturdy::Lit { value }))
|
|
}
|
|
|
|
fn parse_attenuation(r: &Record<AnyValue>) -> io::Result<Option<(String, Vec<RewriteTemplate>)>> {
|
|
if r.label() != &AnyValue::symbol("*") {
|
|
return Ok(None);
|
|
}
|
|
|
|
if r.fields().len() != 2 {
|
|
Err(bad_instruction(&format!(
|
|
"Attenuation requires a reference and a sequence of rewrites; got {:?}",
|
|
r)))?;
|
|
}
|
|
|
|
let base_name = match r.fields()[0].value().as_symbol().map(|s| analyze(&s)) {
|
|
Some(Symbolic::Reference(s)) => s,
|
|
_ => Err(bad_instruction(&format!(
|
|
"Attenuation must have variable reference as first argument; got {:?}",
|
|
r.fields()[0])))?,
|
|
};
|
|
|
|
let raw_alternatives = match r.fields()[1].value().as_sequence() {
|
|
None => Err(bad_instruction(&format!(
|
|
"Attenuation of {:?} must have sequence of rewrites; got {:?}",
|
|
r.fields()[0],
|
|
r.fields()[1])))?,
|
|
Some(vs) => vs,
|
|
};
|
|
|
|
let mut alternatives = Vec::new();
|
|
|
|
for e in raw_alternatives.iter() {
|
|
match e.value().as_simple_record("filter", Some(1)) {
|
|
Some(fields) =>
|
|
alternatives.push(RewriteTemplate::Filter {
|
|
pattern_template: fields[0].clone()
|
|
}),
|
|
None => match e.value().as_simple_record("rewrite", Some(2)) {
|
|
Some(fields) =>
|
|
alternatives.push(RewriteTemplate::Rewrite {
|
|
pattern_template: fields[0].clone(),
|
|
template_template: fields[1].clone(),
|
|
}),
|
|
None => Err(bad_instruction(&format!(
|
|
"Bad rewrite in attenuation of {:?}: {:?}",
|
|
r.fields()[0],
|
|
e)))?,
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(Some((base_name, alternatives)))
|
|
}
|
|
|
|
impl<'env> PatternInstantiator<'env> {
|
|
fn instantiate_pattern(&mut self, template: &AnyValue) -> io::Result<P::Pattern> {
|
|
Ok(match template.value() {
|
|
Value::Boolean(_) |
|
|
Value::Float(_) |
|
|
Value::Double(_) |
|
|
Value::SignedInteger(_) |
|
|
Value::String(_) |
|
|
Value::ByteString(_) |
|
|
Value::Embedded(_) =>
|
|
dlit(template.clone()),
|
|
|
|
Value::Symbol(s) => match analyze(s) {
|
|
Symbolic::Discard => discard(),
|
|
Symbolic::Binder(s) => {
|
|
self.binding_names.push(s);
|
|
P::Pattern::DBind(Box::new(P::DBind { pattern: discard() }))
|
|
}
|
|
Symbolic::Reference(s) =>
|
|
dlit(self.env.lookup(&s, "pattern-template variable")?.clone()),
|
|
Symbolic::Literal(s) | Symbolic::Bare(s) =>
|
|
dlit(Value::Symbol(s).wrap()),
|
|
},
|
|
|
|
Value::Record(r) => match parse_attenuation(r)? {
|
|
Some((base_name, alternatives)) =>
|
|
dlit(self.env.eval_attenuation(base_name, alternatives)?),
|
|
None => match self.maybe_binder_with_pattern(r)? {
|
|
Some(pat) => pat,
|
|
None => {
|
|
let label = self.instantiate_pattern(r.label())?;
|
|
let fields = r.fields().iter().map(|p| self.instantiate_pattern(p))
|
|
.collect::<io::Result<Vec<P::Pattern>>>()?;
|
|
P::Pattern::DCompound(Box::new(P::DCompound::Rec {
|
|
label: drop_literal(&label)
|
|
.ok_or(bad_instruction("Record pattern must have literal label"))?,
|
|
fields,
|
|
}))
|
|
}
|
|
}
|
|
},
|
|
Value::Sequence(v) =>
|
|
P::Pattern::DCompound(Box::new(P::DCompound::Arr {
|
|
items: v.iter()
|
|
.map(|p| self.instantiate_pattern(p))
|
|
.collect::<io::Result<Vec<P::Pattern>>>()?,
|
|
})),
|
|
Value::Set(_) =>
|
|
Err(bad_instruction(&format!("Sets not permitted in patterns: {:?}", template)))?,
|
|
Value::Dictionary(v) =>
|
|
P::Pattern::DCompound(Box::new(P::DCompound::Dict {
|
|
entries: v.iter()
|
|
.map(|(a, b)| Ok((a.clone(), self.instantiate_pattern(b)?)))
|
|
.collect::<io::Result<Map<AnyValue, P::Pattern>>>()?,
|
|
})),
|
|
})
|
|
}
|
|
|
|
fn maybe_binder_with_pattern(&mut self, r: &Record<AnyValue>) -> io::Result<Option<P::Pattern>> {
|
|
match r.label().value().as_symbol().map(|s| analyze(&s)) {
|
|
Some(Symbolic::Binder(formal)) => if r.fields().len() == 1 {
|
|
let pattern = self.instantiate_pattern(&r.fields()[0])?;
|
|
self.binding_names.push(formal);
|
|
return Ok(Some(P::Pattern::DBind(Box::new(P::DBind { pattern }))));
|
|
},
|
|
_ => (),
|
|
}
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
impl Env {
|
|
pub fn new(path: PathBuf, bindings: Map<String, AnyValue>) -> Self {
|
|
Env {
|
|
path: path.clone(),
|
|
bindings,
|
|
}
|
|
}
|
|
|
|
pub fn clone_with_path(&self, path: PathBuf) -> Self {
|
|
Env {
|
|
path,
|
|
bindings: self.bindings.clone(),
|
|
}
|
|
}
|
|
|
|
fn lookup_target(&self, s: &str) -> io::Result<Arc<Cap>> {
|
|
Ok(self.lookup(s, "target variable")?.value().to_embedded()?.clone())
|
|
}
|
|
|
|
fn lookup(&self, s: &str, what: &'static str) -> io::Result<AnyValue> {
|
|
if s == "." {
|
|
Ok(AnyValue::new(self.bindings.iter().map(|(k, v)| (AnyValue::symbol(k), v.clone()))
|
|
.collect::<Map<AnyValue, AnyValue>>()))
|
|
} else {
|
|
Ok(self.bindings.get(s).ok_or_else(
|
|
|| bad_instruction(&format!("Undefined {}: {:?}", what, s)))?.clone())
|
|
}
|
|
}
|
|
|
|
fn instantiate_pattern(
|
|
&self,
|
|
pattern_template: &AnyValue,
|
|
) -> io::Result<(Vec<String>, P::Pattern)> {
|
|
let mut inst = PatternInstantiator {
|
|
env: self,
|
|
binding_names: Vec::new(),
|
|
};
|
|
let pattern = inst.instantiate_pattern(pattern_template)?;
|
|
Ok((inst.binding_names, pattern))
|
|
}
|
|
|
|
fn instantiate_value(&self, template: &AnyValue) -> io::Result<AnyValue> {
|
|
Ok(match template.value() {
|
|
Value::Boolean(_) |
|
|
Value::Float(_) |
|
|
Value::Double(_) |
|
|
Value::SignedInteger(_) |
|
|
Value::String(_) |
|
|
Value::ByteString(_) |
|
|
Value::Embedded(_) =>
|
|
template.clone(),
|
|
|
|
Value::Symbol(s) => match analyze(s) {
|
|
Symbolic::Binder(_) | Symbolic::Discard =>
|
|
Err(bad_instruction(&format!(
|
|
"Invalid use of wildcard in template: {:?}", template)))?,
|
|
Symbolic::Reference(s) =>
|
|
self.lookup(&s, "template variable")?,
|
|
Symbolic::Literal(s) | Symbolic::Bare(s) =>
|
|
Value::Symbol(s).wrap(),
|
|
},
|
|
|
|
Value::Record(r) => match parse_attenuation(r)? {
|
|
Some((base_name, alternatives)) =>
|
|
self.eval_attenuation(base_name, alternatives)?,
|
|
None =>
|
|
Value::Record(Record(r.fields_vec().iter().map(|a| self.instantiate_value(a))
|
|
.collect::<Result<Vec<_>, _>>()?)).wrap(),
|
|
},
|
|
Value::Sequence(v) =>
|
|
Value::Sequence(v.iter().map(|a| self.instantiate_value(a))
|
|
.collect::<Result<Vec<_>, _>>()?).wrap(),
|
|
Value::Set(v) =>
|
|
Value::Set(v.iter().map(|a| self.instantiate_value(a))
|
|
.collect::<Result<Set<_>, _>>()?).wrap(),
|
|
Value::Dictionary(v) =>
|
|
Value::Dictionary(v.iter().map(|(a,b)| Ok((self.instantiate_value(a)?,
|
|
self.instantiate_value(b)?)))
|
|
.collect::<io::Result<Map<_, _>>>()?).wrap(),
|
|
})
|
|
}
|
|
|
|
pub fn safe_eval(&mut self, t: &mut Activation, i: &Instruction) -> bool {
|
|
match self.eval(t, i) {
|
|
Ok(()) => true,
|
|
Err(error) => {
|
|
tracing::error!(path = ?self.path, ?error);
|
|
t.stop();
|
|
false
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn extend(&mut self, binding_names: &Vec<String>, captures: Vec<AnyValue>) {
|
|
for (k, v) in binding_names.iter().zip(captures) {
|
|
self.bindings.insert(k.clone(), v);
|
|
}
|
|
}
|
|
|
|
fn eval_attenuation(
|
|
&self,
|
|
base_name: String,
|
|
alternatives: Vec<RewriteTemplate>,
|
|
) -> io::Result<AnyValue> {
|
|
let base_value = self.lookup(&base_name, "attenuation-base variable")?;
|
|
match base_value.value().as_embedded() {
|
|
None => Err(bad_instruction(&format!(
|
|
"Value to be attenuated is {:?} but must be capability",
|
|
base_value))),
|
|
Some(base_cap) => {
|
|
match base_cap.attenuate(&sturdy::Attenuation(vec![
|
|
self.instantiate_caveat(&alternatives)?]))
|
|
{
|
|
Ok(derived_cap) => Ok(AnyValue::domain(derived_cap)),
|
|
Err(caveat_error) =>
|
|
Err(bad_instruction(&format!("Attenuation of {:?} failed: {:?}",
|
|
base_value,
|
|
caveat_error))),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn bind_and_run(
|
|
&self,
|
|
t: &mut Activation,
|
|
binding_names: &Vec<String>,
|
|
captures: AnyValue,
|
|
body: &Instruction,
|
|
) -> ActorResult {
|
|
if let Some(captures) = captures.value_owned().into_sequence() {
|
|
let mut env = self.clone();
|
|
env.extend(binding_names, captures);
|
|
env.safe_eval(t, body);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn eval(&mut self, t: &mut Activation, i: &Instruction) -> io::Result<()> {
|
|
match i {
|
|
Instruction::Assert { target, template } => {
|
|
self.lookup_target(target)?.assert(t, &(), &self.instantiate_value(template)?);
|
|
}
|
|
Instruction::Message { target, template } => {
|
|
self.lookup_target(target)?.message(t, &(), &self.instantiate_value(template)?);
|
|
}
|
|
Instruction::During { target, pattern_template, body } => {
|
|
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
|
|
let observer = during::entity(self.clone())
|
|
.on_asserted_facet(enclose!((binding_names, body) move |env, t, cs: AnyValue| {
|
|
env.bind_and_run(t, &binding_names, cs, &*body) }))
|
|
.create_cap(t);
|
|
self.lookup_target(target)?.assert(t, language(), &dataspace::Observe {
|
|
pattern,
|
|
observer,
|
|
});
|
|
}
|
|
Instruction::OnMessage { target, pattern_template, body } => {
|
|
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
|
|
let observer = during::entity(self.clone())
|
|
.on_message(enclose!((binding_names, body) move |env, t, cs: AnyValue| {
|
|
t.facet(|t| env.bind_and_run(t, &binding_names, cs, &*body))?;
|
|
Ok(())
|
|
}))
|
|
.create_cap(t);
|
|
self.lookup_target(target)?.assert(t, language(), &dataspace::Observe {
|
|
pattern,
|
|
observer,
|
|
});
|
|
}
|
|
Instruction::OnStop { body } => {
|
|
let mut env = self.clone();
|
|
t.on_stop(enclose!((body) move |t| Ok(env.eval(t, &*body)?)));
|
|
}
|
|
Instruction::Sequence { instructions } => {
|
|
for i in instructions {
|
|
self.eval(t, i)?;
|
|
}
|
|
}
|
|
Instruction::Let { pattern_template, expr } => {
|
|
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
|
|
let value = self.eval_expr(t, expr)?;
|
|
match pattern.match_value(&value) {
|
|
None => Err(bad_instruction(
|
|
&format!("Could not match pattern {:?} with value {:?}",
|
|
pattern_template,
|
|
value)))?,
|
|
Some(captures) => {
|
|
self.extend(&binding_names, captures);
|
|
}
|
|
}
|
|
}
|
|
Instruction::Cond { value_var, pattern_template, on_match, on_nomatch } => {
|
|
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
|
|
let value = self.lookup(value_var, "value in conditional expression")?;
|
|
match pattern.match_value(&value) {
|
|
None => self.eval(t, on_nomatch)?,
|
|
Some(captures) => {
|
|
self.extend(&binding_names, captures);
|
|
self.eval(t, on_match)?
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub fn eval_expr(&self, t: &mut Activation, e: &Expr) -> io::Result<AnyValue> {
|
|
match e {
|
|
Expr::Template { template } => self.instantiate_value(template),
|
|
Expr::Dataspace => Ok(AnyValue::domain(Cap::new(&t.create(Dataspace::new(None))))),
|
|
Expr::Timestamp => Ok(AnyValue::new(chrono::Utc::now().to_rfc3339())),
|
|
Expr::Facet => Ok(AnyValue::domain(Cap::new(&t.create(FacetHandle::new())))),
|
|
Expr::Stringify { expr } => {
|
|
let v = self.eval_expr(t, expr)?;
|
|
let s = TextWriter::encode(&mut NoEmbeddedDomainCodec, &v)?;
|
|
Ok(AnyValue::new(s))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn instantiate_caveat(
|
|
&self,
|
|
alternatives: &Vec<RewriteTemplate>,
|
|
) -> io::Result<sturdy::Caveat> {
|
|
let mut rewrites = Vec::new();
|
|
for rw in alternatives {
|
|
match rw {
|
|
RewriteTemplate::Filter { pattern_template } => {
|
|
let (_binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
|
|
rewrites.push(sturdy::Rewrite {
|
|
pattern: embed_pattern(&P::Pattern::DBind(Box::new(P::DBind { pattern }))),
|
|
template: sturdy::Template::TRef(Box::new(sturdy::TRef { binding: 0.into() })),
|
|
})
|
|
}
|
|
RewriteTemplate::Rewrite { pattern_template, template_template } => {
|
|
let (binding_names, pattern) = self.instantiate_pattern(pattern_template)?;
|
|
rewrites.push(sturdy::Rewrite {
|
|
pattern: embed_pattern(&pattern),
|
|
template: self.instantiate_template(&binding_names, template_template)?,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
if rewrites.len() == 1 {
|
|
Ok(sturdy::Caveat::Rewrite(Box::new(rewrites.pop().unwrap())))
|
|
} else {
|
|
Ok(sturdy::Caveat::Alts(Box::new(sturdy::Alts {
|
|
alternatives: rewrites,
|
|
})))
|
|
}
|
|
}
|
|
|
|
fn instantiate_template(
|
|
&self,
|
|
binding_names: &Vec<String>,
|
|
template: &AnyValue,
|
|
) -> io::Result<sturdy::Template> {
|
|
let find_bound = |s: &str| {
|
|
binding_names.iter().enumerate().find(|(_i, n)| *n == s).map(|(i, _n)| i)
|
|
};
|
|
|
|
Ok(match template.value() {
|
|
Value::Boolean(_) |
|
|
Value::Float(_) |
|
|
Value::Double(_) |
|
|
Value::SignedInteger(_) |
|
|
Value::String(_) |
|
|
Value::ByteString(_) |
|
|
Value::Embedded(_) =>
|
|
tlit(template.clone()),
|
|
|
|
Value::Symbol(s) => match analyze(s) {
|
|
Symbolic::Binder(_) | Symbolic::Discard =>
|
|
Err(bad_instruction(&format!(
|
|
"Invalid use of wildcard in template: {:?}", template)))?,
|
|
Symbolic::Reference(s) =>
|
|
match find_bound(&s) {
|
|
Some(i) =>
|
|
sturdy::Template::TRef(Box::new(sturdy::TRef { binding: i.into() })),
|
|
None =>
|
|
tlit(self.lookup(&s, "attenuation-template variable")?),
|
|
},
|
|
Symbolic::Literal(s) | Symbolic::Bare(s) =>
|
|
tlit(Value::Symbol(s).wrap()),
|
|
},
|
|
|
|
Value::Record(r) => match parse_attenuation(r)? {
|
|
Some((base_name, alternatives)) =>
|
|
match find_bound(&base_name) {
|
|
Some(i) =>
|
|
sturdy::Template::TAttenuate(Box::new(sturdy::TAttenuate {
|
|
template: sturdy::Template::TRef(Box::new(sturdy::TRef {
|
|
binding: i.into(),
|
|
})),
|
|
attenuation: sturdy::Attenuation(vec![
|
|
self.instantiate_caveat(&alternatives)?]),
|
|
})),
|
|
None =>
|
|
tlit(self.eval_attenuation(base_name, alternatives)?),
|
|
},
|
|
None => {
|
|
// TODO: properly consolidate constant templates into literals.
|
|
match self.instantiate_template(binding_names, r.label())? {
|
|
sturdy::Template::Lit(b) =>
|
|
sturdy::Template::TCompound(Box::new(sturdy::TCompound::Rec {
|
|
label: b.value,
|
|
fields: r.fields().iter()
|
|
.map(|t| self.instantiate_template(binding_names, t))
|
|
.collect::<io::Result<Vec<sturdy::Template>>>()?,
|
|
})),
|
|
_ => Err(bad_instruction("Record template must have literal label"))?,
|
|
}
|
|
}
|
|
},
|
|
Value::Sequence(v) =>
|
|
sturdy::Template::TCompound(Box::new(sturdy::TCompound::Arr {
|
|
items: v.iter()
|
|
.map(|p| self.instantiate_template(binding_names, p))
|
|
.collect::<io::Result<Vec<sturdy::Template>>>()?,
|
|
})),
|
|
Value::Set(_) =>
|
|
Err(bad_instruction(&format!("Sets not permitted in templates: {:?}", template)))?,
|
|
Value::Dictionary(v) =>
|
|
sturdy::Template::TCompound(Box::new(sturdy::TCompound::Dict {
|
|
entries: v.iter()
|
|
.map(|(a, b)| Ok((a.clone(), self.instantiate_template(binding_names, b)?)))
|
|
.collect::<io::Result<Map<_, sturdy::Template>>>()?,
|
|
})),
|
|
})
|
|
}
|
|
}
|
|
|
|
fn embed_pattern(p: &P::Pattern) -> sturdy::Pattern {
|
|
match p {
|
|
P::Pattern::DDiscard(_) => sturdy::Pattern::PDiscard(Box::new(sturdy::PDiscard)),
|
|
P::Pattern::DBind(b) => sturdy::Pattern::PBind(Box::new(sturdy::PBind {
|
|
pattern: embed_pattern(&b.pattern),
|
|
})),
|
|
P::Pattern::DLit(b) => sturdy::Pattern::Lit(Box::new(sturdy::Lit {
|
|
value: language().unparse(&b.value),
|
|
})),
|
|
P::Pattern::DCompound(b) => sturdy::Pattern::PCompound(Box::new(match &**b {
|
|
P::DCompound::Rec { label, fields } =>
|
|
sturdy::PCompound::Rec {
|
|
label: label.clone(),
|
|
fields: fields.iter().map(embed_pattern).collect(),
|
|
},
|
|
P::DCompound::Arr { items } =>
|
|
sturdy::PCompound::Arr {
|
|
items: items.iter().map(embed_pattern).collect(),
|
|
},
|
|
P::DCompound::Dict { entries } =>
|
|
sturdy::PCompound::Dict {
|
|
entries: entries.iter().map(|(k, v)| (k.clone(), embed_pattern(v))).collect(),
|
|
},
|
|
})),
|
|
}
|
|
}
|
|
|
|
impl<'t> Parser<'t> {
|
|
pub fn new(tokens: &'t [AnyValue]) -> Self {
|
|
Parser {
|
|
tokens,
|
|
errors: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn peek(&mut self) -> &'t Value<AnyValue> {
|
|
self.tokens[0].value()
|
|
}
|
|
|
|
fn shift(&mut self) -> AnyValue {
|
|
let v = self.tokens[0].clone();
|
|
self.drop();
|
|
v
|
|
}
|
|
|
|
fn drop(&mut self) {
|
|
self.tokens = &self.tokens[1..];
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
self.tokens.len()
|
|
}
|
|
|
|
fn ateof(&self) -> bool {
|
|
self.len() == 0
|
|
}
|
|
|
|
fn error<'a, T: Default, E: Into<Cow<'a, str>>>(&mut self, message: E) -> T {
|
|
self.errors.push(message.into().into_owned());
|
|
T::default()
|
|
}
|
|
|
|
pub fn parse(&mut self, target: &str, outer_target: &str) -> Parsed<Instruction> {
|
|
if self.ateof() {
|
|
return Parsed::Eof;
|
|
}
|
|
|
|
if self.peek().is_record() || self.peek().is_dictionary() {
|
|
return Parsed::Value(Instruction::Assert {
|
|
target: target.to_owned(),
|
|
template: self.shift(),
|
|
});
|
|
}
|
|
|
|
if let Some(tokens) = self.peek().as_sequence() {
|
|
self.drop();
|
|
let mut inner_parser = Parser::new(tokens);
|
|
let instructions = inner_parser.parse_all(target, outer_target);
|
|
self.errors.extend(inner_parser.errors);
|
|
return Parsed::Value(Instruction::Sequence { instructions });
|
|
}
|
|
|
|
if let Some(s) = self.peek().as_symbol() {
|
|
match analyze(s) {
|
|
Symbolic::Binder(s) => {
|
|
self.drop();
|
|
|
|
let ctor = match s.as_ref() {
|
|
"" => |target, pattern_template, body| { // "?"
|
|
Instruction::During { target, pattern_template, body } },
|
|
"?" => |target, pattern_template, body| { // "??"
|
|
Instruction::OnMessage { target, pattern_template, body } },
|
|
"-" => match self.parse(target, outer_target) { // "?-"
|
|
Parsed::Value(i) => return Parsed::Value(Instruction::OnStop {
|
|
body: Box::new(i),
|
|
}),
|
|
other => return other,
|
|
},
|
|
_ => return self.error(format!(
|
|
"Invalid use of pattern binder in target: ?{}", s)),
|
|
};
|
|
|
|
if self.ateof() {
|
|
return self.error("Missing pattern and instruction in react");
|
|
}
|
|
let pattern_template = self.shift();
|
|
|
|
return match self.parse(target, outer_target) {
|
|
Parsed::Eof =>
|
|
self.error(format!(
|
|
"Missing instruction in react with pattern {:?}",
|
|
pattern_template)),
|
|
Parsed::Skip =>
|
|
Parsed::Skip,
|
|
Parsed::Value(body) =>
|
|
Parsed::Value(ctor(target.to_owned(),
|
|
pattern_template,
|
|
Box::new(body))),
|
|
};
|
|
}
|
|
Symbolic::Discard => {
|
|
self.drop();
|
|
let m = format!("Invalid use of discard in target: {:?}", self.peek());
|
|
return self.error(m);
|
|
},
|
|
Symbolic::Reference(s) => {
|
|
self.drop();
|
|
if self.ateof() {
|
|
let m = format!("Missing instruction after retarget: {:?}", self.peek());
|
|
return self.error(m);
|
|
}
|
|
return self.parse(&s, target);
|
|
}
|
|
Symbolic::Bare(s) => {
|
|
if s == "let" {
|
|
self.drop();
|
|
if self.len() >= 2 && self.tokens[1].value().as_symbol().map(String::as_str) == Some("=")
|
|
{
|
|
let pattern_template = self.shift();
|
|
self.drop();
|
|
return match self.parse_expr() {
|
|
Some(expr) =>
|
|
Parsed::Value(Instruction::Let { pattern_template, expr }),
|
|
None => Parsed::Skip,
|
|
};
|
|
} else {
|
|
return self.error("Invalid let statement");
|
|
}
|
|
} else if s == "!" {
|
|
self.drop();
|
|
if self.ateof() {
|
|
return self.error("Missing payload after '!'");
|
|
}
|
|
return Parsed::Value(Instruction::Message {
|
|
target: target.to_owned(),
|
|
template: self.shift(),
|
|
});
|
|
} else if s == "+=" {
|
|
self.drop();
|
|
if self.ateof() {
|
|
return self.error("Missing payload after '+='");
|
|
}
|
|
return Parsed::Value(Instruction::Assert {
|
|
target: target.to_owned(),
|
|
template: self.shift(),
|
|
});
|
|
} else {
|
|
/* fall through */
|
|
}
|
|
}
|
|
Symbolic::Literal(s) => {
|
|
if s == "~" { // "=~"
|
|
self.drop();
|
|
if self.ateof() {
|
|
return self.error("Missing pattern, true-instruction and false-continuation in match");
|
|
}
|
|
let match_template = self.shift();
|
|
return match self.parse(outer_target, outer_target) {
|
|
Parsed::Eof =>
|
|
self.error(format!(
|
|
"Missing true-instruction in conditional with pattern {:?}",
|
|
match_template)),
|
|
Parsed::Skip =>
|
|
Parsed::Skip,
|
|
Parsed::Value(true_instruction) => {
|
|
let false_instructions = self.parse_all(outer_target, outer_target);
|
|
Parsed::Value(Instruction::Cond {
|
|
value_var: target.to_owned(),
|
|
pattern_template: match_template,
|
|
on_match: Box::new(true_instruction),
|
|
on_nomatch: Box::new(Instruction::Sequence {
|
|
instructions: false_instructions,
|
|
}),
|
|
})
|
|
}
|
|
};
|
|
} else {
|
|
/* fall through */
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
let m = format!("Invalid token: {:?}", self.shift());
|
|
return self.error(m);
|
|
}
|
|
}
|
|
|
|
pub fn parse_all(&mut self, target: &str, outer_target: &str) -> Vec<Instruction> {
|
|
let mut instructions = Vec::new();
|
|
loop {
|
|
match self.parse(target, outer_target) {
|
|
Parsed::Value(i) => instructions.push(i),
|
|
Parsed::Skip => (),
|
|
Parsed::Eof => break,
|
|
}
|
|
}
|
|
instructions
|
|
}
|
|
|
|
pub fn parse_top(&mut self, target: &str) -> Result<Option<Instruction>, Vec<String>> {
|
|
let instructions = self.parse_all(target, target);
|
|
if self.errors.is_empty() {
|
|
match instructions.len() {
|
|
0 => Ok(None),
|
|
_ => Ok(Some(Instruction::Sequence { instructions })),
|
|
}
|
|
} else {
|
|
Err(std::mem::take(&mut self.errors))
|
|
}
|
|
}
|
|
|
|
pub fn parse_expr(&mut self) -> Option<Expr> {
|
|
if self.ateof() {
|
|
return None;
|
|
}
|
|
|
|
if self.peek() == &Value::symbol("dataspace") {
|
|
self.drop();
|
|
return Some(Expr::Dataspace);
|
|
}
|
|
|
|
if self.peek() == &Value::symbol("timestamp") {
|
|
self.drop();
|
|
return Some(Expr::Timestamp);
|
|
}
|
|
|
|
if self.peek() == &Value::symbol("facet") {
|
|
self.drop();
|
|
return Some(Expr::Facet);
|
|
}
|
|
|
|
if self.peek() == &Value::symbol("stringify") {
|
|
self.drop();
|
|
return Some(Expr::Stringify { expr: Box::new(self.parse_expr()?) });
|
|
}
|
|
|
|
return Some(Expr::Template{ template: self.shift() });
|
|
}
|
|
}
|