12 hours later…
12:27
In the first lesson we matched parentheses, and in the second we parsed an expression without parentheses.
Today we are going to handle parentheses, so we'll now do the more complicated expression
((-3)+√(3×3)-4×2×1)÷2×2
.
We're only shuffling the terms around, so there's actually a part missing in that we won't keep track of how many arguments each function has.
The idea here is that we push every function forward to the end of the expression (
l
), and compensate by moving everything it goes past backwards by 1 (-+`f
).
When we have parentheses, we instead want to push every function up to the containing closing parentheses, or the end if it's at the top level.
The idea here is similar to what we did in the first APL Seeds. We are going to sort the whole expression by nesting depth.
This expression looks fairly confusing at first, since the parens aren't paired any more. Since the depth increases and decreases at/before each paren, the opening paren is considered to be inside the pair but the closing paren is outside.
So the closing paren gets left behind when we do this sorting. That's actually good, because we now have a record of where the expression belongs.
On the other hand, the open paren always ends up at the beginning of the expression in parens when sorting. It serves as a marker of the beginning of a subexpression.
To see what's happening better, we can split the expression based on open parens with
'('(+`∘=⊔⊢)(⍋d)⊏x
.
To find the containing closing parenthesis, I want to find which expression each character belongs to. In the format above, this is easy because expressions are contiguous. I just need to find the number of open parens before the character.
This idea of ordering by depth is pretty important, as you can use it to deal with any sort of paired delimiters. On functions it separates all the bodies so you only have to deal with one function at a time, and similarly for parens it removes all the nesting in a sense.
ii
stands for "interior index", or which interior contains a character. But I said I wanted the closing parenthesis index for each character, not this expression index thing.
Fortunately, the ordering of the interiors matches the ordering of open or closed parentheses by depth (in lesson 1 we discussed why these two sets end up with the same ordering).
Meaning the last closed parenthesis goes first since it contains the other two, but those two have the same depth.
There are three closed parens but four interiors (0 to 3). This is because the top level has no parens.
┌ ( ( - 3 ) + √ ( 3 × 3 ) - 4 × 2 × 1 ) ÷ 2 × 2 18 4 4 4 18 18 18 11 11 11 11 18 18 18 18 18 18 18 23 23 23 23 23 ┘
Note that for this task we only care about the values of
fe
at functions. But the concept of "where is my closing parenthesis" should make sense for any character.
After this, the "target index" is the enclosing parenthesis for functions, but the current index for everything else. We can get this target with
(f×fe)⌈↕l
. ┌ ( ( - 3 ) + √ ( 3 × 3 ) - 4 × 2 × 1 ) ÷ 2 × 2 18 4 4 3 18 18 18 11 8 11 10 18 18 13 18 15 18 17 23 23 20 23 22 ┘
Again, we're going to have to correct these indices by moving everything in the way of a function back by 1. With no parentheses, we used
+`f
, but this will pull back everything to the right of the function, while we want to stop at the closing parenthesis.
So we really want
+`f-corr
, where corr
is a correction that undoes the shifts for the functions each pair of parentheses contains, at the closing parenthesis.
It's equal to the number of functions that pair of parentheses contained. To get it, we should look at all the closing indices
f/fe
, and increment that closing index for each time it appears.
You could do this with a modified assignment in APL, but the idiomatic BQN way is to use Indices (Where) inverse,
/⁼
.
We have to sort (
∧
) f/fe
because the output of Indices is always sorted, so the input to Indices inverse must be sorted.
I won't be explaining how to implement the runtime, which is how functions are evaluated and so on. That part is the same as it would be in an interpreter and there's a reasonable amount of existing material on it.
On the other hand, a compiler just transforms source code into a list of instructions, and it turns out you can do this with array operations that act on the entire source.
But the only person who's actually done this so far is Aaron Hsu (Co-dfns). It's hard, but I think if we as a community can figure out and share the tricks, it will be similar to "normal" compiler implementation, where it's a difficult topic but only requires some dedication to accomplish and not divine inspiration.
4 hours later…
1 hour later…
1 hour later…
« first day (1260 days earlier) ← previous day next day → last day (1688 days later) »