diff --git a/Cargo.lock b/Cargo.lock index 9406153..322c5d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,548 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -[[package]] -name = "aho-corasick" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" -dependencies = [ - "memchr", -] - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "arrayvec" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" - -[[package]] -name = "ascii-canvas" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff8eb72df928aafb99fe5d37b383f2fe25bd2a765e3e5f7c365916b6f2463a29" -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.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" - -[[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0dc55f2d8a1a85650ac47858bb001b4c0dd73d79e3c455a842925e68d29cd3" - -[[package]] -name = "blake2b_simd" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" -dependencies = [ - "arrayref", - "arrayvec", - "constant_time_eq", -] - -[[package]] -name = "block-buffer" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" -dependencies = [ - "byte-tools", -] - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "byteorder" -version = "1.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg", - "cfg-if", - "lazy_static", -] - -[[package]] -name = "diff" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "dirs" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "docopt" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" -dependencies = [ - "lazy_static", - "regex", - "serde", - "strsim", -] - -[[package]] -name = "either" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" - -[[package]] -name = "ena" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" -dependencies = [ - "log", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - -[[package]] -name = "fixedbitset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" - -[[package]] -name = "generic-array" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" -dependencies = [ - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hermit-abi" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" -dependencies = [ - "libc", -] - -[[package]] -name = "indexmap" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" -dependencies = [ - "autocfg", -] - -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - -[[package]] -name = "lalrpop" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f55673d283313791404be21209bb433f128f7e5c451986df107eb5fdbd68d2" -dependencies = [ - "ascii-canvas", - "atty", - "bit-set", - "diff", - "docopt", - "ena", - "itertools", - "lalrpop-util", - "petgraph", - "regex", - "regex-syntax", - "serde", - "serde_derive", - "sha2", - "string_cache", - "term", - "unicode-xid", -] - -[[package]] -name = "lalrpop-util" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e88f15a7d31dfa8fb607986819039127f0161058a3b248a146142d276cbd28" -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.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" - -[[package]] -name = "log" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "memchr" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" - -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[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 = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "proc-macro2" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redox_syscall" -version = "0.1.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" - -[[package]] -name = "redox_users" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" -dependencies = [ - "getrandom", - "redox_syscall", - "rust-argon2", -] - -[[package]] -name = "regex" -version = "1.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", - "thread_local", -] - -[[package]] -name = "regex-syntax" -version = "0.6.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" - -[[package]] -name = "rust-argon2" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" -dependencies = [ - "base64", - "blake2b_simd", - "constant_time_eq", - "crossbeam-utils", -] - -[[package]] -name = "serde" -version = "1.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "serene" version = "0.1.0" -dependencies = [ - "lalrpop", - "lalrpop-util", - "regex", -] - -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] - -[[package]] -name = "siphasher" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" - -[[package]] -name = "string_cache" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" -dependencies = [ - "lazy_static", - "new_debug_unreachable", - "phf_shared", - "precomputed-hash", - "serde", -] - -[[package]] -name = "strsim" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" - -[[package]] -name = "syn" -version = "1.0.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "term" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" -dependencies = [ - "byteorder", - "dirs", - "winapi", -] - -[[package]] -name = "thread_local" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "typenum" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" - -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -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" diff --git a/Cargo.toml b/Cargo.toml index 613e526..eb6c23d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,10 +6,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[build-dependencies] -#lalrpop = "0.19.0" -lalrpop = { version = "0.19.0", features = ["lexer"] } - [dependencies] -lalrpop-util = "0.19.0" -regex = "1" \ No newline at end of file diff --git a/build.rs b/build.rs deleted file mode 100644 index 23c7d3f..0000000 --- a/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -extern crate lalrpop; - -fn main() { - lalrpop::process_root().unwrap(); -} diff --git a/src/ast.rs b/src/ast.rs index f2e5e28..eaa0111 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -6,8 +6,8 @@ // What about usize and isize ? #[derive(Debug, Clone)] pub enum Number { - Integer(i32), - Float(f32), + Integer(i64), + Float(f64), } impl PartialEq for Number { @@ -17,8 +17,8 @@ impl PartialEq for Number { match comb { (Number::Integer(x), Number::Integer(y)) => *x == *y, (Number::Float(x), Number::Float(y)) => *x == *y, - (Number::Integer(x), Number::Float(y)) => *x as f32 == *y, - (Number::Float(x), Number::Integer(y)) => *x == *y as f32, + (Number::Integer(x), Number::Float(y)) => *x as f64 == *y, + (Number::Float(x), Number::Integer(y)) => *x == *y as f64, } } } @@ -34,4 +34,12 @@ pub enum Expr { Num(Number), Comment, Error(String), + Cons(Box, Box), + Nil, + NoMatch, +} + +#[derive(Debug, Eq, PartialEq, Clone)] +pub enum Error { + SyntaxError } diff --git a/src/grammer.lalrpop b/src/grammer.lalrpop deleted file mode 100644 index 0c47a1e..0000000 --- a/src/grammer.lalrpop +++ /dev/null @@ -1,58 +0,0 @@ -//! Serene's grammer based on LALRPOP which is a LR(1) parser. -use std::str::FromStr; -use crate::ast; - -grammar; - -// A collection of Expr objects is simply a sequence of parsed AST objects. -pub Exprs: Vec = { - )*> => v, -}; - -// Parse an AST expression. -Expr: ast::Expr = { - // Matches a list - "(" ")" => ast::Expr::List(<>), - - // Matches a vector - // "[" "]" => Ast::Vector(<>), - - // Match the literal value "nil". - // "nil" => Ast::Nil, - // Match a Num term. - Float => ast::Expr::Num(ast::Number::Float(<>)), - Decimal => ast::Expr::Num(ast::Number::Integer(<>)), - - // Matches a string. - r#""(?:[^"\\]|\\.)*""# => { - let val = <>; - ast::Expr::Str(val[1..val.len() - 1].to_owned()) - }, - - // Matches a quoted expr - "'" => ast::Expr::Quote(Box::new(<>)), - - // Match any token and treat it as an atom. - Symbol => ast::Expr::Symbol(<>), - ! => ast::Expr::Error(format!("{:?}", <>)), -}; - -// Match any sequence of token characters. -Symbol: String = { - r#"[A-Za-z_?+*/=<>-]+[0-9A-Za-z_?+*/=<>-]*"# => (<>).to_owned() -}; - -// Match any valid integer value and treat it as a 32-bit signed integer. -Decimal: i32 = { - r"[0-9]+" => i32::from_str(<>).unwrap() -}; - -Float: f32 = { - r"[0-9]*\.[0-9]+" => f32::from_str(<>).unwrap() -}; - -match { - r"\s*" => { }, - r";[^\n\r]*[\n\r]*" => { }, - _ -} diff --git a/src/main.rs b/src/main.rs index 46305d5..e304e31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,32 +1,14 @@ -#[macro_use] -extern crate lalrpop_util; -lalrpop_mod!(pub grammer); +use std::string::String; pub mod ast; +pub mod reader; pub mod collections; -use collections::list; - -#[test] -fn grammer() { - assert!(grammer::ExprsParser::new().parse("a22").is_ok()); - assert!(grammer::ExprsParser::new().parse("44").is_ok()); - assert!(grammer::ExprsParser::new().parse("is-ok").is_ok()); - assert!(grammer::ExprsParser::new().parse("is-ok?").is_ok()); - assert!(grammer::ExprsParser::new().parse("is-ok+<<>_").is_ok()); - println!("{:?}", grammer::ExprsParser::new().parse("(as -(sad asd) 3i)")); - assert!(true); - assert!(grammer::ExprsParser::new() - .parse("(\"asd\" (symbol (n)))") - .is_ok()); - assert!(grammer::ExprsParser::new() - .parse("(\"asd\" (symbol (n 32)))") - .is_ok()); - assert!(grammer::ExprsParser::new().parse("((22)").is_err()); -} fn main() { - list::List::h(); + let input = String::from("(println \">>>>>\" (+ 2 3))"); + + println!("{:?}", + reader::read_string(&input).unwrap()); } diff --git a/src/reader.rs b/src/reader.rs new file mode 100644 index 0000000..ea40248 --- /dev/null +++ b/src/reader.rs @@ -0,0 +1,272 @@ +use crate::ast::{Expr, Number}; +use std::io::{Read, BufReader}; + +pub type ReadResult = Result; + +pub struct ExprReader { + read_stack: Vec +} + +impl ExprReader { + + fn new() -> ExprReader { + ExprReader { + read_stack: vec![] + } + } + + fn get_char(&mut self, reader: &mut BufReader, skip_whitespace: bool) -> Option { + loop { + match self.read_stack.pop() { + Some(c) if !c.is_whitespace() || !skip_whitespace => + { + return Some(c) + }, + Some(_) => continue, + None => () + }; + + // Rust is weird, it doesn't provide a way to read from a buffer char by char. + let mut single_char_buff = [0]; + let bytes_read = reader.read(&mut single_char_buff); + match bytes_read { + Ok(n) if n > 0 => {}, + Ok(_) => return None, + Err(_) => return None + }; + let ch = single_char_buff[0] as char; + + match ch { + c if !c.is_whitespace() || !skip_whitespace => return Some(c), + _ => (), + }; + } + } + + fn unget_char(&mut self, c: char) { + self.read_stack.push(c); + } + + // Look ahead. AFAIK Rust doesn't provide any unread functoinality like Java input streams which + // sucks. + fn peek_char(&mut self, reader: &mut BufReader, skip_whitespace: bool) -> Option { + match self.get_char(reader, skip_whitespace) { + Some(c) => { + self.unget_char(c); + Some(c) + }, + None => None + } + } + + fn read_quoted_expr(&mut self, reader: &mut BufReader) -> ReadResult { + let rest = self.read_expr(reader)?; + Ok(Expr::Cons(Box::new(Expr::Symbol("quote".to_string())), Box::new(rest))) + } + + fn read_unquoted_expr(&mut self, reader: &mut BufReader) -> ReadResult { + match self.peek_char(reader, true) { + Some('@') => { + // Move forward in the buffer since we peeked it + let _ = self.get_char(reader, true); + let rest = self.read_expr(reader)?; + Ok(Expr::Cons(Box::new(Expr::Symbol("unquote-splicing".to_string())), + Box::new(rest))) + }, + _ => { + let rest = self.read_expr(reader)?; + Ok(Expr::Cons(Box::new(Expr::Symbol("unquote".to_string())), + Box::new(rest))) + } + } + } + + fn read_quasiquoted_expr(&mut self, reader: &mut BufReader) -> ReadResult { + let rest = self.read_expr(reader)?; + Ok(Expr::Cons(Box::new(Expr::Symbol("quasiquote".to_string())), + Box::new(rest))) + } + + // TODO: We might want to replace Cons with an actual List struct + fn read_list(&mut self, reader: &mut BufReader) -> ReadResult { + let first = match self.read_expr(reader) { + Ok(value) => value, + Err(e) => match self.get_char(reader, true) { + // is it an empty list ? + // TODO: we might want to return an actual empty list here + Some(')') => return Ok(Expr::Nil), + _ => return Err(e) + } + }; + let rest = match self.get_char(reader, true) { + Some(e) => { + self.unget_char(e); + self.read_list(reader)? + }, + None => return Err("Unexpected EOF, expected . or symbol".to_string()) + }; + + Ok(Expr::Cons(Box::new(first), Box::new(rest))) + } + + fn is_valid_for_identifier(&self, c: char) -> bool { + match c { + '!' | '$' | '%' | '&' | '*' | '+' | '-' | '.' | '~' | + '/' | ':' | '<' | '=' | '>' | '?' | '@' | '^' | '_' | + 'a'..='z' | 'A'..='Z' | '0'..='9' => true, + _ => false + } + } + + fn _read_symbol(&mut self, reader: &mut BufReader) -> ReadResult { + let mut symbol = match self.peek_char(reader, false) { + Some(e) if self.is_valid_for_identifier(e) => { + // Read into string + let ch = self.get_char(reader, false).unwrap(); + let mut s = String::new(); + s.push(ch); + s + }, + Some(e) => { + return Err(format!("Unexpected character: got {}, expected a symbol", e)) + }, + None => return Err("Unexpected EOF".to_string()) + }; + + loop { + match self.get_char(reader, false) { + Some(v) if self.is_valid_for_identifier(v) => symbol.push(v), + Some(v) => { + self.unget_char(v); + break; + }, + None => break + } + } + + Ok(Expr::Symbol(symbol)) + } + + fn read_escape_char(&mut self, reader: &mut BufReader) -> Option { + match self.get_char(reader, false) { + Some(e) => match e { + '\"' => Some('\"'), + '\'' => Some('\''), + '\\' => Some('\\'), + 'n' => Some('\n'), + 'r' => Some('\r'), + 't' => Some('\t'), + _ => None + }, + None => None + } + } + + fn read_string(&mut self, reader: &mut BufReader) -> ReadResult { + // Skip the " char + let _ = self.get_char(reader, false); + + let mut string = "".to_string(); + loop { + match self.get_char(reader, false) { + Some(e) => match e { + '\"' => break, + '\\' => match self.read_escape_char(reader) { + Some(v) => string.push(v), + None => return Err(format!("Unexpected char to escape, got {}", e)) + }, + //'\n' => return Err("Unescaped newlines are not allowed in string literals".to_string()), + _ => string.push(e) + }, + None => return Err("Unexpected EOF while scanning a string".to_string()) + } + } + Ok(Expr::Str(string)) + } + + fn read_number(&mut self, reader: &mut BufReader, neg: bool) -> ReadResult { + let mut is_double = false; + let mut string = "".to_string(); + loop { + match self.get_char(reader, false) { + Some(e) if e == '.' && is_double => return Err("A double with more that one '.' ???".to_string()), + Some(e) if e == '.' => { + is_double = true; + string.push(e); + } + Some(e) if e.is_digit(10) => string.push(e), + Some(e) => { + self.unget_char(e); + break; + } + None => break + } + } + if is_double { + Ok(Expr::Num(Number::Float(string.parse::().unwrap()))) + } else { + Ok(Expr::Num(Number::Integer(string.parse::().unwrap()))) + } + } + + fn read_symbol(&mut self, reader: &mut BufReader) -> ReadResult { + match self.peek_char(reader, true) { + Some(c) => match c { + '\"' => self.read_string(reader), + c if c.is_digit(10) => self.read_number(reader, false), + + // ':' => self.read_keyword(reader), + // '-' => { + // Neg number + // } + _ => self._read_symbol(reader) + }, + None => Err("Unexpected EOF while scanning atom".to_string()) + } + } + + pub fn read_expr(&mut self, reader: &mut BufReader) -> ReadResult { + match self.get_char(reader, true) { + Some(c) => { + match c { + '\'' => self.read_quoted_expr(reader), + '~' => self.read_unquoted_expr(reader), + '`' => self.read_quasiquoted_expr(reader), + '(' => self.read_list(reader), + //'[' => self.read_vector(reader), + //'{' => self.read_map(reader), + _ => { + self.unget_char(c); + self.read_symbol(reader) + } + } + }, + None => Ok(Expr::NoMatch) + } + } + + pub fn read_from_buffer(&mut self, reader: &mut BufReader) -> Result, String> { + let mut ast = vec![]; + loop { + match self.read_expr(reader) { + Ok(Expr::NoMatch) => break, + Err(v) => return Err(v), + Ok(v) => ast.push(v) + } + } + Ok(ast) + } + + pub fn read(&mut self, string: &str) -> Result, String> { + let reader = BufReader::new(string.as_bytes()); + let mut buf_reader = BufReader::new(reader); + self.read_from_buffer(&mut buf_reader) + } + +} + + +pub fn read_string(input: &str) -> Result, String> { + let mut reader = ExprReader::new(); + reader.read(input) +}