Skip to content

Commit

Permalink
Merge pull request #51 from leonidas1712/functions
Browse files Browse the repository at this point in the history
Add functions, concurrency, wait/post (type checking is not complete)
  • Loading branch information
leonidas1712 authored Apr 18, 2024
2 parents 3186dcf + 701928a commit 7d0cb26
Show file tree
Hide file tree
Showing 31 changed files with 2,525 additions and 155 deletions.
142 changes: 141 additions & 1 deletion compiler/oxidate/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use std::{fmt::Display, rc::Rc, vec};
use types::type_checker::TypeChecker;

use bytecode::{BinOp, ByteCode, Value};
use parser::structs::{BinOpType, BlockSeq, Decl, Expr, IfElseData, LoopData, UnOpType};
use parser::structs::{
BinOpType, BlockSeq, Decl, Expr, FnCallData, FnDeclData, IfElseData, LoopData, UnOpType,
};

pub struct Compiler {
program: BlockSeq,
Expand Down Expand Up @@ -33,6 +35,11 @@ impl Display for CompileError {

impl std::error::Error for CompileError {}

// Workaround to ensure builtins that dont pop produce Unit when compiling fn call
// Because user functions even if empty will produce unit (everything is value producing), so
// this issue only applies to builtins with no value pushed
const BUILTINS_WITH_NO_VAL: [&str; 3] = ["println", "print", "sem_set"];

impl Compiler {
pub fn new(program: BlockSeq) -> Compiler {
Compiler {
Expand Down Expand Up @@ -156,6 +163,7 @@ impl Compiler {
Expr::Integer(val) => arr.push(ByteCode::ldc(*val)),
Expr::Float(val) => arr.push(ByteCode::ldc(*val)),
Expr::Bool(val) => arr.push(ByteCode::ldc(*val)),
Expr::StringLiteral(str) => arr.push(ByteCode::LDC(Value::String(str.to_owned()))),
Expr::BinOpExpr(op, lhs, rhs) => {
self.compile_binop(op, lhs, rhs, arr)?;
}
Expand All @@ -170,6 +178,46 @@ impl Compiler {
self.compile_block(blk, arr)?;
}
Expr::IfElseExpr(if_else) => self.compile_if_else(if_else, arr)?,
Expr::FnCallExpr(fn_call) => self.compile_fn_call(fn_call, arr)?,
Expr::SpawnExpr(fn_call) => self.compile_spawn(fn_call, arr)?,
Expr::JoinExpr(id) => {
arr.push(ByteCode::ld(id));
arr.push(ByteCode::JOIN);
}
}

Ok(())
}

fn compile_spawn(
&mut self,
fn_call: &FnCallData,
arr: &mut Vec<ByteCode>,
) -> Result<(), CompileError> {
// dbg!("SPAWN COMPILE:", _fn_call);
let spawn_idx = arr.len();
arr.push(ByteCode::SPAWN(0));

let goto_idx = arr.len();
arr.push(ByteCode::GOTO(0));

// spawn jumps to POP which is added after this
let spawn_jmp = arr.len();
if let Some(ByteCode::SPAWN(jmp)) = arr.get_mut(spawn_idx) {
*jmp = spawn_jmp;
}

// child pops value on its stack
arr.push(ByteCode::POP);

self.compile_fn_call(fn_call, arr)?;
arr.push(ByteCode::DONE); // child thread finishes

let goto_jmp = arr.len();

// parent jumps after DONE
if let Some(ByteCode::GOTO(jmp)) = arr.get_mut(goto_idx) {
*jmp = goto_jmp;
}

Ok(())
Expand Down Expand Up @@ -267,11 +315,103 @@ impl Compiler {
breaks.push(break_idx);
}
}
Decl::FnDeclStmt(fn_decl) => self.compile_fn_decl(fn_decl, arr)?,
Decl::ReturnStmt(ret_stmt) => {
// compile expr. if not there, push Unit
if let Some(expr) = ret_stmt {
self.compile_expr(expr, arr)?;
} else {
arr.push(ByteCode::ldc(Value::Unit));
}

// push RESET
arr.push(ByteCode::RESET(bytecode::FrameType::CallFrame))
}
// These don't return anything, so push unit after as well
Decl::WaitStmt(sem) => {
arr.push(ByteCode::ld(sem));
arr.push(ByteCode::WAIT);
arr.push(ByteCode::ldc(Value::Unit));
}
Decl::PostStmt(sem) => {
arr.push(ByteCode::ld(sem));
arr.push(ByteCode::POST);
arr.push(ByteCode::ldc(Value::Unit));
}
Decl::YieldStmt => {
arr.push(ByteCode::YIELD);
arr.push(ByteCode::ldc(Value::Unit));
}
};

Ok(())
}

fn compile_fn_decl(
&mut self,
fn_decl: &FnDeclData,
arr: &mut Vec<ByteCode>,
) -> Result<(), CompileError> {
// we are about to push LDF and GOTO before fn compile
let fn_start_idx = arr.len() + 2;

let param_strs: Vec<String> = fn_decl.params.iter().map(|x| x.name.to_string()).collect();

arr.push(ByteCode::ldf(fn_start_idx, param_strs));

// push GOTO for skipping fn compile
let goto_idx = arr.len();
arr.push(ByteCode::GOTO(0));

// add params to fn blk
// let mut fn_blk = fn_decl.body.clone();
// let mut param_names = fn_decl.params.iter().map(|x| x.name.clone()).collect::<Vec<_>>();
// fn_blk.symbols.append(&mut param_names);

// compile the augmented blk

self.compile_block(&fn_decl.body, arr)?;
// self.compile_block(&fn_blk, arr)?;

// push reset to return last value produced by blk, in case no return was there
arr.push(ByteCode::RESET(bytecode::FrameType::CallFrame));

// GOTO will jump to ASSIGN, ASSIGN pops closure and then we load Unit so no underflow
let goto_addr = arr.len();
arr.push(ByteCode::assign(&fn_decl.name));
arr.push(ByteCode::ldc(Value::Unit));

// patch GOTO
if let Some(ByteCode::GOTO(idx)) = arr.get_mut(goto_idx) {
*idx = goto_addr;
}

Ok(())
}

/// Function call expression e.g println(2,3)
fn compile_fn_call(
&mut self,
fn_call: &FnCallData,
arr: &mut Vec<ByteCode>,
) -> Result<(), CompileError> {
// TODO: change to accept arbitary expr for fn
self.compile_expr(&Expr::Symbol(fn_call.name.clone()), arr)?;

for arg in fn_call.args.iter() {
self.compile_expr(arg, arr)?;
}

arr.push(ByteCode::CALL(fn_call.args.len()));

// push unit for builtin that produces no value
if BUILTINS_WITH_NO_VAL.contains(&fn_call.name.as_str()) {
arr.push(ByteCode::ldc(Value::Unit));
}

Ok(())
}

/// Compile if_else as statement or as expr - changes how blocks are compiled
fn compile_if_else(
&mut self,
Expand Down
161 changes: 161 additions & 0 deletions compiler/oxidate/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1026,4 +1026,165 @@ mod tests {
],
);
}

#[test]
fn test_compile_fn_call() {
let t = "print(2, 3)";
test_comp(
t,
vec![
ByteCode::ld("print"),
LDC(Int(2)),
LDC(Int(3)),
CALL(2),
LDC(Unit),
DONE,
],
);

let t = "print(2, 3);";
test_comp(
t,
vec![
ByteCode::ld("print"),
LDC(Int(2)),
LDC(Int(3)),
CALL(2),
LDC(Unit),
POP,
DONE,
],
);
}

#[test]
fn test_compile_fn_decl() {
let t = r"
300;
fn f() {
2
}
";
test_comp(
t,
vec![
ENTERSCOPE(vec!["f".to_string()]),
ByteCode::ldc(300),
POP,
LDF(5, vec![]),
GOTO(7),
ByteCode::ldc(2),
RESET(bytecode::FrameType::CallFrame),
ByteCode::assign("f"),
LDC(Unit),
POP,
EXITSCOPE,
DONE,
],
);

// explicit return - doesn't skip rest of block yet
let t = r"
fn f() {
return 2;
}
";
test_comp(
t,
vec![
ENTERSCOPE(vec!["f".to_string()]),
LDF(3, vec![]),
GOTO(8),
ByteCode::ldc(2),
RESET(bytecode::FrameType::CallFrame),
POP,
LDC(Unit),
RESET(bytecode::FrameType::CallFrame),
ByteCode::assign("f"),
LDC(Unit),
POP,
EXITSCOPE,
DONE,
],
);
}

#[test]
fn test_compile_fn_decl_more() {
// fn with params
let t = r"
fn fac(n: int) {
2 + n
}
";
test_comp(
t,
vec![
ENTERSCOPE(vec!["fac".to_string()]),
LDF(3, vec!["n".to_string()]),
GOTO(7),
ByteCode::ldc(2),
ByteCode::ld("n"),
ByteCode::binop("+"),
RESET(bytecode::FrameType::CallFrame),
ByteCode::assign("fac"),
LDC(Unit),
POP,
EXITSCOPE,
DONE,
],
);
}

#[test]
fn test_compile_spawn() {
let t = r"
2;
spawn func(1);
3;
";
test_comp(
t,
vec![
ByteCode::ldc(2),
POP,
SPAWN(4),
GOTO(9),
POP,
LD("func".to_string()),
ByteCode::ldc(1),
CALL(1),
DONE,
POP,
ByteCode::ldc(3),
POP,
DONE,
],
);
}

#[test]
fn test_compile_wait_post() {
let t = r"
wait sem;
2;
post sem;
";
test_comp(
t,
vec![
ByteCode::ld("sem"),
WAIT,
LDC(Unit),
POP,
ByteCode::ldc(2),
POP,
ByteCode::ld("sem"),
POST,
LDC(Unit),
POP,
DONE,
],
);
}
}
8 changes: 3 additions & 5 deletions example/concurrency-01.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@ fn loop_and_print(x: int) {
let count = 0;

loop {
if count >= 10 {
if count > 10 || count == 10 {
break;
}

println(x);

count += 1;
count = count + 1;
}
}

println("Creating threads");

let thread_id_1 = spawn loop_and_print(1);
let thread_id_2 = spawn loop_and_print(2);
let thread_id_3 = spawn loop_and_print(3);
Expand All @@ -22,4 +20,4 @@ join thread_id_3;
join thread_id_2;
join thread_id_1;

println("All threads finished");
println(true);
2 changes: 2 additions & 0 deletions example/concurrency-02.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Expected: main thread spawns and program exits after main finishes without waiting

let count = 0;

fn infinite_increment() {
Expand Down
Loading

0 comments on commit 7d0cb26

Please sign in to comment.