

The Eval-Utility
-----------------------


EVAL.ZIP contains these files:
    - EVAL01.BAS        ' Library-source
    - EVAL02.FRM        ' Example
    - BIT.BI            ' Include-File
    - EVAL.TXT          ' Documentation
    - All of these files contain copyrighted material
        which may be distributed without any changes.
        

2+6*8:
    To evaluate this arithmetic experession we first calculate
    6 * 8 which equals to 48 after that we add two and the result
    is 50.
    Function EVAL does exactly the same. It is a recursive function,
    that means a function which calls itself. It can handle all
    arithmetic operations which are +, -, *, /, ^, Negation, (, and
    ).

Let's see how it works: Function EVAL
    First we store the expression that we want to evaluate as
    a globel Variable to module EVAL01.BAS. We can do this
    with Function EVALUATE, which is an interface function for
    standard VB-Controls. Since we've dimmed expression$ as a
    shared variable at moudle level (EVAL01.BAS: DIM SHARED expression$)
    we can assign:

        expression$ = BasicFmt$(t$)

    BasicFmt$ is a function that returns a string from t$ without
    commas. That means we make sure that the decimalseperator for
    further calculation is always a dot. This kind of preparation is
    necessary because some countries use commas as decimal seperators
    keyboards and keyboard drivers are designed to handle this.

    There's an easy way to find out which decimal seperator is beeing
    used by the operation system (OS)

        test$=Format$(0.1,"0.0")
        if instr(test$,",") then
            'decimal seperator is comma
        else
            'decimal seperator is dot
        end if

    The eval-utility uses a simular approach. Call Function EvalReg,
    which returns true if registration was successfully or false
    if not, to store the ASCII value of the decimalseperator as
    a shared variable (decimalSeperator) within module EVAL01.BAS.

    Another shared variable Function EVAL uses is SYNTAX. We use
    this to point to the current character of expression$. The
    current character is the one which we are about to interpret.
    At the very beginning SYNTAX points to character zero. When
    we're finished SYNTAX is the same as len(expression$).

    Basically there are two types of characters we have to deal with.
    First Figures: All characters from 0 to 9 including dot as decimal
    seperator and the blank. Second: All above mentioned operators
    (+-*/^).
    First thing we have to do is to collect numbers and to combine
    them to an appropiate numeric value. We do this within a loop:
    Syntax is the loopcounter and the loop will terminate when
    Syntax equals len(expression). A FOR NEXT loop would be fine but
    since we have to change syntax during iteration I've decided
    in favor of a DO WHILE...LOOP.

        DO WHILE syntax < LEN(expression$)
            syntax = syntax + 1
            Temp$ = MID$(expression$, syntax, 1)
            char = ASC(Temp$)
            SELECT CASE char
            CASE 32
            ' ignore blanks
            CASE 48 TO 57
            ' collect figures
                collection$ = collection$ + Temp$
            CASE 46
            ' we've got a dot, see if decimal seperator is
            ' allowed so that we may continue with fractional
            ' part of the number.
                IF decFlag AND bit00 THEN
                ' we've got a fractional part already => syntax error!
                    eValError = 2: EXIT DO
                ELSE
                ' turn on bit00 to indicate we're collecting the
                ' fractional part
                    decFlag = decFlag OR bit00
                    collection$ = collection$ + Temp$
                END IF
            case....
            ....
        LOOP

    This snippet shows how we interpret figures. It ignores blanks,
    and collects single characters from expression$ into collection$.
    The aim is: Once we've finished with a collection that means
    collection$ contains a single number we make this number the
    current number and assign it to variable item# - which is the
    first parameter of the eval-function. AIM:

        ITEM# = VAL(Collection$)

    Since we've allowed decimal seperators, we've got to check for
    it seperately, and because a number can only have one decimalseperator
    we have to do some syntax-checking.
    For this and for more syntax-checking to come I have decided to
    use only one integer variable. This helps reducing stack space
    as you will see soon.
    
    The name of this syntax-checking-integer is decFlag. decFlag
    is function EVAL's second parameter and has to be zero when
    you start evaluating a new expression. We use bit number zero for
    decimalseperator-syntax. Once we've found a dot we set the flag
    using this:

        decFlag = decFlag OR bit00

    Because there's only one decimalseperator allowed we can check
    if the flag has been set previously using this statement:

        decFlag AND bit00

    This will evaluate to TRUE if bit zero is set - that means it will
    indicate the syntax error. Because an integer has 16 bytes, (zero
    to 15) we can keep track of 16 different syntax flags, which
    is far more than we'll ever need.

    Refer to file BIT.BI to see how the BITxx-constants are set and see
    how the syntax of arithmetics is defined using the PUSHx-constants.
    Obviously this file is included whithin EVAL01.BAS using
    metacommand

        '$include:'bit.bi'

    Now you'll see how the syntax of arithmetics is programmed.
    We're using a recursive algorithm. First we finish the loop.
    We add the following case-clauses:

        ...
        CASE plus
            IF decFlag AND push1 THEN EXIT DO
            GOSUB collectItem
            item# = item# + eval(0#, bit01)
        CASE minus
            IF decFlag AND push1 THEN EXIT DO
            GOSUB collectItem
            item# = item# - eval(0#, bit01)
        CASE mult
            IF decFlag AND push2 THEN EXIT DO
            GOSUB collectItem
            item# = item# * eval(0#, bit02)
        CASE divis
            IF decFlag AND push2 THEN EXIT DO
            GOSUB collectItem
            test# = eval(0#, bit02)
            IF test# THEN
                item# = item# / test#
            ELSE
                eValError = 11
            END IF
        CASE expo
            IF decFlag AND push3 THEN EXIT DO
            GOSUB collectItem
            item# = item# ^ eval(0#, bit03)
        CASE brakO
            item# = eval(0#, 0)
        CASE brakC
            syntax = syntax + 1
            EXIT DO
        CASE ELSE
            eValError = 2: EXIT DO
        ....

    As you can see they deal with the second kind of characters (first
    group was numbers, dot and blank). These characters tell how to
    combine the current item# with the one on stack. This will result
    in a new current item#. This is somewhat diffrerent from the
    the first group of characters which composes a numeric value by its
    characters. Also every character of the second kind finishes
    collecting figures, that means it assigns the value of the collection
    to the variable item#. Because this has to be done at the beginning of
    all arithmetic operations there is a special linelabel (collectItem)
    for it, to which we jump to using GOSUB.

        collectItem:
            IF LEN(collection$) THEN item# = VAL(collection$)
            collection$ = ""
        RETURN
    
    Basic manages the stack for us. We only have to code:

        item# = item# - eval(0#, bit01)

    The first Parameter creates a new item#-variable, the second tells
    the eval function that there's a minus or plus calculation on stack.
    (see BIT.BI for details)

    Because "Syntax" and "expression$" are global to that module the
    newly eval-call will continue with the loop where the previous
    instance stopped.

    Let's have a look at this in action. Press F2 to switch to the
    code-window and select the eval function for editing. Move the
    cursor to the DIM statements of the function and press F9.
    (Breakpoint at first line of FUNCTION eval) Now put the following
    variables into the Debug-Window: syntax, Temp$, expression$,
    collection$, item# and decFlag. (Move the cursor to the
    variable and press SHIFT + F9 and then ENTER). Close all windows
    exept the edit and Debug window and choose "Window - Arrange all"
    from the Menue. Now you see them both: The Edit-Window and the
    Debug Window. Press F5 to run the program and enter:

        2+6*8

    When you click the OK-Button execution stops at the previously
    setted breakpoint. From there continue with with the program
    by pressing F8.

    When syntax is "2", temp$ is "+" and the program returned
    from collectItem next statement will be

        item# = item# + eval(0#, bit01)

    Press the F8 button again to put the variables "item#" and
    "decFlag" on stack. Basic starts executing the eval-Function
    again. On stack is:

            item    operation
            --------------------
            2       plus

    Press F8 to continue until syntax equals 4. Watch how a
    newly collected item is passed to variable item# and Basic
    puts another one on stack, that means it calls function eval
    again with a new set of variables. This time the stack looks
    like this:

            item    operation
            --------------------
            6       multi
            2       plus

    Continue pressing F8. When the loop is finished eval pushes from
    stack and returns to its previous instance with value 48.
    On stack is:

            item    operation
            --------------------
            2       plus

    Continue pressing F8 until eval returns from its last instance
    and evaluated the complete expression to 50.

    Because we put the item and its operation on stack, eval knows
    when to pop and when to push. It always pushes when the next
    operation is equal or less in hirarchy than the one on stack.

    This means that an expession like:

        52+4+3-7

    uses only two instances of eval, although it evaluates 4 atoms
    which are:

    atom no     atom
    -----------------
    1.          52 +
    2.          56 +
    3.          59 -
    4.          7
                = 52

    or  2*6/4=3

    atom no     atom
    ----------------
    1.          2*
    2.          12/
    3.          4
                = 3

    Some testing:
    1+2=3
    1-2=-1
    1*2=2
    1/2=0,5
    4+1/2=4,5
    4-1/2=3,5
    4*1/2=2
    4/1/2=2
    4/(1/2)=8
    4/-(1/2) => Division by zero

        It ain't easy to get rid of this bug. Every code I tried
        had side-effects, which created further wrong error-
        messages. I assume this behaviour is by design, that
        means it is up to the user to avoid this bug :-). The
        workaround is:

    4/(-(1/2))=8  -> great eh!

        Wanna see another great one?... here we go..

    4/-2        => Division by zero... Workaround is...
    4/(-2)=-2

        An now we go for a real nasty one:
    4*-2=-2

        Workaround is:
    4*(-2)=-8

    The trick is: All these Workarounds pop a new instance on
    stack when they open a bracket. This is what eval's algorithm
    should do if it finds something like "--" ,"+-", "/-" or "*-".

    I'm sure some of you will find a solution on this.
    For support leave a message in the VB/DOS-Section of CompuServe's
    BASLAN - Forum or e-mail me.
    

(c) 1997 by Peter Benwar-Wagner, e-mail: benwar@compuserve.com
                          CompuServe ID: 100331,1303




