Skip to content

Commit

Permalink
introduce custom grid type
Browse files Browse the repository at this point in the history
  • Loading branch information
bcpeinhardt committed Oct 27, 2024
1 parent c2b2a7d commit eb68e9f
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 207 deletions.
59 changes: 21 additions & 38 deletions squared_away/src/squared_away.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import lustre/event
import pprint
import squared_away_lang as lang
import squared_away_lang/error
import squared_away_lang/grid
import squared_away_lang/interpreter/value
import squared_away_lang/typechecker/typ
import squared_away_lang/typechecker/type_error
Expand All @@ -32,25 +33,16 @@ pub fn main() {
type Model {
Model(
formula_mode: Bool,
active_cell: Option(String),
src_grid: Dict(String, String),
value_grid: Dict(String, Result(value.Value, error.CompileError)),
errors_to_display: List(#(String, error.CompileError)),
active_cell: Option(grid.GridKey),
src_grid: grid.Grid(String),
value_grid: grid.Grid(Result(value.Value, error.CompileError)),
errors_to_display: List(#(grid.GridKey, error.CompileError)),
)
}

fn init(_flags) -> #(Model, effect.Effect(Msg)) {
let cols = list.range(1, grid_width)
let rows = list.range(1, grid_height)

let src_grid =
list.fold(cols, dict.new(), fn(grid, c) {
list.fold(rows, dict.new(), fn(partial_grid, r) {
let key = int.to_string(c) <> "_" <> int.to_string(r)
partial_grid |> dict.insert(key, "")
})
|> dict.merge(grid)
})
let src_grid = grid.new(grid_width, grid_height, "")
let value_grid = grid.new(grid_width, grid_height, Ok(value.Empty))

// We could presume that our value_grid starts with all empty,
// but instead I think we should scan, parse, typecheck, and
Expand All @@ -61,7 +53,7 @@ fn init(_flags) -> #(Model, effect.Effect(Msg)) {
formula_mode: False,
active_cell: None,
src_grid:,
value_grid: dict.new(),
value_grid:,
errors_to_display: [],
)
|> update_grid
Expand All @@ -71,8 +63,8 @@ fn init(_flags) -> #(Model, effect.Effect(Msg)) {

type Msg {
UserToggledFormulaMode(to: Bool)
UserSetCellValue(key: String, val: String)
UserFocusedOnCell(key: String)
UserSetCellValue(key: grid.GridKey, val: String)
UserFocusedOnCell(key: grid.GridKey)
UserFocusedOffCell
}

Expand All @@ -84,7 +76,7 @@ fn update_grid(model: Model) -> Model {

// Loop over the grid to see if there's any errors to display
let errors_to_display =
dict.fold(value_grid, [], fn(acc, key, val) {
grid.fold(value_grid, [], fn(acc, key, val) {
case val {
Error(err) -> [#(key, err), ..acc]
Ok(_) -> acc
Expand All @@ -98,7 +90,7 @@ fn update(model: Model, msg: Msg) -> #(Model, effect.Effect(Msg)) {
case msg {
UserSetCellValue(key, val) -> {
let model =
Model(..model, src_grid: dict.insert(model.src_grid, key, val))
Model(..model, src_grid: grid.insert(model.src_grid, key, val))
#(update_grid(model), effect.none())
}
UserToggledFormulaMode(formula_mode) -> #(
Expand Down Expand Up @@ -127,33 +119,23 @@ fn view(model: Model) -> element.Element(Msg) {
|> result.unwrap(or: html.div([], []))

let rows =
list.range(1, grid_height)
|> list.map(int.to_string)
|> list.map(fn(row) {
model.src_grid.cells
|> list.group(grid.row)
|> dict.map_values(fn(_, keys) {
let cells =
list.map(columns, fn(col) {
let key = col <> "_" <> row
list.map(keys, fn(key) {
let on_input = event.on_input(UserSetCellValue(key:, val: _))
let out_of_focus = event.on_blur(UserFocusedOffCell)
let on_focus = event.on_focus(UserFocusedOnCell(key))
let show_formula =
model.active_cell == Some(key) || model.formula_mode
let value =
case show_formula {
True ->
case dict.get(model.src_grid, key) {
Error(_) -> no_value_found_txt()
Ok(src) -> src
}
True -> grid.get(model.src_grid, key)
False ->
case dict.get(model.value_grid, key) {
Error(_) -> no_value_found_txt()
Ok(v) -> {
case v {
Error(e) -> error.error_type_string(e)
Ok(v) -> value.value_to_string(v)
}
}
case grid.get(model.value_grid, key) {
Error(e) -> error.error_type_string(e)
Ok(v) -> value.value_to_string(v)
}
}
|> attribute.value
Expand All @@ -172,6 +154,7 @@ fn view(model: Model) -> element.Element(Msg) {

html.tr([], cells)
})
|> dict.values

let grid =
html.div([class("table-container")], [
Expand Down
73 changes: 27 additions & 46 deletions squared_away_lang/src/squared_away_lang.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import gleam/list
import gleam/option.{None, Some}
import gleam/result
import squared_away_lang/error
import squared_away_lang/grid
import squared_away_lang/interpreter
import squared_away_lang/interpreter/value
import squared_away_lang/parser
Expand All @@ -13,82 +14,62 @@ import squared_away_lang/scanner/token
import squared_away_lang/typechecker
import squared_away_lang/typechecker/typ
import squared_away_lang/typechecker/typed_expr
import squared_away_lang/util

pub fn interpret_grid(
input: dict.Dict(String, Result(typed_expr.TypedExpr, error.CompileError)),
) -> dict.Dict(String, Result(value.Value, error.CompileError)) {
use acc, key, typed_expr <- dict.fold(input, dict.new())
input: grid.Grid(Result(typed_expr.TypedExpr, error.CompileError)),
) -> grid.Grid(Result(value.Value, error.CompileError)) {
use _, typed_expr <- grid.map_values(input)
case typed_expr {
Error(e) -> dict.insert(acc, key, Error(e))
Ok(typed_expr) -> {
let maybe_value = interpreter.interpret(input, typed_expr)
dict.insert(acc, key, maybe_value)
}
Error(e) -> Error(e)
Ok(typed_expr) -> interpreter.interpret(input, typed_expr)
}
}

pub fn typecheck_grid(
input: dict.Dict(String, Result(expr.Expr, error.CompileError)),
) -> dict.Dict(String, Result(typed_expr.TypedExpr, error.CompileError)) {
use acc, key, expr <- dict.fold(input, dict.new())
input: grid.Grid(Result(expr.Expr, error.CompileError)),
) -> grid.Grid(Result(typed_expr.TypedExpr, error.CompileError)) {
use key, expr <- grid.map_values(input)
case expr {
Error(e) -> dict.insert(acc, key, Error(e))
Error(e) -> Error(e)
Ok(expr.Label(txt)) -> {
case util.cell_to_the_right(key) {
None ->
dict.insert(acc, key, Ok(typed_expr.Label(type_: typ.TNil, txt:)))
Some(new_key) -> {
let val = case dict.get(input, new_key) {
Error(_) -> {
io.debug(
"Unexpected uninitialized cell encountered suring typechecking",
)
panic
}
Ok(v) -> v
}
case grid.cell_to_the_right(input, key) {
Error(Nil) -> Ok(typed_expr.Label(type_: typ.TNil, txt:))
Ok(new_key) -> {
let val = grid.get(input, new_key)
case val {
Error(e) -> dict.insert(acc, key, Error(e))
Error(e) -> Error(e)
Ok(val) -> {
case typechecker.typecheck(input, val) {
Error(e) -> dict.insert(acc, key, Error(e))
Error(e) -> Error(e)
Ok(typed_val) ->
dict.insert(
acc,
key,
Ok(typed_expr.Label(type_: typed_val.type_, txt:)),
)
Ok(typed_expr.Label(type_: typed_val.type_, txt:))
}
}
}
}
}
}
Ok(expr) -> {
let maybe_typed_expr = typechecker.typecheck(input, expr)
dict.insert(acc, key, maybe_typed_expr)
}
Ok(expr) -> typechecker.typecheck(input, expr)
}
}

pub fn parse_grid(
input: dict.Dict(String, Result(List(token.Token), error.CompileError)),
) -> dict.Dict(String, Result(expr.Expr, error.CompileError)) {
use acc, key, toks <- dict.fold(input, dict.new())
input: grid.Grid(Result(List(token.Token), error.CompileError)),
) -> grid.Grid(Result(expr.Expr, error.CompileError)) {
use _, toks <- grid.map_values(input)
case toks {
Error(e) -> dict.insert(acc, key, Error(e))
Error(e) -> Error(e)
Ok(toks) -> {
let expr = parser.parse(toks)
dict.insert(acc, key, expr |> result.map_error(error.ParseError))
expr |> result.map_error(error.ParseError)
}
}
}

pub fn scan_grid(
input: dict.Dict(String, String),
) -> dict.Dict(String, Result(List(token.Token), error.CompileError)) {
use acc, key, src <- dict.fold(input, dict.new())
input: grid.Grid(String),
) -> grid.Grid(Result(List(token.Token), error.CompileError)) {
use _, src <- grid.map_values(input)
let maybe_scanned =
scanner.scan(src)
|> result.map_error(error.ScanError)
Expand All @@ -98,5 +79,5 @@ pub fn scan_grid(
_ -> t
}
}))
dict.insert(acc, key, maybe_scanned)
maybe_scanned
}
84 changes: 84 additions & 0 deletions squared_away_lang/src/squared_away_lang/grid.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//// Grid operations are kind of a pain to get right / can produce some
//// boilerplate around results for get operations, so I'm gonna try and extract
//// them to a module

import gleam/dict
import gleam/list
import gleam/option.{type Option, None, Some}

// Making the type generic since we do a grid of src
// and a grid of interpreted values
pub type Grid(a) {
Grid(inner: dict.Dict(GridKey, a), cells: List(GridKey))
}

pub opaque type GridKey {
GridKey(row: Int, col: Int)
}

pub fn row(grid_key: GridKey) -> Int {
grid_key.row
}

pub fn col(grid_key: GridKey) -> Int {
grid_key.col
}

pub fn new(width: Int, height: Int, default: a) -> Grid(a) {
let cols = list.range(1, width)
let rows = list.range(1, height)

let #(g, c) =
list.fold(rows, #(dict.new(), []), fn(acc, row) {
let #(grid, cells) = acc

let #(g, c) =
list.fold(cols, #(dict.new(), []), fn(acc, col) {
let #(g, c) = acc
#(dict.insert(g, GridKey(row:, col:), default), [
GridKey(row:, col:),
..c
])
})

#(dict.merge(grid, g), list.concat([c, cells]))
})

Grid(g, c)
}

pub fn insert(grid: Grid(a), key: GridKey, item: a) -> Grid(a) {
Grid(..grid, inner: dict.insert(grid.inner, key, item))
}

pub fn fold(grid: Grid(a), acc: b, do: fn(b, GridKey, a) -> b) -> b {
dict.fold(grid.inner, acc, do)
}

pub fn map_values(grid: Grid(a), do: fn(GridKey, a) -> b) -> Grid(b) {
Grid(inner: dict.map_values(grid.inner, do), cells: grid.cells)
}

pub fn get(grid: Grid(a), key: GridKey) -> a {
// Because the `GridKey` is an opaque type produced
// by creating the grid, the get operation is safe.
let assert Ok(item) = dict.get(grid.inner, key)
item
}

pub fn cell_to_the_right(grid: Grid(a), key: GridKey) -> Result(GridKey, Nil) {
list.find(grid.cells, fn(k) { k.row == key.row && k.col == key.col + 1 })
}

pub fn intersect(row_cell: GridKey, col_cell: GridKey) -> Result(GridKey, Nil) {
let GridKey(row, col_check) = row_cell
let GridKey(row_check, col) = col_cell
case row != row_check && col != col_check {
False -> Error(Nil)
True -> Ok(GridKey(row:, col:))
}
}

pub fn to_list(grid: Grid(a)) -> List(#(GridKey, a)) {
dict.to_list(grid.inner)
}
Loading

0 comments on commit eb68e9f

Please sign in to comment.