Skip to content

Commit

Permalink
feat: builtin compilation and execution
Browse files Browse the repository at this point in the history
  • Loading branch information
ZakFarmer committed Oct 30, 2023
1 parent 9dd8357 commit eaaf72e
Show file tree
Hide file tree
Showing 14 changed files with 184 additions and 23 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 38 additions & 16 deletions compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use parser::ast::{
BlockStatement, BooleanLiteral, Expression, IntegerLiteral, Literal, Node, Statement,
StringLiteral,
};
use symbol_table::{SymbolScope, SymbolTable};
use symbol_table::{SymbolScope, SymbolTable, Symbol};

pub mod symbol_table;

Expand Down Expand Up @@ -60,21 +60,29 @@ impl Compiler {
},
};

let mut symbol_table = SymbolTable::new();

for (i, builtin) in object::builtins::BUILTINS.iter().enumerate() {
symbol_table.define_builtin(i, builtin.0.to_string());
}

Self {
constants: Vec::new(),
symbol_table: SymbolTable::new(),
symbol_table,
scopes: vec![main_scope],
scope_index: 0,
}
}

pub fn new_with_state(constants: Vec<Rc<object::Object>>, symbol_table: SymbolTable) -> Self {
let compiler = Self::new();

pub fn new_with_state(constants: Vec<Rc<object::Object>>, mut symbol_table: SymbolTable) -> Self {
for (i, builtin) in object::builtins::BUILTINS.iter().enumerate() {
symbol_table.define_builtin(i, builtin.0.to_string());
}

Self {
constants,
symbol_table,
..compiler
..Self::new()
}
}

Expand Down Expand Up @@ -109,6 +117,27 @@ impl Compiler {
instructions
}

fn load_symbol(&mut self, symbol: &Symbol) {
match symbol.scope {
SymbolScope::Global => {
self.emit(Opcode::OpGetGlobal, vec![symbol.index]);
}
SymbolScope::Local => {
self.emit(Opcode::OpGetLocal, vec![symbol.index]);
}
SymbolScope::Builtin => {
self.emit(Opcode::OpGetBuiltin, vec![symbol.index]);
}
SymbolScope::Free => {
self.emit(Opcode::OpGetFree, vec![symbol.index]);
}
SymbolScope::Function => {
self.emit(Opcode::OpCurrentClosure, vec![]);
}
_ => {}
}
}

pub fn scopes(&self) -> &Vec<CompilationScope> {
&self.scopes
}
Expand Down Expand Up @@ -275,18 +304,11 @@ impl Compiler {
fn compile_expression(&mut self, e: &Expression) -> Result<(), Error> {
match e {
Expression::Identifier(identifier) => {
let symbol = self.symbol_table.resolve(&identifier.value);
let symbol = self.symbol_table.resolve(identifier.value.clone());

match symbol {
Some(symbol) => {
self.emit(
if symbol.scope == SymbolScope::Global {
Opcode::OpGetGlobal
} else {
Opcode::OpGetLocal
},
vec![symbol.index],
);
self.load_symbol(&symbol);
}
None => {
return Err(Error::msg(format!(
Expand All @@ -300,7 +322,7 @@ impl Compiler {
}
Expression::Function(function_literal) => {
self.enter_scope();

for parameter in function_literal.parameters.iter() {
self.symbol_table.define(&parameter.value);
}
Expand Down
16 changes: 14 additions & 2 deletions compiler/src/symbol_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,20 @@ impl SymbolTable {
symbol
}

pub fn resolve(&self, name: &str) -> Option<Rc<Symbol>> {
let symbol = self.store.get(name);
pub fn define_builtin(&mut self, index: usize, name: String) -> Rc<Symbol> {
let symbol = Rc::new(Symbol {
name: name.clone(),
scope: SymbolScope::Builtin,
index,
});

self.store.insert(name.to_string(), Rc::clone(&symbol));

symbol
}

pub fn resolve(&self, name: String) -> Option<Rc<Symbol>> {
let symbol = self.store.get(&name);

if symbol.is_none() && self.outer.is_some() {
return self.outer.as_ref().unwrap().resolve(name);
Expand Down
29 changes: 29 additions & 0 deletions compiler/tests/compiler_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,35 @@ fn test_boolean_expressions() -> Result<(), Error> {
Ok(())
}

#[test]
fn test_builtin_functions() -> Result<(), Error> {
let tests = vec![
CompilerTestCase {
input: "$x = [1, 2, 3]; len($x);".to_string(),
expected_constants: vec![
Object::Integer(1),
Object::Integer(2),
Object::Integer(3),
],
expected_instructions: vec![
opcode::make(opcode::Opcode::OpConst, &vec![0]),
opcode::make(opcode::Opcode::OpConst, &vec![1]),
opcode::make(opcode::Opcode::OpConst, &vec![2]),
opcode::make(opcode::Opcode::OpArray, &vec![3]),
opcode::make(opcode::Opcode::OpSetGlobal, &vec![0]),
opcode::make(opcode::Opcode::OpGetBuiltin, &vec![0]),
opcode::make(opcode::Opcode::OpGetGlobal, &vec![0]),
opcode::make(opcode::Opcode::OpCall, &vec![1]),
opcode::make(opcode::Opcode::OpPop, &vec![]),
],
},
];

run_compiler_tests(tests)?;

Ok(())
}

#[test]
fn test_compilation_scopes() -> Result<(), Error> {
let mut compiler = Compiler::new();
Expand Down
2 changes: 1 addition & 1 deletion compiler/tests/symbol_table_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ fn test_resolve_global() -> Result<(), Error> {
];

for (name, expected_symbol) in expected {
let symbol = global.resolve(&name);
let symbol = global.resolve(name.clone());

if symbol.is_none() {
panic!("symbol for {} is None", name);
Expand Down
Empty file added evaluator/src/builtins.rs
Empty file.
10 changes: 8 additions & 2 deletions evaluator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use parser::ast::{

use object::{
environment::{Env, Environment},
Object,
Object, builtins,
};

pub fn eval(node: Node, env: &Env) -> Result<Rc<Object>> {
Expand Down Expand Up @@ -48,6 +48,9 @@ fn apply_function(function: &Rc<Object>, args: &Vec<Rc<Object>>) -> Result<Rc<Ob
let evaluated = eval_statements(&body.statements, &Rc::new(RefCell::new(env)))?;
return unwrap_return_value(evaluated);
}
Object::Builtin(builtin) => {
Ok(builtin(args.to_vec()))
}
f => Err(Error::msg(format!("expected {} to be a function", f))),
}
}
Expand Down Expand Up @@ -299,7 +302,10 @@ fn eval_statement(statement: &Statement, env: &Env) -> Result<Rc<Object>> {
fn eval_identifier(identifier: String, env: &Env) -> Result<Rc<Object>> {
match env.borrow().get(&identifier) {
Some(value) => Ok(value),
None => Err(Error::msg(format!("Identifier not found: {}", identifier))),
None => match object::builtins::get_builtin_by_name(&identifier) {
Some(builtin) => Ok(Rc::new(Object::Builtin(builtin))),
None => Err(Error::msg(format!("Unknown identifier: {}", identifier))),
},
}
}

Expand Down
4 changes: 3 additions & 1 deletion interpreter/src/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ pub fn init_repl() -> Result<(), Error> {
let mut parser = Parser::new(lexer);

let program = parser.parse_program()?;

parser.check_errors()?;

let mut compiler = Compiler::new_with_state(constants, symbol_table);
let mut compiler = Compiler::new_with_state(constants, symbol_table.clone());

match compiler.compile(&Node::Program(program)) {
Ok(bytecode) => {

let mut vm = Vm::new_with_globals_store(bytecode, globals);

match vm.run() {
Expand Down
3 changes: 2 additions & 1 deletion object/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ anyhow = "1.0.75"
env_logger = "0.10.0"
log = "0.4.20"
parser = { path = "../parser" }
opcode = { path = "../opcode" }
opcode = { path = "../opcode" }
lazy_static = "1.4.0"
30 changes: 30 additions & 0 deletions object/src/builtins.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use std::{collections::HashMap, rc::Rc};

use lazy_static::lazy_static;

use crate::{BuiltinFunction, Object};

lazy_static! {
pub static ref BUILTINS: Vec<(&'static str, BuiltinFunction)> = vec![
("len", len),
];
}

pub fn get_builtin_by_name(name: &str) -> Option<BuiltinFunction> {
match BUILTINS.iter().find(|(builtin_name, _)| *builtin_name == name) {
Some((_, builtin)) => Some(*builtin),
None => None,
}
}

pub fn len(args: Vec<Rc<Object>>) -> Rc<Object> {
if args.len() != 1 {
return Rc::new(Object::Null);
}

match &*args[0] {
Object::Array(elements) => Rc::new(Object::Integer(elements.len() as i64)),
Object::String(s) => Rc::new(Object::Integer(s.len() as i64)),
_ => Rc::new(Object::Null),
}
}
4 changes: 4 additions & 0 deletions object/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ use parser::ast::{BlockStatement, Identifier};

use self::environment::Env;

pub mod builtins;
pub mod environment;

pub type BuiltinFunction = fn(Vec<Rc<Object>>) -> Rc<Object>;

#[derive(Clone, Debug, PartialEq)]
pub enum Object {
Integer(i64),
Boolean(bool),
String(String),
Function(Vec<Identifier>, BlockStatement, Env),
Builtin(BuiltinFunction),
CompiledFunction(Rc<CompiledFunction>),
Return(Rc<Object>),
Array(Vec<Rc<Object>>),
Expand Down
10 changes: 10 additions & 0 deletions opcode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ pub enum Opcode {
OpGetFree,
/// 0x1C - Current closure
OpCurrentClosure,
/// 0x1D - Get a builtin function
OpGetBuiltin,
}

impl From<u8> for Opcode {
Expand Down Expand Up @@ -212,6 +214,7 @@ impl From<u8> for Opcode {
0x1A => Opcode::OpClosure,
0x1B => Opcode::OpGetFree,
0x1C => Opcode::OpCurrentClosure,
0x1D => Opcode::OpGetBuiltin,
_ => panic!("Opcode not found: {}", opcode),
}
}
Expand Down Expand Up @@ -456,6 +459,13 @@ lazy_static! {
operand_widths: vec![],
},
);
definitions.insert(
Opcode::OpGetBuiltin,
OpcodeDefinition {
name: "OpGetBuiltin",
operand_widths: vec![1],
},
);

definitions
};
Expand Down
22 changes: 22 additions & 0 deletions vm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,15 @@ impl Vm {
let function = &*self.stack[self.stack_pointer - 1 - num_args];

match function {
Object::Builtin(builtin) => {
let args = self.stack[self.stack_pointer - num_args..self.stack_pointer].to_vec();

let result = builtin(args);

self.stack_pointer -= num_args + 1;

self.push(result);
}
Object::CompiledFunction(compiled_function) => {
let base_pointer = self.stack_pointer - num_args;
let cloned_function = compiled_function.as_ref().clone();
Expand Down Expand Up @@ -182,11 +191,24 @@ impl Vm {

self.stack[base_pointer + local_index] = self.pop();
}
Opcode::OpGetBuiltin => {
let builtin_index = instructions[instruction_pointer + 1] as usize;

self.current_frame().instruction_pointer += 1;

let builtin_definition = object::builtins::BUILTINS.get(builtin_index)
.unwrap()
.1;

self.push(Rc::new(Object::Builtin(builtin_definition)));
}
Opcode::OpCall => {
let num_args = instructions[instruction_pointer + 1] as usize;

self.current_frame().instruction_pointer += 1;

println!("Calling function");

self.call_function(num_args);
}
Opcode::OpReturn => {
Expand Down
22 changes: 22 additions & 0 deletions vm/tests/vm_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,28 @@ fn test_conditionals() -> Result<(), Error> {
Ok(())
}

#[test]
fn test_builtin_functions() -> Result<(), Error> {
let tests = vec![
VmTestCase {
input: "len(\"\")".to_string(),
expected: Object::Integer(0),
},
VmTestCase {
input: "len(\"four\")".to_string(),
expected: Object::Integer(4),
},
VmTestCase {
input: "len([1, 2, 3, 4, 5])".to_string(),
expected: Object::Integer(5),
},
];

run_vm_tests(tests)?;

Ok(())
}

#[test]
fn test_functions_with_no_arguments() -> Result<(), Error> {
let tests = vec![
Expand Down

0 comments on commit eaaf72e

Please sign in to comment.