From 4e30201be436da9eb6be92add0b55e8e5b57ce5b Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Sat, 1 Jan 2022 13:47:56 +0100 Subject: [PATCH] if --- src/bytecode.rs | 5 ++ src/compile.rs | 57 +++++++++++++++++-- src/lib.rs | 22 +++++-- src/main.rs | 24 +++++++- src/vm.rs | 24 ++++++-- test.dil | 12 +++- tests/common.rs | 24 ++++++++ tests/control_flow.rs | 53 +++++++++++++++++ tests/snapshots/control_flow__if_else.snap | 7 +++ tests/snapshots/control_flow__if_else_if.snap | 7 +++ tests/snapshots/control_flow__single_if.snap | 7 +++ 11 files changed, 222 insertions(+), 20 deletions(-) create mode 100644 tests/common.rs create mode 100644 tests/control_flow.rs create mode 100644 tests/snapshots/control_flow__if_else.snap create mode 100644 tests/snapshots/control_flow__if_else_if.snap create mode 100644 tests/snapshots/control_flow__single_if.snap diff --git a/src/bytecode.rs b/src/bytecode.rs index 6b57deb..faf1e7a 100644 --- a/src/bytecode.rs +++ b/src/bytecode.rs @@ -38,4 +38,9 @@ pub enum Instr { /// Println the value on top of the stack Print, + + /// If the current stack value is true, skip `usize` instructions. + JumpFalse(usize), + /// Same as `JumpCond`, but unconditional + Jmp(usize), } diff --git a/src/compile.rs b/src/compile.rs index e3eb4f7..8158cea 100644 --- a/src/compile.rs +++ b/src/compile.rs @@ -1,8 +1,8 @@ //! The compiler that compiles the AST down to bytecode use crate::ast::{ - Assignment, BinaryOp, BinaryOpKind, Block, Call, Declaration, Expr, FnDecl, Ident, IfStmt, - Literal, Program, Stmt, UnaryOp, WhileStmt, + Assignment, BinaryOp, BinaryOpKind, Block, Call, Declaration, ElsePart, Expr, FnDecl, Ident, + IfStmt, Literal, Program, Stmt, UnaryOp, WhileStmt, }; use crate::bytecode::{FnBlock, Instr}; use crate::errors::{CompilerError, Span}; @@ -146,8 +146,52 @@ impl<'ast, 'bc, 'gc> Compiler<'ast, 'bc, 'gc> { todo!() } - fn compile_if(&mut self, _: &IfStmt) -> CResult<()> { - todo!() + fn compile_if(&mut self, if_stmt: &'ast IfStmt) -> CResult<()> { + /* + 0 PushVal (true) + ╭─1 JumpCond (2) + │ 2 // it is true + ╭│─4 Jmp (1) │this is optional only for else + │╰>5 // it it false │ + ╰─>7 // continue here + */ + + self.compile_expr(&if_stmt.cond)?; + + // the offset will be fixed later + let jmp_idx = self.push_instr(Instr::JumpFalse(0), StackChange::Shrink, if_stmt.span); + + self.compile_block(&if_stmt.body)?; + + if let Some(else_part) = if_stmt.else_part { + let else_skip_jmp_idx = self.push_instr(Instr::Jmp(0), StackChange::None, if_stmt.span); + + let block = &mut self.blocks[self.current_block]; + let next_index = block.code.len(); + let jmp_pos = (next_index - 1) - jmp_idx; + block.code[jmp_idx] = Instr::JumpFalse(jmp_pos); + + match else_part { + ElsePart::Else(block, _) => { + self.compile_block(block)?; + } + ElsePart::ElseIf(if_stmt, _) => { + self.compile_if(if_stmt)?; + } + } + + let block = &mut self.blocks[self.current_block]; + let next_index = block.code.len(); + let jmp_pos = (next_index - else_skip_jmp_idx) - 1; + block.code[else_skip_jmp_idx] = Instr::Jmp(jmp_pos); + } else { + let block = &mut self.blocks[self.current_block]; + let next_index = block.code.len(); + let jmp_pos = (next_index - 1) - jmp_idx; + block.code[jmp_idx] = Instr::JumpFalse(jmp_pos); + } + + Ok(()) } fn compile_loop(&mut self, _: &Block, _: Span) -> CResult<()> { @@ -266,7 +310,8 @@ impl<'ast, 'bc, 'gc> Compiler<'ast, 'bc, 'gc> { *block.stack_sizes.last().expect("empty stack") - 1 } - fn push_instr(&mut self, instr: Instr, stack_change: StackChange, span: Span) { + /// Pushes an instruction and returns the index of the new instruction + fn push_instr(&mut self, instr: Instr, stack_change: StackChange, span: Span) -> usize { let block = &mut self.blocks[self.current_block]; let stack_top = block.stack_sizes.last().copied().unwrap_or(0); let new_stack_top = stack_top as isize + stack_change as isize; @@ -279,6 +324,8 @@ impl<'ast, 'bc, 'gc> Compiler<'ast, 'bc, 'gc> { debug_assert_eq!(block.code.len(), block.stack_sizes.len()); debug_assert_eq!(block.code.len(), block.spans.len()); + + block.code.len() - 1 } } diff --git a/src/lib.rs b/src/lib.rs index 14ac4dd..2a61658 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod vm; use crate::ast::Program; use crate::gc::RtAlloc; +use std::io::Write; pub use bumpalo::Bump; pub use lex::*; @@ -30,7 +31,12 @@ type HashSet = std::collections::HashSet; #[cfg(feature = "fxhash")] type HashSet = rustc_hash::FxHashSet; -pub fn run_program(program: &str) { +pub struct Config<'io> { + pub debug: bool, + pub stdout: &'io mut dyn Write, +} + +pub fn run_program(program: &str, cfg: &mut Config) { let ast_alloc = Bump::new(); // SAFETY: I will try to 🥺 @@ -40,13 +46,15 @@ pub fn run_program(program: &str) { let ast = parse::parse(lexer, &ast_alloc); match ast { - Ok(ast) => process_ast(program, ast, runtime), + Ok(ast) => process_ast(program, ast, runtime, cfg), Err(err) => errors::display_error(program, err), } } -fn process_ast(program: &str, ast: Program, mut runtime: RtAlloc) { - // println!("AST:\n{:?}\n", ast); +fn process_ast(program: &str, ast: Program, mut runtime: RtAlloc, cfg: &mut Config<'_>) { + if cfg.debug { + println!("AST:\n{:?}\n", ast); + } let bytecode_alloc = Bump::new(); @@ -54,9 +62,11 @@ fn process_ast(program: &str, ast: Program, mut runtime: RtAlloc) { match bytecode { Ok(code) => { - // println!("Bytecode:\n{:#?}\n", code); + if cfg.debug { + println!("Bytecode:\n{:#?}\n", code); + } - let result = vm::execute(&code, runtime); + let result = vm::execute(&code, runtime, cfg.stdout); if let Err(result) = result { eprintln!("error: {}", result); } diff --git a/src/main.rs b/src/main.rs index 0c8ecd0..38f3173 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,28 @@ +use dilaria::Config; +use std::io; + fn main() { - if let Some(filename) = std::env::args().nth(1) { + let mut args = std::env::args(); + + if let Some(filename) = args.nth(1) { + let mut stdout = io::stdout(); + + let mut cfg = Config { + debug: false, + stdout: &mut stdout, + }; + + for arg in args { + match &*arg { + "--debug" => cfg.debug = true, + "--shut-up-clippy" => println!("yeah shut up pls"), // please do + _ => {} + } + } + match std::fs::read_to_string(filename) { Ok(contents) => { - dilaria::run_program(&contents); + dilaria::run_program(&contents, &mut cfg); } Err(err) => { eprintln!("{}", err); diff --git a/src/vm.rs b/src/vm.rs index fc27fc0..56b18e6 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,17 +1,23 @@ use crate::bytecode::{FnBlock, Instr}; use crate::gc::{Object, RtAlloc, Symbol}; use std::fmt::{Debug, Display, Formatter}; +use std::io::Write; type VmError = &'static str; type VmResult = Result<(), VmError>; -pub fn execute<'bc>(bytecode: &'bc [FnBlock<'bc>], alloc: RtAlloc) -> Result<(), VmError> { +pub fn execute<'bc>( + bytecode: &'bc [FnBlock<'bc>], + alloc: RtAlloc, + stdout: &mut dyn Write, +) -> Result<(), VmError> { let mut vm = Vm { _blocks: bytecode, current: bytecode.first().ok_or("no bytecode found")?, pc: 0, stack: Vec::with_capacity(1024 << 5), _alloc: alloc, + stdout, }; vm.execute_function() @@ -37,15 +43,16 @@ const fn _check_val_size() { const TRUE: Value = Value::Bool(true); const FALSE: Value = Value::Bool(false); -struct Vm<'bc> { +struct Vm<'bc, 'io> { _blocks: &'bc [FnBlock<'bc>], current: &'bc FnBlock<'bc>, _alloc: RtAlloc, pc: usize, stack: Vec, + stdout: &'io mut dyn Write, } -impl<'bc> Vm<'bc> { +impl<'bc> Vm<'bc, '_> { fn execute_function(&mut self) -> VmResult { let code = &self.current.code; @@ -141,8 +148,17 @@ impl<'bc> Vm<'bc> { })?, Instr::Print => { let val = self.stack.pop().unwrap(); - println!("{}", val); + writeln!(self.stdout, "{}", val).map_err(|_| "failed to write to stdout")?; } + Instr::JumpFalse(pos) => { + let val = self.stack.pop().unwrap(); + match val { + Value::Bool(false) => self.pc += pos, + Value::Bool(true) => {} + _ => return Err("bad type"), + } + } + Instr::Jmp(pos) => self.pc += pos, } Ok(()) diff --git a/test.dil b/test.dil index 4e16444..e5f5db3 100644 --- a/test.dil +++ b/test.dil @@ -1,5 +1,11 @@ -print "hi"; +let x = 5; -let is_bigger = "hallo" > "a"; +let y = 0; -print (not is_bigger) or true; \ No newline at end of file +if x < 0 { + y = x; +} else { + y = "hello it is smaller"; +} + +print y; \ No newline at end of file diff --git a/tests/common.rs b/tests/common.rs new file mode 100644 index 0000000..5f01285 --- /dev/null +++ b/tests/common.rs @@ -0,0 +1,24 @@ +#[macro_export] +macro_rules! run_test { + ($name:ident, $code:expr) => { + #[test] + fn $name() { + let code = $code; + let output = _run_test(code); + insta::assert_debug_snapshot!(output); + } + }; +} + +#[doc(hidden)] +pub fn _run_test(code: &str) -> String { + let mut stdout = Vec::::new(); + let mut cfg = dilaria::Config { + debug: false, + stdout: &mut stdout, + }; + + dilaria::run_program(code, &mut cfg); + + String::from_utf8(stdout).unwrap() +} diff --git a/tests/control_flow.rs b/tests/control_flow.rs new file mode 100644 index 0000000..40dd069 --- /dev/null +++ b/tests/control_flow.rs @@ -0,0 +1,53 @@ +mod common; +use crate::common::_run_test; + +run_test!( + single_if, + r#" +if true { + print "true!"; +} + +if false { + print "WRONG"; +} +"# +); + +run_test!( + if_else, + r#" +if true { + print "true!"; +} else { + print "WRONG"; +} + +if false { + print "WRONG"; +} else { + print "true!"; +} +"# +); + +run_test!( + if_else_if, + r#" +if false { + print "WRONG"; +} else if true { + print "true!"; +} else { + print "WRONG"; +} + +if false { + print "WRONG"; +} else if false { + print "WRONG"; +} else { + print "true!"; +} +"# +); diff --git a/tests/snapshots/control_flow__if_else.snap b/tests/snapshots/control_flow__if_else.snap new file mode 100644 index 0000000..7643d16 --- /dev/null +++ b/tests/snapshots/control_flow__if_else.snap @@ -0,0 +1,7 @@ +--- +source: tests/control_flow.rs +assertion_line: 17 +expression: output + +--- +"true!\ntrue!\n" diff --git a/tests/snapshots/control_flow__if_else_if.snap b/tests/snapshots/control_flow__if_else_if.snap new file mode 100644 index 0000000..4c36c36 --- /dev/null +++ b/tests/snapshots/control_flow__if_else_if.snap @@ -0,0 +1,7 @@ +--- +source: tests/control_flow.rs +assertion_line: 34 +expression: output + +--- +"true!\ntrue!\n" diff --git a/tests/snapshots/control_flow__single_if.snap b/tests/snapshots/control_flow__single_if.snap new file mode 100644 index 0000000..c3fcfa9 --- /dev/null +++ b/tests/snapshots/control_flow__single_if.snap @@ -0,0 +1,7 @@ +--- +source: tests/control_flow.rs +assertion_line: 4 +expression: output + +--- +"true!\n"