To complete this exercise, you will need to install all the Fable pre-requisites and get an F# editor that works well with Fable. The recommended setup is to use Ionide in VS Code:
- Follow the Ionide installation instructions
- Follow the Fable Getting started tutorial
Go to the renderSheet
function. Currently, the function generates
a static HTML table. Change the function so that it generates the table
dynamically. There should be one column for every index in state.Cols
and one row for every index in state.Rows
. You can do this using
List.map
, or using F# list comprehensions (for
loop and yield
)
To get the element with body for a given cell, call the renderCell
function with the right position.
The renderCell
function currently renders an editor for cell A1 and
displays values as text for all other cells. Now, we want to be able to
click on another cell to edit it. The events to do this are already
defined and the code to trigger them is also in-place:
- In
renderEditor
, theonkeyup
handler triggersFinishEdit
when the key code is [Enter] - In
renderView
, theonclick
handler triggersStartEdit
To support selection of cells, you first need to modify the State
type
and add a field that will represent the selected cell. This is optional,
because if you finish editing one cell, no other call is selected
(the Position
type is a type alias for char * int
). You need to:
- Add
Active : option<Position>
to theState
record - Add an initial value to
initial
- Handle
StartEdit
andFinishEdit
in theupdate
function - Check if
state.Active
matchespos
inrenderCell
Currently, the renderCell
just displays the text you enter in the editor.
Now, we want to evaluate basic expressions like =1+2
and =A10*3
.
The parser for the expressions is already implemented in evaluator.fs
,
so all you need to do is to implement and call the evaluate
function!
First, go to renderCell
. In the case when we are rendering view (not
the editor), you need to get the entered string from state.Cells
and,
if it is Some, parse it using the parse
function and then pass the
parsed expression to evaluate
(these two functions are in evaluator.fs
)
Second, open evaluator.fs
. The evaluate
function is a recursive
function, but currently it just returns 0, 1 or 2. Implement the
evaluation function! (Don't worry about handling errors correctly,
we'll fix that in the next step.)
The evaluator can fail:
- If you reference a cell without a value
- If you reference a cell from within itself
To handle errors, we need to modify the evaluate
function so that
it returns option<int>
rather than just int
. Go to evaluator.fs
and modify the function! You will need to propagate the None
values
correctly - the best way to do this is using Option.bind
and
Option.map
, but you can also use pattern matching using match
.
Once you modify evaluate
to return option<int>
, you will need
to modify the code in renderCell
below. For a start, you can just
display #ERR
, but you can also modify renderView
to indicate
errors with a different background color.
Handling the second case is harder, because we currently just get into
an infinite loop and get a stack overflow. To handle this, you need
to modify the evaluate
function so that it has an additional parameter
of type Set<Position>
that keeps a set with all cells that we are
evaluating. Then, when handling Reference
, you need to make sure that
the referenced cell is not in this set.