Compare commits
7 Commits
dadf839cca
...
93266a980e
Author | SHA1 | Date |
---|---|---|
Alona EM | 93266a980e | |
Alona EM | bc0d17e58f | |
Alona EM | 1129e1e2d9 | |
Alona EM | 4c4928ea9f | |
Alona EM | 46b96f3e82 | |
Alona EM | c1eb1ed61e | |
Alona EM | ebf46fc9e6 |
|
@ -1 +1,6 @@
|
|||
/target
|
||||
|
||||
# dr racket
|
||||
*.scm~
|
||||
*.scm#*
|
||||
.#*.scm
|
|
@ -29,6 +29,20 @@ dependencies = [
|
|||
"term",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"wait-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
|
@ -73,6 +87,17 @@ version = "1.3.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.72"
|
||||
|
@ -105,6 +130,19 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "console"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
|
||||
dependencies = [
|
||||
"encode_unicode",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"terminal_size",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.13"
|
||||
|
@ -136,6 +174,7 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
|||
[[package]]
|
||||
name = "debug2"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/adotinthevoid/debug2?rev=8b14afb90fc1d9ce470792ea393a9ab821a9e07f#8b14afb90fc1d9ce470792ea393a9ab821a9e07f"
|
||||
dependencies = [
|
||||
"debug2-derive",
|
||||
]
|
||||
|
@ -143,6 +182,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "debug2-derive"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/adotinthevoid/debug2?rev=8b14afb90fc1d9ce470792ea393a9ab821a9e07f#8b14afb90fc1d9ce470792ea393a9ab821a9e07f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -156,6 +196,12 @@ version = "0.1.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
|
||||
[[package]]
|
||||
name = "difflib"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
|
@ -177,6 +223,12 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
|
@ -192,6 +244,12 @@ dependencies = [
|
|||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.1.2"
|
||||
|
@ -242,12 +300,27 @@ dependencies = [
|
|||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "handball"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"assert_cmd",
|
||||
"chumsky",
|
||||
"debug2",
|
||||
"insta",
|
||||
"lalrpop",
|
||||
"lalrpop-util",
|
||||
"logos",
|
||||
|
@ -280,6 +353,23 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86c4e56d571b4cc829f0ce71506bd865a90369eeab5f3d3657ba96230beb8012"
|
||||
dependencies = [
|
||||
"console",
|
||||
"globset",
|
||||
"lazy_static",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"similar",
|
||||
"uuid",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
|
@ -298,6 +388,12 @@ dependencies = [
|
|||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "lalrpop"
|
||||
version = "0.19.6"
|
||||
|
@ -342,6 +438,12 @@ version = "0.2.108"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.5"
|
||||
|
@ -483,6 +585,33 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||
|
||||
[[package]]
|
||||
name = "predicates"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95e5a7689e456ab905c22c2b48225bb921aba7c8dfa58440d68ba13f6222a715"
|
||||
dependencies = [
|
||||
"difflib",
|
||||
"itertools",
|
||||
"predicates-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "predicates-core"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
|
||||
|
||||
[[package]]
|
||||
name = "predicates-tree"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "338c7be2905b732ae3984a2f40032b5e94fd8f52505b186c7d4d68d193445df7"
|
||||
dependencies = [
|
||||
"predicates-core",
|
||||
"termtree",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
|
@ -547,6 +676,12 @@ dependencies = [
|
|||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
|
@ -593,12 +728,76 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b9875c23cf305cd1fd7eb77234cbb705f21ea6a72c637a5c6db5fe4b8e7f008"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc0db5cb2556c0e558887d9bbdcf6ac4471e83ff66cf696e5419024d1606276"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bcbd0344bc6533bc7ec56df11d42fb70f1b912351c0825ccb7211b59d8af7cf5"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"ryu",
|
||||
"serde",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "similar"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.7"
|
||||
|
@ -664,6 +863,22 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termtree"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16"
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
|
@ -703,6 +918,32 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
|
@ -725,6 +966,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -773,3 +1023,12 @@ name = "windows_x86_64_msvc"
|
|||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f2b8c7cbd3bfdddd9ab98769f9746a7fad1bca236554cd032b78d768bc0e89f"
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
|
|
@ -7,11 +7,18 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
chumsky = "0.6.0"
|
||||
debug2 = { git = "https://github.com/adotinthevoid/debug2", rev="8b14afb90fc1d9ce470792ea393a9ab821a9e07f" }
|
||||
# fs-err = "2.6.0"
|
||||
lalrpop-util = { version = "0.19.6", features = ["lexer"] }
|
||||
logos = "0.12.0"
|
||||
debug2 = {path="../debug2"}
|
||||
rustyline = "9.1.1"
|
||||
rustyline-derive = "0.6.0"
|
||||
|
||||
[build-dependencies]
|
||||
lalrpop = "0.19.6"
|
||||
|
||||
[features]
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0.2"
|
||||
insta = { version = "1.9.0", features = ["glob"] }
|
||||
|
|
2
build.rs
2
build.rs
|
@ -2,5 +2,5 @@ fn main() {
|
|||
lalrpop::Configuration::new()
|
||||
.generate_in_source_tree()
|
||||
.process()
|
||||
.unwrap();
|
||||
.expect("handball lalrpop failed");
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
nightly-2021-12-16
|
||||
nightly-2021-12-27
|
|
@ -0,0 +1,23 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, debug2::Debug, PartialEq)]
|
||||
|
||||
crate enum Tree {
|
||||
Leaf(Literal),
|
||||
Define(String, Box<Tree>),
|
||||
If(Box<[Tree; 3]>),
|
||||
Lambda(Lambda),
|
||||
Branch(Vec<Tree>),
|
||||
}
|
||||
|
||||
#[derive(Debug, debug2::Debug, PartialEq, Clone)]
|
||||
|
||||
crate struct Lambda(crate Rc<[String]>, crate Rc<[Tree]>);
|
||||
|
||||
#[derive(Debug, debug2::Debug, PartialEq)]
|
||||
|
||||
crate enum Literal {
|
||||
Sym(String),
|
||||
Num(f64),
|
||||
Bool(bool),
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
use std::assert_matches::assert_matches;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::value::Value;
|
||||
use crate::{ast, prims};
|
||||
|
||||
pub(crate) fn eval(t: &ast::Tree, env: &mut Env) -> Result<Value, RTError> {
|
||||
Ok(match t {
|
||||
ast::Tree::Leaf(l) => match l {
|
||||
ast::Literal::Sym(s) => match env.lookup(s) {
|
||||
Some(v) => v,
|
||||
None => return err(format!("Undefined variable `{}`", s)),
|
||||
},
|
||||
ast::Literal::Num(v) => Value::Num(*v),
|
||||
ast::Literal::Bool(b) => Value::Bool(*b),
|
||||
},
|
||||
ast::Tree::Lambda(l) => Value::Lambda(l.clone()),
|
||||
ast::Tree::Branch(args) => {
|
||||
let Some(fun) = args.get(0) else { return err("No argument given".to_owned()) };
|
||||
let fun = eval(fun, env)?;
|
||||
let fun = fun.as_func()?;
|
||||
let args = args
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|a| eval(a, env))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
// return fun(&args);
|
||||
match fun {
|
||||
Callable::Func(f) => f(&args)?,
|
||||
Callable::Lambda(l) => {
|
||||
if l.0.len() == args.len() {
|
||||
let mut env = env.child();
|
||||
for (x, y) in l.0.iter().zip(args) {
|
||||
env.define(x.clone(), y)
|
||||
}
|
||||
|
||||
let [main @ .., tail] = &l.1[..] else { unreachable!("Body has 1+ element by parser") };
|
||||
for i in main {
|
||||
eval(i, &mut env)?;
|
||||
}
|
||||
|
||||
eval(tail, &mut env)?
|
||||
} else {
|
||||
return err(format!("Need {} args, got {}", l.0.len(), args.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::Tree::Define(name, to) => {
|
||||
let val = eval(to, env)?;
|
||||
env.define(name.to_owned(), val);
|
||||
Value::Trap
|
||||
}
|
||||
ast::Tree::If(box [cond, tcase, fcase]) => {
|
||||
let b = eval(cond, env)?.as_bool()?;
|
||||
let body = if b { tcase } else { fcase };
|
||||
eval(body, env)?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
crate struct RTError(crate String);
|
||||
|
||||
pub(crate) fn err<T>(s: String) -> Result<T, RTError> {
|
||||
Err(RTError(s))
|
||||
}
|
||||
|
||||
pub(crate) enum Callable<'a> {
|
||||
Func(prims::Func),
|
||||
Lambda(&'a ast::Lambda),
|
||||
}
|
||||
|
||||
pub(crate) struct Env<'a> {
|
||||
pub(crate) vars: HashMap<String, Value>,
|
||||
pub(crate) enclosing: Option<&'a Env<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Env<'a> {
|
||||
pub(crate) fn child(&'a self) -> Env<'a> {
|
||||
Env {
|
||||
vars: HashMap::new(),
|
||||
enclosing: Some(self),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, s: &str) -> Option<Value> {
|
||||
if let Some(v) = self.vars.get(s) {
|
||||
Some(v.clone())
|
||||
} else if let Some(parent) = self.enclosing {
|
||||
parent.lookup(s)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn define(&mut self, name: String, val: Value) {
|
||||
self.vars.insert(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default_env() -> Env<'static> {
|
||||
let mut vars = HashMap::new();
|
||||
|
||||
for (name, fun) in prims::prims() {
|
||||
assert_matches!(vars.insert((*name).to_owned(), Value::Func(*fun)), None)
|
||||
}
|
||||
|
||||
Env {
|
||||
vars,
|
||||
enclosing: None,
|
||||
}
|
||||
}
|
|
@ -1,9 +1,18 @@
|
|||
use crate::{Tree, Literal, Lambda};
|
||||
use crate::ast::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
grammar<'s>();
|
||||
|
||||
pub(crate) Trees = Tree+;
|
||||
match {
|
||||
r"\s*" => { }, // The default whitespace skipping is disabled an `ignore pattern` is specified
|
||||
r";[^\n\r]*[\n\r]*" => { }, // Skip `;` comments
|
||||
_
|
||||
}
|
||||
|
||||
|
||||
pub(crate) File = {Lang? <Trees> }
|
||||
|
||||
Trees = Tree+;
|
||||
|
||||
pub(crate) Tree: Tree = {
|
||||
"(" <Tree+> ")" => Tree::Branch(<>),
|
||||
|
@ -31,4 +40,11 @@ Num: f64 = { r"[0-9]+(\.[0-9]+)?" => <>.parse().unwrap() }
|
|||
Bool: bool = {
|
||||
"#t" => true,
|
||||
"#f" => false,
|
||||
}
|
||||
}
|
||||
|
||||
Lang: () = { "#lang" LangName }
|
||||
LangName = {
|
||||
// TODO: What should these be?
|
||||
"scheme",
|
||||
"r7rs",
|
||||
}
|
3982
src/grammar.rs
3982
src/grammar.rs
File diff suppressed because it is too large
Load Diff
344
src/main.rs
344
src/main.rs
|
@ -1,145 +1,25 @@
|
|||
#![feature(let_else)]
|
||||
#![feature(array_windows)]
|
||||
#![feature(assert_matches)]
|
||||
#![feature(box_patterns)]
|
||||
#![feature(crate_visibility_modifier)]
|
||||
#![feature(let_else)]
|
||||
|
||||
mod grammar;
|
||||
use std::io;
|
||||
|
||||
// use debug2::dbg;
|
||||
use rustyline::validate::{
|
||||
MatchingBracketValidator, ValidationContext, ValidationResult, Validator,
|
||||
};
|
||||
use rustyline::Editor;
|
||||
use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, debug2::Debug, PartialEq)]
|
||||
enum Tree {
|
||||
Leaf(Literal),
|
||||
Define(String, Box<Tree>),
|
||||
If(Box<[Tree; 3]>),
|
||||
Lambda(Lambda),
|
||||
Branch(Vec<Tree>),
|
||||
}
|
||||
|
||||
#[derive(Debug, debug2::Debug, PartialEq, Clone)]
|
||||
struct Lambda(Rc<[String]>, Rc<[Tree]>);
|
||||
|
||||
#[derive(Debug, debug2::Debug, PartialEq)]
|
||||
enum Literal {
|
||||
Sym(String),
|
||||
Num(f64),
|
||||
Bool(bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RTError(String);
|
||||
|
||||
fn err<T>(s: String) -> Result<T, RTError> {
|
||||
Err(RTError(s))
|
||||
}
|
||||
|
||||
type Func = fn(&[Value]) -> Result<Value, RTError>;
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Value {
|
||||
Num(f64),
|
||||
Func(Func),
|
||||
Lambda(Lambda),
|
||||
Bool(bool),
|
||||
NotAValue,
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use Value::*;
|
||||
match (self, other) {
|
||||
(Num(l), Num(r)) => l == r,
|
||||
(Func(l), Func(r)) => *l as usize == *r as usize,
|
||||
(Bool(l), Bool(r)) => l == r,
|
||||
(Lambda(l), Lambda(r)) => Rc::ptr_eq(&l.0, &r.0) && Rc::ptr_eq(&l.1, &r.1),
|
||||
|
||||
(Num(_), _) => false,
|
||||
(Func(_), _) => false,
|
||||
(Bool(_), _) => false,
|
||||
(Lambda(_), _) => false,
|
||||
|
||||
(NotAValue, _) => panic!("Trap value"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Num(n) => n.fmt(f),
|
||||
Self::Func(_) => f.write_str("#<procedure>"),
|
||||
Self::Lambda(_) => f.write_str("#<procedure>"),
|
||||
Self::Bool(b) => f.write_str(if *b { "#t" } else { "#f" }),
|
||||
Self::NotAValue => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
fn as_num(&self) -> Result<f64, RTError> {
|
||||
if let Self::Num(n) = self {
|
||||
Ok(*n)
|
||||
} else {
|
||||
Err(RTError("Expected a number".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
fn as_func(&self) -> Result<Callable, RTError> {
|
||||
match self {
|
||||
Self::Func(f) => Ok(Callable::Func(*f)),
|
||||
Self::Lambda(l) => Ok(Callable::Lambda(l)),
|
||||
_ => Err(RTError(format!("Expected a function, got {:?}", self))),
|
||||
}
|
||||
}
|
||||
|
||||
fn as_bool(&self) -> Result<bool, RTError> {
|
||||
if let Self::Bool(b) = self {
|
||||
Ok(*b)
|
||||
} else {
|
||||
Err(RTError("Expected a bool".to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Callable<'a> {
|
||||
Func(Func),
|
||||
Lambda(&'a Lambda),
|
||||
}
|
||||
|
||||
struct Env<'a> {
|
||||
vars: HashMap<String, Value>,
|
||||
enclosing: Option<&'a Env<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Env<'a> {
|
||||
fn child(&'a self) -> Env<'a> {
|
||||
Env {
|
||||
vars: HashMap::new(),
|
||||
enclosing: Some(self),
|
||||
}
|
||||
}
|
||||
|
||||
fn lookup(&self, s: &str) -> Option<Value> {
|
||||
if let Some(v) = self.vars.get(s) {
|
||||
Some(v.clone())
|
||||
} else if let Some(parent) = self.enclosing {
|
||||
parent.lookup(s)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn define(&mut self, name: String, val: Value) {
|
||||
self.vars.insert(name, val);
|
||||
}
|
||||
}
|
||||
mod ast;
|
||||
mod eval;
|
||||
#[allow(clippy::all)]
|
||||
mod grammar;
|
||||
mod prims;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod value;
|
||||
|
||||
fn main() {
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
|
@ -152,10 +32,10 @@ fn main() {
|
|||
|
||||
fn run_file(file: &str) -> io::Result<()> {
|
||||
let src = std::fs::read_to_string(file)?;
|
||||
let tree = grammar::TreesParser::new().parse(&src).unwrap();
|
||||
let mut env = default_env();
|
||||
let tree = grammar::FileParser::new().parse(&src).unwrap();
|
||||
let mut env = eval::default_env();
|
||||
for i in tree {
|
||||
eval(&i, &mut env).unwrap();
|
||||
eval::eval(&i, &mut env).unwrap();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -166,207 +46,17 @@ fn repl() {
|
|||
brackets: MatchingBracketValidator::new(),
|
||||
}));
|
||||
|
||||
let mut env = default_env();
|
||||
let mut env = eval::default_env();
|
||||
|
||||
while let Ok(line) = rl.readline("> ") {
|
||||
rl.add_history_entry(&line);
|
||||
|
||||
let tree = grammar::TreeParser::new().parse(&line).unwrap();
|
||||
// dbg!(&tree);
|
||||
println!("< {:?}", eval(&tree, &mut env))
|
||||
println!("< {:?}", eval::eval(&tree, &mut env))
|
||||
}
|
||||
}
|
||||
|
||||
fn eval(t: &Tree, env: &mut Env) -> Result<Value, RTError> {
|
||||
Ok(match t {
|
||||
Tree::Leaf(l) => match l {
|
||||
Literal::Sym(s) => match env.lookup(s) {
|
||||
Some(v) => v.clone(),
|
||||
None => return err(format!("Undefined variable `{}`", s)),
|
||||
},
|
||||
Literal::Num(v) => Value::Num(*v),
|
||||
Literal::Bool(b) => Value::Bool(*b),
|
||||
},
|
||||
Tree::Lambda(l) => Value::Lambda(l.clone()),
|
||||
Tree::Branch(args) => {
|
||||
let Some(fun) = args.get(0) else { return err("No argument given".to_owned()) };
|
||||
let fun = eval(fun, env)?;
|
||||
let fun = fun.as_func()?;
|
||||
let args = args
|
||||
.iter()
|
||||
.skip(1)
|
||||
.map(|a| eval(a, env))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
// return fun(&args);
|
||||
match fun {
|
||||
Callable::Func(f) => f(&args)?,
|
||||
Callable::Lambda(l) => {
|
||||
if l.0.len() == args.len() {
|
||||
let mut env = env.child();
|
||||
for (x, y) in l.0.iter().zip(args) {
|
||||
env.define(x.clone(), y)
|
||||
}
|
||||
|
||||
let [main @ .., tail] = &l.1[..] else { unreachable!("Body has 1+ element by parser") };
|
||||
for i in main {
|
||||
eval(i, &mut env)?;
|
||||
}
|
||||
|
||||
eval(tail, &mut env)?
|
||||
} else {
|
||||
return err(format!("Need {} args, got {}", l.0.len(), args.len()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Tree::Define(name, to) => {
|
||||
let val = eval(to, env)?;
|
||||
env.define(name.to_owned(), val);
|
||||
Value::NotAValue
|
||||
}
|
||||
Tree::If(box [cond, tcase, fcase]) => {
|
||||
let b = eval(cond, env)?.as_bool()?;
|
||||
let body = if b { tcase } else { fcase };
|
||||
eval(body, env)?
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn default_env() -> Env<'static> {
|
||||
let mut vars = HashMap::new();
|
||||
|
||||
for (name, fun) in [
|
||||
("*", prims::mul as Func),
|
||||
("+", prims::add as Func),
|
||||
("-", prims::sub as Func),
|
||||
("/", prims::div as Func),
|
||||
// TODO: Spec compiance
|
||||
("=", prims::equals as Func),
|
||||
("eq?", prims::equals as Func),
|
||||
("eqv?", prims::equals as Func),
|
||||
("equal?", prims::equals as Func),
|
||||
("display", prims::display as Func),
|
||||
("newline", prims::newline as Func),
|
||||
] {
|
||||
vars.insert(name.to_owned(), Value::Func(fun));
|
||||
}
|
||||
|
||||
Env {
|
||||
vars,
|
||||
enclosing: None,
|
||||
}
|
||||
}
|
||||
|
||||
mod prims {
|
||||
use crate::{err, RTError, Value};
|
||||
|
||||
// TODO: DRY +-/*
|
||||
pub(crate) fn add(args: &[Value]) -> Result<Value, RTError> {
|
||||
args.iter()
|
||||
.map(Value::as_num)
|
||||
.try_fold(0.0, |a, b| Ok(a + b?))
|
||||
.map(Value::Num)
|
||||
}
|
||||
|
||||
pub(crate) fn mul(args: &[Value]) -> Result<Value, RTError> {
|
||||
args.iter()
|
||||
.map(Value::as_num)
|
||||
.try_fold(1.0, |a, b| Ok(a * b?))
|
||||
.map(Value::Num)
|
||||
}
|
||||
|
||||
pub(crate) fn div(args: &[Value]) -> Result<Value, RTError> {
|
||||
let init = args
|
||||
.get(0)
|
||||
.ok_or_else(|| RTError("`div` needs at least one argument".to_owned()))?
|
||||
.as_num()?;
|
||||
|
||||
Ok(Value::Num(if args.len() == 1 {
|
||||
1.0 / init
|
||||
} else {
|
||||
let rest = mul(&args[1..])?.as_num().unwrap();
|
||||
init / rest
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn sub(args: &[Value]) -> Result<Value, RTError> {
|
||||
let init = args
|
||||
.get(0)
|
||||
.ok_or_else(|| RTError("`sub` needs at least one argument".to_owned()))?
|
||||
.as_num()?;
|
||||
|
||||
Ok(Value::Num(if args.len() == 1 {
|
||||
-init
|
||||
} else {
|
||||
let rest = add(&args[1..])?.as_num().unwrap();
|
||||
init - rest
|
||||
}))
|
||||
}
|
||||
|
||||
pub(crate) fn equals(args: &[Value]) -> Result<Value, RTError> {
|
||||
Ok(Value::Bool(args.array_windows().all(|[l, r]| l == r)))
|
||||
}
|
||||
|
||||
pub(crate) fn display(args: &[Value]) -> Result<Value, RTError> {
|
||||
let [arg] = args else {return err("To many args to `display`".to_owned())};
|
||||
print!("{:?}", arg);
|
||||
Ok(Value::NotAValue)
|
||||
}
|
||||
|
||||
pub(crate) fn newline(args: &[Value]) -> Result<Value, RTError> {
|
||||
if !args.is_empty() {
|
||||
return err("Newline takes no args".to_owned());
|
||||
}
|
||||
println!("");
|
||||
Ok(Value::NotAValue)
|
||||
}
|
||||
}
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use super::*;
|
||||
|
||||
// #[test]
|
||||
// fn simple_math_space() {
|
||||
// let t = grammar::TreeParser::new()
|
||||
// .parse("( + 1 2 ( / 2 3 4 5) )")
|
||||
// .unwrap();
|
||||
// assert_eq!(
|
||||
// t,
|
||||
// Tree::Add(vec![
|
||||
// Tree::Val(1.0),
|
||||
// Tree::Val(2.0),
|
||||
// Tree::Div(vec![
|
||||
// Tree::Val(2.0),
|
||||
// Tree::Val(3.0),
|
||||
// Tree::Val(4.0),
|
||||
// Tree::Val(5.0),
|
||||
// ])
|
||||
// ])
|
||||
// );
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn simple_math_dence() {
|
||||
// let t = grammar::TreeParser::new()
|
||||
// .parse("(+ 1 2 (/ 2 3 4 5))")
|
||||
// .unwrap();
|
||||
// assert_eq!(
|
||||
// t,
|
||||
// Tree::Add(vec![
|
||||
// Tree::Val(1.0),
|
||||
// Tree::Val(2.0),
|
||||
// Tree::Div(vec![
|
||||
// Tree::Val(2.0),
|
||||
// Tree::Val(3.0),
|
||||
// Tree::Val(4.0),
|
||||
// Tree::Val(5.0),
|
||||
// ])
|
||||
// ])
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
#[derive(Completer, Helper, Highlighter, Hinter)]
|
||||
struct InputValidator {
|
||||
brackets: MatchingBracketValidator,
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
use crate::eval::{err, RTError};
|
||||
use crate::value::Value;
|
||||
|
||||
crate type Func = fn(&[Value]) -> Result<Value, RTError>;
|
||||
|
||||
crate fn prims() -> &'static [(&'static str, Func)] {
|
||||
&[
|
||||
("*", mul),
|
||||
("+", add),
|
||||
("-", sub),
|
||||
("/", div),
|
||||
// TODO: Spec compiance
|
||||
("=", equals),
|
||||
("eq?", equals),
|
||||
("eqv?", equals),
|
||||
("equal?", equals),
|
||||
("display", display),
|
||||
("newline", newline),
|
||||
("abs", abs),
|
||||
("<", lt),
|
||||
(">", gt),
|
||||
("<=", le),
|
||||
(">=", ge),
|
||||
]
|
||||
}
|
||||
|
||||
// TODO: DRY +-/*
|
||||
fn add(args: &[Value]) -> Result<Value, RTError> {
|
||||
args.iter()
|
||||
.map(Value::as_num)
|
||||
.try_fold(0.0, |a, b| Ok(a + b?))
|
||||
.map(Value::Num)
|
||||
}
|
||||
|
||||
fn mul(args: &[Value]) -> Result<Value, RTError> {
|
||||
args.iter()
|
||||
.map(Value::as_num)
|
||||
.try_fold(1.0, |a, b| Ok(a * b?))
|
||||
.map(Value::Num)
|
||||
}
|
||||
|
||||
fn div(args: &[Value]) -> Result<Value, RTError> {
|
||||
let init = args
|
||||
.get(0)
|
||||
.ok_or_else(|| RTError("`div` needs at least one argument".to_owned()))?
|
||||
.as_num()?;
|
||||
|
||||
Ok(Value::Num(if args.len() == 1 {
|
||||
1.0 / init
|
||||
} else {
|
||||
let rest = mul(&args[1..])?.as_num().unwrap();
|
||||
init / rest
|
||||
}))
|
||||
}
|
||||
|
||||
fn sub(args: &[Value]) -> Result<Value, RTError> {
|
||||
let init = args
|
||||
.get(0)
|
||||
.ok_or_else(|| RTError("`sub` needs at least one argument".to_owned()))?
|
||||
.as_num()?;
|
||||
|
||||
Ok(Value::Num(if args.len() == 1 {
|
||||
-init
|
||||
} else {
|
||||
let rest = add(&args[1..])?.as_num().unwrap();
|
||||
init - rest
|
||||
}))
|
||||
}
|
||||
|
||||
fn equals(args: &[Value]) -> Result<Value, RTError> {
|
||||
Ok(Value::Bool(args.array_windows().all(|[l, r]| l == r)))
|
||||
}
|
||||
|
||||
fn display(args: &[Value]) -> Result<Value, RTError> {
|
||||
let [arg] = args else {return err("To many args to `display`".to_owned())};
|
||||
print!("{:?}", arg);
|
||||
Ok(Value::Trap)
|
||||
}
|
||||
|
||||
fn newline(args: &[Value]) -> Result<Value, RTError> {
|
||||
if !args.is_empty() {
|
||||
return err("Newline takes no args".to_owned());
|
||||
}
|
||||
println!();
|
||||
Ok(Value::Trap)
|
||||
}
|
||||
|
||||
fn abs(args: &[Value]) -> Result<Value, RTError> {
|
||||
let [v] = args else { return err("abs takes 1 arg".to_owned()) };
|
||||
let ans = v.as_num()?.abs();
|
||||
Ok(Value::Num(ans))
|
||||
}
|
||||
|
||||
crate fn compare_core(
|
||||
args: &[Value],
|
||||
f: fn(f64, f64) -> bool,
|
||||
name: &'static str,
|
||||
) -> Result<Value, RTError> {
|
||||
for [l, r] in args.array_windows() {
|
||||
let (Value::Num(l), Value::Num(r)) = (l,r) else
|
||||
{
|
||||
return err(format!("`{}` args must be numbers", name));
|
||||
};
|
||||
|
||||
// TODO: Ensure this is correct wrt NaN
|
||||
if !f(*l, *r) {
|
||||
return Ok(Value::Bool(false));
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Value::Bool(true))
|
||||
}
|
||||
|
||||
macro_rules! cmps {
|
||||
($(($name:ident $op:tt))*) => {
|
||||
$(
|
||||
fn $name(args: &[Value]) -> Result<Value, RTError> {
|
||||
compare_core(args, |l, r| l $op r, stringify!($op))
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
cmps! { (lt <) (gt >) (le <=) (ge >=) }
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
source: src/tests.rs
|
||||
assertion_line: 45
|
||||
expression: run-pass fib.scm
|
||||
|
||||
---
|
||||
10.0
|
||||
89.0
|
||||
|
||||
9.0
|
||||
55.0
|
||||
|
||||
8.0
|
||||
34.0
|
||||
|
||||
7.0
|
||||
21.0
|
||||
|
||||
6.0
|
||||
13.0
|
||||
|
||||
5.0
|
||||
8.0
|
||||
|
||||
4.0
|
||||
5.0
|
||||
|
||||
3.0
|
||||
3.0
|
||||
|
||||
2.0
|
||||
2.0
|
||||
|
||||
1.0
|
||||
1.0
|
||||
|
||||
0.0
|
||||
1.0
|
||||
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
source: src/tests.rs
|
||||
assertion_line: 47
|
||||
expression: run-pass funcs.scm
|
||||
|
||||
---
|
||||
5.0
|
||||
11.0
|
||||
14.0
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
source: src/tests.rs
|
||||
assertion_line: 47
|
||||
expression: run-pass lambda-calc.scm
|
||||
|
||||
---
|
||||
1.0
|
||||
0.0
|
||||
-1.0
|
||||
0.0
|
||||
1.0
|
||||
1.0
|
||||
0.0
|
||||
0.0
|
||||
0.0
|
||||
1.0
|
||||
1.0
|
||||
1.0
|
||||
0.0
|
||||
1.0
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
---
|
||||
source: src/tests.rs
|
||||
assertion_line: 47
|
||||
expression: run-pass math.scm
|
||||
|
||||
---
|
||||
0.0
|
||||
1.0
|
||||
10.0
|
||||
-1.0
|
||||
-8.0
|
||||
7.0
|
||||
2.0
|
||||
1.0
|
||||
0.1
|
||||
1.0
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: src/tests.rs
|
||||
assertion_line: 45
|
||||
expression: run-pass multi.scm
|
||||
|
||||
---
|
||||
3.0
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: src/tests.rs
|
||||
assertion_line: 41
|
||||
expression: run-pass nested-defs.scm
|
||||
|
||||
---
|
||||
1.4142156862745097
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
source: src/tests.rs
|
||||
assertion_line: 41
|
||||
expression: run-pass sqrt.scm
|
||||
|
||||
---
|
||||
1.4142156862745097
|
|
@ -0,0 +1,21 @@
|
|||
#lang scheme
|
||||
|
||||
(define (displayln x) (display x) (newline))
|
||||
|
||||
(define (my-or a b) (if a #t b)) ; Test was written before we had `or`
|
||||
|
||||
|
||||
(define (fib-base? n) (my-or (= n 1) (= n 0)))
|
||||
|
||||
(define (fib n)
|
||||
(if (fib-base? n) 1
|
||||
(+ (fib (- n 1)) (fib (- n 2)))))
|
||||
|
||||
|
||||
(define (pfib n) (displayln n) (displayln (fib n)) (newline))
|
||||
|
||||
(define (pfibs n)
|
||||
(if (= n (- 1)) 0 ; Hack
|
||||
((lambda () (pfib n) (pfibs (- n 1)))))) ; Hack
|
||||
|
||||
(pfibs 10)
|
|
@ -0,0 +1,12 @@
|
|||
#lang scheme
|
||||
|
||||
(define (displayln x) (display x) (newline))
|
||||
|
||||
(define add1 +)
|
||||
(displayln (add1 2 3))
|
||||
|
||||
(define add2 (lambda (a b) (+ a b)))
|
||||
(displayln (add2 5 6))
|
||||
|
||||
(define (add3 a b) (+ a b))
|
||||
(displayln (add3 5 9))
|
|
@ -1,3 +1,5 @@
|
|||
#lang scheme
|
||||
|
||||
(define (displayln x) (display x) (newline))
|
||||
(define (printbool x) (displayln (bool->int x)))
|
||||
|
||||
|
@ -9,8 +11,8 @@
|
|||
|
||||
(define (bool->int x)
|
||||
(if
|
||||
(equal? x true) 1
|
||||
(if (equal? x false) 0 (- 1))))
|
||||
(equal? x true) 1
|
||||
(if (equal? x false) 0 (- 1))))
|
||||
|
||||
(printbool true)
|
||||
(printbool false)
|
|
@ -1,14 +1,15 @@
|
|||
(define (displayln x) (display x) (newline))
|
||||
(define (displayln x)
|
||||
(display x)
|
||||
(newline))
|
||||
|
||||
(displayln (+))
|
||||
(displayln (+ 1))
|
||||
(displayln (+ 1 2 3 4))
|
||||
(displayln (+))
|
||||
|
||||
(displayln (- 1))
|
||||
(displayln (- 1 2 3 4))
|
||||
|
||||
(displayln (* 1))
|
||||
(displayln (* 7))
|
||||
(displayln (* 1 2))
|
||||
(displayln (*))
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#lang scheme
|
||||
|
||||
(define (multi) 1 2 3)
|
||||
|
||||
(display (multi))
|
|
@ -0,0 +1,20 @@
|
|||
#lang scheme
|
||||
|
||||
; We dont have closures at the time of writing, but each func gets its own env
|
||||
; http://sarabander.github.io/sicp/html/1_002e1.xhtml#g_t1_002e1_002e7
|
||||
|
||||
(define (sqrt x)
|
||||
(define (sqrt-iter guess x)
|
||||
(define (good-enough? guess x)
|
||||
(define (square x) (* x x))
|
||||
(< (abs (- (square guess) x)) 0.001))
|
||||
(define (improve guess x)
|
||||
(define (average x y)
|
||||
(/ (+ x y) 2))
|
||||
(average guess (/ x guess)))
|
||||
(if (good-enough? guess x)
|
||||
guess
|
||||
(sqrt-iter (improve guess x) x)))
|
||||
(sqrt-iter 1.0 x))
|
||||
|
||||
(display (sqrt 2))
|
|
@ -1,3 +1,5 @@
|
|||
;; TODO: Enable when ready
|
||||
|
||||
(display (real? 2.5+0.0i))
|
||||
(newline)
|
||||
(display (real? 2.5+0i))
|
|
@ -0,0 +1,25 @@
|
|||
#lang scheme
|
||||
|
||||
; We dont have closures at the time of writing, but each func gets its own env
|
||||
; http://sarabander.github.io/sicp/html/1_002e1.xhtml#g_t1_002e1_002e7
|
||||
|
||||
(define (sqrt-iter guess x)
|
||||
(if (good-enough? guess x)
|
||||
guess
|
||||
(sqrt-iter (improve guess x) x)))
|
||||
|
||||
(define (improve guess x)
|
||||
(average guess (/ x guess)))
|
||||
|
||||
(define (average x y)
|
||||
(/ (+ x y) 2))
|
||||
|
||||
(define (square x) (* x x))
|
||||
|
||||
(define (good-enough? guess x)
|
||||
(< (abs (- (square guess) x)) 0.001))
|
||||
|
||||
(define (sqrt x)
|
||||
(sqrt-iter 1.0 x))
|
||||
|
||||
(display (sqrt 2))
|
|
@ -0,0 +1,47 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
// use fs_err as fs;
|
||||
|
||||
/*
|
||||
# Why are the tests in src
|
||||
|
||||
the way insta glob works is it finds the dir the file is in, and then
|
||||
looks at all children, and then checks if they have a match. This means we
|
||||
cant use .. in the glob, as parents arnt generated.
|
||||
|
||||
A potential solution would be to update insta to use CARGO_MAINIFEST_DIR
|
||||
as the base, and add file!() to the glob, but I'd need to update insta
|
||||
upstream for this
|
||||
*/
|
||||
|
||||
#[test]
|
||||
fn run_pass() {
|
||||
let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
|
||||
insta::glob!("test/run-pass/**.scm", |p| {
|
||||
let p = PathBuf::from(
|
||||
p.canonicalize()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.replace("\\\\?\\", ""), // Work around IDK on windows
|
||||
);
|
||||
|
||||
let cmd = assert_cmd::Command::cargo_bin("handball")
|
||||
.unwrap()
|
||||
.arg(&p)
|
||||
.assert()
|
||||
.success();
|
||||
|
||||
let p = p.strip_prefix(manifest_dir).unwrap();
|
||||
let p = p.strip_prefix("src/test/run-pass").unwrap();
|
||||
let p = p.to_str().unwrap();
|
||||
|
||||
assert_eq!(cmd.get_output().stderr, Vec::<u8>::new());
|
||||
insta::assert_snapshot!(
|
||||
"run-pass",
|
||||
String::from_utf8(cmd.get_output().stdout.clone()).unwrap(),
|
||||
&format!("run-pass {}", p)
|
||||
);
|
||||
})
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
use crate::{ast, eval, prims};
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone)]
|
||||
crate enum Value {
|
||||
Num(f64),
|
||||
Func(prims::Func),
|
||||
Lambda(ast::Lambda),
|
||||
Bool(bool),
|
||||
/// Result of things that shouldnt have values, like (define x 3)
|
||||
Trap,
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use Value::*;
|
||||
match (self, other) {
|
||||
(Num(l), Num(r)) => l == r,
|
||||
(Func(l), Func(r)) => *l as usize == *r as usize,
|
||||
(Bool(l), Bool(r)) => l == r,
|
||||
(Lambda(l), Lambda(r)) => Rc::ptr_eq(&l.0, &r.0) && Rc::ptr_eq(&l.1, &r.1),
|
||||
|
||||
(Num(_), _) => false,
|
||||
(Func(_), _) => false,
|
||||
(Bool(_), _) => false,
|
||||
(Lambda(_), _) => false,
|
||||
|
||||
(Trap, _) => panic!("Trap value"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Num(n) => n.fmt(f),
|
||||
Self::Func(_) => f.write_str("#<procedure>"),
|
||||
Self::Lambda(_) => f.write_str("#<procedure>"),
|
||||
Self::Bool(b) => f.write_str(if *b { "#t" } else { "#f" }),
|
||||
Self::Trap => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
crate fn as_num(&self) -> Result<f64, eval::RTError> {
|
||||
if let Self::Num(n) = self {
|
||||
Ok(*n)
|
||||
} else {
|
||||
Err(eval::RTError("Expected a number".to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
crate fn as_func(&self) -> Result<eval::Callable, eval::RTError> {
|
||||
match self {
|
||||
Self::Func(f) => Ok(eval::Callable::Func(*f)),
|
||||
Self::Lambda(l) => Ok(eval::Callable::Lambda(l)),
|
||||
_ => Err(eval::RTError(format!(
|
||||
"Expected a function, got {:?}",
|
||||
self
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
crate fn as_bool(&self) -> Result<bool, eval::RTError> {
|
||||
if let Self::Bool(b) = self {
|
||||
Ok(*b)
|
||||
} else {
|
||||
Err(eval::RTError("Expected a bool".to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue