From 7ab2192f12646bbcd72906a3afa5808e3476d103 Mon Sep 17 00:00:00 2001 From: Benjamin Peinhardt <benjaminpeinhardt@gmail.com> Date: Sat, 7 Dec 2024 00:15:09 -0600 Subject: [PATCH] interpreter cleanup --- priv/static/squared_away.mjs | 350 +++++++++++++----- src/squared_away.gleam | 2 - src/squared_away/renderable_error.gleam | 4 +- .../squared_away_lang/interpreter.gleam | 204 ++++++++-- .../squared_away_lang/util/rational.gleam | 54 +++ 5 files changed, 488 insertions(+), 126 deletions(-) diff --git a/priv/static/squared_away.mjs b/priv/static/squared_away.mjs index d8272d8..15faf7f 100644 --- a/priv/static/squared_away.mjs +++ b/priv/static/squared_away.mjs @@ -115,24 +115,44 @@ var UtfCodepoint = class { } }; function byteArrayToInt(byteArray, start3, end, isBigEndian, isSigned) { - let value3 = 0; - if (isBigEndian) { - for (let i = start3; i < end; i++) { - value3 = value3 * 256 + byteArray[i]; + const byteSize = end - start3; + if (byteSize <= 6) { + let value3 = 0; + if (isBigEndian) { + for (let i = start3; i < end; i++) { + value3 = value3 * 256 + byteArray[i]; + } + } else { + for (let i = end - 1; i >= start3; i--) { + value3 = value3 * 256 + byteArray[i]; + } + } + if (isSigned) { + const highBit = 2 ** (byteSize * 8 - 1); + if (value3 >= highBit) { + value3 -= highBit * 2; + } } + return value3; } else { - for (let i = end - 1; i >= start3; i--) { - value3 = value3 * 256 + byteArray[i]; + let value3 = 0n; + if (isBigEndian) { + for (let i = start3; i < end; i++) { + value3 = (value3 << 8n) + BigInt(byteArray[i]); + } + } else { + for (let i = end - 1; i >= start3; i--) { + value3 = (value3 << 8n) + BigInt(byteArray[i]); + } } - } - if (isSigned) { - const byteSize = end - start3; - const highBit = 2 ** (byteSize * 8 - 1); - if (value3 >= highBit) { - value3 -= highBit * 2; + if (isSigned) { + const highBit = 1n << BigInt(byteSize * 8 - 1); + if (value3 >= highBit) { + value3 -= highBit * 2n; + } } + return Number(value3); } - return value3; } function byteArrayToFloat(byteArray, start3, end, isBigEndian) { const view2 = new DataView(byteArray.buffer); @@ -4487,6 +4507,26 @@ var Rat = class extends CustomType { function from_int(input2) { return new Rat(from2(input2), from2(1)); } +function is_negative(r) { + let n = r.numerator; + let d = r.denominator; + return isEqual( + (() => { + let _pipe = multiply(n, d); + return compare4(_pipe, from2(0)); + })(), + new Lt() + ); +} +function is_zero(r) { + let n = r.numerator; + return isEqual(from2(0), n); +} +function is_whole_number(r) { + let n = r.numerator; + let d = r.denominator; + return isEqual(modulo(n, d), from2(0)); +} function commas(n) { let _pipe = n; let _pipe$1 = reverse3(_pipe); @@ -4730,6 +4770,28 @@ function multiply2(lhs, rhs) { let d2 = rhs.denominator; return simplify(new Rat(multiply(n1, n2), multiply(d1, d2))); } +function do_power(loop$base, loop$exponent, loop$acc) { + while (true) { + let base = loop$base; + let exponent = loop$exponent; + let acc = loop$acc; + if (exponent === 1) { + return new Ok(acc); + } else { + loop$base = base; + loop$exponent = exponent - 1; + loop$acc = multiply2(base, acc); + } + } +} +function power5(base, exponent) { + let $ = isEqual(base, from_int(0)) && exponent < 0; + if ($) { + return new Error(void 0); + } else { + return do_power(base, exponent, base); + } +} function divide2(lhs, rhs) { let n1 = lhs.numerator; let d1 = lhs.denominator; @@ -5663,31 +5725,193 @@ function interpret(loop$env, loop$expr) { let a = lhs2.f; let b = rhs2.f; return new Ok(new FloatingPointNumber(min(a, b))); - } else if (lhs2 instanceof Integer && op instanceof Power && rhs2 instanceof FloatingPointNumber) { - let a = lhs2.n; - let b = rhs2.f; - let $ = power3(a, b); + } else if (lhs2 instanceof FloatingPointNumber && op instanceof Power && rhs2 instanceof FloatingPointNumber) { + let base = lhs2.f; + let exponent = rhs2.f; + let fractional_exponent = ceiling2(exponent) - exponent > 0; + return guard( + base < 0 && fractional_exponent, + new Error( + new RuntimeError2( + new RuntimeError( + "Cannot raise negative number to fractional power as it produces an imaginary number." + ) + ) + ), + () => { + return guard( + base === 0 && exponent < 0, + new Error( + new RuntimeError2( + new RuntimeError( + "Raising 0.0 to a negative power is equivalent to doing division by zero." + ) + ) + ), + () => { + let $ = power2(base, exponent); + if (!$.isOk()) { + throw makeError( + "let_assert", + "squared_away/squared_away_lang/interpreter", + 160, + "", + "Pattern match failed, no pattern matched the value.", + { value: $ } + ); + } + let x = $[0]; + return new Ok(new FloatingPointNumber(x)); + } + ); + } + ); + } else if (lhs2 instanceof FloatingPointNumber && op instanceof Power && rhs2 instanceof Integer) { + let base = lhs2.f; + let exponent = rhs2.n; + return guard( + base === 0 && exponent < 0, + new Error( + new RuntimeError2( + new RuntimeError( + "\n Raising 0.0 to a negative power is equivalent to doing division by zero.\n " + ) + ) + ), + () => { + let $ = power2( + base, + (() => { + let _pipe = exponent; + return to_float(_pipe); + })() + ); + if (!$.isOk()) { + throw makeError( + "let_assert", + "squared_away/squared_away_lang/interpreter", + 182, + "", + "Pattern match failed, no pattern matched the value.", + { value: $ } + ); + } + let x = $[0]; + return new Ok(new FloatingPointNumber(x)); + } + ); + } else if (lhs2 instanceof Usd && op instanceof Multiply && rhs2 instanceof Integer) { + let c = lhs2.cents; + let i = rhs2.n; + return new Ok( + new Usd(multiply2(c, from_int(i))) + ); + } else if (lhs2 instanceof Usd && op instanceof Divide && rhs2 instanceof Integer) { + let d = lhs2.cents; + let p2 = rhs2.n; + return new Ok( + new Usd(divide2(d, from_int(p2))) + ); + } else if (lhs2 instanceof Percent && op instanceof Power && rhs2 instanceof Integer) { + let p2 = lhs2.percent; + let i = rhs2.n; + let $ = power5(p2, i); if (!$.isOk()) { - throw makeError( - "let_assert", - "squared_away/squared_away_lang/interpreter", - 136, - "", - "Pattern match failed, no pattern matched the value.", - { value: $ } + return new Error( + new RuntimeError2( + new RuntimeError( + "\n Cannot raise 0% to a negative power, it's equivalent to dividing by zero.\n " + ) + ) ); + } else { + let x = $[0]; + return new Ok(new Percent(x)); } - let p2 = $[0]; - return new Ok(new FloatingPointNumber(p2)); - } else if (lhs2 instanceof FloatingPointNumber && op instanceof Power && rhs2 instanceof FloatingPointNumber) { - let a = lhs2.f; + } else if (lhs2 instanceof Usd && op instanceof Add && rhs2 instanceof Usd) { + let c1 = lhs2.cents; + let c2 = rhs2.cents; + return new Ok(new Usd(add4(c1, c2))); + } else if (lhs2 instanceof Usd && op instanceof Subtract && rhs2 instanceof Usd) { + let c1 = lhs2.cents; + let c2 = rhs2.cents; + return new Ok(new Usd(subtract2(c1, c2))); + } else if (lhs2 instanceof Usd && op instanceof Divide && rhs2 instanceof Usd) { + let d1 = lhs2.cents; + let d2 = rhs2.cents; + return new Ok(new Percent(divide2(d1, d2))); + } else if (lhs2 instanceof Usd && op instanceof Minimum && rhs2 instanceof Usd) { + let d = lhs2.cents; + let p2 = rhs2.cents; + return new Ok(new Usd(min3(d, p2))); + } else if (lhs2 instanceof Percent && op instanceof Multiply && rhs2 instanceof Usd) { + let p2 = lhs2.percent; + let d = rhs2.cents; + return new Ok(new Usd(multiply2(p2, d))); + } else if (lhs2 instanceof Usd && op instanceof Multiply && rhs2 instanceof Percent) { + let c = lhs2.cents; + let p2 = rhs2.percent; + return new Ok(new Usd(multiply2(c, p2))); + } else if (lhs2 instanceof Usd && op instanceof Divide && rhs2 instanceof Percent) { + let d = lhs2.cents; + let p2 = rhs2.percent; + return new Ok(new Usd(divide2(d, p2))); + } else if (lhs2 instanceof Percent && op instanceof Multiply && rhs2 instanceof Percent) { + let p1 = lhs2.percent; + let p2 = rhs2.percent; + return new Ok(new Percent(multiply2(p1, p2))); + } else if (lhs2 instanceof Percent && op instanceof Divide && rhs2 instanceof Percent) { + let p1 = lhs2.percent; + let p2 = rhs2.percent; + return new Ok(new Percent(divide2(p1, p2))); + } else if (lhs2 instanceof Percent && op instanceof Minimum && rhs2 instanceof Percent) { + let p1 = lhs2.percent; + let p2 = rhs2.percent; + return new Ok(new Percent(min3(p1, p2))); + } else if (lhs2 instanceof Percent && op instanceof Power && rhs2 instanceof Percent) { + let base = lhs2.percent; + let exponent = rhs2.percent; + let fractional_exponent = !is_whole_number(exponent); + return guard( + is_negative(base) && fractional_exponent, + new Error( + new RuntimeError2( + new RuntimeError( + "Cannot raise negative number to fractional power as it produces an imaginary number." + ) + ) + ), + () => { + return guard( + is_zero(base) && is_negative(exponent), + new Error( + new RuntimeError2( + new RuntimeError( + "Raising zero to a negative power is equivalent to doing division by zero." + ) + ) + ), + () => { + return new Error( + new RuntimeError2( + new RuntimeError( + "Raising a rational number to another rational number power is not implemented yet." + ) + ) + ); + } + ); + } + ); + } else if (lhs2 instanceof Integer && op instanceof Power && rhs2 instanceof FloatingPointNumber) { + let a = lhs2.n; let b = rhs2.f; - let $ = power2(a, b); + let $ = power3(a, b); if (!$.isOk()) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 140, + 270, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -5720,52 +5944,12 @@ function interpret(loop$env, loop$expr) { } else { return new Ok(new TestPass()); } - } else if (lhs2 instanceof Usd && op instanceof Add && rhs2 instanceof Usd) { - let c1 = lhs2.cents; - let c2 = rhs2.cents; - return new Ok(new Usd(add4(c1, c2))); - } else if (lhs2 instanceof Usd && op instanceof Subtract && rhs2 instanceof Usd) { - let c1 = lhs2.cents; - let c2 = rhs2.cents; - return new Ok(new Usd(subtract2(c1, c2))); - } else if (lhs2 instanceof Usd && op instanceof Multiply && rhs2 instanceof Integer) { - let c = lhs2.cents; - let i = rhs2.n; - return new Ok( - new Usd(multiply2(c, from_int(i))) - ); } else if (lhs2 instanceof Integer && op instanceof Multiply && rhs2 instanceof Usd) { let i = lhs2.n; let c = rhs2.cents; return new Ok( new Usd(multiply2(c, from_int(i))) ); - } else if (lhs2 instanceof Usd && op instanceof Multiply && rhs2 instanceof Percent) { - let c = lhs2.cents; - let p2 = rhs2.percent; - return new Ok(new Usd(multiply2(c, p2))); - } else if (lhs2 instanceof Usd && op instanceof Divide && rhs2 instanceof Usd) { - let d1 = lhs2.cents; - let d2 = rhs2.cents; - return new Ok(new Percent(divide2(d1, d2))); - } else if (lhs2 instanceof Percent && op instanceof Multiply && rhs2 instanceof Usd) { - let p2 = lhs2.percent; - let c = rhs2.cents; - return new Ok(new Usd(multiply2(p2, c))); - } else if (lhs2 instanceof Usd && op instanceof Divide && rhs2 instanceof Percent) { - let d = lhs2.cents; - let p2 = rhs2.percent; - return new Ok(new Usd(divide2(d, p2))); - } else if (lhs2 instanceof Usd && op instanceof Divide && rhs2 instanceof Integer) { - let d = lhs2.cents; - let p2 = rhs2.n; - return new Ok( - new Usd(divide2(d, from_int(p2))) - ); - } else if (lhs2 instanceof Usd && op instanceof Minimum && rhs2 instanceof Usd) { - let d = lhs2.cents; - let p2 = rhs2.cents; - return new Ok(new Usd(min3(d, p2))); } else if (lhs2 instanceof Percent && op instanceof Multiply && rhs2 instanceof Percent) { let p1 = lhs2.percent; let p2 = rhs2.percent; @@ -5834,7 +6018,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 228, + 336, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5855,7 +6039,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 236, + 344, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5876,7 +6060,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 244, + 352, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5946,7 +6130,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 285, + 393, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5972,7 +6156,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 296, + 404, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5993,7 +6177,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 306, + 414, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -8542,7 +8726,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 265, + 263, "", "Pattern match failed, no pattern matched the value.", { value: maybe_expr } @@ -8557,7 +8741,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 273, + 271, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -8584,7 +8768,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 293, + 291, "", "Pattern match failed, no pattern matched the value.", { value: $1 } @@ -8643,7 +8827,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 333, + 331, "", "Pattern match failed, no pattern matched the value.", { value: maybe_expr } @@ -8658,7 +8842,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 338, + 336, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -8685,7 +8869,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 357, + 355, "", "Pattern match failed, no pattern matched the value.", { value: $1 } @@ -9302,7 +9486,7 @@ function main() { throw makeError( "let_assert", "squared_away", - 36, + 34, "main", "Pattern match failed, no pattern matched the value.", { value: $ } diff --git a/src/squared_away.gleam b/src/squared_away.gleam index e975e19..d9a9e5f 100644 --- a/src/squared_away.gleam +++ b/src/squared_away.gleam @@ -2,7 +2,6 @@ import gleam/bool import gleam/dict import gleam/dynamic import gleam/int -import gleam/io import gleam/javascript/promise import gleam/list import gleam/option.{type Option, None, Some} @@ -21,7 +20,6 @@ import squared_away/squared_away_lang as lang import squared_away/squared_away_lang/error import squared_away/squared_away_lang/grid import squared_away/squared_away_lang/interpreter/value -import squared_away/squared_away_lang/parser/expr import squared_away/squared_away_lang/typechecker/typ import squared_away/squared_away_lang/typechecker/typed_expr diff --git a/src/squared_away/renderable_error.gleam b/src/squared_away/renderable_error.gleam index 2f34974..93d5a00 100644 --- a/src/squared_away/renderable_error.gleam +++ b/src/squared_away/renderable_error.gleam @@ -1,9 +1,9 @@ import gleam/option.{type Option} -/// This type represents an error we want to render. The idea is that the +/// This type represents an error we want to render. The idea is that the /// various error types implement converstion functions to this, rather than /// individual to_string, to_html, etc. functions, which cuts down on work for -/// me as just one person. +/// me as just one person. /// It will hopefully also help to keep error's looking consistent in the UI. pub type RenderableError { RenderableError(title: String, info: String, hint: Option(String)) diff --git a/src/squared_away/squared_away_lang/interpreter.gleam b/src/squared_away/squared_away_lang/interpreter.gleam index 0b40efb..bf2aeb4 100644 --- a/src/squared_away/squared_away_lang/interpreter.gleam +++ b/src/squared_away/squared_away_lang/interpreter.gleam @@ -1,3 +1,4 @@ +import gleam/bool import gleam/float import gleam/int import gleam/list @@ -68,7 +69,7 @@ pub fn interpret( use lhs <- result.try(interpret(env, lhs)) use rhs <- result.try(interpret(env, rhs)) case lhs, op, rhs { - // Integer operations + // Int x Int value.Integer(a), expr.Add, value.Integer(b) -> Ok(value.Integer(a + b)) value.Integer(a), expr.Subtract, value.Integer(b) -> Ok(value.Integer(a - b)) @@ -91,7 +92,7 @@ pub fn interpret( value.Integer(a), expr.Minimum, value.Integer(b) -> Ok(value.Integer(int.min(a, b))) - // Float operations + // Float x Float value.FloatingPointNumber(a), expr.Add, value.FloatingPointNumber(b) -> Ok(value.FloatingPointNumber(a +. b)) value.FloatingPointNumber(a), @@ -131,63 +132,188 @@ pub fn interpret( value.FloatingPointNumber(a), expr.Minimum, value.FloatingPointNumber(b) -> Ok(value.FloatingPointNumber(float.min(a, b))) - // Exponents - value.Integer(a), expr.Power, value.FloatingPointNumber(b) -> { - let assert Ok(p) = int.power(a, b) - Ok(value.FloatingPointNumber(p)) + value.FloatingPointNumber(base), + expr.Power, + value.FloatingPointNumber(exponent) + -> { + let fractional_exponent = float.ceiling(exponent) -. exponent >. 0.0 + use <- bool.guard( + base <. 0.0 && fractional_exponent, + Error( + error.RuntimeError(runtime_error.RuntimeError( + "Cannot raise negative number to fractional power as it produces an imaginary number.", + )), + ), + ) + + use <- bool.guard( + base == 0.0 && exponent <. 0.0, + Error( + error.RuntimeError(runtime_error.RuntimeError( + "Raising 0.0 to a negative power is equivalent to doing division by zero.", + )), + ), + ) + + // The above checks are pulled directly from the float.power function. We do them ourselves so we can provide + // specific error messages. + let assert Ok(x) = float.power(base, exponent) + + Ok(value.FloatingPointNumber(x)) } - value.FloatingPointNumber(a), expr.Power, value.FloatingPointNumber(b) -> { - let assert Ok(p) = float.power(a, b) - Ok(value.FloatingPointNumber(p)) + + // Float x Int + value.FloatingPointNumber(base), expr.Power, value.Integer(exponent) -> { + // In our case we can raise a floating point number to an integer power by converting the int to a float + + use <- bool.guard( + base == 0.0 && exponent < 0, + Error( + error.RuntimeError(runtime_error.RuntimeError( + " + Raising 0.0 to a negative power is equivalent to doing division by zero. + ", + )), + ), + ) + + // The above checks are pulled directly from the float.power function. We do them ourselves so we can provide + // specific error messages. + let assert Ok(x) = float.power(base, exponent |> int.to_float) + + Ok(value.FloatingPointNumber(x)) } - // Boolean operations - value.Boolean(a), expr.And, value.Boolean(b) -> - Ok(value.Boolean(a && b)) - value.Boolean(a), expr.Or, value.Boolean(b) -> Ok(value.Boolean(a || b)) - value.Boolean(a), expr.EqualCheck, value.Boolean(b) -> - Ok(value.Boolean(a == b)) - value.Boolean(a), expr.NotEqualCheck, value.Boolean(b) -> - Ok(value.Boolean(a != b)) + // USD x Int + value.Usd(c), expr.Multiply, value.Integer(i) -> + Ok(value.Usd(rational.multiply(c, rational.from_int(i)))) + value.Usd(d), expr.Divide, value.Integer(p) -> { + Ok(value.Usd(rational.divide(d, rational.from_int(p)))) + } - // MustBe - vlhs, expr.MustBe, vrhs -> - case vlhs == vrhs { - False -> Ok(value.TestFail) - True -> Ok(value.TestPass) + // Percent x Int + value.Percent(p), expr.Power, value.Integer(i) -> { + case rational.power(p, i) { + Error(_) -> + Error( + error.RuntimeError(runtime_error.RuntimeError( + " + Cannot raise 0% to a negative power, it's equivalent to dividing by zero. + ", + )), + ) + Ok(x) -> Ok(value.Percent(x)) } + } - // Money Operations + // Usd x Usd value.Usd(c1), expr.Add, value.Usd(c2) -> Ok(value.Usd(rational.add(c1, c2))) value.Usd(c1), expr.Subtract, value.Usd(c2) -> Ok(value.Usd(rational.subtract(c1, c2))) - value.Usd(c), expr.Multiply, value.Integer(i) -> - Ok(value.Usd(rational.multiply(c, rational.from_int(i)))) - value.Integer(i), expr.Multiply, value.Usd(c) -> - Ok(value.Usd(rational.multiply(c, rational.from_int(i)))) - value.Usd(c), expr.Multiply, value.Percent(p) -> { - Ok(value.Usd(rational.multiply(c, p))) - } value.Usd(d1), expr.Divide, value.Usd(d2) -> Ok(value.Percent(rational.divide(d1, d2))) - value.Percent(p), expr.Multiply, value.Usd(c) -> { - Ok(value.Usd(rational.multiply(p, c))) + value.Usd(d), expr.Minimum, value.Usd(p) -> { + Ok(value.Usd(rational.min(d, p))) } - value.Usd(d), expr.Divide, value.Percent(p) -> { - Ok(value.Usd(rational.divide(d, p))) + + // Percent x Usd + value.Percent(p), expr.Multiply, value.Usd(d) -> { + Ok(value.Usd(rational.multiply(p, d))) } - value.Usd(d), expr.Divide, value.Integer(p) -> { - Ok(value.Usd(rational.divide(d, rational.from_int(p)))) + + // Usd x Percent + value.Usd(c), expr.Multiply, value.Percent(p) -> { + Ok(value.Usd(rational.multiply(c, p))) } - value.Usd(d), expr.Minimum, value.Usd(p) -> { - Ok(value.Usd(rational.min(d, p))) + value.Usd(d), expr.Divide, value.Percent(p) -> { + Ok(value.Usd(rational.divide(d, p))) } - // Percent ops + // Percent x Percent value.Percent(p1), expr.Multiply, value.Percent(p2) -> { Ok(value.Percent(rational.multiply(p1, p2))) } + value.Percent(p1), expr.Divide, value.Percent(p2) -> { + Ok(value.Percent(rational.divide(p1, p2))) + } + value.Percent(p1), expr.Minimum, value.Percent(p2) -> { + Ok(value.Percent(rational.min(p1, p2))) + } + value.Percent(base), expr.Power, value.Percent(exponent) -> { + let fractional_exponent = !rational.is_whole_number(exponent) + use <- bool.guard( + rational.is_negative(base) && fractional_exponent, + Error( + error.RuntimeError(runtime_error.RuntimeError( + "Cannot raise negative number to fractional power as it produces an imaginary number.", + )), + ), + ) + + use <- bool.guard( + rational.is_zero(base) && rational.is_negative(exponent), + Error( + error.RuntimeError(runtime_error.RuntimeError( + "Raising zero to a negative power is equivalent to doing division by zero.", + )), + ), + ) + + Error( + error.RuntimeError(runtime_error.RuntimeError( + "Raising a rational number to another rational number power is not implemented yet.", + )), + ) + } + + // Bool x Bool + value.Boolean(a), expr.And, value.Boolean(b) -> + Ok(value.Boolean(a && b)) + value.Boolean(a), expr.Or, value.Boolean(b) -> Ok(value.Boolean(a || b)) + value.Boolean(a), expr.EqualCheck, value.Boolean(b) -> + Ok(value.Boolean(a == b)) + value.Boolean(a), expr.NotEqualCheck, value.Boolean(b) -> + Ok(value.Boolean(a != b)) + + // Int x Float + value.Integer(base), expr.Power, value.FloatingPointNumber(exponent) -> { + let fractional_exponent = float.ceiling(exponent) -. exponent >. 0.0 + use <- bool.guard( + base < 0 && fractional_exponent, + Error( + error.RuntimeError(runtime_error.RuntimeError( + "Cannot raise negative number to fractional power as it produces an imaginary number.", + )), + ), + ) + + use <- bool.guard( + base == 0 && exponent <. 0.0, + Error( + error.RuntimeError(runtime_error.RuntimeError( + "Raising 0.0 to a negative power is equivalent to doing division by zero.", + )), + ), + ) + + // The above checks are pulled directly from the float.power function. We do them ourselves so we can provide + // specific error messages. + let assert Ok(x) = int.power(base, exponent) + + Ok(value.FloatingPointNumber(x)) + } + + // Int x USD + value.Integer(i), expr.Multiply, value.Usd(c) -> + Ok(value.Usd(rational.multiply(c, rational.from_int(i)))) + + // MustBe + vlhs, expr.MustBe, vrhs -> + case vlhs == vrhs { + False -> Ok(value.TestFail) + True -> Ok(value.TestPass) + } lhs, op, rhs -> { let msg = diff --git a/src/squared_away/squared_away_lang/util/rational.gleam b/src/squared_away/squared_away_lang/util/rational.gleam index e3ad93d..0503d21 100644 --- a/src/squared_away/squared_away_lang/util/rational.gleam +++ b/src/squared_away/squared_away_lang/util/rational.gleam @@ -2,6 +2,7 @@ //// This will be used by the spreadsheet's Money, Percent, and Integer types. import bigi +import gleam/int import gleam/list import gleam/order import gleam/result @@ -69,6 +70,59 @@ pub fn multiply(lhs: Rat, rhs: Rat) -> Rat { simplify(Rat(bigi.multiply(n1, n2), bigi.multiply(d1, d2))) } +fn do_power_bigi(base, exponent, acc) { + case exponent == bigi.from_int(1) { + True -> acc + False -> + do_power_bigi( + base, + exponent |> bigi.subtract(bigi.from_int(1)), + multiply(base, acc), + ) + } +} + +pub fn power(base: Rat, exponent: Int) -> Result(Rat, Nil) { + case base == from_int(0) && exponent < 0 { + // Raising 0 to a negative power means dividing by zero + True -> Error(Nil) + False -> do_power(base, exponent, base) + } +} + +fn do_power(base, exponent, acc) { + case exponent { + 1 -> Ok(acc) + _ -> do_power(base, exponent - 1, multiply(base, acc)) + } +} + +pub fn is_negative(r: Rat) -> Bool { + let Rat(n, d) = r + bigi.multiply(n, d) |> bigi.compare(bigi.from_int(0)) == order.Lt +} + +pub fn is_zero(r: Rat) -> Bool { + let Rat(n, _) = r + bigi.from_int(0) == n +} + +pub fn compare(lhs: Rat, rhs: Rat) -> order.Order { + case lhs == rhs { + True -> order.Eq + False -> + case subtract(lhs, rhs) |> is_negative { + False -> order.Lt + True -> order.Gt + } + } +} + +pub fn is_whole_number(r: Rat) -> Bool { + let Rat(n, d) = r + bigi.modulo(n, d) == bigi.from_int(0) +} + pub fn divide(lhs: Rat, rhs: Rat) -> Rat { let Rat(n1, d1) = lhs let Rat(n2, d2) = rhs