diff --git a/.github/workflows/compiler.yaml b/.github/workflows/compiler.yaml index 2dbd38059..9c7922ad3 100644 --- a/.github/workflows/compiler.yaml +++ b/.github/workflows/compiler.yaml @@ -18,11 +18,11 @@ jobs: - uses: Swatinem/rust-cache@v2.7.5 - name: "Compiler: clippy" - run: cargo clippy -- --deny warnings + run: cargo clippy --package candy_compiler_v4 -- --deny warnings - name: "Compiler: test" - run: cargo test --workspace + run: cargo test --package candy_compiler_v4 - name: "Compiler: fmt" - run: cargo fmt --check + run: cargo fmt --package candy_compiler_v4 --check vscode-extension-check: name: Check VS Code Extension diff --git a/Cargo.lock b/Cargo.lock index b777ab2e5..2c5fde94b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,7 @@ dependencies = [ "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.48", + "syn 2.0.90", "which", ] @@ -177,6 +177,8 @@ dependencies = [ "petgraph", "replace_with", "rustc-hash 2.0.0", + "serde", + "serde_json", "strum", "tracing", "tracing-subscriber", @@ -573,7 +575,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.90", ] [[package]] @@ -831,7 +833,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.90", ] [[package]] @@ -926,7 +928,7 @@ checksum = "b185e7d068d6820411502efa14d8fbf010750485399402156b72dd2a548ef8e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.90", ] [[package]] @@ -1347,7 +1349,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a41cf62165e97c7f814d2221421dbb9afcbcdb0a88068e5ea206e19951c2cbb5" dependencies = [ "proc-macro2", - "syn 2.0.48", + "syn 2.0.90", ] [[package]] @@ -1376,9 +1378,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -1604,31 +1606,32 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] @@ -1743,7 +1746,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn 2.0.48", + "syn 2.0.90", ] [[package]] @@ -1759,9 +1762,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.48" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -1935,7 +1938,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" dependencies = [ "proc-macro2", "quote", - "syn 2.0.48", + "syn 2.0.90", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 0b61421a4..7f8b863a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,21 +1,21 @@ [workspace] resolver = "2" members = [ - "compiler_v4", - "compiler/backend_inkwell", - "compiler/cli", - "compiler/formatter", - "compiler/formatter/fuzz", - "compiler/frontend", - "compiler/fuzzer", - "compiler/language_server", - "compiler/vm", - "compiler/vm/fuzz", + "compiler_v4", + "compiler/backend_inkwell", + "compiler/cli", + "compiler/formatter", + "compiler/formatter/fuzz", + "compiler/frontend", + "compiler/fuzzer", + "compiler/language_server", + "compiler/vm", + "compiler/vm/fuzz", ] [workspace.package] edition = "2021" -rust-version = "1.78.0" +rust-version = "1.82.0" [profile.release] # This adds file and line number information to backtraces while only increasing diff --git a/compiler/backend_inkwell/src/lib.rs b/compiler/backend_inkwell/src/lib.rs index 1c633e86a..80b8848e3 100644 --- a/compiler/backend_inkwell/src/lib.rs +++ b/compiler/backend_inkwell/src/lib.rs @@ -1,14 +1,15 @@ #![feature(let_chains)] -#![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] -#![allow( - clippy::cognitive_complexity, - clippy::match_same_arms, - clippy::missing_errors_doc, - clippy::missing_panics_doc, - clippy::module_name_repetitions, - clippy::similar_names, - clippy::too_many_lines -)] +#![allow(clippy::all)] +// #![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] +// #![allow( +// clippy::cognitive_complexity, +// clippy::match_same_arms, +// clippy::missing_errors_doc, +// clippy::missing_panics_doc, +// clippy::module_name_repetitions, +// clippy::similar_names, +// clippy::too_many_lines +// )] use candy_frontend::{ builtin_functions::BuiltinFunction, diff --git a/compiler/cli/src/main.rs b/compiler/cli/src/main.rs index 20e34d609..8d33f747f 100644 --- a/compiler/cli/src/main.rs +++ b/compiler/cli/src/main.rs @@ -1,14 +1,14 @@ -#![feature(lazy_cell)] -#![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] -#![allow( - clippy::cognitive_complexity, - clippy::match_same_arms, - clippy::missing_errors_doc, - clippy::missing_panics_doc, - clippy::module_name_repetitions, - clippy::similar_names, - clippy::too_many_lines -)] +#![allow(clippy::all)] +// #![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] +// #![allow( +// clippy::cognitive_complexity, +// clippy::match_same_arms, +// clippy::missing_errors_doc, +// clippy::missing_panics_doc, +// clippy::module_name_repetitions, +// clippy::similar_names, +// clippy::too_many_lines +// )] use candy_vm::CAN_USE_STDOUT; use clap::Parser; diff --git a/compiler/formatter/src/lib.rs b/compiler/formatter/src/lib.rs index 6b3ef252d..fb69889f9 100644 --- a/compiler/formatter/src/lib.rs +++ b/compiler/formatter/src/lib.rs @@ -4,13 +4,14 @@ const_trait_impl, let_chains )] -#![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] -#![allow( - clippy::cognitive_complexity, - clippy::match_same_arms, - clippy::module_name_repetitions, - clippy::too_many_lines -)] +#![allow(clippy::all)] +// #![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] +// #![allow( +// clippy::cognitive_complexity, +// clippy::match_same_arms, +// clippy::module_name_repetitions, +// clippy::too_many_lines +// )] use candy_frontend::{cst::Cst, position::Offset}; use existing_whitespace::{TrailingWithIndentationConfig, WhitespacePositionInBody}; diff --git a/compiler/frontend/src/lib.rs b/compiler/frontend/src/lib.rs index 9152212f6..bd9ec48c5 100644 --- a/compiler/frontend/src/lib.rs +++ b/compiler/frontend/src/lib.rs @@ -1,23 +1,23 @@ #![feature( anonymous_lifetime_in_impl_trait, box_patterns, - entry_insert, extract_if, hasher_prefixfree_extras, io_error_more, let_chains, try_blocks )] -#![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] -#![allow( - clippy::cognitive_complexity, - clippy::match_same_arms, - clippy::missing_errors_doc, - clippy::missing_panics_doc, - clippy::module_name_repetitions, - clippy::similar_names, - clippy::too_many_lines -)] +#![allow(clippy::all)] +// #![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] +// #![allow( +// clippy::cognitive_complexity, +// clippy::match_same_arms, +// clippy::missing_errors_doc, +// clippy::missing_panics_doc, +// clippy::module_name_repetitions, +// clippy::similar_names, +// clippy::too_many_lines +// )] pub use self::tracing::{CallTracingMode, TracingConfig, TracingMode}; diff --git a/compiler/fuzzer/src/lib.rs b/compiler/fuzzer/src/lib.rs index c0fbeb607..a3603e280 100644 --- a/compiler/fuzzer/src/lib.rs +++ b/compiler/fuzzer/src/lib.rs @@ -1,6 +1,7 @@ #![feature(let_chains, round_char_boundary)] -#![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] -#![allow(clippy::missing_panics_doc, clippy::module_name_repetitions)] +#![allow(clippy::all)] +// #![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] +// #![allow(clippy::missing_panics_doc, clippy::module_name_repetitions)] mod coverage; mod fuzzer; diff --git a/compiler/language_server/src/lib.rs b/compiler/language_server/src/lib.rs index 36fe2c5c6..9795cc0a7 100644 --- a/compiler/language_server/src/lib.rs +++ b/compiler/language_server/src/lib.rs @@ -2,19 +2,19 @@ anonymous_lifetime_in_impl_trait, async_closure, box_patterns, - let_chains, - strict_provenance -)] -#![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] -#![allow( - clippy::future_not_send, // TODO: Fix clippy::future_not_send occurrences - clippy::large_enum_variant, - clippy::match_same_arms, - clippy::missing_errors_doc, - clippy::missing_panics_doc, - clippy::module_name_repetitions, - clippy::too_many_lines + let_chains )] +#![allow(clippy::all)] +// #![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] +// #![allow( +// clippy::future_not_send, // TODO: Fix clippy::future_not_send occurrences +// clippy::large_enum_variant, +// clippy::match_same_arms, +// clippy::missing_errors_doc, +// clippy::missing_panics_doc, +// clippy::module_name_repetitions, +// clippy::too_many_lines +// )] pub mod database; pub mod debug_adapter; diff --git a/compiler/vm/benches/benchmark.rs b/compiler/vm/benches/benchmark.rs index 5ddbfb09a..092baad30 100644 --- a/compiler/vm/benches/benchmark.rs +++ b/compiler/vm/benches/benchmark.rs @@ -1,5 +1,5 @@ -#![feature(absolute_path)] #![allow(unused_attributes)] +#![allow(clippy::all)] use candy_frontend::module::PackagesPath; use candy_vm::{ diff --git a/compiler/vm/src/instruction_pointer.rs b/compiler/vm/src/instruction_pointer.rs index 7a393205a..be6bc432c 100644 --- a/compiler/vm/src/instruction_pointer.rs +++ b/compiler/vm/src/instruction_pointer.rs @@ -17,16 +17,16 @@ impl InstructionPointer { } } impl Step for InstructionPointer { - fn steps_between(start: &Self, end: &Self) -> Option { - (**end).checked_sub(**start) + fn steps_between(start: &Self, end: &Self) -> (usize, Option) { + usize::steps_between(&**start, &**end) } fn forward_checked(start: Self, count: usize) -> Option { - (*start).checked_add(count).map(Self) + usize::forward_checked(*start, count).map(InstructionPointer) } fn backward_checked(start: Self, count: usize) -> Option { - (*start).checked_sub(count).map(Self) + usize::backward_checked(*start, count).map(InstructionPointer) } } impl Debug for InstructionPointer { diff --git a/compiler/vm/src/lib.rs b/compiler/vm/src/lib.rs index bec1adb15..3eae98833 100644 --- a/compiler/vm/src/lib.rs +++ b/compiler/vm/src/lib.rs @@ -8,7 +8,6 @@ nonzero_ops, slice_ptr_get, step_trait, - strict_provenance, try_blocks )] // We can't enable `unused_crate_dependencies` since it reports false positives about @@ -16,16 +15,17 @@ // https://github.com/rust-lang/rust/issues/57274 // https://github.com/rust-lang/rust/issues/95513 // https://github.com/rust-lang/rust-clippy/issues/4341 -#![warn(clippy::nursery, clippy::pedantic)] -#![allow( - clippy::large_enum_variant, - clippy::match_same_arms, - clippy::missing_errors_doc, - clippy::missing_panics_doc, - clippy::module_name_repetitions, - clippy::similar_names, - clippy::too_many_lines -)] +#![allow(clippy::all)] +// #![warn(clippy::nursery, clippy::pedantic)] +// #![allow( +// clippy::large_enum_variant, +// clippy::match_same_arms, +// clippy::missing_errors_doc, +// clippy::missing_panics_doc, +// clippy::module_name_repetitions, +// clippy::similar_names, +// clippy::too_many_lines +// )] pub use builtin_functions::CAN_USE_STDOUT; pub use instruction_pointer::InstructionPointer; diff --git a/compiler_v4/Cargo.toml b/compiler_v4/Cargo.toml index eac7daea7..446b12f9c 100644 --- a/compiler_v4/Cargo.toml +++ b/compiler_v4/Cargo.toml @@ -18,6 +18,8 @@ itertools = "0.12.0" petgraph = "0.6.5" replace_with = "0.1.7" rustc-hash = "2.0.0" +serde = { version = "1.0.216", features = ["derive"] } +serde_json = "1.0.133" strum = { version = "0.26.1", features = ["derive"] } tracing = { version = "0.1", features = ["release_max_level_debug"] } tracing-subscriber = { version = "0.3.16", features = ["registry"] } diff --git a/compiler_v4/src/ast.rs b/compiler_v4/src/ast.rs index d9fbe771b..4bdd468e4 100644 --- a/compiler_v4/src/ast.rs +++ b/compiler_v4/src/ast.rs @@ -175,6 +175,7 @@ pub struct AstTypeParameter { // Types +#[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum AstType { Named(AstNamedType), @@ -252,7 +253,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 +382,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 +598,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..8c36e6a58 100644 --- a/compiler_v4/src/ast_to_hir.rs +++ b/compiler_v4/src/ast_to_hir.rs @@ -7,9 +7,9 @@ use crate::{ }, error::CompilerError, hir::{ - self, Assignment, Body, BodyOrBuiltin, BuiltinFunction, ContainsError, Expression, - ExpressionKind, Function, FunctionSignature, FunctionType, Hir, Id, Impl, NamedType, - Parameter, ParameterType, SliceOfTypeParameter, StructField, SwitchCase, Trait, + self, Assignment, Body, BodyOrBuiltin, BuiltinFunction, BuiltinType, ContainsError, + Expression, ExpressionKind, Function, FunctionSignature, FunctionType, Hir, Id, Impl, + NamedType, Parameter, ParameterType, SliceOfTypeParameter, StructField, SwitchCase, Trait, TraitDefinition, TraitFunction, Type, TypeDeclaration, TypeDeclarationKind, TypeParameter, }, id::IdGenerator, @@ -130,7 +130,7 @@ struct ImplDeclaration<'a> { trait_: Trait, functions: FxHashMap>, } -impl<'a> ImplDeclaration<'a> { +impl ImplDeclaration<'_> { #[must_use] fn into_impl(self) -> Impl { Impl { @@ -166,7 +166,7 @@ struct FunctionDeclaration<'a> { signature: Signature, body: Option, } -impl<'a> FunctionDeclaration<'a> { +impl FunctionDeclaration<'_> { fn signature_to_string(&self) -> String { format!("{}{}", self.name, self.signature) } @@ -501,10 +501,21 @@ impl<'a> Context<'a> { self.lower_type_parameters(&[], None, struct_type.type_parameters.as_ref()); let self_base_type = NamedType::new(name.string.clone(), type_parameters.type_()).into(); - let fields = match &struct_type.kind { - AstStructKind::Builtin { .. } => None, - AstStructKind::UserDefined { fields, .. } => Some( - fields + let kind = match &struct_type.kind { + AstStructKind::Builtin { .. } => TypeDeclarationKind::Builtin(match &*name.string { + "Int" => BuiltinType::Int, + "List" => BuiltinType::List, + "Text" => BuiltinType::Text, + _ => { + self.add_error( + name.span.clone(), + format!("Unknown builtin type: `{}`", name.string), + ); + return; + } + }), + AstStructKind::UserDefined { fields, .. } => TypeDeclarationKind::Struct { + fields: fields .iter() .filter_map(|field| { let name = field.name.value()?; @@ -520,7 +531,7 @@ impl<'a> Context<'a> { }) }) .collect(), - ), + }, }; if self.traits.contains_key(&name.string) { @@ -540,7 +551,7 @@ impl<'a> Context<'a> { Entry::Vacant(entry) => { entry.insert(TypeDeclaration { type_parameters, - kind: TypeDeclarationKind::Struct { fields }, + kind, }); } }; @@ -1195,7 +1206,7 @@ impl<'a> Context<'a> { .collect_vec(), self_base_type, |builder| { - for parameter in function.signature.parameters.iter() { + for parameter in &function.signature.parameters { builder.push_parameter(parameter.clone()); } @@ -1229,7 +1240,7 @@ impl<'a> Context<'a> { .unwrap() } - fn get_all_functions_matching_name(&mut self, name: &str) -> Vec { + fn get_all_functions_matching_name(&self, name: &str) -> Vec { self.functions .iter() .chain(self.traits.iter().flat_map(|(_, trait_)| &trait_.functions)) @@ -1285,7 +1296,9 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { self.type_parameters, self.self_base_type, |builder| { - builder.local_identifiers = self.local_identifiers.clone(); + builder + .local_identifiers + .clone_from(&self.local_identifiers); fun(builder); self.global_assignment_dependencies .extend(&builder.global_assignment_dependencies); @@ -1357,7 +1370,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 +1410,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 @@ -1431,14 +1446,12 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { type_: NamedType::text().into(), } } - AstExpressionKind::Parenthesized(parenthesized) => { - return parenthesized - .inner - .value() - .map_or(LoweredExpression::Error, |it| { - self.lower_expression_raw(it, context_type) - }); - } + AstExpressionKind::Parenthesized(parenthesized) => parenthesized + .inner + .value() + .map_or(LoweredExpression::Error, |it| { + self.lower_expression_raw(it, context_type) + }), AstExpressionKind::Call(call) => { let type_arguments = call.type_arguments.as_ref().map(|it| { it.arguments @@ -1471,7 +1484,8 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { let type_declaration = self.context.hir.type_declarations[&type_].clone(); match &type_declaration.kind { - TypeDeclarationKind::Struct { .. } => { + TypeDeclarationKind::Builtin(_) + | TypeDeclarationKind::Struct { .. } => { self.context.add_error( key.span.clone(), format!( @@ -1517,6 +1531,13 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { let type_declaration = self.context.hir.type_declarations[&type_].clone(); match &type_declaration.kind { + TypeDeclarationKind::Builtin(builtin_type) => { + self.context.add_error( + call.receiver.span.clone(), + format!("Can't instantiate builtin type `{builtin_type}` directly."), + ); + LoweredExpression::Error + } TypeDeclarationKind::Struct { fields } => { // Foo(bar, baz) self.lower_struct_creation( @@ -1525,7 +1546,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { type_arguments.as_deref(), &type_, &type_declaration.type_parameters, - fields, + Some(fields), ) } TypeDeclarationKind::Enum { .. } => { @@ -1547,7 +1568,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) => { @@ -1564,9 +1585,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { } => match &receiver_type { Type::Named(named_type) => { let type_ = &self.context.hir.type_declarations[&named_type.name]; - if let TypeDeclarationKind::Struct { - fields: Some(fields), - } = &type_.kind + if let TypeDeclarationKind::Struct { fields } = &type_.kind && let Some(field) = fields.iter().find(|it| it.name == key.string) { return self.push_lowered( @@ -1610,11 +1629,21 @@ 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(); match &declaration.kind { + TypeDeclarationKind::Builtin(_) => { + self.context.add_error( + key.span.clone(), + format!( + "Builtin type `{type_:?}` doesn't have a field `{}`", + key.string, + ), + ); + LoweredExpression::Error + } TypeDeclarationKind::Struct { .. } => { self.context.add_error( key.span.clone(), @@ -1668,7 +1697,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { ); let body = self.build_inner(|builder| { - for parameter in parameters.iter() { + for parameter in ¶meters { builder.push_parameter(parameter.clone()); } @@ -1713,6 +1742,13 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { return LoweredExpression::Error; }; match &declaration.kind { + TypeDeclarationKind::Builtin(_) => { + self.context.add_error( + expression.span.clone(), + format!("Can't switch over builtin `{enum_:?}`"), + ); + return LoweredExpression::Error; + } TypeDeclarationKind::Struct { .. } => { self.context.add_error( expression.span.clone(), @@ -1859,7 +1895,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { } else if let Some(type_parameter) = self.type_parameters.iter().find(|it| it.name == *name) { LoweredExpression::TypeParameterReference(type_parameter.type_()) - } else if self.context.hir.type_declarations.get(name).is_some() { + } else if self.context.hir.type_declarations.contains_key(name) { LoweredExpression::NamedTypeReference(name.clone()) } else { self.context.add_error( @@ -1904,18 +1940,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(), @@ -2060,7 +2093,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { type_arguments: Option<&[Type]>, type_: &str, type_parameters: &[TypeParameter], - fields: &Option>, + fields: Option<&[StructField]>, ) -> LoweredExpression { let Some(fields) = fields else { self.context.add_error( @@ -2214,7 +2247,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> { .collect::>() } fn match_signature( - &mut self, + &self, trait_goal_and_subgoals: Option<(&SolverGoal, &[SolverGoal])>, type_parameters: &[TypeParameter], parameter_types: &[Type], diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index 98e0a93f9..b2e1f8464 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -75,21 +75,22 @@ impl ToText for Hir { .sorted_by_key(|(name, _)| *name) { match &declaration.kind { + TypeDeclarationKind::Builtin(_) => { + builder.push(format!("struct {name}")); + declaration.type_parameters.build_text(builder); + builder.push(" = builtin"); + } TypeDeclarationKind::Struct { fields } => { builder.push(format!("struct {name}")); declaration.type_parameters.build_text(builder); - if let Some(fields) = fields { - builder.push(" {"); - builder.push_children_custom_multiline(fields.iter(), |builder, it| { - builder.push(format!("{}: {},", it.name, it.type_)); - }); - if !fields.is_empty() { - builder.push_newline(); - } - builder.push("}"); - } else { - builder.push(" = builtin"); + builder.push(" {"); + builder.push_children_custom_multiline(fields.iter(), |builder, it| { + builder.push(format!("{}: {},", it.name, it.type_)); + }); + if !fields.is_empty() { + builder.push_newline(); } + builder.push("}"); } TypeDeclarationKind::Enum { variants } => { builder.push(format!("enum {name}")); @@ -117,7 +118,7 @@ impl ToText for Hir { (name.as_ref(), definition).build_text(builder); builder.push_newline(); } - for impl_ in self.impls.iter() { + for impl_ in &self.impls { impl_.build_text(builder); builder.push_newline(); } @@ -150,15 +151,30 @@ pub struct TypeDeclaration { } #[derive(Clone, Debug, Eq, PartialEq)] pub enum TypeDeclarationKind { + Builtin(BuiltinType), Struct { - /// `None` if the struct is a builtin. - fields: Option>, + fields: Box<[StructField]>, }, Enum { variants: Box<[(Box, Option)]>, }, } #[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum BuiltinType { + Int, + List, + Text, +} +impl Display for BuiltinType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Int => write!(f, "Int"), + Self::List => write!(f, "List[T]"), + Self::Text => write!(f, "Text"), + } + } +} +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct StructField { pub name: Box, pub type_: Type, @@ -732,8 +748,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 +767,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 +789,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 +817,7 @@ pub enum BuiltinFunction { IntSubtract, IntToText, ListFilled, + ListGenerate, ListGet, ListInsert, ListLength, @@ -966,6 +978,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..60f581383 100644 --- a/compiler_v4/src/hir_to_mono.rs +++ b/compiler_v4/src/hir_to_mono.rs @@ -2,6 +2,7 @@ use crate::{ ast_to_hir::TypeUnifier, hir::{self, BuiltinFunction, Hir, NamedType, ParameterType, Type}, id::IdGenerator, + memory_layout::lay_out_memory, mono::{self, Mono}, type_solver::{goals::SolverSolution, values::SolverVariable}, utils::HashMapExtension, @@ -33,12 +34,16 @@ impl<'h> Context<'h> { }; let main_function = context.lower_function(hir.main_function_id, &FxHashMap::default()); context.lower_function(BuiltinFunction::Panic.id(), &FxHashMap::default()); + + let type_declarations = context + .type_declarations + .into_iter() + .map(|(name, declaration)| (name, declaration.unwrap())) + .collect(); + let memory_layout_result = lay_out_memory(&type_declarations); + Mono { - type_declarations: context - .type_declarations - .into_iter() - .map(|(name, declaration)| (name, declaration.unwrap())) - .collect(), + type_declarations, assignments: context .assignments .into_iter() @@ -52,6 +57,8 @@ impl<'h> Context<'h> { .into_iter() .map(|(name, function)| (name, function.unwrap())) .collect(), + memory_layouts: memory_layout_result.memory_layouts, + type_declaration_order: memory_layout_result.type_declaration_order, main_function, } } @@ -170,7 +177,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 +237,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, @@ -275,32 +284,36 @@ impl<'h> Context<'h> { return mangled_name; }; match &declaration.kind { + hir::TypeDeclarationKind::Builtin(builtin_type) => { + entry.insert(None); + match builtin_type { + hir::BuiltinType::Int => { + mono::TypeDeclaration::Builtin(mono::BuiltinType::Int) + } + hir::BuiltinType::List => mono::TypeDeclaration::Builtin( + mono::BuiltinType::List(self.lower_type(&type_arguments[0])), + ), + hir::BuiltinType::Text => { + mono::TypeDeclaration::Builtin(mono::BuiltinType::Text) + } + } + } hir::TypeDeclarationKind::Struct { fields } => { entry.insert(None); let environment = hir::Type::build_environment( &declaration.type_parameters, type_arguments, ); - if let Some(fields) = fields.as_ref() { - let fields = fields - .iter() - .map(|field| { - ( - field.name.clone(), - self.lower_type(&field.type_.substitute(&environment)), - ) - }) - .collect(); - mono::TypeDeclaration::Struct { fields } - } else { - mono::TypeDeclaration::Builtin { - name: name.clone(), - type_arguments: type_arguments - .iter() - .map(|it| self.lower_type(it)) - .collect(), - } - } + let fields = fields + .iter() + .map(|field| { + ( + field.name.clone(), + self.lower_type(&field.type_.substitute(&environment)), + ) + }) + .collect(); + mono::TypeDeclaration::Struct { fields } } hir::TypeDeclarationKind::Enum { variants } => { entry.insert(None); @@ -356,7 +369,7 @@ impl<'h> Context<'h> { result.push_str(&type_.name); if !type_.type_arguments.is_empty() { result.push_str("$of$"); - for type_ in type_.type_arguments.iter() { + for type_ in &type_.type_arguments { Self::mangle_type_helper(result, type_); result.push('$'); } @@ -373,7 +386,7 @@ impl<'h> Context<'h> { result.push_str("$Fun$"); if !type_.parameter_types.is_empty() { result.push_str("of$"); - for type_ in type_.parameter_types.iter() { + for type_ in &type_.parameter_types { Self::mangle_type_helper(result, type_); result.push('$'); } @@ -425,7 +438,7 @@ impl<'c, 'h> BodyBuilder<'c, 'h> { id_generator: IdGenerator::default(), id_mapping: FxHashMap::default(), }; - builder.id_mapping = self.id_mapping.clone(); + builder.id_mapping.clone_from(&self.id_mapping); builder.id_generator = mem::take(&mut self.id_generator); fun(&mut builder); @@ -478,7 +491,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, @@ -556,6 +569,14 @@ impl<'c, 'h> BodyBuilder<'c, 'h> { } => { let value = self.lower_id(*value); let enum_ = self.lower_type(enum_); + + let mono::TypeDeclaration::Enum { variants } = + &self.context.type_declarations[&enum_].as_ref().unwrap() + else { + unreachable!(); + }; + let variants = variants.clone(); + let cases = cases .iter() .map(|case| { @@ -564,7 +585,19 @@ impl<'c, 'h> BodyBuilder<'c, 'h> { .map(|hir_id| (hir_id, self.id_generator.generate())); mono::SwitchCase { variant: case.variant.clone(), - value_id: value_ids.map(|(_, mir_id)| mir_id), + value: value_ids.map(|(_, mir_id)| { + ( + mir_id, + variants + .iter() + .find(|it| it.name == case.variant) + .unwrap() + .value_type + .as_ref() + .unwrap() + .clone(), + ) + }), body: self .build_inner(|builder| { if let Some((hir_id, mir_id)) = value_ids { diff --git a/compiler_v4/src/main.rs b/compiler_v4/src/main.rs index 2baea39d8..a56a68e89 100644 --- a/compiler_v4/src/main.rs +++ b/compiler_v4/src/main.rs @@ -2,10 +2,9 @@ anonymous_lifetime_in_impl_trait, box_patterns, if_let_guard, - iter_repeat_n, let_chains, - option_take_if, - try_blocks + try_blocks, + unsigned_is_multiple_of )] #![warn(clippy::nursery, clippy::pedantic, unused_crate_dependencies)] #![allow( @@ -27,7 +26,10 @@ use clap::{arg, Parser, Subcommand, ValueHint}; use error::CompilerError; use hir::Hir; use hir_to_mono::hir_to_mono; +use itertools::Itertools; use mono_to_c::mono_to_c; +use position::{Position, RangeOfOffset}; +use serde::Serialize; use std::{ fs, path::{Path, PathBuf}, @@ -46,6 +48,7 @@ mod error; mod hir; mod hir_to_mono; mod id; +mod memory_layout; mod mono; mod mono_to_c; mod position; @@ -61,6 +64,7 @@ enum CandyOptions { Debug(DebugOptions), Check(CheckOptions), Compile(CompileOptions), + ToolingAnalyze(ToolingAnalyzeOptions), } fn main() -> ProgramResult { @@ -72,6 +76,10 @@ fn main() -> ProgramResult { CandyOptions::Debug(options) => debug(options), CandyOptions::Check(options) => check(options), CandyOptions::Compile(options) => compile(options), + CandyOptions::ToolingAnalyze(options) => { + tooling_analyze(options); + Ok(()) + } } } pub type ProgramResult = Result<(), Exit>; @@ -122,8 +130,9 @@ fn debug(options: DebugOptions) -> ProgramResult { } return Err(Exit::CodeContainsErrors); } + let mono = hir_to_mono(&hir); - println!("{mono:?}"); + println!("{}", mono.to_text(true)); } } Ok(()) @@ -210,6 +219,66 @@ fn compile(options: CompileOptions) -> ProgramResult { Ok(()) } +#[derive(Parser, Debug)] +struct ToolingAnalyzeOptions { + /// The file to analyze. + #[arg(value_hint = ValueHint::FilePath)] + path: PathBuf, +} +#[allow(clippy::needless_pass_by_value)] +fn tooling_analyze(options: ToolingAnalyzeOptions) { + let path_str = options.path.to_string_lossy(); + let path = path_str.strip_prefix("file:/").unwrap(); + let source = fs::read_to_string(Path::new(path)) + .unwrap_or_else(|err| panic!("Couldn't open file `{path}`: {err:?}")); + + let (_, errors) = compile_hir(&options.path, &source); + + // TODO: request file content from VS Code extension + + let diagnostics = errors + .into_iter() + .map(|error| { + let span = error.span.to_positions(&source); + Diagnostic { + message: error.message.into_boxed_str(), + source: DiagnosticSource { + file: error.path.to_string_lossy().into_owned().into_boxed_str(), + start: span.start, + end: span.end, + }, + } + }) + .collect_vec(); + println!( + "{}", + serde_json::to_string(&Message::Diagnostics { diagnostics }).unwrap(), + ); +} + +#[derive(Serialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum Message { + #[allow(dead_code)] + ReadFile { + path: String, + }, + Diagnostics { + diagnostics: Vec, + }, +} +#[derive(Serialize)] +struct Diagnostic { + pub message: Box, + pub source: DiagnosticSource, +} +#[derive(Serialize)] +struct DiagnosticSource { + pub file: Box, + pub start: Position, + pub end: Position, +} + fn compile_hir(path: &Path, source: &str) -> (Hir, Vec) { let asts = string_to_ast::string_to_ast(path, source); let mut errors = asts.collect_errors(); diff --git a/compiler_v4/src/memory_layout.rs b/compiler_v4/src/memory_layout.rs new file mode 100644 index 000000000..391c25355 --- /dev/null +++ b/compiler_v4/src/memory_layout.rs @@ -0,0 +1,221 @@ +use crate::{ + mono::{BuiltinType, TypeDeclaration}, + utils::HashSetExtension, +}; +use itertools::Itertools; +use rustc_hash::{FxHashMap, FxHashSet}; +use std::{cmp::Reverse, collections::hash_map::Entry}; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TypeLayout { + pub layout: Layout, + pub kind: TypeLayoutKind, +} +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum TypeLayoutKind { + Builtin, + Struct { + field_offsets: FxHashMap, usize>, + }, + Enum { + tag_offset: usize, + boxed_variants: FxHashSet>, + }, +} + +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct Layout { + size: usize, + alignment: Alignment, +} +impl Layout { + pub const POINTER: Self = Self::new(8, Alignment::_8); + + #[must_use] + pub const fn new(size: usize, alignment: Alignment) -> Self { + Self { size, alignment } + } +} +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub enum Alignment { + #[default] + _1, + _2, + _4, + _8, +} +impl Alignment { + #[must_use] + const fn get(self) -> usize { + match self { + Self::_1 => 1, + Self::_2 => 2, + Self::_4 => 4, + Self::_8 => 8, + } + } +} + +#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] +pub struct AggregateLayout { + layout: Layout, + part_offsets: Box<[usize]>, +} + +pub struct LayoutResult { + pub memory_layouts: FxHashMap, TypeLayout>, + pub type_declaration_order: Box<[Box]>, +} + +pub fn lay_out_memory(type_declarations: &FxHashMap, TypeDeclaration>) -> LayoutResult { + let mut context = Context::new(type_declarations); + for type_ in type_declarations.keys() { + context.lay_out(type_); + } + let memory_layouts = context + .memory_layouts + .into_iter() + .map(|(type_, layout)| (type_, layout.unwrap())) + .collect(); + LayoutResult { + memory_layouts, + type_declaration_order: context.sorted_declarations.into_boxed_slice(), + } +} +struct Context<'m> { + type_declarations: &'m FxHashMap, TypeDeclaration>, + memory_layouts: FxHashMap, Option>, + sorted_declarations: Vec>, +} +impl<'m> Context<'m> { + fn new(type_declarations: &'m FxHashMap, TypeDeclaration>) -> Self { + Self { + type_declarations, + memory_layouts: FxHashMap::default(), + sorted_declarations: Vec::new(), + } + } + + fn lay_out(&mut self, type_: &str) -> Option { + match self.memory_layouts.entry(type_.into()) { + Entry::Occupied(entry) => { + return entry.get().as_ref().map(|it| it.layout); + } + Entry::Vacant(entry) => { + entry.insert(None); + } + } + + let declaration = &self.type_declarations[type_]; + let type_layout = match declaration { + TypeDeclaration::Builtin(builtin_type) => { + let layout = match builtin_type { + BuiltinType::Int => Layout::new(8, Alignment::_8), + BuiltinType::List(_) => Layout::new(16, Alignment::_8), + BuiltinType::Text => Layout::new(8, Alignment::_8), + }; + TypeLayout { + layout, + kind: TypeLayoutKind::Builtin, + } + } + TypeDeclaration::Struct { fields } => { + let Some(field_layouts) = fields + .iter() + .map(|(name, type_)| try { (name, self.lay_out(type_)?) }) + .collect::>>() + else { + assert!(self.memory_layouts.remove(type_).unwrap().is_none()); + return None; + }; + let parts = field_layouts + .iter() + .map(|(_, layout)| *layout) + .collect::>(); + let aggregate_layout = Self::lay_out_aggregate(&parts); + TypeLayout { + layout: aggregate_layout.layout, + kind: TypeLayoutKind::Struct { + field_offsets: field_layouts + .iter() + .zip_eq(aggregate_layout.part_offsets.iter()) + .map(|((name, _), offset)| ((*name).clone(), *offset)) + .collect(), + }, + } + } + TypeDeclaration::Enum { variants } => { + if variants.len() > 256 { + todo!("support enums with more than 256 variants") + } + + let mut size = 0; + let mut alignment = Alignment::default(); + let mut boxed_variants = FxHashSet::default(); + for variant in variants { + if let Some(value_type) = variant.value_type.as_ref() { + let layout = self.lay_out(value_type).unwrap_or_else(|| { + boxed_variants.force_insert(variant.name.clone()); + Layout::POINTER + }); + size = size.max(layout.size); + alignment = alignment.max(layout.alignment); + } + } + let tag_offset = size; + size += 1; + TypeLayout { + layout: Layout { size, alignment }, + kind: TypeLayoutKind::Enum { + tag_offset, + boxed_variants, + }, + } + } + TypeDeclaration::Function { .. } => TypeLayout { + layout: Layout::new(16, Alignment::_8), + kind: TypeLayoutKind::Builtin, + }, + }; + self.sorted_declarations.push(type_.into()); + let layout = type_layout.layout; + assert!(self + .memory_layouts + .insert(type_.into(), Some(type_layout)) + .unwrap() + .is_none()); + Some(layout) + } + + fn lay_out_aggregate(parts: &[Layout]) -> AggregateLayout { + if parts.is_empty() { + return AggregateLayout::default(); + } + + let parts = parts + .iter() + .enumerate() + .sorted_by_key(|(index, layout)| { + (Reverse(layout.alignment), Reverse(layout.size), *index) + }) + .collect_vec(); + let alignment = parts.first().unwrap().1.alignment; + + let mut part_offsets = Box::<[usize]>::new_uninit_slice(parts.len()); + let mut offset = 0usize; + for (index, layout) in parts { + offset = offset.next_multiple_of(layout.alignment.get()); + part_offsets[index].write(offset); + offset += layout.size; + } + + let part_offsets = unsafe { part_offsets.assume_init() }; + AggregateLayout { + layout: Layout { + size: offset, + alignment, + }, + part_offsets, + } + } +} diff --git a/compiler_v4/src/mono.rs b/compiler_v4/src/mono.rs index dd2f493bc..5b2955284 100644 --- a/compiler_v4/src/mono.rs +++ b/compiler_v4/src/mono.rs @@ -1,5 +1,11 @@ -use crate::{hir::BuiltinFunction, impl_countable_id}; +use crate::{ + hir::BuiltinFunction, + impl_countable_id, + memory_layout::TypeLayout, + to_text::{TextBuilder, ToText}, +}; use derive_more::Deref; +use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use std::fmt::{self, Display, Formatter}; @@ -11,22 +17,103 @@ 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 { pub type_declarations: FxHashMap, TypeDeclaration>, + pub memory_layouts: FxHashMap, TypeLayout>, + pub type_declaration_order: Box<[Box]>, pub assignments: FxHashMap, Assignment>, pub assignment_initialization_order: Box<[Box]>, 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(builtin_type) => { + builder.push(format!("builtin struct {name} = {builtin_type}")); + } + 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 { + (&**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 { - Builtin { - name: Box, - type_arguments: Box<[Box]>, - }, + Builtin(BuiltinType), Struct { fields: Box<[(Box, Box)]>, }, @@ -39,6 +126,21 @@ pub enum TypeDeclaration { }, } #[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum BuiltinType { + Int, + List(Box), + Text, +} +impl Display for BuiltinType { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + Self::Int => write!(f, "Int"), + Self::List(item_type) => write!(f, "List[{item_type}]"), + Self::Text => write!(f, "Text"), + } + } +} +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct EnumVariant { pub name: Box, pub value_type: Option>, @@ -49,6 +151,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 +165,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 { @@ -95,7 +246,7 @@ impl Body { match &expression.kind { ExpressionKind::Int(_) | ExpressionKind::Text(_) => {} ExpressionKind::CreateStruct { fields, .. } => { - defined_ids.extend(fields.iter()); + referenced_ids.extend(fields.iter()); } ExpressionKind::StructAccess { struct_, .. } => { referenced_ids.insert(*struct_); @@ -116,8 +267,15 @@ impl Body { referenced_ids.insert(*lambda); referenced_ids.extend(arguments.iter()); } - ExpressionKind::Switch { value, .. } => { + ExpressionKind::Switch { value, cases, .. } => { referenced_ids.insert(*value); + for case in &**cases { + if let Some((value_id, _)) = &case.value { + referenced_ids.insert(*value_id); + } + case.body + .collect_defined_and_referenced_ids(defined_ids, referenced_ids); + } } ExpressionKind::Lambda(Lambda { parameters, body }) => { defined_ids.extend(parameters.iter().map(|it| it.id)); @@ -127,22 +285,49 @@ impl Body { } } #[must_use] - pub fn find_expression(&self, id: Id) -> Option<&Expression> { + pub fn find_expression(&self, id: Id) -> Option<(&str, Option<&Expression>)> { self.expressions.iter().find_map(|(it_id, _, expression)| { if *it_id == id { - return Some(expression); + return Some((expression.type_.as_ref(), Some(expression))); } match &expression.kind { - ExpressionKind::Switch { cases, .. } => { - cases.iter().find_map(|it| it.body.find_expression(id)) - } - ExpressionKind::Lambda(Lambda { body, .. }) => body.find_expression(id), + ExpressionKind::Switch { cases, .. } => cases.iter().find_map(|it| { + if let Some((value_id, box value_type)) = &it.value + && *value_id == id + { + return Some((value_type, None)); + } + it.body.find_expression(id) + }), + ExpressionKind::Lambda(Lambda { parameters, body }) => parameters + .iter() + .find(|it| it.id == id) + .map(|it| (it.type_.as_ref(), None)) + .or_else(|| body.find_expression(id)), _ => None, } }) } } +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,13 +369,81 @@ 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 { pub variant: Box, - pub value_id: Option, + pub value: Option<(Id, Box)>, pub body: Body, } +impl ToText for SwitchCase { + fn build_text(&self, builder: &mut TextBuilder) { + builder.push(format!("{}", self.variant)); + if let Some((value_id, value_type)) = &self.value { + builder.push(format!("({value_id}: {value_type})")); + } + builder.push(" => "); + self.body.build_text(builder); + } +} #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Lambda { @@ -199,10 +452,33 @@ 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:?}" + ) + }) + .0 + .to_string() + .into_boxed_str() + }, + |it| it.type_.clone(), + ) + ) + }) .collect() } #[must_use] @@ -215,3 +491,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..79c634dfb 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -1,11 +1,13 @@ use crate::{ hir::BuiltinFunction, + memory_layout::TypeLayoutKind, mono::{ - Body, BodyOrBuiltin, Expression, ExpressionKind, Function, Id, Lambda, Mono, - TypeDeclaration, + Body, BodyOrBuiltin, BuiltinType, Expression, ExpressionKind, Function, Id, Lambda, Mono, + Parameter, TypeDeclaration, }, }; use itertools::Itertools; +use rustc_hash::FxHashSet; pub fn mono_to_c(mono: &Mono) -> String { let mut context = Context::new(mono); @@ -58,13 +60,10 @@ impl<'h> Context<'h> { self.lower_function_definitions(); self.push("int main() {\n"); - for name in self.mono.assignment_initialization_order.iter() { + for name in &self.mono.assignment_initialization_order { self.push(format!("{name}$init();\n")); } - self.push(format!( - "return {}()->value;\n}}\n", - self.mono.main_function, - )); + self.push(format!("return {}().value;\n}}\n", self.mono.main_function,)); } fn lower_type_forward_declarations(&mut self) { @@ -73,50 +72,53 @@ impl<'h> Context<'h> { } } fn lower_type_declarations(&mut self) { - for (name, declaration) in &self.mono.type_declarations { + for name in &self.mono.type_declaration_order { + let declaration = &self.mono.type_declarations[name]; self.push(format!("struct {name} {{\n")); match declaration { - TypeDeclaration::Builtin { - name, - type_arguments, - } => { - match name.as_ref() { - "Int" => { - assert!(type_arguments.is_empty()); - self.push("uint64_t value;\n"); + TypeDeclaration::Builtin(builtin_type) => { + match builtin_type { + BuiltinType::Int => { + self.push("int64_t value;\n"); } - "List" => { - assert_eq!(type_arguments.len(), 1); + BuiltinType::List(item_type_) => { self.push("uint64_t length;\n"); - self.push(format!("{}** values;\n", type_arguments[0])); + self.push(format!("{item_type_}* values;\n")); } - "Text" => { - assert!(type_arguments.is_empty()); + BuiltinType::Text => { self.push("char* value;\n"); } - _ => panic!("Unknown builtin type: {name}"), } self.push("};\n"); } TypeDeclaration::Struct { fields } => { - for (name, type_) in fields.iter() { - self.push(format!("{type_}* {name}; ")); + for (name, type_) in &**fields { + self.push(format!("{type_} {name}; ")); } self.push("};\n"); } TypeDeclaration::Enum { variants } => { if !variants.is_empty() { self.push("enum {"); - for variant in variants.iter() { + for variant in &**variants { self.push(format!("{name}_{},", variant.name)); } self.push("} variant;\n"); } self.push("union {"); - for variant in variants.iter() { + let boxed_variants = self.get_boxed_variants(name).clone(); + for variant in &**variants { if let Some(value_type) = &variant.value_type { - self.push(format!("{value_type}* {};", variant.name)); + self.push(format!( + "{value_type}{} {};", + if boxed_variants.contains(&*variant.name) { + "*" + } else { + "" + }, + variant.name, + )); } } self.push("} value;\n};\n"); @@ -126,9 +128,9 @@ impl<'h> Context<'h> { return_type, } => { self.push("void* closure;\n"); - self.push(format!("{return_type}* (*function)(void*")); - for parameter_type in parameter_types.iter() { - self.push(format!(", {parameter_type}*")); + self.push(format!("{return_type} (*function)(void*")); + for parameter_type in &**parameter_types { + self.push(format!(", {parameter_type}")); } self.push(");\n"); self.push("};\n"); @@ -136,16 +138,23 @@ impl<'h> Context<'h> { } } } + fn get_boxed_variants(&self, enum_type: &str) -> &FxHashSet> { + let TypeLayoutKind::Enum { boxed_variants, .. } = &self.mono.memory_layouts[enum_type].kind + else { + panic!("Not an enum type: `{enum_type}`"); + }; + boxed_variants + } fn lower_assignment_declarations(&mut self) { for (name, assignment) in &self.mono.assignments { self.lower_lambda_declarations_in(name, &assignment.body); - self.push(format!("{}* {name};\n", &assignment.type_)); + self.push(format!("{} {name};\n", &assignment.type_)); } } 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 +188,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,13 +197,18 @@ 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 { - self.push(format!("{type_}* {id}; ")); + self.push(format!("{type_} {id}; ")); } self.push("};\n"); @@ -204,29 +218,29 @@ impl<'h> Context<'h> { "{declaration_name}$lambda{id}_closure* closure = raw_closure;\n" )); for (id, type_) in &closure { - self.push(format!("{type_}* {id} = closure->{id};\n")); + self.push(format!("{type_} {id} = closure->{id};\n")); } self.lower_body(declaration_name, &lambda.body); self.push("}\n"); }); } fn lower_function_signature(&mut self, name: &str, function: &Function) { - self.push(format!("{}* {name}(", &function.return_type)); + self.push(format!("{} {name}(", &function.return_type)); for (index, parameter) in function.parameters.iter().enumerate() { if index > 0 { self.push(", "); } - self.push(format!("{}* {}", ¶meter.type_, parameter.id)); + self.push(format!("{} {}", ¶meter.type_, parameter.id)); } self.push(")"); } fn lower_lambda_signature(&mut self, declaration_name: &str, id: Id, lambda: &Lambda) { self.push(format!( - "{}* {declaration_name}$lambda{id}_function(void* raw_closure", + "{} {declaration_name}$lambda{id}_function(void* raw_closure", &lambda.body.return_type() )); - for parameter in lambda.parameters.iter() { - self.push(format!(", {}* {}", ¶meter.type_, parameter.id)); + for parameter in &lambda.parameters { + self.push(format!(", {} {}", ¶meter.type_, parameter.id)); } self.push(")"); } @@ -242,8 +256,12 @@ impl<'h> Context<'h> { | ExpressionKind::GlobalAssignmentReference(_) | ExpressionKind::LocalReference(_) | ExpressionKind::CallFunction { .. } - | ExpressionKind::CallLambda { .. } - | ExpressionKind::Switch { .. } => {} + | ExpressionKind::CallLambda { .. } => {} + ExpressionKind::Switch { cases, .. } => { + for case in &**cases { + Self::visit_lambdas_inside_body(&case.body, visitor); + } + } ExpressionKind::Lambda(lambda) => { Self::visit_lambdas_inside_body(&lambda.body, visitor); visitor(*id, lambda); @@ -262,185 +280,208 @@ impl<'h> Context<'h> { match builtin_function { BuiltinFunction::IntAdd => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {a}->value + {b}->value; - return result_pointer;", + Int result = {{.value = {a}.value + {b}.value}}; + return result;", a = function.parameters[0].id, b = function.parameters[1].id, )), BuiltinFunction::IntBitwiseAnd => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {a}->value & {b}->value; - return result_pointer;", + Int result = {{.value = {a}.value & {b}.value}}; + return result;", a = function.parameters[0].id, b = function.parameters[1].id, )), BuiltinFunction::IntBitwiseOr => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {a}->value | {b}->value; - return result_pointer;", + Int result = {{.value = {a}.value | {b}.value}}; + return result;", a = function.parameters[0].id, b = function.parameters[1].id, )), BuiltinFunction::IntBitwiseXor => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {a}->value ^ {b}->value; - return result_pointer;", + Int result = {{.value = {a}.value ^ {b}.value}}; + return result;", a = function.parameters[0].id, b = function.parameters[1].id, )), BuiltinFunction::IntCompareTo => self.push(format!( "\ - Ordering* result_pointer = malloc(sizeof(Ordering)); - result_pointer->variant = {a}->value < {b}->value ? Ordering_less - : {a}->value == {b}->value ? Ordering_equal - : Ordering_greater; - return result_pointer;", + Ordering result = {{.variant = {a}.value < {b}.value ? Ordering_less + : {a}.value == {b}.value ? Ordering_equal + : Ordering_greater}}; + return result;", a = function.parameters[0].id, b = function.parameters[1].id, )), BuiltinFunction::IntDivideTruncating => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {dividend}->value / {divisor}->value; - return result_pointer;", + Int result = {{.value = {dividend}.value / {divisor}.value}}; + return result;", dividend = function.parameters[0].id, divisor = function.parameters[1].id, )), BuiltinFunction::IntMultiply => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {factorA}->value * {factorB}->value; - return result_pointer;", + Int result = {{.value = {factorA}.value * {factorB}.value}}; + return result;", factorA = function.parameters[0].id, factorB = function.parameters[1].id, )), BuiltinFunction::IntParse => self.push(format!( "\ - {return_type}* result_pointer = malloc(sizeof({return_type})); + {return_type} result; char *end_pointer; errno = 0; - uint64_t value = strtol({text}->value, &end_pointer, 10); + uint64_t value = strtol({text}.value, &end_pointer, 10); if (errno == ERANGE) {{ - result_pointer->variant = {return_type}_error; - result_pointer->value.error = malloc(sizeof(Text)); - result_pointer->value.error->value = \"Value is out of range.\"; - }} else if (end_pointer == {text}->value) {{ - result_pointer->variant = {return_type}_error; - result_pointer->value.error = malloc(sizeof(Text)); - result_pointer->value.error->value = \"Text is empty.\"; + result = {{ + .variant = {return_type}_error, + .value.error = {{.value = \"Value is out of range.\"}}, + }}; + }} else if (end_pointer == {text}.value) {{ + result = {{ + .variant = {return_type}_error, + .value.error = {{.value = \"Text is empty.\"}}, + }}; }} else if (*end_pointer != '\\0') {{ char* message_format = \"Non-numeric character \\\"%c\\\" at index %ld.\"; - int length = snprintf(NULL, 0, message_format, *end_pointer, end_pointer - {text}->value); + int length = snprintf(NULL, 0, message_format, *end_pointer, end_pointer - {text}.value); char *message = malloc(length + 1); - snprintf(message, length + 1, message_format, *end_pointer, end_pointer - {text}->value); - - result_pointer->variant = {return_type}_error; - result_pointer->value.error = malloc(sizeof(Text)); - result_pointer->value.error->value = message; + snprintf(message, length + 1, message_format, *end_pointer, end_pointer - {text}.value); + result = {{ + .variant = {return_type}_error, + .value.error = {{.value = message}}, + }}; }} else {{ - result_pointer->variant = {return_type}_ok; - result_pointer->value.ok = malloc(sizeof(Int)); - result_pointer->value.ok ->value = value; + result = {{ + .variant = {return_type}_ok, + .value.ok = {{.value = value}}, + }}; }} - return result_pointer;", + return result;", text = function.parameters[0].id, return_type = function.return_type, )), BuiltinFunction::IntRemainder => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {dividend}->value % {divisor}->value; - return result_pointer;", + Int result = {{.value = {dividend}.value % {divisor}.value}}; + return result;", dividend = function.parameters[0].id, divisor = function.parameters[1].id, )), BuiltinFunction::IntShiftLeft => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {value}->value << {amount}->value; - return result_pointer;", + Int result = {{.value = {value}.value << {amount}.value}}; + return result;", value = function.parameters[0].id, amount = function.parameters[1].id, )), BuiltinFunction::IntShiftRight => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {value}->value >> {amount}->value; - return result_pointer;", + Int result = {{.value = {value}.value >> {amount}.value}}; + return result;", value = function.parameters[0].id, amount = function.parameters[1].id, )), BuiltinFunction::IntSubtract => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {minuend}->value - {subtrahend}->value; - return result_pointer;", + Int result = {{.value = {minuend}.value - {subtrahend}.value}}; + return result;", minuend = function.parameters[0].id, subtrahend = function.parameters[1].id, )), BuiltinFunction::IntToText => self.push(format!( "\ - 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;", + int length = snprintf(NULL, 0, \"%ld\", {int}.value); + Text result = {{.value = malloc(length + 1)}}; + snprintf(result.value, length + 1, \"%ld\", {int}.value); + return result;", int = function.parameters[0].id, )), BuiltinFunction::ListFilled => self.push(format!( "\ - {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++) {{ - result_pointer->values[i] = {item}; + if ({length}.value < 0) {{ + char* message_format = \"List length must not be negative; was %ld.\"; + int length = snprintf(NULL, 0, message_format, {length}.value); + Text message = {{.value = malloc(length + 1)}}; + snprintf(message.value, length + 1, message_format, {length}.value); + builtinPanic$$Text(message); }} - return result_pointer;", + + {list_type} result = {{ + length = {length}.value, + values = malloc({length}.value * sizeof({item_type})), + }}; + for (uint64_t i = 0; i < {length}.value; i++) {{ + result.values[i] = {item}; + }} + return result;", item_type = substitutions["T"], list_type = function.return_type, 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); + Text message = {{.value = malloc(length + 1)}}; + snprintf(message.value, length + 1, message_format, {length}.value); + builtinPanic$$Text(message); + }} + + {list_type} result = {{ + .length = {length}.value, + .values = malloc({length}.value * sizeof({item_type}*)), + }}; + for (uint64_t i = 0; i < {length}.value; i++) {{ + Int index = {{.value = i}}; + result.values[i] = {item_getter}.function({item_getter}.closure, index); + }} + return result;", + 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})); - if (0 <= {index}->value && {index}->value < {list}->length) {{ - result_pointer->variant = {return_type}_some; - result_pointer->value.some = {list}->values[{index}->value]; + {return_type} result = malloc(sizeof({return_type})); + if (0 <= {index}.value && {index}.value < {list}.length) {{ + result = {{ + .variant = {return_type}_some, + .value.some = {list}.values[{index}.value], + }}; }} else {{ - result_pointer->variant = {return_type}_none; + result = {{.variant = {return_type}_none}}; }} - return result_pointer;", + return result;", return_type = function.return_type, list = function.parameters[0].id, index = function.parameters[1].id, )), BuiltinFunction::ListInsert => self.push(format!( "\ - if (0 > {index}->value || {index}->value > {list}->length) {{ + if (0 > {index}.value || {index}.value > {list}.length) {{ char* message_format = \"Index out of bounds: Tried inserting at index %ld in list of length %ld.\"; - int length = snprintf(NULL, 0, message_format, {index}->value, {list}->length); - char *message = malloc(length + 1); - snprintf(message, length + 1, message_format, {index}->value, {list}->length); - - Text *message_pointer = malloc(sizeof(Text)); - message_pointer->value = message; - builtinPanic$$Text(message_pointer); + int length = snprintf(NULL, 0, message_format, {index}.value, {list}.length); + Text message = {{.value = malloc(length + 1)}}; + snprintf(message.value, length + 1, message_format, {index}.value, {list}.length); + builtinPanic$$Text(message); }} - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = {list}->length + 1; - result_pointer->values = malloc(result_pointer->length * sizeof({item_type})); - memcpy(result_pointer->values, {list}->values, {index}->value * sizeof({item_type})); - result_pointer->values[{index}->value] = {item}; - memcpy(result_pointer->values + {index}->value + 1, {list}->values + {index}->value, ({list}->length - {index}->value) * sizeof({item_type})); - return result_pointer;", + uint64_t length = {list}.length + 1; + {list_type} result = {{ + .length = length, + .values = malloc(length * sizeof({item_type})), + }}; + memcpy(result.values, {list}.values, {index}.value * sizeof({item_type}*)); + result.values[{index}.value] = {item}; + memcpy(result.values + {index}.value + 1, {list}.values + {index}.value, ({list}.length - {index}.value) * sizeof({item_type})); + return result;", item_type = substitutions["T"], list_type = function.return_type, list = function.parameters[0].id, @@ -449,38 +490,40 @@ impl<'h> Context<'h> { )), BuiltinFunction::ListLength => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {list}->length; - return result_pointer;", + Int result = {{.value = {list}.length}}; + return result;", list = function.parameters[0].id, )), BuiltinFunction::ListOf0 => self.push(format!( "\ - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = 0; - result_pointer->values = nullptr; - return result_pointer;", + {list_type} result = {{ + .length = 0, + .values = NULL, + }}; + return result;", list_type = function.return_type, )), BuiltinFunction::ListOf1 => self.push(format!( "\ - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = 1; - result_pointer->values = malloc(sizeof({item_type})); - result_pointer->values[0] = {item0}; - return result_pointer;", + {list_type} result = {{ + .length = 1, + .values = malloc(sizeof({item_type})), + }}; + result.values[0] = {item0}; + return result;", item_type = substitutions["T"], list_type = function.return_type, item0 = function.parameters[0].id, )), BuiltinFunction::ListOf2 => self.push(format!( "\ - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = 2; - result_pointer->values = malloc(2 * sizeof({item_type})); - result_pointer->values[0] = {item0}; - result_pointer->values[1] = {item1}; - return result_pointer;", + {list_type} result = {{ + .length = 2, + .values = malloc(2 * sizeof({item_type})), + }}; + result.values[0] = {item0}; + result.values[1] = {item1}; + return result;", item_type = substitutions["T"], list_type = function.return_type, item0 = function.parameters[0].id, @@ -488,13 +531,14 @@ impl<'h> Context<'h> { )), BuiltinFunction::ListOf3 => self.push(format!( "\ - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = 3; - result_pointer->values = malloc(3 * sizeof({item_type})); - result_pointer->values[0] = {item0}; - result_pointer->values[1] = {item1}; - result_pointer->values[2] = {item2}; - return result_pointer;", + {list_type} result = {{ + .length = 3, + .values = malloc(3 * sizeof({item_type})), + }}; + result.values[0] = {item0}; + result.values[1] = {item1}; + result.values[2] = {item2}; + return result;", item_type = substitutions["T"], list_type = function.return_type, item0 = function.parameters[0].id, @@ -503,14 +547,15 @@ impl<'h> Context<'h> { )), BuiltinFunction::ListOf4 => self.push(format!( "\ - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = 4; - result_pointer->values = malloc(4 * sizeof({item_type})); - result_pointer->values[0] = {item0}; - result_pointer->values[1] = {item1}; - result_pointer->values[2] = {item2}; - result_pointer->values[3] = {item3}; - return result_pointer;", + {list_type} result = {{ + .length = 4, + .values = malloc(4 * sizeof({item_type})), + }}; + result.values[0] = {item0}; + result.values[1] = {item1}; + result.values[2] = {item2}; + result.values[3] = {item3}; + return result;", item_type = substitutions["T"], list_type = function.return_type, item0 = function.parameters[0].id, @@ -520,15 +565,16 @@ impl<'h> Context<'h> { )), BuiltinFunction::ListOf5 => self.push(format!( "\ - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = 5; - result_pointer->values = malloc(5 * sizeof({item_type})); - result_pointer->values[0] = {item0}; - result_pointer->values[1] = {item1}; - result_pointer->values[2] = {item2}; - result_pointer->values[3] = {item3}; - result_pointer->values[4] = {item4}; - return result_pointer;", + {list_type} result = {{ + .length = 5, + .values = malloc(5 * sizeof({item_type})), + }}; + result.values[0] = {item0}; + result.values[1] = {item1}; + result.values[2] = {item2}; + result.values[3] = {item3}; + result.values[4] = {item4}; + return result;", item_type = substitutions["T"], list_type = function.return_type, item0 = function.parameters[0].id, @@ -539,23 +585,22 @@ impl<'h> Context<'h> { )), BuiltinFunction::ListRemoveAt => self.push(format!( "\ - if (0 > {index}->value || {index}->value >= {list}->length) {{ + if (0 > {index}.value || {index}.value >= {list}.length) {{ char* message_format = \"Index out of bounds: Tried removing item at index %ld from list of length %ld.\"; - int length = snprintf(NULL, 0, message_format, {index}->value, {list}->length); - char *message = malloc(length + 1); - snprintf(message, length + 1, message_format, {index}->value, {list}->length); - - Text *message_pointer = malloc(sizeof(Text)); - message_pointer->value = message; - builtinPanic$$Text(message_pointer); + int length = snprintf(NULL, 0, message_format, {index}.value, {list}.length); + Text message = {{.value = malloc(length + 1)}}; + snprintf(message.value, length + 1, message_format, {index}.value, {list}.length); + builtinPanic$$Text(message); }} - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = {list}->length - 1; - result_pointer->values = malloc(result_pointer->length * sizeof({item_type})); - memcpy(result_pointer->values, {list}->values, {index}->value * sizeof({item_type})); - memcpy(result_pointer->values + {index}->value, {list}->values + {index}->value + 1, ({list}->length - {index}->value - 1) * sizeof({item_type})); - return result_pointer;", + uint64_t length = {list}.length - 1; + {list_type} result = {{ + .length = length, + .values = malloc(length * sizeof({item_type})), + }}; + memcpy(result.values, {list}.values, {index}.value * sizeof({item_type}*)); + memcpy(result.values + {index}.value, {list}.values + {index}.value + 1, ({list}.length - {index}.value - 1) * sizeof({item_type}*)); + return result;", item_type = substitutions["T"], list_type = function.return_type, list = function.parameters[0].id, @@ -563,23 +608,21 @@ impl<'h> Context<'h> { )), BuiltinFunction::ListReplace => self.push(format!( "\ - if (0 > {index}->value || {index}->value >= {list}->length) {{ + if (0 > {index}.value || {index}.value >= {list}.length) {{ char* message_format = \"Index out of bounds: Tried replacing index %ld in list of length %ld.\"; - int length = snprintf(NULL, 0, message_format, {index}->value, {list}->length); - char *message = malloc(length + 1); - snprintf(message, length + 1, message_format, {index}->value, {list}->length); - - Text *message_pointer = malloc(sizeof(Text)); - message_pointer->value = message; - builtinPanic$$Text(message_pointer); + int length = snprintf(NULL, 0, message_format, {index}.value, {list}.length); + Text message = {{.value = malloc(length + 1)}}; + snprintf(message.value, length + 1, message_format, {index}.value, {list}.length); + builtinPanic$$Text(message); }} - {list_type}* result_pointer = malloc(sizeof({list_type})); - result_pointer->length = {list}->length; - result_pointer->values = malloc(result_pointer->length * sizeof({item_type})); - memcpy(result_pointer->values, {list}->values, {list}->length * sizeof({item_type})); - result_pointer->values[{index}->value] = {new_item}; - return result_pointer;", + {list_type} result = {{ + .length = {list}.length, + .values = malloc({list}.length * sizeof({item_type})), + }}; + memcpy(result.values, {list}.values, {list}.length * sizeof({item_type}*)); + result.values[{index}.value] = {new_item}; + return result;", item_type = substitutions["T"], list_type = function.return_type, list = function.parameters[0].id, @@ -589,7 +632,7 @@ impl<'h> Context<'h> { BuiltinFunction::Panic => { self.push(format!( "\ - fputs({}->value, stderr); + fputs({}.value, stderr); exit(1);", function.parameters[0].id, )); @@ -597,90 +640,80 @@ impl<'h> Context<'h> { BuiltinFunction::Print => { self.push(format!( "\ - puts({}->value); - Nothing *_1 = malloc(sizeof(Nothing)); + puts({}.value); + Nothing _1 = {{}}; return _1;", function.parameters[0].id, )); } BuiltinFunction::TextCompareTo => self.push(format!( "\ - int raw_result = strcmp({a}->value, {b}->value); - Ordering* result_pointer = malloc(sizeof(Ordering)); - result_pointer->variant = raw_result < 0 ? Ordering_less - : raw_result == 0 ? Ordering_equal - : Ordering_greater; - return result_pointer;", + int raw_result = strcmp({a}.value, {b}.value); + Ordering result = {{.variant = raw_result < 0 ? Ordering_less + : raw_result == 0 ? Ordering_equal + : Ordering_greater}}; + return result;", a = function.parameters[0].id, b = function.parameters[1].id, )), BuiltinFunction::TextConcat => self.push(format!( "\ - size_t lengthA = strlen({a}->value);\n\ - size_t lengthB = strlen({b}->value);\n\ - char* result = malloc(lengthA + lengthB + 1);\n\ - memcpy(result, {a}->value, lengthA);\n\ - memcpy(result + lengthA, {b}->value, lengthB + 1);\n\ - Text* result_pointer = malloc(sizeof(Text)); - result_pointer->value = result; - return result_pointer;", + size_t lengthA = strlen({a}.value);\n\ + size_t lengthB = strlen({b}.value);\n\ + Text result = {{.value = malloc(lengthA + lengthB + 1)}};\n\ + memcpy(result.value, {a}.value, lengthA);\n\ + memcpy(result.value + lengthA, {b}.value, lengthB + 1);\n\ + return result;", a = function.parameters[0].id, b = function.parameters[1].id, )), BuiltinFunction::TextGetRange => self.push(format!( "\ - size_t text_length = strlen({text}->value); - if (0 > {start_inclusive}->value || {start_inclusive}->value > text_length - || 0 > {end_exclusive}->value || {end_exclusive}->value > text_length) {{ + size_t text_length = strlen({text}.value); + if (0 > {start_inclusive}.value || {start_inclusive}.value > text_length + || 0 > {end_exclusive}.value || {end_exclusive}.value > text_length) {{ char* message_format = \"Index out of bounds: Tried getting range %ld..%ld from text that is only %ld long.\"; - int length = snprintf(NULL, 0, message_format, {start_inclusive}->value, {end_exclusive}->value, text_length); - char *message = malloc(length + 1); - snprintf(message, length + 1, message_format, {start_inclusive}->value, {end_exclusive}->value, text_length); - Text *message_pointer = malloc(sizeof(Text)); - message_pointer->value = message; - builtinPanic$$Text(message_pointer); - }} else if ({start_inclusive}->value > {end_exclusive}->value) {{ + int length = snprintf(NULL, 0, message_format, {start_inclusive}.value, {end_exclusive}.value, text_length); + Text message = {{.value = malloc(length + 1)}}; + snprintf(message.value, length + 1, message_format, {start_inclusive}.value, {end_exclusive}.value, text_length); + builtinPanic$$Text(message); + }} else if ({start_inclusive}.value > {end_exclusive}.value) {{ char* message_format = \"Invalid range %ld..%ld: `start_inclusive` must be less than or equal to `end_exclusive`.\"; - int length = snprintf(NULL, 0, message_format, {start_inclusive}->value, {end_exclusive}->value); - char *message = malloc(length + 1); - snprintf(message, length + 1, message_format, {start_inclusive}->value, {end_exclusive}->value); - Text *message_pointer = malloc(sizeof(Text)); - message_pointer->value = message; - builtinPanic$$Text(message_pointer); + int length = snprintf(NULL, 0, message_format, {start_inclusive}.value, {end_exclusive}.value); + Text message = {{.value = malloc(length + 1)}}; + snprintf(message.value, length + 1, message_format, {start_inclusive}.value, {end_exclusive}.value); + builtinPanic$$Text(message); }} - size_t length = {end_exclusive}->value - {start_inclusive}->value;\n\ - char* result = malloc(length + 1);\n\ - memcpy(result, {text}->value + {start_inclusive}->value, length);\n\ - Text* result_pointer = malloc(sizeof(Text)); - result_pointer->value = result; - return result_pointer;", + size_t length = {end_exclusive}.value - {start_inclusive}.value;\n\ + Text result = {{.value = malloc(length + 1)}};\n\ + memcpy(result.value, {text}.value + {start_inclusive}.value, length);\n\ + return result;", text = function.parameters[0].id, start_inclusive = function.parameters[1].id, end_exclusive = function.parameters[2].id, )), BuiltinFunction::TextIndexOf => self.push(format!( "\ - {return_type}* result_pointer = malloc(sizeof({return_type})); - char* result = strstr({a}->value, {b}->value); - if (result == NULL) {{ - result_pointer->variant = {return_type}_none; + {return_type} result; + char* result_raw = strstr({a}.value, {b}.value); + if (result_raw == NULL) {{ + result = {{.variant = {return_type}_none}}; }} else {{ - result_pointer->variant = {return_type}_some; - Int* index_pointer = malloc(sizeof(Int)); - index_pointer->value = result - {a}->value; - result_pointer->value.some = index_pointer; + result = {{ + .variant = {return_type}_some, + .value.some = {{.value = result_raw - {a}.value}}, + }}; }} - return result_pointer;", + return result;", a = function.parameters[0].id, b = function.parameters[1].id, return_type = function.return_type, )), BuiltinFunction::TextLength => self.push(format!( "\ - Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = strlen({text}->value); - return result_pointer;", + Int result = {{.value = strlen({text}.value)}}; + return result;", text = function.parameters[0].id, )), } @@ -708,19 +741,14 @@ impl<'h> Context<'h> { fn lower_expression(&mut self, declaration_name: &str, id: Id, expression: &Expression) { match &expression.kind { ExpressionKind::Int(int) => { - self.push(format!( - "{}* {id} = malloc(sizeof({}));", - &expression.type_, &expression.type_, - )); - self.push(format!("{id}->value = {int};")); + self.push(format!("{} {id} = {{.value = {int} }};", &expression.type_)); } ExpressionKind::Text(text) => { self.push(format!( - "{}* {id} = malloc(sizeof({}));", - &expression.type_, &expression.type_, + "{} {id} = {{.value = \"{}\"}};", + &expression.type_, + text.escape_default(), )); - // TODO: escape text - self.push(format!("{id}->value = \"{text}\";")); } ExpressionKind::CreateStruct { struct_, fields } => { let TypeDeclaration::Struct { @@ -730,42 +758,66 @@ impl<'h> Context<'h> { unreachable!(); }; - self.push(format!( - "{}* {id} = malloc(sizeof({}));", - &expression.type_, &expression.type_, - )); + self.push(format!("{} {id} = {{", &expression.type_)); for ((name, _), value) in type_fields.iter().zip_eq(fields.iter()) { - self.push(format!("\n{id}->{name} = {value};")); + self.push(format!("\n.{name} = {value},")); } + self.push("};"); } ExpressionKind::StructAccess { struct_, field } => { - self.push(format!("{}* {id} = {struct_}->{field};", &expression.type_)); + self.push(format!("{} {id} = {struct_}.{field};", &expression.type_)); } ExpressionKind::CreateEnum { enum_, variant, value, } => { - self.push(format!( - "{}* {id} = malloc(sizeof({}));", - &expression.type_, &expression.type_, - )); - self.push(format!("{id}->variant = {enum_}_{variant};")); - if let Some(value) = value { - self.push(format!("\n{id}->value.{variant} = {value};")); + if self.get_boxed_variants(enum_).contains(variant) { + let TypeDeclaration::Enum { variants } = &self.mono.type_declarations[enum_] + else { + unreachable!() + }; + let value_type = variants + .iter() + .find(|it| &it.name == variant) + .as_ref() + .unwrap() + .value_type + .as_ref() + .unwrap(); + self.push(format!( + "\ + {value_type}* {id}_value_boxed = malloc(sizeof({value_type}*)); + *{id}_value_boxed = {value}; + {type_} {id} = {{ + .variant = {enum_}_{variant}, + .value.{variant} = {id}_value_boxed, + }};", + value = value.unwrap(), + type_ = &expression.type_, + )); + } else { + self.push(format!( + "{} {id} = {{.variant = {enum_}_{variant}", + &expression.type_, + )); + if let Some(value) = value { + self.push(format!("\n.value.{variant} = {value};")); + } + self.push("};"); } } ExpressionKind::GlobalAssignmentReference(assignment) => { - self.push(format!("{}* {id} = {assignment};", &expression.type_)); + self.push(format!("{} {id} = {assignment};", &expression.type_)); } ExpressionKind::LocalReference(referenced_id) => { - self.push(format!("{}* {id} = {referenced_id};", &expression.type_)); + self.push(format!("{} {id} = {referenced_id};", &expression.type_)); } ExpressionKind::CallFunction { function, arguments, } => { - self.push(format!("{}* {id} = {function}(", &expression.type_)); + self.push(format!("{} {id} = {function}(", &expression.type_)); for (index, argument) in arguments.iter().enumerate() { if index > 0 { self.push(", "); @@ -776,10 +828,10 @@ impl<'h> Context<'h> { } ExpressionKind::CallLambda { lambda, arguments } => { self.push(format!( - "{}* {id} = {lambda}->function({lambda}->closure", + "{} {id} = {lambda}.function({lambda}.closure", &expression.type_ )); - for argument in arguments.iter() { + for argument in &**arguments { self.push(format!(", {argument}")); } self.push(");"); @@ -789,25 +841,19 @@ impl<'h> Context<'h> { enum_, cases, } => { - let TypeDeclaration::Enum { variants } = &self.mono.type_declarations[enum_] else { - unreachable!(); - }; - - self.push(format!("{}* {id};\n", &expression.type_)); + self.push(format!("{} {id};\n", &expression.type_)); - self.push(format!("switch ({value}->variant) {{")); - for case in cases.iter() { + self.push(format!("switch ({value}.variant) {{")); + for case in &**cases { self.push(format!("case {enum_}_{}:\n", case.variant)); - if let Some(value_id) = case.value_id { - let variant_type = variants - .iter() - .find(|variant| variant.name == case.variant) - .unwrap() - .value_type - .as_ref() - .unwrap(); + if let Some((value_id, value_type)) = &case.value { self.push(format!( - "{variant_type}* {value_id} = {value}->value.{};\n", + "{value_type} {value_id} = {}{value}.value.{};\n", + if self.get_boxed_variants(enum_).contains(&case.variant) { + "*" + } else { + "" + }, case.variant, )); } @@ -831,13 +877,13 @@ impl<'h> Context<'h> { )); } self.push(format!( - "{type_}* {id} = malloc(sizeof({type_}));", + "\ + {type_} {id} = {{ + .closure = {id}_closure, + .function = {declaration_name}$lambda{id}_function, + }};", type_ = &expression.type_, )); - self.push(format!("{id}->closure = {id}_closure;")); - self.push(format!( - "{id}->function = {declaration_name}$lambda{id}_function;", - )); } } } diff --git a/compiler_v4/src/position.rs b/compiler_v4/src/position.rs index b6818463a..7077bb906 100644 --- a/compiler_v4/src/position.rs +++ b/compiler_v4/src/position.rs @@ -1,5 +1,6 @@ use derive_more::{Deref, DerefMut, From}; use extension_trait::extension_trait; +use serde::Serialize; use std::{ fmt::{self, Display, Formatter}, ops::Range, @@ -41,7 +42,7 @@ pub impl RangeOfOffset for Range { } } -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub struct Position { /// Zero-based line index (`\n`-separated) pub line: usize, diff --git a/compiler_v4/src/string_to_ast/declarations.rs b/compiler_v4/src/string_to_ast/declarations.rs index e13be0960..86b93b0a6 100644 --- a/compiler_v4/src/string_to_ast/declarations.rs +++ b/compiler_v4/src/string_to_ast/declarations.rs @@ -418,7 +418,7 @@ fn parameter<'a>(parser: Parser) -> Option<(Parser, AstParameter, Option } #[instrument(level = "trace")] -fn type_parameters<'s>(parser: Parser<'s>) -> Option<(Parser, AstTypeParameters)> { +fn type_parameters(parser: Parser) -> Option<(Parser, AstTypeParameters)> { let start_offset = parser.offset(); let mut parser = opening_bracket(parser)?.and_trailing_whitespace(); diff --git a/compiler_v4/src/string_to_ast/expression.rs b/compiler_v4/src/string_to_ast/expression.rs index 7db0ba8c0..9967284da 100644 --- a/compiler_v4/src/string_to_ast/expression.rs +++ b/compiler_v4/src/string_to_ast/expression.rs @@ -53,7 +53,7 @@ pub fn expression(parser: Parser) -> Option<(Parser, AstExpression)> { result: &mut AstExpression, parse: fn(Parser<'a>, &mut AstExpression) -> Option>, ) -> bool { - parse(*parser, result).map_or(false, |new_parser| { + parse(*parser, result).is_some_and(|new_parser| { *parser = new_parser; true }) @@ -290,7 +290,7 @@ fn expression_suffix_call<'s>( Some(parser) } #[instrument(level = "trace")] -fn arguments<'s>(parser: Parser<'s>) -> Option<(Parser, AstArguments)> { +fn arguments<'s>(parser: Parser<'s>) -> Option<(Parser<'s>, AstArguments)> { let opening_parenthesis_start = parser.offset(); let mut parser = opening_parenthesis(parser)?.and_trailing_whitespace(); let opening_parenthesis_span = opening_parenthesis_start..parser.offset(); diff --git a/compiler_v4/src/string_to_ast/parser.rs b/compiler_v4/src/string_to_ast/parser.rs index 72ebaf93f..cee2e5bee 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 { @@ -70,7 +75,7 @@ impl<'s> Parser<'s> { } #[must_use] - pub fn consume_literal(mut self, literal: &'static str) -> Option> { + pub fn consume_literal(mut self, literal: &'static str) -> Option { if self.rest().starts_with(literal) { self.offset = Offset(*self.offset + literal.len()); Some(self) @@ -82,7 +87,7 @@ impl<'s> Parser<'s> { pub fn consume_while_not_empty( self, predicate: impl FnMut(char) -> bool, - ) -> Option<(Parser<'s>, AstString)> { + ) -> Option<(Self, AstString)> { let (parser, string) = self.consume_while(predicate); if string.is_empty() { None @@ -91,10 +96,7 @@ impl<'s> Parser<'s> { } } #[must_use] - pub fn consume_while( - mut self, - mut predicate: impl FnMut(char) -> bool, - ) -> (Parser<'s>, AstString) { + pub fn consume_while(mut self, mut predicate: impl FnMut(char) -> bool) -> (Self, AstString) { let start_offset = self.offset(); while let Some((new_parser, c)) = self.consume_char() && predicate(c) @@ -104,7 +106,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<(Self, 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/compiler_v4/src/string_to_ast/type_.rs b/compiler_v4/src/string_to_ast/type_.rs index 4fc416a42..a200f4a6e 100644 --- a/compiler_v4/src/string_to_ast/type_.rs +++ b/compiler_v4/src/string_to_ast/type_.rs @@ -93,7 +93,7 @@ fn function_type_parameter_type<'a>( } #[instrument(level = "trace")] -pub fn type_arguments<'s>(parser: Parser<'s>) -> Option<(Parser, AstTypeArguments)> { +pub fn type_arguments<'s>(parser: Parser<'s>) -> Option<(Parser<'s>, AstTypeArguments)> { let start_offset = parser.offset(); let mut parser = opening_bracket(parser)?.and_trailing_whitespace(); diff --git a/compiler_v4/src/type_solver/goals.rs b/compiler_v4/src/type_solver/goals.rs index 52ef2291c..ea84ab593 100644 --- a/compiler_v4/src/type_solver/goals.rs +++ b/compiler_v4/src/type_solver/goals.rs @@ -280,7 +280,7 @@ struct SolverTree { goal: SolverGoal, } impl SolverTree { - fn solve(&mut self, context: &mut Solver) -> SolverSolution { + fn solve(&self, context: &mut Solver) -> SolverSolution { let mut strands = context .rules .iter() diff --git a/flake.lock b/flake.lock index a0064b550..75a2f0aa9 100644 --- a/flake.lock +++ b/flake.lock @@ -5,29 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "flake-utils_2": { - "inputs": { - "systems": "systems_2" - }, - "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", "type": "github" }, "original": { @@ -38,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1710272261, - "narHash": "sha256-g0bDwXFmTE7uGDOs9HcJsfLFhH7fOsASbAuOzDC+fhQ=", + "lastModified": 1733940404, + "narHash": "sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g=", "owner": "nixos", "repo": "nixpkgs", - "rev": "0ad13a6833440b8e238947e47bea7f11071dc2b2", + "rev": "5d67ea6b4b63378b9c13be21e2ec9d1afc921713", "type": "github" }, "original": { @@ -61,17 +43,16 @@ }, "rust-overlay": { "inputs": { - "flake-utils": "flake-utils_2", "nixpkgs": [ "nixpkgs" ] }, "locked": { - "lastModified": 1710382258, - "narHash": "sha256-2FW1q+o34VBweYQiEkRaSEkNMq3ecrn83VzETeGiVbY=", + "lastModified": 1734143514, + "narHash": "sha256-1+r8wYucn8kp9d/IBW1uYGs31QQmSZURElsiOTx65xM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "8ce81e71ab04a7e906fae62da086d6ee5d6cfc21", + "rev": "81fe5c27cb281a9b796d7ad05ad9179e5bd0c78d", "type": "github" }, "original": { @@ -94,21 +75,6 @@ "repo": "default", "type": "github" } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 9d6141004..7c808e1ce 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -1,11 +1,6 @@ struct Nothing {} enum Never {} -# struct List[T] {} -# impl[T: Equal] List[T]: Equal { -# # fun equals(self: Self, other: Self) Bool {} -# } - trait ToText { fun toText(self: Self) Text } @@ -30,7 +25,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,14 +39,54 @@ 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, greater => true, } } +fun coerceAtLeast[T: Compare](value: T, minimum: T) T { + switch value.compareTo(minimum) { + less => minimum, + equal => value, + greater => value, + } +} +fun coerceAtMost[T: Compare](value: T, maximum: T) T { + switch value.compareTo(maximum) { + less => value, + equal => value, + greater => maximum, + } +} +fun coerceInRange[T: Compare](value: T, minimum: T, maximum: T) T { + needs(minimum.isAtMost(maximum)) + + value.coerceAtLeast(minimum).coerceAtMost(maximum) +} 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 +129,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() { @@ -219,24 +254,33 @@ fun concat(self: Text, other: Text) Text { self.builtinTextConcat(other) } # TODO: Support ranges when we have them. +fun get(self: Text, index: Int) Maybe[Text] { + switch index.isInRange(0, self.length()) { + false => none[Text](), + true => some(self.getRange(index, index.add(1))), + } +} fun getRange(self: Text, startInclusive: Int, endExclusive: Int) Text { self.builtinTextGetRange(startInclusive, endExclusive) } fun characterAt(self: Text, index: Int) Maybe[Text] { - switch index.isNonNegative().and(index.isLessThan(self.length())) { + switch index.isInRange(0, self.length()) { false => none[Text](), true => some(self.getRange(index, index.add(1))), } } +fun characters(self: Text) List[Text] { + listGenerate(self.length(), (i: Int) { self.get(i).unwrap() }) +} 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,9 +298,44 @@ 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 lines(self: Text) List[Text] { + self.split("\n") +} + fun indexOf(self: Text, other: Text) Maybe[Int] { self.builtinTextIndexOf(other) } +fun allIndexesOfOverlapping(self: Text, other: Text) List[Int] { + self.allIndexesOfOverlappingHelper(other, 0, listOf[Int]()) +} +fun allIndexesOfOverlappingHelper(self: Text, other: Text, offset: Int, result: List[Int]) List[Int] { + switch self.getRange(offset, self.length()).indexOf(other) { + none => result, + some(index) => { + let index = offset.add(index) + self.allIndexesOfOverlappingHelper(other, index.add(1), result.append(index)) + }, + } +} fun contains(self: Text, other: Text) Bool { self.indexOf(other).isSome() } @@ -307,7 +386,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 +398,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 +449,247 @@ 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 skip[T](list: List[T], count: Int) List[T] { + needs(count.isNonNegative()) + getRange(list, count, list.length()) +} +fun skipLast[T](list: List[T], count: Int) List[T] { + needs(count.isNonNegative()) + getRange(list, 0, list.length().subtract(count)) +} +fun range(length: Int) List[Int] { + needs(length.isNonNegative()) + + listGenerate(length, (i: Int) { i }) +} +fun range(startInclusive: Int, endExclusive: Int) List[Int] { + needs(startInclusive.isAtMost(endExclusive)) + + listGenerate(endExclusive.subtract(startInclusive), (i: Int) { startInclusive.add(i) }) +} +fun indexes[T](list: List[T]) List[Int] { + list.length().range() +} + +fun isInRange[T: Compare](self: T, startInclusive: T, endExclusive: T) Bool { + needs(startInclusive.isAtMost(endExclusive)) + + self.isAtLeast(startInclusive).and(self.isLessThan(endExclusive)) +} + +struct List2D[T] { + width: Int, + height: Int, + items: List[T], +} +fun list2D[T](width: Int, height: Int, items: List[T]) List2D[T] { + needs(width.isNonNegative()) + needs(height.isNonNegative()) + needs(width.multiply(height).equals(items.length())) + + List2D[T](width, height, items) +} +fun list2DFilled[T](width: Int, height: Int, item: T) List2D[T] { + needs(width.isNonNegative()) + needs(height.isNonNegative()) + + list2D(width, height, listFilled(width.multiply(height), item)) +} +fun get[T](self: List2D[T], position: Pair[Int, Int]) Maybe[T] { + self.get(position.first, position.second) +} +fun get[T](self: List2D[T], x: Int, y: Int) Maybe[T] { + switch x.isInRange(0, self.width).and(y.isInRange(0, self.height)) { + false => none[T](), + true => some(self.items.get(self.rawIndex(x, y)).unwrap()), + } +} +fun replace[T](self: List2D[T], x: Int, y: Int, value: T) List2D[T] { + needs(x.isInRange(0, self.width)) + needs(y.isInRange(0, self.height)) + + let newItems = self.items.replace(self.rawIndex(x, y), value) + list2D(self.width, self.height, newItems) +} +fun indexesRowWise[T](self: List2D[T]) List[Pair[Int, Int]] { + range(self.height).flatMap((y: Int) { + range(self.width).map((x: Int) { Pair(x, y) }) + }) +} +fun rows[T](self: List2D[T]) List[List[T]] { + range(self.height).map((y: Int) { self.row(y).unwrap() }) +} +fun row[T](self: List2D[T], y: Int) Maybe[List[T]] { + switch y.isInRange(0, self.height) { + false => none[List[T]](), + true => some(self.items.getRange(self.rawIndex(0, y), self.rawIndex(0, y.add(1)))), + } +} +fun rawIndex[T](self: List2D[T], x: Int, y: Int) Int { + needs(x.isInRange(0, self.width)) + needs(y.isInRange(0, self.height)) + + y.multiply(self.width).add(x) +} + +# 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.foldIndexed(initial, (state: R, index: Int, item: T) { combine(state, item) }) +} +fun foldIndexed[T, R](list: List[T], initial: R, combine: (R, Int, T) R) R { + list.foldIndexedHelper(0, initial, combine) +} +fun foldIndexedHelper[T, R](list: List[T], index: Int, state: R, combine: (R, Int, T) R) R { + switch index.isLessThan(list.length()) { + true => list.foldIndexedHelper(index.add(1), combine(state, index, list.get(index).unwrap()), combine), + false => state, + } +} +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 sum(list: List[Int]) Int { + list.fold(0, (sum: Int, item: Int) { sum.add(item) }) +} + +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 index.isLessThan(list.length()) { + true => switch predicate(list.get(index).unwrap()) { + true => list.allHelper(predicate, index.add(1)), + false => false, + }, + false => 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.mapIndexed((index: Int, item: T) { transform(item) }) +} +fun mapIndexed[T, R](list: List[T], transform: (Int, T) R) List[R] { + list.fold(listOf[R](), (result: List[R], item: T) { + result.append(transform(result.length(), item)) + }) +} +fun flatMap[T, R](list: List[T], transform: (T) List[R]) List[R] { + list.fold(listOf[R](), (result: List[R], item: T) { + result.concat(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() }) +} + +fun join[T: ToText](self: List[T], separator: Text) Text { + self.map((item: T) { item.toText() }) + .reduce((result: Text, item: Text) { "{result}{separator}{item}" }) + .unwrapOr("") +} +impl[T: ToText] List[T]: ToText { + fun toText(self: List[T]) Text { + "[{self.join(", ")}]" + } +} + +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]](), + } +} +# TODO: Use this impl when it compiles +# impl[T0: Equal, T1: Equal] Pair[T0, T1]: Equal { +# fun equals(self: Pair[T0, T1], other: Pair[T0, T1]) Bool { +# self.first.equals(other.first).and(self.second.equals(other.second)) +# } +# } +fun equals[T: Equal](self: Pair[T, T], other: Pair[T, T]) Bool { + self.first.equals(other.first).and(self.second.equals(other.second)) +} + struct MyStruct { name: Text, color: Int, @@ -405,11 +722,23 @@ 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 { +fun toInt(self: Bool) Int { + switch self { + false => 0, + true => 1, + } +} +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 +762,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 +810,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(value) => panic("`unwrap()` called on `error()`"), + 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(error) => getDefaultValue(error), } } fun isOk[T, E](self: Result[T, E]) Bool { @@ -529,6 +880,226 @@ fun identity[T](t: T) T { t } +# Advent of Code 2024 +# https://adventofcode.com/2024 + +fun day2Part1(input: Text) Int { + day2Wrapper(input, (line: List[Int]) { day2CheckLine(line) }) +} +fun day2Part2(input: Text) Int { + day2Wrapper(input, (line: List[Int]) { + listOf(line) + .concat(line.indexes().map((index: Int) { line.removeAt(index) })) + .any((line: List[Int]) { day2CheckLine(line) }) + }) +} +fun day2Wrapper(input: Text, checkLine: (List[Int]) Bool) Int { + input + .lines() + .map((line: Text) { line.split(" ").map((number: Text) { parseInt(number).unwrap() }) }) + .filter(checkLine) + .length() +} +fun day2CheckLine(line: List[Int]) Bool { + line + .pairs() + .all((pair: Pair[Int, Int]) { + let difference = pair.second.subtract(pair.first).absolute() + difference.isAtLeast(1).and(difference.isAtMost(3)) + }) + .and(line.isStrictlyAscending().or(line.isStrictlyDescending())) +} + +fun day4Part1(input: Text) Int { + let lines = input.lines() + let rows = lines.length() + let columns = lines.first().unwrap().length() + lines + .mapIndexed((row: Int, line: Text) { + columns.range() + .map((column: Int) { + let horizontalText = line.getRange(column, column.add(4).coerceAtMost(line.length())) + let verticalText = lines.getRange(row, row.add(4).coerceAtMost(lines.length())) + .map((line: Text) { line.get(column).unwrap() }) + .join("") + let diagonalRightDownText = switch row.isAtMost(rows.subtract(4)) + .and(column.isAtMost(columns.subtract(4))) { + true => range(4) + .map((i: Int) { lines.get(row.add(i)).unwrap().get(column.add(i)).unwrap() }) + .join(""), + false => "", + } + let diagonalLeftDownText = switch row.isAtLeast(3) + .and(column.isAtMost(columns.subtract(4))) { + true => range(4) + .map((i: Int) { lines.get(row.subtract(i)).unwrap().get(column.add(i)).unwrap() }) + .join(""), + false => "", + } + listOf(horizontalText, verticalText, diagonalRightDownText, diagonalLeftDownText) + .filter((text: Text) { text.equals("XMAS").or(text.equals("SAMX")) }) + .length() + }) + .sum() + }) + .sum() +} +fun day4Part2(input: Text) Int { + let lines = input.lines() + let rows = lines.length() + let columns = lines.first().unwrap().length() + range(1, rows.subtract(1)) + .map((row: Int) { + range(1, columns.subtract(1)) + .filter((column: Int) { + switch lines.get(row).unwrap().get(column).unwrap().equals("A") { + false => false, + true => { + let topLeft = lines.get(row.subtract(1)).unwrap().get(column.subtract(1)).unwrap() + let topRight = lines.get(row.subtract(1)).unwrap().get(column.add(1)).unwrap() + let bottomLeft = lines.get(row.add(1)).unwrap().get(column.subtract(1)).unwrap() + let bottomRight = lines.get(row.add(1)).unwrap().get(column.add(1)).unwrap() + let rightDown = topLeft.equals("M").and(bottomRight.equals("S")) + .or(topLeft.equals("S").and(bottomRight.equals("M"))) + let leftDown = topRight.equals("M").and(bottomLeft.equals("S")) + .or(topRight.equals("S").and(bottomLeft.equals("M"))) + rightDown.and(leftDown) + }, + } + }) + .length() + }) + .sum() +} + +fun day6Part1(input: Text) Int { + let lines = input.lines() + let width = lines.first().unwrap().length() + let height = lines.length() + + let state = lines.foldIndexed( + Day6Part1State( + list2DFilled(width, height, false), + list2DFilled(width, height, false), + Pair(0, 0), + Day6Part1Direction.up(), + ), + (state: Day6Part1State, y: Int, line: Text) { + line.characters() + .foldIndexed( + state, + (state: Day6Part1State, x: Int, cell: Text) { + Day6Part1State( + state.obstructions.replace(x, y, cell.equals("#")), + switch cell.equals("^") { + true => state.visited.replace(x, y, true), + false => state.visited, + }, + switch cell.equals("^") { + true => Pair(x, y), + false => state.position, + }, + state.direction, + ) + }, + ) + }, + ) + let state = state.moveToEnd() + print(state) + state.visitedCellCount() +} +struct Day6Part1State { + obstructions: List2D[Bool], + visited: List2D[Bool], + position: Pair[Int, Int], + direction: Day6Part1Direction, +} +enum Day6Part1Direction { up, right, down, left } +fun rotateRight(self: Day6Part1Direction) Day6Part1Direction { + switch self { + up => Day6Part1Direction.right(), + right => Day6Part1Direction.down(), + down => Day6Part1Direction.left(), + left => Day6Part1Direction.up(), + } +} +fun moveToEnd(self: Day6Part1State) Day6Part1State { + switch self.move() { + none => self, + some(newState) => moveToEnd(newState), + } +} +fun move(self: Day6Part1State) Maybe[Day6Part1State] { + switch self.nextPosition() { + none => none[Day6Part1State](), + some(nextPosition) => switch self.obstructions.get(nextPosition).unwrap() { + true => some(Day6Part1State( + self.obstructions, + self.visited, + self.position, + self.direction.rotateRight(), + )), + false => some(Day6Part1State( + self.obstructions, + self.visited.replace(nextPosition.first, nextPosition.second, true), + nextPosition, + self.direction, + )), + }, + } +} +fun nextPosition(self: Day6Part1State) Maybe[Pair[Int, Int]] { + switch self.direction { + up => switch self.position.second.equals(0) { + true => none[Pair[Int, Int]](), + false => some(Pair(self.position.first, self.position.second.subtract(1))), + }, + right => switch self.position.first.equals(self.obstructions.width.subtract(1)) { + true => none[Pair[Int, Int]](), + false => some(Pair(self.position.first.add(1), self.position.second)), + }, + down => switch self.position.second.equals(self.obstructions.height.subtract(1)) { + true => none[Pair[Int, Int]](), + false => some(Pair(self.position.first, self.position.second.add(1))), + }, + left => switch self.position.first.equals(0) { + true => none[Pair[Int, Int]](), + false => some(Pair(self.position.first.subtract(1), self.position.second)), + }, + } +} +fun visitedCellCount(self: Day6Part1State) Int { + self.visited.items.filter((visited: Bool) { visited }).length() +} +impl Day6Part1State: ToText { + fun toText(self: Day6Part1State) Text { + self.obstructions.height.range() + .map((y: Int) { + self.obstructions.width.range() + .map((x: Int) { + switch self.position.equals(Pair(x, y)) { + true => switch self.direction { + up => "^", + right => ">", + down => "v", + left => "<", + }, + false => switch self.visited.get(x, y).unwrap() { + true => "X", + false => switch self.obstructions.get(x, y).unwrap() { + true => "#", + false => ".", + }, + }, + } + }) + .join("") + }) + .join("\n") + } +} + fun main() Int { identity(1) identity("foo") @@ -561,6 +1132,8 @@ fun main() Int { repeat(3, () { print("Hello, World!") }) + print(listOf(1, 2, 3).map((x: Int) { x.multiply(2) })) + 0 } diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 9fd746d58..588a32421 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2024-02-22" +channel = "nightly-2024-11-26" profile = "minimal" components = ["clippy", "rust-src", "rustfmt"] diff --git a/third_party/dap-rs/src/events.rs b/third_party/dap-rs/src/events.rs index 2ebf88ed6..e507391ee 100644 --- a/third_party/dap-rs/src/events.rs +++ b/third_party/dap-rs/src/events.rs @@ -260,18 +260,18 @@ pub struct StoppedEventBody { /// If `allThreadsStopped` is true, a debug adapter can announce that all /// threads have stopped. /// - The client should use this information to enable that all threads can - /// be expanded to access their stacktraces. + /// be expanded to access their stacktraces. /// - If the attribute is missing or false, only the thread with the given - /// `threadId` can be expanded. + /// `threadId` can be expanded. pub all_threads_stopped: Option, /// Ids of the breakpoints that triggered the event. In most cases there is /// only a single breakpoint but here are some examples for multiple /// breakpoints: /// - Different types of breakpoints map to the same location. /// - Multiple source breakpoints get collapsed to the same instruction by - /// the compiler/runtime. + /// the compiler/runtime. /// - Multiple function breakpoints with different function names map to the - /// same location. + /// same location. pub hit_breakpoint_ids: Option>, } diff --git a/third_party/dap-rs/src/types.rs b/third_party/dap-rs/src/types.rs index 656b1d2b5..bb88b9ef4 100644 --- a/third_party/dap-rs/src/types.rs +++ b/third_party/dap-rs/src/types.rs @@ -1156,7 +1156,7 @@ fromstr_deser! { VariablesArgumentsFilter } tostr_ser! { VariablesArgumentsFilter } /// Properties of a breakpoint location returned from the breakpointLocations request. - +/// /// Specfication: [BreakpointLocation](https://microsoft.github.io/debug-adapter-protocol/specification#Types_BreakpointLocation) #[derive(Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] diff --git a/vscode_extension_v4/candy.tmGrammar.json b/vscode_extension_v4/candy.tmGrammar.json index 54fcfde2b..af6f0d042 100644 --- a/vscode_extension_v4/candy.tmGrammar.json +++ b/vscode_extension_v4/candy.tmGrammar.json @@ -41,7 +41,7 @@ }, { "name": "keyword.control", - "match": "([a-zA-Z_0-9]*)(?=(?:\\(.*\\))?\\s*=>)", + "match": "\\b([a-zA-Z_0-9]+)(?=(?:\\(.*\\))?\\s*=>)", "captures": { "1": { "name": "emphasis" @@ -69,6 +69,10 @@ } ] }, + { + "name": "variable.language", + "match": "\\bself\\b" + }, { "name": "constant.numeric", "match": "\\b[0-9]+\\b" diff --git a/vscode_extension_v4/declarative/language-configuration.json b/vscode_extension_v4/declarative/language-configuration.json index c3047f27a..be8471ffe 100644 --- a/vscode_extension_v4/declarative/language-configuration.json +++ b/vscode_extension_v4/declarative/language-configuration.json @@ -19,7 +19,7 @@ }, "onEnterRules": [ { - // Assignment start with optional comment but no actual content. + // Assignment starts with optional comment but no actual content. "beforeText": "^[^#]*=\\s*(?:#.*)?$", "action": { "indent": "indent" diff --git a/vscode_extension_v4/package-lock.json b/vscode_extension_v4/package-lock.json index fb44e9713..68d202404 100644 --- a/vscode_extension_v4/package-lock.json +++ b/vscode_extension_v4/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "candy", "version": "0.4.0", + "dependencies": { + "linebyline": "^1.3.0" + }, "devDependencies": { "@types/node": "^20.11.20", "@types/vscode": "^1.86.0", @@ -1145,6 +1148,11 @@ "node": ">= 0.8.0" } }, + "node_modules/linebyline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/linebyline/-/linebyline-1.3.0.tgz", + "integrity": "sha512-3fpIYMrSU77OCf89hjXKuCx6vGwgWEu4N5DDCGqgZ1BF0HYy9V8IbQb/3+VWIU17iBQ83qQoUokH0AhPMOTi7w==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2498,6 +2506,11 @@ "type-check": "~0.4.0" } }, + "linebyline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/linebyline/-/linebyline-1.3.0.tgz", + "integrity": "sha512-3fpIYMrSU77OCf89hjXKuCx6vGwgWEu4N5DDCGqgZ1BF0HYy9V8IbQb/3+VWIU17iBQ83qQoUokH0AhPMOTi7w==" + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", diff --git a/vscode_extension_v4/package.json b/vscode_extension_v4/package.json index 5e8645447..bdc7c92d9 100644 --- a/vscode_extension_v4/package.json +++ b/vscode_extension_v4/package.json @@ -9,7 +9,6 @@ "url": "https://github.com/candy-lang/candy.git", "directory": "vscode_extension_v4" }, - "//": "Keep in sync with `devDependencies.@types/vscode`", "//": "https://github.com/ewanharris/vscode-versions", "engines": { "node": "^18.17.1", @@ -23,6 +22,17 @@ ], "main": "./out/extension.js", "contributes": { + "configuration": { + "title": "🍭 Candy", + "properties": { + "candy.compilerExecutablePath": { + "type": "string", + "default": "", + "markdownDescription": "Path to the 🍭 Candy compiler executable. If empty, we'll attempt to find the 🍭 Candy executable in the `PATH` environment variable.", + "scope": "machine-overridable" + } + } + }, "configurationDefaults": { "[candy]": { "editor.detectIndentation": false, @@ -58,7 +68,9 @@ "lint": "eslint --ext ts .", "watch": "tsc -watch -p ./" }, - "dependencies": {}, + "dependencies": { + "linebyline": "^1.3.0" + }, "devDependencies": { "@types/node": "^20.11.20", "@types/vscode": "^1.86.0", diff --git a/vscode_extension_v4/src/@types/linebyline/index.d.ts b/vscode_extension_v4/src/@types/linebyline/index.d.ts new file mode 100644 index 000000000..88700c055 --- /dev/null +++ b/vscode_extension_v4/src/@types/linebyline/index.d.ts @@ -0,0 +1,18 @@ +declare module "linebyline" { + import { Stream } from "stream"; + + function readLine( + readingObject: string | Stream, + options?: { maxLineLength?: number; retainBuffer?: boolean }, + ): EventEmitter; + export = readLine; + + interface EventEmitter { + on( + event: "line", + listener: (line: string, lineCount: number, byteCount: number) => void, + ): EventEmitter; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + on(event: "error", listener: (error: any) => void): EventEmitter; + } +} diff --git a/vscode_extension_v4/src/extension.ts b/vscode_extension_v4/src/extension.ts index 1067d87fd..04f53143f 100644 --- a/vscode_extension_v4/src/extension.ts +++ b/vscode_extension_v4/src/extension.ts @@ -1,6 +1,172 @@ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +import * as child_process from "child_process"; +import linebyline from "linebyline"; import * as vscode from "vscode"; +let diagnosticCollection: vscode.DiagnosticCollection; + // eslint-disable-next-line @typescript-eslint/no-unused-vars -export function activate(_context: vscode.ExtensionContext) { - console.log("Activated 🍭 Candy extension!"); +export function activate(context: vscode.ExtensionContext) { + console.info("Activated the 🍭 Candy extension!"); + + diagnosticCollection = vscode.languages.createDiagnosticCollection("candy"); + context.subscriptions.push(diagnosticCollection); + + vscode.window.onDidChangeVisibleTextEditors(() => onlyRunOneAtATime(update)); + vscode.workspace.onDidChangeTextDocument(() => onlyRunOneAtATime(update)); +} + +// Updates can be triggered very frequently (on every keystroke), but they can +// take long to process. Thus, here we make sure that only one update runs at a +// time. +let generation = 0; +let currentRun = Promise.resolve(null); +async function onlyRunOneAtATime(callback: () => Promise) { + console.log("Scheduling update"); + const myGeneration = ++generation; + await currentRun; + if (generation != myGeneration) return; // a newer update exists and will run + // eslint-disable-next-line @typescript-eslint/no-misused-promises, no-async-promise-executor + currentRun = new Promise(async (resolve) => { + await callback(); + resolve(null); + }); +} + +async function update() { + console.log("Updating"); + const promises = []; + for (const editor of vscode.window.visibleTextEditors) { + const uri = editor.document.uri.toString(); + if (!uri.endsWith(".candy")) continue; + + const analysis = analyze(uri); + promises.push(analysis); + analysis + .then((diagnostics) => { + diagnosticCollection.clear(); + const diagnosticMap = new Map(); + for (const diagnostic of diagnostics) { + console.info(`Error: ${JSON.stringify(diagnostic)}`); + if (diagnostic.source.file != uri) continue; + const range = new vscode.Range( + new vscode.Position( + diagnostic.source.start.line, + diagnostic.source.start.character, + ), + new vscode.Position( + diagnostic.source.end.line, + diagnostic.source.end.character, + ), + ); + const diagnostics = diagnosticMap.get(diagnostic.source.file) ?? []; + diagnostics.push( + new vscode.Diagnostic( + range, + diagnostic.message, + vscode.DiagnosticSeverity.Error, + ), + ); + diagnosticMap.set(diagnostic.source.file, diagnostics); + } + diagnosticMap.forEach((diags, file) => { + diagnosticCollection.set(vscode.Uri.parse(file), diags); + }); + }) + .catch((error) => { + console.error(`Analyzing failed: ${error}`); + }); + } + await Promise.all(promises); +} + +/// Communication with the Candy language server works using the following +/// schema. + +type AnalyzeMessage = ReadFileMessage | DiagnosticsMessage; // candy tooling-analyze ... +interface ReadFileMessage { + type: "read_file"; + path: string; +} +interface DiagnosticsMessage { + type: "diagnostics"; + diagnostics: Diagnostic[]; +} +interface Diagnostic { + message: string; + source: { + file: string; + start: { line: number; character: number }; + end: { line: number; character: number }; + }; +} + +async function analyze(path: string): Promise { + console.log(`Analyzing ${path}`); + const candy = child_process.spawn( + vscode.workspace + .getConfiguration("candy") + .get("compilerExecutablePath") ?? "candy", + ["tooling-analyze", path], + { env: { ...process.env, RUST_BACKTRACE: "1" } }, + ); + candy.on("error", (error) => { + console.error(`Failed to spawn: ${error.name}: ${error.message}`); + }); + linebyline(candy.stderr).on("line", (line: string) => { + console.log(line); + }); + + let diagnostics: Diagnostic[] = []; + // eslint-disable-next-line @typescript-eslint/no-misused-promises + linebyline(candy.stdout).on("line", async function (line: string) { + console.info("Line: " + line); + const message = JSON.parse(line) as AnalyzeMessage; + switch (message.type) { + case "read_file": + candy.stdin.write(await handleReadFileMessage(message)); + break; + case "diagnostics": + diagnostics = message.diagnostics; + break; + } + }); + + const exitCode: number | null = await new Promise((resolve) => + candy.on("close", (exitCode) => { + resolve(exitCode); + }), + ); + console.info(`\`candy tooling-analyze\` exited with exit code ${exitCode}.`); + + return diagnostics; +} + +async function handleReadFileMessage( + message: ReadFileMessage, +): Promise { + const uri = vscode.Uri.parse(message.path); + const content = await readFile(uri); + const response = content + ? { type: "read_file", success: true, content: content } + : { type: "read_file", success: false }; + return `${JSON.stringify(response)}\n`; +} + +/** + * Returns the source code of the given URI. Prefers the content of open text + * documents, even if they're not saved yet. If none exists, asks the file + * system. + */ +async function readFile(uri: vscode.Uri): Promise { + for (const doc of vscode.workspace.textDocuments) { + if (doc.uri.toString() == uri.toString()) return doc.getText(); + } + + try { + const bytes = await vscode.workspace.fs.readFile(uri); + return new TextDecoder("utf8").decode(bytes); + } catch (e) { + return null; + } } diff --git a/vscode_extension_v4/tsconfig.json b/vscode_extension_v4/tsconfig.json index 865bc376b..50bc10384 100644 --- a/vscode_extension_v4/tsconfig.json +++ b/vscode_extension_v4/tsconfig.json @@ -10,6 +10,8 @@ "noImplicitAny": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, - "noUnusedParameters": true + "noUnusedParameters": true, + "esModuleInterop": true, + "typeRoots": ["src/@types", "node_modules/@types"] } }