From 0ca3fc57dd4f0d30863f5813650f6bd6a09c0aab Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 14 Nov 2024 10:02:56 -0500 Subject: [PATCH] rudimentary test coverage highlighting --- priv/static/squared_away.mjs | 236 ++++++++++++------ src/squared_away.gleam | 51 +++- src/squared_away/squared_away_lang.gleam | 17 ++ .../squared_away_lang/interpreter.gleam | 51 +--- .../squared_away_lang/typechecker.gleam | 9 +- .../typechecker/typed_expr.gleam | 29 ++- 6 files changed, 273 insertions(+), 120 deletions(-) diff --git a/priv/static/squared_away.mjs b/priv/static/squared_away.mjs index 2f2868e..65fd794 100644 --- a/priv/static/squared_away.mjs +++ b/priv/static/squared_away.mjs @@ -1767,6 +1767,23 @@ function do_take(loop$list, loop$n, loop$acc) { function take(list, n) { return do_take(list, n, toList([])); } +function do_append(loop$first, loop$second) { + while (true) { + let first2 = loop$first; + let second2 = loop$second; + if (first2.hasLength(0)) { + return second2; + } else { + let item = first2.head; + let rest$1 = first2.tail; + loop$first = rest$1; + loop$second = prepend(item, second2); + } + } +} +function append(first2, second2) { + return do_append(reverse(first2), second2); +} function reverse_and_prepend(loop$prefix, loop$suffix) { while (true) { let prefix = loop$prefix; @@ -4756,9 +4773,10 @@ var PercentLiteral2 = class extends CustomType { } }; var Label2 = class extends CustomType { - constructor(type_2, txt) { + constructor(type_2, key, txt) { super(); this.type_ = type_2; + this.key = key; this.txt = txt; } }; @@ -4858,6 +4876,48 @@ function visit_cross_labels(te, f) { return new Ok(te); } } +function dependency_list(te, acc) { + if (te instanceof BinaryOp2) { + let lhs = te.lhs; + let rhs = te.rhs; + return flatten2( + toList([ + dependency_list(lhs, toList([])), + dependency_list(rhs, toList([])), + acc + ]) + ); + } else if (te instanceof BooleanLiteral2) { + return acc; + } else if (te instanceof BuiltinSum2) { + let keys2 = te.keys; + return flatten2(toList([keys2, acc])); + } else if (te instanceof CrossLabel2) { + let key = te.key; + return prepend(key, acc); + } else if (te instanceof Empty3) { + return acc; + } else if (te instanceof FloatLiteral2) { + return acc; + } else if (te instanceof Group2) { + let inner = te.expr; + return flatten2(toList([dependency_list(inner, toList([])), acc])); + } else if (te instanceof IntegerLiteral2) { + return acc; + } else if (te instanceof Label2) { + let key = te.key; + return prepend(key, acc); + } else if (te instanceof LabelDef2) { + return acc; + } else if (te instanceof PercentLiteral2) { + return acc; + } else if (te instanceof UnaryOp2) { + let inner = te.expr; + return flatten2(toList([dependency_list(inner, toList([])), acc])); + } else { + return acc; + } +} function do_to_string2(te) { if (te instanceof BooleanLiteral2) { let b = te.b; @@ -5190,57 +5250,24 @@ function interpret(loop$env, loop$expr) { return new Ok(new Empty4()); } } else if (expr instanceof Label2) { + let key = expr.key; let txt = expr.txt; - let key = (() => { - let _pipe = env; - let _pipe$1 = to_list3(_pipe); - let _pipe$2 = fold_until( - _pipe$1, - new None(), - (_, i) => { - if (i[1].isOk() && i[1][0] instanceof LabelDef2 && i[1][0].txt === txt) { - let cell_ref = i[0]; - let label_txt = i[1][0].txt; - return new Stop(new Some(cell_ref)); - } else { - return new Continue(new None()); - } - } - ); - let _pipe$3 = map( - _pipe$2, - (_capture) => { - return cell_to_the_right(env, _capture); - } - ); - let _pipe$4 = map(_pipe$3, from_result); - return flatten(_pipe$4); - })(); - if (key instanceof None) { - return new Error( - new RuntimeError2( - new RuntimeError("Label doesn't point to anything") - ) - ); + let $ = get4(env, key); + if (!$.isOk()) { + let e = $[0]; + return new Error(e); } else { - let key$1 = key[0]; - let $ = get4(env, key$1); - if (!$.isOk()) { - let e = $[0]; - return new Error(e); + let te = $[0]; + if (te instanceof Label2 && te.txt === txt) { + let ltxt = te.txt; + return new Error( + new RuntimeError2( + new RuntimeError("Label points to itself") + ) + ); } else { - let te = $[0]; - if (te instanceof Label2 && te.txt === txt) { - let ltxt = te.txt; - return new Error( - new RuntimeError2( - new RuntimeError("Label points to itself") - ) - ); - } else { - loop$env = env; - loop$expr = te; - } + loop$env = env; + loop$expr = te; } } } else if (expr instanceof BooleanLiteral2) { @@ -5381,7 +5408,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 161, + 135, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -5397,7 +5424,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 165, + 139, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -5532,7 +5559,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 247, + 221, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5553,7 +5580,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 255, + 229, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -5574,7 +5601,7 @@ function interpret(loop$env, loop$expr) { throw makeError( "let_assert", "squared_away/squared_away_lang/interpreter", - 263, + 237, "", "Pattern match failed, no pattern matched the value.", { value: v } @@ -6970,7 +6997,11 @@ function typecheck(env, expr) { return flatten(_pipe$4); })(); if (key instanceof None) { - return new Ok(new Label2(new TNil(), txt)); + return new Error( + new TypeError2( + new TypeError("Label doesn't point to anything") + ) + ); } else { let key$1 = key[0]; let x = get4(env, key$1); @@ -6989,7 +7020,7 @@ function typecheck(env, expr) { let $ = typecheck(env, expr$1); if ($.isOk()) { let te = $[0]; - return new Ok(new Label2(te.type_, txt)); + return new Ok(new Label2(te.type_, key$1, txt)); } else { let e = $[0]; return new Error(e); @@ -7395,6 +7426,26 @@ function typecheck_grid(input2) { } ); } +function dependency_list2(input2, key, acc) { + let $ = get4(input2, key); + if (!$.isOk()) { + return acc; + } else { + let te = $[0]; + let deps = dependency_list(te, toList([])); + let double_deps = (() => { + let _pipe = map2( + deps, + (_capture) => { + return dependency_list2(input2, _capture, toList([])); + } + ); + let _pipe$1 = flatten2(_pipe); + return append(_pipe$1, deps); + })(); + return double_deps; + } +} function parse_grid(input2) { return map_values2( input2, @@ -7472,7 +7523,7 @@ function uploadFile() { // build/dev/javascript/squared_away/squared_away.mjs var Model2 = class extends CustomType { - constructor(holding_shift, grid_width, grid_height, display_formulas, display_coords, active_cell, src_grid, value_grid, errors_to_display) { + constructor(holding_shift, grid_width, grid_height, display_formulas, display_coords, active_cell, src_grid, type_checked_grid, value_grid, errors_to_display) { super(); this.holding_shift = holding_shift; this.grid_width = grid_width; @@ -7481,6 +7532,7 @@ var Model2 = class extends CustomType { this.display_coords = display_coords; this.active_cell = active_cell; this.src_grid = src_grid; + this.type_checked_grid = type_checked_grid; this.value_grid = value_grid; this.errors_to_display = errors_to_display; } @@ -7578,8 +7630,8 @@ function focus2(id2) { function update_grid(model) { let scanned = scan_grid(model.src_grid); let parsed = parse_grid(scanned); - let typechecked = typecheck_grid(parsed); - let value_grid = interpret_grid(typechecked); + let type_checked_grid = typecheck_grid(parsed); + let value_grid = interpret_grid(type_checked_grid); let errors_to_display = fold4( value_grid, toList([]), @@ -7594,6 +7646,7 @@ function update_grid(model) { ); return model.withFields({ value_grid, + type_checked_grid, errors_to_display }); } @@ -7793,12 +7846,12 @@ function view(model) { return class$("errorcell"); } })(); - let $2 = (() => { - let $13 = get4(model.value_grid, key); - if (!$13.isOk()) { + let colors = (() => { + let $3 = get4(model.value_grid, key); + if (!$3.isOk()) { return ["#b30000", "#ffe6e6"]; } else { - let v = $13[0]; + let v = $3[0]; if (v instanceof Text2) { return ["#4a4a4a", "#f2f2f2"]; } else if (v instanceof TestPass) { @@ -7810,6 +7863,43 @@ function view(model) { } } })(); + let $2 = (() => { + let $13 = model.active_cell; + if ($13 instanceof None) { + return colors; + } else { + let active_cell = $13[0]; + let $22 = get4(model.type_checked_grid, active_cell); + if (!$22.isOk()) { + return colors; + } else { + let typed_expr = $22[0]; + let $3 = typed_expr.type_; + if ($3 instanceof TTestResult) { + let $4 = get4(model.value_grid, active_cell); + if ($4.isOk() && $4[0] instanceof TestPass) { + let $5 = (() => { + let _pipe$32 = dependency_list2( + model.type_checked_grid, + active_cell, + toList([]) + ); + return contains(_pipe$32, key); + })(); + if (!$5) { + return colors; + } else { + return ["#006400", "#e6ffe6"]; + } + } else { + return colors; + } + } else { + return colors; + } + } + } + })(); let color = $2[0]; let background_color = $2[1]; let input2 = input( @@ -7985,6 +8075,11 @@ var initial_grid_width = 7; var initial_grid_height = 20; function init2(_) { let src_grid = new$4(initial_grid_width, initial_grid_height, ""); + let type_checked_grid = new$4( + initial_grid_width, + initial_grid_height, + new Ok(new Empty3(new TNil())) + ); let value_grid = new$4( initial_grid_width, initial_grid_height, @@ -7999,6 +8094,7 @@ function init2(_) { false, new None(), src_grid, + type_checked_grid, value_grid, toList([]) ); @@ -8082,7 +8178,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 194, + 201, "", "Pattern match failed, no pattern matched the value.", { value: maybe_expr } @@ -8097,7 +8193,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 202, + 209, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -8124,7 +8220,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 222, + 229, "", "Pattern match failed, no pattern matched the value.", { value: $1 } @@ -8183,7 +8279,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 262, + 269, "", "Pattern match failed, no pattern matched the value.", { value: maybe_expr } @@ -8198,7 +8294,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 267, + 274, "", "Pattern match failed, no pattern matched the value.", { value: $ } @@ -8225,7 +8321,7 @@ function update(model, msg) { throw makeError( "let_assert", "squared_away", - 286, + 293, "", "Pattern match failed, no pattern matched the value.", { value: $1 } @@ -8304,7 +8400,7 @@ function main() { throw makeError( "let_assert", "squared_away", - 29, + 32, "main", "Pattern match failed, no pattern matched the value.", { value: $ } diff --git a/src/squared_away.gleam b/src/squared_away.gleam index 315fba6..be0bae5 100644 --- a/src/squared_away.gleam +++ b/src/squared_away.gleam @@ -7,6 +7,7 @@ import gleam/list import gleam/option.{type Option, None, Some} import gleam/pair import gleam/result +import gleam/string import lustre import lustre/attribute.{class} import lustre/effect @@ -14,10 +15,12 @@ import lustre/element import lustre/element/html import lustre/event import squared_away/renderable_error +import squared_away/squared_away_lang 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/typechecker/typ import squared_away/squared_away_lang/typechecker/typed_expr const initial_grid_width = 7 @@ -57,6 +60,9 @@ type Model { display_coords: Bool, active_cell: Option(grid.GridKey), src_grid: grid.Grid(String), + type_checked_grid: grid.Grid( + Result(typed_expr.TypedExpr, error.CompileError), + ), value_grid: grid.Grid(Result(value.Value, error.CompileError)), errors_to_display: List(#(grid.GridKey, error.CompileError)), ) @@ -64,6 +70,12 @@ type Model { fn init(_flags) -> #(Model, effect.Effect(Msg)) { let src_grid = grid.new(initial_grid_width, initial_grid_height, "") + let type_checked_grid = + grid.new( + initial_grid_width, + initial_grid_height, + Ok(typed_expr.Empty(typ.TNil)), + ) let value_grid = grid.new(initial_grid_width, initial_grid_height, Ok(value.Empty)) @@ -77,6 +89,7 @@ fn init(_flags) -> #(Model, effect.Effect(Msg)) { active_cell: None, src_grid:, value_grid:, + type_checked_grid:, errors_to_display: [], ) |> update_grid @@ -106,8 +119,8 @@ type Msg { fn update_grid(model: Model) -> Model { let scanned = lang.scan_grid(model.src_grid) let parsed = lang.parse_grid(scanned) - let typechecked = lang.typecheck_grid(parsed) - let value_grid = lang.interpret_grid(typechecked) + let type_checked_grid = lang.typecheck_grid(parsed) + let value_grid = lang.interpret_grid(type_checked_grid) // Loop over the grid to see if there's any errors to display let errors_to_display = @@ -118,7 +131,7 @@ fn update_grid(model: Model) -> Model { } }) - Model(..model, value_grid:, errors_to_display:) + Model(..model, value_grid:, type_checked_grid:, errors_to_display:) } // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#specifications @@ -410,9 +423,7 @@ fn view(model: Model) -> element.Element(Msg) { True -> attribute.class("errorcell") } - let #(color, background_color) = case - grid.get(model.value_grid, key) - { + let colors = case grid.get(model.value_grid, key) { Error(_) -> #("#b30000", "#ffe6e6") Ok(v) -> case v { @@ -423,6 +434,34 @@ fn view(model: Model) -> element.Element(Msg) { } } + let #(color, background_color) = case model.active_cell { + None -> colors + Some(active_cell) -> + case grid.get(model.type_checked_grid, active_cell) { + Error(_) -> colors + Ok(typed_expr) -> + case typed_expr.type_ { + typ.TTestResult -> + case grid.get(model.value_grid, active_cell) { + Ok(value.TestPass) -> + case + squared_away_lang.dependency_list( + model.type_checked_grid, + active_cell, + [], + ) + |> list.contains(key) + { + False -> colors + True -> #("#006400", "#e6ffe6") + } + _ -> colors + } + _ -> colors + } + } + } + let input = html.input([ on_input, diff --git a/src/squared_away/squared_away_lang.gleam b/src/squared_away/squared_away_lang.gleam index 16d1280..659e9f0 100644 --- a/src/squared_away/squared_away_lang.gleam +++ b/src/squared_away/squared_away_lang.gleam @@ -32,6 +32,23 @@ pub fn typecheck_grid( } } +pub fn dependency_list( + input: grid.Grid(Result(typed_expr.TypedExpr, error.CompileError)), + key: grid.GridKey, + acc: List(grid.GridKey), +) -> List(grid.GridKey) { + case grid.get(input, key) { + Error(_) -> acc + Ok(te) -> { + let deps = typed_expr.dependency_list(te, []) + let double_deps = + list.map(deps, dependency_list(input, _, [])) + |> list.flatten + |> list.append(deps) + } + } +} + pub fn parse_grid( input: grid.Grid(Result(List(token.Token), error.CompileError)), ) -> grid.Grid(Result(expr.Expr, error.CompileError)) { diff --git a/src/squared_away/squared_away_lang/interpreter.gleam b/src/squared_away/squared_away_lang/interpreter.gleam index 714c439..34ccba7 100644 --- a/src/squared_away/squared_away_lang/interpreter.gleam +++ b/src/squared_away/squared_away_lang/interpreter.gleam @@ -29,46 +29,19 @@ pub fn interpret( Error(_) -> Ok(value.Empty) } } - typed_expr.Label(_, txt) -> { - let key = - env - |> grid.to_list - |> list.fold_until(None, fn(_, i) { - case i { - #(cell_ref, Ok(typed_expr.LabelDef(_, label_txt))) - if label_txt == txt - -> { - Stop(Some(cell_ref)) - } - _ -> Continue(None) - } - }) - |> option.map(grid.cell_to_the_right(env, _)) - |> option.map(option.from_result) - |> option.flatten - - case key { - None -> - Error( - error.RuntimeError(runtime_error.RuntimeError( - "Label doesn't point to anything", - )), - ) - Some(key) -> { - case grid.get(env, key) { - Error(e) -> Error(e) - Ok(te) -> { - case te { - typed_expr.Label(_, ltxt) if ltxt == txt -> { - Error( - error.RuntimeError(runtime_error.RuntimeError( - "Label points to itself", - )), - ) - } - _ -> interpret(env, te) - } + typed_expr.Label(_, key, txt) -> { + case grid.get(env, key) { + Error(e) -> Error(e) + Ok(te) -> { + case te { + typed_expr.Label(_, _, ltxt) if ltxt == txt -> { + Error( + error.RuntimeError(runtime_error.RuntimeError( + "Label points to itself", + )), + ) } + _ -> interpret(env, te) } } } diff --git a/src/squared_away/squared_away_lang/typechecker.gleam b/src/squared_away/squared_away_lang/typechecker.gleam index fadc7de..d80197f 100644 --- a/src/squared_away/squared_away_lang/typechecker.gleam +++ b/src/squared_away/squared_away_lang/typechecker.gleam @@ -131,7 +131,12 @@ pub fn typecheck( |> option.flatten case key { - None -> Ok(typed_expr.Label(typ.TNil, txt)) + None -> + Error( + error.TypeError(type_error.TypeError( + "Label doesn't point to anything", + )), + ) Some(key) -> { let x = grid.get(env, key) case x { @@ -143,7 +148,7 @@ pub fn typecheck( } Ok(expr) -> { case typecheck(env, expr) { - Ok(te) -> Ok(typed_expr.Label(type_: te.type_, txt:)) + Ok(te) -> Ok(typed_expr.Label(type_: te.type_, key:, txt:)) Error(e) -> Error(e) } } diff --git a/src/squared_away/squared_away_lang/typechecker/typed_expr.gleam b/src/squared_away/squared_away_lang/typechecker/typed_expr.gleam index a2b5a05..0e9a483 100644 --- a/src/squared_away/squared_away_lang/typechecker/typed_expr.gleam +++ b/src/squared_away/squared_away_lang/typechecker/typed_expr.gleam @@ -1,5 +1,6 @@ import gleam/float import gleam/int +import gleam/list import gleam/string import squared_away/squared_away_lang/grid import squared_away/squared_away_lang/parser/expr @@ -11,7 +12,7 @@ pub type TypedExpr { FloatLiteral(type_: typ.Typ, f: Float) UsdLiteral(type_: typ.Typ, cents: rational.Rat) PercentLiteral(type_: typ.Typ, percent: rational.Rat) - Label(type_: typ.Typ, txt: String) + Label(type_: typ.Typ, key: grid.GridKey, txt: String) CrossLabel( type_: typ.Typ, key: grid.GridKey, @@ -56,9 +57,31 @@ pub fn visit_cross_labels( } } +pub fn dependency_list( + te: TypedExpr, + acc: List(grid.GridKey), +) -> List(grid.GridKey) { + case te { + BinaryOp(_, lhs:, op: _, rhs:) -> + list.flatten([dependency_list(lhs, []), dependency_list(rhs, []), acc]) + BooleanLiteral(_, _) -> acc + BuiltinSum(_, keys) -> list.flatten([keys, acc]) + CrossLabel(_, key, _, _) -> [key, ..acc] + Empty(_) -> acc + FloatLiteral(_, _) -> acc + Group(_, inner) -> list.flatten([dependency_list(inner, []), acc]) + IntegerLiteral(_, _) -> acc + Label(_, key:, txt: _) -> [key, ..acc] + LabelDef(_, _) -> acc + PercentLiteral(_, _) -> acc + UnaryOp(_, _, inner) -> list.flatten([dependency_list(inner, []), acc]) + UsdLiteral(_, _) -> acc + } +} + pub fn to_string(te: TypedExpr) -> String { case te { - Label(_, _) + Label(_, _, _) | UnaryOp(_, _, _) | BinaryOp(_, _, _, _) | BuiltinSum(_, _) @@ -82,7 +105,7 @@ fn do_to_string(te: TypedExpr) -> String { PercentLiteral(_, p) -> rational.to_string(rational.multiply(p, rational.from_int(100)), 100) <> "%" - Label(_, l) -> l + Label(_, _, l) -> l LabelDef(_, l) -> l Group(_, t) -> "(" <> do_to_string(t) <> ")" UnaryOp(_, op, te) -> expr.unary_to_string(op) <> do_to_string(te)