[fine] Gobs of work

- Assertion improvements
- Type check function calls
- Functions in the environment
- Compile function calls
This commit is contained in:
John Doty 2024-01-13 08:12:39 -08:00
parent 9d226b205d
commit 5ebede4a21
6 changed files with 539 additions and 142 deletions

View file

@ -2,35 +2,10 @@ use std::collections::HashMap;
use crate::{
parser::{SyntaxTree, Tree, TreeKind, TreeRef},
semantics::{Location, Semantics, Type},
semantics::{Declaration, Location, Semantics, Type},
tokens::TokenKind,
};
macro_rules! compiler_assert_eq {
($compiler:expr, $tr:expr, $ll:expr, $rr:expr, $($t:tt)*) => {{
let left = &$ll;
let right = &$rr;
if left != right {
let semantics = $compiler.semantics;
semantics.dump_compiler_state(Some($tr));
let message = format!($($t)*);
assert_eq!(left, right, "{}", message);
}
}};
}
macro_rules! compiler_assert {
($compiler:expr, $tr:expr, $($t:tt)*) => {{
if !($($t)*) {
let semantics = $compiler.semantics;
semantics.dump_compiler_state(Some($tr));
assert!($($t)*);
}
}};
}
// TODO: If I were cool this would by actual bytecode.
// But I'm not cool.
#[derive(Debug)]
@ -56,6 +31,10 @@ pub enum Instruction {
PushTrue,
StoreLocal(usize),
StoreModule(usize),
LoadFunction(usize),
LoadExtern(usize),
LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index.
Call(usize),
}
pub enum Export {
@ -130,7 +109,9 @@ struct Compiler<'a> {
semantics: &'a Semantics<'a>,
syntax: &'a SyntaxTree<'a>,
function_bindings: HashMap<String, usize>,
// TODO: generic functions will actually be keyed by treeref and concrete
// types
function_bindings: HashMap<TreeRef, usize>,
module: Module,
function: Function,
}
@ -158,6 +139,62 @@ impl<'a> Compiler<'a> {
}
}
macro_rules! compiler_assert_eq {
($compiler:expr, $tr:expr, $ll:expr, $rr:expr $(,)?) => {{
let left = &$ll;
let right = &$rr;
if left != right {
let semantics = $compiler.semantics;
semantics.dump_compiler_state(Some($tr));
assert_eq!(left, right);
}
}};
($compiler:expr, $tr:expr, $ll:expr, $rr:expr, $($t:tt)+) => {{
let left = &$ll;
let right = &$rr;
if left != right {
let semantics = $compiler.semantics;
semantics.dump_compiler_state(Some($tr));
assert_eq!(left, right, $($t)*);
}
}};
}
macro_rules! compiler_assert {
($compiler:expr, $tr:expr, $cond:expr $(,)?) => {{
if !$cond {
let semantics = $compiler.semantics;
semantics.dump_compiler_state(Some($tr));
assert!($cond);
}
}};
($compiler:expr, $tr:expr, $cond:expr, $($arg:tt)+) => {{
if !$cond {
let semantics = $compiler.semantics;
semantics.dump_compiler_state(Some($tr));
assert!($cond, $($arg)*);
}
}};
}
macro_rules! ice {
($compiler: expr, $tr:expr, $($t:tt)+) => {{
let semantics = $compiler.semantics;
semantics.dump_compiler_state(Some($tr));
panic!($($t)*)
}}
}
// macro_rules! ice {
// ($compiler:expr, $tr:expr, $($t:tt)*) => {{}};
// }
pub fn compile(semantics: &Semantics) -> Module {
let mut compiler = Compiler {
semantics,
@ -198,15 +235,14 @@ fn compile_expression(c: &mut Compiler, t: TreeRef) {
TreeKind::Error => None,
TreeKind::LiteralExpression => compile_literal(c, t, tree),
TreeKind::GroupingExpression => compile_grouping(c, tree),
TreeKind::UnaryExpression => compile_unary_operator(c, tree),
TreeKind::UnaryExpression => compile_unary_operator(c, t, tree),
TreeKind::ConditionalExpression => compile_condition_expression(c, tree),
TreeKind::BinaryExpression => compile_binary_expression(c, tree),
TreeKind::BinaryExpression => compile_binary_expression(c, t, tree),
TreeKind::Identifier => compile_identifier_expression(c, t, tree),
TreeKind::CallExpression => todo!(),
TreeKind::CallExpression => compile_call_expression(c, tree),
TreeKind::Block => compile_block_expression(c, tree),
_ => c
.semantics
.internal_compiler_error(Some(t), "tree is not an expression, cannot compile"),
TreeKind::Argument => compile_argument(c, tree),
_ => ice!(c, t, "{tree:?} is not an expression, cannot compile"),
};
if matches!(cr, None) {
c.push(Instruction::Panic);
@ -246,7 +282,7 @@ fn compile_literal(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
c.push(Instruction::PushString(index))
}
Type::Error => c.push(Instruction::Panic),
_ => panic!("unsupported literal type: {t:?}"),
_ => ice!(c, t, "unsupported literal type: {t:?}"),
};
OK
}
@ -256,10 +292,10 @@ fn compile_grouping(c: &mut Compiler, t: &Tree) -> CR {
OK
}
fn compile_unary_operator(c: &mut Compiler, t: &Tree) -> CR {
compile_expression(c, t.nth_tree(1)?);
fn compile_unary_operator(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
compile_expression(c, tr.nth_tree(1)?);
let tok = t.nth_token(0)?;
let tok = tr.nth_token(0)?;
match tok.kind {
TokenKind::Minus => {
c.push(Instruction::PushFloat(-1.0));
@ -268,7 +304,7 @@ fn compile_unary_operator(c: &mut Compiler, t: &Tree) -> CR {
TokenKind::Bang => {
c.push(Instruction::BoolNot);
}
_ => panic!("unsupported unary operator"),
_ => ice!(c, t, "unsupported unary operator"),
}
OK
}
@ -294,23 +330,23 @@ fn compile_condition_expression(c: &mut Compiler, t: &Tree) -> CR {
OK
}
fn compile_binary_expression(c: &mut Compiler, t: &Tree) -> CR {
compile_expression(c, t.nth_tree(0)?);
match t.nth_token(1)?.kind {
fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
compile_expression(c, tr.nth_tree(0)?);
match tr.nth_token(1)?.kind {
TokenKind::Plus => {
compile_expression(c, t.nth_tree(2)?);
compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatAdd);
}
TokenKind::Minus => {
compile_expression(c, t.nth_tree(2)?);
compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatSubtract);
}
TokenKind::Star => {
compile_expression(c, t.nth_tree(2)?);
compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatMultiply);
}
TokenKind::Slash => {
compile_expression(c, t.nth_tree(2)?);
compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatDivide);
}
TokenKind::And => {
@ -321,7 +357,7 @@ fn compile_binary_expression(c: &mut Compiler, t: &Tree) -> CR {
c.patch(jump_false_index, |i| Instruction::JumpFalse(i));
compile_expression(c, t.nth_tree(2)?);
compile_expression(c, tr.nth_tree(2)?);
c.patch(jump_end_index, |i| Instruction::Jump(i));
}
@ -333,11 +369,11 @@ fn compile_binary_expression(c: &mut Compiler, t: &Tree) -> CR {
c.patch(jump_true_index, |i| Instruction::JumpTrue(i));
compile_expression(c, t.nth_tree(2)?);
compile_expression(c, tr.nth_tree(2)?);
c.patch(jump_end_index, |i| Instruction::Jump(i));
}
_ => panic!("Unsupported binary expression"),
_ => ice!(c, t, "Unsupported binary expression"),
}
OK
}
@ -347,28 +383,69 @@ fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> O
let environment = c.semantics.environment_of(t);
let declaration = environment.bind(ident)?;
let instruction = match declaration.location {
Location::Local => {
if declaration.index >= c.function.locals {
c.function.locals = declaration.index + 1;
// TODO: Load function declaration. :P
let instruction = match declaration {
Declaration::Variable {
location, index, ..
} => {
let index = *index;
match location {
Location::Local => {
if index >= c.function.locals {
c.function.locals = index + 1;
}
Instruction::LoadLocal(index)
}
Location::Argument => {
compiler_assert!(c, t, index < c.function.args);
Instruction::LoadArgument(index)
}
Location::Module => {
compiler_assert!(c, t, index < c.module.globals);
Instruction::LoadModule(index)
}
}
Instruction::LoadLocal(declaration.index)
}
Location::Argument => {
compiler_assert!(c, t, declaration.index < c.function.args);
Instruction::LoadArgument(declaration.index)
}
Location::Module => {
compiler_assert!(c, t, declaration.index < c.module.globals);
Instruction::LoadModule(declaration.index)
Declaration::Function { declaration, .. } => {
let index = match c.function_bindings.get(declaration) {
Some(index) => *index,
None => {
let tree = &c.syntax[*declaration];
compiler_assert_eq!(c, t, tree.kind, TreeKind::FunctionDecl);
compile_function_declaration(c, t, tree, false)?;
*c.function_bindings
.get(declaration)
.expect("did not compile the function!")
}
};
Instruction::LoadFunction(index)
}
Declaration::ExternFunction { id, .. } => Instruction::LoadExternFunction(id.id()),
};
c.push(instruction);
OK
}
fn compile_block_expression(c: &mut Compiler, tree: &Tree) -> Option<()> {
fn compile_call_expression(c: &mut Compiler, tree: &Tree) -> CR {
let arg_list = tree.child_tree_of_kind(c.syntax, TreeKind::ArgumentList)?;
let mut args: Vec<_> = arg_list.child_trees().collect();
let arg_count = args.len();
args.reverse();
for arg in args {
compile_expression(c, arg);
}
let func = tree.nth_tree(0)?;
compile_expression(c, func);
c.push(Instruction::Call(arg_count));
OK
}
fn compile_block_expression(c: &mut Compiler, tree: &Tree) -> CR {
let last_is_brace = tree.nth_token(tree.children.len() - 1).is_some();
let last_index = tree.children.len() - if last_is_brace { 2 } else { 1 };
@ -379,14 +456,19 @@ fn compile_block_expression(c: &mut Compiler, tree: &Tree) -> Option<()> {
OK
}
fn compile_argument(c: &mut Compiler, tree: &Tree) -> CR {
compile_expression(c, tree.nth_tree(0)?);
OK
}
fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) {
let tree = &c.semantics.tree()[t];
let cr = match tree.kind {
TreeKind::FunctionDecl => compile_function_declaration(c, tree, gen_value),
TreeKind::FunctionDecl => compile_function_declaration(c, t, tree, gen_value),
TreeKind::LetStatement => compile_let_statement(c, t, tree, gen_value),
TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value),
TreeKind::IfStatement => compile_if_statement(c, tree, gen_value),
_ => panic!("unsupported tree kind {:?}", tree.kind),
_ => ice!(c, t, "unsupported tree kind {:?}", tree.kind),
};
if matches!(cr, None) {
c.push(Instruction::Panic);
@ -424,20 +506,28 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b
let environment = c.semantics.environment_of(t);
let declaration = environment.bind(tree.nth_token(1)?)?;
let instruction = match declaration.location {
let Declaration::Variable {
location, index, ..
} = declaration
else {
ice!(c, t, "let cannot make a non-variable declaration")
};
let index = *index;
let instruction = match location {
Location::Local => {
if declaration.index >= c.function.locals {
c.function.locals = declaration.index + 1;
if index >= c.function.locals {
c.function.locals = index + 1;
}
Instruction::StoreLocal(declaration.index)
Instruction::StoreLocal(index)
}
Location::Module => {
if declaration.index >= c.module.globals {
c.module.globals = declaration.index + 1;
if index >= c.module.globals {
c.module.globals = index + 1;
}
Instruction::StoreModule(declaration.index)
Instruction::StoreModule(index)
}
_ => panic!("unsuitable location for let declaration"),
_ => ice!(c, t, "unsuitable location for let declaration"),
};
c.push(instruction);
if gen_value {
@ -447,30 +537,26 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b
OK
}
fn compile_function_declaration(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR {
let name = tree.nth_token(1)?;
let block = if tree
.nth_token(3)
.is_some_and(|t| t.kind == TokenKind::Arrow)
{
tree.nth_tree(4)?
} else {
tree.nth_tree(3)?
};
fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR {
// Only compile a given function once.
// TODO: This should actually be compiled on access! How is this going to work??
if !c.function_bindings.contains_key(&t) {
let name = tree.nth_token(1)?;
let arg_list = tree.nth_tree(2)?;
let arg_count = c.syntax[arg_list].children.len() - 2;
let block = tree.child_of_kind(c.syntax, TreeKind::Block)?;
let param_list = tree.child_tree_of_kind(c.syntax, TreeKind::ParamList)?;
let param_count = param_list.children.len() - 2;
let mut prev = Function::new(name.as_str(), arg_count);
std::mem::swap(&mut c.function, &mut prev);
let mut prev = Function::new(name.as_str(), param_count);
std::mem::swap(&mut c.function, &mut prev);
c.function_bindings
.insert(c.function.name.clone(), c.module.functions.len());
c.function_bindings.insert(t, c.module.functions.len());
compile_expression(c, block);
compile_expression(c, block);
std::mem::swap(&mut c.function, &mut prev);
c.module.functions.push(prev);
std::mem::swap(&mut c.function, &mut prev);
c.module.functions.push(prev);
}
if gen_value {
c.push(Instruction::PushNothing);