Skip to content

Commit

Permalink
Merge pull request #343 from candy-lang/fixed-decimals
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcelGarus authored Feb 23, 2023
2 parents 8ad8d8b + 5a2983a commit 5ff6b5d
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 13 deletions.
51 changes: 38 additions & 13 deletions compiler/frontend/src/hir_to_mir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,9 +297,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,
Expand Down Expand Up @@ -408,56 +414,75 @@ 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>,
) -> 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.ongoing_destructuring = Some(OngoingDestructuring {
result: pattern_result,
is_trivial: 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_match,
)
}
}
Expand Down
9 changes: 9 additions & 0 deletions compiler/vm/src/builtin_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(&dividend.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)))
})
}
Expand All @@ -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(&dividend.value % &divisor.value))
})
}
Expand Down
1 change: 1 addition & 0 deletions packages/Core/_.candy
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ channel := use ".channel"
[async, await, parallel] := use ".concurrency"
[if, ifElse, loop, recursive, repeat] := use ".controlFlow"
[equals] := use ".equality"
fixedDecimal := use ".fixedDecimal"
function := use ".function"
int := use ".int"
iterable := use ".iterable"
Expand Down
131 changes: 131 additions & 0 deletions packages/Core/fixedDecimal.candy
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# 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"
[equals] = use "..equality"
[run] = use "..function"
int = use "..int"
text = use "..text"
[toDebugText] = use "..toDebugText"

decimalsPow = 10000000
# TODO: Perhaps allow different levels of precision.

is := int.is

fromInt a :=
needs (int.is a)
a | int.multiply decimalsPow
floorToInt a :=
needs (is a)
a | int.divideTruncating decimalsPow

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

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

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)
needs (is b)
needs (is 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
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}"
3 changes: 3 additions & 0 deletions packages/Core/int.candy
Original file line number Diff line number Diff line change
Expand Up @@ -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 :=
Expand Down
17 changes: 17 additions & 0 deletions packages/examples/sqrt.candy
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[equals, fixedDecimal, ifElse, recursive] = use "...Core"

sqrt x :=
needs (fixedDecimal.is x)
needs (fixedDecimal.isNonNegative x)

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 | fixedDecimal.fromInt | sqrt
✨.print "The root of {input} is {result | fixedDecimal.toText}"

1 comment on commit 5ff6b5d

@jwbot
Copy link
Collaborator

@jwbot jwbot commented on 5ff6b5d Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compiler

Benchmark suite Current: 5ff6b5d Previous: 8ad8d8b Ratio
Time: Compiler/hello_world 24339103 ns/iter (± 546903) 32101724 ns/iter (± 955340) 0.76
Time: Compiler/fibonacci 887873042 ns/iter (± 3332966) 962495231 ns/iter (± 9307315) 0.92
Time: VM Runtime/hello_world 19599907 ns/iter (± 200098) 25174112 ns/iter (± 479396) 0.78
Time: VM Runtime/fibonacci/15 198526755 ns/iter (± 1687323) 250683122 ns/iter (± 2093043) 0.79

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.