Skip to content

Commit

Permalink
feat: implement assign to globals
Browse files Browse the repository at this point in the history
  • Loading branch information
viddrobnic committed Jun 3, 2024
1 parent fdf95a5 commit 1aff169
Show file tree
Hide file tree
Showing 9 changed files with 527 additions and 50 deletions.
9 changes: 9 additions & 0 deletions runtime/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ pub enum Instruction {

Jump(usize),
JumpNotTruthy(usize),

// Puts all array values on stack, where
// array should be given size long.
UnpackArray(usize),

IndexSet,

StoreGlobal(usize),
LoadGlobal(usize),
}

#[derive(Debug, PartialEq, Clone)]
Expand Down
121 changes: 98 additions & 23 deletions runtime/src/compiler.rs → runtime/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use std::rc::Rc;

use crate::{
bytecode::{Bytecode, Instruction},
error::{Error, ErrorKind},
object::Object,
};

Expand All @@ -10,6 +11,13 @@ use parser::{
position::Range,
};

use self::symbol_table::{Symbol, SymbolTable};

mod symbol_table;

#[cfg(test)]
mod test;

#[derive(Debug, Default)]
struct Scope {
instructions: Vec<Instruction>,
Expand All @@ -20,6 +28,8 @@ struct Scope {
pub struct Compiler {
constants: Vec<Object>,

symbol_table: SymbolTable,

scopes: Vec<Scope>,
scope_index: usize,
}
Expand All @@ -28,6 +38,7 @@ impl Compiler {
pub fn new() -> Self {
Self {
constants: vec![],
symbol_table: SymbolTable::new(),
scopes: vec![Scope::default()],
scope_index: 0,
}
Expand All @@ -50,9 +61,9 @@ impl Compiler {
scope.instructions.len() - 1
}

pub fn compile(mut self, program: &ast::Program) -> Bytecode {
pub fn compile(mut self, program: &ast::Program) -> Result<Bytecode, Error> {
for node in &program.statements {
self.compile_node(node);
self.compile_node(node)?;

if node.kind() == ast::NodeKind::Expression {
self.emit(Instruction::Pop, node.range);
Expand All @@ -62,16 +73,16 @@ impl Compiler {
// If compiler works correctly, we should have one scope.
let scope = self.scopes.pop().expect("Invalid number of scopes");

Bytecode {
Ok(Bytecode {
constants: self.constants,
instructions: scope.instructions,
ranges: scope.ranges,
}
})
}

fn compile_node(&mut self, node: &ast::Node) {
fn compile_node(&mut self, node: &ast::Node) -> Result<(), Error> {
match &node.value {
ast::NodeValue::Identifier(_) => todo!(),
ast::NodeValue::Identifier(ident) => self.compile_ident(ident, node.range)?,
ast::NodeValue::IntegerLiteral(int) => {
self.compile_constant(Object::Integer(*int), node.range);
}
Expand All @@ -84,14 +95,17 @@ impl Compiler {
ast::NodeValue::StringLiteral(string) => {
self.compile_constant(Object::String(Rc::new(string.to_string())), node.range);
}
ast::NodeValue::ArrayLiteral(arr) => self.compile_array(arr, node.range),
ast::NodeValue::HashLiteral(elements) => self.compile_hash_map(elements, node.range),
ast::NodeValue::PrefixOperator { .. } => self.compile_prefix_operator(node),
ast::NodeValue::ArrayLiteral(arr) => self.compile_array(arr, node.range)?,
ast::NodeValue::HashLiteral(elements) => self.compile_hash_map(elements, node.range)?,
ast::NodeValue::PrefixOperator { .. } => self.compile_prefix_operator(node)?,
ast::NodeValue::InfixOperator { .. } => todo!(),
ast::NodeValue::Assign { .. } => todo!(),
ast::NodeValue::Assign { ident, value } => {
self.compile_node(value)?;
self.compile_assign(ident, node.range)?;
}
ast::NodeValue::Index { .. } => todo!(),
ast::NodeValue::If(_) => todo!(),
ast::NodeValue::While { .. } => self.compile_while(node),
ast::NodeValue::While { .. } => self.compile_while(node)?,
ast::NodeValue::For { .. } => todo!(),
ast::NodeValue::Break => todo!(),
ast::NodeValue::Continue => todo!(),
Expand All @@ -100,69 +114,130 @@ impl Compiler {
ast::NodeValue::Return(_) => todo!(),
ast::NodeValue::Use(_) => todo!(),
}

Ok(())
}

fn compile_constant(&mut self, constant: Object, range: Range) {
let const_idx = self.add_constant(constant);
self.emit(Instruction::Constant(const_idx), range);
}

fn compile_array(&mut self, arr: &[ast::Node], range: Range) {
fn compile_array(&mut self, arr: &[ast::Node], range: Range) -> Result<(), Error> {
for node in arr {
self.compile_node(node);
self.compile_node(node)?;
}

self.emit(Instruction::Array(arr.len()), range);
Ok(())
}

fn compile_hash_map(&mut self, elements: &[ast::HashLiteralPair], range: Range) {
fn compile_hash_map(
&mut self,
elements: &[ast::HashLiteralPair],
range: Range,
) -> Result<(), Error> {
for elt in elements {
self.compile_node(&elt.key);
self.compile_node(&elt.value);
self.compile_node(&elt.key)?;
self.compile_node(&elt.value)?;
}

self.emit(Instruction::HashMap(elements.len() * 2), range);

Ok(())
}

fn compile_prefix_operator(&mut self, node: &ast::Node) {
fn compile_prefix_operator(&mut self, node: &ast::Node) -> Result<(), Error> {
let ast::NodeValue::PrefixOperator { operator, right } = &node.value else {
panic!("Expected prefix operator node, got: {node:?}");
};

self.compile_node(right);
self.compile_node(right)?;

match operator {
PrefixOperatorKind::Not => self.emit(Instruction::Bang, node.range),
PrefixOperatorKind::Negative => self.emit(Instruction::Minus, node.range),
};

Ok(())
}

fn compile_while(&mut self, node: &ast::Node) {
fn compile_while(&mut self, node: &ast::Node) -> Result<(), Error> {
let ast::NodeValue::While { condition, body } = &node.value else {
panic!("Expected while node, got: {node:?}");
};

let start_index = self.current_scope().instructions.len();
self.compile_node(condition);
self.compile_node(condition)?;

// Jump position will be fixed after
let jump_index = self.emit(Instruction::JumpNotTruthy(0), condition.range);

self.compile_block(body);
self.compile_block(body)?;

self.emit(Instruction::Jump(start_index), body.range);

let end_index = self.current_scope().instructions.len();
self.current_scope().instructions[jump_index] = Instruction::JumpNotTruthy(end_index);

Ok(())
}

fn compile_block(&mut self, block: &ast::Block) {
fn compile_block(&mut self, block: &ast::Block) -> Result<(), Error> {
for node in &block.nodes {
self.compile_node(node);
self.compile_node(node)?;

if node.kind() == ast::NodeKind::Expression {
self.emit(Instruction::Pop, node.range);
}
}

Ok(())
}

fn compile_ident(&mut self, ident: &str, range: Range) -> Result<(), Error> {
let Some(symbol) = self.symbol_table.resolve(ident) else {
return Err(Error {
kind: ErrorKind::UndefinedSymbol(ident.to_string()),
range,
});
};

match symbol {
Symbol::Global(index) => self.emit(Instruction::LoadGlobal(index), range),
};

Ok(())
}

fn compile_assign(&mut self, ident: &ast::Node, range: Range) -> Result<(), Error> {
match &ident.value {
ast::NodeValue::Identifier(identifier) => {
let symbol = self.symbol_table.define(identifier.to_string());
self.compile_store_instruction(symbol, range);
}
ast::NodeValue::Index { left, index } => {
self.compile_node(left)?;
self.compile_node(index)?;
self.emit(Instruction::IndexSet, range);
}
ast::NodeValue::ArrayLiteral(arr) => {
self.emit(Instruction::UnpackArray(arr.len()), range);

for node in arr {
self.compile_assign(node, range)?;
}
}

_ => panic!("Invalid asignee: {ident:?}"),
}

Ok(())
}

fn compile_store_instruction(&mut self, symbol: Symbol, range: Range) {
match symbol {
Symbol::Global(index) => self.emit(Instruction::StoreGlobal(index), range),
};
}
}
38 changes: 38 additions & 0 deletions runtime/src/compiler/symbol_table.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use std::collections::HashMap;

#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Symbol {
Global(usize),
}

#[derive(Debug)]
pub struct SymbolTable {
store: HashMap<String, Symbol>,
num_definitions: usize,
}

impl SymbolTable {
pub fn new() -> Self {
Self {
store: HashMap::new(),
num_definitions: 0,
}
}

pub fn define(&mut self, name: String) -> Symbol {
if let Some(symbol) = self.store.get(&name) {
return *symbol;
}

let symbol = Symbol::Global(self.num_definitions);

self.store.insert(name, symbol);
self.num_definitions += 1;

symbol
}

pub fn resolve(&self, name: &str) -> Option<Symbol> {
self.store.get(name).copied()
}
}
Loading

0 comments on commit 1aff169

Please sign in to comment.