diff --git a/compiler_v4/src/ast.rs b/compiler_v4/src/ast.rs index 7910e5cdf..d9fbe771b 100644 --- a/compiler_v4/src/ast.rs +++ b/compiler_v4/src/ast.rs @@ -176,11 +176,30 @@ pub struct AstTypeParameter { // Types #[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub struct AstType { +pub enum AstType { + Named(AstNamedType), + Function(AstFunctionType), +} + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct AstNamedType { pub name: AstResult, pub type_arguments: Option, } +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct AstFunctionType { + pub parameter_types: Vec, + pub closing_parenthesis_error: Option, + pub return_type: AstResult>, + pub span: Range, +} +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct AstFunctionTypeParameterType { + pub type_: Box, + pub comma_error: Option, +} + #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct AstTypeArguments { pub span: Range, @@ -496,11 +515,31 @@ impl CollectAstErrors for AstTypeParameter { } impl CollectAstErrors for AstType { + fn collect_errors_to(&self, errors: &mut Vec) { + match &self { + Self::Named(named) => named.collect_errors_to(errors), + Self::Function(function) => function.collect_errors_to(errors), + } + } +} +impl CollectAstErrors for AstNamedType { fn collect_errors_to(&self, errors: &mut Vec) { self.name.collect_errors_to(errors); self.type_arguments.collect_errors_to(errors); } } +impl CollectAstErrors for AstFunctionType { + fn collect_errors_to(&self, errors: &mut Vec) { + self.parameter_types.collect_errors_to(errors); + self.return_type.collect_errors_to(errors); + } +} +impl CollectAstErrors for AstFunctionTypeParameterType { + fn collect_errors_to(&self, errors: &mut Vec) { + self.type_.collect_errors_to(errors); + self.comma_error.collect_errors_to(errors); + } +} impl CollectAstErrors for AstTypeArguments { fn collect_errors_to(&self, errors: &mut Vec) { self.arguments.collect_errors_to(errors); diff --git a/compiler_v4/src/ast_to_hir.rs b/compiler_v4/src/ast_to_hir.rs index fa234cafa..a6346e03b 100644 --- a/compiler_v4/src/ast_to_hir.rs +++ b/compiler_v4/src/ast_to_hir.rs @@ -654,6 +654,13 @@ impl<'a> Context<'a> { return hir::Err; }; + let type_ = match type_ { + AstType::Named(type_) => type_, + AstType::Function(function_type) => { + self.add_error(function_type.span.clone(), "Function type is not a trait"); + return hir::Err; + } + }; let Some(name) = type_.name.value() else { return hir::Err; }; @@ -873,6 +880,23 @@ impl<'a> Context<'a> { return Type::Error; }; + let type_ = match type_ { + AstType::Named(type_) => type_, + AstType::Function(type_) => { + return Type::Function(FunctionType { + parameter_types: type_ + .parameter_types + .iter() + .map(|it| self.lower_type(type_parameters, self_base_type, &*it.type_)) + .collect(), + return_type: Box::new(self.lower_type( + type_parameters, + self_base_type, + type_.return_type.value().map(AsRef::as_ref), + )), + }) + } + }; let Some(name) = type_.name.value() else { return Type::Error; }; @@ -1883,7 +1907,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { Ok(substitutions) => { assert!(substitutions.is_empty()); self.push_lowered( - name.string.clone(), + None, ExpressionKind::Call { function: id, substitutions, diff --git a/compiler_v4/src/main.rs b/compiler_v4/src/main.rs index 5210c9bd4..2baea39d8 100644 --- a/compiler_v4/src/main.rs +++ b/compiler_v4/src/main.rs @@ -104,13 +104,14 @@ fn debug(options: DebugOptions) -> ProgramResult { DebugOptions::Hir(options) => { let source = fs::read_to_string(&options.path).unwrap(); let (hir, errors) = compile_hir(&options.path, &source); + println!("{}", hir.to_text(true)); + if !errors.is_empty() { for error in errors { error!("{}", error.to_string_with_location(&source)); } return Err(Exit::CodeContainsErrors); } - println!("{}", hir.to_text(true)); } DebugOptions::Mono(options) => { let source = fs::read_to_string(&options.path).unwrap(); diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index 3fecab355..049681681 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -699,6 +699,9 @@ impl<'h> Context<'h> { } self.lower_expression(declaration_name, *id, expression); + if &*expression.type_ == "Never" { + self.push("// Returns `Never`\n"); + } self.push("\n\n"); } } @@ -810,8 +813,11 @@ impl<'h> Context<'h> { } self.lower_body_expressions(declaration_name, &case.body); - - self.push(format!("{id} = {};\n", case.body.return_value_id())); + if case.body.return_type() == "Never" { + self.push("// Returns `Never`\n"); + } else { + self.push(format!("{id} = {};\n", case.body.return_value_id())); + } self.push("break;"); } diff --git a/compiler_v4/src/string_to_ast/type_.rs b/compiler_v4/src/string_to_ast/type_.rs index 4d076311d..4fc416a42 100644 --- a/compiler_v4/src/string_to_ast/type_.rs +++ b/compiler_v4/src/string_to_ast/type_.rs @@ -1,25 +1,97 @@ use super::{ - literal::{closing_bracket, comma, opening_bracket}, + literal::{closing_bracket, closing_parenthesis, comma, opening_bracket, opening_parenthesis}, parser::{OptionOfParser, OptionOfParserWithValue, Parser}, whitespace::{AndTrailingWhitespace, ValueAndTrailingWhitespace}, word::raw_identifier, }; -use crate::ast::{AstError, AstString, AstType, AstTypeArgument, AstTypeArguments}; +use crate::ast::{ + AstError, AstFunctionType, AstFunctionTypeParameterType, AstNamedType, AstString, AstType, + AstTypeArgument, AstTypeArguments, +}; use tracing::instrument; #[instrument(level = "trace")] pub fn type_(parser: Parser) -> Option<(Parser, AstType)> { + None.or_else(|| named_type(parser).map(|(parser, it)| (parser, AstType::Named(it)))) + .or_else(|| function_type(parser).map(|(parser, it)| (parser, AstType::Function(it)))) +} + +#[instrument(level = "trace")] +pub fn named_type(parser: Parser) -> Option<(Parser, AstNamedType)> { let (parser, name) = raw_identifier(parser)?.and_trailing_whitespace(); let (parser, type_arguments) = type_arguments(parser).optional(parser); Some(( parser, - AstType { + AstNamedType { name, type_arguments, }, )) } +#[instrument(level = "trace")] +pub fn function_type(parser: Parser) -> Option<(Parser, AstFunctionType)> { + let start_offset = parser.offset(); + let mut parser = opening_parenthesis(parser)?.and_trailing_whitespace(); + + // TODO: error on duplicate type parameter names + let mut parameter_types: Vec = vec![]; + let mut parser_for_missing_comma_error: Option = None; + while let Some((new_parser, parameter_type, new_parser_for_missing_comma_error)) = + function_type_parameter_type(parser.and_trailing_whitespace()) + { + if let Some(parser_for_missing_comma_error) = parser_for_missing_comma_error { + parameter_types.last_mut().unwrap().comma_error = Some( + parser_for_missing_comma_error + .error_at_current_offset("This parameter type is missing a comma."), + ); + } + + parser = new_parser; + parameter_types.push(parameter_type); + parser_for_missing_comma_error = new_parser_for_missing_comma_error; + } + let parser = parser.and_trailing_whitespace(); + + let (parser, closing_parenthesis_error) = closing_parenthesis(parser) + .unwrap_or_ast_error( + parser, + "These parameter types are missing a closing parenthesis.", + ) + .and_trailing_whitespace(); + + let (parser, return_type) = + type_(parser).unwrap_or_ast_error(parser, "This function type is missing a return type."); + + Some(( + parser, + AstFunctionType { + parameter_types, + closing_parenthesis_error, + return_type: return_type.map(Box::new), + span: start_offset..parser.offset(), + }, + )) +} +#[instrument(level = "trace")] +fn function_type_parameter_type<'a>( + parser: Parser, +) -> Option<(Parser, AstFunctionTypeParameterType, Option)> { + let (parser, type_) = type_(parser)?.and_trailing_whitespace(); + + let (parser, parser_for_missing_comma_error) = + comma(parser).map_or((parser, Some(parser)), |parser| (parser, None)); + + Some(( + parser, + AstFunctionTypeParameterType { + type_: Box::new(type_), + comma_error: None, + }, + parser_for_missing_comma_error, + )) +} + #[instrument(level = "trace")] pub fn type_arguments<'s>(parser: Parser<'s>) -> Option<(Parser, AstTypeArguments)> { let start_offset = parser.offset(); diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 6660b471c..9d6141004 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -288,6 +288,21 @@ fun needs(condition: Bool, message: Text) { } } +fun loop(body: () Nothing) { + body() + loop(body) +} +fun repeat(times: Int, body: () Nothing) { + needs(times.isNonNegative()) + switch times.isGreaterThan(0) { + false => {}, + true => { + body() + repeat(times.subtract(1), body) + }, + } +} + struct List[T] = builtin fun listFilled[T](length: Int, item: T) List[T] { builtinListFilled(length, item) @@ -538,11 +553,14 @@ fun main() Int { print("Length: {list.length().toText()}") print("[{list.get(0).toText()}, {list.get(1).toText()}, {list.get(2).toText()}]") + print("orld!".endsWith("World!").toText().isEmpty()) + let foo = 123 let addCaptured = (x: Int) { x.add(foo) } print("addCaptured(1) = {addCaptured(1).toText()}") - print("orld!".endsWith("World!").toText().isEmpty()) + repeat(3, () { print("Hello, World!") }) + 0 }