Add array example, ArrayObj implementation

* VM creates arrays using the BeginArray and EndArray instructions
* ArrayObj is implemented with a __str__ function (annoying)
* Simple example of array creation is added

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2022-02-17 18:53:29 -08:00
parent fe2a83174f
commit 76a50ce8c0
6 changed files with 172 additions and 3 deletions

15
examples/array.sy Normal file
View File

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

View File

@@ -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<ObjPtr>;
#[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::<ArrayObj>()
.ok_or_else(|| RuntimeError::WrongValue("array".to_string()))?;
let index_int = index_obj.as_any().downcast_ref::<IntObj>()
.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::<StrObj>()
.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::<IntObj>()
.ok_or_else(|| RuntimeError::WrongValue("int".to_string()))?;
let working_obj = machine.stack_pop()?;
let working = working_obj.as_any().downcast_ref::<StrObj>()
.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::<IntObj>() {
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<Self> {
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<Span>, _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
}
}

View File

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

View File

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

View File

@@ -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<RuntimeError>),
}

View File

@@ -69,6 +69,7 @@ pub struct Machine {
quote_table: QuoteTable,
scope_stack: ScopeStack,
call_stack: Vec<Frame>,
array_marker_stack: Vec<usize>,
}
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)?;
}
};