From 59fa4bad6beb80e9ad53d309fab7edee3bdf24fe Mon Sep 17 00:00:00 2001 From: TriMill Date: Tue, 6 Sep 2022 09:52:29 -0400 Subject: [PATCH] initial commit --- .gitignore | 2 + Cargo.lock | 468 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 13 ++ idea.cxpr | 28 +++ src/bin/main.rs | 37 ++++ src/eval.rs | 108 +++++++++++ src/expr.rs | 51 +++++ src/interpreter.rs | 26 +++ src/lexer.rs | 250 ++++++++++++++++++++++++ src/lib.rs | 55 ++++++ src/parser.rs | 231 ++++++++++++++++++++++ src/token.rs | 70 +++++++ src/value.rs | 80 ++++++++ tests/code.cxpr | 3 + tests/test.rs | 19 ++ 15 files changed, 1441 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 idea.cxpr create mode 100644 src/bin/main.rs create mode 100644 src/eval.rs create mode 100644 src/expr.rs create mode 100644 src/interpreter.rs create mode 100644 src/lexer.rs create mode 100644 src/lib.rs create mode 100644 src/parser.rs create mode 100644 src/token.rs create mode 100644 src/value.rs create mode 100644 tests/code.cxpr create mode 100644 tests/test.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f84cc9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..462e306 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,468 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clipboard-win" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219" +dependencies = [ + "error-code", + "str-buf", + "winapi", +] + +[[package]] +name = "complexpr" +version = "0.1.0" +dependencies = [ + "lazy_static", + "num-complex", + "num-rational", + "num-traits", + "rustyline", +] + +[[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 = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "error-code" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" +dependencies = [ + "libc", + "str-buf", +] + +[[package]] +name = "fd-lock" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "io-lifetimes" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" + +[[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.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" + +[[package]] +name = "linux-raw-sys" +version = "0.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + +[[package]] +name = "nix" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "rustix" +version = "0.35.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustyline" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1cd5ae51d3f7bf65d7969d579d502168ef578f289452bd8ccc91de28fda20e" +dependencies = [ + "bitflags", + "cfg-if", + "clipboard-win", + "dirs-next", + "fd-lock", + "libc", + "log", + "memchr", + "nix", + "radix_trie", + "scopeguard", + "unicode-segmentation", + "unicode-width", + "utf8parse", + "winapi", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "str-buf" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" + +[[package]] +name = "syn" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1b05ca9d106ba7d2e31a9dab4a64e7be2cce415321966ea3132c49a656e252" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8f2591983642de85c921015f3f070c665a197ed69e417af436115e3a1407487" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..362e9b7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "complexpr" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +lazy_static = "1.4.0" +num-complex = "0.4.1" +num-rational = "0.4.0" +num-traits = "0.2.15" +rustyline = "10.0.0" \ No newline at end of file diff --git a/idea.cxpr b/idea.cxpr new file mode 100644 index 0000000..e9db2fa --- /dev/null +++ b/idea.cxpr @@ -0,0 +1,28 @@ +let square = x -> x^2; +let also_square(x) = x^2 + +let primes = range(100) |: filter(is_prime); +let prime_squares = range(100) |: map(square); +let also_prime_squares = range(100) |? is_prime |> square; + +# +let primes_and_prime_squares = primes |& prime_squares; + +if 49 <~ data { + println("49 is the square of a prime"); +} else { + println("49 is not the square of a prime"); +} + +# LET IDENT(square) EQ IDENT(x) ARROW IDENT(x) CARET INT(2) SEMICOLON +# +# LET IDENT(data) EQ IDENT(range) LPAREN INT(100) RPAREN PIPECOLON IDENT(filter) LPAREN IDENT(is_prime) RPAREN PIPECOLON IDENT(map) LPAREN IDENT(square) RPAREN SEMICOLON +# LET IDENT(also_data) EQ IDENT(range) LPAREN INT(100) RPAREN PIPEQUESTION IDENT(is_prime) PIPEPOINT IDENT(square) SEMICOLON + +# D = x:(fold(map(factors(x), n:(to_int(fold(factors(x),mul)/n))), add)) + +let D = x -> { + let facts = factors(x); + let product = facts |: foldl(1, mul); + return facts |> (n -> product / n) |: foldl(0, add); +} \ No newline at end of file diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..a6a8034 --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,37 @@ +use std::{rc::Rc, cell::RefCell, fs::{File, self}, io::{BufReader, Read}}; + +use complexpr::{eval::Environment, interpreter::interpret, value::Value}; +use rustyline::{self, error::ReadlineError}; + +fn main() -> Result<(), Box> { + let args: Vec = std::env::args().collect(); + if args.len() == 2 { + let fname = &args[1]; + let src = fs::read_to_string(fname)?; + interpret(&src, None)?; + } else { + repl()?; + } + Ok(()) +} + +fn repl() -> Result<(), Box> { + let mut rl = rustyline::Editor::<()>::new()?; + let env = Rc::new(RefCell::new(Environment::new())); + loop { + let readline = rl.readline(">> "); + match readline { + Ok(line) => { + let result = interpret(&line, Some(env.clone())); + match result { + Ok(Value::Nil) => (), + Ok(value) => println!("{:?}", value), + Err(e) => println!("Error: {}", e) + } + } + Err(ReadlineError::Eof) => break, + Err(_) => (), + } + } + Ok(()) +} \ No newline at end of file diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..a94be78 --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,108 @@ +use std::{collections::HashMap, rc::Rc, cell::RefCell}; + +use num_complex::Complex64; + +use crate::{value::Value, expr::{Stmt, Expr}, token::{TokenType, Token, OpType}}; + +#[derive(Debug)] +pub struct Environment { + parent: Option>>, + map: HashMap, Value> +} + +impl Environment { + pub fn new() -> Self { + Self { parent: None, map: HashMap::new() } + } + + pub fn extend(parent: Rc>) -> Self { + Self { parent: Some(parent), map: HashMap::new() } + } + + pub fn get(&self, name: &str) -> Result { + match self.map.get(name) { + Some(v) => Ok(v.clone()), + None => match self.parent { + Some(ref p) => p.borrow().get(name), + None => Err(()) + } + } + } + + pub fn declare(&mut self, name: Rc, value: Value) { + self.map.insert(name, value); + } + + pub fn set(&mut self, name: Rc, value: Value) -> Result<(),()> { + match self.map.contains_key(&name) { + true => { self.map.insert(name, value); Ok(()) }, + false => match self.parent { + Some(ref mut p) => p.borrow_mut().set(name, value), + None => Err(()) + } + } + } +} + +pub fn eval_stmt(stmt: &Stmt, env: Rc>) -> Result<(), String> { + match stmt { + Stmt::Expr{ expr } + => drop(eval_expr(expr, env)), + Stmt::Let { lhs: Token{ty: TokenType::Ident(s),..}, rhs: None } + => env.borrow_mut().declare(s.clone(), Value::Nil), + Stmt::Let { lhs: Token{ty: TokenType::Ident(s),..}, rhs: Some(rhs) } => { + let r = eval_expr(rhs, env.clone())?; + env.borrow_mut().declare(s.clone(), r) + }, + Stmt::If { conditions, bodies, else_clause } + => todo!(), // TODO if statements + _ => unreachable!() + } + Ok(()) +} + +pub fn eval_expr(expr: &Expr, env: Rc>) -> Result { + match expr { + Expr::Literal { value } => match &value.ty { + TokenType::Nil => Ok(Value::Nil), + TokenType::True => Ok(Value::Bool(true)), + TokenType::False => Ok(Value::Bool(false)), + TokenType::Int(n) => Ok(Value::Int(*n)), + TokenType::Float(f) => Ok(Value::Float(*f)), + TokenType::ImFloat(f) => Ok(Value::Complex(Complex64::new(0.0, *f))), + TokenType::String(s) => Ok(Value::String(s.clone())), + _ => todo!() + }, + Expr::Ident { value } => if let Token { ty: TokenType::Ident(name), ..} = value { + env.borrow_mut().get(name).map_err(|_| "Variable not defined in scope".into()) + } else { unreachable!() }, + Expr::Binary { lhs, rhs, op } => match op.ty.get_op_type() { + Some(OpType::Assignment) => { + let r = eval_expr(rhs, env.clone())?; + if let Expr::Ident{value: Token{ty: TokenType::Ident(name),..}} = &**lhs { + if op.ty == TokenType::Equal { + env.borrow_mut().set(name.clone(), r).map_err(|_| "Variable not declared before assignment")?; + Ok(Value::Nil) + } else { + todo!() // TODO +=, -=, etc + } + } else { + unreachable!() + } + }, + Some(OpType::Additive) | Some(OpType::Multiplicative) => { + let l = eval_expr(lhs, env.clone())?; + let r = eval_expr(rhs, env)?; + match op.ty { + TokenType::Plus => &l + &r, + TokenType::Minus => &l - &r, + TokenType::Star => &l * &r, + TokenType::Slash => &l / &r, + _ => todo!() // TODO other operations + } + }, + o => todo!("{:?}", o) // TODO other operations + }, + e => todo!("{:?}", e) // TODO other expression types + } +} \ No newline at end of file diff --git a/src/expr.rs b/src/expr.rs new file mode 100644 index 0000000..d118102 --- /dev/null +++ b/src/expr.rs @@ -0,0 +1,51 @@ +use std::fmt; + +use crate::{token::{Token, OpType}}; + +pub enum Stmt { + Expr { expr: Expr }, + Let { lhs: Token, rhs: Option }, + If { conditions: Vec, bodies: Vec>, else_clause: Option> } +} + +impl fmt::Debug for Stmt { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Expr { expr } => write!(f, "{:?}", expr), + Self::Let { lhs, rhs } => write!(f, "(let {:?} = {:?})", lhs, rhs), + _ => todo!() + } + } +} + +pub enum Expr { + Binary { lhs: Box, rhs: Box, op: Token }, + Unary { arg: Box, op: Token }, + Ident { value: Token }, + Literal { value: Token }, + List { items: Vec }, + FuncCall { func: Box, args: Vec } +} + +impl fmt::Debug for Expr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Binary { lhs: left, rhs: right, op} => write!(f, "({:?} {:?} {:?})", op, left, right), + Self::Unary { arg, op} => write!(f, "({:?} {:?})", op, arg), + Self::Ident { value, .. } => write!(f, "(ident {:?})", value), + Self::Literal { value, .. } => write!(f, "(lit {:?})", value), + Self::List { items } => write!(f, "(list {:?})", items), + Self::FuncCall { func, args } => write!(f, "(call {:?} {:?})", func, args), + } + } +} + +impl Expr { + pub fn is_lvalue(&self) -> bool { + matches!(self, Expr::Ident{..}) + } + + pub fn is_assignment(&self) -> bool { + matches!(self, Expr::Binary{op, ..} if op.ty.get_op_type() == Some(OpType::Assignment)) + } +} \ No newline at end of file diff --git a/src/interpreter.rs b/src/interpreter.rs new file mode 100644 index 0000000..5ce8bbb --- /dev/null +++ b/src/interpreter.rs @@ -0,0 +1,26 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::{value::Value, lexer::Lexer, parser::Parser, eval::{Environment, eval_stmt, eval_expr}, expr::Stmt}; + +pub fn interpret(src: &str, env: Option>>) -> Result> { + let mut lexer = Lexer::new(src, None); + lexer.lex()?; + let mut parser = Parser::new(lexer.into_tokens()); + let ast = parser.parse()?; + let environ; + if let Some(env) = env { + environ = env; + } else { + environ = Rc::new(RefCell::new(Environment::new())) + } + let mut result = Value::Nil; + for stmt in ast { + if let Stmt::Expr{expr} = stmt { + result = eval_expr(&expr, environ.clone())?; + } else { + eval_stmt(&stmt, environ.clone())?; + result = Value::Nil; + } + } + Ok(result) +} \ No newline at end of file diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..8320e7c --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,250 @@ +use std::rc::Rc; + +use crate::{ParserError, Position, token::{Token, TokenType}}; + +pub struct Lexer { + // name of file being lexed + filename: Option>, + line: usize, + col: usize, + tokens: Vec, + start: usize, + current: usize, + code: Vec +} + +impl Lexer { + pub fn new(code: &str, filename: Option) -> Self { + Self { filename: filename.map(|s| Rc::from(s)), line: 1, col: 1, tokens: vec![], start: 0, current: 0, code: code.chars().collect() } + } + + pub fn into_tokens(self) -> Vec { + self.tokens + } + + fn next(&mut self) -> char { + let c = self.code[self.current]; + self.advance(c == '\n'); + c + } + + fn expect(&mut self, chars: &[char]) -> Option { + for c in chars { + if self.code[self.current] == *c { + self.advance(*c == '\n'); + return Some(*c) + } + } + None + } + + fn at_end(&self) -> bool { + self.current >= self.code.len() + } + + fn peek(&self) -> char { + self.code[self.current] + } + + fn add_token(&mut self, ty: TokenType, text: S) where S: Into { + self.tokens.push(Token { + ty, + text: text.into(), + pos: Position { + file: self.filename.clone(), + pos: self.start, + line: self.line, + col: self.col - (self.current - self.start) + } + }); + } + + fn mk_error(&self, msg: S) -> ParserError where S: Into { + ParserError { + pos: Position { file: self.filename.clone(), pos: self.start, line: self.line, col: self.col}, + message: msg.into() + } + } + + fn advance(&mut self, newline: bool) { + if newline { + self.line += 1; + self.col = 1; + } else { + self.col += 1; + } + self.current += 1; + } + + pub fn lex(&mut self) -> Result<(), ParserError> { + while !self.at_end() { + self.start = self.current; + match self.next() { + '+' => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::PlusEqual, "+="), + _ => self.add_token(TokenType::Plus, "+"), + }, + '-' => match self.expect(&['=', '>']) { + Some('=') => self.add_token(TokenType::MinusEqual, "-="), + Some('>') => self.add_token(TokenType::Arrow, "->"), + _ => self.add_token(TokenType::Minus, "-"), + }, + '*' => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::StarEqual, "*="), + _ => self.add_token(TokenType::Star, "*"), + }, + '%' => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::PercentEqual, "%="), + _ => self.add_token(TokenType::Percent, "%"), + }, + '/' => match self.expect(&['=', '/']) { + Some('=') => self.add_token(TokenType::SlashEqual, "/="), + Some('/') => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::DoubleSlashEqual, "//="), + _ => self.add_token(TokenType::DoubleSlash, "//") + } + _ => self.add_token(TokenType::Slash, "/"), + }, + '^' => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::CaretEqual, "^="), + _ => self.add_token(TokenType::Caret, "^"), + }, + '=' => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::DoubleEqual, "=="), + _ => self.add_token(TokenType::Equal, "=") + }, + '!' => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::BangEqual, "!="), + _ => self.add_token(TokenType::Bang, "!") + }, + '>' => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::GreaterEqual, ">="), + _ => self.add_token(TokenType::Greater, ">") + }, + '<' => match self.expect(&['=']) { + Some('=') => self.add_token(TokenType::LessEqual, "<="), + _ => self.add_token(TokenType::Less, "<") + }, + '&' => match self.expect(&['&']) { + Some('&') => self.add_token(TokenType::DoubleAmper, "&&"), + _ => self.add_token(TokenType::Amper, "&"), + }, + '|' => match self.expect(&['|', ':', '?', '>', '&']) { + Some('|') => self.add_token(TokenType::DoublePipe, "||"), + Some(':') => self.add_token(TokenType::PipeColon, "|:"), + Some('?') => self.add_token(TokenType::PipeQuestion, "|?"), + Some('>') => self.add_token(TokenType::PipePoint, "|>"), + Some('&') => self.add_token(TokenType::PipeAmper, "|&"), + _ => self.add_token(TokenType::Pipe, "|"), + }, + ',' => self.add_token(TokenType::Comma, ","), + ';' => self.add_token(TokenType::Semicolon, ";"), + '(' => self.add_token(TokenType::LParen, "("), + ')' => self.add_token(TokenType::RParen, ")"), + '[' => self.add_token(TokenType::LBrack, "["), + ']' => self.add_token(TokenType::RBrack, "]"), + '{' => self.add_token(TokenType::LBrace, "{"), + '}' => self.add_token(TokenType::RBrace, "}"), + '#' => { + while !self.at_end() && self.peek() != '\n' { + self.advance(false); + } + self.advance(true); + }, + '"' => self.string()?, + ' ' | '\t' | '\r' | '\n' => (), + '0'..='9' => self.number()?, + 'a'..='z' | 'A'..='Z' | '_' => self.ident()?, + c => return Err(self.mk_error(format!("Unexpected character: {}", c))) + } + } + Ok(()) + } + + fn string(&mut self) -> Result<(), ParserError> { + let mut s = String::new(); + while !self.at_end() && self.peek() != '"' { + if self.peek() == '\\' { + self.advance(false); + // TODO more escape codes! \xHH, \u{HH..} or maybe \uhhhh \Uhhhhhhhh + match self.peek() { + '0' => s.push('\0'), + 'n' => s.push('\n'), + 't' => s.push('\t'), + 'r' => s.push('\r'), + 'e' => s.push('\x1b'), + '\\' => s.push('\\'), + '"' => s.push('"'), + '\n' => (), + c => return Err(self.mk_error(format!("Unknown escape code \\{}", c))) + } + self.advance(self.peek() == '\n') + } else { + s.push(self.peek()); + self.advance(self.peek() == '\n'); + } + } + if self.at_end() { + return Err(self.mk_error("Unexpected EOF while parsing string")) + } + self.advance(false); + self.add_token(TokenType::String(Rc::from(s)), self.code[self.start..self.current].iter().collect::()); + Ok(()) + } + + fn number(&mut self) -> Result<(), ParserError> { + let mut has_dot = false; + while !self.at_end() && (self.peek().is_numeric() || self.peek() == '.') { + if self.peek() == '.' { + if has_dot { + return Err(self.mk_error("Numeric literals cannot contain two decimal points")) + } else { + has_dot = true; + } + } + self.advance(false); + } + let is_imag = !self.at_end() && self.peek() == 'i'; + if is_imag { self.advance(false); } + let literal = self.code[self.start..self.current].iter().collect::(); + if is_imag { + match literal[..literal.len()-1].parse::() { + Ok(num) => self.add_token(TokenType::ImFloat(num), literal), + Err(e) => return Err(self.mk_error(format!("Error parsing float: {}", e))) + } + } else if has_dot { + match literal.parse::() { + Ok(num) => self.add_token(TokenType::Float(num), literal), + Err(e) => return Err(self.mk_error(format!("Error parsing float: {}", e))) + } + } else { + match literal.parse::() { + Ok(num) => self.add_token(TokenType::Int(num), literal), + Err(e) => return Err(self.mk_error(format!("Error parsing float: {}", e))) + } + } + Ok(()) + } + + fn ident(&mut self) -> Result<(), ParserError> { + while !self.at_end() && (self.peek().is_ascii_alphanumeric() || self.peek() == '_') { + self.advance(false); + } + let literal = self.code[self.start..self.current].iter().collect::(); + let token_ty = match literal.as_ref() { + "true" => TokenType::True, + "false" => TokenType::False, + "nil" => TokenType::Nil, + "if" => TokenType::If, + "elif" => TokenType::Elif, + "else" => TokenType::Else, + "while" => TokenType::While, + "for" => TokenType::For, + "let" => TokenType::Let, + "return" => TokenType::Return, + s => TokenType::Ident(Rc::from(s)) + }; + self.add_token(token_ty, literal); + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..09a2a83 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,55 @@ +use std::{rc::Rc, error::Error, fmt}; + +pub mod token; +pub mod expr; +pub mod lexer; +pub mod parser; +pub mod value; +pub mod eval; +pub mod interpreter; + +#[derive(Clone, Debug)] +pub struct Position { + pub pos: usize, + pub line: usize, + pub col: usize, + pub file: Option> +} + + +#[derive(Debug)] +pub struct ParserError { + pub message: String, + pub pos: Position +} + +#[derive(Debug)] +pub struct RuntimeError { + pub message: String, + pub pos: Position +} + +impl fmt::Display for ParserError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error: {}\n In {} at {},{}", + self.message, + self.pos.file.as_ref().map(|o| o.as_ref()).unwrap_or(""), + self.pos.line, + self.pos.col + ) + } +} + +impl fmt::Display for RuntimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Error: {}\n In {} at {},{}", + self.message, + self.pos.file.as_ref().map(|o| o.as_ref()).unwrap_or(""), + self.pos.line, + self.pos.col + ) + } +} + +impl Error for ParserError {} +impl Error for RuntimeError {} \ No newline at end of file diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..db9f33f --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,231 @@ +use crate::{token::{Token, TokenType, OpType}, ParserError, expr::{Stmt, Expr}}; + +pub struct Parser { + tokens: Vec, + idx: usize +} + +impl Parser { + pub fn new(tokens: Vec) -> Self { + Self { tokens, idx: 0 } + } + + fn at_end(&self) -> bool { + self.idx >= self.tokens.len() + } + + fn peek(&self) -> &Token { + &self.tokens[self.idx] + } + + fn next(&mut self) -> Token { + let t = self.tokens[self.idx].clone(); + self.idx += 1; + t + } + + fn mk_error(&self, msg: S) -> ParserError where S: Into { + let token = if self.at_end() { + self.tokens.last().unwrap() + } else { + self.peek() + }; + ParserError { pos: token.pos.clone(), message: msg.into() } + } + + fn err_on_eof(&self) -> Result<(), ParserError> { + if self.at_end() { + Err(self.mk_error("Unexpected EOF")) + } else { + Ok(()) + } + } + + pub fn parse(&mut self) -> Result, ParserError> { + let mut stmts = vec![]; + while !self.at_end() { + stmts.push(self.statement()?); + } + Ok(stmts) + } + + fn statement(&mut self) -> Result { + let next = self.peek(); + + match next.ty { + // let statement + TokenType::Let => { + self.next(); + let expr = self.assignment()?; + + // must be followed by an assignment expression + if let Expr::Binary{lhs, rhs, op: Token{ty: TokenType::Equal,..}} = expr { + if let Expr::Ident{value: tok} = *lhs { + if self.at_end() { + return Ok(Stmt::Let{lhs: tok, rhs: Some(*rhs)}) + } + let next = self.next(); + return match next.ty { + TokenType::Semicolon => Ok(Stmt::Let{lhs: tok, rhs: Some(*rhs)}), + _ => Err(self.mk_error("Missing semicolon after 'let' statement".to_owned())) + }; + } else { + Err(self.mk_error("Invalid expression after 'let'".to_owned())) + } + } else if let Expr::Ident{value: tok} = expr { + if self.at_end() { + return Ok(Stmt::Let{lhs: tok, rhs: None}) + } + let next = self.next(); + return match next.ty { + TokenType::Semicolon => Ok(Stmt::Let{lhs: tok, rhs: None}), + _ => Err(self.mk_error("Missing semicolon after 'let' statement".to_owned())) + }; + } else { + Err(self.mk_error("Invalid expression after 'let'".to_owned())) + } + } + + // if statement + TokenType::If => { + self.next(); + return self.ifstmt() + } + + // fallback to an expression terminated with a semicolon + _ => { + let expr = self.assignment()?; + if self.at_end() { + return Ok(Stmt::Expr{expr}) + } + + let next = self.next(); + + return match next.ty { + TokenType::Semicolon => Ok(Stmt::Expr{expr}), + _ => Err(self.mk_error("Missing semicolon after statement")) + }; + } + } + } + + // Generic method for left-associative operators + fn expr(&mut self, op_type: OpType, next_level: fn(&mut Parser) -> Result) -> Result { + let mut expr = next_level(self)?; + while !self.at_end() && self.peek().ty.get_op_type() == Some(op_type) { + let op = self.next(); + let right = next_level(self)?; + expr = Expr::Binary { lhs: Box::new(expr), rhs: Box::new(right), op }; + } + Ok(expr) + } + + fn commalist(&mut self, terminator: TokenType, parse_item: fn(&mut Parser) -> Result) -> Result, ParserError> { + let mut items = vec![]; + while self.peek().ty != terminator { + let expr = parse_item(self)?; + items.push(expr); + if self.peek().ty == TokenType::Comma { + self.next(); + } else if self.peek().ty == terminator { + break; + } else { + return Err(self.mk_error(format!("Expected Comma or {:?} after list", terminator))) + } + } + self.next(); + Ok(items) + } + + fn assignment(&mut self) -> Result { + let mut stack= vec![]; + let mut expr = self.pipeline()?; + while !self.at_end() && self.peek().ty.get_op_type() == Some(OpType::Assignment) { + let op = self.next(); + stack.push((expr, op)); + expr = self.pipeline()?; + } + while let Some(item) = stack.pop() { + if !item.0.is_lvalue() { + return Err(self.mk_error("Invalid LValue for assignment operation")) + } + expr = Expr::Binary{ lhs: Box::new(item.0), rhs: Box::new(expr), op: item.1 }; + } + Ok(expr) + } + + fn pipeline(&mut self) -> Result { + self.expr(OpType::Pipeline, Self::additive) + } + + fn additive(&mut self) -> Result { + self.expr(OpType::Additive, Self::multiplicative) + } + + fn multiplicative(&mut self) -> Result { + self.expr(OpType::Multiplicative, Self::exponential) + } + + // Right associative, so cannot use self.expr(..) + fn exponential(&mut self) -> Result { + let mut stack= vec![]; + let mut expr = self.unary()?; + while !self.at_end() && self.peek().ty == TokenType::Caret { + let op = self.next(); + stack.push((expr, op)); + expr = self.unary()?; + } + while let Some(item) = stack.pop() { + expr = Expr::Binary{ lhs: Box::new(item.0), rhs: Box::new(expr), op: item.1 }; + } + Ok(expr) + } + + fn unary(&mut self) -> Result { + self.err_on_eof()?; + if matches!(self.peek().ty, TokenType::Bang | TokenType::Minus) { + let op = self.next(); + Ok(Expr::Unary { arg: Box::new(self.fncall()?), op }) + } else { + self.fncall() + } + } + + fn fncall(&mut self) -> Result { + let expr = self.expr_base()?; + if !self.at_end() && self.peek().ty == TokenType::LParen { + self.next(); + let args = self.commalist(TokenType::RParen, Self::assignment)?; + Ok(Expr::FuncCall { func: Box::new(expr), args }) + } else { + Ok(expr) + } + } + + fn expr_base(&mut self) -> Result { + self.err_on_eof()?; + let next = self.next(); + if matches!(next.ty, + TokenType::True | TokenType::False | TokenType::Nil + | TokenType::Int(_) | TokenType::Float(_) | TokenType::ImFloat(_) + | TokenType::String(_) + ) { + Ok(Expr::Literal { value: next }) + } else if let TokenType::Ident(..) = next.ty { + Ok(Expr::Ident { value: next }) + } else if next.ty == TokenType::LParen { + let expr = self.assignment()?; + if self.at_end() || TokenType::RParen != self.next().ty { + Err(self.mk_error("Left parenthesis never closed")) + } else { + Ok(expr) + } + } else { + Err(self.mk_error(format!("Unexpected token: {:?}", next.ty))) + } + } + + fn ifstmt(&mut self) -> Result { + todo!() + } +} \ No newline at end of file diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 0000000..2e9199c --- /dev/null +++ b/src/token.rs @@ -0,0 +1,70 @@ +use std::{fmt, rc::Rc}; + +use crate::Position; + + +#[derive(Clone)] +pub struct Token { + pub ty: TokenType, + pub text: String, + pub pos: Position +} + +impl fmt::Debug for Token { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?} @ {},{}", self.ty, self.pos.line, self.pos.col) + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum TokenType { + Int(i64), Float(f64), ImFloat(f64), String(Rc), + Ident(Rc), + + Plus, Minus, Star, Slash, Percent, DoubleSlash, Caret, + Bang, Amper, Pipe, DoubleAmper, DoublePipe, + + Equal, PlusEqual, MinusEqual, StarEqual, SlashEqual, PercentEqual, DoubleSlashEqual, CaretEqual, + DoubleEqual, BangEqual, Greater, GreaterEqual, Less, LessEqual, + + Arrow, PipeColon, PipePoint, PipeQuestion, PipeAmper, + + Comma, Semicolon, + + LParen, RParen, LBrack, RBrack, LBrace, RBrace, + + True, False, Nil, If, Elif, Else, For, While, Let, Return +} + +impl TokenType { + pub fn get_op_type(&self) -> Option { + match self { + Self::Plus | Self::Minus => Some(OpType::Additive), + + Self::Star | Self::Slash | Self::DoubleSlash + | Self::Percent => Some(OpType::Multiplicative), + + Self::Caret => Some(OpType::Exponential), + + Self::PipeColon | Self::PipeAmper | Self::PipePoint + | Self::PipeQuestion => Some(OpType::Pipeline), + + Self::Equal | Self::PlusEqual | Self::MinusEqual + | Self::StarEqual | Self::SlashEqual | Self::DoubleSlashEqual + | Self::CaretEqual | Self::PercentEqual => Some(OpType::Assignment), + + _ => None + } + } +} + +#[derive(Clone,Copy,Debug,PartialEq)] +pub enum OpType { + Assignment, Pipeline, Additive, Multiplicative, Exponential +} + +impl OpType { + pub fn is_right_associative(&self) -> bool { + matches!(self, OpType::Exponential | OpType::Assignment) + } +} \ No newline at end of file diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..af00ab3 --- /dev/null +++ b/src/value.rs @@ -0,0 +1,80 @@ +use std::{rc::Rc, collections::HashMap, ops::*}; + +type Rational = num_rational::Ratio; +type Complex = num_complex::Complex64; + +macro_rules! value_from { + ($variant:ident, $($kind:ty)*) => { + $( + impl From<$kind> for Value { + fn from(x: $kind) -> Self { + Self::$variant(x.into()) + } + } + )* + }; +} + +macro_rules! impl_numeric_op { + ($optrait:ty, $fnname:ident, { $($bonus:tt)* }) => { + impl $optrait for &Value { + type Output = Result; + fn $fnname(self, other: Self) -> Self::Output { + use Value::*; + use num_traits::ToPrimitive; + const RATIO_CAST_FAIL: &'static str = "Failed to cast Rational to Float"; + match (self, other) { + (Int(a), Int(b)) => Ok(a.$fnname(b).into()), + (Rational(a), Int(b)) => Ok(a.$fnname(b).into()), + (Int(a), Rational(b)) => Ok(self::Rational::from(*a).$fnname(b).into()), + (Float(a), Int(b)) => Ok(a.$fnname(*b as f64).into()), + (Int(a), Float(b)) => Ok((*a as f64).$fnname(b).into()), + (Float(a), Rational(b)) => Ok(a.$fnname(b.to_f64().ok_or(RATIO_CAST_FAIL)?).into()), + (Rational(a), Float(b)) => Ok(a.to_f64().ok_or(RATIO_CAST_FAIL)?.$fnname(b).into()), + (Float(a), Float(b)) => Ok(a.$fnname(b).into()), + (Int(a), Complex(b)) => Ok(self::Complex::from(*a as f64).$fnname(b).into()), + (Complex(a), Int(b)) => Ok(a.$fnname(self::Complex::from(*b as f64)).into()), + (Float(a), Complex(b)) => Ok(self::Complex::from(a).$fnname(b).into()), + (Complex(a), Float(b)) => Ok(a.$fnname(self::Complex::from(b)).into()), + (Rational(a), Complex(b)) => Ok(self::Complex::from(a.to_f64().ok_or(RATIO_CAST_FAIL)?).$fnname(b).into()), + (Complex(a), Rational(b)) => Ok(a.$fnname(self::Complex::from(b.to_f64().ok_or(RATIO_CAST_FAIL)?)).into()), + (Complex(a), Complex(b)) => Ok(a.$fnname(b).into()), + $($bonus)* + (lhs, rhs) => Err(format!("Unsupported operation '{}' between {:?} and {:?}", stringify!($fnname), lhs, rhs)) + } + } + } + } +} + +#[derive(Clone, Debug)] +pub enum Value { + Nil, + Int(i64), Float(f64), Complex(Complex), Rational(Rational), + Bool(bool), + String(Rc), + List(Rc>), Map(Rc>), +} + + +value_from!(Int, u8 u16 u32 i8 i16 i32 i64); +value_from!(Float, f32 f64); +value_from!(Complex, Complex); +value_from!(Rational, Rational); +value_from!(Bool, bool); +value_from!(String, String Rc); +value_from!(List, Vec); +value_from!(Map, HashMap); + +impl_numeric_op!(Add, add, { + (String(a), String(b)) => Ok(((**a).to_owned() + b).into()), + (List(a), List(b)) => { + let mut a = (**a).clone(); + a.append(&mut (**b).clone()); + Ok(a.into()) + }, +}); +impl_numeric_op!(Sub, sub, {}); +impl_numeric_op!(Mul, mul, {}); +impl_numeric_op!(Div, div, {}); +impl_numeric_op!(Rem, rem, {}); \ No newline at end of file diff --git a/tests/code.cxpr b/tests/code.cxpr new file mode 100644 index 0000000..7f306a8 --- /dev/null +++ b/tests/code.cxpr @@ -0,0 +1,3 @@ +let a = 1; +let b = 2; +a = a + b; \ No newline at end of file diff --git a/tests/test.rs b/tests/test.rs new file mode 100644 index 0000000..00c415d --- /dev/null +++ b/tests/test.rs @@ -0,0 +1,19 @@ +#![cfg(test)] + +use std::{cell::RefCell, rc::Rc}; + +use complexpr::{lexer::Lexer, parser::Parser, eval::{Environment, eval_stmt}}; + +#[test] +pub fn test() { + let mut lexer = Lexer::new("let a = 1 + 1; let b = a + 1;", None); + lexer.lex().unwrap(); + let mut parser = Parser::new(lexer.into_tokens()); + let ast = parser.parse().unwrap(); + let env = Rc::new(RefCell::new(Environment::new())); + for stmt in ast { + eval_stmt(&stmt, env.clone()).unwrap(); + } + println!("{:?}", env); + todo!("end of tests") +} \ No newline at end of file