[fine] Assignments!

And new error capabilities!
This commit is contained in:
John Doty 2024-01-19 19:08:17 -08:00
parent 92cf840766
commit f20f5a5e03
12 changed files with 400 additions and 64 deletions

View file

@ -14,6 +14,10 @@ pub enum Instruction {
Panic,
BoolNot,
Call(usize),
CompareBool,
CompareFloat,
CompareString,
Discard,
FloatAdd,
FloatDivide,
@ -23,6 +27,8 @@ pub enum Instruction {
JumpFalse(usize),
JumpTrue(usize),
LoadArgument(usize),
LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index.
LoadFunction(usize),
LoadLocal(usize),
LoadModule(usize),
PushFalse,
@ -30,16 +36,12 @@ pub enum Instruction {
PushNothing,
PushString(usize),
PushTrue,
Return,
StoreArgument(usize),
StoreLocal(usize),
StoreModule(usize),
LoadFunction(usize),
LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index.
Call(usize),
Return,
StringAdd,
CompareBool,
CompareString,
CompareFloat,
Dup,
}
pub enum Export {
@ -372,30 +374,41 @@ fn compile_condition_expression(c: &mut Compiler, t: &Tree) -> CR {
OK
}
fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
fn compile_simple_binary_expression<T>(c: &mut Compiler, tr: &Tree, f: T) -> CR
where
T: FnOnce(&mut Compiler, &Type) -> Instruction,
{
compile_expression(c, tr.nth_tree(0)?);
let arg_tree = tr.nth_tree(2)?;
let arg_type = c.semantics.type_of(arg_tree);
compile_expression(c, arg_tree);
let inst = f(c, &arg_type);
c.push(inst);
OK
}
fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
match tr.nth_token(1)?.kind {
TokenKind::Plus => {
compile_expression(c, tr.nth_tree(2)?);
c.push(match c.semantics.type_of(t) {
Type::F64 => Instruction::FloatAdd,
Type::String => Instruction::StringAdd,
_ => Instruction::Panic,
});
}
TokenKind::Plus => compile_simple_binary_expression(c, tr, |_, t| match t {
Type::F64 => Instruction::FloatAdd,
Type::String => Instruction::StringAdd,
_ => Instruction::Panic,
}),
TokenKind::Minus => {
compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatSubtract);
compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatSubtract)
}
TokenKind::Star => {
compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatMultiply);
compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatMultiply)
}
TokenKind::Slash => {
compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::FloatDivide);
compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatDivide)
}
TokenKind::And => {
compile_expression(c, tr.nth_tree(0)?);
let jump_false_index = c.push(Instruction::JumpFalse(0));
c.push(Instruction::PushTrue);
@ -406,8 +419,10 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
compile_expression(c, tr.nth_tree(2)?);
c.patch(jump_end_index, |i| Instruction::Jump(i));
OK
}
TokenKind::Or => {
compile_expression(c, tr.nth_tree(0)?);
let jump_true_index = c.push(Instruction::JumpTrue(0));
c.push(Instruction::PushTrue);
@ -418,29 +433,70 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
compile_expression(c, tr.nth_tree(2)?);
c.patch(jump_end_index, |i| Instruction::Jump(i));
OK
}
TokenKind::EqualEqual => {
let arg_tree = tr.nth_tree(2)?;
let arg_type = c.semantics.type_of(arg_tree);
compile_simple_binary_expression(c, tr, |c, arg_type| {
if c.semantics.type_compat(&arg_type, &Type::Nothing) {
c.push(Instruction::Discard);
c.push(Instruction::Discard);
Instruction::PushTrue
} else {
match arg_type {
Type::F64 => Instruction::CompareFloat,
Type::String => Instruction::CompareString,
Type::Bool => Instruction::CompareBool, // ?
_ => Instruction::Panic,
}
}
})
}
TokenKind::Equal => {
compile_expression(c, tr.nth_tree(2)?);
c.push(Instruction::Dup);
if c.semantics.type_compat(&arg_type, &Type::Nothing) {
c.push(Instruction::Discard);
c.push(Instruction::Discard);
c.push(Instruction::PushTrue);
} else {
c.push(match arg_type {
Type::F64 => Instruction::CompareFloat,
Type::String => Instruction::CompareString,
Type::Bool => Instruction::CompareBool, // ?
_ => Instruction::Panic,
});
let lvalue = tr.nth_tree(0)?;
let ltree = &c.syntax[lvalue];
match ltree.kind {
TreeKind::Identifier => {
let ident = ltree.nth_token(0)?;
let environment = c.semantics.environment_of(lvalue);
let declaration = environment.bind(ident)?;
let instruction = match declaration {
Declaration::Variable {
location, index, ..
} => {
let index = *index;
match location {
Location::Argument => {
compiler_assert!(c, t, index < c.function.args);
Instruction::StoreArgument(index)
}
Location::Local => {
if index >= c.function.locals {
c.function.locals = index + 1;
}
Instruction::StoreLocal(index)
}
Location::Module => {
compiler_assert!(c, t, index < c.module.globals);
Instruction::StoreModule(index)
}
}
}
Declaration::ExternFunction { .. } => Instruction::Panic,
Declaration::Function { .. } => Instruction::Panic,
};
c.push(instruction);
}
_ => ice!(c, t, "Unsupported lvalue type"),
}
OK
}
_ => ice!(c, t, "Unsupported binary expression"),
}
OK
}
fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> Option<()> {

View file

@ -119,30 +119,32 @@ impl<'a> std::ops::IndexMut<TreeRef> for SyntaxTree<'a> {
#[derive(Debug, Eq, PartialEq)]
pub enum TreeKind {
Error,
File,
FunctionDecl,
ParamList,
Parameter,
TypeExpression,
Block,
LetStatement,
ReturnStatement,
ExpressionStatement,
LiteralExpression,
GroupingExpression,
UnaryExpression,
ConditionalExpression,
CallExpression,
ArgumentList,
Argument,
ArgumentList,
BinaryExpression,
IfStatement,
Block,
CallExpression,
ConditionalExpression,
ExpressionStatement,
File,
ForStatement,
FunctionDecl,
GroupingExpression,
Identifier,
ReturnType,
TypeParameterList,
TypeParameter,
IfStatement,
LetStatement,
ListConstructor,
ListConstructorElement,
LiteralExpression,
ParamList,
Parameter,
ReturnStatement,
ReturnType,
TypeExpression,
TypeParameter,
TypeParameterList,
UnaryExpression,
}
pub struct Tree<'a> {
@ -628,6 +630,7 @@ fn statement(p: &mut CParser) {
TokenKind::LeftBrace => block(p),
TokenKind::Let => statement_let(p),
TokenKind::Return => statement_return(p),
TokenKind::For => statement_for(p),
// NOTE: Technically 'if' is an expression, but `if` doesn't
// require a semicolon at the end if it's all by itself.
@ -677,6 +680,22 @@ fn statement_return(p: &mut CParser) {
p.end(m, TreeKind::ReturnStatement);
}
fn statement_for(p: &mut CParser) {
assert!(p.at(TokenKind::For));
let m = p.start();
p.expect(TokenKind::For, "expect a for to start a for loop");
p.expect(
TokenKind::Identifier,
"expected an identifier for the loop variable",
);
p.expect(TokenKind::In, "expect an 'in' after the loop variable");
expression(p);
block(p);
p.end(m, TreeKind::ForStatement);
}
fn statement_expression(p: &mut CParser) {
let m = p.start();

View file

@ -69,6 +69,12 @@ pub enum Type {
// everything's fine.
Unreachable,
// The type of an assignment expression. Assignments look like
// expressions but cannot be used in places that expect them (e.g., in
// `if` conditions), and so this is how we signal that. (We can, however,
// chain assignments, and so we flow the type of the assignment through.)
Assignment(Box<Type>),
// This is until generics are working
MagicPrintGarbage,
@ -108,6 +114,7 @@ impl fmt::Display for Type {
match self {
Error => write!(f, "<< INTERNAL ERROR >>"),
Unreachable => write!(f, "<< UNREACHABLE >>"),
Assignment(_) => write!(f, "assignment"),
Nothing => write!(f, "()"),
F64 => write!(f, "f64"),
String => write!(f, "string"),
@ -138,6 +145,8 @@ pub enum Location {
Argument,
Local,
Module,
// TODO: Member
// TODO: ArrayIndex
}
// TODO: Is `usize` what we want? Do we want e.g. dyn trait for invoke?
@ -300,6 +309,24 @@ fn set_logical_parents(
}
}
}
TreeKind::ForStatement => {
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, Some(t));
} else {
// If it's not the body then it must be the
// iterable and the iterable doesn't have the
// loop variable in scope.
set_logical_parents(parents, syntax_tree, *ct, parent);
}
}
}
}
}
_ => {
// By default, the parent for each child is current tree.
for child in &tree.children {
@ -424,6 +451,9 @@ impl<'a> Semantics<'a> {
self.report_error_span(tree.start_pos, tree.end_pos, error)
}
// pub fn lvalue_declaration(&self, t: TreeRef) -> Option<&Declaration> {
// }
fn gather_errors(&mut self, tree: TreeRef) {
let mut stack = vec![tree];
while let Some(tr) = stack.pop() {
@ -475,6 +505,8 @@ impl<'a> Semantics<'a> {
TreeKind::File => self.environment_of_file(parent, tree),
TreeKind::Block => self.environment_of_block(parent, tree),
TreeKind::ForStatement => self.environment_of_for(parent, tree),
// TODO: Blocks should introduce a local environment if required.
// Test with a type error in a block statement and a
// binding outside. You will need a new assertion type and
@ -606,7 +638,31 @@ impl<'a> Semantics<'a> {
EnvironmentRef::new(environment)
}
fn environment_of_for(&self, parent: EnvironmentRef, tree: &Tree) -> EnvironmentRef {
let Some(id) = tree.nth_token(1) else {
return parent;
};
let Some(enumerable) = tree.nth_tree(3) else {
return parent;
};
let item_type = match self.type_of(enumerable) {
Type::Error => Type::Error,
Type::List(x) => (&*x).clone(),
_ => {
self.report_error_tree_ref(enumerable, "this expression is not enumerable");
Type::Error
}
};
let mut environment = Environment::new(Some(parent), Location::Local);
environment.insert(id, item_type);
EnvironmentRef::new(environment)
}
pub fn type_compat(&self, a: &Type, b: &Type) -> bool {
// TODO: Convert this into "can become" or something.
// TODO: This is wrong; we because of numeric literals etc.
match (a, b) {
(Type::F64, Type::F64) => true,
@ -615,12 +671,20 @@ impl<'a> Semantics<'a> {
(Type::Unreachable, Type::Unreachable) => true,
(Type::Nothing, Type::Nothing) => true,
(Type::List(a), Type::List(b)) => self.type_compat(a, b),
(Type::Function(a_args, a_ret), Type::Function(b_args, b_ret)) => {
a_args.len() == b_args.len()
&& self.type_compat(a_ret, b_ret)
&& a_args
.iter()
.zip(b_args.iter())
.all(|(a, b)| self.type_compat(a, b))
}
// Avoid introducing more errors
(Type::Error, _) => true,
(_, Type::Error) => true,
(Type::List(a), Type::List(b)) => self.type_compat(a, b),
// TODO: Unification on type variables! :D
(_, _) => false,
}
@ -660,6 +724,7 @@ impl<'a> Semantics<'a> {
TreeKind::LetStatement => Some(Type::Nothing),
TreeKind::ReturnStatement => Some(Type::Unreachable),
TreeKind::ForStatement => Some(Type::Nothing),
TreeKind::ExpressionStatement => self.type_of_expression_statement(tree),
TreeKind::IfStatement => self.type_of_if_statement(tree),
TreeKind::Identifier => self.type_of_identifier(t, tree),
@ -720,7 +785,8 @@ impl<'a> Semantics<'a> {
fn type_of_binary(&self, tree: &Tree) -> Option<Type> {
assert_eq!(tree.kind, TreeKind::BinaryExpression);
let lhs = self.type_of(tree.nth_tree(0)?);
let left_tree = tree.nth_tree(0)?;
let lhs = self.type_of(left_tree);
let op = tree.nth_token(1)?;
let rhs = self.type_of(tree.nth_tree(2)?);
@ -760,6 +826,9 @@ impl<'a> Semantics<'a> {
(_, Type::Error, _) => Some(Type::Error),
(_, _, Type::Error) => Some(Type::Error),
// Assignments are fun.
(TokenKind::Equal, a, b) => self.type_of_assignment(left_tree, a, b, op),
// Missed the whole table, it must be an error.
(_, left_type, right_type) => {
self.report_error(
@ -771,6 +840,63 @@ impl<'a> Semantics<'a> {
}
}
fn type_of_assignment(
&self,
left_tree: TreeRef,
left_type: Type,
right_type: Type,
op: &Token,
) -> Option<Type> {
// Ensure the left tree is an lvalue
let environment = self.environment_of(left_tree);
let tree = &self.syntax_tree[left_tree];
let declaration = match tree.kind {
// TODO: Assign to member access or list access
TreeKind::Identifier => environment.bind(tree.nth_token(0)?),
_ => None,
};
match declaration {
Some(d) => match d {
Declaration::Variable { .. } => (),
Declaration::ExternFunction { .. } | Declaration::Function { .. } => {
self.report_error_tree_ref(
left_tree,
"cannot assign a new value to a function declaration",
);
return Some(Type::Error);
}
},
None => {
self.report_error_tree_ref(
left_tree,
"cannot assign a value to this expression, it is not a place you can store things",
);
return Some(Type::Error);
}
}
let left_type = match left_type {
Type::Assignment(x) => *x,
t => t,
};
let right_type = match right_type {
Type::Assignment(x) => *x,
t => t,
};
if left_type.is_error() || right_type.is_error() {
Some(Type::Error)
} else if self.type_compat(&left_type, &right_type) {
Some(Type::Assignment(Box::new(left_type)))
} else {
self.report_error(
op.start,
format!("cannot assign a value of type `{right_type}` to type `{left_type}`"),
);
Some(Type::Error)
}
}
fn type_of_type_expr(&self, tree: &Tree) -> Option<Type> {
assert_eq!(tree.kind, TreeKind::TypeExpression);
@ -1216,6 +1342,7 @@ pub fn check(s: &Semantics) {
TreeKind::ListConstructorElement => {
let _ = s.type_of(t);
}
TreeKind::ForStatement => check_for_statement(s, t),
}
}
}
@ -1295,6 +1422,10 @@ fn check_return_statement(s: &Semantics, tree: &Tree) {
// OK this one is a little bit messed up because it reaches *up*, sorry.
}
fn check_for_statement(s: &Semantics, t: TreeRef) {
let _ = s.environment_of(t);
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -47,6 +47,7 @@ pub enum TokenKind {
Fun,
If,
Import,
In,
Let,
Or,
Return,
@ -292,6 +293,9 @@ impl<'a> Tokens<'a> {
if ident == "import" {
return TokenKind::Import;
}
if ident == "in" {
return TokenKind::In;
}
}
'l' => {
if ident == "let" {

View file

@ -161,6 +161,15 @@ impl Frame {
}
}
fn store_argument(&mut self, i: usize, v: StackValue) -> Result<()> {
if i >= self.locals.len() {
Err(VMErrorCode::LocalOutOfRange(i))
} else {
self.args[i] = v;
Ok(())
}
}
fn pop_function(&mut self) -> Result<FuncValue> {
match self.pop_value()? {
StackValue::Function(f) => Ok(FuncValue::Function(f)),
@ -236,6 +245,11 @@ fn eval_one(
Instruction::Discard => {
f.pop_value()?;
}
Instruction::Dup => {
let v = f.pop_value()?;
f.push_value(v.clone());
f.push_value(v);
}
Instruction::FloatAdd => {
let x = f.pop_float()?;
let y = f.pop_float()?;
@ -300,6 +314,10 @@ fn eval_one(
Instruction::PushTrue => {
f.push_bool(true);
}
Instruction::StoreArgument(i) => {
let v = f.pop_value()?;
f.store_argument(i, v)?;
}
Instruction::StoreLocal(i) => {
let v = f.pop_value()?;
f.store_local(i, v)?;