diff --git a/LOG.md b/LOG.md index 125cbe1..84d6c73 100644 --- a/LOG.md +++ b/LOG.md @@ -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 REPL using that, connected to stdin and stdout. This demonstrated a 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.