[fine] Starting on lists, god help me
This commit is contained in:
parent
106f2eb30f
commit
9ee8d39963
5 changed files with 190 additions and 35 deletions
|
|
@ -425,7 +425,7 @@ fn compile_binary_expression(c: &mut Compiler, t: TreeRef, tr: &Tree) -> CR {
|
|||
|
||||
compile_expression(c, tr.nth_tree(2)?);
|
||||
|
||||
if arg_type.compatible_with(&Type::Nothing) {
|
||||
if c.semantics.type_compat(&arg_type, &Type::Nothing) {
|
||||
c.push(Instruction::Discard);
|
||||
c.push(Instruction::Discard);
|
||||
c.push(Instruction::PushTrue);
|
||||
|
|
|
|||
|
|
@ -139,6 +139,10 @@ pub enum TreeKind {
|
|||
IfStatement,
|
||||
Identifier,
|
||||
ReturnType,
|
||||
TypeParameterList,
|
||||
TypeParameter,
|
||||
ListConstructor,
|
||||
ListConstructorElement,
|
||||
}
|
||||
|
||||
pub struct Tree<'a> {
|
||||
|
|
@ -566,11 +570,46 @@ fn return_type(p: &mut CParser) {
|
|||
|
||||
fn type_expr(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
// TODO: Other kinds of type expressions probably!
|
||||
p.expect(TokenKind::Identifier, "expected the identifier of a type");
|
||||
|
||||
if p.at(TokenKind::Less) {
|
||||
type_parameter_list(p);
|
||||
}
|
||||
|
||||
p.end(m, TreeKind::TypeExpression);
|
||||
}
|
||||
|
||||
fn type_parameter_list(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::Less));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(TokenKind::Less, "expected < to start type parameter list");
|
||||
while !p.at(TokenKind::Greater) && !p.eof() {
|
||||
if p.at(TokenKind::Identifier) {
|
||||
type_parameter(p);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
p.expect(TokenKind::Greater, "expected > to end type parameter list");
|
||||
|
||||
p.end(m, TreeKind::TypeParameterList);
|
||||
}
|
||||
|
||||
fn type_parameter(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::Identifier));
|
||||
let m = p.start();
|
||||
|
||||
type_expr(p);
|
||||
if !p.at(TokenKind::Greater) {
|
||||
p.expect(TokenKind::Comma, "expect a comma between type parameters");
|
||||
}
|
||||
|
||||
p.end(m, TreeKind::TypeParameter);
|
||||
}
|
||||
|
||||
fn block(p: &mut CParser) {
|
||||
assert!(p.at(TokenKind::LeftBrace));
|
||||
let m = p.start();
|
||||
|
|
@ -758,6 +797,8 @@ fn prefix_expression(p: &mut CParser) -> MarkClosed {
|
|||
|
||||
TokenKind::Identifier => identifier(p),
|
||||
|
||||
TokenKind::LeftBracket => list_constructor(p),
|
||||
|
||||
_ => p.advance_with_error("expected an expression"),
|
||||
}
|
||||
}
|
||||
|
|
@ -816,6 +857,39 @@ fn identifier(p: &mut CParser) -> MarkClosed {
|
|||
p.end(m, TreeKind::Identifier)
|
||||
}
|
||||
|
||||
fn list_constructor(p: &mut CParser) -> MarkClosed {
|
||||
assert!(p.at(TokenKind::LeftBracket));
|
||||
let m = p.start();
|
||||
|
||||
p.expect(
|
||||
TokenKind::LeftBracket,
|
||||
"expect a list constructor to start with [",
|
||||
);
|
||||
while !p.at(TokenKind::RightBracket) && !p.eof() {
|
||||
list_constructor_element(p);
|
||||
}
|
||||
p.expect(
|
||||
TokenKind::RightBracket,
|
||||
"expected a ] to end the list constructor",
|
||||
);
|
||||
|
||||
p.end(m, TreeKind::ListConstructor)
|
||||
}
|
||||
|
||||
fn list_constructor_element(p: &mut CParser) {
|
||||
let m = p.start();
|
||||
|
||||
expression(p);
|
||||
if !p.at(TokenKind::RightBracket) {
|
||||
p.expect(
|
||||
TokenKind::Comma,
|
||||
"expected a comma between list constructor elements",
|
||||
);
|
||||
}
|
||||
|
||||
p.end(m, TreeKind::ListConstructorElement);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ pub enum Type {
|
|||
// This is until generics are working
|
||||
MagicPrintGarbage,
|
||||
|
||||
// An potentially-bound type variable.
|
||||
// We need to ... like ... unify these things if possible.
|
||||
TypeVariable(TreeRef),
|
||||
|
||||
Nothing,
|
||||
// TODO: Numeric literals should be implicitly convertable, unlike other
|
||||
// types. Maybe just "numeric literal" type?
|
||||
|
|
@ -80,6 +84,7 @@ pub enum Type {
|
|||
Bool,
|
||||
|
||||
Function(Vec<Box<Type>>, Box<Type>),
|
||||
List(Box<Type>),
|
||||
}
|
||||
|
||||
impl Type {
|
||||
|
|
@ -89,23 +94,6 @@ impl Type {
|
|||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compatible_with(&self, other: &Type) -> bool {
|
||||
// TODO: This is wrong; we because of numeric literals etc.
|
||||
match (self, other) {
|
||||
(Type::F64, Type::F64) => true,
|
||||
(Type::String, Type::String) => true,
|
||||
(Type::Bool, Type::Bool) => true,
|
||||
(Type::Unreachable, Type::Unreachable) => true,
|
||||
(Type::Nothing, Type::Nothing) => true,
|
||||
|
||||
// Avoid introducing more errors
|
||||
(Type::Error, _) => true,
|
||||
(_, Type::Error) => true,
|
||||
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Type {
|
||||
|
|
@ -137,6 +125,10 @@ impl fmt::Display for Type {
|
|||
}
|
||||
write!(f, ") -> {ret}")
|
||||
}
|
||||
|
||||
// TODO: Better names
|
||||
TypeVariable(_) => write!(f, "$_"),
|
||||
List(t) => write!(f, "list<{t}>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -614,6 +606,26 @@ impl<'a> Semantics<'a> {
|
|||
EnvironmentRef::new(environment)
|
||||
}
|
||||
|
||||
pub fn type_compat(&self, a: &Type, b: &Type) -> bool {
|
||||
// TODO: This is wrong; we because of numeric literals etc.
|
||||
match (a, b) {
|
||||
(Type::F64, Type::F64) => true,
|
||||
(Type::String, Type::String) => true,
|
||||
(Type::Bool, Type::Bool) => true,
|
||||
(Type::Unreachable, Type::Unreachable) => true,
|
||||
(Type::Nothing, Type::Nothing) => true,
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_of(&self, t: TreeRef) -> Type {
|
||||
{
|
||||
let state = &mut self.types.borrow_mut()[t.index()];
|
||||
|
|
@ -638,6 +650,7 @@ impl<'a> Semantics<'a> {
|
|||
TreeKind::UnaryExpression => self.type_of_unary(tree),
|
||||
TreeKind::BinaryExpression => self.type_of_binary(tree),
|
||||
TreeKind::TypeExpression => self.type_of_type_expr(tree),
|
||||
TreeKind::TypeParameter => self.type_of_type_parameter(tree),
|
||||
TreeKind::Block => self.type_of_block(tree),
|
||||
TreeKind::LiteralExpression => self.type_of_literal(tree),
|
||||
TreeKind::GroupingExpression => self.type_of_grouping(tree),
|
||||
|
|
@ -655,6 +668,9 @@ impl<'a> Semantics<'a> {
|
|||
TreeKind::ReturnType => self.type_of_return_type(tree),
|
||||
TreeKind::Parameter => self.type_of_parameter(tree),
|
||||
|
||||
TreeKind::ListConstructorElement => self.type_of_list_constructor_element(tree),
|
||||
TreeKind::ListConstructor => self.type_of_list_constructor(t, tree),
|
||||
|
||||
_ => self.internal_compiler_error(Some(t), "asking for a nonsense type"),
|
||||
};
|
||||
|
||||
|
|
@ -765,13 +781,30 @@ impl<'a> Semantics<'a> {
|
|||
"string" => Some(Type::String),
|
||||
"bool" => Some(Type::Bool),
|
||||
"()" => Some(Type::Nothing),
|
||||
"list" => {
|
||||
let args =
|
||||
tree.child_tree_of_kind(self.syntax_tree, TreeKind::TypeParameterList)?;
|
||||
let mut arg_types: Vec<_> = args.child_trees().map(|t| self.type_of(t)).collect();
|
||||
|
||||
if arg_types.len() != 1 {
|
||||
self.report_error_tree(tree, "list takes a single type argument");
|
||||
Some(Type::Error)
|
||||
} else {
|
||||
Some(Type::List(Box::new(arg_types.pop().unwrap())))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.report_error_tree(tree, "Unrecognized type");
|
||||
self.report_error_tree(tree, format!("Unrecognized type: '{token}'"));
|
||||
Some(Type::Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_of_type_parameter(&self, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::TypeParameter);
|
||||
Some(self.type_of(tree.nth_tree(0)?))
|
||||
}
|
||||
|
||||
fn type_of_block(&self, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::Block);
|
||||
|
||||
|
|
@ -849,7 +882,7 @@ impl<'a> Semantics<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
if !cond_type.compatible_with(&Type::Bool) {
|
||||
if !self.type_compat(&cond_type, &Type::Bool) {
|
||||
if !cond_type.is_error() {
|
||||
self.report_error_tree_ref(cond_tree, "conditions must yield a boolean");
|
||||
}
|
||||
|
|
@ -865,7 +898,7 @@ impl<'a> Semantics<'a> {
|
|||
|
||||
(then_type, else_type) => {
|
||||
let else_type = else_type.unwrap_or(Type::Nothing);
|
||||
if !then_type.compatible_with(&else_type) {
|
||||
if !self.type_compat(&then_type, &else_type) {
|
||||
self.report_error_tree(
|
||||
tree,
|
||||
format!("the type of the `then` branch ({then_type}) must match the type of the `else` branch ({else_type})"),
|
||||
|
|
@ -909,7 +942,7 @@ impl<'a> Semantics<'a> {
|
|||
}
|
||||
|
||||
for (i, ((t, a), p)) in arg_types.iter().zip(params.iter()).enumerate() {
|
||||
if !a.compatible_with(p) {
|
||||
if !self.type_compat(&a, p) {
|
||||
self.report_error_tree_ref(
|
||||
*t,
|
||||
format!(
|
||||
|
|
@ -1025,6 +1058,38 @@ impl<'a> Semantics<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn type_of_list_constructor(&self, t: TreeRef, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::ListConstructor);
|
||||
let mut element_type = None;
|
||||
for ct in tree.child_trees() {
|
||||
let child_type = self.type_of(ct);
|
||||
element_type = match element_type {
|
||||
None => Some(child_type),
|
||||
Some(list_type) => {
|
||||
if list_type.is_error() {
|
||||
Some(child_type)
|
||||
} else if child_type.is_error() {
|
||||
Some(list_type)
|
||||
} else {
|
||||
if !self.type_compat(&child_type, &list_type) {
|
||||
self.report_error_tree_ref(ct, format!("list element of type {child_type} is not compatible with the list type {list_type}"));
|
||||
}
|
||||
Some(list_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let element_type = element_type.unwrap_or_else(|| Type::TypeVariable(t));
|
||||
|
||||
Some(Type::List(Box::new(element_type)))
|
||||
}
|
||||
|
||||
fn type_of_list_constructor_element(&self, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::ListConstructorElement);
|
||||
Some(self.type_of(tree.nth_tree(0)?))
|
||||
}
|
||||
|
||||
fn type_of_return_type(&self, tree: &Tree) -> Option<Type> {
|
||||
assert_eq!(tree.kind, TreeKind::ReturnType);
|
||||
Some(self.type_of(tree.nth_tree(1)?)) // type expression
|
||||
|
|
@ -1112,12 +1177,14 @@ pub fn check(s: &Semantics) {
|
|||
TreeKind::File => {}
|
||||
TreeKind::FunctionDecl => check_function_decl(s, t, tree),
|
||||
TreeKind::ParamList => {}
|
||||
TreeKind::Parameter => check_parameter(s, t),
|
||||
TreeKind::Parameter => {
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
TreeKind::TypeExpression => {
|
||||
let _ = s.type_of_type_expr(tree);
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
TreeKind::Block => {
|
||||
let _ = s.type_of_block(tree);
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
TreeKind::LetStatement => {
|
||||
let _ = s.environment_of(t);
|
||||
|
|
@ -1141,6 +1208,14 @@ pub fn check(s: &Semantics) {
|
|||
let _ = s.type_of(t);
|
||||
}
|
||||
TreeKind::ReturnType => {}
|
||||
TreeKind::TypeParameter => {}
|
||||
TreeKind::TypeParameterList => {}
|
||||
TreeKind::ListConstructor => {
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
TreeKind::ListConstructorElement => {
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1156,7 +1231,7 @@ fn check_function_decl(s: &Semantics, t: TreeRef, tree: &Tree) {
|
|||
|
||||
if let Some(body) = tree.child_of_kind(s.syntax_tree, TreeKind::Block) {
|
||||
let body_type = s.type_of(body);
|
||||
if !body_type.compatible_with(&return_type) {
|
||||
if !s.type_compat(&body_type, &return_type) {
|
||||
// Just work very hard to get an appropriate error span.
|
||||
let (start, end) = return_type_tree
|
||||
.map(|t| {
|
||||
|
|
@ -1206,7 +1281,7 @@ fn check_return_statement(s: &Semantics, tree: &Tree) {
|
|||
Type::Error
|
||||
};
|
||||
|
||||
if !expected_type.compatible_with(&actual_type) {
|
||||
if !s.type_compat(&expected_type, &actual_type) {
|
||||
s.report_error_tree(tree, format!("callers of this function expect a value of type `{expected_type}` but this statement returns a value of type `{actual_type}`"));
|
||||
}
|
||||
}
|
||||
|
|
@ -1220,10 +1295,6 @@ fn check_return_statement(s: &Semantics, tree: &Tree) {
|
|||
// OK this one is a little bit messed up because it reaches *up*, sorry.
|
||||
}
|
||||
|
||||
fn check_parameter(s: &Semantics, t: TreeRef) {
|
||||
let _ = s.type_of(t);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ pub enum TokenKind {
|
|||
Whitespace,
|
||||
Comment,
|
||||
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LeftBracket,
|
||||
RightBracket,
|
||||
LeftBrace, // TODO: LeftCurly
|
||||
RightBrace, // TODO: RightCurly
|
||||
LeftBracket, // TODO: LeftSquare
|
||||
RightBracket, // TODO: RightSquare
|
||||
LeftParen,
|
||||
RightParen,
|
||||
Comma,
|
||||
|
|
|
|||
10
fine/tests/expression/lists.fine
Normal file
10
fine/tests/expression/lists.fine
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
fun sum(x: list<f64>) -> f64 {
|
||||
75 // lol
|
||||
}
|
||||
|
||||
fun test() {
|
||||
let val = [1, 2, 3];
|
||||
sum(val);
|
||||
}
|
||||
|
||||
// @no-errors
|
||||
Loading…
Add table
Add a link
Reference in a new issue