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.


parrot