[fine] Assignments!
And new error capabilities!
This commit is contained in:
parent
92cf840766
commit
f20f5a5e03
12 changed files with 400 additions and 64 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
|
@ -675,6 +675,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.47",
|
"syn 2.0.47",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
@ -1786,9 +1787,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.75"
|
version = "1.0.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
|
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
5
fine/Cargo.lock
generated
5
fine/Cargo.lock
generated
|
|
@ -15,6 +15,7 @@ dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
@ -48,9 +49,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.75"
|
version = "1.0.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
|
checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ pretty_assertions = "1.4.0"
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
prettyplease = "0.2.16"
|
prettyplease = "0.2.16"
|
||||||
|
proc-macro2 = "1.0.76"
|
||||||
quote = "1.0.35"
|
quote = "1.0.35"
|
||||||
syn = "2.0.47"
|
syn = "2.0.47"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,25 @@
|
||||||
use quote::{format_ident, quote};
|
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream};
|
||||||
|
use quote::{format_ident, quote, TokenStreamExt};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
struct ExpectedErrors(Vec<String>);
|
||||||
|
|
||||||
|
impl quote::ToTokens for ExpectedErrors {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let mut inner = TokenStream::new();
|
||||||
|
for err in self.0.iter() {
|
||||||
|
inner.append(Literal::string(err));
|
||||||
|
inner.append(Punct::new(',', Spacing::Alone));
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.append(Ident::new("vec", Span::call_site()));
|
||||||
|
tokens.append(Punct::new('!', Spacing::Joint));
|
||||||
|
tokens.append(Group::new(Delimiter::Parenthesis, inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn generate_test_for_file(path: PathBuf) -> String {
|
fn generate_test_for_file(path: PathBuf) -> String {
|
||||||
let contents = fs::read_to_string(&path)
|
let contents = fs::read_to_string(&path)
|
||||||
.expect("Unable to read input")
|
.expect("Unable to read input")
|
||||||
|
|
@ -92,6 +109,21 @@ fn generate_test_for_file(path: PathBuf) -> String {
|
||||||
assertions.push(quote! {
|
assertions.push(quote! {
|
||||||
crate::assert_check_error(&_tree, &_lines, #expected);
|
crate::assert_check_error(&_tree, &_lines, #expected);
|
||||||
});
|
});
|
||||||
|
} else if line == "@expect-errors:" {
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
let line = match line.strip_prefix("// | ") {
|
||||||
|
Some(line) => line,
|
||||||
|
None => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
errors.push(line.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let errors = ExpectedErrors(errors);
|
||||||
|
assertions.push(quote! {
|
||||||
|
crate::assert_errors(&_tree, &_lines, #errors);
|
||||||
|
});
|
||||||
} else if line.starts_with("@") {
|
} else if line.starts_with("@") {
|
||||||
panic!("Test file {display_path} has unknown directive: {line}");
|
panic!("Test file {display_path} has unknown directive: {line}");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,10 @@ pub enum Instruction {
|
||||||
Panic,
|
Panic,
|
||||||
|
|
||||||
BoolNot,
|
BoolNot,
|
||||||
|
Call(usize),
|
||||||
|
CompareBool,
|
||||||
|
CompareFloat,
|
||||||
|
CompareString,
|
||||||
Discard,
|
Discard,
|
||||||
FloatAdd,
|
FloatAdd,
|
||||||
FloatDivide,
|
FloatDivide,
|
||||||
|
|
@ -23,6 +27,8 @@ pub enum Instruction {
|
||||||
JumpFalse(usize),
|
JumpFalse(usize),
|
||||||
JumpTrue(usize),
|
JumpTrue(usize),
|
||||||
LoadArgument(usize),
|
LoadArgument(usize),
|
||||||
|
LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index.
|
||||||
|
LoadFunction(usize),
|
||||||
LoadLocal(usize),
|
LoadLocal(usize),
|
||||||
LoadModule(usize),
|
LoadModule(usize),
|
||||||
PushFalse,
|
PushFalse,
|
||||||
|
|
@ -30,16 +36,12 @@ pub enum Instruction {
|
||||||
PushNothing,
|
PushNothing,
|
||||||
PushString(usize),
|
PushString(usize),
|
||||||
PushTrue,
|
PushTrue,
|
||||||
|
Return,
|
||||||
|
StoreArgument(usize),
|
||||||
StoreLocal(usize),
|
StoreLocal(usize),
|
||||||
StoreModule(usize),
|
StoreModule(usize),
|
||||||
LoadFunction(usize),
|
|
||||||
LoadExternFunction(usize), // NOTE: FUNKY, might want to indirect this index.
|
|
||||||
Call(usize),
|
|
||||||
Return,
|
|
||||||
StringAdd,
|
StringAdd,
|
||||||
CompareBool,
|
Dup,
|
||||||
CompareString,
|
|
||||||
CompareFloat,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum Export {
|
pub enum Export {
|
||||||
|
|
@ -372,30 +374,41 @@ fn compile_condition_expression(c: &mut Compiler, t: &Tree) -> CR {
|
||||||
OK
|
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)?);
|
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 {
|
match tr.nth_token(1)?.kind {
|
||||||
TokenKind::Plus => {
|
TokenKind::Plus => compile_simple_binary_expression(c, tr, |_, t| match t {
|
||||||
compile_expression(c, tr.nth_tree(2)?);
|
Type::F64 => Instruction::FloatAdd,
|
||||||
c.push(match c.semantics.type_of(t) {
|
Type::String => Instruction::StringAdd,
|
||||||
Type::F64 => Instruction::FloatAdd,
|
_ => Instruction::Panic,
|
||||||
Type::String => Instruction::StringAdd,
|
}),
|
||||||
_ => Instruction::Panic,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
TokenKind::Minus => {
|
TokenKind::Minus => {
|
||||||
compile_expression(c, tr.nth_tree(2)?);
|
compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatSubtract)
|
||||||
c.push(Instruction::FloatSubtract);
|
|
||||||
}
|
}
|
||||||
TokenKind::Star => {
|
TokenKind::Star => {
|
||||||
compile_expression(c, tr.nth_tree(2)?);
|
compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatMultiply)
|
||||||
c.push(Instruction::FloatMultiply);
|
|
||||||
}
|
}
|
||||||
TokenKind::Slash => {
|
TokenKind::Slash => {
|
||||||
compile_expression(c, tr.nth_tree(2)?);
|
compile_simple_binary_expression(c, tr, |_, _| Instruction::FloatDivide)
|
||||||
c.push(Instruction::FloatDivide);
|
|
||||||
}
|
}
|
||||||
TokenKind::And => {
|
TokenKind::And => {
|
||||||
|
compile_expression(c, tr.nth_tree(0)?);
|
||||||
let jump_false_index = c.push(Instruction::JumpFalse(0));
|
let jump_false_index = c.push(Instruction::JumpFalse(0));
|
||||||
|
|
||||||
c.push(Instruction::PushTrue);
|
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)?);
|
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));
|
||||||
|
OK
|
||||||
}
|
}
|
||||||
TokenKind::Or => {
|
TokenKind::Or => {
|
||||||
|
compile_expression(c, tr.nth_tree(0)?);
|
||||||
let jump_true_index = c.push(Instruction::JumpTrue(0));
|
let jump_true_index = c.push(Instruction::JumpTrue(0));
|
||||||
|
|
||||||
c.push(Instruction::PushTrue);
|
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)?);
|
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));
|
||||||
|
OK
|
||||||
}
|
}
|
||||||
TokenKind::EqualEqual => {
|
TokenKind::EqualEqual => {
|
||||||
let arg_tree = tr.nth_tree(2)?;
|
compile_simple_binary_expression(c, tr, |c, arg_type| {
|
||||||
let arg_type = c.semantics.type_of(arg_tree);
|
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)?);
|
compile_expression(c, tr.nth_tree(2)?);
|
||||||
|
c.push(Instruction::Dup);
|
||||||
|
|
||||||
if c.semantics.type_compat(&arg_type, &Type::Nothing) {
|
let lvalue = tr.nth_tree(0)?;
|
||||||
c.push(Instruction::Discard);
|
let ltree = &c.syntax[lvalue];
|
||||||
c.push(Instruction::Discard);
|
match ltree.kind {
|
||||||
c.push(Instruction::PushTrue);
|
TreeKind::Identifier => {
|
||||||
} else {
|
let ident = ltree.nth_token(0)?;
|
||||||
c.push(match arg_type {
|
let environment = c.semantics.environment_of(lvalue);
|
||||||
Type::F64 => Instruction::CompareFloat,
|
let declaration = environment.bind(ident)?;
|
||||||
Type::String => Instruction::CompareString,
|
|
||||||
Type::Bool => Instruction::CompareBool, // ?
|
let instruction = match declaration {
|
||||||
_ => Instruction::Panic,
|
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"),
|
_ => ice!(c, t, "Unsupported binary expression"),
|
||||||
}
|
}
|
||||||
OK
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> Option<()> {
|
fn compile_identifier_expression(c: &mut Compiler, t: TreeRef, tree: &Tree) -> Option<()> {
|
||||||
|
|
|
||||||
|
|
@ -119,30 +119,32 @@ impl<'a> std::ops::IndexMut<TreeRef> for SyntaxTree<'a> {
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum TreeKind {
|
pub enum TreeKind {
|
||||||
Error,
|
Error,
|
||||||
File,
|
|
||||||
FunctionDecl,
|
|
||||||
ParamList,
|
|
||||||
Parameter,
|
|
||||||
TypeExpression,
|
|
||||||
Block,
|
|
||||||
LetStatement,
|
|
||||||
ReturnStatement,
|
|
||||||
ExpressionStatement,
|
|
||||||
LiteralExpression,
|
|
||||||
GroupingExpression,
|
|
||||||
UnaryExpression,
|
|
||||||
ConditionalExpression,
|
|
||||||
CallExpression,
|
|
||||||
ArgumentList,
|
|
||||||
Argument,
|
Argument,
|
||||||
|
ArgumentList,
|
||||||
BinaryExpression,
|
BinaryExpression,
|
||||||
IfStatement,
|
Block,
|
||||||
|
CallExpression,
|
||||||
|
ConditionalExpression,
|
||||||
|
ExpressionStatement,
|
||||||
|
File,
|
||||||
|
ForStatement,
|
||||||
|
FunctionDecl,
|
||||||
|
GroupingExpression,
|
||||||
Identifier,
|
Identifier,
|
||||||
ReturnType,
|
IfStatement,
|
||||||
TypeParameterList,
|
LetStatement,
|
||||||
TypeParameter,
|
|
||||||
ListConstructor,
|
ListConstructor,
|
||||||
ListConstructorElement,
|
ListConstructorElement,
|
||||||
|
LiteralExpression,
|
||||||
|
ParamList,
|
||||||
|
Parameter,
|
||||||
|
ReturnStatement,
|
||||||
|
ReturnType,
|
||||||
|
TypeExpression,
|
||||||
|
TypeParameter,
|
||||||
|
TypeParameterList,
|
||||||
|
UnaryExpression,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Tree<'a> {
|
pub struct Tree<'a> {
|
||||||
|
|
@ -628,6 +630,7 @@ fn statement(p: &mut CParser) {
|
||||||
TokenKind::LeftBrace => block(p),
|
TokenKind::LeftBrace => block(p),
|
||||||
TokenKind::Let => statement_let(p),
|
TokenKind::Let => statement_let(p),
|
||||||
TokenKind::Return => statement_return(p),
|
TokenKind::Return => statement_return(p),
|
||||||
|
TokenKind::For => statement_for(p),
|
||||||
|
|
||||||
// NOTE: Technically 'if' is an expression, but `if` doesn't
|
// NOTE: Technically 'if' is an expression, but `if` doesn't
|
||||||
// require a semicolon at the end if it's all by itself.
|
// 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);
|
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) {
|
fn statement_expression(p: &mut CParser) {
|
||||||
let m = p.start();
|
let m = p.start();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,12 @@ pub enum Type {
|
||||||
// everything's fine.
|
// everything's fine.
|
||||||
Unreachable,
|
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
|
// This is until generics are working
|
||||||
MagicPrintGarbage,
|
MagicPrintGarbage,
|
||||||
|
|
||||||
|
|
@ -108,6 +114,7 @@ impl fmt::Display for Type {
|
||||||
match self {
|
match self {
|
||||||
Error => write!(f, "<< INTERNAL ERROR >>"),
|
Error => write!(f, "<< INTERNAL ERROR >>"),
|
||||||
Unreachable => write!(f, "<< UNREACHABLE >>"),
|
Unreachable => write!(f, "<< UNREACHABLE >>"),
|
||||||
|
Assignment(_) => write!(f, "assignment"),
|
||||||
Nothing => write!(f, "()"),
|
Nothing => write!(f, "()"),
|
||||||
F64 => write!(f, "f64"),
|
F64 => write!(f, "f64"),
|
||||||
String => write!(f, "string"),
|
String => write!(f, "string"),
|
||||||
|
|
@ -138,6 +145,8 @@ pub enum Location {
|
||||||
Argument,
|
Argument,
|
||||||
Local,
|
Local,
|
||||||
Module,
|
Module,
|
||||||
|
// TODO: Member
|
||||||
|
// TODO: ArrayIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Is `usize` what we want? Do we want e.g. dyn trait for invoke?
|
// 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.
|
// By default, the parent for each child is current tree.
|
||||||
for child in &tree.children {
|
for child in &tree.children {
|
||||||
|
|
@ -424,6 +451,9 @@ impl<'a> Semantics<'a> {
|
||||||
self.report_error_span(tree.start_pos, tree.end_pos, error)
|
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) {
|
fn gather_errors(&mut self, tree: TreeRef) {
|
||||||
let mut stack = vec![tree];
|
let mut stack = vec![tree];
|
||||||
while let Some(tr) = stack.pop() {
|
while let Some(tr) = stack.pop() {
|
||||||
|
|
@ -475,6 +505,8 @@ impl<'a> Semantics<'a> {
|
||||||
TreeKind::File => self.environment_of_file(parent, tree),
|
TreeKind::File => self.environment_of_file(parent, tree),
|
||||||
TreeKind::Block => self.environment_of_block(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.
|
// 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
|
||||||
// binding outside. You will need a new assertion type and
|
// binding outside. You will need a new assertion type and
|
||||||
|
|
@ -606,7 +638,31 @@ impl<'a> Semantics<'a> {
|
||||||
EnvironmentRef::new(environment)
|
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 {
|
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.
|
// TODO: This is wrong; we because of numeric literals etc.
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(Type::F64, Type::F64) => true,
|
(Type::F64, Type::F64) => true,
|
||||||
|
|
@ -615,12 +671,20 @@ impl<'a> Semantics<'a> {
|
||||||
(Type::Unreachable, Type::Unreachable) => true,
|
(Type::Unreachable, Type::Unreachable) => true,
|
||||||
(Type::Nothing, Type::Nothing) => 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
|
// Avoid introducing more errors
|
||||||
(Type::Error, _) => true,
|
(Type::Error, _) => true,
|
||||||
(_, Type::Error) => true,
|
(_, Type::Error) => true,
|
||||||
|
|
||||||
(Type::List(a), Type::List(b)) => self.type_compat(a, b),
|
|
||||||
|
|
||||||
// TODO: Unification on type variables! :D
|
// TODO: Unification on type variables! :D
|
||||||
(_, _) => false,
|
(_, _) => false,
|
||||||
}
|
}
|
||||||
|
|
@ -660,6 +724,7 @@ impl<'a> Semantics<'a> {
|
||||||
|
|
||||||
TreeKind::LetStatement => Some(Type::Nothing),
|
TreeKind::LetStatement => Some(Type::Nothing),
|
||||||
TreeKind::ReturnStatement => Some(Type::Unreachable),
|
TreeKind::ReturnStatement => Some(Type::Unreachable),
|
||||||
|
TreeKind::ForStatement => Some(Type::Nothing),
|
||||||
TreeKind::ExpressionStatement => self.type_of_expression_statement(tree),
|
TreeKind::ExpressionStatement => self.type_of_expression_statement(tree),
|
||||||
TreeKind::IfStatement => self.type_of_if_statement(tree),
|
TreeKind::IfStatement => self.type_of_if_statement(tree),
|
||||||
TreeKind::Identifier => self.type_of_identifier(t, 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> {
|
fn type_of_binary(&self, tree: &Tree) -> Option<Type> {
|
||||||
assert_eq!(tree.kind, TreeKind::BinaryExpression);
|
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 op = tree.nth_token(1)?;
|
||||||
let rhs = self.type_of(tree.nth_tree(2)?);
|
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),
|
||||||
(_, _, 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.
|
// Missed the whole table, it must be an error.
|
||||||
(_, left_type, right_type) => {
|
(_, left_type, right_type) => {
|
||||||
self.report_error(
|
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> {
|
fn type_of_type_expr(&self, tree: &Tree) -> Option<Type> {
|
||||||
assert_eq!(tree.kind, TreeKind::TypeExpression);
|
assert_eq!(tree.kind, TreeKind::TypeExpression);
|
||||||
|
|
||||||
|
|
@ -1216,6 +1342,7 @@ pub fn check(s: &Semantics) {
|
||||||
TreeKind::ListConstructorElement => {
|
TreeKind::ListConstructorElement => {
|
||||||
let _ = s.type_of(t);
|
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.
|
// 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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ pub enum TokenKind {
|
||||||
Fun,
|
Fun,
|
||||||
If,
|
If,
|
||||||
Import,
|
Import,
|
||||||
|
In,
|
||||||
Let,
|
Let,
|
||||||
Or,
|
Or,
|
||||||
Return,
|
Return,
|
||||||
|
|
@ -292,6 +293,9 @@ impl<'a> Tokens<'a> {
|
||||||
if ident == "import" {
|
if ident == "import" {
|
||||||
return TokenKind::Import;
|
return TokenKind::Import;
|
||||||
}
|
}
|
||||||
|
if ident == "in" {
|
||||||
|
return TokenKind::In;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
'l' => {
|
'l' => {
|
||||||
if ident == "let" {
|
if ident == "let" {
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
fn pop_function(&mut self) -> Result<FuncValue> {
|
||||||
match self.pop_value()? {
|
match self.pop_value()? {
|
||||||
StackValue::Function(f) => Ok(FuncValue::Function(f)),
|
StackValue::Function(f) => Ok(FuncValue::Function(f)),
|
||||||
|
|
@ -236,6 +245,11 @@ fn eval_one(
|
||||||
Instruction::Discard => {
|
Instruction::Discard => {
|
||||||
f.pop_value()?;
|
f.pop_value()?;
|
||||||
}
|
}
|
||||||
|
Instruction::Dup => {
|
||||||
|
let v = f.pop_value()?;
|
||||||
|
f.push_value(v.clone());
|
||||||
|
f.push_value(v);
|
||||||
|
}
|
||||||
Instruction::FloatAdd => {
|
Instruction::FloatAdd => {
|
||||||
let x = f.pop_float()?;
|
let x = f.pop_float()?;
|
||||||
let y = f.pop_float()?;
|
let y = f.pop_float()?;
|
||||||
|
|
@ -300,6 +314,10 @@ fn eval_one(
|
||||||
Instruction::PushTrue => {
|
Instruction::PushTrue => {
|
||||||
f.push_bool(true);
|
f.push_bool(true);
|
||||||
}
|
}
|
||||||
|
Instruction::StoreArgument(i) => {
|
||||||
|
let v = f.pop_value()?;
|
||||||
|
f.store_argument(i, v)?;
|
||||||
|
}
|
||||||
Instruction::StoreLocal(i) => {
|
Instruction::StoreLocal(i) => {
|
||||||
let v = f.pop_value()?;
|
let v = f.pop_value()?;
|
||||||
f.store_local(i, v)?;
|
f.store_local(i, v)?;
|
||||||
|
|
|
||||||
|
|
@ -283,6 +283,25 @@ fn assert_eval_ok(tree: &SyntaxTree, lines: &Lines, expected: &str) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_errors(tree: &SyntaxTree, lines: &Lines, expected_errors: Vec<&str>) {
|
||||||
|
let semantics = Semantics::new(tree, lines);
|
||||||
|
check(&semantics);
|
||||||
|
|
||||||
|
let errors: Vec<String> = semantics
|
||||||
|
.snapshot_errors()
|
||||||
|
.iter()
|
||||||
|
.map(|e| format!("{}", e))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
semantic_assert_eq!(
|
||||||
|
&semantics,
|
||||||
|
None,
|
||||||
|
expected_errors,
|
||||||
|
errors,
|
||||||
|
"expected no errors"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn assert_check_error(tree: &SyntaxTree, lines: &Lines, expected: &str) {
|
fn assert_check_error(tree: &SyntaxTree, lines: &Lines, expected: &str) {
|
||||||
let semantics = Semantics::new(tree, lines);
|
let semantics = Semantics::new(tree, lines);
|
||||||
check(&semantics);
|
check(&semantics);
|
||||||
|
|
|
||||||
34
fine/tests/expression/assignment.fine
Normal file
34
fine/tests/expression/assignment.fine
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
fun test() -> f64 {
|
||||||
|
let x = 12;
|
||||||
|
let y = 13;
|
||||||
|
let z = 2;
|
||||||
|
x = y = z;
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
// @no-errors
|
||||||
|
// @compiles-to:
|
||||||
|
// | function << module >> (0 args, 0 locals):
|
||||||
|
// | strings (0):
|
||||||
|
// | code (2):
|
||||||
|
// | 0: PushNothing
|
||||||
|
// | 1: Return
|
||||||
|
// | function test (0 args, 3 locals):
|
||||||
|
// | strings (0):
|
||||||
|
// | code (14):
|
||||||
|
// | 0: PushFloat(12.0)
|
||||||
|
// | 1: StoreLocal(0)
|
||||||
|
// | 2: PushFloat(13.0)
|
||||||
|
// | 3: StoreLocal(1)
|
||||||
|
// | 4: PushFloat(2.0)
|
||||||
|
// | 5: StoreLocal(2)
|
||||||
|
// | 6: LoadLocal(2)
|
||||||
|
// | 7: Dup
|
||||||
|
// | 8: StoreLocal(1)
|
||||||
|
// | 9: Dup
|
||||||
|
// | 10: StoreLocal(0)
|
||||||
|
// | 11: Discard
|
||||||
|
// | 12: LoadLocal(0)
|
||||||
|
// | 13: Return
|
||||||
|
// |
|
||||||
|
// @eval: Float(2.0)
|
||||||
20
fine/tests/expression/errors/assignment_errors.fine
Normal file
20
fine/tests/expression/errors/assignment_errors.fine
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
fun something() { }
|
||||||
|
fun something_else() { }
|
||||||
|
|
||||||
|
fun wrong() {
|
||||||
|
let x = 12;
|
||||||
|
let y = "hello";
|
||||||
|
x = y; // should be an error
|
||||||
|
y = x; // should be an error
|
||||||
|
|
||||||
|
let z = 23;
|
||||||
|
y = x = z; // should be an error
|
||||||
|
|
||||||
|
something_else = something; // should be an error
|
||||||
|
}
|
||||||
|
|
||||||
|
// @expect-errors:
|
||||||
|
// | 7:4: cannot assign a value of type `string` to type `f64`
|
||||||
|
// | 8:4: cannot assign a value of type `f64` to type `string`
|
||||||
|
// | 11:4: cannot assign a value of type `f64` to type `string`
|
||||||
|
// | 13:2: cannot assign a new value to a function declaration
|
||||||
Loading…
Add table
Add a link
Reference in a new issue