Update development log

This commit is contained in:
cdo committed 2026-09-26 11:57:44 +01:00
1 parent cdaf2e7013
commit 03a17aa4a0
1 file changed
+50
+50
View File
@@ -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.