diff --git a/examples/array.sy b/examples/array.sy new file mode 100644 index 0000000..7a4352e --- /dev/null +++ b/examples/array.sy @@ -0,0 +1,15 @@ +# Create an array with just some numbers +{ 1 2 3 } +println! + +[ :x x x ] :dup +# Call some functions +{ 1 dup! dup! } +println! + +# Call your own functions, why not? +{ + [ 2 *! ] :double + 1 dup! double! dup! double! +} +println! \ No newline at end of file diff --git a/src/obj/array.rs b/src/obj/array.rs index e69de29..e02583d 100644 --- a/src/obj/array.rs +++ b/src/obj/array.rs @@ -0,0 +1,140 @@ +use crate::obj::prelude::*; +use crate::syn::span::Span; +use crate::vm::{error::*, machine::Machine}; +use gc::{Finalize, Gc, Trace}; + +pub type Array = Vec; + +#[derive(Debug, Trace, Finalize)] +pub struct ArrayObj { + value: Array, + vtable: VTable, +} + +thread_local! { + static VTABLE: VTable = VTableBuilder::default() + .with_builtin("__str__", |machine, reentry| { + // stack (top to bottom): + // - array[i].__str__ + // - array[i] + // - array + // - int i + // - working string + + // TODO - re-implement this in the language itself ffs + // TODO - catch array recursion + const INNER_BEGIN: usize = 1; + const INNER_END: usize = 2; + match reentry { + 0 => { + let array = machine.stack_pop()?; + let index = IntObj::new(0); + let working = StrObj::new(String::new()); + machine.stack_push(working)?; + machine.stack_push(index)?; + machine.stack_push(array)?; + // this will basically just dump us down below, except + // slower and kludgier and just worse overall + Ok(BuiltinExit::Resume(INNER_BEGIN)) + } + INNER_BEGIN => { + machine.stack(); + let array_obj = machine.stack_pop()?; + let index_obj = machine.stack_pop()?; + // cast the array and index to their appropriate types + let array = array_obj.as_any().downcast_ref::() + .ok_or_else(|| RuntimeError::WrongValue("array".to_string()))?; + let index_int = index_obj.as_any().downcast_ref::() + .ok_or_else(|| RuntimeError::WrongValue("int".to_string()))?; + let index = index_int.value() as usize; + if array.value().len() <= index { + // done iterating, exit + return Ok(BuiltinExit::Return); + } + // get the current item from the array, push the index and + // array back onto the stack + let item = Gc::clone(array.value().get(index).unwrap()); + machine.stack_push(index_obj)?; + machine.stack_push(array_obj)?; + + // push the array item at the current index to the stack + let str_fun = item.get("__str__") + .ok_or_else(|| RuntimeError::UnsetWord("__str__".to_string()))?; + machine.stack_push(item)?; + // call the __str__ item + str_fun.call(None, machine)?; + Ok(BuiltinExit::Resume(INNER_END)) + } + INNER_END => { + machine.stack(); + // get the returned string, add it to the accumulator + + let returned_obj = machine.stack_pop()?; + let returned = returned_obj.as_any().downcast_ref::() + .ok_or_else(|| RuntimeError::WrongValue("str".to_string()))?; + + let array_obj = machine.stack_pop()?; + let index_obj = machine.stack_pop()?; + let index = index_obj.as_any().downcast_ref::() + .ok_or_else(|| RuntimeError::WrongValue("int".to_string()))?; + + let working_obj = machine.stack_pop()?; + let working = working_obj.as_any().downcast_ref::() + .ok_or_else(|| RuntimeError::WrongValue("str".to_string()))?; + + let new_str = StrObj::new(format!("{}{}", working.value(), returned.value())); + // index gets incremented here + let new_index = IntObj::new(index.value() + 1); + + machine.stack_push(new_str)?; + machine.stack_push(new_index)?; + machine.stack_push(array_obj)?; + Ok(BuiltinExit::Resume(INNER_BEGIN)) + } + _ => unreachable!() + } + /* + let obj_ptr: ObjPtr = machine.stack_pop()?; + let obj: &(dyn Obj + 'static) = &*obj_ptr; + if let Some(int) = obj.as_any().downcast_ref::() { + let string = StrObj::new(int.value.to_string()); + machine.stack_push(string)?; + Ok(BuiltinExit::Return) + } else { + Err(RuntimeError::WrongValue("int".to_string())) + } + */ + }) + .finish(); +} + +impl ArrayObj { + pub fn new(value: Array) -> Gc { + Gc::new(Self { + value, + vtable: VTABLE.with(Clone::clone), + }) + } + + pub fn value(&self) -> &Array { + &self.value + } +} + +impl Obj for ArrayObj { + fn vtable(&self) -> &VTable { + &self.vtable + } + + fn vtable_mut(&mut self) -> &mut VTable { + &mut self.vtable + } + + fn call(&self, call_site: Option, _machine: &mut Machine) -> Result<()> { + Err(RuntimeError::CannotCall("array value".to_string()).with_location(call_site)) + } + + fn as_any(&self) -> &(dyn std::any::Any + 'static) { + self + } +} diff --git a/src/obj/mod.rs b/src/obj/mod.rs index f56844e..9c573fc 100644 --- a/src/obj/mod.rs +++ b/src/obj/mod.rs @@ -79,5 +79,8 @@ pub trait Obj: Trace + Finalize + Debug { self.vtable_mut().insert(key, value) } + /// Gets this object as an `Any`. + /// + /// This is useful for downcasting to expected values. fn as_any(&self) -> &(dyn Any + 'static); } diff --git a/src/syn/parser.pest b/src/syn/parser.pest index 54ba63c..235e7f2 100644 --- a/src/syn/parser.pest +++ b/src/syn/parser.pest @@ -20,7 +20,7 @@ assign = { ":" ~ word } atom = { float | int | assign | word | str | apply } quote = { "[" ~ stmt* ~ "]" } array = { "{" ~ stmt* ~ "}" } -expr = { atom | quote } +expr = { atom | quote | array } include = { "%include" ~ str } stmt = { include | expr } diff --git a/src/vm/error.rs b/src/vm/error.rs index 264c8ab..6ea8eaf 100644 --- a/src/vm/error.rs +++ b/src/vm/error.rs @@ -18,6 +18,9 @@ pub enum RuntimeError { #[error("expected {0}")] WrongValue(String), + #[error("underflow when trying to finalize a new array (stack was smaller than when array creation began)")] + ArrayUnderflow, + #[error("at {0}")] Span(Span, Box), } diff --git a/src/vm/machine.rs b/src/vm/machine.rs index 7bd1ccc..98a68ea 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -69,6 +69,7 @@ pub struct Machine { quote_table: QuoteTable, scope_stack: ScopeStack, call_stack: Vec, + array_marker_stack: Vec, } impl Machine { @@ -84,6 +85,7 @@ impl Machine { quote_table: Default::default(), scope_stack, call_stack: Default::default(), + array_marker_stack: Default::default(), } } @@ -296,10 +298,16 @@ impl Machine { value.call(Some(inst.span().clone()), self)?; } Inst::BeginArray => { - todo!() + self.array_marker_stack.push(self.stack().len()); } Inst::EndArray => { - todo!() + let array_start = self.array_marker_stack.pop().expect("expected array end"); + if self.stack().len() <= array_start + 1 { + return Err(RuntimeError::ArrayUnderflow); + } + let array = self.stack_mut().split_off(array_start); + let array_obj = ArrayObj::new(array); + self.stack_push(array_obj)?; } };