diff --git a/Cargo.toml b/Cargo.toml index c55dcef..9ef2dab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,5 @@ once_cell = "1.4.1" [dependencies.shredder] git = "https://github.com/Others/shredder" +rev = "c71335" features = ["nightly-features"] diff --git a/src/bin/not.rs b/src/bin/not.rs index d3aa8da..7a7d530 100644 --- a/src/bin/not.rs +++ b/src/bin/not.rs @@ -1,5 +1,6 @@ use not_python::{compile::Compile, syn::ast, vm::Vm}; +use shredder::run_with_gc_cleanup; use std::{fs, path::PathBuf}; use structopt::StructOpt; @@ -23,42 +24,43 @@ struct Options { } fn main() -> Result<()> { - let opt = Options::from_args(); - let text = fs::read_to_string(&opt.input)?; - let action = &opt.action.to_lowercase(); - match action.as_str() { - "run" | "parse" | "dump" => {} - _ => eprintln!("WARNING: unknown action {}", action), - } - - let ast = match ast::parse(text.as_str()) { - Ok(ast) => ast, - Err(errs) => { - for err in errs { - eprintln!("{}", err); - } - return Err("errors reported, exiting".into()); + run_with_gc_cleanup(|| { + let opt = Options::from_args(); + let text = fs::read_to_string(&opt.input)?; + let action = &opt.action.to_lowercase(); + match action.as_str() { + "run" | "parse" | "dump" => {} + _ => eprintln!("WARNING: unknown action {}", action), } - } - .unwrap(); - if action == "parse" { - println!("{:#?}", ast); - return Ok(()); - } - let (const_pool, main) = Compile::new().compile(&ast)?; + let ast = match ast::parse(text.as_str()) { + Ok(ast) => ast, + Err(errs) => { + for err in errs { + eprintln!("{}", err); + } + return Err("errors reported, exiting".into()); + } + } + .unwrap(); + if action == "parse" { + println!("{:#?}", ast); + return Ok(()); + } - if action == "dump" { - let mut stdout = std::io::stdout(); - not_python::read_obj!(let main_obj = main); - main_obj.dump(&mut stdout, &const_pool, main_obj.locals())?; - return Ok(()); - } + let (const_pool, main) = Compile::new().compile(&ast)?; - if action == "run" { - let mut vm = Vm::new(&const_pool); - vm.call(main, vec![])?; - } + if action == "dump" { + let mut stdout = std::io::stdout(); + not_python::read_obj!(let main_obj = main); + main_obj.dump(&mut stdout, &const_pool, main_obj.locals())?; + return Ok(()); + } - Ok(()) + if action == "run" { + let mut vm = Vm::new(&const_pool); + vm.call(main, vec![])?; + } + Ok(()) + }) } diff --git a/src/compile/locals.rs b/src/compile/locals.rs index 4cf2da7..fe9303f 100644 --- a/src/compile/locals.rs +++ b/src/compile/locals.rs @@ -76,6 +76,12 @@ impl Visit for CollectLocals<'_> { fn visit_fun_expr(&mut self, _expr: &FunExpr) -> Self::Out { // Do not collect names for function expressions, since they have their own scope. } + fn visit_if_expr(&mut self, expr: &IfExpr) -> Self::Out { + DefaultAccept::default_accept(expr, self); + } + fn visit_cond_body(&mut self, cond_body: &CondBody) -> Self::Out { + DefaultAccept::default_accept(cond_body, self); + } fn visit_atom(&mut self, atom: &Atom) -> Self::Out { DefaultAccept::default_accept(atom, self); } diff --git a/src/compile/thunk.rs b/src/compile/thunk.rs index 1430aec..4c42ba3 100644 --- a/src/compile/thunk.rs +++ b/src/compile/thunk.rs @@ -1,6 +1,6 @@ use crate::{ compile::{basic_block::*, error::*, Compile}, - obj::prelude::*, + obj::{prelude::*, reserved::*}, syn::{ast::*, visit::*}, vm::inst::*, }; @@ -25,10 +25,7 @@ pub enum Thunk { /// /// Only one of these thunks will be executed. At the end of either thunk, the program will /// continue at the address following this branch. - Branch { - thunk_true: Box, - thunk_false: Box, - }, + Branch(ThunkBranch), /// Based on the conditional flag in the VM, code for this loop will continue to execute. /// @@ -94,11 +91,11 @@ impl Thunk { Thunk::List(thunks) => thunks .iter() .fold(0, |n, thunk| n + thunk.basic_block_count()), - Thunk::Branch { + Thunk::Branch(ThunkBranch { + preamble, thunk_true, thunk_false, - // length is true + false block count, + 1 for the branch basic block at the start - } => thunk_true.basic_block_count() + thunk_false.basic_block_count() + 1, + }) => preamble.basic_block_count() + thunk_true.basic_block_count() + thunk_false.basic_block_count() + 1, // length is thunk, + 1 for branch at the start of the loop Thunk::Loop(thunk) => thunk.basic_block_count() + 1, Thunk::Nop => 0, @@ -128,6 +125,16 @@ impl From> for Thunk { } } +// +// struct ThunkBranch +// +#[derive(Debug, Clone, PartialEq)] +pub struct ThunkBranch { + preamble: Box, + thunk_true: Box, + thunk_false: Box, +} + // // struct Flatten // @@ -150,6 +157,9 @@ impl Flatten { let last_block = thunk.basic_block_count(); self.flatten_next(last_block, thunk); assert_eq!(self.blocks.len(), last_block); + // push an extra null block at the very end so that anything pointing to `last_block` will + // have a valid block index + self.blocks.insert(last_block, BasicBlock::Block { exit: last_block + 1, block: Default::default() }); self.blocks } @@ -174,13 +184,17 @@ impl Flatten { } assert_eq!(next_block, self.this_block()); } - Thunk::Branch { + Thunk::Branch(ThunkBranch { + preamble, thunk_true, thunk_false, - } => { - let branch_block = self.this_block(); - let block_true = self.this_block() + 1; + }) => { + let preamble_block = self.this_block(); + let branch_block = preamble_block + preamble.basic_block_count(); + let block_true = branch_block + 1; let block_false = block_true + thunk_true.basic_block_count(); + self.flatten_next(branch_block, *preamble); + assert_eq!(self.this_block(), branch_block); self.blocks.insert( branch_block, BasicBlock::Branch { @@ -188,7 +202,9 @@ impl Flatten { block_false, }, ); + assert_eq!(self.this_block(), block_true); self.flatten_next(next_block, *thunk_true); + assert_eq!(self.this_block(), block_false); self.flatten_next(next_block, *thunk_false); assert_eq!(self.this_block(), next_block); } @@ -263,7 +279,7 @@ impl Visit for CompileBody<'_> { let mut thunk = if let Some(expr) = stmt.expr.as_ref() { self.visit_expr(expr)? } else { - Inst::PushSym(crate::obj::reserved::NIL_NAME.sym).into() + Inst::PushSym(NIL_NAME.sym).into() }; thunk.push(Inst::Return); @@ -394,7 +410,7 @@ impl Visit for CompileBody<'_> { // If the last instruction is not a return, or if there are no instructions, then return // :nil value. if !matches!(code.last(), Some(Inst::Return)) { - code.push(Inst::PushSym(crate::obj::reserved::NIL_NAME.sym)); + code.push(Inst::PushSym(NIL_NAME.sym)); code.push(Inst::Return); } @@ -429,6 +445,46 @@ impl Visit for CompileBody<'_> { Ok(Inst::PushConst(hdl).into()) } + fn visit_if_expr(&mut self, expr: &IfExpr) -> Self::Out { + // base if condition + let mut thunk = self.visit_cond_body(&expr.cond_body)?; + { + // elif branches + let mut prev_thunk: &mut Thunk = &mut thunk; + for elif_cond_body in expr.elif.iter() { + let elif_thunk = self.visit_cond_body(elif_cond_body)?; + if let Thunk::Branch(thunk_branch) = prev_thunk { + thunk_branch.thunk_false = Box::new(elif_thunk); + prev_thunk = &mut thunk_branch.thunk_false; + } else { + unreachable!("accidentally found a non-branch thunk in elif expression") + } + } + + // el branch + if let (Some(el_body), Thunk::Branch(thunk_branch)) = (&expr.el, prev_thunk) { + thunk_branch.thunk_false = Box::new(self.visit_body(el_body)?); + //prev_thunk = &mut thunk_branch.thunk_false; + } + } + Ok(thunk) + } + + fn visit_cond_body(&mut self, cond_body: &CondBody) -> Self::Out { + let mut preamble = self.visit_expr(&cond_body.cond)?; + // Attempt to call the __bool__ function on this object which leaves a value on the stack + preamble.push_thunk(vec![ + Inst::GetAttr(BOOL_MEMBER_NAME.sym), + Inst::Call(0), + Inst::CheckTruth, + ]); + Ok(Thunk::Branch(ThunkBranch { + preamble: preamble.into(), + thunk_true: self.visit_body(&cond_body.body)?.into(), + thunk_false: Box::new(Thunk::Nop), + })) + } + fn visit_atom(&mut self, atom: &Atom) -> Self::Out { let thunk = match atom { Atom::Ident(ident) => { @@ -490,13 +546,12 @@ fn test_flatten_thunk() { let end_body = vec![Inst::PushSym(Sym::new(1)), Inst::Call(1)]; let thunk = Thunk::List(vec![ - // do something before - Thunk::Body(init_body.clone()), // branch - Thunk::Branch { + Thunk::Branch(ThunkBranch { + preamble: Thunk::Body(init_body.clone()).into(), thunk_true: Thunk::Body(true_body.clone()).into(), thunk_false: Thunk::Body(false_body.clone()).into(), - }, + }), // do something after Thunk::Body(end_body.clone()), ]); @@ -504,7 +559,7 @@ fn test_flatten_thunk() { let block_count = thunk.basic_block_count(); let blocks = thunk.flatten(); - assert_eq!(blocks.len(), block_count); + assert_eq!(blocks.len(), block_count + 1); let mut iter = blocks.into_iter(); assert_eq!( @@ -557,5 +612,16 @@ fn test_flatten_thunk() { } ) ); + // empty block + assert_eq!( + iter.next().unwrap(), + ( + 5, + BasicBlock::Block { + exit: 6, + block: vec![], + } + ) + ); assert!(iter.next().is_none()); } diff --git a/src/obj/boolean.rs b/src/obj/boolean.rs new file mode 100644 index 0000000..99ae3a9 --- /dev/null +++ b/src/obj/boolean.rs @@ -0,0 +1,92 @@ +use crate::{ + obj::{prelude::*, reserved::*}, + vm::error::*, +}; +use once_cell::sync::Lazy; +use shredder::Scan; +use std::fmt::{self, Debug, Formatter}; + +pub type BoolRef = ObjRef; + +#[derive(Scan)] +pub struct Bool { + value: bool, + vtable: Vtable, + attrs: Attrs, +} + +impl Bool { + /// Gets the value of this boolean. + pub fn value(&self) -> bool { + self.value + } +} + +impl From for BoolRef { + fn from(value: bool) -> Self { + if value { + BOOL_TRUE.clone() + } else { + BOOL_FALSE.clone() + } + } +} + +impl_obj_readonly!(Bool); + +impl Debug for Bool { + fn fmt(&self, fmt: &mut Formatter) -> fmt::Result { + fmt.debug_struct("Bool").field("value", &self.value).finish() + } +} + +/// A true boolean value. +/// +/// This value should be the only instance of a "true" boolean. +pub static BOOL_TRUE: Lazy = Lazy::new(|| + self_referring_obj! { + Bool { + value: true, + vtable: Default::default(), + attrs: Default::default(), + }, + vtable: |obj_ref: ObjRef| vtable! { + BOOL_MEMBER_NAME.sym => Method::new_obj(obj_ref.clone(), BOOL_BOOL_FUN.clone()), + } + } +); + +/// A false boolean value. +/// +/// This value should be the only instance of a "false" boolean. +pub static BOOL_FALSE: Lazy = Lazy::new(|| + self_referring_obj! { + Bool { + value: false, + vtable: Default::default(), + attrs: Default::default(), + }, + vtable: |obj_ref: ObjRef| vtable! { + BOOL_MEMBER_NAME.sym => Method::new_obj(obj_ref.clone(), BOOL_BOOL_FUN.clone()), + } + } +); + +//////////////////////////////////////////////////////////////////////////////// +// Builtin Bool functions +//////////////////////////////////////////////////////////////////////////////// + +/// Bool.__bool__(self) impl +static BOOL_BOOL_FUN: Lazy = Lazy::new(|| { + NativeFun::new_obj(1, |_callee, vm, args| { + { + read_obj_downcast!(let value: Option<&Bool> = &args[0]); + value.ok_or_else(|| Error::ValueError { + error: "expected Bool value".to_string(), + })?; + } + // just push a copy of the "self" value + vm.push(args[0].clone()); + Ok(crate::vm::signal::Signal::Return) + }) +}); diff --git a/src/obj/fun.rs b/src/obj/fun.rs index 064ef15..c2748ba 100644 --- a/src/obj/fun.rs +++ b/src/obj/fun.rs @@ -3,7 +3,7 @@ use crate::{ vm::{consts::ConstPool, error::*, frame::*, inst::Inst, signal::*, Vm}, }; use once_cell::sync::Lazy; -use shredder::{GcSafeWrapper, Scan}; +use shredder::Scan; use std::{ fmt::{self, Debug, Formatter}, io::{self, Write}, @@ -88,10 +88,10 @@ pub struct UserFun { vtable: Vtable, attrs: Attrs, // Safe because Vec doesn't need to be scanned - #[shredder(unsafe_skip)] + #[shredder(unsafe_skip_gc_safe)] code: Arc>, // Safe because this is just an interner that points to symbols, which aren't GC'd - #[shredder(unsafe_skip)] + #[shredder(unsafe_skip_gc_safe)] locals: FunLocals, arity: usize, } @@ -179,7 +179,7 @@ impl UserFun { ("(discarded)".to_string(), String::new()) } } - Inst::Jump(addr) | Inst::JumpTrue(addr) => (addr.to_string(), String::new()), + Inst::Jump(addr) | Inst::JumpTrue(addr) => (format!("{:0addr_w$x}", addr, addr_w = addr_w), String::new()), Inst::Call(argc) => (argc.to_string(), String::new()), _ => (String::new(), String::new()), }; @@ -261,15 +261,32 @@ pub static USER_FUN_TY: Lazy> = Lazy::new(|| Ty::new_obj(USER_FUN_NAM // struct NativeFun // -pub type NativeFunPtr = fn(ObjRef, &mut Vm, Vec) -> Result; +pub type FunPtr = fn(ObjRef, &mut Vm, Vec) -> Result; +pub struct NativeFunPtr(pub FunPtr); + +impl Clone for NativeFunPtr { + fn clone(&self) -> Self { + NativeFunPtr(self.0) + } +} + +impl std::ops::Deref for NativeFunPtr { + type Target = FunPtr; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +unsafe impl shredder::marker::GcDrop for NativeFunPtr { } + pub type NativeFunRef = ObjRef; #[derive(Scan)] pub struct NativeFun { vtable: Vtable, attrs: Attrs, - #[shredder(skip)] - fun: GcSafeWrapper, + #[shredder(unsafe_skip_gc_safe)] + fun: NativeFunPtr, arity: usize, } @@ -278,12 +295,12 @@ pub struct NativeFun { // impl NativeFun { - pub fn new_obj(arity: usize, fun: NativeFunPtr) -> ObjRef { + pub fn new_obj(arity: usize, fun: FunPtr) -> ObjRef { self_referring_obj! ( Self { vtable: Default::default(), attrs: Default::default(), - fun: GcSafeWrapper::new(fun), + fun: NativeFunPtr(fun), arity, }, vtable: |obj_ref: ObjRef| vtable! { @@ -345,7 +362,7 @@ impl Fun for NativeFun { got: args.len(), }); } - Ok(Frame::Native(NativeFrame::new(callee, *self.fun, args))) + Ok(Frame::Native(NativeFrame::new(callee, self.fun.clone(), args))) } } diff --git a/src/obj/int.rs b/src/obj/int.rs index a32ce5d..de92d55 100644 --- a/src/obj/int.rs +++ b/src/obj/int.rs @@ -26,6 +26,7 @@ impl Int { attrs: Default::default(), }, vtable: |obj_ref: ObjRef| vtable! { + BOOL_MEMBER_NAME.sym => Method::new_obj(obj_ref.clone(), INT_BOOL_FUN.clone()), REPR_MEMBER_NAME.sym => Method::new_obj(obj_ref.clone(), INT_STR_FUN.clone()), STR_MEMBER_NAME.sym => Method::new_obj(obj_ref.clone(), INT_STR_FUN.clone()), INT_MEMBER_NAME.sym => Method::new_obj(obj_ref.clone(), INT_INT_FUN.clone()), @@ -33,6 +34,8 @@ impl Int { MINUS_OP_NAME.sym => Method::new_obj(obj_ref.clone(), INT_SUB_FUN.clone()), TIMES_OP_NAME.sym => Method::new_obj(obj_ref.clone(), INT_MUL_FUN.clone()), DIV_OP_NAME.sym => Method::new_obj(obj_ref.clone(), INT_DIV_FUN.clone()), + EQ_OP_NAME.sym => Method::new_obj(obj_ref.clone(), INT_EQ_FUN.clone()), + NE_OP_NAME.sym => Method::new_obj(obj_ref.clone(), INT_NE_FUN.clone()), } } } @@ -54,6 +57,18 @@ impl Debug for Int { // Builtin Int functions //////////////////////////////////////////////////////////////////////////////// +/// Int.__bool__(self) impl +static INT_BOOL_FUN: Lazy = Lazy::new(|| { + NativeFun::new_obj(1, |_callee, vm, args| { + read_obj_downcast!(let int_obj: Option<&Int> = &args[0]); + let int_obj = int_obj.ok_or_else(|| Error::ValueError { + error: "expected Int value".to_string(), + })?; + vm.push(BoolRef::from(int_obj.value != 0)); + Ok(crate::vm::signal::Signal::Return) + }) +}); + /// Int.__str__(self) impl static INT_STR_FUN: Lazy = Lazy::new(|| { NativeFun::new_obj(1, |_callee, vm, args| { @@ -157,3 +172,41 @@ static INT_DIV_FUN: Lazy = Lazy::new(|| { Ok(crate::vm::signal::Signal::Return) }) }); + +/// Int.__eq__(self, Int) impl +static INT_EQ_FUN: Lazy = Lazy::new(|| { + NativeFun::new_obj(2, |_callee, vm, args| { + read_obj_downcast!(let lhs: Option<&Int> = &args[0]); + let lhs = lhs.ok_or_else(|| Error::ValueError { + error: "expected int for LHS".to_string(), + })?; + + read_obj_downcast!(let rhs: Option<&Int> = &args[1]); + let rhs = rhs.ok_or_else(|| Error::ValueError { + error: "expected int for RHS".to_string(), + })?; + + let result = lhs.value == rhs.value; + vm.push(BoolRef::from(result)); + Ok(crate::vm::signal::Signal::Return) + }) +}); + +/// Int.__ne__(self, Int) impl +static INT_NE_FUN: Lazy = Lazy::new(|| { + NativeFun::new_obj(2, |_callee, vm, args| { + read_obj_downcast!(let lhs: Option<&Int> = &args[0]); + let lhs = lhs.ok_or_else(|| Error::ValueError { + error: "expected int for LHS".to_string(), + })?; + + read_obj_downcast!(let rhs: Option<&Int> = &args[1]); + let rhs = rhs.ok_or_else(|| Error::ValueError { + error: "expected int for RHS".to_string(), + })?; + + let result = lhs.value != rhs.value; + vm.push(BoolRef::from(result)); + Ok(crate::vm::signal::Signal::Return) + }) +}); diff --git a/src/obj/macros.rs b/src/obj/macros.rs index e3f6471..88177fe 100644 --- a/src/obj/macros.rs +++ b/src/obj/macros.rs @@ -108,7 +108,7 @@ macro_rules! vtable { #[macro_export] macro_rules! handle_type { ($name:ident) => { - #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, shredder::Scan)] pub struct $name(usize); impl $name { @@ -128,7 +128,6 @@ macro_rules! handle_type { } } - impl shredder::EmptyScan for $name {} }; } diff --git a/src/obj/mod.rs b/src/obj/mod.rs index 5c82dfd..61bd8c8 100644 --- a/src/obj/mod.rs +++ b/src/obj/mod.rs @@ -2,6 +2,7 @@ mod macros; pub mod attrs; +pub mod boolean; pub mod builtin; pub mod fun; pub mod int; @@ -14,11 +15,11 @@ pub mod ty; pub mod prelude { pub use crate::obj::{ - attrs::*, fun::*, int::*, intern::*, name::*, str::*, sym::*, ty::*, Obj, ObjRef, + attrs::*, boolean::*, fun::*, int::*, intern::*, name::*, str::*, sym::*, ty::*, Obj, ObjRef, }; } -use shredder::{Gc, Scan}; +use shredder::{marker::GcDrop, Gc, Scan}; use std::{ fmt::{self, Debug, Formatter}, marker::Unsize, @@ -43,7 +44,7 @@ macro_rules! obj_attr { }; } -pub trait Obj: Scan + Debug { +pub trait Obj: Scan + GcDrop + Debug { fn vtable(&self) -> &Vtable; fn attrs(&self) -> &Attrs; fn attrs_mut(&mut self) -> Option<&mut Attrs>; diff --git a/src/obj/reserved.rs b/src/obj/reserved.rs index 31dc835..04c3b4a 100644 --- a/src/obj/reserved.rs +++ b/src/obj/reserved.rs @@ -46,6 +46,7 @@ name!(FUNC_MEMBER_NAME, "__func__"); name!(REPR_MEMBER_NAME, "__repr__"); name!(STR_MEMBER_NAME, "__str__"); name!(INT_MEMBER_NAME, "__int__"); +name!(BOOL_MEMBER_NAME, "__bool__"); // // Predefined VM-aware symbols @@ -64,8 +65,6 @@ name!(PRINT_BUILTIN_NAME, "print"); // // Builtin constants // -name!(TRUE_NAME, "true"); -name!(FALSE_NAME, "false"); name!(NIL_NAME, "nil"); // Operator function names diff --git a/src/syn/ast.rs b/src/syn/ast.rs index 4d5851c..533b5b2 100644 --- a/src/syn/ast.rs +++ b/src/syn/ast.rs @@ -133,6 +133,7 @@ pub enum Expr { Index(Box), Access(Box), Fun(Box), + If(Box), Atom(Atom), } @@ -157,6 +158,7 @@ impl DefaultAccept for Expr { Expr::Index(i) => i.accept(visitor), Expr::Access(a) => a.accept(visitor), Expr::Fun(f) => f.accept(visitor), + Expr::If(i) => i.accept(visitor), Expr::Atom(a) => a.accept(visitor), } } @@ -207,6 +209,12 @@ impl From for Expr { } } +impl From for Expr { + fn from(other: IfExpr) -> Self { + Expr::If(other.into()) + } +} + #[derive(Debug, Clone, PartialEq, Eq)] pub enum BinOp { Plus, @@ -434,6 +442,78 @@ impl DefaultAccept for FunExpr { } } +// +// struct IfExpr +// +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct IfExpr { + pub cond_body: CondBody, + pub elif: Vec, + pub el: Option, +} + +impl IfExpr { + pub fn new_expr(cond_body: CondBody, elif: Vec, el: Option) -> Expr { + Self { + cond_body, + elif, + el, + }.into() + } +} + +// +// impl Accept for IfExpr +// +impl Accept for IfExpr { + fn accept(&self, visitor: &mut V) -> V::Out { + visitor.visit_if_expr(self) + } +} + +// +// impl DefaultAccept for IfExpr +// +impl> DefaultAccept for IfExpr { + fn default_accept(&self, visitor: &mut V) -> V::Out { + visitor.visit_cond_body(&self.cond_body); + for elif in self.elif.iter() { + visitor.visit_cond_body(elif); + } + if let Some(el) = &self.el { + visitor.visit_body(&el); + } + } +} + +// +// struct CondBody +// +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct CondBody { + pub cond: Expr, + pub body: Body, +} + +// +// impl Accept for CondBody +// +impl Accept for CondBody { + fn accept(&self, visitor: &mut V) -> V::Out { + visitor.visit_cond_body(self) + } +} + +// +// impl DefaultAccept for CondBody +// +impl> DefaultAccept for CondBody { + fn default_accept(&self, visitor: &mut V) -> V::Out { + visitor.visit_expr(&self.cond); + visitor.visit_body(&self.body); + } +} + // // enum Atom // diff --git a/src/syn/lexer.l b/src/syn/lexer.l index fb79f56..1338512 100644 --- a/src/syn/lexer.l +++ b/src/syn/lexer.l @@ -1,6 +1,9 @@ %% fn "fn" return "return" +if "if" +elif "elif" +el "el" [\r\n;]+ "EOL" [a-zA-Z_][a-zA-Z0-9_]* "IDENT" diff --git a/src/syn/parser.y b/src/syn/parser.y index a657b50..ef916a4 100644 --- a/src/syn/parser.y +++ b/src/syn/parser.y @@ -104,7 +104,7 @@ AccessExpr -> Result: ; FunExpr -> Result: - AtomExpr { $1 } + IfExpr { $1 } | 'fn' '(' FunParams ')' '{' Body '}' { Ok(FunExpr::new_expr($3?, $6?)) } ; @@ -118,6 +118,31 @@ FunParamsTail -> Result>: | Ident { Ok(vec![$1?]) } ; +IfExpr -> Result: + AtomExpr { $1 } + | 'if' Expr '{' Body '}' Elif El { + Ok(IfExpr::new_expr( + CondBody { cond: $2?, body: $4?, }, + $6?, + $7?, + )) + } + ; + +Elif -> Result>: + Elif 'elif' Expr '{' Body '}' { + let mut head = $1?; + head.push(CondBody { cond: $3?, body: $5? }); + Ok(head) + } + | { Ok(Vec::new()) } + ; + +El -> Result>: + 'el' '{' Body '}' { $3.map(Some) } + | { Ok(None) } + ; + AtomExpr -> Result: Atom { $1.map(Expr::Atom) } | '(' Expr ')' { $2 } diff --git a/src/syn/visit.rs b/src/syn/visit.rs index d9809be..4f01964 100644 --- a/src/syn/visit.rs +++ b/src/syn/visit.rs @@ -23,6 +23,8 @@ pub trait Visit { fn visit_index_expr(&mut self, expr: &IndexExpr) -> Self::Out; fn visit_access_expr(&mut self, expr: &AccessExpr) -> Self::Out; fn visit_fun_expr(&mut self, expr: &FunExpr) -> Self::Out; + fn visit_if_expr(&mut self, expr: &IfExpr) -> Self::Out; + fn visit_cond_body(&mut self, cond_body: &CondBody) -> Self::Out; fn visit_atom(&mut self, atom: &Atom) -> Self::Out; } @@ -41,6 +43,8 @@ copy/paste of default_accepts fn visit_index_expr(&mut self, expr: &IndexExpr) -> Self::Out { DefaultAccept::default_accept(expr, self); } fn visit_access_expr(&mut self, expr: &AccessExpr) -> Self::Out { DefaultAccept::default_accept(expr, self); } fn visit_fun_expr(&mut self, expr: &FunExpr) -> Self::Out { DefaultAccept::default_accept(expr, self); } + fn visit_if_expr(&mut self, expr: &IfExpr) -> Self::Out { DefaultAccept::default_accept(expr, self); } + fn visit_cond_body(&mut self, cond_body: &CondBody) -> Self::Out { DefaultAccept::default_accept(cond_body, self); } fn visit_atom(&mut self, atom: &Atom) -> Self::Out { DefaultAccept(atom, self); } */ diff --git a/src/vm/consts.rs b/src/vm/consts.rs index 1c9fdfe..4768cd5 100644 --- a/src/vm/consts.rs +++ b/src/vm/consts.rs @@ -7,9 +7,6 @@ pub struct ConstPool { pool: Vec, } -// Constant pools are constant, and live for the lifetime of the entire program. -impl shredder::EmptyScan for ConstPool {} - impl ConstPool { pub fn new() -> Self { Default::default() diff --git a/src/vm/inst.rs b/src/vm/inst.rs index 69e561c..3366dd6 100644 --- a/src/vm/inst.rs +++ b/src/vm/inst.rs @@ -1,5 +1,4 @@ use crate::{obj::prelude::*, vm::consts::*}; -use shredder::EmptyScan; #[derive(Debug, PartialEq, Clone, Copy)] pub enum Inst { @@ -36,6 +35,9 @@ pub enum Inst { /// SetAttr(Sym), + /// Pops the top value off of the stack and checks if it is a `Bool` of a true value. + CheckTruth, + /// Jump to a given address in the current function unconditionally. Jump(usize), @@ -131,6 +133,7 @@ impl Inst { Inst::PopGlobal(_) => "POP_GLOBAL", Inst::GetAttr(_) => "GET_ATTR", Inst::SetAttr(_) => "SET_ATTR", + Inst::CheckTruth => "CHECK_TRUTH", Inst::Jump(_) => "JUMP", Inst::JumpTrue(_) => "JUMP_TRUE", Inst::Call(_) => "CALL", @@ -154,4 +157,4 @@ impl Inst { } } -impl EmptyScan for Inst {} +unsafe impl shredder::marker::GcDrop for Inst {} diff --git a/src/vm/mod.rs b/src/vm/mod.rs index c4db78f..10dea60 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -254,6 +254,15 @@ impl<'c> Vm<'c> { todo!("TODO: throw an error for attributes that can't be set"); } } + Inst::CheckTruth => { + let source = self.pop().expect("no source available for CheckTruth"); + read_obj_downcast!(let obj: Option<&Bool> = &source); + if let Some(obj) = obj { + self.condition = obj.value(); + } else { + unreachable!("Expected a boolean for CheckTruth but got {:?} instead", source); + } + } Inst::Jump(addr) => { next_pc = addr; } @@ -272,12 +281,12 @@ impl<'c> Vm<'c> { .expect("TODO: throw an error for missing __call__ attr"); signal = Some(Signal::Call(callee, args)); } - Inst::Index => todo!(), + Inst::Index => todo!("Inst::Index"), Inst::Return => { signal = Some(Signal::Return); } - Inst::UnNeg => todo!(), - Inst::UnPos => todo!(), + Inst::UnNeg => todo!("Inst::UnNeg"), + Inst::UnPos => todo!("Inst::UnPos"), Inst::BinPlus => { let rhs = self.pop().unwrap(); let lhs = self.pop().unwrap();