[fine] Assignments!
And new error capabilities!
This commit is contained in:
parent
92cf840766
commit
f20f5a5e03
12 changed files with 400 additions and 64 deletions
|
|
@ -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::*;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue