Browse Source

switch to using boxed (rather than static) builtins

Getty Ritter 2 years ago
parent
commit
1a2f9051ef
7 changed files with 191 additions and 170 deletions
  1. 2 2
      src/ast.rs
  2. 151 136
      src/builtins.rs
  3. 13 14
      src/interp.rs
  4. 1 1
      src/lexer.rs
  5. 16 12
      src/main.rs
  6. 3 3
      src/rand.rs
  7. 5 2
      tools/regenerate.rs

+ 2 - 2
src/ast.rs

@@ -1,5 +1,5 @@
-use std::fmt;
 pub use crate::lexer::{FileRef, Located, Span};
+use std::fmt;
 
 pub type StrRef = string_interner::DefaultSymbol;
 pub type Name = Located<StrRef>;
@@ -97,7 +97,7 @@ impl ASTArena {
         let end_of_line = end_of_line.unwrap_or_else(|| src.len());
 
         let mut result = format!("{:3} |", line_number);
-        result.push_str(&src[start_of_line .. end_of_line]);
+        result.push_str(&src[start_of_line..end_of_line]);
         result.push_str("\n     ");
         for _ in start_of_line..(span.start as usize) {
             result.push(' ');

+ 151 - 136
src/builtins.rs

@@ -6,159 +6,174 @@ use anyhow::{bail, Error};
 /// The list of builtins provided at startup.
 ///
 /// TODO: move this to a separate file and clean it up
-pub 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,
-                    _ => {
+pub fn builtins() -> Vec<BuiltinFunc> {
+    vec![
+        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 tuple\n{}", span)
+                        bail!(
+                            "`rep`: expected two arguments, got {}\n{}",
+                            args.len(),
+                            span
+                        )
                     }
+                    (args[0], args[1])
                 };
-                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)
+                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(),
+                    );
                 }
-                (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)))
+                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: "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: "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: "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: "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: "sub",
-        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 [x, y] = args {
-                let x = state.hnf(x)?.as_num(&state.ast.borrow())?;
-                let y = state.hnf(y)?.as_num(&state.ast.borrow())?;
-                Ok(Value::Lit(Literal::Num(x - y)))
-            } else {
-                bail!("`sub`: expected 2 arguments, got {}", args.len());
-            }
+        BuiltinFunc {
+            name: "sub",
+            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 [x, y] = args {
+                    let x = state.hnf(x)?.as_num(&state.ast.borrow())?;
+                    let y = state.hnf(y)?.as_num(&state.ast.borrow())?;
+                    Ok(Value::Lit(Literal::Num(x - y)))
+                } else {
+                    bail!("`sub`: expected 2 arguments, got {}", args.len());
+                }
+            },
         },
-    },
-
-    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());
+        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))
+                Ok(Value::Tup(contents))
+            },
         },
-    },
-
-    BuiltinFunc {
-        name: "tuple-index",
-        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 [tup, idx] = args {
-                let tup = state.hnf(tup)?;
-                let idx = state.hnf(idx)?;
-                state.hnf(&tup.as_tup(&state.ast.borrow())?[idx.as_num(&state.ast.borrow())? as usize])
-            } else {
-                bail!("`tuple-index`: expected 2 arguments, got {}", args.len());
-            }
+        BuiltinFunc {
+            name: "tuple-index",
+            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 [tup, idx] = args {
+                    let tup = state.hnf(tup)?;
+                    let idx = state.hnf(idx)?;
+                    state.hnf(
+                        &tup.as_tup(&state.ast.borrow())?
+                            [idx.as_num(&state.ast.borrow())? as usize],
+                    )
+                } else {
+                    bail!("`tuple-index`: expected 2 arguments, got {}", args.len());
+                }
+            },
         },
-    },
+        BuiltinFunc {
+            name: "tuple-replace",
+            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 [tup, idx, new] = args {
+                    let tup_val = state.hnf(tup)?;
+                    let tup = tup_val.as_tup(&state.ast.borrow())?;
+                    let idx = state.hnf(idx)?.as_num(&state.ast.borrow())?;
 
-    BuiltinFunc {
-        name: "tuple-replace",
-        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 [tup, idx, new] = args {
-                let tup_val = state.hnf(tup)?;
-                let tup = tup_val.as_tup(&state.ast.borrow())?;
-                let idx = state.hnf(idx)?.as_num(&state.ast.borrow())?;
-
-                let mut modified = Vec::with_capacity(tup.len());
-                for i in 0..idx {
-                    modified.push(tup[i as usize].clone());
-                }
-                modified.push(new.clone());
-                for i in (idx+1)..(tup.len() as i64) {
-                    modified.push(tup[i as usize].clone());
+                    let mut modified = Vec::with_capacity(tup.len());
+                    for i in 0..idx {
+                        modified.push(tup[i as usize].clone());
+                    }
+                    modified.push(new.clone());
+                    for i in (idx + 1)..(tup.len() as i64) {
+                        modified.push(tup[i as usize].clone());
+                    }
+                    Ok(Value::Tup(modified))
+                } else {
+                    bail!("`tuple-index`: expected 2 arguments, got {}", args.len());
                 }
-                Ok(Value::Tup(modified))
-            } else {
-                bail!("`tuple-index`: expected 2 arguments, got {}", args.len());
-            }
+            },
         },
-    },
+        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)?;
 
-    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(),
+                            )?);
+                    }
 
-                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());
                 }
-
-                state.hnf(&result)
-            } else {
-                bail!("`tuple-fold`: expected 3 arguments, got {}", args.len());
-            }
+            },
         },
-    },
-];
+    ]
+}

+ 13 - 14
src/interp.rs

@@ -171,7 +171,7 @@ pub struct State {
     /// top-level definitions and builtins.
     root_scope: RefCell<HashMap<StrRef, Thunk>>,
     /// The set of builtin (i.e. implemented-in-Rust) functions
-    builtins: Vec<&'static BuiltinFunc>,
+    builtins: Vec<BuiltinFunc>,
     /// The thread-local RNG.
     rand: RefCell<Box<dyn MatzoRand>>,
     /// The instantiated parser used to parse Matzo programs
@@ -198,13 +198,13 @@ impl State {
             ast: RefCell::new(ASTArena::new()),
             builtins: Vec::new(),
         };
-        for builtin in crate::builtins::BUILTINS {
+        for builtin in crate::builtins::builtins() {
             let idx = s.builtins.len();
-            s.builtins.push(builtin);
-            let sym = s.ast.borrow_mut().add_string(builtin.name);
+            let sym = s.ast.borrow_mut().add_string(&builtin.name);
             s.root_scope
                 .borrow_mut()
                 .insert(sym, Thunk::Builtin(BuiltinRef { idx }));
+            s.builtins.push(builtin);
         }
         s
     }
@@ -220,13 +220,13 @@ impl State {
             ast: RefCell::new(ASTArena::new()),
             builtins: Vec::new(),
         };
-        for builtin in crate::builtins::BUILTINS {
+        for builtin in crate::builtins::builtins() {
             let idx = s.builtins.len();
-            s.builtins.push(builtin);
-            let sym = s.ast.borrow_mut().add_string(builtin.name);
+            let sym = s.ast.borrow_mut().add_string(&builtin.name);
             s.root_scope
                 .borrow_mut()
                 .insert(sym, Thunk::Builtin(BuiltinRef { idx }));
+            s.builtins.push(builtin);
         }
         s
     }
@@ -309,7 +309,7 @@ impl State {
                 for stmt in stmts {
                     self.execute(&stmt, io::stdout())?;
                 }
-            },
+            }
             Err(err) => {
                 let lexed = crate::lexer::tokens(src);
                 let expr = {
@@ -398,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(());
                 }
 
@@ -524,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),
                 )))
             }
 
@@ -545,7 +544,7 @@ impl State {
                     self.eval_closure(&c, scrut)
                 }
                 Value::Builtin(b) => {
-                    let builtin = self.builtins[b.idx];
+                    let builtin = &self.builtins[b.idx];
                     (builtin.callback)(self, *val, env)
                 }
                 _ => bail!("Bad function: {:?}", func),

+ 1 - 1
src/lexer.rs

@@ -38,7 +38,7 @@ impl<T> Located<T> {
     }
 }
 
-impl <T: Clone> Located<T> {
+impl<T: Clone> Located<T> {
     pub fn map<R>(&self, func: impl FnOnce(T) -> R) -> Located<R> {
         Located {
             item: func(self.item.clone()),

+ 16 - 12
src/main.rs

@@ -1,6 +1,6 @@
 use matzo::interp::State;
 
-use clap::{Arg, App};
+use clap::{App, Arg};
 
 #[derive(Debug)]
 struct Opts {
@@ -15,17 +15,21 @@ impl Opts {
             .version(format!("git {}", env!("VERGEN_GIT_SHA")).as_ref())
             .author("Getty Ritter <matzo@infinitenegativeutility.com>")
             .about("A language for random text")
-            .arg(Arg::with_name("seed")
-                 .short("s")
-                 .long("seed")
-                 .value_name("NUMBER")
-                 .help("Sets a custom RNG seed")
-                 .takes_value(true))
-            .arg(Arg::with_name("input")
-                 .value_name("FILES")
-                 .help("Files to evaluate")
-                 .multiple(true)
-                 .takes_value(true))
+            .arg(
+                Arg::with_name("seed")
+                    .short("s")
+                    .long("seed")
+                    .value_name("NUMBER")
+                    .help("Sets a custom RNG seed")
+                    .takes_value(true),
+            )
+            .arg(
+                Arg::with_name("input")
+                    .value_name("FILES")
+                    .help("Files to evaluate")
+                    .multiple(true)
+                    .takes_value(true),
+            )
             .get_matches();
 
         let seed = matches.value_of("seed").map(|s| s.parse().unwrap());

+ 3 - 3
src/rand.rs

@@ -1,4 +1,4 @@
-use rand::{SeedableRng, Rng};
+use rand::{Rng, SeedableRng};
 
 pub trait MatzoRand {
     fn gen_range_i64(&mut self, min: i64, max: i64) -> i64;
@@ -12,7 +12,7 @@ pub struct DefaultRNG {
 impl DefaultRNG {
     pub fn new() -> DefaultRNG {
         DefaultRNG {
-            rand: rand::thread_rng()
+            rand: rand::thread_rng(),
         }
     }
 }
@@ -40,7 +40,7 @@ pub struct SeededRNG {
 impl SeededRNG {
     pub fn from_seed(seed: u64) -> SeededRNG {
         SeededRNG {
-            rand: rand::rngs::StdRng::seed_from_u64(seed)
+            rand: rand::rngs::StdRng::seed_from_u64(seed),
         }
     }
 }

+ 5 - 2
tools/regenerate.rs

@@ -1,6 +1,6 @@
 use matzo::grammar;
-use matzo::lexer;
 use matzo::interp;
+use matzo::lexer;
 
 use std::collections::BTreeMap;
 use std::io::Write;
@@ -18,7 +18,10 @@ fn generate_runs(source: &str) -> Result<BTreeMap<String, String>, Box<dyn std::
             let _ = found_results.insert(out, seed);
         }
     }
-    let output = found_results.into_iter().map(|(k, v)| (format!("{}", v), k)).collect();
+    let output = found_results
+        .into_iter()
+        .map(|(k, v)| (format!("{}", v), k))
+        .collect();
     Ok(output)
 }