This commit is contained in:
nora 2022-01-01 13:47:56 +01:00
parent 88a3a585e7
commit 4e30201be4
11 changed files with 222 additions and 20 deletions

View file

@ -38,4 +38,9 @@ pub enum Instr {
/// Println the value on top of the stack /// Println the value on top of the stack
Print, Print,
/// If the current stack value is true, skip `usize` instructions.
JumpFalse(usize),
/// Same as `JumpCond`, but unconditional
Jmp(usize),
} }

View file

@ -1,8 +1,8 @@
//! The compiler that compiles the AST down to bytecode //! The compiler that compiles the AST down to bytecode
use crate::ast::{ use crate::ast::{
Assignment, BinaryOp, BinaryOpKind, Block, Call, Declaration, Expr, FnDecl, Ident, IfStmt, Assignment, BinaryOp, BinaryOpKind, Block, Call, Declaration, ElsePart, Expr, FnDecl, Ident,
Literal, Program, Stmt, UnaryOp, WhileStmt, IfStmt, Literal, Program, Stmt, UnaryOp, WhileStmt,
}; };
use crate::bytecode::{FnBlock, Instr}; use crate::bytecode::{FnBlock, Instr};
use crate::errors::{CompilerError, Span}; use crate::errors::{CompilerError, Span};
@ -146,8 +146,52 @@ impl<'ast, 'bc, 'gc> Compiler<'ast, 'bc, 'gc> {
todo!() todo!()
} }
fn compile_if(&mut self, _: &IfStmt) -> CResult<()> { fn compile_if(&mut self, if_stmt: &'ast IfStmt) -> CResult<()> {
todo!() /*
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<()> { 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 *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 block = &mut self.blocks[self.current_block];
let stack_top = block.stack_sizes.last().copied().unwrap_or(0); let stack_top = block.stack_sizes.last().copied().unwrap_or(0);
let new_stack_top = stack_top as isize + stack_change as isize; 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.stack_sizes.len());
debug_assert_eq!(block.code.len(), block.spans.len()); debug_assert_eq!(block.code.len(), block.spans.len());
block.code.len() - 1
} }
} }

View file

@ -11,6 +11,7 @@ mod vm;
use crate::ast::Program; use crate::ast::Program;
use crate::gc::RtAlloc; use crate::gc::RtAlloc;
use std::io::Write;
pub use bumpalo::Bump; pub use bumpalo::Bump;
pub use lex::*; pub use lex::*;
@ -30,7 +31,12 @@ type HashSet<T> = std::collections::HashSet<T>;
#[cfg(feature = "fxhash")] #[cfg(feature = "fxhash")]
type HashSet<T> = rustc_hash::FxHashSet<T>; type HashSet<T> = rustc_hash::FxHashSet<T>;
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(); let ast_alloc = Bump::new();
// SAFETY: I will try to 🥺 // SAFETY: I will try to 🥺
@ -40,13 +46,15 @@ pub fn run_program(program: &str) {
let ast = parse::parse(lexer, &ast_alloc); let ast = parse::parse(lexer, &ast_alloc);
match ast { match ast {
Ok(ast) => process_ast(program, ast, runtime), Ok(ast) => process_ast(program, ast, runtime, cfg),
Err(err) => errors::display_error(program, err), Err(err) => errors::display_error(program, err),
} }
} }
fn process_ast(program: &str, ast: Program, mut runtime: RtAlloc) { fn process_ast(program: &str, ast: Program, mut runtime: RtAlloc, cfg: &mut Config<'_>) {
// println!("AST:\n{:?}\n", ast); if cfg.debug {
println!("AST:\n{:?}\n", ast);
}
let bytecode_alloc = Bump::new(); let bytecode_alloc = Bump::new();
@ -54,9 +62,11 @@ fn process_ast(program: &str, ast: Program, mut runtime: RtAlloc) {
match bytecode { match bytecode {
Ok(code) => { 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 { if let Err(result) = result {
eprintln!("error: {}", result); eprintln!("error: {}", result);
} }

View file

@ -1,8 +1,28 @@
use dilaria::Config;
use std::io;
fn main() { 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) { match std::fs::read_to_string(filename) {
Ok(contents) => { Ok(contents) => {
dilaria::run_program(&contents); dilaria::run_program(&contents, &mut cfg);
} }
Err(err) => { Err(err) => {
eprintln!("{}", err); eprintln!("{}", err);

View file

@ -1,17 +1,23 @@
use crate::bytecode::{FnBlock, Instr}; use crate::bytecode::{FnBlock, Instr};
use crate::gc::{Object, RtAlloc, Symbol}; use crate::gc::{Object, RtAlloc, Symbol};
use std::fmt::{Debug, Display, Formatter}; use std::fmt::{Debug, Display, Formatter};
use std::io::Write;
type VmError = &'static str; type VmError = &'static str;
type VmResult = Result<(), VmError>; 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 { let mut vm = Vm {
_blocks: bytecode, _blocks: bytecode,
current: bytecode.first().ok_or("no bytecode found")?, current: bytecode.first().ok_or("no bytecode found")?,
pc: 0, pc: 0,
stack: Vec::with_capacity(1024 << 5), stack: Vec::with_capacity(1024 << 5),
_alloc: alloc, _alloc: alloc,
stdout,
}; };
vm.execute_function() vm.execute_function()
@ -37,15 +43,16 @@ const fn _check_val_size() {
const TRUE: Value = Value::Bool(true); const TRUE: Value = Value::Bool(true);
const FALSE: Value = Value::Bool(false); const FALSE: Value = Value::Bool(false);
struct Vm<'bc> { struct Vm<'bc, 'io> {
_blocks: &'bc [FnBlock<'bc>], _blocks: &'bc [FnBlock<'bc>],
current: &'bc FnBlock<'bc>, current: &'bc FnBlock<'bc>,
_alloc: RtAlloc, _alloc: RtAlloc,
pc: usize, pc: usize,
stack: Vec<Value>, stack: Vec<Value>,
stdout: &'io mut dyn Write,
} }
impl<'bc> Vm<'bc> { impl<'bc> Vm<'bc, '_> {
fn execute_function(&mut self) -> VmResult { fn execute_function(&mut self) -> VmResult {
let code = &self.current.code; let code = &self.current.code;
@ -141,8 +148,17 @@ impl<'bc> Vm<'bc> {
})?, })?,
Instr::Print => { Instr::Print => {
let val = self.stack.pop().unwrap(); 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(()) Ok(())

View file

@ -1,5 +1,11 @@
print "hi"; let x = 5;
let is_bigger = "hallo" > "a"; let y = 0;
print (not is_bigger) or true; if x < 0 {
y = x;
} else {
y = "hello it is smaller";
}
print y;

24
tests/common.rs Normal file
View file

@ -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::<u8>::new();
let mut cfg = dilaria::Config {
debug: false,
stdout: &mut stdout,
};
dilaria::run_program(code, &mut cfg);
String::from_utf8(stdout).unwrap()
}

53
tests/control_flow.rs Normal file
View file

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

View file

@ -0,0 +1,7 @@
---
source: tests/control_flow.rs
assertion_line: 17
expression: output
---
"true!\ntrue!\n"

View file

@ -0,0 +1,7 @@
---
source: tests/control_flow.rs
assertion_line: 34
expression: output
---
"true!\ntrue!\n"

View file

@ -0,0 +1,7 @@
---
source: tests/control_flow.rs
assertion_line: 4
expression: output
---
"true!\n"