From d9f36be74446e54c3c8844313046da05bea8e8bc Mon Sep 17 00:00:00 2001 From: "Stephen M. Coakley" Date: Fri, 10 May 2019 22:50:38 -0500 Subject: [PATCH] Move builtin init routine out from runtime --- riptide/src/builtins.rs | 54 +++++++++++++++++++++++--------- riptide/src/init.rip | 6 ++++ riptide/src/runtime.rs | 59 ++++++++++++++--------------------- riptide/src/stdlib/process.rs | 26 +++++++++++++++ riptide/src/value.rs | 12 +++++++ 5 files changed, 107 insertions(+), 50 deletions(-) diff --git a/riptide/src/builtins.rs b/riptide/src/builtins.rs index 45d690f..f474481 100644 --- a/riptide/src/builtins.rs +++ b/riptide/src/builtins.rs @@ -1,8 +1,34 @@ +//! Implementations of built-in global functions that are always available. + +use crate::modules; use crate::prelude::*; use crate::runtime::Scope; +pub fn init(runtime: &mut Runtime) { + runtime.globals().set("require", Value::ForeignFunction(modules::require)); + runtime.globals().set("backtrace", Value::ForeignFunction(backtrace)); + runtime.globals().set("call", Value::ForeignFunction(call)); + runtime.globals().set("catch", Value::ForeignFunction(catch)); + runtime.globals().set("def", Value::ForeignFunction(def)); + runtime.globals().set("export", Value::ForeignFunction(export)); + runtime.globals().set("include", Value::ForeignFunction(include)); + runtime.globals().set("list", Value::ForeignFunction(list)); + runtime.globals().set("nil", Value::ForeignFunction(nil)); + runtime.globals().set("nth", Value::ForeignFunction(nth)); + runtime.globals().set("set", Value::ForeignFunction(set)); + runtime.globals().set("table", Value::ForeignFunction(table)); + runtime.globals().set("table-set", Value::ForeignFunction(table_set)); + runtime.globals().set("throw", Value::ForeignFunction(throw)); + runtime.globals().set("typeof", Value::ForeignFunction(type_of)); + + runtime.globals().set("modules", Value::from(table! { + "loaders" => Value::List(Vec::new()), + "loaded" => Value::from(table!()), + })); +} + /// Binds a value to a new variable. -pub fn def(runtime: &mut Runtime, args: &[Value]) -> Result { +fn def(runtime: &mut Runtime, args: &[Value]) -> Result { let name = match args.get(0).and_then(Value::as_string) { Some(s) => s.clone(), None => throw!("variable name required"), @@ -15,7 +41,7 @@ pub fn def(runtime: &mut Runtime, args: &[Value]) -> Result { Ok(Value::Nil) } -pub fn set(runtime: &mut Runtime, args: &[Value]) -> Result { +fn set(runtime: &mut Runtime, args: &[Value]) -> Result { let name = match args.get(0).and_then(Value::as_string) { Some(s) => s.clone(), None => throw!("variable name required"), @@ -28,7 +54,7 @@ pub fn set(runtime: &mut Runtime, args: &[Value]) -> Result { Ok(Value::Nil) } -pub fn export(runtime: &mut Runtime, args: &[Value]) -> Result { +fn export(runtime: &mut Runtime, args: &[Value]) -> Result { let name = match args.get(0).and_then(Value::as_string) { Some(s) => s.clone(), None => throw!("variable name to export required"), @@ -43,16 +69,16 @@ pub fn export(runtime: &mut Runtime, args: &[Value]) -> Result } /// Returns the name of the primitive type of the given arguments. -pub fn type_of(_: &mut Runtime, args: &[Value]) -> Result { +fn type_of(_: &mut Runtime, args: &[Value]) -> Result { Ok(args.first().map(Value::type_name).map(Value::from).unwrap_or(Value::Nil)) } /// Constructs a list from the given arguments. -pub fn list(_: &mut Runtime, args: &[Value]) -> Result { +fn list(_: &mut Runtime, args: &[Value]) -> Result { Ok(Value::List(args.to_vec())) } -pub fn nth(_: &mut Runtime, args: &[Value]) -> Result { +fn nth(_: &mut Runtime, args: &[Value]) -> Result { let list = match args.get(0).and_then(Value::as_list) { Some(s) => s.to_vec(), None => throw!("first argument must be a list"), @@ -67,7 +93,7 @@ pub fn nth(_: &mut Runtime, args: &[Value]) -> Result { } /// Constructs a table from the given arguments. -pub fn table(_: &mut Runtime, args: &[Value]) -> Result { +fn table(_: &mut Runtime, args: &[Value]) -> Result { if args.len() & 1 == 1 { throw!("an even number of arguments is required"); } @@ -88,7 +114,7 @@ pub fn table(_: &mut Runtime, args: &[Value]) -> Result { Ok(table.into()) } -pub fn table_set(_: &mut Runtime, args: &[Value]) -> Result { +fn table_set(_: &mut Runtime, args: &[Value]) -> Result { let table = match args.get(0).and_then(Value::as_table) { Some(s) => s.clone(), None => throw!("first argument must be a table"), @@ -107,19 +133,19 @@ pub fn table_set(_: &mut Runtime, args: &[Value]) -> Result { } /// Function that always returns Nil. -pub fn nil(_: &mut Runtime, _: &[Value]) -> Result { +fn nil(_: &mut Runtime, _: &[Value]) -> Result { Ok(Value::Nil) } /// Throw an exception. -pub fn throw(_: &mut Runtime, args: &[Value]) -> Result { +fn throw(_: &mut Runtime, args: &[Value]) -> Result { match args.first() { Some(value) => Err(Exception::from(value.clone())), None => Err(Exception::from(Value::Nil)), } } -pub fn call(runtime: &mut Runtime, args: &[Value]) -> Result { +fn call(runtime: &mut Runtime, args: &[Value]) -> Result { if let Some(function) = args.first() { let args = match args.get(1) { Some(Value::List(args)) => &args[..], @@ -133,7 +159,7 @@ pub fn call(runtime: &mut Runtime, args: &[Value]) -> Result { } /// Invoke a block. If the block throws an exception, catch it and return it. -pub fn catch(runtime: &mut Runtime, args: &[Value]) -> Result { +fn catch(runtime: &mut Runtime, args: &[Value]) -> Result { if let Some(function) = args.first() { match runtime.invoke(function, &[]) { Ok(_) => Ok(Value::Nil), @@ -144,12 +170,12 @@ pub fn catch(runtime: &mut Runtime, args: &[Value]) -> Result } } -pub fn include(_: &mut Runtime, _: &[Value]) -> Result { +fn include(_: &mut Runtime, _: &[Value]) -> Result { throw!("not implemented"); } /// Returns a backtrace of the call stack as a list of strings. -pub fn backtrace(runtime: &mut Runtime, _: &[Value]) -> Result { +fn backtrace(runtime: &mut Runtime, _: &[Value]) -> Result { fn scope_to_value(scope: impl AsRef) -> Value { let scope = scope.as_ref(); Value::from(table! { diff --git a/riptide/src/init.rip b/riptide/src/init.rip index f9dc212..cd9f400 100644 --- a/riptide/src/init.rip +++ b/riptide/src/init.rip @@ -2,6 +2,12 @@ # # This script must never throw an exception, or the runtime will panic. +# Pre-define the global modules table. +# table-set $GLOBALS modules [ +# loaders: [] # A list of loader functions. +# loaded: [:] # A table mapping module names to the loaded object. +# ] + def lang (require lang) table-set $GLOBALS print $lang->print diff --git a/riptide/src/runtime.rs b/riptide/src/runtime.rs index 0b7bdf2..4938c7f 100644 --- a/riptide/src/runtime.rs +++ b/riptide/src/runtime.rs @@ -82,8 +82,7 @@ impl Scope { /// Configure a runtime. pub struct RuntimeBuilder { - module_loaders: Vec, - globals: Table, + module_loaders: Vec, } impl Default for RuntimeBuilder { @@ -96,33 +95,12 @@ impl RuntimeBuilder { pub fn new() -> Self { Self { module_loaders: Vec::new(), - globals: table! { - "require" => Value::ForeignFunction(modules::require), - "backtrace" => Value::ForeignFunction(builtins::backtrace), - "call" => Value::ForeignFunction(builtins::call), - "catch" => Value::ForeignFunction(builtins::catch), - "def" => Value::ForeignFunction(builtins::def), - "export" => Value::ForeignFunction(builtins::export), - "include" => Value::ForeignFunction(builtins::include), - "list" => Value::ForeignFunction(builtins::list), - "nil" => Value::ForeignFunction(builtins::nil), - "nth" => Value::ForeignFunction(builtins::nth), - "set" => Value::ForeignFunction(builtins::set), - "table" => Value::ForeignFunction(builtins::table), - "table-set" => Value::ForeignFunction(builtins::table_set), - "throw" => Value::ForeignFunction(builtins::throw), - "typeof" => Value::ForeignFunction(builtins::type_of), - "modules" => Value::from(table! { - "loaded" => Value::from(table!()), - "loaders" => Value::Nil, - }), - }, } } /// Register a module loader. pub fn module_loader(mut self, loader: ForeignFunction) -> Self { - self.module_loaders.push(loader.into()); + self.module_loaders.push(loader); self } @@ -131,16 +109,27 @@ impl RuntimeBuilder { } pub fn build(self) -> Runtime { - self.globals.get("modules").as_table().unwrap().set("loaders", Value::List(self.module_loaders)); - let mut runtime = Runtime { - globals: Rc::new(self.globals), + globals: Rc::new(Table::new()), stack: Vec::new(), is_exiting: false, exit_code: 0, }; - runtime.init(); + // Set up globals + runtime.globals.set("GLOBALS", runtime.globals.clone()); + runtime.globals.set("env", env::vars().collect::()); // Isn't that easy? + + // Initialize builtins + builtins::init(&mut runtime); + + // Register predefined module loaders + for loader in self.module_loaders { + runtime.register_module_loader(loader); + } + + // Execute initialization + runtime.execute(None, include_str!("init.rip")).expect("error in runtime initialization"); runtime } @@ -170,18 +159,16 @@ impl Default for Runtime { } impl Runtime { - /// Initialize the runtime environment. - fn init(&mut self) { - self.globals.set("GLOBALS", self.globals.clone()); - self.globals.set("env", env::vars().collect::
()); // Isn't that easy? - - self.execute(None, include_str!("init.rip")).expect("error in runtime initialization"); - } - pub fn builder() -> RuntimeBuilder { RuntimeBuilder::new() } + /// Register a module loader. + pub fn register_module_loader(&self, loader: ForeignFunction) { + let modules = self.globals.get("modules").as_table().unwrap(); + modules.set("loaders", modules.get("loaders").append(loader).unwrap()); + } + pub fn exit_code(&self) -> i32 { self.exit_code } diff --git a/riptide/src/stdlib/process.rs b/riptide/src/stdlib/process.rs index d1e0bac..7518aa9 100644 --- a/riptide/src/stdlib/process.rs +++ b/riptide/src/stdlib/process.rs @@ -1,11 +1,14 @@ use crate::prelude::*; use crate::process; use std::process::Command; +use std::thread; +use std::time::Duration; pub fn load() -> Result { Ok(table! { "command" => Value::ForeignFunction(command), "exec" => Value::ForeignFunction(exec), + "sleep" => Value::ForeignFunction(sleep), "spawn" => Value::ForeignFunction(spawn), } .into()) @@ -60,3 +63,26 @@ fn command(_: &mut Runtime, args: &[Value]) -> Result { fn exec(_: &mut Runtime, _: &[Value]) -> Result { unimplemented!(); } + +/// Puts the current process to sleep for a given number of seconds. +fn sleep(_: &mut Runtime, args: &[Value]) -> Result { + if let Some(Value::Number(seconds)) = args.first() { + let seconds = *seconds; + + let duration = if seconds.is_normal() && seconds > 0f64 { + Duration::new( + seconds.trunc() as u64, + (seconds.fract() * 1_000_000_000f64) as u32, + ) + } else { + Duration::from_secs(0) + }; + + log::debug!("sleeping for {}ms", duration.as_millis()); + thread::sleep(duration); + + Ok(Value::Nil) + } else { + throw!("sleep duration required") + } +} diff --git a/riptide/src/value.rs b/riptide/src/value.rs index 735e553..9abb700 100644 --- a/riptide/src/value.rs +++ b/riptide/src/value.rs @@ -205,6 +205,18 @@ impl Value { pub fn get(&self, key: impl AsRef<[u8]>) -> Value { self.as_table().map(|t| t.get(key)).unwrap_or(Value::Nil) } + + /// If this is a list, return a new list with the given value appended. + pub fn append(&self, value: impl Into) -> Option { + match self { + Value::List(items) => { + let mut new = items.clone(); + new.push(value.into()); + Some(Value::List(new)) + }, + _ => None, + } + } } impl PartialEq for Value {