diff --git a/README.md b/README.md index 0b592703..bc54a9fb 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,9 @@ Lykia is a toy document database basically written for educational purposes. It - [x] Core scripting language - [x] A minimal standard library - [x] Data manipulation language ("SELECT", "INSERT", "UPDATE", "DELETE") +- [x] Event loop, client-server communication - [ ] Data definition language ("CREATE COLLECTION", etc.) (in progress) - [ ] In-memory (dummy) storage engine (in progress) -- [ ] Async runtime/event loop (in progress) - [ ] Query planning - [ ] Persistent storage engine (Bitcask) - [ ] B-Tree implementation for indexing diff --git a/server/src/runtime/environment.rs b/server/src/runtime/environment.rs index c30fb123..44a17452 100644 --- a/server/src/runtime/environment.rs +++ b/server/src/runtime/environment.rs @@ -1,46 +1,65 @@ use crate::runtime::interpreter::HaltReason; use crate::runtime::types::RV; -use crate::util::{alloc_shared, Shared}; use core::panic; +use std::borrow::{Borrow, BorrowMut}; use rustc_hash::FxHashMap; use super::interpreter::InterpretError; +#[repr(transparent)] +#[derive(Debug, Eq, PartialEq, Clone, Copy)] +pub struct EnvId(pub usize); + #[derive(Debug)] pub struct Environment { map: FxHashMap, - pub parent: Option>, + pub parent: Option, +} + +#[derive(Debug)] +pub struct EnvironmentArena { + pub envs: Vec, } -impl Environment { - pub fn new(parent: Option>) -> Shared { - alloc_shared(Environment { +impl EnvironmentArena { + pub fn new() -> Self { + let mut arena = EnvironmentArena { + envs: vec![] + }; + arena.push(None); + arena + } + + fn get(&self, idx: EnvId) -> &Environment { + &self.envs[idx.0] + } + + pub fn push(&mut self, parent: Option) -> EnvId { + self.envs.push(Environment { map: FxHashMap::default(), parent, - }) + }); + EnvId(self.envs.len() - 1) } - pub fn pop(&mut self) -> Shared { - self.parent.clone().unwrap() + pub fn pop(&self, env_id: EnvId) -> EnvId { + // TODO: Remove the env for real + self.get(env_id).parent.unwrap() } - pub fn declare(&mut self, name: String, value: RV) { - self.map.insert(name, value); + pub fn top(&self) -> EnvId { + EnvId(self.envs.len() - 1) } - pub fn assign(&mut self, name: String, value: RV) -> Result { - if self.map.contains_key(&name) { - self.map.insert(name, value); + pub fn assign(&mut self, env_id: EnvId, name: String, value: RV) -> Result { + let env = self.envs[env_id.0].borrow(); + if env.map.contains_key(&name) { + self.envs[env_id.0].borrow_mut().map.insert(name, value); return Ok(true); } - if self.parent.is_some() { - return self - .parent - .as_mut() - .unwrap() - .borrow_mut() - .assign(name, value); + if env.parent.is_some() { + return self.assign(env.parent.unwrap(), name, value); } Err(HaltReason::Error(InterpretError::Other { message: format!("Assignment to an undefined variable '{}'", &name), @@ -49,29 +68,30 @@ impl Environment { pub fn assign_at( &mut self, + env_id: EnvId, distance: usize, name: &str, value: RV, ) -> Result { - let ancestor = self.ancestor(distance); + let ancestor = self.ancestor(env_id, distance); if let Some(unwrapped) = ancestor { - unwrapped.borrow_mut().map.insert(name.to_string(), value); + self.envs[unwrapped.0].borrow_mut().map.insert(name.to_string(), value); } else { - self.map.insert(name.to_string(), value); + self.envs[env_id.0].borrow_mut().map.insert(name.to_string(), value); } Ok(true) } - pub fn read(&self, name: &str) -> Result { - if self.map.contains_key(name) { + pub fn read(&self, env_id: EnvId, name: &str) -> Result { + if self.get(env_id).map.contains_key(name) { // TODO(vck): Remove clone - return Ok(self.map.get(name).unwrap().clone()); + return Ok(self.get(env_id).map.get(name).unwrap().clone()); } - if self.parent.is_some() { - return self.parent.as_ref().unwrap().borrow().read(name); + if self.get(env_id).parent.is_some() { + return self.read(self.get(env_id).parent.unwrap(), name); } Err(HaltReason::Error(InterpretError::Other { @@ -79,79 +99,80 @@ impl Environment { })) } - pub fn read_at(&self, distance: usize, name: &str) -> Result { - let ancestor = self.ancestor(distance); + pub fn read_at(&self, env_id: EnvId, distance: usize, name: &str) -> Result { + let ancestor = self.ancestor(env_id, distance); if let Some(unwrapped) = ancestor { // TODO(vck): Remove clone - return Ok(unwrapped.borrow().map.get(name).unwrap().clone()); + return Ok(self.get(unwrapped).map.get(name).unwrap().clone()); } - return Ok(self.map.get(name).unwrap().clone()); + return Ok(self.get(env_id).map.get(name).unwrap().clone()); } - pub fn ancestor(&self, distance: usize) -> Option> { + pub fn ancestor(&self, env_id: EnvId, distance: usize) -> Option { if distance == 0 { return None; } if distance == 1 { - return Some(self.parent.as_ref().unwrap().clone()); + return Some(self.get(env_id).parent.unwrap()); } - if self.parent.is_some() { - let pref = self.parent.as_ref().unwrap().borrow_mut(); - return pref.ancestor(distance - 1); + if self.get(env_id).parent.is_some() { + let pref = self.get(env_id).parent.unwrap(); + return self.ancestor(pref, distance - 1); } panic!("Invalid variable distance."); } + + pub fn declare(&mut self, env_id: EnvId, name: String, value: RV) { + self.envs[env_id.0].map.insert(name, value); + } } + #[cfg(test)] mod test { use crate::runtime::types::RV; #[test] fn test_read_basic() { - let env = super::Environment::new(None); - env.borrow_mut().declare("five".to_string(), RV::Num(5.0)); - assert_eq!(env.borrow().read("five").unwrap(), RV::Num(5.0)); + let mut env_arena = super::EnvironmentArena::new(); + let env = env_arena.top(); + env_arena.declare(env, "five".to_string(), RV::Num(5.0)); + assert_eq!(env_arena.read(env, "five").unwrap(), RV::Num(5.0)); } #[test] fn test_read_from_parent() { - let parent = super::Environment::new(None); - parent - .borrow_mut() - .declare("five".to_string(), RV::Num(5.0)); - let child = super::Environment::new(Some(parent.clone())); - assert_eq!(child.borrow().read("five").unwrap(), RV::Num(5.0)); + let mut env_arena = super::EnvironmentArena::new(); + let parent = env_arena.top(); + env_arena.declare(parent, "five".to_string(), RV::Num(5.0)); + let child = env_arena.push(Some(parent)); + assert_eq!(env_arena.read(child, "five").unwrap(), RV::Num(5.0)); } #[test] fn test_write_to_parent() { - let parent = super::Environment::new(None); - parent - .borrow_mut() - .declare("five".to_string(), RV::Num(5.0)); - let child = super::Environment::new(Some(parent.clone())); - child - .borrow_mut() - .assign("five".to_string(), RV::Num(5.1)) + let mut env_arena = super::EnvironmentArena::new(); + let parent = env_arena.top(); + env_arena.declare(parent, "five".to_string(), RV::Num(5.0)); + let child = env_arena.push(Some(parent)); + env_arena.assign(child, "five".to_string(), RV::Num(5.1)) .unwrap(); - assert_eq!(parent.borrow().read("five").unwrap(), RV::Num(5.1)); - assert_eq!(child.borrow().read("five").unwrap(), RV::Num(5.1)); + assert_eq!(env_arena.read(parent, "five").unwrap(), RV::Num(5.1)); + assert_eq!(env_arena.read(child, "five").unwrap(), RV::Num(5.1)); } #[test] fn test_read_undefined_variable() { - let env = super::Environment::new(None); - assert!(env.borrow().read("five").is_err()); + let env_arena = super::EnvironmentArena::new(); + let env = env_arena.top(); + assert!(env_arena.read(env, "five").is_err()); } #[test] fn test_assign_to_undefined_variable() { - let env = super::Environment::new(None); - assert!(env - .borrow_mut() - .assign("five".to_string(), RV::Num(5.0)) - .is_err()); + let mut env_arena = super::EnvironmentArena::new(); + let env = env_arena.top(); + assert!(env_arena.assign(env, "five".to_string(), RV::Num(5.0)).is_err()); } } diff --git a/server/src/runtime/interpreter.rs b/server/src/runtime/interpreter.rs index 3c736ac2..448ecf6a 100644 --- a/server/src/runtime/interpreter.rs +++ b/server/src/runtime/interpreter.rs @@ -1,7 +1,9 @@ use rustc_hash::FxHashMap; +use super::environment::{EnvId, EnvironmentArena}; use super::eval::{coerce2number, eval_binary, is_value_truthy}; use super::resolver::Resolver; +use super::types::Stateful; use crate::lang::ast::expr::{Expr, ExprId, Operation}; use crate::lang::ast::program::AstArena; use crate::lang::ast::stmt::{Stmt, StmtId}; @@ -10,10 +12,9 @@ use crate::lang::Literal; use crate::lang::ast::visitor::VisitorMut; use crate::lang::tokens::token::Span; use crate::lang::tokens::token::Spanned; -use crate::runtime::environment::Environment; use crate::runtime::types::RV::Callable; use crate::runtime::types::{Function, RV}; -use crate::util::{alloc_shared, Shared}; +use crate::util::alloc_shared; use std::rc::Rc; use std::vec; @@ -132,8 +133,9 @@ impl LoopStack { } pub struct Interpreter { - env: Shared, - root_env: Shared, + env: EnvId, + root_env: EnvId, + env_arena: EnvironmentArena, arena: Rc, loop_stack: LoopStack, resolver: Rc, @@ -141,12 +143,14 @@ pub struct Interpreter { impl Interpreter { pub fn new( - env: Shared, + env_arena: EnvironmentArena, + env: EnvId, arena: Rc, resolver: Rc, ) -> Interpreter { Interpreter { - env: env.clone(), + env_arena, + env: env, root_env: env, arena: Rc::clone(&arena), loop_stack: LoopStack::new(), @@ -180,32 +184,42 @@ impl Interpreter { fn look_up_variable(&self, name: &str, eid: ExprId) -> Result { let distance = self.resolver.get_distance(eid); if let Some(unwrapped) = distance { - self.env.borrow().read_at(unwrapped, name) + self.env_arena.read_at(self.env, unwrapped, name) } else { - self.root_env.borrow().read(name) + self.env_arena.read(self.root_env, name) } } pub fn user_fn_call( &mut self, statements: &Vec, - environment: Shared, + closure: EnvId, + parameters: &Vec, + arguments: &[RV] ) -> Result { - self.execute_block(statements, Some(environment)) + let fn_env = self.env_arena.push(Some(closure)); + + for (i, param) in parameters.iter().enumerate() { + // TODO: Remove clone here + self.env_arena + .declare(fn_env, param.to_string(), arguments.get(i).unwrap().clone()); + } + + self.execute_block(statements, Some(fn_env)) } pub fn execute_block( &mut self, statements: &Vec, - env_opt: Option>, + env_opt: Option, ) -> Result { - let mut env_tmp: Option> = None; + let mut env_tmp: Option = None; if let Some(env_opt_unwrapped) = env_opt { - env_tmp = Some(self.env.clone()); + env_tmp = Some(self.env); self.env = env_opt_unwrapped; } else { - self.env = Environment::new(Some(self.env.clone())); + self.env = self.env_arena.push(Some(self.env)); } let mut ret = Ok(RV::Undefined); @@ -218,7 +232,7 @@ impl Interpreter { if let Some(env_tmp_unwrapped) = env_tmp { self.env = env_tmp_unwrapped; } else { - self.env = self.env.clone().borrow_mut().pop(); + self.env = self.env_arena.pop(self.env); } ret } @@ -278,13 +292,11 @@ impl VisitorMut for Interpreter { let distance = self.resolver.get_distance(eidx); let evaluated = self.visit_expr(*expr)?; let result = if let Some(distance_unv) = distance { - self.env - .borrow_mut() - .assign_at(distance_unv, &dst.name, evaluated.clone()) + self.env_arena + .assign_at( self.env, distance_unv, &dst.name, evaluated.clone()) } else { - self.root_env - .borrow_mut() - .assign(dst.name.clone(), evaluated.clone()) + self.env_arena + .assign( self.env, dst.name.clone(), evaluated.clone()) }; if result.is_err() { return Err(result.err().unwrap()); @@ -362,9 +374,8 @@ impl VisitorMut for Interpreter { if name.is_some() { // TODO(vck): Callable shouldn't be cloned here - self.env - .borrow_mut() - .declare(name.as_ref().unwrap().name.to_string(), callable.clone()); + self.env_arena + .declare(self.env, name.as_ref().unwrap().name.to_string(), callable.clone()); } Ok(callable) @@ -435,9 +446,8 @@ impl VisitorMut for Interpreter { } Stmt::Declaration { dst, expr, span: _ } => { let evaluated = self.visit_expr(*expr)?; - self.env - .borrow_mut() - .declare(dst.name.to_string(), evaluated.clone()); + self.env_arena + .declare(self.env, dst.name.to_string(), evaluated.clone()); } Stmt::Block { body: stmts, @@ -503,71 +513,49 @@ impl VisitorMut for Interpreter { } } -pub mod test_helpers { - use crate::runtime::environment::Environment; - use crate::runtime::interpreter::{HaltReason, Interpreter}; - use crate::runtime::std::stdlib; - use crate::runtime::types::{Function, Stateful, RV}; - use crate::runtime::{Runtime, RuntimeMode}; - use crate::util::{alloc_shared, Shared}; - use rustc_hash::FxHashMap; - use std::rc::Rc; - #[derive(Clone)] - pub struct Output { - out: Vec, - } +#[derive(Clone)] +pub struct Output { + out: Vec, +} - impl Output { - pub fn new() -> Output { - Output { out: Vec::new() } - } +impl Output { + pub fn new() -> Output { + Output { out: Vec::new() } + } - pub fn push(&mut self, rv: RV) { - self.out.push(rv); - } + pub fn push(&mut self, rv: RV) { + self.out.push(rv); + } - pub fn expect(&mut self, rv: Vec) { - assert_eq!(self.out, rv); - } + pub fn expect(&mut self, rv: Vec) { + assert_eq!(self.out, rv); } +} - impl Stateful for Output { - fn call(&mut self, _interpreter: &mut Interpreter, rv: &[RV]) -> Result { - for item in rv { - self.push(item.clone()); - } - Ok(RV::Undefined) +impl Stateful for Output { + fn call(&mut self, _interpreter: &mut Interpreter, rv: &[RV]) -> Result { + for item in rv { + self.push(item.clone()); } + Ok(RV::Undefined) } +} - pub fn get_runtime() -> (Shared, Runtime) { - let env = Environment::new(None); - - let out = alloc_shared(Output::new()); - - let mut native_fns = stdlib(); - - let mut test_namespace = FxHashMap::default(); - - test_namespace.insert( - "out".to_owned(), - RV::Callable(None, Rc::new(Function::Stateful(out.clone()))), - ); +pub mod test_helpers { + use crate::runtime::types::RV; + use crate::runtime::{Runtime, RuntimeMode}; + use crate::util::{alloc_shared, Shared}; - native_fns.insert( - "TestUtils".to_owned(), - RV::Object(alloc_shared(test_namespace)), - ); + use super::Output; - for (name, value) in native_fns { - env.borrow_mut().declare(name, value); - } + pub fn get_runtime() -> (Shared, Runtime) { + let out = alloc_shared(Output::new()); ( - out, + out.clone(), Runtime { - env, + out: Some(out), mode: RuntimeMode::File, }, ) diff --git a/server/src/runtime/mod.rs b/server/src/runtime/mod.rs index cbdefebf..af1dc903 100644 --- a/server/src/runtime/mod.rs +++ b/server/src/runtime/mod.rs @@ -1,5 +1,6 @@ +use self::environment::EnvironmentArena; use self::error::{report_error, ExecutionError}; -use self::interpreter::HaltReason; +use self::interpreter::{HaltReason, Output}; use self::resolver::Resolver; use self::std::stdlib; use crate::lang::ast::visitor::VisitorMut; @@ -7,7 +8,6 @@ use crate::lang::ast::visitor::VisitorMut; use crate::lang::ast::parser::{ParseError, Parser}; use crate::lang::ast::program::AstArena; use crate::lang::tokens::scanner::Scanner; -use crate::runtime::environment::Environment; use crate::runtime::interpreter::Interpreter; use crate::runtime::types::RV; use crate::util::Shared; @@ -22,8 +22,8 @@ mod std; pub mod types; pub struct Runtime { - env: Shared, mode: RuntimeMode, + out: Option> } #[derive(Eq, PartialEq)] @@ -34,15 +34,7 @@ pub enum RuntimeMode { impl Runtime { pub fn new(mode: RuntimeMode) -> Runtime { - let env = Environment::new(None); - - let native_fns = stdlib(); - - for (name, value) in native_fns { - env.borrow_mut().declare(name.to_string(), value); - } - - Runtime { env, mode } + Runtime { mode, out: None } } pub fn print_ast(&mut self, source: &str) -> Result<(), ParseError> { @@ -74,7 +66,16 @@ impl Runtime { let mut resolver = Resolver::new(arena.clone()); resolver.resolve_stmt(program_unw.root); // - let mut interpreter = Interpreter::new(self.env.clone(), arena, Rc::new(resolver)); + let mut env_arena = EnvironmentArena::new(); + let env = env_arena.top(); + + let native_fns = stdlib(self.out.clone()); + + for (name, value) in native_fns { + env_arena.declare(env, name.to_string(), value); + } + + let mut interpreter = Interpreter::new(env_arena, env, arena, Rc::new(resolver)); let out = interpreter.visit_stmt(program_unw.root); if self.mode == RuntimeMode::Repl { diff --git a/server/src/runtime/std/mod.rs b/server/src/runtime/std/mod.rs index 71f46262..97e429a1 100644 --- a/server/src/runtime/std/mod.rs +++ b/server/src/runtime/std/mod.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use rustc_hash::FxHashMap; -use crate::util::alloc_shared; +use crate::util::{alloc_shared, Shared}; use self::{ fib::nt_fib, @@ -11,14 +11,14 @@ use self::{ time::nt_clock, }; -use super::types::{Function, RV}; +use super::{interpreter::Output, types::{Function, RV}}; pub mod fib; pub mod json; pub mod out; pub mod time; -pub fn stdlib() -> FxHashMap { +pub fn stdlib(out: Option>) -> FxHashMap { let mut std = FxHashMap::default(); let mut benchmark_namespace = FxHashMap::default(); @@ -55,6 +55,20 @@ pub fn stdlib() -> FxHashMap { RV::Callable(Some(0), Rc::new(Function::Lambda { function: nt_clock })), ); + if out.is_some() { + let mut test_namespace = FxHashMap::default(); + + test_namespace.insert( + "out".to_owned(), + RV::Callable(None, Rc::new(Function::Stateful(out.unwrap().clone()))), + ); + + std.insert( + "TestUtils".to_owned(), + RV::Object(alloc_shared(test_namespace)), + ); + } + std.insert( "Benchmark".to_owned(), RV::Object(alloc_shared(benchmark_namespace)), diff --git a/server/src/runtime/types.rs b/server/src/runtime/types.rs index 1256adf6..c741e049 100644 --- a/server/src/runtime/types.rs +++ b/server/src/runtime/types.rs @@ -1,5 +1,4 @@ use crate::lang::ast::stmt::StmtId; -use crate::runtime::environment::Environment; use crate::runtime::interpreter::{HaltReason, Interpreter}; use crate::util::Shared; use rustc_hash::FxHashMap; @@ -7,6 +6,8 @@ use serde::{Deserialize, Serialize}; use std::fmt::{Debug, Display, Formatter}; use std::rc::Rc; +use super::environment::EnvId; + pub trait Stateful { fn call(&mut self, interpreter: &mut Interpreter, rv: &[RV]) -> Result; } @@ -20,7 +21,7 @@ pub enum Function { UserDefined { name: String, parameters: Vec, - closure: Shared, + closure: EnvId, body: Rc>, }, } @@ -85,16 +86,7 @@ impl Function { closure, body, } => { - let fn_env = Environment::new(Some(Rc::clone(closure))); - - for (i, param) in parameters.iter().enumerate() { - // TODO: Remove clone here - fn_env - .borrow_mut() - .declare(param.to_string(), arguments.get(i).unwrap().clone()); - } - - interpreter.user_fn_call(body, fn_env) + interpreter.user_fn_call(body, *closure, parameters, arguments) } } }