From a893d99f6bc1e54a42917dbaa0e5ef402933487e Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Mon, 20 Feb 2023 00:04:52 +0100 Subject: [PATCH 01/12] Add fixed decimals --- packages/Core/_.candy | 1 + packages/Core/fixedDecimal.candy | 51 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 packages/Core/fixedDecimal.candy diff --git a/packages/Core/_.candy b/packages/Core/_.candy index 995e2df59..7958f132f 100644 --- a/packages/Core/_.candy +++ b/packages/Core/_.candy @@ -4,6 +4,7 @@ channel := use ".channel" [async, await, parallel] := use ".concurrency" [if, ifElse, loop, recursive] := use ".controlFlow" [equals] := use ".equality" +fixedDecimal := use ".fixedDecimal" function := use ".function" int := use ".int" iterable := use ".iterable" diff --git a/packages/Core/fixedDecimal.candy b/packages/Core/fixedDecimal.candy new file mode 100644 index 000000000..abf4b5723 --- /dev/null +++ b/packages/Core/fixedDecimal.candy @@ -0,0 +1,51 @@ +bool = use "..bool" +[ifElse, recursive] = use "..controlFlow" +[equals] = use "..equality" +int = use "..int" +text = use "..text" +[toDebugText] = use "..toDebugText" + +decimalsPow = 10000000 + +is := int.is + +fromInt a := + needs (is a) + int.multiply a decimalsPow + +toInt a := + needs (is a) + int.divideTruncating a decimalsPow + +isNonNegative a := + needs (is a) + int.isNonNegative a + +add a b := + needs (is a) + needs (is b) + a | int.add b + +multiply a b := + needs (is a) + needs (is b) + a | int.multiply b | int.divideTruncating decimalsPow + +divide a b := + needs (is a) + needs (is b) + needs (b | equals 0 | bool.not) + a | int.multiply decimalsPow | int.divideTruncating b + +approxEquals a b delta := + needs (is a) + needs (is b) + needs (int.is delta) + needs (int.isNonNegative delta) + a | int.subtract b | int.absolute | int.isLessThanOrEqualTo delta + +toText a := + needs (is a) + beforeDot = a | toInt | toDebugText + afterDot = a | int.modulo decimalsPow | int.add decimalsPow | toDebugText | text.removePrefix "1" + "{beforeDot}.{afterDot}" From 021f81a2e8c3ff8d9b8455e94a4dcc033a7bc123 Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Mon, 20 Feb 2023 00:05:12 +0100 Subject: [PATCH 02/12] Add sqrt example --- packages/examples/sqrt.candy | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 packages/examples/sqrt.candy diff --git a/packages/examples/sqrt.candy b/packages/examples/sqrt.candy new file mode 100644 index 000000000..cac4c8b9a --- /dev/null +++ b/packages/examples/sqrt.candy @@ -0,0 +1,18 @@ +[equals, fixedDecimal, ifElse, recursive] = use "...Core" +fixed = fixedDecimal + +sqrt x := + needs (fixed.is x) + needs (fixed.isNonNegative x) + + recursive (x | fixed.divide (2 | fixed.fromInt)) { recurse guess -> + refinedGuess = fixed.divide + (guess | fixed.add (x | fixed.divide guess)) + (2 | fixed.fromInt) + ifElse (fixed.approxEquals guess refinedGuess 10) { guess } { recurse refinedGuess } + } + +main _ := + input = 2 + result = input | fixed.fromInt | sqrt + ✨.print "The root of {input} is {result | fixed.toText}" From 0a3f7cc9ce92723729c2605eb6005284895e0e2e Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Mon, 20 Feb 2023 22:51:14 +0100 Subject: [PATCH 03/12] Fix attribution of responsibility in needs in match --- compiler/frontend/src/hir_to_mir.rs | 51 +++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/compiler/frontend/src/hir_to_mir.rs b/compiler/frontend/src/hir_to_mir.rs index 2a00394f9..e89fb0db6 100644 --- a/compiler/frontend/src/hir_to_mir.rs +++ b/compiler/frontend/src/hir_to_mir.rs @@ -294,9 +294,15 @@ impl<'a> LoweringContext<'a> { hir::Expression::Match { expression, cases } => { assert!(!cases.is_empty()); - let responsible = body.push_hir_id(hir_id.clone()); + let responsible_for_match = body.push_hir_id(hir_id.clone()); let expression = self.mapping[expression]; - self.compile_match(body, expression, cases, responsible) + self.compile_match( + body, + expression, + cases, + responsible_for_needs, + responsible_for_match, + ) } hir::Expression::Lambda(hir::Lambda { parameters: original_parameters, @@ -405,53 +411,72 @@ impl<'a> LoweringContext<'a> { body: &mut BodyBuilder, expression: Id, cases: &[(hir::Pattern, hir::Body)], - responsible: Id, + responsible_for_needs: Id, + responsible_for_match: Id, ) -> Id { - self.compile_match_rec(body, expression, cases, responsible, vec![]) + self.compile_match_rec( + body, + expression, + cases, + responsible_for_needs, + responsible_for_match, + vec![], + ) } fn compile_match_rec( &mut self, body: &mut BodyBuilder, expression: Id, cases: &[(hir::Pattern, hir::Body)], - responsible: Id, + responsible_for_needs: Id, + responsible_for_match: Id, mut no_match_reasons: Vec, ) -> Id { match cases { [] => { let reason = body.push_text("No case matched the given expression.".to_string()); // TODO: concat reasons - body.push_panic(reason, responsible) + body.push_panic(reason, responsible_for_match) } [(case_pattern, case_body), rest @ ..] => { let pattern_result = PatternLoweringContext::compile_pattern( self.db, body, - responsible, + responsible_for_match, expression, case_pattern, ); - let is_match = body.push_is_match(pattern_result, responsible); + let is_match = body.push_is_match(pattern_result, responsible_for_match); let builtin_if_else = body.push_builtin(BuiltinFunction::IfElse); let then_lambda = body.push_lambda(|body, _| { self.last_pattern_result = Some((pattern_result, false)); - self.compile_expressions(body, responsible, &case_body.expressions); + self.compile_expressions(body, responsible_for_needs, &case_body.expressions); }); let else_lambda = body.push_lambda(|body, _| { let list_get_function = body.push_builtin(BuiltinFunction::ListGet); let one = body.push_int(1.into()); - let reason = - body.push_call(list_get_function, vec![pattern_result, one], responsible); + let reason = body.push_call( + list_get_function, + vec![pattern_result, one], + responsible_for_match, + ); no_match_reasons.push(reason); - self.compile_match_rec(body, expression, rest, responsible, no_match_reasons); + self.compile_match_rec( + body, + expression, + rest, + responsible_for_needs, + responsible_for_match, + no_match_reasons, + ); }); body.push_call( builtin_if_else, vec![is_match, then_lambda, else_lambda], - responsible, + responsible_for_needs, ) } } From db665fda9fcda3d356dea82e488c3e9946dbbe9f Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Wed, 22 Feb 2023 13:20:42 +0100 Subject: [PATCH 04/12] Don't crash VM if divsor is zero --- compiler/vm/src/builtin_functions.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/compiler/vm/src/builtin_functions.rs b/compiler/vm/src/builtin_functions.rs index 4d13795bb..9a5913210 100644 --- a/compiler/vm/src/builtin_functions.rs +++ b/compiler/vm/src/builtin_functions.rs @@ -277,11 +277,17 @@ impl Heap { } fn int_divide_truncating(&mut self, args: &[Pointer]) -> BuiltinResult { unpack_and_later_drop!(self, args, |dividend: &Int, divisor: &Int| { + if divisor.data.value == 0.into() { + return Err("Can't divide by zero.".to_string()); + } Return(self.create_int(÷nd.value / &divisor.value)) }) } fn int_modulo(&mut self, args: &[Pointer]) -> BuiltinResult { unpack_and_later_drop!(self, args, |dividend: &Int, divisor: &Int| { + if divisor.data.value == 0.into() { + return Err("Can't divide by zero.".to_string()); + } Return(self.create_int(dividend.value.mod_floor(&divisor.value))) }) } @@ -301,6 +307,9 @@ impl Heap { } fn int_remainder(&mut self, args: &[Pointer]) -> BuiltinResult { unpack_and_later_drop!(self, args, |dividend: &Int, divisor: &Int| { + if divisor.data.value == 0.into() { + return Err("Can't divide by zero.".to_string()); + } Return(self.create_int(÷nd.value % &divisor.value)) }) } From 6bdd464fe6fdb2ff7567928f32cfad6e9e164239 Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Wed, 22 Feb 2023 13:21:20 +0100 Subject: [PATCH 05/12] Add needs for the divisor not being zero --- packages/Core/fixedDecimal.candy | 2 +- packages/Core/int.candy | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/Core/fixedDecimal.candy b/packages/Core/fixedDecimal.candy index abf4b5723..225e89934 100644 --- a/packages/Core/fixedDecimal.candy +++ b/packages/Core/fixedDecimal.candy @@ -34,7 +34,7 @@ multiply a b := divide a b := needs (is a) needs (is b) - needs (b | equals 0 | bool.not) + needs (b | equals 0 | bool.not) "You can't divide by zero." a | int.multiply decimalsPow | int.divideTruncating b approxEquals a b delta := diff --git a/packages/Core/int.candy b/packages/Core/int.candy index ca1f0d0b4..390874300 100644 --- a/packages/Core/int.candy +++ b/packages/Core/int.candy @@ -24,14 +24,17 @@ multiply factorA factorB := divideTruncating dividend divisor := needs (is dividend) needs (is divisor) + needs (divisor | equals 0 | bool.not) "You can't divide by zero." dividend | ✨.intDivideTruncating divisor remainder dividend divisor := needs (is dividend) needs (is divisor) + needs (divisor | equals 0 | bool.not) "You can't divide by zero." dividend | ✨.intRemainder divisor modulo dividend divisor := needs (is dividend) needs (is divisor) + needs (divisor | equals 0 | bool.not) "You can't divide by zero." dividend | ✨.intModulo divisor compareTo valueA valueB := From a4ebf7aa0a7761ca65d4b41f4a3c8998632693c2 Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Wed, 22 Feb 2023 17:00:35 +0100 Subject: [PATCH 06/12] Add some comments and clean up code --- packages/Core/fixedDecimal.candy | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/Core/fixedDecimal.candy b/packages/Core/fixedDecimal.candy index 225e89934..0271b6652 100644 --- a/packages/Core/fixedDecimal.candy +++ b/packages/Core/fixedDecimal.candy @@ -1,3 +1,6 @@ +# As soon as tags or value hiding is supported, change fixed point numbers to +# that. Currently, they're just normal ints. + bool = use "..bool" [ifElse, recursive] = use "..controlFlow" [equals] = use "..equality" @@ -6,6 +9,7 @@ text = use "..text" [toDebugText] = use "..toDebugText" decimalsPow = 10000000 +# TODO: Perhaps allow different levels of precision. is := int.is @@ -13,7 +17,7 @@ fromInt a := needs (is a) int.multiply a decimalsPow -toInt a := +floorToInt a := needs (is a) int.divideTruncating a decimalsPow @@ -38,6 +42,9 @@ divide a b := a | int.multiply decimalsPow | int.divideTruncating b approxEquals a b delta := + # TODO: Instead of having to know the default precision and pass in an + # absolute delta, you should be able to specify how many digits of precision + # should be equal. needs (is a) needs (is b) needs (int.is delta) @@ -46,6 +53,6 @@ approxEquals a b delta := toText a := needs (is a) - beforeDot = a | toInt | toDebugText + beforeDot = a | floorToInt | toDebugText afterDot = a | int.modulo decimalsPow | int.add decimalsPow | toDebugText | text.removePrefix "1" "{beforeDot}.{afterDot}" From 2baa7282778c185584b902dbcd4285c55a75dde5 Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Thu, 23 Feb 2023 15:27:23 +0100 Subject: [PATCH 07/12] Apply suggestions from code review Co-authored-by: Jonas Wanke --- compiler/frontend/src/hir_to_mir.rs | 2 +- packages/Core/fixedDecimal.candy | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/frontend/src/hir_to_mir.rs b/compiler/frontend/src/hir_to_mir.rs index e89fb0db6..965491491 100644 --- a/compiler/frontend/src/hir_to_mir.rs +++ b/compiler/frontend/src/hir_to_mir.rs @@ -476,7 +476,7 @@ impl<'a> LoweringContext<'a> { body.push_call( builtin_if_else, vec![is_match, then_lambda, else_lambda], - responsible_for_needs, + responsible_for_match, ) } } diff --git a/packages/Core/fixedDecimal.candy b/packages/Core/fixedDecimal.candy index 0271b6652..78fa3fcab 100644 --- a/packages/Core/fixedDecimal.candy +++ b/packages/Core/fixedDecimal.candy @@ -1,4 +1,4 @@ -# As soon as tags or value hiding is supported, change fixed point numbers to +# TODO: As soon as tags or value hiding is supported, change fixed point numbers to # that. Currently, they're just normal ints. bool = use "..bool" @@ -14,16 +14,16 @@ decimalsPow = 10000000 is := int.is fromInt a := - needs (is a) - int.multiply a decimalsPow + needs (int.is a) + a | int.multiply decimalsPow floorToInt a := needs (is a) - int.divideTruncating a decimalsPow + a | int.divideTruncating decimalsPow isNonNegative a := needs (is a) - int.isNonNegative a + a | int.isNonNegative add a b := needs (is a) From 99a21ebe1f48a6ade779938118cfc6dd8e74ef43 Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Thu, 23 Feb 2023 15:32:39 +0100 Subject: [PATCH 08/12] Don't use an import alias --- packages/examples/sqrt.candy | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/examples/sqrt.candy b/packages/examples/sqrt.candy index cac4c8b9a..2bd30c97b 100644 --- a/packages/examples/sqrt.candy +++ b/packages/examples/sqrt.candy @@ -1,18 +1,17 @@ [equals, fixedDecimal, ifElse, recursive] = use "...Core" -fixed = fixedDecimal sqrt x := - needs (fixed.is x) - needs (fixed.isNonNegative x) + needs (fixedDecimal.is x) + needs (fixedDecimal.isNonNegative x) - recursive (x | fixed.divide (2 | fixed.fromInt)) { recurse guess -> - refinedGuess = fixed.divide - (guess | fixed.add (x | fixed.divide guess)) - (2 | fixed.fromInt) - ifElse (fixed.approxEquals guess refinedGuess 10) { guess } { recurse refinedGuess } + recursive (x | fixedDecimal.divide (2 | fixedDecimal.fromInt)) { recurse guess -> + refinedGuess = fixedDecimal.divide + (guess | fixedDecimal.add (x | fixedDecimal.divide guess)) + (2 | fixedDecimal.fromInt) + ifElse (fixedDecimal.approxEquals guess refinedGuess 10) { guess } { recurse refinedGuess } } main _ := input = 2 - result = input | fixed.fromInt | sqrt - ✨.print "The root of {input} is {result | fixed.toText}" + result = input | fixedDecimal.fromInt | sqrt + ✨.print "The root of {input} is {result | fixedDecimal.toText}" From 288998b8b29aa3e9374656deb9e27290577dcdc0 Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Thu, 23 Feb 2023 16:01:52 +0100 Subject: [PATCH 09/12] Reflow comment text --- packages/Core/fixedDecimal.candy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/Core/fixedDecimal.candy b/packages/Core/fixedDecimal.candy index 78fa3fcab..f7053e6e3 100644 --- a/packages/Core/fixedDecimal.candy +++ b/packages/Core/fixedDecimal.candy @@ -1,5 +1,5 @@ -# TODO: As soon as tags or value hiding is supported, change fixed point numbers to -# that. Currently, they're just normal ints. +# TODO: As soon as tags or value hiding is supported, change fixed point numbers +# to that. Currently, they're just normal ints. bool = use "..bool" [ifElse, recursive] = use "..controlFlow" From 7d8d6601df3a2c6560370fc07e49d2707ff385dc Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Thu, 23 Feb 2023 16:02:06 +0100 Subject: [PATCH 10/12] Use fixed decimal delta in approxEquals --- packages/Core/fixedDecimal.candy | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/Core/fixedDecimal.candy b/packages/Core/fixedDecimal.candy index f7053e6e3..d3a7c783e 100644 --- a/packages/Core/fixedDecimal.candy +++ b/packages/Core/fixedDecimal.candy @@ -42,13 +42,10 @@ divide a b := a | int.multiply decimalsPow | int.divideTruncating b approxEquals a b delta := - # TODO: Instead of having to know the default precision and pass in an - # absolute delta, you should be able to specify how many digits of precision - # should be equal. needs (is a) needs (is b) - needs (int.is delta) - needs (int.isNonNegative delta) + needs (is delta) + needs (isNonNegative delta) a | int.subtract b | int.absolute | int.isLessThanOrEqualTo delta toText a := From ead65d06d4b6c9e9478fc6ed10fa3214a13900b5 Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Thu, 23 Feb 2023 16:02:23 +0100 Subject: [PATCH 11/12] Fix fixedDecimal.toText for negative fixed decimals --- packages/Core/fixedDecimal.candy | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/Core/fixedDecimal.candy b/packages/Core/fixedDecimal.candy index d3a7c783e..5c96c8fe5 100644 --- a/packages/Core/fixedDecimal.candy +++ b/packages/Core/fixedDecimal.candy @@ -4,6 +4,7 @@ bool = use "..bool" [ifElse, recursive] = use "..controlFlow" [equals] = use "..equality" +[run] = use "..function" int = use "..int" text = use "..text" [toDebugText] = use "..toDebugText" @@ -51,5 +52,12 @@ approxEquals a b delta := toText a := needs (is a) beforeDot = a | floorToInt | toDebugText - afterDot = a | int.modulo decimalsPow | int.add decimalsPow | toDebugText | text.removePrefix "1" + afterDot = run { + tmp = a | int.remainder decimalsPow + ifElse (isNonNegative tmp) { + tmp | int.add decimalsPow | toDebugText | text.removePrefix "1" + } { + tmp | int.subtract decimalsPow | toDebugText | text.removePrefix "-1" + } + } "{beforeDot}.{afterDot}" From 809462d40d08cb37991c400fb825fe2298c1fe65 Mon Sep 17 00:00:00 2001 From: Marcel Garus Date: Thu, 23 Feb 2023 16:12:28 +0100 Subject: [PATCH 12/12] Add more fixedDecimal methods --- packages/Core/fixedDecimal.candy | 104 +++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 18 deletions(-) diff --git a/packages/Core/fixedDecimal.candy b/packages/Core/fixedDecimal.candy index 5c96c8fe5..37ca63a0b 100644 --- a/packages/Core/fixedDecimal.candy +++ b/packages/Core/fixedDecimal.candy @@ -17,30 +17,69 @@ is := int.is fromInt a := needs (int.is a) a | int.multiply decimalsPow - floorToInt a := needs (is a) a | int.divideTruncating decimalsPow -isNonNegative a := - needs (is a) - a | int.isNonNegative - -add a b := - needs (is a) - needs (is b) - a | int.add b +add summandA summandB := + needs (is summandA) + needs (is summandB) + summandA | int.add summandB +subtract minuend subtrahend := + needs (is minuend) + needs (is subtrahend) + minuend | int.subtract subtrahend +negate value := + needs (is value) + value | int.negate +multiply factorA factorB := + needs (is factorA) + needs (is factorB) + factorA | int.multiply factorB | int.divideTruncating decimalsPow +divide dividend divisor := + needs (is dividend) + needs (is divisor) + needs (divisor | equals 0 | bool.not) "You can't divide by zero." + dividend | int.multiply decimalsPow | int.divideTruncating divisor -multiply a b := - needs (is a) - needs (is b) - a | int.multiply b | int.divideTruncating decimalsPow +compareTo valueA valueB := + needs (is valueA) + needs (is valueB) + result = valueA | int.compare valueB + check (equals result Equal | bool.implies (equals valueA valueB)) + result +isLessThan valueA valueB := + needs (is valueA) + needs (is valueB) + equals (compareTo valueA valueB) Less +isGreaterThan valueA valueB := + needs (is valueA) + needs (is valueB) + equals (compareTo valueA valueB) Greater +isLessThanOrEqualTo valueA valueB := + needs (is valueA) + needs (is valueB) + valueA | isGreaterThan valueB | bool.not +isGreaterThanOrEqualTo valueA valueB := + needs (is valueA) + needs (is valueB) + valueA | isLessThan valueB | bool.not -divide a b := - needs (is a) - needs (is b) - needs (b | equals 0 | bool.not) "You can't divide by zero." - a | int.multiply decimalsPow | int.divideTruncating b +isPositive value := + needs (is value) + value | isGreaterThan 0 +isNonPositive value := + needs (is value) + value | isPositive | bool.not +isNegative value := + needs (is value) + value | isLessThan 0 +isNonNegative value := + needs (is value) + value | isNegative | bool.not +absolute value := + needs (is value) + ifElse (isNegative value) { negate value } { value } approxEquals a b delta := needs (is a) @@ -49,6 +88,35 @@ approxEquals a b delta := needs (isNonNegative delta) a | int.subtract b | int.absolute | int.isLessThanOrEqualTo delta +min valueA valueB := + needs (is valueA) + needs (is valueB) + ifElse + (valueA | isLessThanOrEqualTo valueB) + { valueA } + { valueB } +max valueA valueB := + needs (is valueA) + needs (is valueB) + ifElse + (valueA | isGreaterThanOrEqualTo valueB) + { valueA } + { valueB } +coerceAtLeast value minimum := + needs (is value) + needs (is minimum) + max value minimum +coerceAtMost value maximum := + needs (is value) + needs (is maximum) + min value maximum +coerceIn value minimum maximum := + needs (is value) + needs (is minimum) + needs (is maximum) + needs (minimum | isLessThanOrEqualTo maximum) + value | coerceAtLeast minimum | coerceAtMost maximum + toText a := needs (is a) beforeDot = a | floorToInt | toDebugText