[fine] Starting on lists, god help me

This commit is contained in:
John Doty 2024-01-17 15:58:48 -08:00
parent 106f2eb30f
commit 9ee8d39963
5 changed files with 190 additions and 35 deletions

View file

@ -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);

View file

@ -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::*;

View file

@ -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::*;

View file

@ -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,

View file

@ -0,0 +1,10 @@
fun sum(x: list<f64>) -> f64 {
75 // lol
}
fun test() {
let val = [1, 2, 3];
sum(val);
}
// @no-errors