From c66a193a6e025ae3a65d90b26f38792d3b15159b Mon Sep 17 00:00:00 2001 From: me Date: Sat, 13 Dec 2025 23:46:36 +0200 Subject: [PATCH] updates and lexing --- Cargo.lock | 201 +++++++++++++++++++++++- Cargo.toml | 2 + Makefile | 4 + readme.md | 8 +- src/ast/helpers.rs | 11 +- src/ast/types.rs | 97 ++++-------- src/interpret/interpret.rs | 292 ++++++++++++++++++++++++++++++++++ src/interpret/mod.rs | 313 +------------------------------------ src/interpret/types.rs | 55 +------ src/runtime/mod.rs | 1 + src/runtime/parser.rs | 205 ++++++++++++++++++++++++ src/runtime/runtime.rs | 2 + src/runtime/types.rs | 2 +- 13 files changed, 746 insertions(+), 447 deletions(-) create mode 100644 src/interpret/interpret.rs create mode 100644 src/runtime/parser.rs diff --git a/Cargo.lock b/Cargo.lock index 9975e2a..eb5622d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,7 +59,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -70,7 +70,16 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "ar_archive_writer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +dependencies = [ + "object", ] [[package]] @@ -83,8 +92,10 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" name = "ayin-lang" version = "0.1.0" dependencies = [ + "chumsky", "env_logger", "log", + "lyn", "macroquad", "thiserror", ] @@ -107,12 +118,36 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "chumsky" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acc17a6284abccac6e50db35c1cee87f605474a72939b959a3a67d9371800efd" +dependencies = [ + "hashbrown", + "regex-automata 0.3.9", + "serde", + "stacker", + "unicode-ident", + "unicode-segmentation", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -172,6 +207,12 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "flate2" version = "1.1.5" @@ -270,6 +311,12 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "lyn" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f08d9299a146aa1eb3c5451e6d9045708f232a43fd8d4436f69cf00fcb2a019c" + [[package]] name = "macroquad" version = "0.4.14" @@ -351,6 +398,15 @@ dependencies = [ "malloc_buf", ] +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell_polyfill" version = "1.70.2" @@ -394,6 +450,16 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "psm" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +dependencies = [ + "ar_archive_writer", + "cc", +] + [[package]] name = "quad-rand" version = "0.2.3" @@ -417,8 +483,19 @@ checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", - "regex-automata", - "regex-syntax", + "regex-automata 0.4.13", + "regex-syntax 0.8.8", +] + +[[package]] +name = "regex-automata" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", ] [[package]] @@ -429,15 +506,31 @@ checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.8", ] +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "regex-syntax" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -458,12 +551,31 @@ dependencies = [ "syn", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "simd-adler32" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "stacker" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1f8b29fb42aafcea4edeeb6b2f2d7ecd0d969c48b4cf0d2e64aafc471dd6e59" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", +] + [[package]] name = "syn" version = "2.0.111" @@ -507,6 +619,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "utf8parse" version = "0.2.2" @@ -541,6 +659,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -549,3 +676,67 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index d5ab67a..2ccae2c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,8 @@ thiserror = "1" env_logger = "0.11.8" log = "0.4.27" macroquad = "0.4.14" +lyn = "0.1.0" +chumsky = "0.11.2" [workspace.lints.clippy] all = { level = "warn", priority = -1 } diff --git a/Makefile b/Makefile index e559ab7..45d213b 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,10 @@ watch: cargo watch --clear -x 'clippy' -x 'test --all' +.PHONY: build +build: + cargo build + .PHONY: run run: cargo run diff --git a/readme.md b/readme.md index fe837b2..0281dc2 100644 --- a/readme.md +++ b/readme.md @@ -13,21 +13,21 @@ Ayin - first class functions ``` -fn init() { +let init = fn() { return { player: { position: { x: 10, y: 20 }, }, } } -fn update(state, events) { +let update = fn(state, events) { return state'; } -fn draw(frame, state) { +let draw = fn(frame, state) { frame.clear(0,0,0); } -fn migrate(state) { +let migrate = fn(state) { return { player: { pos: state.player.position } }, } ``` diff --git a/src/ast/helpers.rs b/src/ast/helpers.rs index a65629c..e38887a 100644 --- a/src/ast/helpers.rs +++ b/src/ast/helpers.rs @@ -12,7 +12,7 @@ pub fn var(str: &str) -> Expr { Expr::Var(name(str)) } -pub fn record(rec: Vec<(&str, Value)>) -> Value { +pub fn record(rec: Vec<(&str, Expr)>) -> Value { Value::Record(Record({ rec.into_iter() .map(|(lbl, val)| (Label(lbl.into()), val)) @@ -20,15 +20,12 @@ pub fn record(rec: Vec<(&str, Value)>) -> Value { })) } -pub fn record_expr(rec: Vec<(&str, Value)>) -> Expr { +pub fn record_expr(rec: Vec<(&str, Expr)>) -> Expr { Expr::Value(record(rec)) } pub fn assign(name: &str, expr: Expr) -> Statement { - Statement::Assign { - name: name.into(), - expr, - } + Statement::Assign(define_expr(name, expr)) } pub fn stmt_expr(expr: Expr) -> Statement { @@ -36,7 +33,7 @@ pub fn stmt_expr(expr: Expr) -> Statement { } pub fn define_expr(name: &str, expr: Expr) -> Definition { - Definition::Expr { + Definition { name: name.into(), expr, } diff --git a/src/ast/types.rs b/src/ast/types.rs index 2c5e03b..09aebbe 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -7,15 +7,6 @@ use std::collections::BTreeMap; pub enum Expr { Value(Value), Var(Name), - Access { - expr: Box, - field: Label, - }, - MethodCall { - expr: Box, - method: MethodName, - args: Vec, - }, Func(Box), FunCall { func: Box, @@ -27,13 +18,31 @@ pub enum Expr { then: Box, r#else: Box, }, + Access { + expr: Box, + field: Label, + }, + /* + Loop { + body: Box, + }, + Continue, + Break, + Return(Box), + */ } /// A statement. #[derive(PartialEq, PartialOrd, Debug, Clone)] pub enum Statement { Expr(Expr), - Assign { name: Name, expr: Expr }, + Assign(Definition), +} + +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub struct Definition { + pub name: Name, + pub expr: Expr, } /// A reduced value. @@ -44,8 +53,8 @@ pub enum Value { String(String), Boolean(bool), Record(Record), - Closure { func: Box, env: EnvName }, - Deferred { expr: Box, env: EnvName }, + Vector(Vector), + Closure { env: EnvName, expr: Box }, } /// An anonymous function. @@ -59,28 +68,12 @@ pub struct Fn { #[derive(PartialEq, PartialOrd, Debug, Clone)] pub struct Arg { pub name: Name, - pub r#type: Type, -} - -/// A type. -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] -pub enum Type { - Named(TypeName), - App { typefun: Name, args: Vec }, } /// A symbol. #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] pub struct Name(pub String); -/// A type name. -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] -pub struct TypeName(pub String); - -/// A method name. -#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] -pub struct MethodName(pub String); - /// A field. #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] pub struct Label(pub String); @@ -90,50 +83,25 @@ pub struct Label(pub String); pub struct EnvName(pub String); #[derive(PartialEq, PartialOrd, Debug, Clone)] -pub struct Record(pub BTreeMap); +pub struct Record(pub BTreeMap); + +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub struct Vector(pub Vec); /// A Program. #[derive(PartialEq, PartialOrd, Debug)] pub struct Program(pub Vec); -/// A definition. -#[derive(PartialEq, PartialOrd, Debug)] -pub enum Definition { - Function { name: Name, func: Fn }, - Expr { name: Name, expr: Expr }, -} - // ----- // // Impls // // ----- // -impl Definition { - pub fn name(&self) -> &Name { - match self { - Definition::Function { name, .. } => name, - Definition::Expr { name, .. } => name, - } - } -} - impl From<&str> for Name { fn from(value: &str) -> Name { Name(value.into()) } } -impl From<&str> for TypeName { - fn from(value: &str) -> TypeName { - TypeName(value.into()) - } -} - -impl From<&str> for MethodName { - fn from(value: &str) -> MethodName { - MethodName(value.into()) - } -} - impl From<&str> for Label { fn from(value: &str) -> Label { Label(value.into()) @@ -146,26 +114,23 @@ impl From<&str> for Expr { } } -impl From> for Value { - fn from(rec: Vec<(&str, Value)>) -> Value { +impl From> for Value { + fn from(rec: Vec<(&str, Expr)>) -> Value { Value::Record(Record({ rec.into_iter() - .map(|(lbl, val)| (Label(lbl.into()), val)) + .map(|(lbl, expr)| (Label(lbl.into()), expr)) .collect() })) } } -impl From> for Expr { - fn from(rec: Vec<(&str, Value)>) -> Expr { +impl From> for Expr { + fn from(rec: Vec<(&str, Expr)>) -> Expr { Expr::Value(rec.into()) } } impl Expr { - pub fn get_type(&self) -> &Type { - todo!() - } pub fn field(self, label: &str) -> Expr { Expr::Access { expr: Box::new(self), @@ -217,7 +182,7 @@ impl From> for Expr { } impl Record { - pub fn get(&self, label: &Label) -> Result<&Value, Error> { + pub fn get(&self, label: &Label) -> Result<&Expr, Error> { match self.0.get(label) { Some(v) => Ok(v), None => Err(Error::LabelNotFound(label.clone())), diff --git a/src/interpret/interpret.rs b/src/interpret/interpret.rs new file mode 100644 index 0000000..02d1be8 --- /dev/null +++ b/src/interpret/interpret.rs @@ -0,0 +1,292 @@ +//! Interpreter for Ayin. + +use super::types::*; + +use crate::ast; + +pub fn run( + program: ast::Program, + func: ast::Name, + args: Vec, +) -> Result { + let env_name = ast::EnvName("global".to_string()); + let mut state = State::new("global".into()); + + defs_to_env(program.0, &env_name, &mut state.envs)?; + + let main = ast::Expr::Value(state.envs.get(&env_name)?.get(&func)?.clone()); + + let expr = ast::Expr::FunCall { + func: Box::new(main), + args, + }; + + let env: Env = state.envs.get(&env_name)?.clone(); + let result = eval_expr(&env, &mut state, &expr)?; + + Ok(result) +} + +fn eval_statement( + expr_env: &mut Env, + state: &mut State, + statement: &ast::Statement, +) -> Result { + match statement { + ast::Statement::Expr(expr) => eval_expr(expr_env, state, expr), + ast::Statement::Assign(ast::Definition { name, expr }) => { + let result = eval_expr(expr_env, state, expr)?; + expr_env.insert(name.clone(), result.clone()); + Ok(result) + } + } +} + +fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result { + match expr { + ast::Expr::If { + condition, + then, + r#else, + } => match eval_expr(expr_env, state, condition)? { + ast::Value::Boolean(condition) => { + if condition { + eval_expr(expr_env, state, then) + } else { + eval_expr(expr_env, state, r#else) + } + } + v => Err(Error::NotABoolean(v)), + }, + ast::Expr::Func(func) => { + let env_name = state.generate_env(expr_env.clone())?; + Ok(ast::Value::Closure { + expr: Box::new(ast::Expr::Func(func.clone())), + env: env_name, + }) + } + ast::Expr::Block(statements) => { + let mut block_env = expr_env.clone(); + if let Some(last) = statements.iter().try_fold(None, |_, statement| { + let result = eval_statement(&mut block_env, state, statement)?; + Ok::, Error>(Some(result)) + })? { + Ok(last) + } else { + Err(Error::LastStatementNotAnExpr) + } + } + ast::Expr::Access { expr, field } => match eval_expr(expr_env, state, expr)? { + ast::Value::Record(record) => eval_expr( + expr_env, + state, + &record.get(field).map_err(Error::from)?.clone(), + ), + v => Err(Error::NotARecord(v)), + }, + ast::Expr::Var(var) => { + let value = expr_env.get(var)?.clone(); + eval_expr(expr_env, state, &ast::Expr::Value(value)) + } + + ast::Expr::FunCall { func, args } => match eval_expr(expr_env, state, func)? { + ast::Value::Closure { expr, env } => match *expr { + ast::Expr::Func(func) => { + let mut closure_env: Env = state.envs.get(&env)?.clone(); + + if func.args.len() != args.len() { + Err(Error::ArgumentsMismatch)?; + } + + for (arg, e) in func.args.into_iter().zip(args.iter()) { + let evalled = eval_expr(&closure_env, state, e)?; + closure_env.0.insert(arg.name, evalled); + } + eval_expr(&closure_env, state, &func.body) + } + e => Err(Error::NotAFunction(e)), + }, + e => Err(Error::NotAFunction(ast::Expr::Value(e))), + }, + ast::Expr::Value(v) => match v { + ast::Value::Closure { expr, env } => { + let closure_env: Env = state.envs.get(env)?.clone(); + eval_expr(&closure_env, state, expr) + } + _ => Ok(v.clone()), + }, + } +} + +fn defs_to_env( + defs: Vec, + env_name: &ast::EnvName, + envs: &mut Envs, +) -> Result<(), Error> { + let mut env = Env::new(); + + for def in defs { + let (name, closure) = match def { + ast::Definition { expr, name } => ( + name, + ast::Value::Closure { + expr: Box::new(expr.clone()), + env: env_name.clone(), + }, + ), + }; + env.insert_nodup(&name, closure)?; + } + + envs.0.insert(env_name.clone(), env); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::helpers; + + #[test] + fn main_0() { + let program = vec![helpers::define_expr("main", 0.into())].into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + #[test] + fn var_lookup() { + let program = vec![ + helpers::define_expr("main", "lit".into()), + helpers::define_expr("lit", 0.into()), + ] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + #[test] + fn var_assign_and_lookup() { + let program = vec![helpers::define_expr( + "main", + vec![ + helpers::assign("zero", 0.into()), + helpers::stmt_expr("zero".into()), + ] + .into(), + )] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + + #[test] + fn field_access() { + let program = vec![ + helpers::define_expr("main", "record".into()), + helpers::define_expr( + "record", + ast::Expr::from(vec![("my_field", 0.into())]).field("my_field"), + ), + ] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + + #[test] + fn fun_call() { + let program = vec![ + helpers::define_expr("main", "zero".into()), + helpers::define_expr( + "zero", + helpers::func(ast::Fn { + args: vec![], + body: 0.into(), + }) + .call(vec![]), + ), + ] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + + #[test] + fn if_then_else() { + let program = vec![helpers::define_expr( + "main", + ast::Expr::If { + condition: Box::new(false.into()), + then: Box::new(0.into()), + r#else: Box::new(ast::Expr::If { + condition: Box::new(true.into()), + then: Box::new(1.into()), + r#else: Box::new(2.into()), + }), + }, + )] + .into(); + let result = run(program); + assert_eq!(result, Ok(1.into())); + } + + #[test] + fn fun_call_args() { + let program = vec![ + helpers::define_expr("main", "zero".into()), + helpers::define_expr( + "zero", + helpers::func(ast::Fn { + args: vec![ + ast::Arg { + name: "a".into(), + r#type: ast::Type::Named("i64".into()), + }, + ast::Arg { + name: "b".into(), + r#type: ast::Type::Named("i64".into()), + }, + ], + body: "b".into(), + }) + .call(vec![1.into(), 0.into()]), + ), + ] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + + // Errors + + #[test] + fn duplicate_toplevel_defs() { + let program = vec![ + helpers::define_expr("main", "record".into()), + helpers::define_expr("main", 0.into()), + ] + .into(); + let result = run(program); + assert_eq!(result, Err(Error::DuplicateNames("main".into()))); + } + + #[test] + fn field_access_not_a_record() { + let program = vec![ + helpers::define_expr("main", "record".into()), + helpers::define_expr("record", ast::Expr::from(0).field("my_field")), + ] + .into(); + let result = run(program); + assert_eq!(result, Err(Error::NotARecord(0.into()))); + } + #[test] + fn fun_call_not_a_function() { + let program = vec![ + helpers::define_expr("main", "zero".into()), + helpers::define_expr("zero", ast::Expr::from(0).call(vec![1.into()])), + ] + .into(); + let result = run(program); + assert_eq!(result, Err(Error::NotAFunction(0.into()))); + } +} diff --git a/src/interpret/mod.rs b/src/interpret/mod.rs index fa635a8..a84e1c0 100644 --- a/src/interpret/mod.rs +++ b/src/interpret/mod.rs @@ -1,313 +1,6 @@ -//! Interpreter for Z. +//! Interpreter for Ayin. +pub mod interpret; pub mod types; -use types::*; - -use crate::ast; - -pub fn run(program: ast::Program) -> Result { - let env_name = ast::EnvName("global".to_string()); - let mut state = State::new("global".into()); - - defs_to_env(program.0, &env_name, &mut state.envs)?; - - let main = ast::Expr::Value(state.envs.get(&env_name)?.get(&"main".into())?.clone()); - - let env: Env = state.envs.get(&env_name)?.clone(); - let result = eval_expr(&env, &mut state, &main)?; - println!("{:?}", result); - - Ok(result) -} - -fn eval_statement( - expr_env: &mut Env, - state: &mut State, - statement: &ast::Statement, -) -> Result { - match statement { - ast::Statement::Expr(expr) => eval_expr(expr_env, state, expr), - ast::Statement::Assign { name, expr } => { - let result = eval_expr(expr_env, state, expr)?; - expr_env.insert(name.clone(), result.clone()); - Ok(result) - } - } -} - -fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result { - match expr { - ast::Expr::If { - condition, - then, - r#else, - } => match eval_expr(expr_env, state, condition)? { - ast::Value::Boolean(condition) => { - if condition { - eval_expr(expr_env, state, then) - } else { - eval_expr(expr_env, state, r#else) - } - } - v => Err(Error::NotABoolean(v)), - }, - ast::Expr::Func(func) => { - let env_name = state.generate_env(expr_env.clone())?; - Ok(ast::Value::Closure { - func: func.clone(), - env: env_name, - }) - } - ast::Expr::Block(statements) => { - let mut block_env = expr_env.clone(); - if let Some(last) = statements.iter().try_fold(None, |_, statement| { - let result = eval_statement(&mut block_env, state, statement)?; - Ok::, Error>(Some(result)) - })? { - Ok(last) - } else { - Err(Error::LastStatementNotAnExpr) - } - } - ast::Expr::Access { expr, field } => match eval_expr(expr_env, state, expr)? { - ast::Value::Record(record) => eval_expr( - expr_env, - state, - &ast::Expr::Value(record.get(field).map_err(Error::from)?.clone()), - ), - v => Err(Error::NotARecord(v)), - }, - ast::Expr::Var(var) => { - let value = expr_env.get(var)?.clone(); - eval_expr(expr_env, state, &ast::Expr::Value(value)) - } - ast::Expr::MethodCall { expr, method, args } => { - let r#type = expr.get_type(); - let method = state.method_env.get(r#type, method)?; - match eval_expr(expr_env, state, &ast::helpers::func(method.clone()))? { - ast::Value::Closure { func, env } => { - let mut closure_env: Env = state.envs.get(&env)?.clone(); - - if func.args.len() != args.len() + 1 { - Err(Error::ArgumentsMismatch)?; - } - - let mut args = args.clone(); - args.push(*expr.clone()); - - for (arg, e) in func.args.into_iter().zip(args.iter()) { - let evalled = eval_expr(&closure_env, state, e)?; - closure_env.0.insert(arg.name, evalled); - } - eval_expr(&closure_env, state, &func.body) - } - v => Err(Error::NotAFunction(v)), - } - } - - ast::Expr::FunCall { func, args } => match eval_expr(expr_env, state, func)? { - ast::Value::Closure { func, env } => { - let mut closure_env: Env = state.envs.get(&env)?.clone(); - - if func.args.len() != args.len() { - Err(Error::ArgumentsMismatch)?; - } - - for (arg, e) in func.args.into_iter().zip(args.iter()) { - let evalled = eval_expr(&closure_env, state, e)?; - closure_env.0.insert(arg.name, evalled); - } - eval_expr(&closure_env, state, &func.body) - } - v => Err(Error::NotAFunction(v)), - }, - ast::Expr::Value(v) => match v { - ast::Value::Deferred { expr, env } => { - let closure_env: Env = state.envs.get(env)?.clone(); - eval_expr(&closure_env, state, expr) - } - _ => Ok(v.clone()), - }, - } -} - -fn defs_to_env( - defs: Vec, - env_name: &ast::EnvName, - envs: &mut Envs, -) -> Result<(), Error> { - let mut env = Env::new(); - - for def in defs { - let (name, closure) = match def { - ast::Definition::Function { func, name } => ( - name, - ast::Value::Closure { - func: Box::new(func), - env: env_name.clone(), - }, - ), - ast::Definition::Expr { expr, name } => ( - name, - ast::Value::Deferred { - expr: Box::new(expr.clone()), - env: env_name.clone(), - }, - ), - }; - env.insert_nodup(&name, closure)?; - } - - envs.0.insert(env_name.clone(), env); - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::helpers; - - #[test] - fn main_0() { - let program = vec![helpers::define_expr("main", 0.into())].into(); - let result = run(program); - assert_eq!(result, Ok(0.into())); - } - #[test] - fn var_lookup() { - let program = vec![ - helpers::define_expr("main", "lit".into()), - helpers::define_expr("lit", 0.into()), - ] - .into(); - let result = run(program); - assert_eq!(result, Ok(0.into())); - } - #[test] - fn var_assign_and_lookup() { - let program = vec![helpers::define_expr( - "main", - vec![ - helpers::assign("zero", 0.into()), - helpers::stmt_expr("zero".into()), - ] - .into(), - )] - .into(); - let result = run(program); - assert_eq!(result, Ok(0.into())); - } - - #[test] - fn field_access() { - let program = vec![ - helpers::define_expr("main", "record".into()), - helpers::define_expr( - "record", - ast::Expr::from(vec![("my_field", 0.into())]).field("my_field"), - ), - ] - .into(); - let result = run(program); - assert_eq!(result, Ok(0.into())); - } - - #[test] - fn fun_call() { - let program = vec![ - helpers::define_expr("main", "zero".into()), - helpers::define_expr( - "zero", - helpers::func(ast::Fn { - args: vec![], - body: 0.into(), - }) - .call(vec![]), - ), - ] - .into(); - let result = run(program); - assert_eq!(result, Ok(0.into())); - } - - #[test] - fn if_then_else() { - let program = vec![helpers::define_expr( - "main", - ast::Expr::If { - condition: Box::new(false.into()), - then: Box::new(0.into()), - r#else: Box::new(ast::Expr::If { - condition: Box::new(true.into()), - then: Box::new(1.into()), - r#else: Box::new(2.into()), - }), - }, - )] - .into(); - let result = run(program); - assert_eq!(result, Ok(1.into())); - } - - #[test] - fn fun_call_args() { - let program = vec![ - helpers::define_expr("main", "zero".into()), - helpers::define_expr( - "zero", - helpers::func(ast::Fn { - args: vec![ - ast::Arg { - name: "a".into(), - r#type: ast::Type::Named("i64".into()), - }, - ast::Arg { - name: "b".into(), - r#type: ast::Type::Named("i64".into()), - }, - ], - body: "b".into(), - }) - .call(vec![1.into(), 0.into()]), - ), - ] - .into(); - let result = run(program); - assert_eq!(result, Ok(0.into())); - } - - // Errors - - #[test] - fn duplicate_toplevel_defs() { - let program = vec![ - helpers::define_expr("main", "record".into()), - helpers::define_expr("main", 0.into()), - ] - .into(); - let result = run(program); - assert_eq!(result, Err(Error::DuplicateNames("main".into()))); - } - - #[test] - fn field_access_not_a_record() { - let program = vec![ - helpers::define_expr("main", "record".into()), - helpers::define_expr("record", ast::Expr::from(0).field("my_field")), - ] - .into(); - let result = run(program); - assert_eq!(result, Err(Error::NotARecord(0.into()))); - } - #[test] - fn fun_call_not_a_function() { - let program = vec![ - helpers::define_expr("main", "zero".into()), - helpers::define_expr("zero", ast::Expr::from(0).call(vec![1.into()])), - ] - .into(); - let result = run(program); - assert_eq!(result, Err(Error::NotAFunction(0.into()))); - } -} +pub use interpret::*; diff --git a/src/interpret/types.rs b/src/interpret/types.rs index 769bcdf..1d13a63 100644 --- a/src/interpret/types.rs +++ b/src/interpret/types.rs @@ -9,14 +9,8 @@ pub enum Error { DuplicateEnvNames(ast::EnvName), #[error("Duplicate names: {0:?}")] DuplicateNames(ast::Name), - #[error("Duplicate method names: {0:?}")] - DuplicateMethodNames(ast::MethodName), #[error("Name not found: {0:?}")] NameNotFound(ast::Name), - #[error("Type not found: {0:?}")] - TypeNotFound(ast::Type), - #[error("Method name not found: {0:?}")] - MethodNameNotFound(ast::MethodName), #[error("Field not found: {0:?}")] FieldNotFound(ast::Label), #[error("Env not found: {0:?}")] @@ -24,7 +18,7 @@ pub enum Error { #[error("Last statement is not an expression")] LastStatementNotAnExpr, #[error("Not a function {0:?}")] - NotAFunction(ast::Value), + NotAFunction(ast::Expr), #[error("Not a record {0:?}")] NotARecord(ast::Value), #[error("Not a boolean {0:?}")] @@ -59,7 +53,6 @@ pub struct State { pub name: String, namer: Namer, pub envs: Envs, - pub method_env: MethodEnv, } impl State { @@ -68,7 +61,6 @@ impl State { name, namer: Namer::new(), envs: Envs(BTreeMap::new()), - method_env: MethodEnv::new(), } } pub fn generate_env(&mut self, env: Env) -> Result { @@ -104,51 +96,6 @@ impl Env { } } -#[derive(PartialEq, Debug, Clone)] -pub struct MethodEnv(pub BTreeMap>); - -impl MethodEnv { - pub fn new() -> MethodEnv { - MethodEnv(BTreeMap::new()) - } - pub fn get( - &self, - r#type: &ast::Type, - method_name: &ast::MethodName, - ) -> Result<&ast::Fn, Error> { - self.0 - .get(r#type) - .ok_or(Error::TypeNotFound(r#type.clone()))? - .get(method_name) - .ok_or(Error::MethodNameNotFound(method_name.clone())) - } - // todo: check for dups - pub fn insert( - &mut self, - r#type: ast::Type, - method_name: ast::MethodName, - func: ast::Fn, - ) -> Result<(), Error> { - self.0 - .entry(r#type.clone()) - .and_modify(|map| { - map.insert(method_name.clone(), func.clone()); - }) - .or_insert({ - let mut map = BTreeMap::new(); - map.insert(method_name.clone(), func); - map - }); - Ok(()) - } -} - -impl Default for MethodEnv { - fn default() -> Self { - Self::new() - } -} - #[derive(PartialEq, Debug, Clone)] pub struct Envs(pub BTreeMap); diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index 16df112..ca89a84 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -1,3 +1,4 @@ +pub mod parser; pub mod runtime; pub use runtime::*; diff --git a/src/runtime/parser.rs b/src/runtime/parser.rs new file mode 100644 index 0000000..5d24be4 --- /dev/null +++ b/src/runtime/parser.rs @@ -0,0 +1,205 @@ +use crate::ast; +use chumsky::text::Char; +use log; +use lyn::Scanner; + +struct LocatedToken { + start: usize, + end: usize, + token: Token, +} + +#[derive(Debug, Clone, PartialEq)] +enum Token { + Let, + Fn, + Equals, + Semicolon, + OpenParen, + CloseParen, + OpenBracket, + CloseBracket, + OpenCurly, + CloseCurly, + Dot, + Comma, + Name(String), + Number(u32), + String(String), + Identifier(String), +} + +fn scan(source: String) -> Vec { + let mut scanner = Scanner::new(&source); + let mut tokens = Vec::new(); + + loop { + let start = scanner.cursor(); + if let Some(c) = scanner.pop() { + match *c { + '.' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::Dot, + }), + ',' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::Comma, + }), + '{' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::OpenCurly, + }), + '}' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::CloseCurly, + }), + '(' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::OpenParen, + }), + ')' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::CloseParen, + }), + '[' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::OpenBracket, + }), + ']' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::CloseBracket, + }), + '=' => tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::Equals, + }), + // comments + '#' => { + let mut str = "".to_string(); + while let Some(ch) = scanner.pop() { + if ch.is_newline() { + break; + } + str.push(*ch); + } + } + // strings + '"' => { + let mut str = "".to_string(); + while let Some(ch) = scanner.pop() { + if *ch == '"' { + break; + } + str.push(*ch); + } + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::String(str), + }); + } + _ if c.is_whitespace() => {} + // numbers + _ if c.is_numeric() => { + let mut str = "".to_string(); + loop { + if let Some(ch) = scanner.peek() + && !ch.is_numeric() + { + break; + } + if let Some(ch) = scanner.pop() { + str.push(*ch); + } else { + break; + } + } + let i = str.parse::().unwrap(); + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: Token::Number(i), + }); + } + // identifiers and keywords + _ if c.is_alphabetic() => { + let mut str = "".to_string(); + loop { + if let Some(ch) = scanner.peek() + && !ch.is_alphanumeric() + { + break; + } + if let Some(ch) = scanner.pop() { + str.push(*ch); + } else { + break; + } + } + + tokens.push(LocatedToken { + start, + end: scanner.cursor(), + token: match str.as_str() { + "fn" => Token::Fn, + "let" => Token::Let, + _ => Token::Identifier(str), + }, + }); + } + // error + _ => { + log::error!("Unexpected character: {c}"); + } + } + } else { + break; + } + } + + tokens +} + +fn parse_expr(tokens: &mut Vec) -> Result { + todo!() +} + +enum Error { + UnexpectedToken(LocatedToken), + UnexpectedEndOfInput, +} + +fn parse_definition(tokens: &mut Vec) -> Result { + if let Some(start) = tokens.pop_if(|tok| tok.token == Token::Let) { + if let Some(LocatedToken { + token: Token::Identifier(identifier), + .. + }) = tokens.pop_if(|tok| match tok.token { + Token::Identifier(_) => true, + _ => false, + }) { + if let Some(_eq) = tokens.pop_if(|tok| tok.token == Token::Equals) { + let expr = parse_expr(tokens)?; + Ok(ast::Definition { + name: ast::Name(identifier), + expr, + }) + } else { + Err(Error::UnexpectedToken(start)) + } + } else { + Err(Error::UnexpectedToken(start)) + } + } else { + Err(Error::UnexpectedEndOfInput) + } +} diff --git a/src/runtime/runtime.rs b/src/runtime/runtime.rs index a78284d..ff4915c 100644 --- a/src/runtime/runtime.rs +++ b/src/runtime/runtime.rs @@ -1,4 +1,5 @@ use super::types::*; +use crate::ast; use macroquad::prelude::*; pub async fn setup() -> State { @@ -6,6 +7,7 @@ pub async fn setup() -> State { State { assets: Assets { font }, + code: ast::Program(vec![]), } } diff --git a/src/runtime/types.rs b/src/runtime/types.rs index deceaba..7be8c8c 100644 --- a/src/runtime/types.rs +++ b/src/runtime/types.rs @@ -3,7 +3,7 @@ use macroquad::prelude::*; pub struct State { pub assets: Assets, - code: ast::Program, + pub code: ast::Program, } pub struct Assets {