From 8be02e2f65951ade06bbcab6153a37f8bb34d56c Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 22 Nov 2024 00:16:06 +0100 Subject: [PATCH 01/24] =?UTF-8?q?Add=20text.getRange(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler_v4/src/hir.rs | 12 ++++++++++++ compiler_v4/src/hir_to_mono.rs | 3 ++- compiler_v4/src/mono_to_c.rs | 32 ++++++++++++++++++++++++++++++++ packages_v5/example.candy | 3 +++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index 41c4e5ca0..8776ef0ec 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -719,6 +719,7 @@ pub enum BuiltinFunction { Panic, Print, TextConcat, + TextGetRange, } impl BuiltinFunction { #[must_use] @@ -807,6 +808,17 @@ impl BuiltinFunction { .into(), return_type: NamedType::text().into(), }, + Self::TextGetRange => BuiltinFunctionSignature { + name: "builtinTextGetRange".into(), + type_parameters: Box::default(), + parameters: [ + ("text".into(), NamedType::text().into()), + ("startInclusive".into(), NamedType::int().into()), + ("endExclusive".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::text().into(), + }, } } } diff --git a/compiler_v4/src/hir_to_mono.rs b/compiler_v4/src/hir_to_mono.rs index 9710c9df8..0a3ee9112 100644 --- a/compiler_v4/src/hir_to_mono.rs +++ b/compiler_v4/src/hir_to_mono.rs @@ -1,6 +1,6 @@ use crate::{ ast_to_hir::TypeUnifier, - hir::{self, Hir, NamedType, ParameterType, Type}, + hir::{self, BuiltinFunction, Hir, NamedType, ParameterType, Type}, id::IdGenerator, mono::{self, Mono}, type_solver::{goals::SolverSolution, values::SolverVariable}, @@ -32,6 +32,7 @@ impl<'h> Context<'h> { functions: FxHashMap::default(), }; let main_function = context.lower_function(hir.main_function_id, &FxHashMap::default()); + context.lower_function(BuiltinFunction::Panic.id(), &FxHashMap::default()); Mono { type_declarations: context .type_declarations diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index be1f9d3ff..32fdf4a8e 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -266,6 +266,38 @@ impl<'h> Context<'h> { 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) {{ + 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) {{ + 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); + }} + + 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;", + text = function.parameters[0].id, + start_inclusive = function.parameters[1].id, + end_exclusive = function.parameters[2].id, + )), } } BodyOrBuiltin::Body(body) => self.lower_body(body), diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 0bbfad42d..738dc2f9a 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -102,6 +102,9 @@ impl Int: Subtract { } struct Text = builtin +fun getRange(self: Text, startInclusive: Int, endExclusive: Int) Text { + self.builtinTextGetRange(startInclusive, endExclusive) +} impl Text: ToText { fun toText(self: Text) Text { self From 18959472359be4942aa59802b2054d097d5b7241 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 22 Nov 2024 00:16:54 +0100 Subject: [PATCH 02/24] =?UTF-8?q?Expose=20text.concat(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages_v5/example.candy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 738dc2f9a..037da532d 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -102,6 +102,9 @@ impl Int: Subtract { } struct Text = builtin +fun concat(self: Text, other: Text) Text { + self.builtinTextConcat(other) +} fun getRange(self: Text, startInclusive: Int, endExclusive: Int) Text { self.builtinTextGetRange(startInclusive, endExclusive) } From dc2b31317c2e11f7d1831d25d053152375800085 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 22 Nov 2024 00:19:41 +0100 Subject: [PATCH 03/24] Add text.length() --- compiler_v4/src/hir.rs | 7 +++++++ compiler_v4/src/mono_to_c.rs | 7 +++++++ packages_v5/example.candy | 3 +++ 3 files changed, 17 insertions(+) diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index 8776ef0ec..0415f2033 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -720,6 +720,7 @@ pub enum BuiltinFunction { Print, TextConcat, TextGetRange, + TextLength, } impl BuiltinFunction { #[must_use] @@ -819,6 +820,12 @@ impl BuiltinFunction { .into(), return_type: NamedType::text().into(), }, + Self::TextLength => BuiltinFunctionSignature { + name: "builtinTextLength".into(), + type_parameters: Box::default(), + parameters: [("text".into(), NamedType::text().into())].into(), + return_type: NamedType::int().into(), + }, } } } diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index 32fdf4a8e..651d70fb1 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -298,6 +298,13 @@ impl<'h> Context<'h> { start_inclusive = function.parameters[1].id, end_exclusive = function.parameters[2].id, )), + BuiltinFunction::TextLength => self.push(format!( + "\ + Int* result_pointer = malloc(sizeof(Int)); + result_pointer->value = strlen({text}->value); + return result_pointer;", + text = function.parameters[0].id, + )), } } BodyOrBuiltin::Body(body) => self.lower_body(body), diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 037da532d..af2c5d8c3 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -102,6 +102,9 @@ impl Int: Subtract { } struct Text = builtin +fun length(self: Text) Int { + self.builtinTextLength() +} fun concat(self: Text, other: Text) Text { self.builtinTextConcat(other) } From bba7bbf29d4e84eaf243f0cedf493de788b0c418 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 22 Nov 2024 00:26:00 +0100 Subject: [PATCH 04/24] impl Text: Compare --- compiler_v4/src/hir.rs | 11 +++++++++++ compiler_v4/src/mono_to_c.rs | 11 +++++++++++ packages_v5/example.candy | 5 +++++ 3 files changed, 27 insertions(+) diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index 0415f2033..fa58ea9ab 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -718,6 +718,7 @@ pub enum BuiltinFunction { IntToText, Panic, Print, + TextCompareTo, TextConcat, TextGetRange, TextLength, @@ -799,6 +800,16 @@ impl BuiltinFunction { parameters: [("message".into(), NamedType::text().into())].into(), return_type: NamedType::nothing().into(), }, + Self::TextCompareTo => BuiltinFunctionSignature { + name: "builtinTextCompareTo".into(), + type_parameters: Box::default(), + parameters: [ + ("a".into(), NamedType::text().into()), + ("b".into(), NamedType::text().into()), + ] + .into(), + return_type: NamedType::ordering().into(), + }, Self::TextConcat => BuiltinFunctionSignature { name: "builtinTextConcat".into(), type_parameters: Box::default(), diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index 651d70fb1..901b49e20 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -253,6 +253,17 @@ impl<'h> Context<'h> { 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;", + a = function.parameters[0].id, + b = function.parameters[1].id, + )), BuiltinFunction::TextConcat => self.push(format!( "\ size_t lengthA = strlen({a}->value);\n\ diff --git a/packages_v5/example.candy b/packages_v5/example.candy index af2c5d8c3..9d0ca2417 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -116,6 +116,11 @@ impl Text: ToText { self } } +impl Text: Compare { + fun compareTo(self: Text, other: Text) Ordering { + self.builtinTextCompareTo(other) + } +} struct Array[T] = builtin fun arrayFilled[T](length: Int, item: T) Array[T] { From 828c73b4c8441fd5e2f9d2925ff9e9cf7995080d Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 15:28:08 +0100 Subject: [PATCH 05/24] =?UTF-8?q?Add=20text.startsWith(=E2=80=A6),=20.ends?= =?UTF-8?q?With(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages_v5/example.candy | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 9d0ca2417..54bc28f1c 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -111,6 +111,18 @@ fun concat(self: Text, other: Text) Text { fun getRange(self: Text, startInclusive: Int, endExclusive: Int) Text { self.builtinTextGetRange(startInclusive, endExclusive) } +fun startsWith(self: Text, prefix: Text) Bool { + switch self.length().isGreaterThanOrEqualTo(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()) { + false => false, + true => self.getRange(self.length().subtract(suffix.length()), self.length()).equals(suffix), + } +} impl Text: ToText { fun toText(self: Text) Text { self From 5d5b05082df0577ec0988bce36910cc3d397c66a Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 15:28:31 +0100 Subject: [PATCH 06/24] Add bool functions --- packages_v5/example.candy | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 54bc28f1c..236f1f086 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -154,6 +154,38 @@ struct MyStruct { enum Bool { true, false } let true: Bool = Bool.true() let false: Bool = Bool.false() +fun not(value: Bool) Bool { + switch value { + true => false, + false => true, + } +} +fun and(a: Bool, b: Bool) Bool { + switch a { + true => b, + false => false, + } +} +fun or(a: Bool, b: Bool) Bool { + switch a { + true => true, + false => b, + } +} +fun xor(a: Bool, b: Bool) Bool { + a.equals(b).not() +} +fun implies(a: Bool, b: Bool) Bool { + not(a).or(b) +} +impl Bool: Equal { + fun equals(self: Bool, other: Bool) Bool { + switch self { + true => other, + false => other.not(), + } + } +} impl Bool: ToText { fun toText(self: Bool) Text { switch self { From 28bcf5f4564c82b230168e253f4772f651ad0d58 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 15:29:51 +0100 Subject: [PATCH 07/24] Add trait HasLength --- packages_v5/example.candy | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 236f1f086..d7cd9ce15 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -101,9 +101,18 @@ impl Int: Subtract { } } +trait HasLength { + fun length(self: Self) Int +} +fun isEmpty[T: HasLength](t: T) Bool { + t.length().equals(0) +} + struct Text = builtin -fun length(self: Text) Int { - self.builtinTextLength() +impl Text: HasLength { + fun length(self: Text) Int { + self.builtinTextLength() + } } fun concat(self: Text, other: Text) Text { self.builtinTextConcat(other) @@ -138,8 +147,10 @@ struct Array[T] = builtin fun arrayFilled[T](length: Int, item: T) Array[T] { builtinArrayFilled(length, item) } -fun length[T](array: Array[T]) Int { - builtinArrayLength(array) +impl[T] Array[T]: HasLength { + fun length(self: Array[T]) Int { + builtinArrayLength(self) + } } fun print[T: ToText](t: T) { @@ -285,6 +296,7 @@ fun main() Int { print(true) print(false) print("1.equals(2): {1.equals(2).toText()}") + print("orld!".endsWith("World!").toText().isEmpty()) 0 } From a4b9a304d744aee47f3bd2c58fe22c815b898669 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 18:57:17 +0100 Subject: [PATCH 08/24] Add int.is[Non](Positive|Negative)(), .negate(), .absolute() --- packages_v5/example.candy | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index d7cd9ce15..1bad02409 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -90,6 +90,24 @@ impl Int: Compare { self.builtinIntCompareTo(other) } } +fun isPositive(self: Int) Bool { + self.isGreaterThan(0) +} +fun isNonPositive(self: Int) Bool { + self.isLessThanOrEqualTo(0) +} +fun isNegative(self: Int) Bool { + self.isLessThan(0) +} +fun isNonNegative(self: Int) Bool { + self.isGreaterThanOrEqualTo(0) +} +fun absolute(self: Int) Int { + switch self.isNegative() { + true => self.negate(), + false => self, + } +} impl Int: Add { fun add(self: Int, other: Int) Int { self.builtinIntAdd(other) @@ -100,6 +118,9 @@ impl Int: Subtract { self.builtinIntSubtract(other) } } +fun negate(self: Int) Int { + 0.subtract(self) +} trait HasLength { fun length(self: Self) Int From 8b2679530bea765110747a72f251482437c6aee0 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 18:58:23 +0100 Subject: [PATCH 09/24] Add maybe.isSome(), .isNone() --- packages_v5/example.candy | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 1bad02409..d1e81e870 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -237,6 +237,18 @@ fun some[T](value: T) Maybe[T] { fun none[T]() Maybe[T] { Maybe.none[T]() } +fun isSome[T](self: Maybe[T]) Bool { + switch self { + some(value) => true, + none => false, + } +} +fun isNone[T](self: Maybe[T]) Bool { + switch self { + some(value) => false, + none => true, + } +} # impl[T] Maybe[T]: ToText { # fun toText(self: Maybe[T]) Text { # switch self { From 46da39a8743ba9d357385c1b9dd7b9b4e16d9bf2 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 18:59:28 +0100 Subject: [PATCH 10/24] =?UTF-8?q?Add=20text.removePrefix(=E2=80=A6),=20.re?= =?UTF-8?q?moveSuffix(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages_v5/example.candy | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index d1e81e870..f62e0a13a 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -153,6 +153,18 @@ fun endsWith(self: Text, suffix: Text) Bool { true => self.getRange(self.length().subtract(suffix.length()), self.length()).equals(suffix), } } +fun removePrefix(self: Text, prefix: Text) Text { + switch self.startsWith(prefix) { + false => self, + true => self.getRange(prefix.length(), self.length()), + } +} +fun removeSuffix(self: Text, suffix: Text) Text { + switch self.endsWith(suffix) { + false => self, + true => self.getRange(self.length().subtract(suffix.length()), self.length()), + } +} impl Text: ToText { fun toText(self: Text) Text { self From 9bf4bce32b897cd7baf9dcd87f5fc5dde6154d9d Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 18:59:34 +0100 Subject: [PATCH 11/24] =?UTF-8?q?Add=20text.characterAt(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages_v5/example.candy | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index f62e0a13a..81ed32f2a 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -141,6 +141,13 @@ fun concat(self: Text, other: Text) Text { 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())) { + false => none[Text](), + true => some(self.getRange(index, index.add(1))), + } +} + fun startsWith(self: Text, prefix: Text) Bool { switch self.length().isGreaterThanOrEqualTo(prefix.length()) { false => false, From dce73898e7985ba79fbfea88a6c677a782fd9ea4 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 18:59:58 +0100 Subject: [PATCH 12/24] =?UTF-8?q?Add=20text.indexOf(=E2=80=A6),=20.contain?= =?UTF-8?q?s(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler_v4/src/hir.rs | 11 +++++++++++ compiler_v4/src/mono_to_c.rs | 15 +++++++++++++++ packages_v5/example.candy | 7 +++++++ 3 files changed, 33 insertions(+) diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index fa58ea9ab..20869f369 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -721,6 +721,7 @@ pub enum BuiltinFunction { TextCompareTo, TextConcat, TextGetRange, + TextIndexOf, TextLength, } impl BuiltinFunction { @@ -831,6 +832,16 @@ impl BuiltinFunction { .into(), return_type: NamedType::text().into(), }, + Self::TextIndexOf => BuiltinFunctionSignature { + name: "builtinTextIndexOf".into(), + type_parameters: Box::default(), + parameters: [ + ("a".into(), NamedType::text().into()), + ("b".into(), NamedType::text().into()), + ] + .into(), + return_type: NamedType::maybe(NamedType::int()).into(), + }, Self::TextLength => BuiltinFunctionSignature { name: "builtinTextLength".into(), type_parameters: Box::default(), diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index 901b49e20..844f8759d 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -309,6 +309,21 @@ impl<'h> Context<'h> { 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 == nullptr) {{ + result_pointer->variant = {return_type}_none; + }} else {{ + result_pointer->variant = {return_type}_some; + result_pointer->value.some = result - {a}->value; + }} + return result_pointer;", + 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)); diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 81ed32f2a..39355abd5 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -172,6 +172,13 @@ fun removeSuffix(self: Text, suffix: Text) Text { true => self.getRange(self.length().subtract(suffix.length()), self.length()), } } + +fun indexOf(self: Text, other: Text) Maybe[Int] { + self.builtinTextIndexOf(other) +} +fun contains(self: Text, other: Text) Bool { + self.indexOf(other).isSome() +} impl Text: ToText { fun toText(self: Text) Text { self From b3df2b8967174ad2490d0a61ee2972596024612b Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 19:00:11 +0100 Subject: [PATCH 13/24] Remove unnecessary type annotations --- packages_v5/example.candy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 39355abd5..e18c5bcf2 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -210,8 +210,8 @@ struct MyStruct { } enum Bool { true, false } -let true: Bool = Bool.true() -let false: Bool = Bool.false() +let true = Bool.true() +let false = Bool.false() fun not(value: Bool) Bool { switch value { true => false, From c6e5f821a17bf61e55c55c6aeacd3535779cfbd9 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 19:10:39 +0100 Subject: [PATCH 14/24] Revert "Remove unnecessary type annotations" This reverts commit b3df2b8967174ad2490d0a61ee2972596024612b. --- packages_v5/example.candy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index cdbb65547..adeef2f08 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -281,8 +281,8 @@ struct MyStruct { } enum Bool { true, false } -let true = Bool.true() -let false = Bool.false() +let true: Bool = Bool.true() +let false: Bool = Bool.false() fun not(value: Bool) Bool { switch value { true => false, From 7595096efbb75724db0d4c86864f3544e09682bb Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 19:15:20 +0100 Subject: [PATCH 15/24] =?UTF-8?q?Fix=20builtinTextIndexOf(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler_v4/src/mono_to_c.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index 7e801d3cc..21cb8e2b4 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -487,13 +487,15 @@ impl<'h> Context<'h> { )), BuiltinFunction::TextIndexOf => self.push(format!( "\ - {return_type} result_pointer = malloc(sizeof({return_type})); + {return_type}* result_pointer = malloc(sizeof({return_type})); char* result = strstr({a}->value, {b}->value); - if (result == nullptr) {{ + if (result == NULL) {{ result_pointer->variant = {return_type}_none; }} else {{ result_pointer->variant = {return_type}_some; - result_pointer->value.some = result - {a}->value; + Int* index_pointer = malloc(sizeof(Int)); + index_pointer->value = result - {a}->value; + result_pointer->value.some = index_pointer; }} return result_pointer;", a = function.parameters[0].id, From e9d71524e5bd3c3e906b27dc5582f967495bf619 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 19:15:28 +0100 Subject: [PATCH 16/24] Fix HIR text formatting --- compiler_v4/src/hir.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index 21d5fd68f..f3a31bb10 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -182,6 +182,9 @@ impl ToText for (&str, &TraitDefinition) { .sorted_by_key(|(_, it)| it.signature.name.clone()), |builder, (id, function)| (*id, *function).build_text(builder), ); + if !definition.functions.is_empty() { + builder.push_newline(); + } builder.push("}"); } } @@ -252,6 +255,9 @@ impl ToText for Impl { (*id, *function).build_text(builder); }, ); + if !self.functions.is_empty() { + builder.push_newline(); + } builder.push("}"); } } From 77af947fc267cabe935505ba986e5359ddab0f71 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 19:56:27 +0100 Subject: [PATCH 17/24] Add error for missing assignment type --- compiler_v4/src/ast.rs | 1 + compiler_v4/src/ast_to_hir.rs | 13 +++++++++---- compiler_v4/src/string_to_ast/declarations.rs | 4 ++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/compiler_v4/src/ast.rs b/compiler_v4/src/ast.rs index 3f80dfab3..7910e5cdf 100644 --- a/compiler_v4/src/ast.rs +++ b/compiler_v4/src/ast.rs @@ -131,6 +131,7 @@ pub struct AstImpl { #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct AstAssignment { + pub display_span: Range, pub name: AstResult, pub type_: Option>, pub equals_sign_error: Option, diff --git a/compiler_v4/src/ast_to_hir.rs b/compiler_v4/src/ast_to_hir.rs index d01632b13..f68d63d34 100644 --- a/compiler_v4/src/ast_to_hir.rs +++ b/compiler_v4/src/ast_to_hir.rs @@ -982,10 +982,15 @@ impl<'a> Context<'a> { let id = self.id_generator.generate(); // TODO: infer type - let type_ = assignment - .type_ - .as_ref() - .map_or(Type::Error, |it| self.lower_type(&[], None, it.value())); + let type_ = if let Some(type_) = assignment.type_.as_ref() { + self.lower_type(&[], None, type_.value()) + } else { + self.add_error( + assignment.display_span.clone(), + "Assignment is missing a type", + ); + Type::Error + }; match self.global_identifiers.entry(name.string.clone()) { Entry::Occupied(mut entry) => { diff --git a/compiler_v4/src/string_to_ast/declarations.rs b/compiler_v4/src/string_to_ast/declarations.rs index 654092ad3..e13be0960 100644 --- a/compiler_v4/src/string_to_ast/declarations.rs +++ b/compiler_v4/src/string_to_ast/declarations.rs @@ -285,11 +285,14 @@ fn impl_<'a>(parser: Parser) -> Option<(Parser, AstImpl)> { #[instrument(level = "trace")] pub fn assignment<'a>(parser: Parser) -> Option<(Parser, AstAssignment)> { + let let_keyword_start = parser.offset(); let parser = let_keyword(parser)?.and_trailing_whitespace(); + let let_keyword_span = let_keyword_start..parser.offset(); let (parser, name) = raw_identifier(parser) .and_trailing_whitespace() .unwrap_or_ast_error_result(parser, "This assignment is missing a name."); + let display_span = name.value().map_or(let_keyword_span, |it| it.span.clone()); let (parser, type_) = colon(parser) @@ -312,6 +315,7 @@ pub fn assignment<'a>(parser: Parser) -> Option<(Parser, AstAssignment)> { Some(( parser, AstAssignment { + display_span, name, type_, equals_sign_error, From 6fe6fdb82d06675b42c8e9dfd5f803542a7c1036 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 19:56:39 +0100 Subject: [PATCH 18/24] Remove unused code --- packages_v5/example.candy | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index adeef2f08..e89f93d9d 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -361,26 +361,8 @@ impl[T: ToText] Maybe[T]: ToText { } } -enum MyEnum { - foo: Int, - bar, -} - # function type: `Fun[Int, Text, Int]`, later `Fun (Int, Text) Int` -# Expressions -let intExpression: Int = 42 -let textExpression: Text = "Hello, World!" -# let textExpressionWithInterpolation = "The answer is {intExpression}!" -let identifierExpression: Int = intExpression -# let parenthesizedExpression = (intExpression) -let structExpression: MyStruct = MyStruct("Banana", 12345) -# let lambdaExpression = (x: Int) { x } - -# Assignments -let valueWithExplicitType: Int = 42 -let valueWithoutExplicitType = 42 - # Uniform Function Call Syntax # Functions can also be overloaded # fun add(aDate: Date, duration: Duration) Self { @@ -393,15 +375,6 @@ let valueWithoutExplicitType = 42 # } # aDate.add(Duration(days: 1)) # is equivalent to `add(aDate, Duration(days: 1))` -# fun twar() Int { -# foo -# bar -# fds -# % True: foo -# fff -# False: blub -#} - fun fibonacci(n: Int) Int { switch n.isLessThan(2) { true => n, From 9a56de274898f3dff61f969da96b654fdb9c7d30 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Thu, 28 Nov 2024 19:56:52 +0100 Subject: [PATCH 19/24] impl[T] List[T]: HasLength --- packages_v5/example.candy | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index e89f93d9d..d5c10617f 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -194,16 +194,6 @@ fun panic(message: Text) Never { builtinPanic(message) } -struct Array[T] = builtin -fun arrayFilled[T](length: Int, item: T) Array[T] { - builtinArrayFilled(length, item) -} -impl[T] Array[T]: HasLength { - fun length(self: Array[T]) Int { - builtinArrayLength(self) - } -} - struct List[T] = builtin fun listFilled[T](length: Int, item: T) List[T] { builtinListFilled(length, item) @@ -227,11 +217,10 @@ fun listOf[T](item0: T, item1: T, item2: T, item3: T) List[T] { fun listOf[T](item0: T, item1: T, item2: T, item3: T, item4: T) List[T] { builtinListOf(item0, item1, item2, item3, item4) } -fun length[T](list: List[T]) Int { - builtinListLength(list) -} -fun isEmpty[T](list: List[T]) Bool { - list.length().equals(0) +impl[T] List[T]: HasLength { + fun length(self: List[T]) Int { + builtinListLength(self) + } } fun lastIndex[T](list: List[T]) Maybe[Int] { switch list.isEmpty() { From 41e0e64e7d40d6fa325ff5a89fbd2e0c74c112a1 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 29 Nov 2024 00:14:54 +0100 Subject: [PATCH 20/24] =?UTF-8?q?Add=20needs(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages_v5/example.candy | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index d5c10617f..2554e2317 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -193,6 +193,19 @@ impl Text: Compare { fun panic(message: Text) Never { builtinPanic(message) } +# TODO: build this into the language; fuzzing +fun needs(condition: Bool) { + switch condition { + true => Nothing(), + false => panic("Needs not fulfilled"), + } +} +fun needs(condition: Bool, message: Text) { + switch condition { + true => Nothing(), + false => panic("Needs not fulfilled: {message}"), + } +} struct List[T] = builtin fun listFilled[T](length: Int, item: T) List[T] { From 910e46028d7a8b5827a46a6acc1a4d18df0deb6e Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 29 Nov 2024 00:15:23 +0100 Subject: [PATCH 21/24] Add Result[T, E] --- compiler_v4/src/hir.rs | 4 ++++ packages_v5/example.candy | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index f3a31bb10..eacb80412 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -367,6 +367,10 @@ impl NamedType { pub fn ordering() -> Self { Self::new("Ordering", []) } + #[must_use] + pub fn result(t: impl Into, e: impl Into) -> Self { + Self::new("Result", [t.into(), e.into()]) + } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct ParameterType { diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 2554e2317..f910f4bac 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -363,6 +363,51 @@ impl[T: ToText] Maybe[T]: ToText { } } +enum Result[T, E] { + ok: T, + error: E, +} +fun ok[T, E](value: T) Result[T, E] { + Result.ok[T, E](value) +} +fun error[T, E](value: E) Result[T, E] { + Result.error[T, E](value) +} +fun unwrap[T, E](self: Result[T, E]) T { + switch self { + ok(value) => value, + error(value) => panic("`unwrap()` called on `error()`"), + } +} +fun isOk[T, E](self: Result[T, E]) Bool { + switch self { + ok(value) => true, + error(value) => false, + } +} +fun isError[T, E](self: Result[T, E]) Bool { + switch self { + ok(value) => false, + error(value) => true, + } +} +# impl[T: ToText, E: ToText] Result[T, E]: ToText { +# fun toText(self: Result[T, E]) Text { +# switch self { +# ok(value) => "ok({value.toText()})", +# error(error) => "error({error.toText()})", +# } +# } +# } +impl[T: ToText] Result[T, Text]: ToText { + fun toText(self: Result[T, Text]) Text { + switch self { + ok(value) => "ok({value.toText()})", + error(error) => "error({error})", + } + } +} + # function type: `Fun[Int, Text, Int]`, later `Fun (Int, Text) Int` # Uniform Function Call Syntax From 9d80fa7dfe7c421f9da36224ac55003b93985112 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 29 Nov 2024 00:15:45 +0100 Subject: [PATCH 22/24] Add Int functions --- compiler_v4/src/hir.rs | 99 +++++++++++++++++++++++++++++++++++- compiler_v4/src/mono_to_c.rs | 97 +++++++++++++++++++++++++++++++++-- packages_v5/example.candy | 81 +++++++++++++++++++++++++++++ 3 files changed, 272 insertions(+), 5 deletions(-) diff --git a/compiler_v4/src/hir.rs b/compiler_v4/src/hir.rs index eacb80412..0fe76c22f 100644 --- a/compiler_v4/src/hir.rs +++ b/compiler_v4/src/hir.rs @@ -727,7 +727,16 @@ pub struct SwitchCase { #[strum(serialize_all = "camelCase")] pub enum BuiltinFunction { IntAdd, + IntBitwiseAnd, + IntBitwiseOr, + IntBitwiseXor, IntCompareTo, + IntDivideTruncating, + IntMultiply, + IntParse, + IntRemainder, + IntShiftLeft, + IntShiftRight, IntSubtract, IntToText, ListFilled, @@ -769,6 +778,36 @@ impl BuiltinFunction { .into(), return_type: NamedType::int().into(), }, + Self::IntBitwiseAnd => BuiltinFunctionSignature { + name: "builtinIntBitwiseAnd".into(), + type_parameters: Box::default(), + parameters: [ + ("a".into(), NamedType::int().into()), + ("b".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::int().into(), + }, + Self::IntBitwiseOr => BuiltinFunctionSignature { + name: "builtinIntBitwiseOr".into(), + type_parameters: Box::default(), + parameters: [ + ("a".into(), NamedType::int().into()), + ("b".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::int().into(), + }, + Self::IntBitwiseXor => BuiltinFunctionSignature { + name: "builtinIntBitwiseXor".into(), + type_parameters: Box::default(), + parameters: [ + ("a".into(), NamedType::int().into()), + ("b".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::int().into(), + }, Self::IntCompareTo => BuiltinFunctionSignature { name: "builtinIntCompareTo".into(), type_parameters: Box::default(), @@ -779,12 +818,68 @@ impl BuiltinFunction { .into(), return_type: NamedType::ordering().into(), }, + Self::IntDivideTruncating => BuiltinFunctionSignature { + name: "builtinIntDivideTruncating".into(), + type_parameters: Box::default(), + parameters: [ + ("dividend".into(), NamedType::int().into()), + ("divisor".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::int().into(), + }, + Self::IntMultiply => BuiltinFunctionSignature { + name: "builtinIntMultiply".into(), + type_parameters: Box::default(), + parameters: [ + ("factorA".into(), NamedType::int().into()), + ("factorB".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::int().into(), + }, + Self::IntParse => BuiltinFunctionSignature { + name: "builtinIntParse".into(), + type_parameters: Box::default(), + parameters: [("text".into(), NamedType::text().into())].into(), + return_type: NamedType::result(NamedType::int(), NamedType::text()).into(), + }, + Self::IntRemainder => BuiltinFunctionSignature { + name: "builtinIntRemainder".into(), + type_parameters: Box::default(), + parameters: [ + ("dividend".into(), NamedType::int().into()), + ("divisor".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::int().into(), + }, + Self::IntShiftLeft => BuiltinFunctionSignature { + name: "builtinIntShiftLeft".into(), + type_parameters: Box::default(), + parameters: [ + ("value".into(), NamedType::int().into()), + ("amount".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::int().into(), + }, + Self::IntShiftRight => BuiltinFunctionSignature { + name: "builtinIntShiftRight".into(), + type_parameters: Box::default(), + parameters: [ + ("value".into(), NamedType::int().into()), + ("amount".into(), NamedType::int().into()), + ] + .into(), + return_type: NamedType::int().into(), + }, Self::IntSubtract => BuiltinFunctionSignature { name: "builtinIntSubtract".into(), type_parameters: Box::default(), parameters: [ - ("a".into(), NamedType::int().into()), - ("b".into(), NamedType::int().into()), + ("minuend".into(), NamedType::int().into()), + ("subtrahend".into(), NamedType::int().into()), ] .into(), return_type: NamedType::int().into(), diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index 21cb8e2b4..c09b6e721 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -190,6 +190,30 @@ impl<'h> Context<'h> { 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;", + 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;", + 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;", + a = function.parameters[0].id, + b = function.parameters[1].id, + )), BuiltinFunction::IntCompareTo => self.push(format!( "\ Ordering* result_pointer = malloc(sizeof(Ordering)); @@ -200,13 +224,80 @@ impl<'h> Context<'h> { 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;", + 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;", + factorA = function.parameters[0].id, + factorB = function.parameters[1].id, + )), + BuiltinFunction::IntParse => self.push(format!( + "\ + {return_type}* result_pointer = malloc(sizeof({return_type})); + char *end_pointer; + uint64_t value = strtol({text}->value, &end_pointer, 10); + 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\"; + }} 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); + 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; + }} else {{ + result_pointer->variant = {return_type}_ok; + result_pointer->value.ok = malloc(sizeof(Int)); + result_pointer->value.ok ->value = value; + }} + return result_pointer;", + 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;", + 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;", + 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;", + value = function.parameters[0].id, + amount = function.parameters[1].id, + )), BuiltinFunction::IntSubtract => self.push(format!( "\ Int* result_pointer = malloc(sizeof(Int)); - result_pointer->value = {a}->value - {b}->value; + result_pointer->value = {minuend}->value - {subtrahend}->value; return result_pointer;", - a = function.parameters[0].id, - b = function.parameters[1].id, + minuend = function.parameters[0].id, + subtrahend = function.parameters[1].id, )), BuiltinFunction::IntToText => self.push(format!( "\ diff --git a/packages_v5/example.candy b/packages_v5/example.candy index f910f4bac..7551cf5ef 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -121,6 +121,86 @@ impl Int: Subtract { fun negate(self: Int) Int { 0.subtract(self) } +fun multiply(self: Int, other: Int) Int { + self.builtinIntMultiply(other) +} +fun square(self: Int) Int { + self.multiply(self) +} +fun divideTruncating(dividend: Int, divisor: Int) Int { + needs(divisor.equals(0).not()) + + dividend.builtinIntDivideTruncating(divisor) +} +fun remainder(dividend: Int, divisor: Int) Int { + # Returns the remainder you get when dividing the `dividend` by the `divisor`. + # + # The result has the same sign as the `dividend`. + # + # | `dividend` | `divisor` | `dividend.remainder(divisor)` | + # |-----------:|----------:|---------------------------:| + # | 6 | 3 | 0 | + # | 5 | 3 | 2 | + # | -5 | 3 | -2 | + # | 5 | -3 | 2 | + # | -5 | -3 | -2 | + needs(divisor.equals(0).not()) + + dividend.builtinIntRemainder(divisor) +} +fun modulo(dividend: Int, divisor: Int) Int { + # Returns `dividend` % `divisor`. + # + # The result of a modulo operation is the smallest possible number $x$ such + # that there exists a $y$ with $dividend = y * divisor + x$. + # + # | `dividend` | `divisor` | `dividend.modulo(divisor)` | + # |-----------:|----------:|---------------------------:| + # | 6 | 3 | 0 | + # | 5 | 3 | 2 | + # | 5 | -3 | 2 | + # | -5 | 3 | 1 | + # | -5 | -3 | 1 | + needs(divisor.equals(0).not()) + + let remainder = dividend.remainder(divisor) + switch remainder.isNegative() { + true => switch divisor.isNegative() { + true => remainder.subtract(divisor), + false => remainder.add(divisor), + }, + false => remainder, + } +} +fun shiftLeft(value: Int, amount: Int) Int { + value.builtinIntShiftLeft(amount) +} +fun shiftRight(value: Int, amount: Int) Int { + value.builtinIntShiftRight(amount) +} +fun bitwiseAnd(self: Int, other: Int) Int { + self.builtinIntBitwiseAnd(other) +} +fun bitwiseOr(self: Int, other: Int) Int { + self.builtinIntBitwiseOr(other) +} +fun bitwiseXor(self: Int, other: Int) Int { + self.builtinIntBitwiseXor(other) +} +fun isEven(self: Int) Bool { + self.remainder(2).equals(0) +} +fun isOdd(self: Int) Bool { + self.remainder(2).equals(1) +} +fun lowestBits(value: Int, bitCount: Int) Int { + needs(bitCount.isNonNegative()) + let mask = 1.shiftLeft(bitCount).subtract(1) + value.bitwiseAnd(mask) +} +fun parseInt(text: Text) Result[Int, Text] { + text.builtinIntParse() +} trait HasLength { fun length(self: Self) Int @@ -138,6 +218,7 @@ impl Text: HasLength { fun concat(self: Text, other: Text) Text { self.builtinTextConcat(other) } +# TODO: Support ranges when we have them. fun getRange(self: Text, startInclusive: Int, endExclusive: Int) Text { self.builtinTextGetRange(startInclusive, endExclusive) } From fc1f00124ca2a35d4b36ef33fbe481481f0b7387 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Fri, 29 Nov 2024 00:21:43 +0100 Subject: [PATCH 23/24] =?UTF-8?q?Fix=20builtinIntParse(=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler_v4/src/mono_to_c.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/compiler_v4/src/mono_to_c.rs b/compiler_v4/src/mono_to_c.rs index c09b6e721..ad06e8017 100644 --- a/compiler_v4/src/mono_to_c.rs +++ b/compiler_v4/src/mono_to_c.rs @@ -25,6 +25,7 @@ impl<'h> Context<'h> { } fn lower_mono(&mut self) { + self.push("#include \n"); self.push("#include \n"); self.push("#include \n"); self.push("#include \n"); @@ -244,13 +245,18 @@ impl<'h> Context<'h> { "\ {return_type}* result_pointer = malloc(sizeof({return_type})); char *end_pointer; + errno = 0; uint64_t value = strtol({text}->value, &end_pointer, 10); - if (end_pointer == {text}->value) {{ + if (errno == ERANGE) {{ result_pointer->variant = {return_type}_error; result_pointer->value.error = malloc(sizeof(Text)); - result_pointer->value.error->value = \"Text is empty\"; - }} else if (*end_pointer != '\0') {{ - char* message_format = \"Non-numeric character \"%c\" at index %ld.\"; + 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.\"; + }} 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); char *message = malloc(length + 1); snprintf(message, length + 1, message_format, *end_pointer, end_pointer - {text}->value); From fbaea78b0ddf7abe623e2fd2de9148d5fd417399 Mon Sep 17 00:00:00 2001 From: Jonas Wanke Date: Sun, 8 Dec 2024 19:33:16 +0100 Subject: [PATCH 24/24] =?UTF-8?q?Fix=20doc=20comment=20for=20int.modulo(?= =?UTF-8?q?=E2=80=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages_v5/example.candy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages_v5/example.candy b/packages_v5/example.candy index 7551cf5ef..7277658d8 100644 --- a/packages_v5/example.candy +++ b/packages_v5/example.candy @@ -151,8 +151,8 @@ fun remainder(dividend: Int, divisor: Int) Int { fun modulo(dividend: Int, divisor: Int) Int { # Returns `dividend` % `divisor`. # - # The result of a modulo operation is the smallest possible number $x$ such - # that there exists a $y$ with $dividend = y * divisor + x$. + # The result of a modulo operation is the smallest non-negative number $x$ + # such that there exists a $y$ with $dividend = y * divisor + x$. # # | `dividend` | `divisor` | `dividend.modulo(divisor)` | # |-----------:|----------:|---------------------------:|