summaryrefslogtreecommitdiff
path: root/src/ast
diff options
context:
space:
mode:
Diffstat (limited to 'src/ast')
-rw-r--r--src/ast/convert.rs115
1 files changed, 105 insertions, 10 deletions
diff --git a/src/ast/convert.rs b/src/ast/convert.rs
index c27c75f..f828a16 100644
--- a/src/ast/convert.rs
+++ b/src/ast/convert.rs
@@ -1,8 +1,16 @@
+use std::borrow::Borrow;
+use std::collections::HashMap;
+use std::hash::Hash;
+use std::mem;
+use std::rc::Rc;
+
use super::Term;
-#[derive(Clone, Debug, Default)]
+#[derive(Debug, Default)]
pub struct Context {
- vars: Vec<String>,
+ bindings: Vec<String>,
+ variables: HashMap<String, Term>,
+ functions: HashMap<String, (Rc<dyn Convert>, Vec<String>)>,
}
impl Context {
@@ -19,6 +27,9 @@ impl Context {
Self::default()
}
+ /// # Errors
+ /// Returns [`None`] if `name.is_empty()` or if `name` is unbound.
+ ///
/// # Examples
/// ```
/// use chomp::ast::convert::Context;
@@ -36,10 +47,14 @@ impl Context {
///
/// assert_eq!(context.get("x"), None);
/// ```
- pub fn get<T: ?Sized + PartialEq<str>>(&self, name: &T) -> Option<usize> {
- let mut iter = self.vars.iter();
+ pub fn get_binding<T: ?Sized + PartialEq<str>>(&self, name: &T) -> Option<usize> {
+ let mut iter = self.bindings.iter();
let mut pos = 0;
+ if name == "" {
+ return None;
+ }
+
while let Some(var) = iter.next_back() {
if T::eq(&name, &var) {
return Some(pos);
@@ -51,6 +66,9 @@ impl Context {
None
}
+ /// # Panics
+ /// If `name.is_empty()`.
+ ///
/// # Examples
/// ```
/// use chomp::ast::convert::Context;
@@ -64,13 +82,90 @@ impl Context {
///
/// assert_eq!(context.get("x"), None);
/// ```
- pub fn push<F: FnOnce(&Self) -> T, T>(&self, var: String, f: F) -> T {
- let mut context = self.clone();
- context.vars.push(var);
- f(&context)
+ pub fn push_binding<F: FnOnce(&mut Self) -> T, T>(&mut self, name: String, f: F) -> T {
+ if name.is_empty() {
+ panic!()
+ }
+
+ self.bindings.push(name);
+ let res = f(self);
+ self.bindings.pop();
+ res
+ }
+
+ /// # Errors
+ /// If `name == "".to_owned().borrow()` or `name` is unbound.
+ pub fn get_variable<T: ?Sized + Hash + Eq>(&self, name: &T) -> Option<&Term>
+ where
+ String: Borrow<T>,
+ {
+ if name == "".to_owned().borrow() {
+ return None
+ }
+
+ self.variables.get(name)
+ }
+
+ /// # Panics
+ /// If any variable name is empty.
+ pub fn add_function(&mut self, name: String, source: Rc<dyn Convert>, variables: Vec<String>) {
+ if variables.iter().any(|s| s.is_empty()) {
+ panic!()
+ }
+
+ self.functions.insert(name, (source, variables));
+ }
+
+ /// This uses dynamic scope for bindings.
+ /// # Errors
+ /// If `name` is unbound or has been called with the wrong number of arguments.
+ pub fn call_function<I: IntoIterator<Item = Term>, T: ?Sized + Hash + Eq>(
+ &mut self,
+ name: &T,
+ args: I,
+ ) -> Option<Term>
+ where
+ String: Borrow<T>,
+ <I as IntoIterator>::IntoIter: ExactSizeIterator,
+ {
+ let (term, vars) = self.functions.get(name)?;
+ let args_iter = args.into_iter();
+
+ if vars.len() != args_iter.len() {
+ None
+ } else {
+ let mut old = Vec::new();
+ for (var, value) in vars.clone().into_iter().zip(args_iter) {
+ if let Some((old_name, old_value)) = self.variables.remove_entry(var.borrow()) {
+ let mut indices = Vec::new();
+
+ for (index, binding) in self.bindings.iter_mut().enumerate() {
+ if *binding == old_name {
+ indices.push((index, mem::take(binding)));
+ }
+ }
+
+ old.push((old_name, old_value, indices))
+ }
+
+ self.variables.insert(var, value);
+ }
+
+ let res = Some(term.clone().convert(self));
+
+ for (name, value, indices) in old {
+ for (index, binding) in indices {
+ self.bindings[index] = binding
+ }
+
+ self.variables.insert(name, value);
+ }
+
+ res
+ }
}
}
-pub trait Convert {
- fn convert(self, context: &Context) -> Term;
+pub trait Convert: std::fmt::Debug {
+ fn convert(&self, context: &mut Context) -> Term;
}