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); println(None);
message = "hell world"; message = "hell world";
println(message); 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::compile::{scope::GlobalScope, thunk::Thunk};
use crate::obj::prelude::*; use crate::obj::prelude::*;
use crate::obj::ObjPtr; use crate::obj::ObjPtr;
use crate::syn::ast::*; use crate::syn::ast::*;
use crate::vm::inst::Inst; use crate::vm::inst::*;
pub struct Compiler { pub struct Compiler {
scope: GlobalScope, scope: GlobalScope,
@@ -28,20 +30,28 @@ impl Compiler {
/// Compile the given list of AST statements, returning the body. /// Compile the given list of AST statements, returning the body.
pub fn compile_stmts(&mut self, stmts: &Vec<SpStmt>) -> Vec<Inst> { pub fn compile_stmts(&mut self, stmts: &Vec<SpStmt>) -> Vec<Inst> {
self.gather_names(stmts); self.gather_names(stmts);
let mut thunks = Vec::new(); let mut thunks = VecDeque::new();
for stmt in stmts { for stmt in stmts {
thunks.push(self.emit_stmt(stmt)); thunks.push_back(self.emit_stmt(stmt));
} }
Thunk::List(thunks).flatten() 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 { fn emit_stmt(&mut self, stmt: &SpStmt) -> Thunk {
match stmt.inner() { match stmt.inner() {
Stmt::Assign(lhs_expr, expr) => match lhs_expr.inner() { Stmt::Assign(lhs_expr, expr) => match lhs_expr.inner() {
AssignLhs::Name(n) => { AssignLhs::Name(n) => {
let name = self.scope().lookup_scoped(n).unwrap(); let name = self.scope().lookup_scoped(n).unwrap();
let mut thunk = self.emit_expr(expr); let mut thunk = self.emit_expr(expr);
thunk.push(Inst::Store(name)); thunk.push_back(Inst::Store(name));
thunk thunk
} }
AssignLhs::Complex(_, _) => { AssignLhs::Complex(_, _) => {
@@ -50,10 +60,80 @@ impl Compiler {
}, },
Stmt::Expr(expr) => { Stmt::Expr(expr) => {
let mut thunk = self.emit_expr(expr); let mut thunk = self.emit_expr(expr);
thunk.push(Inst::Pop); thunk.push_back(Inst::Pop);
thunk 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::Def { .. } => todo!(),
Stmt::Import { .. } => todo!(), Stmt::Import { .. } => todo!(),
} }
@@ -62,16 +142,16 @@ impl Compiler {
fn emit_expr(&mut self, expr: &SpExpr) -> Thunk { fn emit_expr(&mut self, expr: &SpExpr) -> Thunk {
match expr.inner() { match expr.inner() {
Expr::Call(expr, args) => { Expr::Call(expr, args) => {
let mut thunk = Vec::with_capacity(args.len() + 1); let mut thunk = VecDeque::with_capacity(args.len() + 1);
thunk.push(self.emit_expr(expr)); thunk.push_back(self.emit_expr(expr));
thunk.extend(args.iter().map(|arg| self.emit_expr(arg))); thunk.extend(args.iter().map(|arg| self.emit_expr(arg)));
let mut thunk = Thunk::List(thunk); let mut thunk = Thunk::List(thunk);
thunk.push(Inst::Call(args.len())); thunk.push_back(Inst::Call(args.len()));
thunk thunk
} }
Expr::Get(expr, name) => { Expr::Get(expr, name) => {
let mut thunk = self.emit_expr(expr); let mut thunk = self.emit_expr(expr);
thunk.push(Inst::GetAttr(name.clone())); thunk.push_back(Inst::GetAttr(name.clone()));
thunk thunk
} }
Expr::Atom(atom) => self.emit_atom(atom), Expr::Atom(atom) => self.emit_atom(atom),
@@ -80,19 +160,19 @@ impl Compiler {
fn emit_atom(&mut self, atom: &SpAtom) -> Thunk { fn emit_atom(&mut self, atom: &SpAtom) -> Thunk {
match atom.inner() { 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::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::Sym(_) => todo!(),
Atom::Name(n) => { Atom::Name(n) => {
// Look up the symbol first. It may already exist. // Look up the symbol first. It may already exist.
// If it doesn't exist, create it. It may be created // If it doesn't exist, create it. It may be created
// dynamically. // Dynamically.
let name = self let name = self
.scope() .scope()
.lookup_scoped(n) .lookup_scoped(n)
.unwrap_or_else(|| self.scope_mut().insert_local(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 // Get the names of the variables used in this expr
self.gather_expr_names(expr.inner()); self.gather_expr_names(expr.inner());
} }
Stmt::If { .. } => { Stmt::If {
todo!() 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 { .. } => { Stmt::Import { .. } => {
todo!() todo!()

View File

@@ -1,52 +1,62 @@
use std::collections::VecDeque;
use crate::vm::inst::Inst; use crate::vm::inst::Inst;
pub enum Thunk { pub enum Thunk {
Block(Vec<Inst>), Block(VecDeque<Inst>),
List(Vec<Thunk>), List(VecDeque<Thunk>),
/*
IfElse {
cond: Box<Thunk>,
if_true: Box<Thunk>,
if_false: Box<Thunk>,
},
*/
} }
impl Thunk { impl Thunk {
pub fn empty() -> Self {
Thunk::Block(Default::default())
}
pub fn flatten(self) -> Vec<Inst> { pub fn flatten(self) -> Vec<Inst> {
use Thunk::*; use Thunk::*;
match self { match self {
Block(block) => block, Block(block) => block.into(),
List(list) => list.into_iter().flat_map(|thunk| thunk.flatten()).collect(), 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::*; use Thunk::*;
match self { match self {
Block(block) => { Block(block) => {
block.push(inst); block.push_back(inst);
} }
List(list) => { 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 // if this is a block, then push the inst to the end of the
// block instead of creating a new element // block instead of creating a new element
last.push(inst); last.push_back(inst);
} else { } else {
// otherwise, push a new list block // 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>; 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 { fn main() -> Result {
let opt = Opt::from_args(); 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::Push(ObjPtr::new(Int::new(0))));
exec_body.push(Inst::Return); exec_body.push(Inst::Return);
//println!("{:#?}", exec_body); //disassemble(&exec_body);
//return Ok(());
let locals: Vec<_> = scope.keys().copied().collect(); let locals: Vec<_> = scope.keys().copied().collect();
let main_fun: ObjPtr<_> = UserFun::new(None, vec![], locals, exec_body).into(); 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) { fn as_any(&self) -> &(dyn std::any::Any + 'static) {
self self
} }
fn is_truthy(&self) -> bool {
true
}
} }

View File

@@ -1,12 +1,20 @@
//! Builtin functions and objects. //! Builtin functions and objects.
use crate::obj::prelude::*; use crate::obj::prelude::*;
////////////////////////////////////////////////////////////////////////////////
// Builtin functions
////////////////////////////////////////////////////////////////////////////////
pub const BUILTIN_FUNS: &[(&str, BuiltinFunPtr)] = &[ pub const BUILTIN_FUNS: &[(&str, BuiltinFunPtr)] = &[
// //
("println", println), ("println", println),
("print", print), ("print", print),
]; ];
////////////////////////////////////////////////////////////////////////////////
// Shared functionality between builtins
////////////////////////////////////////////////////////////////////////////////
fn println(vm: BuiltinFunVm, args: Vec<ObjPtr>, state: BuiltinFunState) -> BuiltinFunResult { fn println(vm: BuiltinFunVm, args: Vec<ObjPtr>, state: BuiltinFunState) -> BuiltinFunResult {
do_print(vm, args, state, "\n") 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())))) 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) { fn as_any(&self) -> &(dyn Any + 'static) {
self self
} }
fn is_truthy(&self) -> bool {
self.value() != 0
}
} }
impl ToString for Int { impl ToString for Int {

View File

@@ -1,3 +1,4 @@
pub mod boolean;
pub mod builtin_fun; pub mod builtin_fun;
pub mod builtins; pub mod builtins;
pub mod int; pub mod int;
@@ -7,7 +8,7 @@ pub mod user_fun;
pub mod prelude { pub mod prelude {
// Module types // 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 // Local types
pub use super::{Attrs, AttrsPtr, Obj, ObjPtr}; pub use super::{Attrs, AttrsPtr, Obj, ObjPtr};
// Macros // Macros
@@ -70,4 +71,10 @@ pub trait Obj: Debug + Sync + Send {
let mut attrs = attrs_ptr.write().unwrap(); let mut attrs = attrs_ptr.write().unwrap();
attrs.insert(key, value) 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))) 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) { fn as_any(&self) -> &(dyn Any + 'static) {
self 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())) 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) { fn as_any(&self) -> &(dyn Any + 'static) {
self self
} }
fn is_truthy(&self) -> bool {
self.value().len() > 0
}
} }
impl ToString for Str { impl ToString for Str {

View File

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

View File

@@ -1,13 +1,16 @@
use crate::syn::Spanned; use crate::syn::Spanned;
pub type SpBody = Spanned<Body>;
pub type Body = Vec<SpStmt>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Stmt { pub enum Stmt {
Assign(SpAssignLhs, SpExpr), Assign(SpAssignLhs, SpExpr),
Expr(SpExpr), Expr(SpExpr),
If { If {
if_true: SpCondExpr, if_true: SpCondBody,
elseif: Vec<SpCondExpr>, elseif: Vec<SpCondBody>,
else_expr: Option<SpExpr>, else_body: Option<SpBody>,
}, },
Def { Def {
name: String, name: String,
@@ -38,12 +41,12 @@ pub enum AssignLhs {
pub type SpStmt = Spanned<Stmt>; pub type SpStmt = Spanned<Stmt>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct CondExpr { pub struct CondBody {
pub cond: SpExpr, pub cond: SpExpr,
pub expr: SpExpr, pub body: SpBody,
} }
pub type SpCondExpr = Spanned<CondExpr>; pub type SpCondBody = Spanned<CondBody>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Param { 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), <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?> => { <mut head:(<SpStmt> ";")*> <tail:SpStmt?> => {
if let Some(tail) = tail { if let Some(tail) = tail {
head.push(tail); head.push(tail);
@@ -18,11 +32,16 @@ pub File: Vec<SpStmt> = {
}, },
} }
////////////////////////////////////////////////////////////////////////////////
// Statements, assignment
////////////////////////////////////////////////////////////////////////////////
pub SpStmt = Spanned<Stmt>; pub SpStmt = Spanned<Stmt>;
pub Stmt: Stmt = { pub Stmt: Stmt = {
<SpExpr> => Stmt::Expr(<>), <SpExpr> => Stmt::Expr(<>),
<lhs:SpAssignLhs> "=" <expr:SpExpr> => Stmt::Assign(lhs, expr), <lhs:SpAssignLhs> "=" <expr:SpExpr> => Stmt::Assign(lhs, expr),
<IfStmt> => <>,
} }
pub SpAssignLhs = Spanned<AssignLhs>; pub SpAssignLhs = Spanned<AssignLhs>;
@@ -31,6 +50,46 @@ pub AssignLhs: AssignLhs = {
<Name> => { AssignLhs::Name(<>) } <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 SpExpr = Spanned<Expr>;
pub Expr: Expr = { pub Expr: Expr = {
@@ -48,6 +107,9 @@ pub Expr: Expr = {
"(" <expr:Expr> ")" => expr, "(" <expr:Expr> ")" => expr,
}; };
////////////////////////////////////////////////////////////////////////////////
// Atoms
////////////////////////////////////////////////////////////////////////////////
pub SpAtom = Spanned<Atom>; pub SpAtom = Spanned<Atom>;

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,13 @@
use crate::obj::ObjPtr; use crate::obj::ObjPtr;
use crate::vm::name::Name; use crate::vm::name::Name;
#[derive(Debug, Clone)]
pub enum JumpCondition {
False,
True,
Always,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Inst { pub enum Inst {
/// Pushes an object to the stack. /// 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 /// resize the stack to its last size (pre-call), and push the return value
/// back to the top of the stack. /// back to the top of the stack.
Return, 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::obj::prelude::*;
use crate::vm::frame::{BuiltinFrame, UserFrame}; 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum State { pub enum State {
@@ -18,6 +18,7 @@ pub struct Vm {
frames: Vec<Frame>, frames: Vec<Frame>,
state: State, state: State,
stack: Vec<ObjPtr>, stack: Vec<ObjPtr>,
condition: bool,
} }
impl Vm { impl Vm {
@@ -26,6 +27,7 @@ impl Vm {
frames: Default::default(), frames: Default::default(),
state: State::Stopped, state: State::Stopped,
stack: Default::default(), stack: Default::default(),
condition: false,
} }
} }
@@ -73,6 +75,14 @@ impl Vm {
self.stack_mut().pop() 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. /// Loads a given name in the currently executing context.
pub fn load(&self, name: Name) -> Option<ObjPtr> { pub fn load(&self, name: Name) -> Option<ObjPtr> {
// Search up the stack frame for local variable with the given name // 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. /// Returns the address of the next instruction to load.
fn exec_inst(&mut self) { 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 // don't like this, but this is the consequence of not using a pipeline I think
let frame = match self.active_frame_mut() { let frame = match self.active_frame_mut() {
Some(Frame::User(frame)) => frame, Some(Frame::User(frame)) => frame,
@@ -129,7 +141,7 @@ impl Vm {
}; };
let inst = frame.get_inst(); 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 next_frame: Option<Frame> = None;
let mut pop_frame = false; let mut pop_frame = false;
@@ -151,11 +163,34 @@ impl Vm {
let value = self.pop_stack().expect("Stack underflow"); let value = self.pop_stack().expect("Stack underflow");
self.store(name, Some(value)); self.store(name, Some(value));
} }
Inst::GetAttr(_attr) => todo!(), Inst::GetAttr(attr) => {
Inst::Pop => { // XXX
self // 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() .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) => { Inst::Call(argc) => {
let argc = *argc; let argc = *argc;
@@ -182,6 +217,29 @@ impl Vm {
self.push_stack(return_value); self.push_stack(return_value);
pop_frame = true; 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 // Same as the top - this time update the program counter