diff --git a/priv/static/squared_away.mjs b/priv/static/squared_away.mjs index 0466c14..c974e32 100644 --- a/priv/static/squared_away.mjs +++ b/priv/static/squared_away.mjs @@ -1393,6 +1393,14 @@ function parse(string3) { function to_string2(x) { return float_to_string(x); } +function min(a, b) { + let $ = a < b; + if ($) { + return a; + } else { + return b; + } +} function ceiling2(x) { return ceiling(x); } @@ -1455,6 +1463,14 @@ function compare(a, b) { } } } +function min2(a, b) { + let $ = a < b; + if ($) { + return a; + } else { + return b; + } +} function max(a, b) { let $ = a > b; if ($) { @@ -4423,6 +4439,15 @@ function to_string8(bigint) { function zero() { return 0n; } +function compare4(a, b) { + if (a < b) { + return new Lt(); + } else if (a > b) { + return new Gt(); + } else { + return new Eq(); + } +} function add3(a, b) { return a + b; } @@ -4725,6 +4750,19 @@ function avg(rats) { })() ); } +function min3(r1, r2) { + let $ = subtract2(r1, r2); + let x = $.numerator; + let y = $.denominator; + let num_neg = isEqual(compare4(x, from2(0)), new Lt()); + let den_neg = isEqual(compare4(y, from2(0)), new Lt()); + let $1 = num_neg === den_neg; + if (!$1) { + return r1; + } else { + return r2; + } +} // build/dev/javascript/squared_away/squared_away/squared_away_lang/parser/expr.mjs var Empty2 = class extends CustomType { @@ -4839,6 +4877,8 @@ var Or = class extends CustomType { }; var MustBe = class extends CustomType { }; +var Minimum = class extends CustomType { +}; var Negate = class extends CustomType { }; var Not = class extends CustomType { @@ -4870,8 +4910,10 @@ function binary_to_string(b) { return "**"; } else if (b instanceof Subtract) { return "-"; - } else { + } else if (b instanceof MustBe) { return "mustbe"; + } else { + return "min"; } } function unary_to_string(u) { @@ -5191,8 +5233,10 @@ function describe_binary_op_kind_for_err(bo) { return "To The Power Of `**`"; } else if (bo instanceof Subtract) { return "Subtraction `-`"; - } else { + } else if (bo instanceof MustBe) { return "MustBe `mustbe`"; + } else { + return "Minimum `min`"; } } function to_renderable_error(te) { @@ -5504,6 +5548,10 @@ function interpret(loop$env, loop$expr) { let a = lhs2.n; let b = rhs2.n; return new Ok(new Boolean(a <= b)); + } else if (lhs2 instanceof Integer && op instanceof Minimum && rhs2 instanceof Integer) { + let a = lhs2.n; + let b = rhs2.n; + return new Ok(new Integer(min2(a, b))); } else if (lhs2 instanceof FloatingPointNumber && op instanceof Add && rhs2 instanceof FloatingPointNumber) { let a = lhs2.f; let b = rhs2.f; @@ -5544,6 +5592,10 @@ function interpret(loop$env, loop$expr) { let a = lhs2.f; let b = rhs2.f; return new Ok(new Boolean(a <= b)); + } else if (lhs2 instanceof FloatingPointNumber && op instanceof Minimum && rhs2 instanceof FloatingPointNumber) { + 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; @@ -5552,7 +5604,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 132, + 138, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -5568,7 +5620,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 136, + 142, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -5637,6 +5689,10 @@ function interpret(loop$env, loop$expr) { let d = lhs2.cents; let p2 = rhs2.percent; return new Ok(new Usd(divide2(d, 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; @@ -5689,6 +5745,8 @@ function interpret(loop$env, loop$expr) { return false; } else if (v instanceof TestPass) { return false; + } else if (v instanceof Empty4) { + return false; } else { return true; } @@ -5703,7 +5761,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 218, + 227, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5724,7 +5782,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 226, + 235, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5745,7 +5803,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 234, + 243, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5798,6 +5856,8 @@ function interpret(loop$env, loop$expr) { return false; } else if (v instanceof TestPass) { return false; + } else if (v instanceof Empty4) { + return false; } else { return true; } @@ -5813,7 +5873,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 275, + 284, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5839,7 +5899,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 286, + 295, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5860,7 +5920,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 296, + 305, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5977,6 +6037,8 @@ var BuiltinAvg2 = class extends CustomType { }; var MustBe2 = class extends CustomType { }; +var Minimum2 = class extends CustomType { +}; // build/dev/javascript/squared_away/squared_away/squared_away_lang/parser.mjs function try_parse_binary_ops(tokens) { @@ -6382,6 +6444,33 @@ function try_parse_binary_ops(tokens) { ); } ); + } else if (tokens.atLeastLength(1) && tokens.head instanceof Minimum2) { + let rest = tokens.tail; + return try$( + do_parse2(rest), + (_use0) => { + let rhs = _use0[0]; + let rest$1 = _use0[1]; + return guard( + isEqual(rhs, new Empty2()), + new Error( + new ParseError( + "No item on right hand side of binary operation." + ) + ), + () => { + return new Ok( + [ + (_capture) => { + return new BinaryOp(_capture, new Minimum(), rhs); + }, + rest$1 + ] + ); + } + ); + } + ); } else { return new Error(new ParseError("Not a binary operation")); } @@ -6873,6 +6962,10 @@ function do_scan(loop$src, loop$acc) { let rest = src.slice(3); loop$src = trim_left2(rest); loop$acc = prepend(new BuiltinAvg2(new None()), acc); + } else if (src.startsWith("min")) { + let rest = src.slice(3); + loop$src = trim_left2(rest); + loop$acc = prepend(new Minimum2(), acc); } else if (src.startsWith("&&")) { let rest = src.slice(2); loop$src = trim_left2(rest); @@ -7195,7 +7288,10 @@ function typecheck(env, expr) { return filter( _pipe$1, (t2) => { - return !isEqual(t2, new TTestResult()); + return !isEqual(t2, new TTestResult()) && !isEqual( + t2, + new TNil() + ); } ); })(); @@ -7339,7 +7435,10 @@ function typecheck(env, expr) { return filter( _pipe$1, (t2) => { - return !isEqual(t2, new TTestResult()); + return !isEqual(t2, new TTestResult()) && !isEqual( + t2, + new TNil() + ); } ); })(); @@ -7616,6 +7715,10 @@ function typecheck(env, expr) { return new Ok( new BinaryOp2(new TFloat(), lhs2, op, rhs2) ); + } else if ($ instanceof TFloat && op instanceof Minimum && $1 instanceof TFloat) { + return new Ok( + new BinaryOp2(new TFloat(), lhs2, op, rhs2) + ); } else if ($ instanceof TFloat && op instanceof Power && $1 instanceof TInt) { return new Ok( new BinaryOp2(new TFloat(), lhs2, op, rhs2) @@ -7644,6 +7747,10 @@ function typecheck(env, expr) { return new Ok( new BinaryOp2(new TPercent(), lhs2, op, rhs2) ); + } else if ($ instanceof TUsd && op instanceof Minimum && $1 instanceof TUsd) { + return new Ok( + new BinaryOp2(new TUsd(), lhs2, op, rhs2) + ); } else if ($ instanceof TUsd && op instanceof Multiply && $1 instanceof TUsd) { return new Error( new TypeError2( @@ -7674,6 +7781,10 @@ function typecheck(env, expr) { return new Ok( new BinaryOp2(new TPercent(), lhs2, op, rhs2) ); + } else if ($ instanceof TPercent && op instanceof Minimum && $1 instanceof TPercent) { + return new Ok( + new BinaryOp2(new TPercent(), lhs2, op, rhs2) + ); } else if ($ instanceof TBool && op instanceof And && $1 instanceof TBool) { return new Ok( new BinaryOp2(new TBool(), lhs2, op, rhs2) @@ -7702,6 +7813,10 @@ function typecheck(env, expr) { return new Ok( new BinaryOp2(new TInt(), lhs2, op, rhs2) ); + } else if ($ instanceof TInt && op instanceof Minimum && $1 instanceof TInt) { + return new Ok( + new BinaryOp2(new TInt(), lhs2, op, rhs2) + ); } else if ($ instanceof TInt && op instanceof Multiply && $1 instanceof TUsd) { return new Ok( new BinaryOp2(new TUsd(), lhs2, op, rhs2) diff --git a/src/squared_away/squared_away_lang/interpreter.gleam b/src/squared_away/squared_away_lang/interpreter.gleam index efad04b..2dc78e5 100644 --- a/src/squared_away/squared_away_lang/interpreter.gleam +++ b/src/squared_away/squared_away_lang/interpreter.gleam @@ -88,6 +88,8 @@ pub fn interpret( Ok(value.Boolean(a < b)) value.Integer(a), expr.LessThanOrEqualCheck, value.Integer(b) -> Ok(value.Boolean(a <= b)) + value.Integer(a), expr.Minimum, value.Integer(b) -> + Ok(value.Integer(int.min(a, b))) // Float operations value.FloatingPointNumber(a), expr.Add, value.FloatingPointNumber(b) -> @@ -126,6 +128,8 @@ pub fn interpret( expr.LessThanOrEqualCheck, value.FloatingPointNumber(b) -> Ok(value.Boolean(a <=. b)) + value.FloatingPointNumber(a), expr.Minimum, value.FloatingPointNumber(b) + -> Ok(value.FloatingPointNumber(float.min(a, b))) // Exponents value.Integer(a), expr.Power, value.FloatingPointNumber(b) -> { @@ -173,6 +177,9 @@ pub fn interpret( value.Usd(d), expr.Divide, value.Percent(p) -> { Ok(value.Usd(rational.divide(d, p))) } + value.Usd(d), expr.Minimum, value.Usd(p) -> { + Ok(value.Usd(rational.min(d, p))) + } // Percent ops value.Percent(p1), expr.Multiply, value.Percent(p2) -> { @@ -207,7 +214,7 @@ pub fn interpret( }) |> list.filter(fn(v) { case v { - value.TestFail | value.TestPass -> False + value.TestFail | value.TestPass | value.Empty -> False _ -> True } }) @@ -263,7 +270,7 @@ pub fn interpret( }) |> list.filter(fn(v) { case v { - value.TestFail | value.TestPass -> False + value.TestFail | value.TestPass | value.Empty -> False _ -> True } }) diff --git a/src/squared_away/squared_away_lang/parser.gleam b/src/squared_away/squared_away_lang/parser.gleam index a393d10..06b7ed5 100644 --- a/src/squared_away/squared_away_lang/parser.gleam +++ b/src/squared_away/squared_away_lang/parser.gleam @@ -263,6 +263,16 @@ fn try_parse_binary_ops( ) Ok(#(expr.BinaryOp(_, expr.MustBe, rhs), rest)) } + [token.Minimum, ..rest] -> { + use #(rhs, rest) <- result.try(do_parse(rest)) + use <- bool.guard( + rhs == expr.Empty, + Error(parse_error.ParseError( + "No item on right hand side of binary operation.", + )), + ) + Ok(#(expr.BinaryOp(_, expr.Minimum, rhs), rest)) + } _ -> Error(parse_error.ParseError("Not a binary operation")) } } diff --git a/src/squared_away/squared_away_lang/parser/expr.gleam b/src/squared_away/squared_away_lang/parser/expr.gleam index 887e6c7..d728eb7 100644 --- a/src/squared_away/squared_away_lang/parser/expr.gleam +++ b/src/squared_away/squared_away_lang/parser/expr.gleam @@ -34,6 +34,7 @@ pub type BinaryOpKind { And Or MustBe + Minimum } pub fn binary_to_string(b: BinaryOpKind) -> String { @@ -52,6 +53,7 @@ pub fn binary_to_string(b: BinaryOpKind) -> String { Power -> "**" Subtract -> "-" MustBe -> "mustbe" + Minimum -> "min" } } diff --git a/src/squared_away/squared_away_lang/scanner.gleam b/src/squared_away/squared_away_lang/scanner.gleam index e7bc660..ccae987 100644 --- a/src/squared_away/squared_away_lang/scanner.gleam +++ b/src/squared_away/squared_away_lang/scanner.gleam @@ -98,6 +98,7 @@ fn do_scan( do_scan(string.trim_left(rest), [token.BuiltinSum(option.None), ..acc]) "avg" <> rest -> do_scan(string.trim_left(rest), [token.BuiltinAvg(option.None), ..acc]) + "min" <> rest -> do_scan(string.trim_left(rest), [token.Minimum, ..acc]) // Operators "&&" <> rest -> do_scan(string.trim_left(rest), [token.And, ..acc]) diff --git a/src/squared_away/squared_away_lang/scanner/token.gleam b/src/squared_away/squared_away_lang/scanner/token.gleam index ea9afe7..1215b69 100644 --- a/src/squared_away/squared_away_lang/scanner/token.gleam +++ b/src/squared_away/squared_away_lang/scanner/token.gleam @@ -55,4 +55,5 @@ pub type Token { BuiltinSum(key: option.Option(grid.GridKey)) BuiltinAvg(key: option.Option(grid.GridKey)) MustBe + Minimum } diff --git a/src/squared_away/squared_away_lang/typechecker.gleam b/src/squared_away/squared_away_lang/typechecker.gleam index 4bb6a13..6c70d62 100644 --- a/src/squared_away/squared_away_lang/typechecker.gleam +++ b/src/squared_away/squared_away_lang/typechecker.gleam @@ -76,7 +76,7 @@ pub fn typecheck( texpr.type_ }) |> list.unique - |> list.filter(fn(t) { t != typ.TTestResult }) + |> list.filter(fn(t) { t != typ.TTestResult && t != typ.TNil }) case types { [typ.TFloat] -> Ok(typed_expr.BuiltinSum(typ.TFloat, keys)) @@ -151,7 +151,7 @@ pub fn typecheck( texpr.type_ }) |> list.unique - |> list.filter(fn(t) { t != typ.TTestResult }) + |> list.filter(fn(t) { t != typ.TTestResult && t != typ.TNil }) case types { [typ.TFloat] -> Ok(typed_expr.BuiltinAvg(typ.TFloat, keys)) @@ -339,6 +339,8 @@ pub fn typecheck( Ok(typed_expr.BinaryOp(type_: typ.TFloat, lhs:, op:, rhs:)) typ.TFloat, expr.Power, typ.TFloat -> Ok(typed_expr.BinaryOp(type_: typ.TFloat, lhs:, op:, rhs:)) + typ.TFloat, expr.Minimum, typ.TFloat -> + Ok(typed_expr.BinaryOp(type_: typ.TFloat, lhs:, op:, rhs:)) // USD x Float (None for now) // Percent x Float (None for now) @@ -368,6 +370,8 @@ pub fn typecheck( Ok(typed_expr.BinaryOp(type_: typ.TUsd, lhs:, op:, rhs:)) typ.TUsd, expr.Divide, typ.TUsd -> Ok(typed_expr.BinaryOp(type_: typ.TPercent, lhs:, op:, rhs:)) + typ.TUsd, expr.Minimum, typ.TUsd -> + Ok(typed_expr.BinaryOp(type_: typ.TUsd, lhs:, op:, rhs:)) typ.TUsd, expr.Multiply, typ.TUsd -> Error(error.TypeError(type_error.CannotMultiplyUsdByUsd(lhs:, rhs:))) @@ -391,6 +395,8 @@ pub fn typecheck( Ok(typed_expr.BinaryOp(type_: typ.TPercent, lhs:, op:, rhs:)) typ.TPercent, expr.Power, typ.TPercent -> Ok(typed_expr.BinaryOp(type_: typ.TPercent, lhs:, op:, rhs:)) + typ.TPercent, expr.Minimum, typ.TPercent -> + Ok(typed_expr.BinaryOp(type_: typ.TPercent, lhs:, op:, rhs:)) // String x Percent (None for now) // Boolean x Percent (None for now) @@ -420,6 +426,8 @@ pub fn typecheck( Ok(typed_expr.BinaryOp(type_: typ.TInt, lhs:, op:, rhs:)) typ.TInt, expr.Divide, typ.TInt -> Ok(typed_expr.BinaryOp(type_: typ.TInt, lhs:, op:, rhs:)) + typ.TInt, expr.Minimum, typ.TInt -> + Ok(typed_expr.BinaryOp(type_: typ.TInt, lhs:, op:, rhs:)) // Int x Usd typ.TInt, expr.Multiply, typ.TUsd -> diff --git a/src/squared_away/squared_away_lang/typechecker/type_error.gleam b/src/squared_away/squared_away_lang/typechecker/type_error.gleam index 6c53015..d7cb4b8 100644 --- a/src/squared_away/squared_away_lang/typechecker/type_error.gleam +++ b/src/squared_away/squared_away_lang/typechecker/type_error.gleam @@ -62,5 +62,6 @@ pub fn describe_binary_op_kind_for_err(bo: expr.BinaryOpKind) -> String { expr.Power -> "To The Power Of `**`" expr.Subtract -> "Subtraction `-`" expr.MustBe -> "MustBe `mustbe`" + expr.Minimum -> "Minimum `min`" } } diff --git a/src/squared_away/squared_away_lang/util/rational.gleam b/src/squared_away/squared_away_lang/util/rational.gleam index c436e54..e3ad93d 100644 --- a/src/squared_away/squared_away_lang/util/rational.gleam +++ b/src/squared_away/squared_away_lang/util/rational.gleam @@ -3,6 +3,7 @@ import bigi import gleam/list +import gleam/order import gleam/result import gleam/string @@ -82,6 +83,16 @@ pub fn avg(rats: List(Rat)) -> Rat { sum(rats) |> divide(list.length(rats) |> from_int) } +pub fn min(r1: Rat, r2: Rat) -> Rat { + let Rat(x, y) = subtract(r1, r2) + let num_neg = bigi.compare(x, bigi.from_int(0)) == order.Lt + let den_neg = bigi.compare(y, bigi.from_int(0)) == order.Lt + case num_neg == den_neg { + False -> r1 + True -> r2 + } +} + pub fn to_string(rat: Rat, precision: Int, with_commas: Bool) -> String { let Rat(n, d) = rat let whole = bigi.to_string(bigi.divide(n, d))