diff --git a/compiler_v4/src/ast.rs b/compiler_v4/src/ast.rs index d9fbe771b..72e805db8 100644 --- a/compiler_v4/src/ast.rs +++ b/compiler_v4/src/ast.rs @@ -252,7 +252,7 @@ pub struct AstText { } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum AstTextPart { - Text(Box), + Text(AstResult>), Interpolation { expression: AstResult>, closing_curly_brace_error: Option, @@ -381,6 +381,9 @@ impl CollectAstErrors for AstError { impl CollectAstErrors for AstString { fn collect_errors_to(&self, _errors: &mut Vec) {} } +impl CollectAstErrors for str { + fn collect_errors_to(&self, _errors: &mut Vec) {} +} impl CollectAstErrors for i64 { fn collect_errors_to(&self, _errors: &mut Vec) {} } @@ -594,7 +597,9 @@ impl CollectAstErrors for AstText { impl CollectAstErrors for AstTextPart { fn collect_errors_to(&self, errors: &mut Vec) { match &self { - Self::Text(_) => {} + Self::Text(text) => { + text.errors.collect_errors_to(errors); + } Self::Interpolation { expression, closing_curly_brace_error, diff --git a/compiler_v4/src/ast_to_hir.rs b/compiler_v4/src/ast_to_hir.rs index a6346e03b..12a897b5e 100644 --- a/compiler_v4/src/ast_to_hir.rs +++ b/compiler_v4/src/ast_to_hir.rs @@ -1357,7 +1357,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { { self.context.add_error( expression.span.clone(), - format!("Expected type `{context_type:?}`, got `{type_:?}`."), + format!("Expected type `{context_type}`, got `{type_}`."), ); (self.push_error(), Type::Error) } else { @@ -1397,9 +1397,11 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { .parts .iter() .map::(|it| match it { - AstTextPart::Text(text) => { - self.push(None, ExpressionKind::Text(text.clone()), NamedType::text()) - } + AstTextPart::Text(text) => self.push( + None, + ExpressionKind::Text(text.value().cloned().unwrap_or_default()), + NamedType::text(), + ), AstTextPart::Interpolation { expression, .. } => { if let Some(expression) = expression.value() { // TODO: accept impl ToText @@ -1547,7 +1549,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { LoweredExpression::Error => LoweredExpression::Error, } } - _ => todo!("Support calling other expressions"), + receiver => todo!("Support calling other expressions: {receiver:?}"), } } AstExpressionKind::Navigation(navigation) => { @@ -1610,7 +1612,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { ); LoweredExpression::Error } - Type::Error => todo!(), + Type::Error => LoweredExpression::Error, }, LoweredExpression::NamedTypeReference(type_) => { let declaration = self.context.hir.type_declarations.get(&type_).unwrap(); @@ -1904,18 +1906,15 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { &argument_types, ); return match result { - Ok(substitutions) => { - assert!(substitutions.is_empty()); - self.push_lowered( - None, - ExpressionKind::Call { - function: id, - substitutions, - arguments: arguments.iter().map(|(id, _)| *id).collect(), - }, - *type_.return_type, - ) - } + Ok(substitutions) => self.push_lowered( + None, + ExpressionKind::Call { + function: id, + substitutions, + arguments: arguments.iter().map(|(id, _)| *id).collect(), + }, + *type_.return_type, + ), Err(error) => { self.context.add_error( name.span.clone(), diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index 98e0a93f9..17cb40901 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -732,8 +732,8 @@ impl ToText for ExpressionKind { builder.push("["); builder.push_children_custom( substitutions.iter(), - |builder, (id, type_)| { - builder.push(format!("{id}: {type_}")); + |builder, (type_parameter, type_argument)| { + builder.push(format!("{type_parameter} = {type_argument}")); }, ", ", ); @@ -751,25 +751,10 @@ impl ToText for ExpressionKind { builder.push("switch "); value.build_text(builder); builder.push(format!(": {enum_} {{")); - builder.push_children_custom_multiline(cases.iter(), |builder, case| { - builder.push(format!("case {}", case.variant)); - if let Some(value_id) = case.value_id { - builder.push(format!("({value_id})")); - } - builder.push(" {"); - builder.push_children_custom_multiline( - case.body.expressions.iter(), - |builder, (id, name, expression)| { - id.build_text(builder); - builder.push(format!(": {} = ", expression.type_)); - if let Some(name) = name { - builder.push(format!("{name} = ")); - } - expression.kind.build_text(builder); - }, - ); - builder.push("}"); - }); + builder.push_children_multiline(cases.iter()); + if !cases.is_empty() { + builder.push_newline(); + } builder.push("}"); } Self::Lambda { parameters, body } => { @@ -788,6 +773,16 @@ pub struct SwitchCase { pub value_id: Option, pub body: Body, } +impl ToText for SwitchCase { + fn build_text(&self, builder: &mut TextBuilder) { + builder.push(format!("{}", self.variant)); + if let Some(value_id) = self.value_id { + builder.push(format!("({value_id})")); + } + builder.push(" => "); + self.body.build_text(builder); + } +} #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, VariantArray)] #[strum(serialize_all = "camelCase")] @@ -806,6 +801,7 @@ pub enum BuiltinFunction { IntSubtract, IntToText, ListFilled, + ListGenerate, ListGet, ListInsert, ListLength, @@ -966,6 +962,20 @@ impl BuiltinFunction { .into(), return_type: NamedType::list(ParameterType::new("T")).into(), }, + Self::ListGenerate => BuiltinFunctionSignature { + name: "builtinListGenerate".into(), + type_parameters: ["T".into()].into(), + parameters: [ + ("length".into(), NamedType::int().into()), + ( + "itemGetter".into(), + FunctionType::new([NamedType::int().into()], ParameterType::new("T")) + .into(), + ), + ] + .into(), + return_type: NamedType::list(ParameterType::new("T")).into(), + }, Self::ListGet => BuiltinFunctionSignature { name: "builtinListGet".into(), type_parameters: ["T".into()].into(), diff --git a/compiler_v4/src/hir_to_mono.rs b/compiler_v4/src/hir_to_mono.rs index 723ab6c61..7787e51ce 100644 --- a/compiler_v4/src/hir_to_mono.rs +++ b/compiler_v4/src/hir_to_mono.rs @@ -170,7 +170,7 @@ impl<'h> Context<'h> { .get(&function_id) .map(|function| (trait_, function)) }) - .unwrap(); + .unwrap_or_else(|| panic!("Unknown trait function: {function_id}")); let self_type = function.signature.parameters[0] .type_ .substitute(substitutions); @@ -230,7 +230,9 @@ impl<'h> Context<'h> { .collect::>>() .is_some() }) - .unwrap() + .unwrap_or_else(|| { + panic!("No matching impl found for trait `{trait_}` with {substitutions:?}") + }) } fn mangle_function( &mut self, @@ -478,7 +480,7 @@ impl<'c, 'h> BodyBuilder<'c, 'h> { ); } hir::ExpressionKind::CreateStruct { struct_, fields } => { - let struct_ = self.context.lower_type(&struct_.clone().into()); + let struct_ = self.lower_type(&struct_.clone().into()); let fields = self.lower_ids(fields); self.push( id, diff --git a/compiler_v4/src/main.rs b/compiler_v4/src/main.rs index 2baea39d8..90d90ac37 100644 --- a/compiler_v4/src/main.rs +++ b/compiler_v4/src/main.rs @@ -122,8 +122,9 @@ fn debug(options: DebugOptions) -> ProgramResult { } return Err(Exit::CodeContainsErrors); } + let mono = hir_to_mono(&hir); - println!("{mono:?}"); + println!("{}", mono.to_text(true)); } } Ok(()) diff --git a/compiler_v4/src/mono.rs b/compiler_v4/src/mono.rs index dd2f493bc..c18373af3 100644 --- a/compiler_v4/src/mono.rs +++ b/compiler_v4/src/mono.rs @@ -1,5 +1,10 @@ -use crate::{hir::BuiltinFunction, impl_countable_id}; +use crate::{ + hir::BuiltinFunction, + impl_countable_id, + to_text::{TextBuilder, ToText}, +}; use derive_more::Deref; +use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use std::fmt::{self, Display, Formatter}; @@ -11,6 +16,11 @@ impl Display for Id { write!(f, "_{}", self.0) } } +impl ToText for Id { + fn build_text(&self, builder: &mut TextBuilder) { + builder.push(format!("${self}")); + } +} #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Mono { @@ -20,6 +30,93 @@ pub struct Mono { pub functions: FxHashMap, Function>, pub main_function: Box, } +impl ToText for Mono { + fn build_text(&self, builder: &mut TextBuilder) { + for (name, declaration) in self + .type_declarations + .iter() + .sorted_by_key(|(name, _)| *name) + { + builder.push("Type Declarations:"); + builder.push_newline(); + match declaration { + TypeDeclaration::Builtin { + name, + type_arguments, + } => { + builder.push(format!( + "builtin struct {name} = {name}{}", + if type_arguments.is_empty() { + String::new() + } else { + format!("[{}]", type_arguments.join(", ")) + } + )); + } + TypeDeclaration::Struct { fields } => { + builder.push(format!("struct {name} {{")); + builder.push_children_custom_multiline( + fields.iter(), + |builder, (box name, box type_)| { + builder.push(format!("{name}: {type_},")); + }, + ); + if !fields.is_empty() { + builder.push_newline(); + } + builder.push("}"); + } + TypeDeclaration::Enum { variants } => { + builder.push(format!("enum {name} {{")); + builder.push_children_custom_multiline(variants.iter(), |builder, variant| { + builder.push(&variant.name); + if let Some(value_type) = &variant.value_type { + builder.push(format!(": {value_type}")); + } + builder.push(","); + }); + if !variants.is_empty() { + builder.push_newline(); + } + builder.push("}"); + } + TypeDeclaration::Function { + parameter_types, + return_type, + } => { + builder.push(format!( + "functionType {name} = ({}) {return_type}", + parameter_types.iter().join(", "), + )); + } + } + builder.push_newline(); + } + builder.push_newline(); + + builder.push("Assignments (in initialization order):"); + builder.push_newline(); + for name in self.assignment_initialization_order.iter() { + (&**name, &self.assignments[name]).build_text(builder); + builder.push_newline(); + } + builder.push_newline(); + + builder.push("Functions:"); + builder.push_newline(); + for (name, function) in self + .functions + .iter() + .sorted_by_key(|(name, _)| (**name).clone()) + { + (&**name, function).build_text(builder); + builder.push_newline(); + } + builder.push_newline(); + + builder.push(format!("Main Function: {}", self.main_function)); + } +} #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum TypeDeclaration { @@ -49,6 +146,13 @@ pub struct Assignment { pub type_: Box, pub body: Body, } +impl ToText for (&str, &Assignment) { + fn build_text(&self, builder: &mut TextBuilder) { + let (name, Assignment { type_, body }) = self; + builder.push(format!("let {name}: {type_} = ")); + body.build_text(builder); + } +} #[derive(Clone, Debug, Eq, PartialEq)] pub struct Function { @@ -56,12 +160,54 @@ pub struct Function { pub return_type: Box, pub body: BodyOrBuiltin, } +impl ToText for (&str, &Function) { + fn build_text(&self, builder: &mut TextBuilder) { + let ( + name, + Function { + parameters, + return_type, + body, + }, + ) = self; + builder.push(format!("fun {name}(")); + builder.push_children(parameters.iter(), ", "); + builder.push(format!(") {return_type} ")); + match body { + BodyOrBuiltin::Body(body) => body.build_text(builder), + BodyOrBuiltin::Builtin { + builtin_function, + substitutions, + } => { + builder.push(format!("= {builtin_function:?}")); + if !substitutions.is_empty() { + builder.push("["); + builder.push_children_custom( + substitutions + .iter() + .sorted_by_key(|(type_parameter, _)| (**type_parameter).clone()), + |builder, (type_parameter, type_argument)| { + builder.push(format!("{type_parameter} = {type_argument}")); + }, + ", ", + ); + builder.push("]"); + } + } + } + } +} #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Parameter { pub id: Id, pub name: Box, pub type_: Box, } +impl ToText for Parameter { + fn build_text(&self, builder: &mut TextBuilder) { + builder.push(format!("{} {}: {}", self.name, self.id, self.type_)); + } +} #[derive(Clone, Debug, Eq, PartialEq)] pub enum BodyOrBuiltin { @@ -143,6 +289,24 @@ impl Body { }) } } +impl ToText for Body { + fn build_text(&self, builder: &mut TextBuilder) { + builder.push("{"); + builder.push_children_custom_multiline( + self.expressions.iter(), + |builder, (id, name, expression)| { + id.build_text(builder); + builder.push(format!(": {} = ", expression.type_)); + if let Some(name) = name { + builder.push(format!("{name} = ")); + } + expression.kind.build_text(builder); + }, + ); + builder.push_newline(); + builder.push("}"); + } +} #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Expression { @@ -184,6 +348,64 @@ pub enum ExpressionKind { }, Lambda(Lambda), } +impl ToText for ExpressionKind { + fn build_text(&self, builder: &mut TextBuilder) { + match self { + Self::Int(value) => builder.push(value.to_string()), + Self::Text(value) => builder.push(format!("\"{value}\"")), + Self::CreateStruct { struct_, fields } => { + builder.push(format!("{struct_} {{")); + builder.push_children(fields.iter(), ", "); + builder.push("}"); + } + Self::StructAccess { struct_, field } => { + builder.push(format!("{struct_}.{field}")); + } + Self::CreateEnum { + enum_, + variant, + value, + } => { + builder.push(format!("{enum_}.{variant}")); + if let Some(value) = value { + builder.push(format!("({value})")); + } + } + Self::GlobalAssignmentReference(name) => builder.push(name), + Self::LocalReference(id) => id.build_text(builder), + Self::CallFunction { + function, + arguments, + } => { + builder.push(function); + builder.push("("); + builder.push_children(arguments.iter(), ", "); + builder.push(")"); + } + Self::CallLambda { lambda, arguments } => { + lambda.build_text(builder); + builder.push("("); + builder.push_children(arguments.iter(), ", "); + builder.push(")"); + } + Self::Switch { + value, + enum_, + cases, + } => { + builder.push("switch "); + value.build_text(builder); + builder.push(format!(": {enum_} {{")); + builder.push_children_multiline(cases.iter()); + if !cases.is_empty() { + builder.push_newline(); + } + builder.push("}"); + } + Self::Lambda(lambda) => lambda.build_text(builder), + } + } +} #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct SwitchCase { @@ -191,6 +413,16 @@ pub struct SwitchCase { pub value_id: Option, pub body: Body, } +impl ToText for SwitchCase { + fn build_text(&self, builder: &mut TextBuilder) { + builder.push(format!("{}", self.variant)); + if let Some(value_id) = self.value_id { + builder.push(format!("({value_id})")); + } + builder.push(" => "); + self.body.build_text(builder); + } +} #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Lambda { @@ -199,10 +431,32 @@ pub struct Lambda { } impl Lambda { #[must_use] - pub fn closure_with_types(&self, function_body: &Body) -> FxHashMap> { + pub fn closure_with_types( + &self, + declaration_parameters: &[Parameter], + declaration_body: &Body, + ) -> FxHashMap> { self.closure() .into_iter() - .map(|id| (id, function_body.find_expression(id).unwrap().type_.clone())) + .map(|id| { + ( + id, + declaration_parameters.iter().find(|it| it.id == id).map_or_else( + || { + declaration_body + .find_expression(id) + .unwrap_or_else(|| { + panic!( + "Couldn't find expression {id} in declaration body {declaration_body:?}" + ) + }) + .type_ + .clone() + }, + |it| it.type_.clone(), + ) + ) + }) .collect() } #[must_use] @@ -215,3 +469,11 @@ impl Lambda { referenced_ids } } +impl ToText for Lambda { + fn build_text(&self, builder: &mut TextBuilder) { + builder.push("("); + builder.push_children(self.parameters.iter(), ", "); + builder.push(") => "); + self.body.build_text(builder); + } +} diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index 049681681..f02aee94f 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -1,7 +1,7 @@ use crate::{ hir::BuiltinFunction, mono::{ - Body, BodyOrBuiltin, Expression, ExpressionKind, Function, Id, Lambda, Mono, + Body, BodyOrBuiltin, Expression, ExpressionKind, Function, Id, Lambda, Mono, Parameter, TypeDeclaration, }, }; @@ -83,7 +83,7 @@ impl<'h> Context<'h> { match name.as_ref() { "Int" => { assert!(type_arguments.is_empty()); - self.push("uint64_t value;\n"); + self.push("int64_t value;\n"); } "List" => { assert_eq!(type_arguments.len(), 1); @@ -145,7 +145,7 @@ impl<'h> Context<'h> { } fn lower_assignment_definitions(&mut self) { for (name, assignment) in &self.mono.assignments { - self.lower_lambda_definitions_in(name, &assignment.body); + self.lower_lambda_definitions_in(name, &[], &assignment.body); self.push(format!("void {name}$init() {{\n")); self.lower_body_expressions(name, &assignment.body); @@ -179,7 +179,7 @@ impl<'h> Context<'h> { fn lower_function_definitions(&mut self) { for (name, function) in &self.mono.functions { if let BodyOrBuiltin::Body(body) = &function.body { - self.lower_lambda_definitions_in(name, body); + self.lower_lambda_definitions_in(name, &function.parameters, body); } self.lower_function_signature(name, function); @@ -188,9 +188,14 @@ impl<'h> Context<'h> { self.push("}\n\n"); } } - fn lower_lambda_definitions_in(&mut self, declaration_name: &str, body: &'h Body) { + fn lower_lambda_definitions_in( + &mut self, + declaration_name: &str, + declaration_parameters: &[Parameter], + body: &'h Body, + ) { Self::visit_lambdas_inside_body(body, &mut |id, lambda| { - let closure = lambda.closure_with_types(body); + let closure = lambda.closure_with_types(declaration_parameters, body); self.push(format!("struct {declaration_name}$lambda{id}_closure {{")); for (id, type_) in &closure { @@ -242,8 +247,12 @@ impl<'h> Context<'h> { | ExpressionKind::GlobalAssignmentReference(_) | ExpressionKind::LocalReference(_) | ExpressionKind::CallFunction { .. } - | ExpressionKind::CallLambda { .. } - | ExpressionKind::Switch { .. } => {} + | ExpressionKind::CallLambda { .. } => {} + ExpressionKind::Switch { cases, .. } => { + for case in cases.iter() { + Self::visit_lambdas_inside_body(&case.body, visitor); + } + } ExpressionKind::Lambda(lambda) => { Self::visit_lambdas_inside_body(&lambda.body, visitor); visitor(*id, lambda); @@ -387,7 +396,7 @@ impl<'h> Context<'h> { int length = snprintf(NULL, 0, \"%ld\", {int}->value); char* result = malloc(length + 1); snprintf(result, length + 1, \"%ld\", {int}->value); - + Text* result_pointer = malloc(sizeof(Text)); result_pointer->value = result; return result_pointer;", @@ -395,6 +404,17 @@ impl<'h> Context<'h> { )), BuiltinFunction::ListFilled => self.push(format!( "\ + if ({length}->value < 0) {{ + char* message_format = \"List length must not be negative; was %ld.\"; + int length = snprintf(NULL, 0, message_format, {length}->value); + char *message = malloc(length + 1); + snprintf(message, length + 1, message_format, {length}->value); + + Text *message_pointer = malloc(sizeof(Text)); + message_pointer->value = message; + builtinPanic$$Text(message_pointer); + }} + {list_type}* result_pointer = malloc(sizeof({list_type})); result_pointer->length = {length}->value; result_pointer->values = malloc({length}->value * sizeof({item_type})); @@ -407,6 +427,33 @@ impl<'h> Context<'h> { length = function.parameters[0].id, item = function.parameters[1].id, )), + BuiltinFunction::ListGenerate => self.push(format!( + "\ + if ({length}->value < 0) {{ + char* message_format = \"List length must not be negative; was %ld.\"; + int length = snprintf(NULL, 0, message_format, {length}->value); + char *message = malloc(length + 1); + snprintf(message, length + 1, message_format, {length}->value); + + Text *message_pointer = malloc(sizeof(Text)); + message_pointer->value = message; + builtinPanic$$Text(message_pointer); + }} + + {list_type}* result_pointer = malloc(sizeof({list_type})); + result_pointer->length = {length}->value; + result_pointer->values = malloc({length}->value * sizeof({item_type})); + for (uint64_t i = 0; i < {length}->value; i++) {{ + Int* index = malloc(sizeof(Int)); + index->value = i; + result_pointer->values[i] = {item_getter}->function({item_getter}->closure, index); + }} + return result_pointer;", + item_type = substitutions["T"], + list_type = function.return_type, + length = function.parameters[0].id, + item_getter = function.parameters[1].id, + )), BuiltinFunction::ListGet => self.push(format!( "\ {return_type}* result_pointer = malloc(sizeof({return_type})); @@ -458,7 +505,7 @@ impl<'h> Context<'h> { "\ {list_type}* result_pointer = malloc(sizeof({list_type})); result_pointer->length = 0; - result_pointer->values = nullptr; + result_pointer->values = NULL; return result_pointer;", list_type = function.return_type, )), @@ -719,8 +766,7 @@ impl<'h> Context<'h> { "{}* {id} = malloc(sizeof({}));", &expression.type_, &expression.type_, )); - // TODO: escape text - self.push(format!("{id}->value = \"{text}\";")); + self.push(format!("{id}->value = \"{}\";", text.escape_default())); } ExpressionKind::CreateStruct { struct_, fields } => { let TypeDeclaration::Struct { diff --git a/compiler_v4/src/string_to_ast/parser.rs b/compiler_v4/src/string_to_ast/parser.rs index 72ebaf93f..d0bd5df1c 100644 --- a/compiler_v4/src/string_to_ast/parser.rs +++ b/compiler_v4/src/string_to_ast/parser.rs @@ -51,11 +51,16 @@ impl<'s> Parser<'s> { pub fn string(self, start_offset: Offset) -> AstString { assert!(start_offset <= self.offset); AstString { - string: self.source[*start_offset..*self.offset].into(), + string: self.str(start_offset).into(), file: self.file.to_path_buf(), span: start_offset..self.offset, } } + #[must_use] + pub fn str(self, start_offset: Offset) -> &'s str { + assert!(start_offset <= self.offset); + &self.source[*start_offset..*self.offset] + } #[must_use] pub fn error_at_current_offset(self, message: impl Into) -> AstError { @@ -104,7 +109,7 @@ impl<'s> Parser<'s> { (self, self.string(start_offset)) } #[must_use] - fn consume_char(self) -> Option<(Parser<'s>, char)> { + pub fn consume_char(self) -> Option<(Parser<'s>, char)> { self.next_char().map(|c| { ( Parser { diff --git a/compiler_v4/src/string_to_ast/text.rs b/compiler_v4/src/string_to_ast/text.rs index ddb730129..f0724061e 100644 --- a/compiler_v4/src/string_to_ast/text.rs +++ b/compiler_v4/src/string_to_ast/text.rs @@ -4,7 +4,7 @@ use super::{ parser::{OptionOfParser, OptionOfParserWithValue, Parser}, whitespace::{AndTrailingWhitespace, ValueAndTrailingWhitespace}, }; -use crate::ast::{AstText, AstTextPart}; +use crate::ast::{AstError, AstResult, AstText, AstTextPart}; use tracing::instrument; // TODO: It might be a good idea to ignore text interpolations in patterns @@ -62,8 +62,43 @@ fn text_part_interpolation(parser: Parser) -> Option<(Parser, AstTextPart)> { } #[instrument(level = "trace")] -fn text_part_text(parser: Parser) -> Option<(Parser, AstTextPart)> { - parser - .consume_while_not_empty(|c| !matches!(c, '{' | '"' | '\r' | '\n')) - .map(|(parser, text)| (parser, AstTextPart::Text(text.string))) +fn text_part_text(mut parser: Parser) -> Option<(Parser, AstTextPart)> { + let mut text = String::new(); + let mut current_start = parser.offset(); + let mut errors = vec![]; + while let Some((new_parser, char)) = parser.consume_char() { + match char { + '{' | '"' | '\r' | '\n' => break, + '\\' => { + let Some((new_new_parser, char)) = new_parser.consume_char() else { + errors.push(new_parser.error_at_current_offset( + "Unexpected end of file, expected escaped character", + )); + break; + }; + text.push_str(parser.str(current_start)); + match char { + '\\' => text.push('\\'), + 'n' => text.push('\n'), + '"' => text.push('"'), + _ => errors.push(AstError { + unparsable_input: new_new_parser.string(new_parser.offset()), + error: "Invalid escape character.".to_string(), + }), + } + current_start = new_new_parser.offset(); + parser = new_new_parser; + } + _ => parser = new_parser, + } + } + + text.push_str(parser.str(current_start)); + if text.is_empty() && errors.is_empty() { + return None; + } + Some(( + parser, + AstTextPart::Text(AstResult::errors(text.into_boxed_str(), errors)), + )) } diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 9d6141004..25c425d9d 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -30,7 +30,7 @@ fun isLessThan[T: Compare](left: T, right: T) Bool { greater => false, } } -fun isLessThanOrEqualTo[T: Compare](left: T, right: T) Bool { +fun isAtMost[T: Compare](left: T, right: T) Bool { switch left.compareTo(right) { less => true, equal => true, @@ -44,7 +44,7 @@ fun isGreaterThan[T: Compare](left: T, right: T) Bool { greater => true, } } -fun isGreaterThanOrEqualTo[T: Compare](left: T, right: T) Bool { +fun isAtLeast[T: Compare](left: T, right: T) Bool { switch left.compareTo(right) { less => false, equal => true, @@ -52,6 +52,27 @@ fun isGreaterThanOrEqualTo[T: Compare](left: T, right: T) Bool { } } enum Ordering { less, equal, greater } +impl Ordering: Compare { + fun compareTo(self: Ordering, other: Ordering) Ordering { + switch self { + less => switch other { + less => Ordering.equal(), + equal => Ordering.less(), + greater => Ordering.less(), + }, + equal => switch other { + less => Ordering.greater(), + equal => Ordering.equal(), + greater => Ordering.less(), + }, + greater => switch other { + less => Ordering.greater(), + equal => Ordering.greater(), + greater => Ordering.equal(), + }, + } + } +} impl Ordering: ToText { fun toText(self: Ordering) Text { switch self { @@ -94,13 +115,13 @@ fun isPositive(self: Int) Bool { self.isGreaterThan(0) } fun isNonPositive(self: Int) Bool { - self.isLessThanOrEqualTo(0) + self.isAtMost(0) } fun isNegative(self: Int) Bool { self.isLessThan(0) } fun isNonNegative(self: Int) Bool { - self.isGreaterThanOrEqualTo(0) + self.isAtLeast(0) } fun absolute(self: Int) Int { switch self.isNegative() { @@ -230,13 +251,13 @@ fun characterAt(self: Text, index: Int) Maybe[Text] { } fun startsWith(self: Text, prefix: Text) Bool { - switch self.length().isGreaterThanOrEqualTo(prefix.length()) { + switch self.length().isAtLeast(prefix.length()) { false => false, true => self.getRange(0, prefix.length()).equals(prefix), } } fun endsWith(self: Text, suffix: Text) Bool { - switch self.length().isGreaterThanOrEqualTo(suffix.length()) { + switch self.length().isAtLeast(suffix.length()) { false => false, true => self.getRange(self.length().subtract(suffix.length()), self.length()).equals(suffix), } @@ -254,6 +275,26 @@ fun removeSuffix(self: Text, suffix: Text) Text { } } +fun split(self: Text, character: Text) List[Text] { + self.splitIf((char: Text) { char.equals(character) }) +} +fun splitIf(self: Text, predicate: (Text) Bool) List[Text] { + self.splitIfHelper(predicate, 0, 0 listOf[Text]()) +} +fun splitIfHelper(self: Text, predicate: (Text) Bool, currentStartOffset: Int, offset: Int, result: List[Text]) List[Text] { + switch self.characterAt(offset) { + none => result.append(self.getRange(currentStartOffset, offset)), + some(char) => switch predicate(char) { + true => { + let nextOffset = offset.add(1) + let nextResult = result.append(self.getRange(currentStartOffset, offset)) + self.splitIfHelper(predicate, nextOffset, nextOffset, nextResult) + }, + false => self.splitIfHelper(predicate, currentStartOffset, offset.add(1), result), + }, + } +} + fun indexOf(self: Text, other: Text) Maybe[Int] { self.builtinTextIndexOf(other) } @@ -307,7 +348,9 @@ struct List[T] = builtin fun listFilled[T](length: Int, item: T) List[T] { builtinListFilled(length, item) } -# TODO: listGenerate(…) +fun listGenerate[T](length: Int, generator: (Int) T) List[T] { + builtinListGenerate(length, generator) +} fun listOf[T]() List[T] { builtinListOf[T]() } @@ -317,7 +360,7 @@ fun listOf[T](item0: T) List[T] { fun listOf[T](item0: T, item1: T) List[T] { builtinListOf(item0, item1) } -fun listOf[T](item0: T, item1: T, item2) List[T] { +fun listOf[T](item0: T, item1: T, item2: T) List[T] { builtinListOf(item0, item1, item2) } fun listOf[T](item0: T, item1: T, item2: T, item3: T) List[T] { @@ -368,11 +411,142 @@ fun replace[T](list: List[T], index: Int, item: T) List[T] { fun removeAt[T](list: List[T], index: Int) List[T] { builtinListRemoveAt(list, index) } -# TODO: list.getRange(…), .concatenate(…), .firstIndexWhere(…), .firstWhere(…), .firstIndexOf(…), .lastIndexWhere(…), .lastWhere(…), .lastIndexOf(…) +fun concat[T](list0: List[T], list1: List[T]) List[T] { + listGenerate(list0.length().add(list1.length()), (i: Int) { + switch i.isLessThan(list0.length()) { + true => list0.get(i).unwrap(), + false => list1.get(i.subtract(list0.length())).unwrap(), + } + }) +} +fun getRange[T](list: List[T], startInclusive: Int, endExclusive: Int) List[T] { + switch startInclusive.isNonNegative() + .and(endExclusive.isNonNegative()) + .and(startInclusive.isAtMost(endExclusive)) + .and(endExclusive.isAtMost(list.length())) { + false => panic("Invalid range"), + true => listGenerate( + endExclusive.subtract(startInclusive), + (i: Int) { list.get(startInclusive.add(i)).unwrap() }, + ), + } +} +fun indexes[T](list: List[T]) List[Int] { + listGenerate(list.length(), (i: Int) { i }) +} + +# TODO: .firstIndexWhere(…), .firstWhere(…), .firstIndexOf(…), .lastIndexWhere(…), .lastWhere(…), .lastIndexOf(…) fun print[T: ToText](t: T) { builtinPrint(t.toText()) } +fun fold[T, R](list: List[T], initial: R, combine: (R, T) R) R { + list.foldHelper(0, initial, combine) +} +fun foldHelper[T, R](list: List[T], index: Int, current: R, combine: (R, T) R) R { + switch index.isLessThan(list.length()) { + true => list.foldHelper(index.add(1), combine(current, list.get(index).unwrap()), combine), + false => current, + } +} +fun reduce[T](list: List[T], combine: (T, T) T) Maybe[T] { + switch list.isEmpty() { + true => none[T](), + false => some(list.reduceHelper(1, list.get(0).unwrap(), combine)), + } +} +fun reduceHelper[T](list: List[T], index: Int, current: T, combine: (T, T) T) T { + switch index.isLessThan(list.length()) { + true => list.reduceHelper(index.add(1), combine(current, list.get(index).unwrap()), combine), + false => current, + } +} + +fun all[T](list: List[T], predicate: (T) Bool) Bool { + list.allHelper(predicate, 0) +} +fun allHelper[T](list: List[T], predicate: (T) Bool, index: Int) Bool { + switch list.get(index) { + some(item) => switch predicate(item) { + true => list.allHelper(predicate, index.add(1)), + false => false, + }, + none => true, + } +} +fun any[T](list: List[T], predicate: (T) Bool) Bool { + list.all((it: T) { predicate(it).not() }).not() +} + +fun isStrictlyAscending[T: Compare](list: List[T]) Bool { + list.isSortedBy((a: T, b: T) { a.compareTo(b).equals(Ordering.less()) }) +} +fun isStrictlyDescending[T: Compare](list: List[T]) Bool { + list.isSortedBy((a: T, b: T) { a.compareTo(b).equals(Ordering.greater()) }) +} +fun isSortedBy[T](list: List[T], checkPair: (T, T) Bool) Bool { + list.pairs().all((pair: Pair[T, T]) { checkPair(pair.first, pair.second) }) +} + +fun map[T, R](list: List[T], transform: (T) R) List[R] { + list.fold(listOf[R](), (result: List[R], item: T) { + result.append(transform(item)) + }) +} +fun filter[T](list: List[T], predicate: (T) Bool) List[T] { + list.fold(listOf[T](), (result: List[T], item: T) { + switch predicate(item) { + true => result.append(item), + false => result, + } + }) +} +fun windows[T](list: List[T], windowLength: Int) List[List[T]] { + # Returns a list over all contiguous windows of length `windowLength`. + # + # The windows overlap. If the `list` is shorter than `windowLength`, the + # resulting list is empty. + needs(windowLength.isPositive()) + + list.windowsHelper(windowLength, listOf[List[T]]()) +} +fun windowsHelper[T](list: List[T], windowLength: Int, resultSoFar: List[List[T]]) List[List[T]] { + let index = resultSoFar.length() + switch index.isAtMost(list.length().subtract(windowLength)) { + true => list.windowsHelper( + windowLength, + resultSoFar.append(list.getRange(index, index.add(windowLength))), + ), + false => resultSoFar, + } +} +fun pairs[T](list: List[T]) List[Pair[T, T]] { + # Returns a list over all contiguous pairs of items. + # + # The pairs overlap. If the `list` is shorter than two, the resulting list is + # empty. + list.windows(2).map((window: List[T]) { window.toPair().unwrap() }) +} +impl[T: ToText] List[T]: ToText { + fun toText(self: List[T]) Text { + let items = self.map((item: T) { item.toText() }) + .reduce((result: Text, item: Text) { "{result}, {item}" }) + .unwrapOr("") + "[{items}]" + } +} + +struct Pair[T0, T1] { + first: T0, + second: T1, +} +fun toPair[T](list: List[T]) Maybe[Pair[T, T]] { + switch list.length().equals(2) { + true => some(Pair[T, T](list.get(0).unwrap(), list.get(1).unwrap())), + false => none[Pair[T, T]](), + } +} + struct MyStruct { name: Text, color: Int, @@ -405,11 +579,17 @@ fun xor(a: Bool, b: Bool) Bool { fun implies(a: Bool, b: Bool) Bool { not(a).or(b) } -impl Bool: Equal { - fun equals(self: Bool, other: Bool) Bool { +impl Bool: Compare { + fun compareTo(self: Bool, other: Bool) Ordering { switch self { - true => other, - false => other.not(), + false => switch other { + false => Ordering.equal(), + true => Ordering.less(), + }, + true => switch other { + false => Ordering.greater(), + true => Ordering.equal(), + }, } } } @@ -433,11 +613,22 @@ fun none[T]() Maybe[T] { Maybe.none[T]() } fun unwrap[T](self: Maybe[T]) T { + # TODO: reuse `unwrapOrElse(…)` when `Never` gets unified with `T` + # self.unwrapOrElse(() { panic("`unwrap()` called on `none()`") }) switch self { some(value) => value, none => panic("`unwrap()` called on `none()`"), } } +fun unwrapOr[T](self: Maybe[T], defaultValue: T) T { + self.unwrapOrElse(() { defaultValue }) +} +fun unwrapOrElse[T](self: Maybe[T], getDefaultValue: () T) T { + switch self { + some(value) => value, + none => getDefaultValue(), + } +} fun isSome[T](self: Maybe[T]) Bool { switch self { some(value) => true, @@ -470,9 +661,20 @@ fun error[T, E](value: E) Result[T, E] { Result.error[T, E](value) } fun unwrap[T, E](self: Result[T, E]) T { + # TODO: reuse `unwrapOrElse(…)` when `Never` gets unified with `T` + # self.unwrapOrElse((error: E) { panic("`unwrap()` called on `error()`") }) + switch self { + ok(value) => value, + error(error) => panic("`unwrap()` called on `error()`"), + } +} +fun unwrapOr[T, E](self: Result[T, E], defaultValue: T) T { + self.unwrapOrElse((error: E) { defaultValue }) +} +fun unwrapOrElse[T, E](self: Result[T, E], getDefaultValue: (E) T) T { switch self { ok(value) => value, - error(value) => panic("`unwrap()` called on `error()`"), + error(error) => getDefaultValue(error), } } fun isOk[T, E](self: Result[T, E]) Bool { @@ -561,6 +763,8 @@ fun main() Int { repeat(3, () { print("Hello, World!") }) + print(listOf(1, 2, 3).map((x: Int) { x.multiply(2) })) + 0 }