From f664aebb5fe1db20b11fe3d29b986636e67c93ef Mon Sep 17 00:00:00 2001 From: Empa Date: Sun, 14 Jan 2024 15:24:28 +0100 Subject: [PATCH] Lower ast to intermediate representation --- Cargo.lock | 27 ++ Cargo.toml | 4 +- crates/saft-ast-to-ir/Cargo.toml | 12 + crates/saft-ast-to-ir/src/lib.rs | 372 +++++++++++++++++++++++++++ crates/saft-ast/src/lib.rs | 15 +- crates/saft-bytecode/Cargo.toml | 10 + crates/saft-bytecode/src/chunk.rs | 25 ++ crates/saft-bytecode/src/compiler.rs | 211 +++++++++++++++ crates/saft-bytecode/src/lib.rs | 5 + crates/saft-bytecode/src/op.rs | 33 +++ crates/saft-eval/src/interpreter.rs | 4 +- crates/saft-ir/Cargo.toml | 9 + crates/saft-ir/src/lib.rs | 123 +++++++++ crates/saft-parser/src/lib.rs | 4 +- crates/saft-tests/src/lib.rs | 10 +- crates/saft/Cargo.toml | 2 + crates/saft/src/main.rs | 41 ++- 17 files changed, 874 insertions(+), 33 deletions(-) create mode 100644 crates/saft-ast-to-ir/Cargo.toml create mode 100644 crates/saft-ast-to-ir/src/lib.rs create mode 100644 crates/saft-bytecode/Cargo.toml create mode 100644 crates/saft-bytecode/src/chunk.rs create mode 100644 crates/saft-bytecode/src/compiler.rs create mode 100644 crates/saft-bytecode/src/lib.rs create mode 100644 crates/saft-bytecode/src/op.rs create mode 100644 crates/saft-ir/Cargo.toml create mode 100644 crates/saft-ir/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index c74b526..15fe753 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,8 +575,10 @@ dependencies = [ "platform-dirs", "rustyline", "saft-ast", + "saft-ast-to-ir", "saft-common", "saft-eval", + "saft-ir", "saft-lexer", "saft-parser", ] @@ -588,6 +590,24 @@ dependencies = [ "saft-common", ] +[[package]] +name = "saft-ast-to-ir" +version = "0.1.0" +dependencies = [ + "codespan-reporting", + "saft-ast", + "saft-common", + "saft-ir", +] + +[[package]] +name = "saft-bytecode" +version = "0.1.0" +dependencies = [ + "saft-ast", + "saft-common", +] + [[package]] name = "saft-common" version = "0.1.0" @@ -603,6 +623,13 @@ dependencies = [ "saft-macro", ] +[[package]] +name = "saft-ir" +version = "0.1.0" +dependencies = [ + "saft-common", +] + [[package]] name = "saft-lexer" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 15e5bbb..4026222 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,9 +2,9 @@ resolver = "2" members = [ "crates/saft", - "crates/saft-ast", + "crates/saft-ast", "crates/saft-ast-to-ir", "crates/saft-bytecode", "crates/saft-common", - "crates/saft-eval", + "crates/saft-eval", "crates/saft-ir", "crates/saft-lexer", "crates/saft-macro", "crates/saft-parser", diff --git a/crates/saft-ast-to-ir/Cargo.toml b/crates/saft-ast-to-ir/Cargo.toml new file mode 100644 index 0000000..3c28e8a --- /dev/null +++ b/crates/saft-ast-to-ir/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "saft-ast-to-ir" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +codespan-reporting = "0.11.1" +saft-ast = { version = "0.1.0", path = "../saft-ast" } +saft-common = { version = "0.1.0", path = "../saft-common" } +saft-ir = { version = "0.1.0", path = "../saft-ir" } diff --git a/crates/saft-ast-to-ir/src/lib.rs b/crates/saft-ast-to-ir/src/lib.rs new file mode 100644 index 0000000..b99b11a --- /dev/null +++ b/crates/saft-ast-to-ir/src/lib.rs @@ -0,0 +1,372 @@ +#![feature(iterator_try_collect)] +use codespan_reporting::diagnostic::{Diagnostic, Label}; +use saft_common::span::{Span, Spanned}; +use std::collections::HashMap; + +use saft_ast as ast; +use saft_ir as ir; + +macro_rules! exotic { + ($msg:expr, $span:expr) => { + return Err(Error::Exotic { + message: $msg.into(), + span: $span.into(), + note: None, + }) + }; + + ($msg:expr, $span:expr, $note:expr) => { + return Err(Error::Exotic { + message: $msg.into(), + span: $span.into(), + note: Some($note.into()), + }) + }; +} + +#[derive(Debug)] +pub enum Error { + Exotic { + message: String, + span: Span, + note: Option, + }, +} + +impl Error { + pub fn diagnostic(&self, file_id: FileId) -> Diagnostic { + match self { + Error::Exotic { + message, + span, + note, + } => Diagnostic::error().with_message(message).with_labels({ + let mut label = Label::primary(file_id, span.r.clone()); + if let Some(note) = note { + label = label.with_message(note); + }; + vec![label] + }), + } + } +} + +pub struct Lowerer { + items: Vec>, + scopes: Vec>, + var_counter: usize, +} + +impl Lowerer { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + items: Vec::new(), + scopes: vec![HashMap::new()], + var_counter: 0, + } + } + + fn resolve_module_items(&mut self, module: &ast::Module) -> Result<(), Error> { + self.resolve_statements_items(&module.stmts) + } + + fn resolve_block_items(&mut self, block: &ast::Block) -> Result<(), Error> { + self.resolve_statements_items(&block.stmts) + } + + fn resolve_statements_items( + &mut self, + stmts: &Vec>, + ) -> Result<(), Error> { + for stmt in stmts { + let s = &stmt.s; + if let ast::Statement::Item(item) = &stmt.v { + self.resolve_item(&s.spanned(item))?; + } + } + Ok(()) + } + + fn resolve_item(&mut self, item: &Spanned<&ast::Item>) -> Result, Error> { + let Spanned { s, v: item } = item; + + Ok(s.spanned(match item { + ast::Item::Function(ast::Function { + ident, + params, + body, + }) => { + // FIXME: Scopes are wrong, this looks up scope which is inaccessible + let item = self.scoped(|l| { + let params = params + .iter() + .map(|ident| l.declare(ident)) + .try_collect::>()?; + + let body = l.lower_block(body)?; + + Ok(ir::Item::Function(ir::Function { params, body })) + })?; + + self.new_item(ident, s.spanned(item)) + } + })) + } + + pub fn lower_module(mut self, module: &ast::Module) -> Result { + self.resolve_module_items(module)?; + + let stmts = module + .stmts + .iter() + .map(|stmt| self.lower_statement(stmt)) + .filter_map(|stmt| match stmt { + Ok(m_stmt) => m_stmt.map(Ok), + Err(e) => Some(Err(e)), + }) + .try_collect::>()?; + + Ok(ir::Module { + items: self.items, + stmts, + }) + } + + fn lower_statements( + &mut self, + stmts: &[Spanned], + ) -> Result>, Error> { + stmts + .iter() + .map(|stmt| self.lower_statement(stmt)) + .filter_map(|stmt| match stmt { + Ok(m_stmt) => m_stmt.map(Ok), + Err(e) => Some(Err(e)), + }) + .try_collect::>() + } + + fn lower_exprs( + &mut self, + exprs: &[Spanned], + ) -> Result>, Error> { + exprs + .iter() + .map(|expr| self.lower_expr(expr)) + .try_collect::>() + } + + pub fn lower_statement( + &mut self, + stmt: &Spanned, + ) -> Result>, Error> { + let s = &stmt.s; + + Ok(Some(match &stmt.v { + ast::Statement::Expr(e) => { + let ir_expr = self.lower_expr(e)?; + s.spanned(ir::Stmt::Expr(ir_expr)) + } + ast::Statement::Declare { ident, expr } => { + let ref_ = self.declare(ident)?; + let expr = self.lower_expr(expr)?; + s.spanned(ir::Stmt::Declare(ref_, expr)) + } + ast::Statement::Return(e) => s.spanned(ir::Stmt::Return(self.lower_expr(e)?)), + ast::Statement::Item(_) => { + // Already handled + return Ok(None); + } + })) + } + + fn lower_expr(&mut self, expr: &Spanned) -> Result, Error> { + fn binary( + lowerer: &mut Lowerer, + lhs: &Spanned, + rhs: &Spanned, + op: ir::BinaryOp, + ) -> Result { + let lhs = Box::new(lowerer.lower_expr(lhs)?); + let rhs = Box::new(lowerer.lower_expr(rhs)?); + Ok(ir::Expr::Binary(lhs, rhs, op)) + } + let s = &expr.s; + + Ok(s.spanned(match &expr.v { + ast::Expr::Nil => ir::Expr::Nil, + ast::Expr::Bool(b) => ir::Expr::Bool(*b), + ast::Expr::Float(f) => ir::Expr::Float(*f), + ast::Expr::Integer(i) => ir::Expr::Integer(*i), + ast::Expr::String(s) => ir::Expr::String(s.clone()), + ast::Expr::Var(ident) => { + let ref_ = self.resolve(ident)?; + ir::Expr::Var(ref_) + } + ast::Expr::Vec(exprs) => ir::Expr::Vec( + exprs + .iter() + .map(|expr| self.lower_expr(expr)) + .try_collect::>()?, + ), + ast::Expr::Grouping(expr) => ir::Expr::Grouping(Box::new(self.lower_expr(expr)?)), + ast::Expr::Block(block) => ir::Expr::Block(Box::new(self.lower_block(block)?)), + ast::Expr::If(cond, body, else_) => ir::Expr::If(ir::If { + cond: Box::new(self.lower_expr(cond)?), + body: Box::new(self.lower_block(body)?), + else_: Box::new( + match else_.as_ref().map(|else_| match self.lower_expr(else_)?.v { + ir::Expr::Block(block) => Ok(ir::Else::Block(block.v)), + ir::Expr::If(if_) => Ok(ir::Else::If(if_)), + _ => panic!("Ast else should only contain block or if"), + }) { + Some(v) => Some(v?), + None => None, + }, + ), + }), + ast::Expr::Loop(stmts) => self.scoped(|l| { + l.resolve_statements_items(stmts)?; + Ok(ir::Expr::Loop(Box::new(ir::UntailBlock( + l.lower_statements(stmts)?, + )))) + })?, + ast::Expr::Break(e) => ir::Expr::Break(Box::new(Some(self.lower_expr(e)?))), + ast::Expr::Neg(expr) => { + ir::Expr::Unary(Box::new(self.lower_expr(expr)?), ir::UnaryOp::Negate) + } + ast::Expr::Not(expr) => { + ir::Expr::Unary(Box::new(self.lower_expr(expr)?), ir::UnaryOp::Not) + } + ast::Expr::Assign(assignable, assignment) => { + let assignable = self.lower_lexpr(assignable)?; + let assignment = self.lower_expr(assignment)?; + ir::Expr::Assign(Box::new(assignable), Box::new(assignment)) + } + ast::Expr::Add(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Add)?, + ast::Expr::Sub(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Sub)?, + ast::Expr::Mul(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Mul)?, + ast::Expr::Div(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Div)?, + ast::Expr::IDiv(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::IDiv)?, + ast::Expr::Pow(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Pow)?, + ast::Expr::And(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::And)?, + ast::Expr::Or(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Or)?, + ast::Expr::Lt(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Lt)?, + ast::Expr::Le(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Le)?, + ast::Expr::Gt(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Gt)?, + ast::Expr::Ge(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Ge)?, + ast::Expr::Eq(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Eq)?, + ast::Expr::Ne(lhs, rhs) => binary(self, lhs, rhs, ir::BinaryOp::Ne)?, + ast::Expr::Call(callable, args) => { + let callable = self.lower_expr(callable)?; + let args = self.lower_exprs(args)?; + ir::Expr::Call(Box::new(callable), args) + } + ast::Expr::Index(indexable, index) => { + let indexable = self.lower_expr(indexable)?; + let index = self.lower_expr(index)?; + ir::Expr::Index(Box::new(indexable), Box::new(index)) + } + })) + } + + fn lower_block(&mut self, block: &Spanned) -> Result, Error> { + let s = &block.s; + self.scoped(|l| { + l.resolve_block_items(&block.v)?; + + let stmts = l.lower_statements(&block.v.stmts)?; + + let tail = match &block.v.tail { + Some(tail) => Some(l.lower_expr(tail)?), + None => None, + }; + + Ok(s.spanned(ir::Block { stmts, tail })) + }) + } + + fn lower_lexpr( + &mut self, + assignable: &Spanned, + ) -> Result, Error> { + let s = &assignable.s; + Ok(s.spanned(match &assignable.v { + ast::Expr::Var(ident) => { + let ref_ = self.resolve(ident)?; + match ref_ { + ir::Ref::Item(_) => exotic!( + "Unassignable", + s.clone(), + "Cannot assign to items, they are constant" + ), + ir::Ref::Var(var_ref) => ir::LExpr::Var(var_ref), + } + } + ast::Expr::Index(indexable, index) => { + let indexable = self.lower_expr(indexable)?; + let index = self.lower_expr(index)?; + ir::LExpr::Index(Box::new(indexable), Box::new(index)) + } + _ => exotic!("Unassignable", s.clone(), "Not an assignable expression"), + })) + } + + fn enter_scope(&mut self) { + self.scopes.push(HashMap::new()); + } + + fn exit_scope(&mut self) { + self.scopes.pop().unwrap(); + } + + fn scoped(&mut self, mut f: F) -> T + where + F: FnMut(&mut Self) -> T, + { + self.enter_scope(); + let res = f(self); + self.exit_scope(); + res + } + + fn declare(&mut self, ident: &Spanned) -> Result, Error> { + let ref_ = self.new_varref(); + self.scopes + .last_mut() + .unwrap() + .insert(ident.v.clone(), ir::Ref::Var(ref_)); + Ok(ident.s.spanned(ref_)) + } + + fn resolve(&self, ident: &Spanned) -> Result { + for scope in self.scopes.iter().rev() { + if let Some(ref_) = scope.get(&ident.v) { + return Ok(*ref_); + } + } + + exotic!( + "Unresolved variable", + ident.s.clone(), + format!("Could not resolve identifier '{}'", ident.v) + ) + } + + fn new_item(&mut self, ident: &Spanned, item: Spanned) -> ir::ItemRef { + let ref_ = ir::ItemRef(self.items.len()); + self.items.push(item); + self.scopes + .last_mut() + .unwrap() + .insert(ident.v.clone(), ir::Ref::Item(ref_)); + ref_ + } + + fn new_varref(&mut self) -> ir::VarRef { + let ref_ = ir::VarRef(self.var_counter); + self.var_counter += 1; + ref_ + } +} diff --git a/crates/saft-ast/src/lib.rs b/crates/saft-ast/src/lib.rs index b6f83eb..95bd7c6 100644 --- a/crates/saft-ast/src/lib.rs +++ b/crates/saft-ast/src/lib.rs @@ -115,7 +115,7 @@ impl Statement { Statement::Expr(..) => "expression statement", Statement::Declare { .. } => "variable declaration", Statement::Item(item) => match item { - Item::Fn { .. } => "function declaration", + Item::Function { .. } => "function declaration", }, Statement::Return(_) => "return", } @@ -124,9 +124,12 @@ impl Statement { #[derive(Debug, Clone, PartialEq)] pub enum Item { - Fn { - ident: Spanned, - params: Vec>, - body: Spanned, - }, + Function(Function), +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Function { + pub ident: Spanned, + pub params: Vec>, + pub body: Spanned, } diff --git a/crates/saft-bytecode/Cargo.toml b/crates/saft-bytecode/Cargo.toml new file mode 100644 index 0000000..b5baa44 --- /dev/null +++ b/crates/saft-bytecode/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "saft-bytecode" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +saft-ast = { version = "0.1.0", path = "../saft-ast" } +saft-common = { version = "0.1.0", path = "../saft-common" } diff --git a/crates/saft-bytecode/src/chunk.rs b/crates/saft-bytecode/src/chunk.rs new file mode 100644 index 0000000..a4e66df --- /dev/null +++ b/crates/saft-bytecode/src/chunk.rs @@ -0,0 +1,25 @@ +use crate::op::Op; + +pub struct Chunk { + pub ops: Vec, +} + +impl Chunk { + pub fn new() -> Self { + Self { ops: Vec::new() } + } + + pub fn emit(&mut self, op: Op) { + self.ops.push(op); + } + + pub fn emit_i(&mut self, op: Op) -> usize { + let i = self.ops.len(); + self.ops.push(op); + i + } + + pub fn end(&self) -> usize { + self.ops.len() + } +} diff --git a/crates/saft-bytecode/src/compiler.rs b/crates/saft-bytecode/src/compiler.rs new file mode 100644 index 0000000..dc18146 --- /dev/null +++ b/crates/saft-bytecode/src/compiler.rs @@ -0,0 +1,211 @@ +use saft_ast as ast; +use saft_common::span::Spanned; + +use crate::{chunk::Chunk, op::Op}; + +enum Error {} + +struct Env { + base: usize, +} + +impl Env { + pub fn new(base: usize) -> Self { + Self { base } + } +} + +struct Compiler { + // + stack_i: usize, + envs: Vec, +} + +impl Compiler { + pub fn new() -> Self { + Self { + stack_i: 0, + envs: vec![Env::new(0)], + } + } + + pub fn compile_module(&mut self, module: &ast::Module) -> Result { + let mut chunk = Chunk::new(); + + for stmt in &module.stmts { + self.compile_stmt(stmt, &mut chunk)? + } + + Ok(chunk) + } + + fn compile_fn(&mut self, function: &ast::Function) -> Result { + todo!() + } + + fn compile_stmt( + &mut self, + stmt: &Spanned, + chunk: &mut Chunk, + ) -> Result<(), Error> { + match &stmt.v { + ast::Statement::Expr(e) => { + self.compile_expr(e, chunk)?; + chunk.emit(Op::Pop); + } + ast::Statement::Declare { ident, expr } => { + self.compile_expr(expr, chunk)?; + todo!("Declare the variable in the scopes"); + } + ast::Statement::Return(e) => { + self.compile_expr(e, chunk)?; + chunk.emit(Op::Return); + } + ast::Statement::Item(ast::Item::Function(fun)) => { + let fn_chunk = self.compile_fn(fun)?; + todo!("create function resource and push function value"); + } + } + + Ok(()) + } + + fn compile_block( + &mut self, + block: &Spanned, + chunk: &mut Chunk, + ) -> Result<(), Error> { + self.enter_scope(); + for stmt in &block.v.stmts { + self.compile_stmt(stmt, chunk)?; + } + + if let Some(tail) = &block.v.tail { + self.compile_expr(tail, chunk)?; + } else { + chunk.emit(Op::Nil); + } + + self.exit_scope_trailing(chunk); + + Ok(()) + } + + fn compile_expr(&mut self, expr: &Spanned, chunk: &mut Chunk) -> Result<(), Error> { + match &expr.v { + ast::Expr::Nil => chunk.emit(Op::Nil), + ast::Expr::Bool(b) => chunk.emit(Op::Bool(*b)), + ast::Expr::Float(f) => chunk.emit(Op::Float(*f)), + ast::Expr::Integer(i) => chunk.emit(Op::Integer(*i)), + ast::Expr::String(s) => chunk.emit(Op::String(s.clone())), + ast::Expr::Var(ident) => { + let i = self.lookup(ident)?; + todo!() + } + ast::Expr::Vec(exprs) => { + for expr in exprs { + self.compile_expr(expr, chunk)?; + } + chunk.emit(Op::Vec(exprs.len())); + } + ast::Expr::Grouping(box e) => self.compile_expr(e, chunk)?, + ast::Expr::Block(block) => self.compile_block(block, chunk)?, + ast::Expr::If(cond, body, else_) => { + self.compile_expr(cond, chunk)?; + let else_jump = chunk.emit_i(Op::JmpFalse(0)); + self.compile_block(body, chunk)?; + let end_jump = chunk.emit_i(Op::Jmp(0)); + + // Else + let else_offset = chunk.end(); + self.patch_jump(else_jump, else_offset, chunk); + if let Some(box else_) = else_ { + self.compile_expr(else_, chunk)?; + } else { + chunk.emit(Op::Nil); + } + + let end_offset = chunk.end(); + self.patch_jump(end_jump, end_offset, chunk); + } + ast::Expr::Loop(_) => todo!(), + ast::Expr::Break(_) => todo!(), + ast::Expr::Neg(_) => todo!(), + ast::Expr::Not(e) => { + self.compile_expr(e, chunk)?; + chunk.emit(Op::Not); + } + ast::Expr::Assign(_, _) => todo!(), + ast::Expr::Add(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Add)?, + ast::Expr::Sub(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Sub)?, + ast::Expr::Mul(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Mul)?, + ast::Expr::Div(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Div)?, + ast::Expr::IDiv(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::IDiv)?, + ast::Expr::Pow(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Pow)?, + ast::Expr::And(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::And)?, + ast::Expr::Or(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Or)?, + ast::Expr::Lt(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Lt)?, + ast::Expr::Le(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Le)?, + ast::Expr::Gt(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Gt)?, + ast::Expr::Ge(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Ge)?, + ast::Expr::Eq(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Eq)?, + ast::Expr::Ne(lhs, rhs) => self.binary(chunk, lhs, rhs, Op::Ne)?, + + ast::Expr::Call(callable, args) => { + self.compile_expr(callable, chunk)?; + for arg in args { + self.compile_expr(arg, chunk)?; + } + chunk.emit(Op::Call(args.len())); + } + ast::Expr::Index(indexable, index) => { + self.compile_expr(indexable, chunk)?; + self.compile_expr(index, chunk)?; + chunk.emit(Op::Index); + } + } + + Ok(()) + } + + fn enter_scope(&mut self) { + self.envs.push(Env::new(self.stack_i)); + } + + fn exit_scope(&mut self, chunk: &mut Chunk) { + let env = self.envs.pop().unwrap(); + for i in 0..self.stack_i - env.base { + chunk.emit(Op::Pop); + } + } + + fn exit_scope_trailing(&mut self, chunk: &mut Chunk) { + let env = self.envs.pop().unwrap(); + let decls = self.stack_i - env.base; + chunk.emit(Op::TrailPop(decls)) + } + + fn binary( + &mut self, + chunk: &mut Chunk, + lhs: &Spanned, + rhs: &Spanned, + op: Op, + ) -> Result<(), Error> { + self.compile_expr(lhs, chunk)?; + self.compile_expr(rhs, chunk)?; + chunk.emit(Op::Add); + Ok(()) + } + + fn patch_jump(&self, jump_i: usize, target: usize, chunk: &mut Chunk) { + chunk.ops[jump_i] = match chunk.ops[jump_i] { + Op::JmpFalse(_) => Op::JmpFalse(target), + _ => panic!("Tried patching something else than a jump"), + } + } + + fn lookup(&self, ident: &Spanned) -> Result { + todo!() + } +} diff --git a/crates/saft-bytecode/src/lib.rs b/crates/saft-bytecode/src/lib.rs new file mode 100644 index 0000000..7bd1fdf --- /dev/null +++ b/crates/saft-bytecode/src/lib.rs @@ -0,0 +1,5 @@ +#![feature(box_patterns)] + +mod chunk; +mod compiler; +mod op; diff --git a/crates/saft-bytecode/src/op.rs b/crates/saft-bytecode/src/op.rs new file mode 100644 index 0000000..2e93af3 --- /dev/null +++ b/crates/saft-bytecode/src/op.rs @@ -0,0 +1,33 @@ +pub enum Op { + Pop, + Return, + Nil, + Bool(bool), + Float(f64), + Integer(i64), + String(String), + Var(usize), + JmpFalse(usize), + JmpTrue(usize), + Jmp(usize), + Not, + Add, + Pow, + IDiv, + Div, + Mul, + Sub, + And, + Or, + Lt, + Le, + Gt, + Ge, + Eq, + Ne, + /// Pop n + 1 values, then push the last one, used in blocks + TrailPop(usize), + Call(usize), + Index, + Vec(usize), +} diff --git a/crates/saft-eval/src/interpreter.rs b/crates/saft-eval/src/interpreter.rs index a838bd1..a0b8196 100644 --- a/crates/saft-eval/src/interpreter.rs +++ b/crates/saft-eval/src/interpreter.rs @@ -137,11 +137,11 @@ impl Interpreter { self.env.declare(ident, res.v); Ok(()) } - Statement::Item(Item::Fn { + Statement::Item(Item::Function(saft_ast::Function { ident, params, body, - }) => { + })) => { let fun = Value::Function(Rc::new(Function::SaftFunction(SaftFunction { params: params.clone(), body: body.clone(), diff --git a/crates/saft-ir/Cargo.toml b/crates/saft-ir/Cargo.toml new file mode 100644 index 0000000..af77e0c --- /dev/null +++ b/crates/saft-ir/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "saft-ir" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +saft-common = { version = "0.1.0", path = "../saft-common" } diff --git a/crates/saft-ir/src/lib.rs b/crates/saft-ir/src/lib.rs new file mode 100644 index 0000000..b8d833d --- /dev/null +++ b/crates/saft-ir/src/lib.rs @@ -0,0 +1,123 @@ +use saft_common::span::Spanned; + +#[derive(Debug)] +pub struct Ident(usize); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Ref { + Item(ItemRef), + Var(VarRef), +} +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ItemRef(pub usize); +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VarRef(pub usize); + +#[derive(Debug)] +pub struct Module { + pub items: Vec>, + pub stmts: Vec>, +} + +#[derive(Debug)] +pub enum Item { + Function(Function), +} + +#[derive(Debug)] +pub struct Function { + pub params: Vec>, + pub body: Spanned, +} + +#[derive(Debug)] +pub enum Stmt { + Expr(Spanned), + Declare(Spanned, Spanned), + Return(Spanned), +} + +#[derive(Debug)] +pub enum Expr { + Nil, + Bool(bool), + Float(f64), + Integer(i64), + Var(Ref), + String(String), + Vec(Vec>), + + Grouping(Box>), + + Block(Box>), + + If(If), + Loop(Box), + Break(Box>>), + + Unary(Box>, UnaryOp), + Binary(Box>, Box>, BinaryOp), + Assign(Box>, Box>), + + Call(Box>, Vec>), + Index(Box>, Box>), +} + +#[derive(Debug)] +pub enum LExpr { + Index(Box>, Box>), + Var(VarRef), +} + +#[derive(Debug)] +pub struct If { + pub cond: Box>, + pub body: Box>, + pub else_: Box>, +} + +#[derive(Debug)] +pub enum Else { + Block(Block), + If(If), +} + +#[derive(Debug)] +pub enum UnaryOp { + Plus, + Negate, + Not, +} + +#[derive(Debug)] +pub enum BinaryOp { + Or, + + And, + + Eq, + Ne, + + Lt, + Le, + Gt, + Ge, + + Mul, + Div, + IDiv, + + Add, + Sub, + + Pow, +} + +#[derive(Debug)] +pub struct UntailBlock(pub Vec>); + +#[derive(Debug)] +pub struct Block { + pub stmts: Vec>, + pub tail: Option>, +} diff --git a/crates/saft-parser/src/lib.rs b/crates/saft-parser/src/lib.rs index 8ba7ef4..594f206 100644 --- a/crates/saft-parser/src/lib.rs +++ b/crates/saft-parser/src/lib.rs @@ -506,11 +506,11 @@ impl<'a> Parser<'a> { let s = start.join(&body.s); Ok(Spanned::new( - Statement::Item(Item::Fn { + Statement::Item(Item::Function(ast::Function { ident, params, body, - }), + })), s, )) } diff --git a/crates/saft-tests/src/lib.rs b/crates/saft-tests/src/lib.rs index d86f47f..c8aaab7 100644 --- a/crates/saft-tests/src/lib.rs +++ b/crates/saft-tests/src/lib.rs @@ -20,15 +20,11 @@ mod test { let mut got_output = false; for line in std::fs::read_to_string(file_name).unwrap().lines() { - if line.starts_with('#') { - let comment = &line[1..].trim(); - + if let Some(comment) = line.strip_prefix('#').map(|l| l.trim()) { if got_output { expected.push(comment.to_string()); - } else { - if *comment == "output:" { - got_output = true; - } + } else if comment == "output:" { + got_output = true; } } } diff --git a/crates/saft/Cargo.toml b/crates/saft/Cargo.toml index 51f3df1..aa52767 100644 --- a/crates/saft/Cargo.toml +++ b/crates/saft/Cargo.toml @@ -10,7 +10,9 @@ indoc = "2.0.4" platform-dirs = "0.3.0" rustyline = "13.0.0" saft-ast = { version = "0.1.0", path = "../saft-ast" } +saft-ast-to-ir = { version = "0.1.0", path = "../saft-ast-to-ir" } saft-common = { version = "0.1.0", path = "../saft-common" } saft-eval = { version = "0.1.0", path = "../saft-eval" } +saft-ir = { version = "0.1.0", path = "../saft-ir" } saft-lexer = { version = "0.1.0", path = "../saft-lexer" } saft-parser = { version = "0.1.0", path = "../saft-parser" } diff --git a/crates/saft/src/main.rs b/crates/saft/src/main.rs index 32f0ffc..d436aad 100644 --- a/crates/saft/src/main.rs +++ b/crates/saft/src/main.rs @@ -102,24 +102,37 @@ fn interpret_stmt(interpreter: &mut Interpreter, s: &str) { let config = codespan_reporting::term::Config::default(); match saft_parser::Parser::new(s).parse_single_statment() { - Ok(spanned_stmt) => match &spanned_stmt.v { - saft_ast::Statement::Expr(se) => match interpreter.eval_outer_expr(se) { - Ok(v) => match v.v { - saft_eval::value::Value::Nil => {} - v => println!("{}", v.repr()), - }, - Err(err) => { - term::emit(&mut writer.lock(), &config, &files, &err.diagnostic(id)).unwrap() + Ok(spanned_stmt) => { + match saft_ast_to_ir::Lowerer::new().lower_statement(&spanned_stmt) { + Ok(ir) => { + println!("{:#?}", ir.unwrap()) } - }, - - _ => match interpreter.exec_outer_statement(&spanned_stmt) { - Ok(..) => {} Err(err) => { term::emit(&mut writer.lock(), &config, &files, &err.diagnostic(id)).unwrap() } - }, - }, + } + + match &spanned_stmt.v { + saft_ast::Statement::Expr(se) => match interpreter.eval_outer_expr(se) { + Ok(v) => match v.v { + saft_eval::value::Value::Nil => {} + v => println!("{}", v.repr()), + }, + Err(err) => { + term::emit(&mut writer.lock(), &config, &files, &err.diagnostic(id)) + .unwrap() + } + }, + + _ => match interpreter.exec_outer_statement(&spanned_stmt) { + Ok(..) => {} + Err(err) => { + term::emit(&mut writer.lock(), &config, &files, &err.diagnostic(id)) + .unwrap() + } + }, + } + } Err(err) => term::emit(&mut writer.lock(), &config, &files, &err.diagnostic(id)).unwrap(), }; }