OVERVIEW
This documents how expressions are handled internally.
Split the string into an array of chunks
If a ( starts the chunk, then get until the closing matched ) and label it as a GROUP
if a { starts the chunk, then get until the closing matched } and label it as a BLOCK
if a [ starts the chunk, then get until the closing ] and label it as a COMMAND
if a " starts the chunk, then get until the next " and label it as a STRING
if a $ starts the chunk, then it's a variable, get according to variable rules, get the value of said variable, and label it as an operand of the appropriate type.
if it's a string that matches one of the defined functions, then grab it and label it as a FUNC. Treat the argument inside the ()'s as an operand ( follow operand rules)
if it matches one of the operators, get it and label it as an OP
if looks like an integer, pull it off and mark it as an INT
if looks like a float, pull it off and mark it as a FLOAT
if it's whitespace, skip.
otherwise, pull it off and mark it as a string.
Convert the chunks into a program stack
Now we have an array of chunks. scan the array for each level of operator precedence. If we find a match for an op at that level of precedence, pull it out and put in the right spot on the stack. (which, unless I'm mistaken, is always the top. but I could be.)
e.g. given 5*2+4/2, we get an array of chunks:
INT 5, OP *, INT 2, OP +, INT 4, OP /, INT 2
Going through our first level of precedence, no hits. Second, we get a hit on the * and the /. Put those on the stack.
INT 5
INT 2
OP *
INT 4
INT 2
OP /
That leaves a lone "OP +", which we then append to the stack, giving a program stack of:
INT 5
INT 2
OP *
INT 4
INT 2
OP /
OP +
Calculate the result
Now we add a result stack to our program stack. Move off all the values until you end up with an OP (or a FUNC) at the top of the program stack:
Program Result
-----------------
OP * INT 5
INT 4 INT 2
INT 2
OP /
OP +
Now we process the * OP. It requires two numeric values, which we have. Since they're both ints, we end up with an INT result, which we put on the result stack. We then move over the other values, until we get to the next op.
Program Result
-----------------
OP / INT 2
OP + INT 4
INT 10
Again, integers yield integers. we do the math, and have no more values to copy over at the moment:
Program Result
-----------------
OP + INT 2
INT 10
We do the addition...
Program Result
-----------------
/empty/ INT 12
Our program stack is empty, we have a single result. At this point, we know the type, so if our interpreter was smart enough (which it's not as of this writing), we could pass around the native value rather than stringifying it first.
TODO
Clarify when we're going to evaluate blocks, commands, strings, etc.
Add notes covering the "BLOCK" concept of deferred evaluation.
Change the example given to one a little more complex, that includes precedence (implied and implicit), deferred evaluation, short circuiting.