Browse Source

start to reimplement matzo in rust

Getty Ritter 2 years ago
commit
b9f1b03394
11 changed files with 770 additions and 0 deletions
  1. 1 0
      .gitignore
  2. 509 0
      Cargo.lock
  3. 30 0
      Cargo.toml
  4. 67 0
      build.rs
  5. 49 0
      src/ast.rs
  6. 62 0
      src/grammar.lalrpop
  7. 12 0
      src/lib.rs
  8. 3 0
      src/main.rs
  9. 1 0
      tests/num_lit.matzo
  10. 9 0
      tests/num_lit.parsed
  11. 27 0
      tools/regenerate.rs

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+/target

+ 509 - 0
Cargo.lock

@@ -0,0 +1,509 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "ascii-canvas"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
+dependencies = [
+ "term",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bit-set"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "crunchy"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+
+[[package]]
+name = "ctor"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
+dependencies = [
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "diff"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
+
+[[package]]
+name = "dirs-next"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
+dependencies = [
+ "cfg-if",
+ "dirs-sys-next",
+]
+
+[[package]]
+name = "dirs-sys-next"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
+dependencies = [
+ "libc",
+ "redox_users",
+ "winapi",
+]
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "ena"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "fixedbitset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itertools"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "lalrpop"
+version = "0.19.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15174f1c529af5bf1283c3bc0058266b483a67156f79589fab2a25e23cf8988"
+dependencies = [
+ "ascii-canvas",
+ "atty",
+ "bit-set",
+ "diff",
+ "ena",
+ "itertools",
+ "lalrpop-util",
+ "petgraph",
+ "pico-args",
+ "regex",
+ "regex-syntax",
+ "string_cache",
+ "term",
+ "tiny-keccak",
+ "unicode-xid",
+]
+
+[[package]]
+name = "lalrpop-util"
+version = "0.19.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e58cce361efcc90ba8a0a5f982c741ff86b603495bb15a998412e957dcd278"
+dependencies = [
+ "regex",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673"
+
+[[package]]
+name = "lock_api"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "matzo"
+version = "0.1.0"
+dependencies = [
+ "lalrpop",
+ "lalrpop-util",
+ "pretty_assertions",
+ "regex",
+]
+
+[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
+
+[[package]]
+name = "output_vt100"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "petgraph"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pico-args"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "pretty_assertions"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc"
+dependencies = [
+ "ansi_term",
+ "ctor",
+ "diff",
+ "output_vt100",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
+dependencies = [
+ "getrandom",
+ "redox_syscall",
+]
+
+[[package]]
+name = "regex"
+version = "1.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
+
+[[package]]
+name = "rustversion"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "siphasher"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b"
+
+[[package]]
+name = "smallvec"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
+
+[[package]]
+name = "string_cache"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "923f0f39b6267d37d23ce71ae7235602134b250ace715dd2c90421998ddac0c6"
+dependencies = [
+ "lazy_static",
+ "new_debug_unreachable",
+ "parking_lot",
+ "phf_shared",
+ "precomputed-hash",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.81"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "term"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
+dependencies = [
+ "dirs-next",
+ "rustversion",
+ "winapi",
+]
+
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

+ 30 - 0
Cargo.toml

@@ -0,0 +1,30 @@
+[package]
+name = "matzo"
+version = "0.1.0"
+edition = "2021"
+default-run = "matzo"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+name = "matzo"
+
+[[bin]]
+name = "matzo"
+path = "src/main.rs"
+
+[[bin]]
+name = "matzo-regenerate"
+test = true
+path = "tools/regenerate.rs"
+
+[dependencies]
+regex = "1"
+lalrpop-util = { version = "*", features = ["lexer"] }
+
+[build-dependencies.lalrpop]
+version = "*"
+features = ["lexer"]
+
+[dev-dependencies]
+pretty_assertions = "*"

+ 67 - 0
build.rs

@@ -0,0 +1,67 @@
+use std::env;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+
+const TEST_PREFIX: &str = "
+#[cfg(test)]
+use pretty_assertions::assert_eq;
+use crate::grammar;
+use std::io::Write;
+
+// to let us use pretty_assertions with strings, we write a newtype
+// that delegates Debug to Display
+#[derive(PartialEq, Eq)]
+struct StringWrapper<'a> {
+  wrapped: &'a str,
+}
+
+impl<'a> core::fmt::Debug for StringWrapper<'a> {
+  fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
+    core::fmt::Display::fmt(self.wrapped, f)
+  }
+}
+
+fn assert_eq(x: &str, y: &str) {
+  assert_eq!(StringWrapper {wrapped: x}, StringWrapper {wrapped: y});
+}
+";
+
+const TEST_TEMPLATE: &str = "
+#[test]
+fn test_%PREFIX%() {
+  let source = include_str!(\"%ROOT%/tests/%PREFIX%.matzo\");
+  let ast = grammar::StmtsParser::new().parse(source);
+  assert!(ast.is_ok());
+  let ast = ast.unwrap();
+
+  let mut buf = Vec::new();
+  writeln!(buf, \"{:#?}\", ast).unwrap();
+  assert_eq(
+    std::str::from_utf8(&buf).unwrap().trim(),
+    include_str!(\"%ROOT%/tests/%PREFIX%.parsed\").trim(),
+  );
+}
+";
+
+fn main() {
+    lalrpop::process_root().unwrap();
+
+    let out_dir = env::var("OUT_DIR").unwrap();
+    let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
+    let dest = Path::new(&out_dir).join("exp_tests.rs");
+    let mut test_file = File::create(&dest).unwrap();
+    writeln!(test_file, "{}", TEST_PREFIX).unwrap();
+
+    for exp in std::fs::read_dir("tests").unwrap() {
+        let exp = exp.unwrap().path().canonicalize().unwrap();
+        let fname = exp.file_name().unwrap().to_string_lossy();
+        if let Some(prefix) = fname.strip_suffix(".matzo") {
+            let test = TEST_TEMPLATE
+                .replace("%FILE%", &fname)
+                .replace("%PREFIX%", prefix)
+                .replace("%ROOT%", &manifest_dir);
+            writeln!(test_file, "{}", test).unwrap();
+        }
+    }
+}

+ 49 - 0
src/ast.rs

@@ -0,0 +1,49 @@
+type Name = String;
+
+#[derive(Debug)]
+pub enum Stmt {
+    Puts(Expr),
+    Fix(Name),
+    Assn(Name, Expr),
+    LitAssn(Name, Vec<String>),
+}
+
+#[derive(Debug)]
+pub enum Expr {
+    Var(Name),
+    Cat(Vec<Expr>),
+    Chc(Vec<Choice>),
+    Rep(i64, Box<Expr>),
+    Lit(Literal),
+    Ap(Box<Expr>, Box<Expr>),
+    Tup(Vec<Expr>),
+    Let(Name, Box<Expr>, Box<Expr>),
+    Fun(Vec<Case>),
+    Case(Box<Expr>, Vec<Case>),
+}
+
+#[derive(Debug)]
+pub struct Case {
+    pub pat: Pat,
+    pub expr: Expr,
+}
+
+#[derive(Debug)]
+pub enum Pat {
+    Var(Name),
+    Lit(Literal),
+    Tup(Vec<Pat>),
+}
+
+#[derive(Debug)]
+pub struct Choice {
+    pub weight: Option<i64>,
+    pub value: Expr,
+}
+
+#[derive(Debug)]
+pub enum Literal {
+    Str(String),
+    Atom(String),
+    Num(i64),
+}

+ 62 - 0
src/grammar.lalrpop

@@ -0,0 +1,62 @@
+use std::str::FromStr;
+
+grammar;
+
+match {
+    "<",
+    ">",
+    "|",
+    ":",
+    ",",
+    ";",
+    ":=",
+    "::=",
+    "puts",
+    "case",
+    "let",
+    "in",
+    r"[a-z][A-Za-z0-9_-]*",
+    r"[A-Z][A-Za-z0-9_-]*",
+    r"[0-9]+",
+}
+
+use crate::ast::*;
+
+pub Stmts: Vec<Stmt> = {
+  <mut stmts:(<Stmt> ";")*> <stmt:Stmt?> => match stmt {
+    None => stmts,
+    Some(stmt) => {
+      stmts.push(stmt);
+      stmts
+    }
+  },
+};
+
+pub Stmt: Stmt = {
+  "puts" <Expr> => Stmt::Puts(<>),
+  <Name> ":=" <Expr> => Stmt::Assn(<>),
+};
+
+pub Name: String = {
+  r"[a-z][A-Za-z0-9_-]*" => <>.to_owned(),
+};
+
+pub Expr: Expr = {
+  <Literal> => Expr::Lit(<>),
+  <Name> => Expr::Var(<>),
+  "<" <mut es:(<Expr> ",")*> <e:Expr> ">" => {
+    es.push(e);
+    Expr::Tup(es)
+  },
+  "let" <name:Name> ":=" <e1:Expr> "in" <e2:Expr> =>
+    Expr::Let(name, Box::new(e1), Box::new(e2)),
+};
+
+pub Num: i64 = {
+  r"[0-9]+" => i64::from_str(<>).unwrap(),
+};
+
+pub Literal: Literal = {
+  <Num> => Literal::Num(<>),
+  r"[A-Z][A-Za-z0-9_-]*" => Literal::Atom(<>.to_owned()),
+};

+ 12 - 0
src/lib.rs

@@ -0,0 +1,12 @@
+#[macro_use]
+extern crate lalrpop_util;
+
+pub mod ast;
+
+#[cfg(test)]
+pub mod test {
+    include!(concat!(env!("OUT_DIR"), "/exp_tests.rs"));
+}
+
+
+lalrpop_mod!(#[allow(clippy::all)] pub grammar);

+ 3 - 0
src/main.rs

@@ -0,0 +1,3 @@
+fn main() {
+    println!("{:?}", matzo::grammar::StmtsParser::new().parse("puts 55"));
+}

+ 1 - 0
tests/num_lit.matzo

@@ -0,0 +1 @@
+puts 55;

+ 9 - 0
tests/num_lit.parsed

@@ -0,0 +1,9 @@
+[
+    Puts(
+        Lit(
+            Num(
+                55,
+            ),
+        ),
+    ),
+]

+ 27 - 0
tools/regenerate.rs

@@ -0,0 +1,27 @@
+use matzo::grammar;
+
+use std::io::Write;
+
+fn main() -> Result<(), Box<dyn std::error::Error>> {
+    for exp in std::fs::read_dir("tests")? {
+        let exp = exp?.path().canonicalize()?;
+        let fname = exp.file_name().ok_or("bad")?.to_string_lossy();
+        if let Some(prefix) = fname.strip_suffix(".matzo") {
+            println!("regenerating {}.matzo", prefix);
+            let exp_filename = |new_suffix| {
+                let mut f = exp.clone();
+                f.pop();
+                f.push(format!("{}.{}", prefix, new_suffix));
+                f
+            };
+
+            let src = std::fs::read_to_string(&exp)?;
+            if let Ok(ast) = grammar::StmtsParser::new().parse(&src) {
+                let mut f = std::fs::File::create(exp_filename("parsed"))?;
+                writeln!(f, "{:#?}", ast)?;
+            }
+        }
+    }
+
+    Ok(())
+}