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