[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::{ use crate::{
parser::{SyntaxTree, Tree, TreeKind, TreeRef}, parser::{SyntaxTree, Tree, TreeKind, TreeRef},
semantics::{Location, Semantics, Type}, semantics::{Declaration, Location, Semantics, Type},
tokens::TokenKind, 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. // TODO: If I were cool this would by actual bytecode.
// But I'm not cool. // But I'm not cool.
#[derive(Debug)] #[derive(Debug)]
@ -56,6 +31,10 @@ pub enum Instruction {
PushTrue, PushTrue,
StoreLocal(usize), StoreLocal(usize),
StoreModule(usize), StoreModule(usize),
LoadFunction(usize),
LoadExtern(usize),
LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index.
Call(usize),
} }
pub enum Export { pub enum Export {
@ -130,7 +109,9 @@ struct Compiler<'a> {
semantics: &'a Semantics<'a>, semantics: &'a Semantics<'a>,
syntax: &'a SyntaxTree<'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, module: Module,
function: Function, 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 { pub fn compile(semantics: &Semantics) -> Module {
let mut compiler = Compiler { let mut compiler = Compiler {
semantics, semantics,
@ -198,15 +235,14 @@ fn compile_expression(c: &mut Compiler, t: TreeRef) {
TreeKind::Error => None, TreeKind::Error => None,
TreeKind::LiteralExpression => compile_literal(c, t, tree), TreeKind::LiteralExpression => compile_literal(c, t, tree),
TreeKind::GroupingExpression => compile_grouping(c, 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::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::Identifier => compile_identifier_expression(c, t, tree),
TreeKind::CallExpression => todo!(), TreeKind::CallExpression => compile_call_expression(c, tree),
TreeKind::Block => compile_block_expression(c, tree), TreeKind::Block => compile_block_expression(c, tree),
_ => c TreeKind::Argument => compile_argument(c, tree),
.semantics _ => ice!(c, t, "{tree:?} is not an expression, cannot compile"),
.internal_compiler_error(Some(t), "tree is not an expression, cannot compile"),
}; };
if matches!(cr, None) { if matches!(cr, None) {
c.push(Instruction::Panic); c.push(Instruction::Panic);
@ -246,7 +282,7 @@ fn compile_literal(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
c.push(Instruction::PushString(index)) c.push(Instruction::PushString(index))
} }
Type::Error => c.push(Instruction::Panic), Type::Error => c.push(Instruction::Panic),
_ => panic!("unsupported literal type: {t:?}"), _ => ice!(c, t, "unsupported literal type: {t:?}"),
}; };
OK OK
} }
@ -256,10 +292,10 @@ fn compile_grouping(c: &mut Compiler, t: &Tree) -> CR {
OK OK
} }
fn compile_unary_operator(c: &mut Compiler, t: &Tree) -> CR { fn compile_unary_operator(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
compile_expression(c, t.nth_tree(1)?); compile_expression(c, tr.nth_tree(1)?);
let tok = t.nth_token(0)?; let tok = tr.nth_token(0)?;
match tok.kind { match tok.kind {
TokenKind::Minus => { TokenKind::Minus => {
c.push(Instruction::PushFloat(-1.0)); c.push(Instruction::PushFloat(-1.0));
@ -268,7 +304,7 @@ fn compile_unary_operator(c: &mut Compiler, t: &Tree) -> CR {
TokenKind::Bang => { TokenKind::Bang => {
c.push(Instruction::BoolNot); c.push(Instruction::BoolNot);
} }
_ => panic!("unsupported unary operator"), _ => ice!(c, t, "unsupported unary operator"),
} }
OK OK
} }
@ -294,23 +330,23 @@ fn compile_condition_expression(c: &mut Compiler, t: &Tree) -> CR {
OK OK
} }
fn compile_binary_expression(c: &mut Compiler, t: &Tree) -> CR { fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
compile_expression(c, t.nth_tree(0)?); compile_expression(c, tr.nth_tree(0)?);
match t.nth_token(1)?.kind { match tr.nth_token(1)?.kind {
TokenKind::Plus => { TokenKind::Plus => {
compile_expression(c, t.nth_tree(2)?); compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatAdd); c.push(Instruction::FloatAdd);
} }
TokenKind::Minus => { TokenKind::Minus => {
compile_expression(c, t.nth_tree(2)?); compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatSubtract); c.push(Instruction::FloatSubtract);
} }
TokenKind::Star => { TokenKind::Star => {
compile_expression(c, t.nth_tree(2)?); compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatMultiply); c.push(Instruction::FloatMultiply);
} }
TokenKind::Slash => { TokenKind::Slash => {
compile_expression(c, t.nth_tree(2)?); compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatDivide); c.push(Instruction::FloatDivide);
} }
TokenKind::And => { 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)); 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)); 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)); 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)); c.patch(jump_end_index, |i| Instruction::Jump(i));
} }
_ => panic!("Unsupported binary expression"), _ => ice!(c, t, "Unsupported binary expression"),
} }
OK 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 environment = c.semantics.environment_of(t);
let declaration = environment.bind(ident)?; let declaration = environment.bind(ident)?;
let instruction = match declaration.location { // TODO: Load function declaration. :P
Location::Local => { let instruction = match declaration {
if declaration.index >= c.function.locals { Declaration::Variable {
c.function.locals = declaration.index + 1; 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 => { Declaration::Function { declaration, .. } => {
compiler_assert!(c, t, declaration.index < c.function.args); let index = match c.function_bindings.get(declaration) {
Instruction::LoadArgument(declaration.index) Some(index) => *index,
} None => {
Location::Module => { let tree = &c.syntax[*declaration];
compiler_assert!(c, t, declaration.index < c.module.globals); compiler_assert_eq!(c, t, tree.kind, TreeKind::FunctionDecl);
Instruction::LoadModule(declaration.index) 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); c.push(instruction);
OK 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_is_brace = tree.nth_token(tree.children.len() - 1).is_some();
let last_index = tree.children.len() - if last_is_brace { 2 } else { 1 }; 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 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) { fn compile_statement(c: &mut Compiler, t: TreeRef, gen_value: bool) {
let tree = &c.semantics.tree()[t]; let tree = &c.semantics.tree()[t];
let cr = match tree.kind { 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::LetStatement => compile_let_statement(c, t, tree, gen_value),
TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value), TreeKind::ExpressionStatement => compile_expression_statement(c, tree, gen_value),
TreeKind::IfStatement => compile_if_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) { if matches!(cr, None) {
c.push(Instruction::Panic); 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 environment = c.semantics.environment_of(t);
let declaration = environment.bind(tree.nth_token(1)?)?; 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 => { Location::Local => {
if declaration.index >= c.function.locals { if index >= c.function.locals {
c.function.locals = declaration.index + 1; c.function.locals = index + 1;
} }
Instruction::StoreLocal(declaration.index) Instruction::StoreLocal(index)
} }
Location::Module => { Location::Module => {
if declaration.index >= c.module.globals { if index >= c.module.globals {
c.module.globals = declaration.index + 1; 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); c.push(instruction);
if gen_value { if gen_value {
@ -447,30 +537,26 @@ fn compile_let_statement(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: b
OK OK
} }
fn compile_function_declaration(c: &mut Compiler, tree: &Tree, gen_value: bool) -> CR { fn compile_function_declaration(c: &mut Compiler, t: TreeRef, tree: &Tree, gen_value: bool) -> CR {
let name = tree.nth_token(1)?; // Only compile a given function once.
let block = if tree // TODO: This should actually be compiled on access! How is this going to work??
.nth_token(3) if !c.function_bindings.contains_key(&t) {
.is_some_and(|t| t.kind == TokenKind::Arrow) let name = tree.nth_token(1)?;
{
tree.nth_tree(4)?
} else {
tree.nth_tree(3)?
};
let arg_list = tree.nth_tree(2)?; let block = tree.child_of_kind(c.syntax, TreeKind::Block)?;
let arg_count = c.syntax[arg_list].children.len() - 2; 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); let mut prev = Function::new(name.as_str(), param_count);
std::mem::swap(&mut c.function, &mut prev); std::mem::swap(&mut c.function, &mut prev);
c.function_bindings c.function_bindings.insert(t, c.module.functions.len());
.insert(c.function.name.clone(), c.module.functions.len());
compile_expression(c, block); compile_expression(c, block);
std::mem::swap(&mut c.function, &mut prev); std::mem::swap(&mut c.function, &mut prev);
c.module.functions.push(prev); c.module.functions.push(prev);
}
if gen_value { if gen_value {
c.push(Instruction::PushNothing); c.push(Instruction::PushNothing);

View file

@ -138,6 +138,7 @@ pub enum TreeKind {
BinaryExpression, BinaryExpression,
IfStatement, IfStatement,
Identifier, Identifier,
ReturnType,
} }
pub struct Tree<'a> { pub struct Tree<'a> {
@ -169,6 +170,31 @@ impl<'a> Tree<'a> {
.flatten() .flatten()
} }
pub fn child_trees<'b>(&'b self) -> impl Iterator<Item = TreeRef> + 'b {
self.children.iter().filter_map(|c| match c {
Child::Tree(t) => Some(*t),
_ => None,
})
}
pub fn child_of_kind(&self, s: &SyntaxTree, kind: TreeKind) -> Option<TreeRef> {
self.children
.iter()
.filter_map(|c| match c {
Child::Tree(t) => Some(*t),
_ => None,
})
.find(|c| s[*c].kind == kind)
}
pub fn child_tree_of_kind<'b>(
&'b self,
s: &'b SyntaxTree<'a>,
kind: TreeKind,
) -> Option<&'b Tree<'a>> {
self.child_of_kind(s, kind).map(|t| &s[t])
}
pub fn dump(&self, tree: &SyntaxTree<'a>, with_positions: bool, output: &mut String) { pub fn dump(&self, tree: &SyntaxTree<'a>, with_positions: bool, output: &mut String) {
let _ = write!(output, "{:?}", self.kind); let _ = write!(output, "{:?}", self.kind);
if with_positions { if with_positions {
@ -481,8 +507,8 @@ fn function(p: &mut CParser) {
if p.at(TokenKind::LeftParen) { if p.at(TokenKind::LeftParen) {
param_list(p); param_list(p);
} }
if p.eat(TokenKind::Arrow) { if p.at(TokenKind::Arrow) {
type_expr(p); return_type(p);
} }
if p.at(TokenKind::LeftBrace) { if p.at(TokenKind::LeftBrace) {
block(p); block(p);
@ -525,6 +551,19 @@ fn parameter(p: &mut CParser) {
p.end(m, TreeKind::Parameter); p.end(m, TreeKind::Parameter);
} }
fn return_type(p: &mut CParser) {
assert!(p.at(TokenKind::Arrow));
let m = p.start();
p.expect(
TokenKind::Arrow,
"function return type starts with an arrow",
);
type_expr(p);
p.end(m, TreeKind::ReturnType);
}
fn type_expr(p: &mut CParser) { fn type_expr(p: &mut CParser) {
let m = p.start(); let m = p.start();
// TODO: Other kinds of type expressions probably! // TODO: Other kinds of type expressions probably!

View file

@ -57,7 +57,7 @@ impl fmt::Display for Error {
} }
} }
#[derive(Copy, Clone)] #[derive(Clone)]
pub enum Type { pub enum Type {
// Signals a type error. If you receive this then you know that an error // Signals a type error. If you receive this then you know that an error
// has already been reported; if you produce this be sure to also note // has already been reported; if you produce this be sure to also note
@ -69,12 +69,17 @@ pub enum Type {
// everything's fine. // everything's fine.
Unreachable, Unreachable,
// This is until generics are working
MagicPrintGarbage,
Nothing, Nothing,
// TODO: Numeric literals should be implicitly convertable, unlike other // TODO: Numeric literals should be implicitly convertable, unlike other
// types. Maybe just "numeric literal" type? // types. Maybe just "numeric literal" type?
F64, F64,
String, String,
Bool, Bool,
Function(Vec<Box<Type>>, Box<Type>),
} }
impl Type { impl Type {
@ -119,50 +124,98 @@ impl fmt::Display for Type {
F64 => write!(f, "f64"), F64 => write!(f, "f64"),
String => write!(f, "string"), String => write!(f, "string"),
Bool => write!(f, "bool"), Bool => write!(f, "bool"),
MagicPrintGarbage => write!(f, "MagicPrintGarbage"),
Function(args, ret) => {
write!(f, "(")?;
let mut first = true;
for arg in args.iter() {
if !first {
write!(f, ", ")?;
}
write!(f, "{arg}")?;
first = false;
}
write!(f, ") -> {ret}")
}
} }
} }
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug)]
pub enum Location { pub enum Location {
Argument, Argument,
Local, Local,
Module, Module,
} }
pub struct Declaration { // TODO: Is `usize` what we want? Do we want e.g. dyn trait for invoke?
pub declaration_type: Type, #[derive(Clone, Debug)]
pub location: Location, pub struct ExternalFunctionId(usize);
pub index: usize,
impl ExternalFunctionId {
pub fn id(&self) -> usize {
self.0
}
}
pub enum Declaration {
Variable {
declaration_type: Type,
location: Location,
index: usize,
},
Function {
declaration_type: Type,
declaration: TreeRef, //?
},
ExternFunction {
declaration_type: Type,
id: ExternalFunctionId,
},
} }
pub struct Environment { pub struct Environment {
pub parent: Option<EnvironmentRef>, pub parent: Option<EnvironmentRef>,
pub location: Location, pub location: Location,
pub base_index: usize, pub next_index: usize,
pub declarations: HashMap<Box<str>, Declaration>, pub declarations: HashMap<Box<str>, Declaration>,
} }
impl Environment { impl Environment {
pub fn new(parent: Option<EnvironmentRef>, location: Location, base_index: usize) -> Self { pub fn new(parent: Option<EnvironmentRef>, location: Location) -> Self {
let parent_location = parent
.as_ref()
.map(|p| p.location)
.unwrap_or(Location::Module);
let base = parent.as_ref().map(|p| p.next_index).unwrap_or(0);
let next_index = match (parent_location, location) {
(_, Location::Argument) => 0,
(Location::Local, Location::Local) => base,
(_, Location::Local) => 0,
(Location::Module, Location::Module) => base,
(_, Location::Module) => panic!("What?"),
};
Environment { Environment {
parent, parent,
location, location,
base_index, next_index,
declarations: HashMap::new(), declarations: HashMap::new(),
} }
} }
pub fn insert(&mut self, token: &Token, t: Type) { pub fn insert(&mut self, token: &Token, t: Type) {
let index = self.base_index + self.declarations.len();
self.declarations.insert( self.declarations.insert(
token.as_str().into(), token.as_str().into(),
Declaration { Declaration::Variable {
declaration_type: t, declaration_type: t,
location: self.location, location: self.location,
index, index: self.next_index,
}, },
); );
self.next_index += 1;
} }
pub fn bind(&self, token: &Token) -> Option<&Declaration> { pub fn bind(&self, token: &Token) -> Option<&Declaration> {
@ -237,6 +290,24 @@ fn set_logical_parents(
} }
} }
} }
TreeKind::FunctionDecl => {
// In a function declaration, the logical parent of the body is
// the parameter list.
let param_list = tree.child_of_kind(syntax_tree, TreeKind::ParamList);
let body = tree.child_of_kind(syntax_tree, TreeKind::Block);
for child in &tree.children {
match child {
Child::Token(_) => (),
Child::Tree(ct) => {
if Some(*ct) == body {
set_logical_parents(parents, syntax_tree, *ct, param_list);
} else {
set_logical_parents(parents, syntax_tree, *ct, Some(t));
}
}
}
}
}
_ => { _ => {
// By default, the parent for each child is current tree. // By default, the parent for each child is current tree.
for child in &tree.children { for child in &tree.children {
@ -270,7 +341,7 @@ pub struct Semantics<'a> {
errors: RefCell<Vec<Error>>, errors: RefCell<Vec<Error>>,
types: RefCell<Vec<Incremental<Type>>>, types: RefCell<Vec<Incremental<Type>>>,
environments: RefCell<Vec<Incremental<EnvironmentRef>>>, environments: RefCell<Vec<Incremental<EnvironmentRef>>>,
empty_environment: EnvironmentRef, root_environment: EnvironmentRef,
} }
impl<'a> Semantics<'a> { impl<'a> Semantics<'a> {
@ -280,6 +351,15 @@ impl<'a> Semantics<'a> {
set_logical_parents(&mut logical_parents, tree, root, None); set_logical_parents(&mut logical_parents, tree, root, None);
} }
let mut root_environment = Environment::new(None, Location::Module);
root_environment.declarations.insert(
"print".into(),
Declaration::ExternFunction {
declaration_type: Type::MagicPrintGarbage,
id: ExternalFunctionId(0),
},
);
let mut semantics = Semantics { let mut semantics = Semantics {
syntax_tree: tree, syntax_tree: tree,
lines, lines,
@ -287,7 +367,7 @@ impl<'a> Semantics<'a> {
errors: RefCell::new(vec![]), errors: RefCell::new(vec![]),
types: RefCell::new(vec![Incremental::None; tree.len()]), types: RefCell::new(vec![Incremental::None; tree.len()]),
environments: RefCell::new(vec![Incremental::None; tree.len()]), environments: RefCell::new(vec![Incremental::None; tree.len()]),
empty_environment: EnvironmentRef::new(Environment::new(None, Location::Module, 0)), root_environment: EnvironmentRef::new(root_environment),
}; };
// NOTE: We ensure all the known errors are reported before we move // NOTE: We ensure all the known errors are reported before we move
@ -379,7 +459,7 @@ impl<'a> Semantics<'a> {
Incremental::Complete(e) => return e.clone(), Incremental::Complete(e) => return e.clone(),
Incremental::InProgress => { Incremental::InProgress => {
// NOTE: Set the state so the ICE doesn't loop on itself. // NOTE: Set the state so the ICE doesn't loop on itself.
*state = Incremental::Complete(self.empty_environment.clone()); *state = Incremental::Complete(self.root_environment.clone());
drop(borrow); drop(borrow);
//eprintln!("environment_of circular => {t:?}"); //eprintln!("environment_of circular => {t:?}");
@ -394,12 +474,14 @@ impl<'a> Semantics<'a> {
let parent = match self.logical_parents[t.index()] { let parent = match self.logical_parents[t.index()] {
Some(t) => self.environment_of(t), Some(t) => self.environment_of(t),
None => self.empty_environment.clone(), None => self.root_environment.clone(),
}; };
let result = match tree.kind { let result = match tree.kind {
TreeKind::LetStatement => self.environment_of_let(parent, tree), TreeKind::LetStatement => self.environment_of_let(parent, tree),
TreeKind::FunctionDecl => self.environment_of_func(parent, tree), TreeKind::ParamList => self.environment_of_paramlist(parent, tree),
TreeKind::File => self.environment_of_file(parent, tree),
TreeKind::Block => self.environment_of_block(parent, tree),
// TODO: Blocks should introduce a local environment if required. // TODO: Blocks should introduce a local environment if required.
// Test with a type error in a block statement and a // Test with a type error in a block statement and a
@ -426,6 +508,62 @@ impl<'a> Semantics<'a> {
result result
} }
fn environment_of_block(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
let mut environment = Environment::new(Some(parent), Location::Local);
for child in tree.children.iter() {
match child {
Child::Tree(t) => {
let ct = &self.syntax_tree[*t];
if ct.kind == TreeKind::FunctionDecl {
// TODO: Should I have accessors for function decls?
let Some(name) = ct.nth_token(1) else {
continue;
};
environment.declarations.insert(
name.as_str().into(),
Declaration::Function {
declaration_type: self.type_of(*t),
declaration: *t,
},
);
}
}
_ => {}
}
}
EnvironmentRef::new(environment)
}
fn environment_of_file(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
let mut environment = Environment::new(Some(parent), Location::Module);
for child in tree.children.iter() {
match child {
Child::Tree(t) => {
let ct = &self.syntax_tree[*t];
if ct.kind == TreeKind::FunctionDecl {
// TODO: Should I have accessors for function decls?
let Some(name) = ct.nth_token(1) else {
continue;
};
environment.declarations.insert(
name.as_str().into(),
Declaration::Function {
declaration_type: self.type_of(*t),
declaration: *t,
},
);
}
}
_ => {}
}
}
EnvironmentRef::new(environment)
}
fn environment_of_let(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef { fn environment_of_let(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
let Some(name) = tree.nth_token(1) else { let Some(name) = tree.nth_token(1) else {
return parent; // Error is already reported? return parent; // Error is already reported?
@ -440,35 +578,23 @@ impl<'a> Semantics<'a> {
None => Type::Error, None => Type::Error,
}; };
let (location, base_index) = match parent.location { let location = match parent.location {
Location::Local => ( Location::Local => Location::Local,
Location::Local, Location::Module => Location::Module,
parent.base_index + parent.declarations.len(), Location::Argument => Location::Local,
),
Location::Module => (
Location::Module,
parent.base_index + parent.declarations.len(),
),
Location::Argument => (Location::Local, 0),
}; };
let mut environment = Environment::new(Some(parent), location, base_index); let mut environment = Environment::new(Some(parent), location);
environment.insert(name, declaration_type); environment.insert(name, declaration_type);
EnvironmentRef::new(environment) EnvironmentRef::new(environment)
} }
fn environment_of_func(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef { fn environment_of_paramlist(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
let Some(param_list) = tree.nth_tree(2) else { assert!(tree.kind == TreeKind::ParamList);
return parent; // SE
};
let param_list = &self.syntax_tree[param_list];
if param_list.kind != TreeKind::ParamList {
return parent; // SE
}
let mut environment = Environment::new(Some(parent), Location::Argument, 0); let mut environment = Environment::new(Some(parent), Location::Argument);
for child in param_list.children.iter() { for child in tree.children.iter() {
let Child::Tree(ct) = child else { let Child::Tree(ct) = child else {
continue; continue;
}; };
@ -529,12 +655,18 @@ impl<'a> Semantics<'a> {
TreeKind::ExpressionStatement => self.type_of_expression_statement(tree), TreeKind::ExpressionStatement => self.type_of_expression_statement(tree),
TreeKind::Identifier => self.type_of_identifier(t, tree), TreeKind::Identifier => self.type_of_identifier(t, tree),
TreeKind::FunctionDecl => self.type_of_function_decl(tree),
_ => self.internal_compiler_error(Some(t), "asking for a nonsense type"), _ => self.internal_compiler_error(Some(t), "asking for a nonsense type"),
}; };
// NOTE: These return `None` if they encounter some problem. // NOTE: These return `None` if they encounter some problem.
let result = result.unwrap_or(Type::Error); let result = result.unwrap_or(Type::Error);
if result.is_error() {
eprintln!("OH NO AN ERROR AT {}: {:?}", t.index(), tree);
}
self.types.borrow_mut()[t.index()] = Incremental::Complete(result.clone()); self.types.borrow_mut()[t.index()] = Incremental::Complete(result.clone());
result result
} }
@ -635,7 +767,10 @@ impl<'a> Semantics<'a> {
"string" => Some(Type::String), "string" => Some(Type::String),
"bool" => Some(Type::Bool), "bool" => Some(Type::Bool),
"()" => Some(Type::Nothing), "()" => Some(Type::Nothing),
_ => Some(Type::Error), _ => {
self.report_error_tree(tree, "Unrecognized type");
Some(Type::Error)
}
} }
} }
@ -748,12 +883,64 @@ impl<'a> Semantics<'a> {
fn type_of_call(&self, tree: &Tree) -> Option<Type> { fn type_of_call(&self, tree: &Tree) -> Option<Type> {
assert_eq!(tree.kind, TreeKind::CallExpression); assert_eq!(tree.kind, TreeKind::CallExpression);
Some(Type::Error)
let f_ref = tree.nth_tree(0)?;
let f = self.type_of(f_ref);
let arg_list = &self.syntax_tree[tree.nth_tree(1)?];
let arg_types: Vec<_> = arg_list
.children
.iter()
.filter_map(|c| match c {
Child::Tree(t) => Some((*t, self.type_of(*t))),
_ => None,
})
.collect();
if f.is_error() || arg_types.iter().any(|(_, t)| t.is_error()) {
return Some(Type::Error);
}
match f {
Type::Function(params, ret) => {
let mut any_errors = false;
if params.len() != arg_types.len() {
// TODO: Augment with function name if known
self.report_error_tree(tree, format!("expected {} parameters", params.len()));
any_errors = true;
}
for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() {
if !a.compatible_with(p) {
self.report_error_tree_ref(
*t,
format!(
"parameter {i} has an incompatible type: expected {} but got {}",
p, a
),
);
any_errors = true;
}
}
if any_errors {
return Some(Type::Error);
}
Some(*ret.clone())
}
_ => {
self.report_error_tree_ref(f_ref, format!("expected a function type, got: {f}"));
Some(Type::Error)
}
}
} }
fn type_of_argument(&self, tree: &Tree) -> Option<Type> { fn type_of_argument(&self, tree: &Tree) -> Option<Type> {
assert_eq!(tree.kind, TreeKind::Argument); assert_eq!(tree.kind, TreeKind::Argument);
Some(Type::Error)
let result = self.type_of(tree.nth_tree(0)?);
Some(result)
} }
fn type_of_expression_statement(&self, tree: &Tree) -> Option<Type> { fn type_of_expression_statement(&self, tree: &Tree) -> Option<Type> {
@ -785,13 +972,42 @@ impl<'a> Semantics<'a> {
let id = tree.nth_token(0)?; let id = tree.nth_token(0)?;
let environment = self.environment_of(t); let environment = self.environment_of(t);
if let Some(declaration) = environment.bind(id) { if let Some(declaration) = environment.bind(id) {
return Some(declaration.declaration_type); return Some(match declaration {
Declaration::Variable {
declaration_type, ..
} => declaration_type.clone(),
Declaration::Function {
declaration_type, ..
} => declaration_type.clone(),
Declaration::ExternFunction {
declaration_type, ..
} => declaration_type.clone(),
});
} }
self.report_error_tree(tree, format!("cannot find value {id} here")); self.report_error_tree(tree, format!("cannot find value {id} here"));
Some(Type::Error) Some(Type::Error)
} }
fn type_of_function_decl(&self, tree: &Tree) -> Option<Type> {
let param_list = tree.child_tree_of_kind(self.syntax_tree, TreeKind::ParamList)?;
let mut parameter_types = Vec::new();
for p in param_list.child_trees() {
let param = &self.syntax_tree[p];
// TODO: Missing type expression means it's a generic function.
let parameter_type = param.child_of_kind(self.syntax_tree, TreeKind::TypeExpression)?;
parameter_types.push(Box::new(self.type_of(parameter_type)));
}
let return_type = match tree.child_tree_of_kind(self.syntax_tree, TreeKind::ReturnType) {
Some(t) => self.type_of(t.child_of_kind(self.syntax_tree, TreeKind::TypeExpression)?),
None => Type::Nothing,
};
let return_type = Box::new(return_type);
Some(Type::Function(parameter_types, return_type))
}
pub fn dump_compiler_state(&self, tr: Option<TreeRef>) { pub fn dump_compiler_state(&self, tr: Option<TreeRef>) {
eprintln!("Parsed the tree as:"); eprintln!("Parsed the tree as:");
eprintln!("\n{}", self.syntax_tree.dump(true)); eprintln!("\n{}", self.syntax_tree.dump(true));
@ -826,7 +1042,28 @@ impl<'a> Semantics<'a> {
let mut environment = Some(self.environment_of(tr)); let mut environment = Some(self.environment_of(tr));
while let Some(env) = environment { while let Some(env) = environment {
for (k, v) in env.declarations.iter() { for (k, v) in env.declarations.iter() {
eprintln!(" {k}: {:?}", v.declaration_type); eprint!(" {k}: ");
match v {
Declaration::Variable {
declaration_type,
location,
index,
} => {
eprintln!("{declaration_type:?} (variable {location:?} {index})");
}
Declaration::Function {
declaration_type,
declaration,
} => {
eprintln!("{declaration_type:?} (function {declaration:?})");
}
Declaration::ExternFunction {
declaration_type,
id,
} => {
eprintln!("{declaration_type:?} (extern {id:?})");
}
};
} }
environment = env.parent.clone(); environment = env.parent.clone();
} }
@ -872,9 +1109,14 @@ pub fn check(s: &Semantics) {
let _ = s.type_of(t); let _ = s.type_of(t);
} }
TreeKind::ArgumentList => {} TreeKind::ArgumentList => {}
TreeKind::Argument => {} TreeKind::Argument => {
let _ = s.type_of(t);
}
TreeKind::IfStatement => {} TreeKind::IfStatement => {}
TreeKind::Identifier => {} TreeKind::Identifier => {
let _ = s.type_of(t);
}
TreeKind::ReturnType => {}
} }
} }
} }

View file

@ -0,0 +1,6 @@
fun foo(x: f64) {}
x;
// @type-error: 19 cannot find value x here
// Used to have a bug where statements after a function declaration would
// bind to the declaration's arguments, whoops.

View file

@ -0,0 +1,5 @@
fun foo(x: f64) {}
let x = foo("hello");
x;
// @type-error: 41 parameter 0 has an incompatible type: expected f64 but got string

View file

@ -1,8 +1,9 @@
let x = 23; let x = 23;
let y = x * 2; let y = x * 2;
y; let z = print(y);
z;
// @type: 27 f64 // @type: 41 f64
// @concrete: // @concrete:
// | File // | File
// | LetStatement // | LetStatement
@ -23,15 +24,29 @@ y;
// | LiteralExpression // | LiteralExpression
// | Number:'"2"' // | Number:'"2"'
// | Semicolon:'";"' // | Semicolon:'";"'
// | LetStatement
// | Let:'"let"'
// | Identifier:'"z"'
// | Equal:'"="'
// | CallExpression
// | Identifier
// | Identifier:'"print"'
// | ArgumentList
// | LeftParen:'"("'
// | Argument
// | Identifier
// | Identifier:'"y"'
// | RightParen:'")"'
// | Semicolon:'";"'
// | ExpressionStatement // | ExpressionStatement
// | Identifier // | Identifier
// | Identifier:'"y"' // | Identifier:'"z"'
// | Semicolon:'";"' // | Semicolon:'";"'
// | // |
// @compiles-to: // @compiles-to:
// | function << module >> (0 args, 0 locals): // | function << module >> (0 args, 0 locals):
// | strings (0): // | strings (0):
// | code (8): // | code (12):
// | 0: PushFloat(23.0) // | 0: PushFloat(23.0)
// | 1: StoreModule(0) // | 1: StoreModule(0)
// | 2: LoadModule(0) // | 2: LoadModule(0)
@ -39,5 +54,9 @@ y;
// | 4: FloatMultiply // | 4: FloatMultiply
// | 5: StoreModule(1) // | 5: StoreModule(1)
// | 6: LoadModule(1) // | 6: LoadModule(1)
// | 7: Discard // | 7: LoadExternFunction(0)
// | 8: Call(1)
// | 9: StoreModule(2)
// | 10: LoadModule(2)
// | 11: Discard
// | // |