|
@@ -19,25 +19,25 @@ use std::rc::Rc;
|
|
|
pub enum Value {
|
|
|
Lit(Literal),
|
|
|
Tup(Vec<Thunk>),
|
|
|
- Builtin(&'static BuiltinFunc),
|
|
|
+ Builtin(BuiltinRef),
|
|
|
Closure(Closure),
|
|
|
Nil,
|
|
|
}
|
|
|
|
|
|
+#[derive(Debug, Clone, Copy)]
|
|
|
+pub struct BuiltinRef {
|
|
|
+ idx: usize,
|
|
|
+}
|
|
|
+
|
|
|
impl Value {
|
|
|
fn to_string(&self, ast: &ASTArena) -> String {
|
|
|
self.with_str(ast, |s| s.to_string())
|
|
|
}
|
|
|
}
|
|
|
-// impl fmt::Display for Value {
|
|
|
-// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
-// self.with_str(|s| write!(f, "{}", s))
|
|
|
-// }
|
|
|
-// }
|
|
|
|
|
|
impl Value {
|
|
|
/// Convert this value to a Rust integer, failing otherwise
|
|
|
- fn as_num(&self, ast: &ASTArena) -> Result<i64, Error> {
|
|
|
+ pub fn as_num(&self, ast: &ASTArena) -> Result<i64, Error> {
|
|
|
match self {
|
|
|
Value::Lit(Literal::Num(n)) => Ok(*n),
|
|
|
_ => self.with_str(ast, |s| bail!("Expected number, got {}", s)),
|
|
@@ -45,7 +45,7 @@ impl Value {
|
|
|
}
|
|
|
|
|
|
/// Convert this value to a Rust string, failing otherwise
|
|
|
- fn as_str(&self, ast: &ASTArena) -> Result<&str, Error> {
|
|
|
+ pub fn as_str(&self, ast: &ASTArena) -> Result<&str, Error> {
|
|
|
match self {
|
|
|
Value::Lit(Literal::Str(s)) => Ok(s),
|
|
|
_ => self.with_str(ast, |s| bail!("Expected string, got {}", s)),
|
|
@@ -53,7 +53,7 @@ impl Value {
|
|
|
}
|
|
|
|
|
|
/// Convert this value to a Rust slice, failing otherwise
|
|
|
- fn as_tup(&self, ast: &ASTArena) -> Result<&[Thunk], Error> {
|
|
|
+ pub fn as_tup(&self, ast: &ASTArena) -> Result<&[Thunk], Error> {
|
|
|
match self {
|
|
|
Value::Tup(vals) => Ok(vals),
|
|
|
_ => self.with_str(ast, |s| bail!("Expected tuple, got {}", s)),
|
|
@@ -61,7 +61,7 @@ impl Value {
|
|
|
}
|
|
|
|
|
|
/// Convert this value to a closure, failing otherwise
|
|
|
- fn as_closure(&self, ast: &ASTArena) -> Result<&Closure, Error> {
|
|
|
+ pub fn as_closure(&self, ast: &ASTArena) -> Result<&Closure, Error> {
|
|
|
match self {
|
|
|
Value::Closure(closure) => Ok(closure),
|
|
|
_ => self.with_str(ast, |s| bail!("Expected tuple, got {}", s)),
|
|
@@ -73,7 +73,7 @@ impl Value {
|
|
|
/// not completely forced already: indeed, this can't, since it
|
|
|
/// doesn't have access to the `State`. Unevaluated fragments of
|
|
|
/// the value will be printed as `#<unevaluated>`.
|
|
|
- fn with_str<U>(&self, ast: &ASTArena, f: impl FnOnce(&str) -> U) -> U {
|
|
|
+ pub fn with_str<U>(&self, ast: &ASTArena, f: impl FnOnce(&str) -> U) -> U {
|
|
|
match self {
|
|
|
Value::Nil => f(""),
|
|
|
Value::Lit(Literal::Str(s)) => f(s),
|
|
@@ -89,13 +89,13 @@ impl Value {
|
|
|
match val {
|
|
|
Thunk::Value(v) => buf.push_str(&v.to_string(ast)),
|
|
|
Thunk::Expr(..) => buf.push_str("#<unevaluated>"),
|
|
|
- Thunk::Builtin(func) => buf.push_str(&format!("#<builtin {}>", func.name)),
|
|
|
+ Thunk::Builtin(func) => buf.push_str(&format!("#<builtin {}>", func.idx)),
|
|
|
}
|
|
|
}
|
|
|
buf.push('>');
|
|
|
f(&buf)
|
|
|
}
|
|
|
- Value::Builtin(func) => f(&format!("#<builtin {}>", func.name)),
|
|
|
+ Value::Builtin(func) => f(&format!("#<builtin {}>", func.idx)),
|
|
|
Value::Closure(_) => f("#<lambda ...>"),
|
|
|
}
|
|
|
}
|
|
@@ -108,10 +108,10 @@ pub struct BuiltinFunc {
|
|
|
/// The name of the builtin: this is used in error messages, in
|
|
|
/// printing the value (e.g. in the case of `puts some-builtin`),
|
|
|
/// and as the Matzo identifier used for this function.
|
|
|
- name: &'static str,
|
|
|
+ pub name: &'static str,
|
|
|
/// The callback here is the Rust implementation of the function,
|
|
|
/// where the provided `ExprRef` is the argument to the function.
|
|
|
- callback: &'static dyn Fn(&State, ExprRef, &Env) -> Result<Value, Error>,
|
|
|
+ pub callback: Box<dyn Fn(&State, &[ExprRef], &Env) -> Result<Value, Error>>,
|
|
|
}
|
|
|
|
|
|
impl fmt::Debug for BuiltinFunc {
|
|
@@ -120,105 +120,6 @@ impl fmt::Debug for BuiltinFunc {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-/// The list of builtins provided at startup.
|
|
|
-///
|
|
|
-/// TODO: move this to a separate file and clean it up
|
|
|
-const BUILTINS: &[BuiltinFunc] = &[
|
|
|
- BuiltinFunc {
|
|
|
- name: "rep",
|
|
|
- callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
|
|
|
- let (rep, expr) = {
|
|
|
- let ast = state.ast.borrow();
|
|
|
- let args = match &ast[expr] {
|
|
|
- Expr::Tup(tup) => tup,
|
|
|
- _ => {
|
|
|
- let span = state.ast.borrow().get_line(expr.file, expr.span);
|
|
|
- bail!("`rep`: expected tuple\n{}", span)
|
|
|
- }
|
|
|
- };
|
|
|
- if args.len() != 2 {
|
|
|
- let span = state.ast.borrow().get_line(expr.file, expr.span);
|
|
|
- bail!("`rep`: expected two arguments, got {}\n{}", args.len(), span)
|
|
|
- }
|
|
|
- (args[0], args[1])
|
|
|
- };
|
|
|
- let mut buf = String::new();
|
|
|
- let num = state.eval(rep, env)?.as_num(&state.ast.borrow())?;
|
|
|
- for _ in 0..num {
|
|
|
- buf.push_str(&state.eval(expr, env)?.as_str(&state.ast.borrow())?.to_string());
|
|
|
- }
|
|
|
- Ok(Value::Lit(Literal::Str(buf)))
|
|
|
- },
|
|
|
- },
|
|
|
- BuiltinFunc {
|
|
|
- name: "length",
|
|
|
- callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
|
|
|
- let args = match state.eval(expr, env)? {
|
|
|
- Value::Tup(tup) => tup,
|
|
|
- _ => bail!("`length`: expected tuple"),
|
|
|
- };
|
|
|
- Ok(Value::Lit(Literal::Num(args.len() as i64)))
|
|
|
- },
|
|
|
- },
|
|
|
- BuiltinFunc {
|
|
|
- name: "to-upper",
|
|
|
- callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
|
|
|
- let s = state.eval(expr, env)?;
|
|
|
- Ok(Value::Lit(Literal::Str(s.as_str(&state.ast.borrow())?.to_uppercase())))
|
|
|
- },
|
|
|
- },
|
|
|
- BuiltinFunc {
|
|
|
- name: "capitalize",
|
|
|
- callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
|
|
|
- let s = state.eval(expr, env)?;
|
|
|
- Ok(Value::Lit(Literal::Str(titlecase::titlecase(s.as_str(&state.ast.borrow())?))))
|
|
|
-
|
|
|
- },
|
|
|
- },
|
|
|
- BuiltinFunc {
|
|
|
- name: "to-lower",
|
|
|
- callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
|
|
|
- let s = state.eval(expr, env)?;
|
|
|
- Ok(Value::Lit(Literal::Str(s.as_str(&state.ast.borrow())?.to_lowercase())))
|
|
|
- },
|
|
|
- },
|
|
|
- BuiltinFunc {
|
|
|
- name: "concat",
|
|
|
- callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
|
|
|
- let val = state.eval(expr, env)?;
|
|
|
- let tup = val.as_tup(&state.ast.borrow())?;
|
|
|
- let mut contents = Vec::new();
|
|
|
- for elem in tup {
|
|
|
- for th in state.hnf(elem)?.as_tup(&state.ast.borrow())? {
|
|
|
- contents.push(th.clone());
|
|
|
- }
|
|
|
- }
|
|
|
- Ok(Value::Tup(contents))
|
|
|
- },
|
|
|
- },
|
|
|
- BuiltinFunc {
|
|
|
- name: "tuple-fold",
|
|
|
- callback: &|state: &State, expr: ExprRef, env: &Env| -> Result<Value, Error> {
|
|
|
- let val = state.eval(expr, env)?;
|
|
|
- let args = val.as_tup(&state.ast.borrow())?;
|
|
|
- if let [func, init, tup] = args {
|
|
|
- let func = state.hnf(func)?;
|
|
|
- let tup = state.hnf(tup)?;
|
|
|
-
|
|
|
- let mut result = init.clone();
|
|
|
- for t in tup.as_tup(&state.ast.borrow())? {
|
|
|
- let partial = state.eval_closure(func.as_closure(&state.ast.borrow())?, result)?;
|
|
|
- result = Thunk::Value(state.eval_closure(partial.as_closure(&state.ast.borrow())?, t.clone())?);
|
|
|
- }
|
|
|
-
|
|
|
- state.hnf(&result)
|
|
|
- } else {
|
|
|
- bail!("`tuple-fold`: expected 3 arguments, got {}", args.len());
|
|
|
- }
|
|
|
- },
|
|
|
- },
|
|
|
-];
|
|
|
-
|
|
|
/// The name `Thunk` is a bit of a misnomer here: this is
|
|
|
/// _potentially_ a `Thunk`, but represents anything that can be
|
|
|
/// stored in a variable: it might be an unevaluated expression (along
|
|
@@ -229,13 +130,13 @@ const BUILTINS: &[BuiltinFunc] = &[
|
|
|
pub enum Thunk {
|
|
|
Expr(ExprRef, Env),
|
|
|
Value(Value),
|
|
|
- Builtin(&'static BuiltinFunc),
|
|
|
+ Builtin(BuiltinRef),
|
|
|
}
|
|
|
|
|
|
/// An environment is either `None` (i.e. in the root scope) or `Some`
|
|
|
/// of some reference-counted scope (since those scopes might be
|
|
|
/// shared in several places, e.g. as pointers in thunks or closures).
|
|
|
-type Env = Option<Rc<Scope>>;
|
|
|
+pub type Env = Option<Rc<Scope>>;
|
|
|
|
|
|
/// A `Scope` represents a _non-root_ scope (since the root scope is
|
|
|
/// treated in a special way) and contains a map from variables to
|
|
@@ -265,10 +166,12 @@ pub struct Closure {
|
|
|
pub struct State {
|
|
|
/// An `ASTArena` that contains all the packed information that
|
|
|
/// results from parsing a program.
|
|
|
- ast: RefCell<ASTArena>,
|
|
|
+ pub ast: RefCell<ASTArena>,
|
|
|
/// The root scope of the program, which contains all the
|
|
|
/// top-level definitions and builtins.
|
|
|
root_scope: RefCell<HashMap<StrRef, Thunk>>,
|
|
|
+ /// The set of builtin (i.e. implemented-in-Rust) functions
|
|
|
+ builtins: Vec<BuiltinFunc>,
|
|
|
/// The thread-local RNG.
|
|
|
rand: RefCell<Box<dyn MatzoRand>>,
|
|
|
/// The instantiated parser used to parse Matzo programs
|
|
@@ -287,18 +190,21 @@ impl State {
|
|
|
/// This initializes a new `State` and adds all the builtin
|
|
|
/// functions to the root scope
|
|
|
pub fn new() -> State {
|
|
|
- let s = State {
|
|
|
+ let mut s = State {
|
|
|
root_scope: RefCell::new(HashMap::new()),
|
|
|
rand: RefCell::new(Box::new(DefaultRNG::new())),
|
|
|
parser: crate::grammar::StmtsParser::new(),
|
|
|
expr_parser: crate::grammar::ExprRefParser::new(),
|
|
|
ast: RefCell::new(ASTArena::new()),
|
|
|
+ builtins: Vec::new(),
|
|
|
};
|
|
|
- for builtin in BUILTINS {
|
|
|
- let sym = s.ast.borrow_mut().add_string(builtin.name);
|
|
|
+ for builtin in crate::builtins::builtins() {
|
|
|
+ let idx = s.builtins.len();
|
|
|
+ let sym = s.ast.borrow_mut().add_string(&builtin.name);
|
|
|
s.root_scope
|
|
|
.borrow_mut()
|
|
|
- .insert(sym, Thunk::Builtin(builtin));
|
|
|
+ .insert(sym, Thunk::Builtin(BuiltinRef { idx }));
|
|
|
+ s.builtins.push(builtin);
|
|
|
}
|
|
|
s
|
|
|
}
|
|
@@ -306,18 +212,21 @@ impl State {
|
|
|
/// This initializes a new `State` and adds all the builtin
|
|
|
/// functions to the root scope
|
|
|
pub fn new_from_seed(seed: u64) -> State {
|
|
|
- let s = State {
|
|
|
+ let mut s = State {
|
|
|
root_scope: RefCell::new(HashMap::new()),
|
|
|
rand: RefCell::new(Box::new(SeededRNG::from_seed(seed))),
|
|
|
parser: crate::grammar::StmtsParser::new(),
|
|
|
expr_parser: crate::grammar::ExprRefParser::new(),
|
|
|
ast: RefCell::new(ASTArena::new()),
|
|
|
+ builtins: Vec::new(),
|
|
|
};
|
|
|
- for builtin in BUILTINS {
|
|
|
- let sym = s.ast.borrow_mut().add_string(builtin.name);
|
|
|
+ for builtin in crate::builtins::builtins() {
|
|
|
+ let idx = s.builtins.len();
|
|
|
+ let sym = s.ast.borrow_mut().add_string(&builtin.name);
|
|
|
s.root_scope
|
|
|
.borrow_mut()
|
|
|
- .insert(sym, Thunk::Builtin(builtin));
|
|
|
+ .insert(sym, Thunk::Builtin(BuiltinRef { idx }));
|
|
|
+ s.builtins.push(builtin);
|
|
|
}
|
|
|
s
|
|
|
}
|
|
@@ -400,7 +309,7 @@ impl State {
|
|
|
for stmt in stmts {
|
|
|
self.execute(&stmt, io::stdout())?;
|
|
|
}
|
|
|
- },
|
|
|
+ }
|
|
|
Err(err) => {
|
|
|
let lexed = crate::lexer::tokens(src);
|
|
|
let expr = {
|
|
@@ -489,10 +398,9 @@ impl State {
|
|
|
if *fixed {
|
|
|
let choice = &strs[self.rand.borrow_mut().gen_range_usize(0, strs.len())];
|
|
|
let str = self.ast.borrow()[choice.item].to_string();
|
|
|
- self.root_scope.borrow_mut().insert(
|
|
|
- name.item,
|
|
|
- Thunk::Value(Value::Lit(Literal::Str(str))),
|
|
|
- );
|
|
|
+ self.root_scope
|
|
|
+ .borrow_mut()
|
|
|
+ .insert(name.item, Thunk::Value(Value::Lit(Literal::Str(str))));
|
|
|
return Ok(());
|
|
|
}
|
|
|
|
|
@@ -544,17 +452,17 @@ impl State {
|
|
|
}
|
|
|
|
|
|
/// Given a thunk, force it to WHNF.
|
|
|
- fn hnf(&self, thunk: &Thunk) -> Result<Value, Error> {
|
|
|
+ pub fn hnf(&self, thunk: &Thunk) -> Result<Value, Error> {
|
|
|
match thunk {
|
|
|
Thunk::Expr(expr, env) => self.eval(*expr, env),
|
|
|
Thunk::Value(val) => Ok(val.clone()),
|
|
|
- Thunk::Builtin(b) => Ok(Value::Builtin(b)),
|
|
|
+ Thunk::Builtin(b) => Ok(Value::Builtin(*b)),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// Given an `ExprRef` and an environment, fetch that expression
|
|
|
/// and then evalute it in that environment
|
|
|
- fn eval(&self, expr_ref: ExprRef, env: &Env) -> Result<Value, Error> {
|
|
|
+ pub fn eval(&self, expr_ref: ExprRef, env: &Env) -> Result<Value, Error> {
|
|
|
let expr = &self.ast.borrow()[expr_ref.item];
|
|
|
match expr {
|
|
|
// literals should be mostly cheap-ish to copy, so a
|
|
@@ -615,7 +523,7 @@ impl State {
|
|
|
let from = self.eval(*from, env)?.as_num(&self.ast.borrow())?;
|
|
|
let to = self.eval(*to, env)?.as_num(&self.ast.borrow())?;
|
|
|
Ok(Value::Lit(Literal::Num(
|
|
|
- self.rand.borrow_mut().gen_range_i64(from, to+1),
|
|
|
+ self.rand.borrow_mut().gen_range_i64(from, to + 1),
|
|
|
)))
|
|
|
}
|
|
|
|
|
@@ -630,12 +538,15 @@ impl State {
|
|
|
// either a closure (i.e. the result of evaluating a
|
|
|
// function) or a builtin, and then handle it
|
|
|
// appropriately
|
|
|
- Expr::Ap(func, val) => match self.eval(*func, env)? {
|
|
|
+ Expr::Ap(func, vals) => match self.eval(*func, env)? {
|
|
|
Value::Closure(c) => {
|
|
|
- let scrut = Thunk::Expr(*val, env.clone());
|
|
|
- self.eval_closure(&c, scrut)
|
|
|
+ let scruts = vals.iter().map(|v| Thunk::Expr(*v, env.clone())).collect();
|
|
|
+ self.eval_closure(&c, scruts)
|
|
|
+ }
|
|
|
+ Value::Builtin(b) => {
|
|
|
+ let builtin = &self.builtins[b.idx];
|
|
|
+ (builtin.callback)(self, vals, env)
|
|
|
}
|
|
|
- Value::Builtin(builtin) => (builtin.callback)(self, *val, env),
|
|
|
_ => bail!("Bad function: {:?}", func),
|
|
|
},
|
|
|
|
|
@@ -663,7 +574,7 @@ impl State {
|
|
|
func: expr_ref,
|
|
|
scope: env.clone(),
|
|
|
};
|
|
|
- self.eval_closure(&closure, Thunk::Expr(*scrut, env.clone()))
|
|
|
+ self.eval_closure(&closure, vec![Thunk::Expr(*scrut, env.clone())])
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -685,7 +596,7 @@ impl State {
|
|
|
/// careful is here:
|
|
|
///
|
|
|
/// ```ignore
|
|
|
- /// {Foo => "1"; Foo => "2"; _ => "..."}.(Foo | Bar)
|
|
|
+ /// {[Foo] => "1"; [Foo] => "2"; _ => "..."}[Foo | Bar]
|
|
|
/// ```
|
|
|
///
|
|
|
/// It should be impossible to get `"2"` in this case. That means
|
|
@@ -694,7 +605,7 @@ impl State {
|
|
|
/// contain non-determinism:
|
|
|
///
|
|
|
/// ```ignore
|
|
|
- /// {<Foo, x> => x x "!"; <Bar, x> => x x "?"}.<Foo | Bar, "a" | "b">
|
|
|
+ /// {[<Foo, x>] => x x "!"; [<Bar, x>] => x x "?"}[<Foo | Bar, "a" | "b">]
|
|
|
/// ```
|
|
|
///
|
|
|
/// The above program should print one of "aa!", "bb!", "aa?", or
|
|
@@ -711,7 +622,7 @@ impl State {
|
|
|
/// function to mutably replace it with progressively more
|
|
|
/// evaluated versions of the same expression, and then that's the
|
|
|
/// thing we put into scope in the body of the function.
|
|
|
- fn eval_closure(&self, closure: &Closure, mut scrut: Thunk) -> Result<Value, Error> {
|
|
|
+ pub fn eval_closure(&self, closure: &Closure, mut scruts: Vec<Thunk>) -> Result<Value, Error> {
|
|
|
let ast = self.ast.borrow();
|
|
|
let cases = match &ast[closure.func] {
|
|
|
Expr::Fun(cases) => cases,
|
|
@@ -721,15 +632,20 @@ impl State {
|
|
|
};
|
|
|
|
|
|
// for each case
|
|
|
- for c in cases {
|
|
|
+ 'cases: for c in cases {
|
|
|
// build a set of potential bindings, which `match_pat`
|
|
|
// will update if it finds matching variables
|
|
|
let mut bindings = Vec::new();
|
|
|
- if !self.match_pat(&c.pat, &mut scrut, &mut bindings)? {
|
|
|
- // if we didn't match, we don't care about any
|
|
|
- // bindings we've found: simply skip it
|
|
|
+ if scruts.len() != c.pats.len() {
|
|
|
continue;
|
|
|
}
|
|
|
+ for (scrut, pat) in scruts.iter_mut().zip(c.pats.iter()) {
|
|
|
+ if !self.match_pat(&pat, scrut, &mut bindings)? {
|
|
|
+ // if we didn't match, we don't care about any
|
|
|
+ // bindings we've found: simply skip it
|
|
|
+ continue 'cases;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
// build a new scope from the bindings discovered
|
|
|
let mut new_scope = HashMap::new();
|
|
@@ -747,7 +663,7 @@ impl State {
|
|
|
}
|
|
|
|
|
|
// we couldn't find a matching pattern, so throw an error
|
|
|
- bail!("No pattern in {:?} matched {:?}", cases, scrut);
|
|
|
+ bail!("No pattern in {:?} matched {:?}", cases, scruts);
|
|
|
}
|
|
|
|
|
|
/// attempt to match the thunk `scrut` against the pattern
|