initial commit

This commit is contained in:
TriMill 2022-09-06 09:52:29 -04:00
commit 59fa4bad6b
15 changed files with 1441 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/.vscode

468
Cargo.lock generated Normal file
View file

@ -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"

13
Cargo.toml Normal file
View file

@ -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"

28
idea.cxpr Normal file
View file

@ -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);
}

37
src/bin/main.rs Normal file
View file

@ -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<dyn std::error::Error>> {
let args: Vec<String> = 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<dyn std::error::Error>> {
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(())
}

108
src/eval.rs Normal file
View file

@ -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<Rc<RefCell<Environment>>>,
map: HashMap<Rc<str>, Value>
}
impl Environment {
pub fn new() -> Self {
Self { parent: None, map: HashMap::new() }
}
pub fn extend(parent: Rc<RefCell<Self>>) -> Self {
Self { parent: Some(parent), map: HashMap::new() }
}
pub fn get(&self, name: &str) -> Result<Value,()> {
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<str>, value: Value) {
self.map.insert(name, value);
}
pub fn set(&mut self, name: Rc<str>, 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<RefCell<Environment>>) -> 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<RefCell<Environment>>) -> Result<Value, String> {
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
}
}

51
src/expr.rs Normal file
View file

@ -0,0 +1,51 @@
use std::fmt;
use crate::{token::{Token, OpType}};
pub enum Stmt {
Expr { expr: Expr },
Let { lhs: Token, rhs: Option<Expr> },
If { conditions: Vec<Expr>, bodies: Vec<Vec<Stmt>>, else_clause: Option<Vec<Stmt>> }
}
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<Expr>, rhs: Box<Expr>, op: Token },
Unary { arg: Box<Expr>, op: Token },
Ident { value: Token },
Literal { value: Token },
List { items: Vec<Expr> },
FuncCall { func: Box<Expr>, args: Vec<Expr> }
}
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))
}
}

26
src/interpreter.rs Normal file
View file

@ -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<Rc<RefCell<Environment>>>) -> Result<Value, Box<dyn std::error::Error>> {
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)
}

250
src/lexer.rs Normal file
View file

@ -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<Rc<str>>,
line: usize,
col: usize,
tokens: Vec<Token>,
start: usize,
current: usize,
code: Vec<char>
}
impl Lexer {
pub fn new(code: &str, filename: Option<String>) -> 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<Token> {
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<char> {
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<S>(&mut self, ty: TokenType, text: S) where S: Into<String> {
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<S>(&self, msg: S) -> ParserError where S: Into<String> {
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::<String>());
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::<String>();
if is_imag {
match literal[..literal.len()-1].parse::<f64>() {
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::<f64>() {
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::<i64>() {
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::<String>();
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(())
}
}

55
src/lib.rs Normal file
View file

@ -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<Rc<str>>
}
#[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("<unknown>"),
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("<unknown>"),
self.pos.line,
self.pos.col
)
}
}
impl Error for ParserError {}
impl Error for RuntimeError {}

231
src/parser.rs Normal file
View file

@ -0,0 +1,231 @@
use crate::{token::{Token, TokenType, OpType}, ParserError, expr::{Stmt, Expr}};
pub struct Parser {
tokens: Vec<Token>,
idx: usize
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> 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<S>(&self, msg: S) -> ParserError where S: Into<String> {
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<Vec<Stmt>, ParserError> {
let mut stmts = vec![];
while !self.at_end() {
stmts.push(self.statement()?);
}
Ok(stmts)
}
fn statement(&mut self) -> Result<Stmt, ParserError> {
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<Expr, ParserError>) -> Result<Expr, ParserError> {
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<Expr, ParserError>) -> Result<Vec<Expr>, 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<Expr, ParserError> {
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<Expr, ParserError> {
self.expr(OpType::Pipeline, Self::additive)
}
fn additive(&mut self) -> Result<Expr, ParserError> {
self.expr(OpType::Additive, Self::multiplicative)
}
fn multiplicative(&mut self) -> Result<Expr, ParserError> {
self.expr(OpType::Multiplicative, Self::exponential)
}
// Right associative, so cannot use self.expr(..)
fn exponential(&mut self) -> Result<Expr, ParserError> {
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<Expr, ParserError> {
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<Expr, ParserError> {
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<Expr, ParserError> {
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<Stmt, ParserError> {
todo!()
}
}

70
src/token.rs Normal file
View file

@ -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<str>),
Ident(Rc<str>),
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<OpType> {
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)
}
}

80
src/value.rs Normal file
View file

@ -0,0 +1,80 @@
use std::{rc::Rc, collections::HashMap, ops::*};
type Rational = num_rational::Ratio<i64>;
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<Value, String>;
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<str>),
List(Rc<Vec<Value>>), Map(Rc<HashMap<Value,Value>>),
}
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<str>);
value_from!(List, Vec<Value>);
value_from!(Map, HashMap<Value,Value>);
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, {});

3
tests/code.cxpr Normal file
View file

@ -0,0 +1,3 @@
let a = 1;
let b = 2;
a = a + b;

19
tests/test.rs Normal file
View file

@ -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")
}