Update development log
This commit is contained in:
1 parent
cdaf2e7013
commit
03a17aa4a0
1 file changed
+50
@@ -143,3 +143,53 @@ I was then able to create a demo program by defining a dummy evaluator
|
|||||||
that simply returns the input expression unmodified, and running a
|
that simply returns the input expression unmodified, and running a
|
||||||
REPL using that, connected to stdin and stdout. This demonstrated a
|
REPL using that, connected to stdin and stdout. This demonstrated a
|
||||||
full round trip through the parsing and printing procedures.
|
full round trip through the parsing and printing procedures.
|
||||||
|
|
||||||
|
## Evaluator
|
||||||
|
|
||||||
|
Rather than trying to handle everything in one complex evaluator, I
|
||||||
|
decided to implement a multi-pass evaluator. This essentially means
|
||||||
|
that the expressions run through multiple different passes that apply
|
||||||
|
different transformations to them, thereby keeping the complexity of
|
||||||
|
each pass more managable. This is a common method used when
|
||||||
|
implementing compilers and interpreters.
|
||||||
|
|
||||||
|
### Normalisation
|
||||||
|
|
||||||
|
The first preliminary pass I implemented was a normalisation pass.
|
||||||
|
This transforms things like `x - y` into `x + (-1)y`, and `x/y` into
|
||||||
|
`xy^(-1)`. The main benefit of this is to make it easier for the
|
||||||
|
later differentiation pass as it will only have to handle sums,
|
||||||
|
products and exponents (not differences and quotients). I decided to
|
||||||
|
leave quotients of integers as-is since these will be untouched by the
|
||||||
|
differentiation process anyway, and "1/2" reads far better than
|
||||||
|
"1*2^(-1)".
|
||||||
|
|
||||||
|
### Constant Folding
|
||||||
|
|
||||||
|
The purpose of this pass is to combine constants together as much as
|
||||||
|
possible, such as transforming `1 + 2*3` into `6`, or `x + 1 + 2` into
|
||||||
|
`x + 3`. This is mostly a nice-to-have rather than an essential
|
||||||
|
feature, though it will make the output much more readable.
|
||||||
|
|
||||||
|
Implementing this pass did get a little hairy, as the binary tree
|
||||||
|
structure I used for the AST made handling cases like `x + 1 + 2`
|
||||||
|
(which is represented in the AST as `(x + 1) + 2` quite complex. The
|
||||||
|
module implementing this therefore ended up being somewhat messy,
|
||||||
|
especially since Rust's move/borrow semantics made flattening trees of
|
||||||
|
sums and products more complex than it would otherwise be. However, I
|
||||||
|
decided to move on rather than spend a great deal of time tidying this
|
||||||
|
up since, as previously mentioned, this was more of a nice-to-have
|
||||||
|
feature rather than core functionality.
|
||||||
|
|
||||||
|
### Evaluator Composition
|
||||||
|
|
||||||
|
I then implemented a mechanism to compose evaluators together, and
|
||||||
|
build a pipeline of arbitrarily many of them. This was simple to do
|
||||||
|
since I was implementing the `Evaluator` trait for each pass, so
|
||||||
|
supporting composition simply meant creating a struct containing two
|
||||||
|
evaluators, and implementing the `Evaluator` trait for this such that
|
||||||
|
the evaluators were run in sequence on any input expression.
|
||||||
|
|
||||||
|
To combine more than two evaluators, I created a `pipeline` macro that
|
||||||
|
constructs nested compositions of evaluators with Rust's `macro_rules`
|
||||||
|
system.
|
||||||
Reference in new issue
Block a user