mirror of
https://github.com/Noratrieb/dilaria.git
synced 2026-01-14 17:35:03 +01:00
if
This commit is contained in:
parent
88a3a585e7
commit
4e30201be4
11 changed files with 222 additions and 20 deletions
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
22
src/lib.rs
22
src/lib.rs
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
src/main.rs
24
src/main.rs
|
|
@ -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);
|
||||||
|
|
|
||||||
24
src/vm.rs
24
src/vm.rs
|
|
@ -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(())
|
||||||
|
|
|
||||||
12
test.dil
12
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;
|
if x < 0 {
|
||||||
|
y = x;
|
||||||
|
} else {
|
||||||
|
y = "hello it is smaller";
|
||||||
|
}
|
||||||
|
|
||||||
|
print y;
|
||||||
24
tests/common.rs
Normal file
24
tests/common.rs
Normal 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
53
tests/control_flow.rs
Normal 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!";
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
);
|
||||||
7
tests/snapshots/control_flow__if_else.snap
Normal file
7
tests/snapshots/control_flow__if_else.snap
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
source: tests/control_flow.rs
|
||||||
|
assertion_line: 17
|
||||||
|
expression: output
|
||||||
|
|
||||||
|
---
|
||||||
|
"true!\ntrue!\n"
|
||||||
7
tests/snapshots/control_flow__if_else_if.snap
Normal file
7
tests/snapshots/control_flow__if_else_if.snap
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
source: tests/control_flow.rs
|
||||||
|
assertion_line: 34
|
||||||
|
expression: output
|
||||||
|
|
||||||
|
---
|
||||||
|
"true!\ntrue!\n"
|
||||||
7
tests/snapshots/control_flow__single_if.snap
Normal file
7
tests/snapshots/control_flow__single_if.snap
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
source: tests/control_flow.rs
|
||||||
|
assertion_line: 4
|
||||||
|
expression: output
|
||||||
|
|
||||||
|
---
|
||||||
|
"true!\n"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue