Add base branch logic

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2023-04-07 00:00:04 -07:00
parent 4d005494a3
commit 7bac4306c3
17 changed files with 20302 additions and 5491 deletions

View File

@@ -2,3 +2,27 @@ println(123);
println(None);
message = "hell world";
println(message);
if 1 {
println("OK");
} else if 0 {
println("1 - fail?");
} else {
println("2 - fail?");
};
if 0 {
println("3 - fail?");
} else if 1 {
println("OK");
} else {
println("4 - fail?");
};
if 0 {
println("5 - fail?");
} else if 0 {
println("6 - fail?");
} else {
println("OK");
};

View File

@@ -1,8 +1,10 @@
use std::collections::VecDeque;
use crate::compile::{scope::GlobalScope, thunk::Thunk};
use crate::obj::prelude::*;
use crate::obj::ObjPtr;
use crate::syn::ast::*;
use crate::vm::inst::Inst;
use crate::vm::inst::*;
pub struct Compiler {
scope: GlobalScope,
@@ -28,20 +30,28 @@ impl Compiler {
/// Compile the given list of AST statements, returning the body.
pub fn compile_stmts(&mut self, stmts: &Vec<SpStmt>) -> Vec<Inst> {
self.gather_names(stmts);
let mut thunks = Vec::new();
let mut thunks = VecDeque::new();
for stmt in stmts {
thunks.push(self.emit_stmt(stmt));
thunks.push_back(self.emit_stmt(stmt));
}
Thunk::List(thunks).flatten()
}
fn emit_body(&mut self, body: &SpBody) -> Thunk {
let mut thunks = VecDeque::new();
for stmt in body.inner() {
thunks.push_back(self.emit_stmt(stmt));
}
Thunk::List(thunks)
}
fn emit_stmt(&mut self, stmt: &SpStmt) -> Thunk {
match stmt.inner() {
Stmt::Assign(lhs_expr, expr) => match lhs_expr.inner() {
AssignLhs::Name(n) => {
let name = self.scope().lookup_scoped(n).unwrap();
let mut thunk = self.emit_expr(expr);
thunk.push(Inst::Store(name));
thunk.push_back(Inst::Store(name));
thunk
}
AssignLhs::Complex(_, _) => {
@@ -50,10 +60,80 @@ impl Compiler {
},
Stmt::Expr(expr) => {
let mut thunk = self.emit_expr(expr);
thunk.push(Inst::Pop);
thunk.push_back(Inst::Pop);
thunk
}
Stmt::If { .. } => todo!(),
Stmt::If {
if_true,
elseif,
else_body,
} => {
// compile the "else" thunk first - it doesn't need to jump
let mut else_thunk = if let Some(else_body) = else_body {
self.emit_body(else_body)
} else {
Thunk::empty()
};
else_thunk.push_front(Inst::Comment("else body".to_string()));
// compile the "else if" thunks next. These need to jump forward N
// instructions at the very end
let mut jump_forward: isize = (else_thunk.len() + 1).try_into().unwrap();
let mut thunks = VecDeque::with_capacity(
1 + // else thunk
elseif.len() + // elseif thunks
1, // if thunk
);
thunks.push_front(else_thunk);
for cond_block in elseif.iter().rev() {
// do block, then condition
let mut body_thunk = self.emit_body(&cond_block.inner().body);
// jump forward by the previous jump forward distance
body_thunk.push_front(Inst::Comment("elseif body".to_string()));
body_thunk.push_back(Inst::JumpRelative(jump_forward, JumpCondition::Always));
let mut cond_thunk = self.emit_expr(&cond_block.inner().cond);
cond_thunk.push_front(Inst::Comment("elseif compare ".to_string()));
cond_thunk.push_back(Inst::Compare);
// jump forward by the length of the body
cond_thunk.push_back(Inst::JumpRelative(
isize::try_from(body_thunk.len()).unwrap() + 1,
JumpCondition::False,
));
jump_forward +=
isize::try_from(body_thunk.len() + cond_thunk.len()).unwrap() + 1;
thunks.push_front(body_thunk);
thunks.push_front(cond_thunk);
}
// same as above: do block, then condition
let mut body_thunk = self.emit_body(&if_true.inner().body);
body_thunk.push_front(Inst::Comment("if body".to_string()));
body_thunk.push_back(Inst::JumpRelative(jump_forward, JumpCondition::Always));
let mut cond_thunk = self.emit_expr(&if_true.inner().cond);
cond_thunk.push_front(Inst::Comment("if compare".to_string()));
cond_thunk.push_back(Inst::Compare);
cond_thunk.push_back(Inst::JumpRelative(
// + 1 because otherwise we won't end up on the next instruction
isize::try_from(body_thunk.len()).unwrap() + 1,
JumpCondition::False,
));
// don't need to add this
//jump_forward += isize::try_from(body_thunk.len() + cond_thunk.len()).unwrap();
thunks.push_front(body_thunk);
thunks.push_front(cond_thunk);
thunks.push_back(Thunk::Block(
vec![Inst::Comment("end of if block".to_string())].into(),
));
Thunk::List(thunks)
}
Stmt::Def { .. } => todo!(),
Stmt::Import { .. } => todo!(),
}
@@ -62,16 +142,16 @@ impl Compiler {
fn emit_expr(&mut self, expr: &SpExpr) -> Thunk {
match expr.inner() {
Expr::Call(expr, args) => {
let mut thunk = Vec::with_capacity(args.len() + 1);
thunk.push(self.emit_expr(expr));
let mut thunk = VecDeque::with_capacity(args.len() + 1);
thunk.push_back(self.emit_expr(expr));
thunk.extend(args.iter().map(|arg| self.emit_expr(arg)));
let mut thunk = Thunk::List(thunk);
thunk.push(Inst::Call(args.len()));
thunk.push_back(Inst::Call(args.len()));
thunk
}
Expr::Get(expr, name) => {
let mut thunk = self.emit_expr(expr);
thunk.push(Inst::GetAttr(name.clone()));
thunk.push_back(Inst::GetAttr(name.clone()));
thunk
}
Expr::Atom(atom) => self.emit_atom(atom),
@@ -80,19 +160,19 @@ impl Compiler {
fn emit_atom(&mut self, atom: &SpAtom) -> Thunk {
match atom.inner() {
Atom::Int(i) => Thunk::Block(vec![Inst::Push(ObjPtr::new(Int::new(*i)))]),
Atom::Int(i) => Thunk::Block(vec![Inst::Push(ObjPtr::new(Int::new(*i)))].into()),
Atom::Float(_) => todo!(), // Thunk::Block(vec![Inst::Push(Gc::new(Float::new(*f)))]),
Atom::Str(s) => Thunk::Block(vec![Inst::Push(ObjPtr::new(Str::new(s.clone())))]),
Atom::Str(s) => Thunk::Block(vec![Inst::Push(ObjPtr::new(Str::new(s.clone())))].into()),
Atom::Sym(_) => todo!(),
Atom::Name(n) => {
// Look up the symbol first. It may already exist.
// If it doesn't exist, create it. It may be created
// dynamically.
// Dynamically.
let name = self
.scope()
.lookup_scoped(n)
.unwrap_or_else(|| self.scope_mut().insert_local(n));
Thunk::Block(vec![Inst::Load(name)])
Thunk::Block(vec![Inst::Load(name)].into())
}
}
}
@@ -114,8 +194,22 @@ impl Compiler {
// Get the names of the variables used in this expr
self.gather_expr_names(expr.inner());
}
Stmt::If { .. } => {
todo!()
Stmt::If {
if_true,
elseif,
else_body,
} => {
self.gather_expr_names(if_true.inner().cond.inner());
self.gather_names(if_true.inner().body.inner());
for cond_body in elseif {
self.gather_expr_names(cond_body.inner().cond.inner());
self.gather_names(cond_body.inner().body.inner());
}
if let Some(else_body) = else_body {
self.gather_names(else_body.inner());
}
}
Stmt::Import { .. } => {
todo!()

View File

@@ -1,52 +1,62 @@
use std::collections::VecDeque;
use crate::vm::inst::Inst;
pub enum Thunk {
Block(Vec<Inst>),
List(Vec<Thunk>),
/*
IfElse {
cond: Box<Thunk>,
if_true: Box<Thunk>,
if_false: Box<Thunk>,
},
*/
Block(VecDeque<Inst>),
List(VecDeque<Thunk>),
}
impl Thunk {
pub fn empty() -> Self {
Thunk::Block(Default::default())
}
pub fn flatten(self) -> Vec<Inst> {
use Thunk::*;
match self {
Block(block) => block,
Block(block) => block.into(),
List(list) => list.into_iter().flat_map(|thunk| thunk.flatten()).collect(),
/*
IfElse { .. } => {
todo!("flatten if/else")
}
*/
}
}
pub fn push(&mut self, inst: Inst) {
pub fn push_back(&mut self, inst: Inst) {
use Thunk::*;
match self {
Block(block) => {
block.push(inst);
block.push_back(inst);
}
List(list) => {
if let Some(Block(last)) = list.last_mut() {
if let Some(Block(last)) = list.back_mut() {
// if this is a block, then push the inst to the end of the
// block instead of creating a new element
last.push(inst);
last.push_back(inst);
} else {
// otherwise, push a new list block
list.push(Block(vec![inst]));
list.push_back(Block(vec![inst].into()));
}
} /*
IfElse { .. } => {
// replace this with a List type, with *self as the first element
todo!()
}
*/
}
}
}
pub fn push_front(&mut self, inst: Inst) {
use Thunk::*;
match self {
Block(block) => block.push_front(inst),
List(list) => {
if let Some(Block(first)) = list.front_mut() {
first.push_front(inst);
} else {
list.push_front(Block(vec![inst].into()));
}
}
}
}
pub fn len(&self) -> usize {
match self {
Thunk::Block(block) => block.len(),
Thunk::List(list) => list.iter().map(Thunk::len).sum(),
}
}
}

View File

@@ -20,6 +20,19 @@ struct Opt {
type Result<T = (), E = Box<dyn std::error::Error>> = std::result::Result<T, E>;
fn disassemble(insts: &Vec<Inst>) {
// I could do some annoying casting shit, or I could just do string length
let max_digits = insts.len().to_string().len();
for (addr, inst) in insts.iter().enumerate() {
println!(
"{:0width$} {}",
addr,
inst.disassemble_string(),
width = max_digits
);
}
}
fn main() -> Result {
let opt = Opt::from_args();
@@ -59,7 +72,8 @@ fn main() -> Result {
exec_body.push(Inst::Push(ObjPtr::new(Int::new(0))));
exec_body.push(Inst::Return);
//println!("{:#?}", exec_body);
//disassemble(&exec_body);
//return Ok(());
let locals: Vec<_> = scope.keys().copied().collect();
let main_fun: ObjPtr<_> = UserFun::new(None, vec![], locals, exec_body).into();

66
src/obj/boolean.rs Normal file
View File

@@ -0,0 +1,66 @@
use crate::obj::prelude::*;
use std::{any::Any, sync::LazyLock};
pub type BooleanValue = bool;
pub static BOOL_TRUE_INST: LazyLock<ObjPtr> = LazyLock::new(|| ObjPtr::new(Boolean::new(true)));
pub static BOOL_FALSE_INST: LazyLock<ObjPtr> = LazyLock::new(|| ObjPtr::new(Boolean::new(false)));
pub static BOOL_INSTS: &[&LazyLock<ObjPtr>] = &[&BOOL_FALSE_INST, &BOOL_TRUE_INST];
static BOOL_ATTRS: LazyLock<AttrsPtr> = LazyLock::new(|| {
attrs_ptr! {
//
"__str__" => builtin_fun_ptr!("__str__", |_vm, args, _state| {
if args.len() != 1 {
todo!("Throw exception: arity error");
}
let arg_any = &args[0].as_any();
let obj = arg_any.downcast_ref::<Boolean>().expect("Boolean.__str__ did not receive Boolean?");
BuiltinFunResult::Return(Some(ObjPtr::new(Str::new(obj.value().to_string()))))
}),
"__bool__" => builtin_fun_ptr!("__bool__", |_vm, args, _state| {
let arg_any = &args[0].as_any();
let obj = arg_any.downcast_ref::<Boolean>().expect("Boolean.__str__ did not receive Boolean?");
BuiltinFunResult::Return(Some(ObjPtr::new(Boolean::new(obj.is_truthy()))))
}),
}
});
#[derive(Debug, Clone)]
pub struct Boolean {
attrs: AttrsPtr,
value: BooleanValue,
}
impl Boolean {
fn new(value: BooleanValue) -> Self {
let attrs = AttrsPtr::clone(&BOOL_ATTRS);
Boolean { attrs, value }
}
pub fn value(&self) -> BooleanValue {
self.value
}
}
impl Obj for Boolean {
fn attrs(&self) -> AttrsPtr {
AttrsPtr::clone(&self.attrs)
}
fn as_any(&self) -> &(dyn Any + 'static) {
self
}
fn is_truthy(&self) -> bool {
self.value()
}
}
impl ToString for Boolean {
fn to_string(&self) -> String {
format!("{}", self.value())
}
}

View File

@@ -134,4 +134,8 @@ impl Obj for BuiltinFun {
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
self
}
fn is_truthy(&self) -> bool {
true
}
}

View File

@@ -1,12 +1,20 @@
//! Builtin functions and objects.
use crate::obj::prelude::*;
////////////////////////////////////////////////////////////////////////////////
// Builtin functions
////////////////////////////////////////////////////////////////////////////////
pub const BUILTIN_FUNS: &[(&str, BuiltinFunPtr)] = &[
//
("println", println),
("print", print),
];
////////////////////////////////////////////////////////////////////////////////
// Shared functionality between builtins
////////////////////////////////////////////////////////////////////////////////
fn println(vm: BuiltinFunVm, args: Vec<ObjPtr>, state: BuiltinFunState) -> BuiltinFunResult {
do_print(vm, args, state, "\n")
}

View File

@@ -15,6 +15,12 @@ static INT_ATTRS: LazyLock<AttrsPtr> = LazyLock::new(|| {
BuiltinFunResult::Return(Some(ObjPtr::new(Str::new(obj.value().to_string()))))
}),
"__bool__" => builtin_fun_ptr!("__bool__", |_vm, args, _state| {
let arg_any = &args[0].as_any();
let obj = arg_any.downcast_ref::<Int>().expect("Int.__bool__ did not receive Int?");
let result = ObjPtr::clone(&BOOL_INSTS[obj.is_truthy() as usize]);
BuiltinFunResult::Return(Some(result))
}),
}
});
@@ -44,6 +50,10 @@ impl Obj for Int {
fn as_any(&self) -> &(dyn Any + 'static) {
self
}
fn is_truthy(&self) -> bool {
self.value() != 0
}
}
impl ToString for Int {

View File

@@ -1,3 +1,4 @@
pub mod boolean;
pub mod builtin_fun;
pub mod builtins;
pub mod int;
@@ -7,7 +8,7 @@ pub mod user_fun;
pub mod prelude {
// Module types
pub use super::{builtin_fun::*, int::*, none::*, str::*, user_fun::*};
pub use super::{boolean::*, builtin_fun::*, int::*, none::*, str::*, user_fun::*};
// Local types
pub use super::{Attrs, AttrsPtr, Obj, ObjPtr};
// Macros
@@ -70,4 +71,10 @@ pub trait Obj: Debug + Sync + Send {
let mut attrs = attrs_ptr.write().unwrap();
attrs.insert(key, value)
}
fn is_truthy(&self) -> bool;
fn is_falsey(&self) -> bool {
!self.is_truthy()
}
}

View File

@@ -13,6 +13,13 @@ static NONE_ATTRS: LazyLock<AttrsPtr> = LazyLock::new(|| {
}
BuiltinFunResult::Return(Some(ObjPtr::clone(&NONE_STR)))
}),
"__bool__" => builtin_fun_ptr!("__bool__", |_vm, args, _state| {
let arg_any = &args[0].as_any();
let obj = arg_any.downcast_ref::<Int>().expect("None.__bool__ did not receive Int?");
let result = ObjPtr::clone(&BOOL_INSTS[obj.is_truthy() as usize]);
BuiltinFunResult::Return(Some(result))
}),
}
});
@@ -36,4 +43,8 @@ impl Obj for None {
fn as_any(&self) -> &(dyn Any + 'static) {
self
}
fn is_truthy(&self) -> bool {
false
}
}

View File

@@ -12,6 +12,12 @@ static STR_ATTRS: LazyLock<AttrsPtr> = LazyLock::new(|| {
}
BuiltinFunResult::Return(Some(args.pop().unwrap()))
}),
"__bool__" => builtin_fun_ptr!("__bool__", |_vm, args, _state| {
let arg_any = &args[0].as_any();
let obj = arg_any.downcast_ref::<Int>().expect("Str.__bool__ did not receive Int?");
let result = ObjPtr::clone(&BOOL_INSTS[obj.is_truthy() as usize]);
BuiltinFunResult::Return(Some(result))
}),
}
});
@@ -44,6 +50,10 @@ impl Obj for Str {
fn as_any(&self) -> &(dyn Any + 'static) {
self
}
fn is_truthy(&self) -> bool {
self.value().len() > 0
}
}
impl ToString for Str {

View File

@@ -47,4 +47,8 @@ impl Obj for UserFun {
fn as_any(&self) -> &(dyn std::any::Any + 'static) {
self
}
fn is_truthy(&self) -> bool {
true
}
}

View File

@@ -1,13 +1,16 @@
use crate::syn::Spanned;
pub type SpBody = Spanned<Body>;
pub type Body = Vec<SpStmt>;
#[derive(Debug, Clone)]
pub enum Stmt {
Assign(SpAssignLhs, SpExpr),
Expr(SpExpr),
If {
if_true: SpCondExpr,
elseif: Vec<SpCondExpr>,
else_expr: Option<SpExpr>,
if_true: SpCondBody,
elseif: Vec<SpCondBody>,
else_body: Option<SpBody>,
},
Def {
name: String,
@@ -38,12 +41,12 @@ pub enum AssignLhs {
pub type SpStmt = Spanned<Stmt>;
#[derive(Debug, Clone)]
pub struct CondExpr {
pub struct CondBody {
pub cond: SpExpr,
pub expr: SpExpr,
pub body: SpBody,
}
pub type SpCondExpr = Spanned<CondExpr>;
pub type SpCondBody = Spanned<CondBody>;
#[derive(Debug, Clone)]
pub struct Param {

View File

@@ -9,7 +9,21 @@ Spanned<T>: Spanned<T> = {
<start:@L> <inner:T> <end:@R> => Spanned::new(Span::new(start, end), inner),
};
pub File: Vec<SpStmt> = {
////////////////////////////////////////////////////////////////////////////////
// File, block, body
////////////////////////////////////////////////////////////////////////////////
pub File: Body = {
<Body> => <>,
}
pub SpBlock = Spanned<Block>;
pub Block: Body = {
"{" <Body> "}" => <>,
}
pub Body: Body = {
<mut head:(<SpStmt> ";")*> <tail:SpStmt?> => {
if let Some(tail) = tail {
head.push(tail);
@@ -18,11 +32,16 @@ pub File: Vec<SpStmt> = {
},
}
////////////////////////////////////////////////////////////////////////////////
// Statements, assignment
////////////////////////////////////////////////////////////////////////////////
pub SpStmt = Spanned<Stmt>;
pub Stmt: Stmt = {
<SpExpr> => Stmt::Expr(<>),
<lhs:SpAssignLhs> "=" <expr:SpExpr> => Stmt::Assign(lhs, expr),
<IfStmt> => <>,
}
pub SpAssignLhs = Spanned<AssignLhs>;
@@ -31,6 +50,46 @@ pub AssignLhs: AssignLhs = {
<Name> => { AssignLhs::Name(<>) }
}
////////////////////////////////////////////////////////////////////////////////
// Branch, condition blocks
////////////////////////////////////////////////////////////////////////////////
pub IfStmt: Stmt = {
<if_start:@L>
"if" <if_condition:SpExpr> <if_block:SpBlock>
<if_end:@R>
<elif_start:@L>
<elif_block:(<@L> "else" "if" <SpExpr> <SpBlock> <@R>)*>
<elif_end:@R>
<else_start:@L>
<else_block:("else" <SpBlock>)?>
<else_end:@R> => {
// Make if statement cond body
let if_true = CondBody {
cond: if_condition,
body: if_block,
};
// Make elif statement cond body
let mut elseif = Vec::with_capacity(elif_block.len());
for (start, expr, block, end) in elif_block {
let elseif_cond_body = CondBody {
cond: expr,
body: block
};
elseif.push(Spanned::new(Span::new(start, end), elseif_cond_body));
}
Stmt::If {
if_true: Spanned::new(Span::new(if_start, if_end), if_true),
elseif,
else_body: else_block,
}
}
}
////////////////////////////////////////////////////////////////////////////////
// Expressions
////////////////////////////////////////////////////////////////////////////////
pub SpExpr = Spanned<Expr>;
pub Expr: Expr = {
@@ -48,6 +107,9 @@ pub Expr: Expr = {
"(" <expr:Expr> ")" => expr,
};
////////////////////////////////////////////////////////////////////////////////
// Atoms
////////////////////////////////////////////////////////////////////////////////
pub SpAtom = Spanned<Atom>;
@@ -66,4 +128,4 @@ Str: String = {
}
Name: String = {
<s:r"[a-zA-Z_][a-zA-Z0-9_]*"> => s.to_string(),
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,13 @@
use crate::obj::ObjPtr;
use crate::vm::name::Name;
#[derive(Debug, Clone)]
pub enum JumpCondition {
False,
True,
Always,
}
#[derive(Debug, Clone)]
pub enum Inst {
/// Pushes an object to the stack.
@@ -29,4 +36,37 @@ pub enum Inst {
/// resize the stack to its last size (pre-call), and push the return value
/// back to the top of the stack.
Return,
/// Adds the given value to the current program counter (positive or
/// negative) when the given condition holds..
///
/// `Inst::JumpRelative(1)` functions as a no-op.
/// `Inst::JumpRelative(0)` functions as a "hang"
/// `Inst::JumpRelative(-1)` jumps to the previous instruction.
JumpRelative(isize, JumpCondition),
/// Pops the top value off of the stack and sets the condition flag to
/// "true" or "false".
Compare,
Comment(String),
}
impl Inst {
pub fn disassemble_string(&self) -> String {
match self {
Inst::Push(ptr) => format!("push <{:#x}>", (ptr as *const _ as usize)),
Inst::Load(name) => format!("load {name:?}"),
Inst::Store(name) => format!("store {name:?}"),
Inst::GetAttr(attr) => format!("getattr {attr}"),
Inst::Pop => format!("pop"),
Inst::Call(argc) => format!("call {argc}"),
Inst::Return => format!("return"),
Inst::JumpRelative(distance, condition) => {
format!("jump {:+} (when condition is {:?})", distance, condition)
}
Inst::Compare => format!("compare"),
Inst::Comment(comment) => format!("# {comment}"),
}
}
}

View File

@@ -4,7 +4,7 @@ pub mod name;
use crate::obj::prelude::*;
use crate::vm::frame::{BuiltinFrame, UserFrame};
use crate::vm::{frame::Frame, inst::Inst, name::Name};
use crate::vm::{frame::Frame, inst::*, name::Name};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State {
@@ -18,6 +18,7 @@ pub struct Vm {
frames: Vec<Frame>,
state: State,
stack: Vec<ObjPtr>,
condition: bool,
}
impl Vm {
@@ -26,6 +27,7 @@ impl Vm {
frames: Default::default(),
state: State::Stopped,
stack: Default::default(),
condition: false,
}
}
@@ -73,6 +75,14 @@ impl Vm {
self.stack_mut().pop()
}
pub fn condition(&self) -> bool {
self.condition
}
pub fn set_condition(&mut self, condition: bool) {
self.condition = condition;
}
/// Loads a given name in the currently executing context.
pub fn load(&self, name: Name) -> Option<ObjPtr> {
// Search up the stack frame for local variable with the given name
@@ -122,6 +132,8 @@ impl Vm {
///
/// Returns the address of the next instruction to load.
fn exec_inst(&mut self) {
let self_condition = self.condition();
// don't like this, but this is the consequence of not using a pipeline I think
let frame = match self.active_frame_mut() {
Some(Frame::User(frame)) => frame,
@@ -129,7 +141,7 @@ impl Vm {
};
let inst = frame.get_inst();
let pc = frame.pc() + 1;
let mut pc = frame.pc() + 1;
let mut next_frame: Option<Frame> = None;
let mut pop_frame = false;
@@ -151,11 +163,34 @@ impl Vm {
let value = self.pop_stack().expect("Stack underflow");
self.store(name, Some(value));
}
Inst::GetAttr(_attr) => todo!(),
Inst::Pop => {
self
Inst::GetAttr(attr) => {
// XXX
// there's an annoying thing with this. We can't use
// self.pop_stack() and then use "attr" afterwards because they
// belong to the same owner (self).
// ALSO:
// We can't just do peek_stack() either, because since `attr`
// above was borrowed while `self` is mutable. If you want to
// borrow `self` immutably, you have to drop the reference above
// as well.
// So either we have to do some unsafe stuff (not a bad option
// here, since nothing is going to move) or clone(). Maybe a
// `get_attr()` function is in order, I don't know.
// For now we're cloning.
let attr = attr.clone();
let obj = self
.pop_stack()
.expect("Inst::Pop executed, but there was no value on top of the stack. This is most likely a bug.");
.expect("Inst::GetAttr executed, but there was no value on top of the stack.");
let value = obj.get(&attr);
if let Some(value) = value {
self.push_stack(value);
} else {
todo!("Throw exception: Could not get attr {attr:?} on object {obj:?}");
}
}
Inst::Pop => {
self.pop_stack()
.expect("Inst::Pop executed, but there was no value on top of the stack.");
}
Inst::Call(argc) => {
let argc = *argc;
@@ -182,6 +217,29 @@ impl Vm {
self.push_stack(return_value);
pop_frame = true;
}
Inst::JumpRelative(amount, condition) => {
match (self_condition, condition) {
(true, JumpCondition::True)
| (false, JumpCondition::False)
| (_, JumpCondition::Always) => {
// jump taken
pc = (pc as isize + amount - 1) as usize;
}
_ => {
// jump not taken, do nothing
}
}
}
Inst::Compare => {
let top = self.pop_stack().expect(
"Inst::Compare expected a value on top of the stack, but it was empty.",
);
self.set_condition(top.is_truthy());
}
Inst::Comment(comment) => {
/* no-op */
//println!("COMMENT: {comment}");
}
}
// Same as the top - this time update the program counter