8. Math constants and functions

Of course you want the expression parser to be able to process mathematical functions like Sqrt, Sin, Cos, Exp, etc. In this chapter we will add this feature, and also the option to implement mathematical constants like Pi and e. Because of the structure of the parser we have created so far, it's rather easy to implement support for constants and functions, as you will see.

Mathematical constants
Currently the Eval function does follow the following steps: First search in the expression for an operator with the lowest precedence, outside parentheses If this failed, then try if the expression is a value.
To add support for mathematical constants, we can add a small routine at the end of the function Eval. After checking if an expression is a value, we need to check if the expression is a constant.

'Check if the expression is a value. If so, return the value
IF isValue(expr) THEN RETURN VAL(expr)

'Check if the expression is equal to a constant, for example "pi" or "e".
'If so, return the value of this constant
SELECT UCASE$(expr)
    CASE "PI"   :RETURN 3.141592654
    CASE "E"    :RETURN 2.718281828
ENDSELECT

Mathematical functions
Mathematical functions in the expression will look like "Sin(0.25*pi)". You see first the function name, then a parentheses open "(", then an expression and the line ends with a parentheses close ")". A special case is when the function name is empty: then we just have an expression enclosed by parentheses. So far we already check for an expression enclosed by parentheses, almost at the end of the function Eval. We will replace this part of the code.

What we will do is the following: Check if there is a parentheses open "(" somewhere in the expression, and check if the expression ends with a parentheses close ")". Then check if the part left from the parentheses open is a function name. If so, execute the part inside the parentheses and execute the found function.

Check for functions
'check if the expression is a function, like "Sin(2+3)"
pos = INSTR(expr,"(")
IF pos>0 AND RIGHT$(expr,1)=")"
    'extract the function name, for example "Sin"
    function = LTRIM$(LEFT$(expr, pos-1))
    
    'extract the expression inside the parentheses, "2+3"
    'evaluate this expression
    part2 = MID$(expr, pos+1, LEN(expr)-pos-1)
    value2 = Eval(part2, msg)
    
    'evaluate the function
    SELECT UCASE$(function)
        'if function="", then the expression is just enclosed
        'with brackets, like "(2+3*4)"
        CASE ""     :RETURN value2
            
        'arithmetic functions
        CASE "ABS"  :RETURN ABS(value2)
        CASE "SQRT" :RETURN SQRT(value2)
        CASE "LOG"  :RETURN LOG(value2)
        CASE "LOG10":RETURN LOG10(value2)
        CASE "EXP"  :RETURN EXP(value2)
        CASE "INT"  :RETURN INT(value2)
        CASE "CEIL" :RETURN CEIL(value2)
        CASE "FLOOR":RETURN FLOOR(value2)

        'Trigonometric functions
        CASE "SIN"  :RETURN SIN(value2)
        CASE "COS"  :RETURN COS(value2)
        CASE "TAN"  :RETURN TAN(value2)
        CASE "ASIN" :RETURN ASIN(value2)
        CASE "ACOS" :RETURN ACOS(value2)
        CASE "ATAN" :RETURN ATAN(value2)
            
        'Hyperbolic functions
        CASE "SINH" :RETURN SINH(value2)
        CASE "COSH" :RETURN COSH(value2)
        CASE "TANH" :RETURN TANH(value2)
            
        'Unknown function
        DEFAULT     :errSet(msg, MSG_DOMAINERROR, "Error: Unknown function \"" + function + "\"")
    ENDSELECT
ENDIF

The operator E
Now there is one tricky thing. it is possible that the character "E" occurs in these newly introduced constants and functions. For example in the constant "E" and in the function "Exp". This implies that when the character E is found in an expression, this not per definition is the operator E. Therefore we need to add some extra code in the function isOperator that checks if a found E is an operator or not.

'check if the E at position pos is an operator
IF op="E" OR op="e"
    'If the E is at first or last postition in the expr, then it
    'is no operator but the variable E. If left or right from E
    'is no number or point, then it is no operator but the variable E
    IF n=1 THEN RETURN FALSE
    IF n=LEN(expr) THEN RETURN FALSE
    IF INSTR("1234567890.", MID$(expr,n-1,1))=0 THEN RETURN FALSE
    IF INSTR("1234567890.-+", MID$(expr,n+1,1))=0 THEN RETURN FALSE
    RETURN TRUE
ENDIF

Example 5
The resulting code for the parser looks as follows. This is a fully functional expression parser that includes operators, constants and functions. With only 360 lines of code.

Example 5
/*
Recursive expression parser, Example 5
Written with IBasic Professional
By Jos de Jong, june 2006

Supports expressions with:
Operators: + - * / ^ E and parentheses ( )
Constants: Pi, e
Functions: Abs, Sqrt, Log, Log10, Exp, Int, Ceil, Floor,
           Sin, Cos, Tan, Acos, Atan, Sinh, Cosh, Tanh
Error handling is included.
*/
AUTODEFINE "Off"

TYPE MESSAGE        'this type can store an error message
    INT t           'type of the error
    STRING desc     'description of the error
ENDTYPE

'the following error types are available for error messages
CONST MSG_EMPTY = 0
CONST MSG_SYNTAXERROR = 1
CONST MSG_DOMAINERROR = 2
CONST MSG_INFO = 3
CONST MSG_MEMERROR = 4
CONST MSG_SYSTEMERROR = 5
CONST MSG_OVERFLOW = 6


OPENCONSOLE

'print some examples of expressions with their answers
PRINT "Expression Calculator"
PRINT ""
PRINT "Available constants: Pi, e"
PRINT "Available operators: + - * / ^ E and parentheses ( )"
PRINT "Available functions: Abs, Sqrt, Log, Log10, Exp, Int, Ceil, Floor,"
PRINT "                     Sin, Cos, Tan, Acos, Atan, Sinh, Cosh, Tanh"
PRINT ""
PRINT "Enter an expression and press Enter to calculate it."
PRINT "Enter an empty expression to quit"
PRINT ""
PRINT "Examples:"
PRINT "2 + 3 * -4"
PRINT "    ", evalLine("2 + 3 * -4")
PRINT
PRINT "2 + 3 / 4 - 5"
PRINT "    ", evalLine("2 + 3 / 4 - 5")
PRINT
PRINT "2.5 ^ 3.25 - 5E2 / 100"
PRINT "    ", evalLine("2.5 ^ 3.25 - 5E2 / 100 ")
PRINT
PRINT "(1.5+2.5) * Sin(0.25*Pi)^2"
PRINT "    ", evalLine("(1.5+2.5) * Sin(0.25*Pi)^2")
PRINT

DEF inputexpr:STRING
DO
    'ask the user to enter an expression. After this the expression
    'will be evaluated and the answer will be printed
    INPUT ">>", inputexpr
    IF RTRIM$(inputexpr)<>""
        PRINT "    ", evalLine(inputexpr)
        PRINT ""
    ENDIF
UNTIL RTRIM$(inputexpr)=""

'end the program
CLOSECONSOLE
END

'_______________________________________________________________
SUB evalLine(expression:STRING), STRING
    'this sub evaluates the expression and returns a string that
    'can contain the answer or an (error) message. The expression can
    'have values, operators + - * / ^ E and parentheses ( )
    DOUBLE ans
    MESSAGE msg
    
    'make the error message empty (ready for use)
    errClear(msg)
    
    'evaluate the expression
    ans = eval(expression, msg)
    
    'return (error) message if not empty
    IF msg.t<>MSG_EMPTY THEN RETURN msg.desc
    
    'if no error occured, return the answer
    RETURN "Ans = " + USING("#.#####", ans)
ENDSUB

'_____________________________________________________________________
SUB Eval(Expression:STRING, msg:MESSAGE), DOUBLE
    'evaluate the given expression.
    'The evaluation is done in the following steps:
    '1. Search for an operator with the lowest precedence, outside parentheses
    '2. Try if the expression starts with a function, like "Sin(0.5*pi)"
    '3. Try if the expression is a value
    '4. Try if the expression is a variable
    DEF pos, n:INT
    DEF expr:STRING
    DEF part1, part2:STRING
    DEF value1, value2:DOUBLE
    DEF operator:STRING
    DEF function:STRING
    
    'remove spaces at start and end of the expression
    expr = LTRIM$(RTRIM$(Expression))
    
    FOR n=1 TO 6
        'choose an operator
        '(for each loop choose the following of the five operators)
        operator = MID$("-+/*^E", n, 1)
        
        'search for the last occurance of this operator outside
        'parentheses
        pos = InstrP(UCASE$(expr), operator, 255)
        WHILE pos>0
            'Check if the found operator realy is an operator
            '(and not a unary plus or minus)
            IF isOperator(expr, operator, pos)=TRUE
                'there is an operator found at position pos. Split up
                'the expression in two parts and calculate the result
                'of that two parts. Then calculate the result for the
                'complete expression.
                part1 = LEFT$(expr, pos-1)
                part2 = RIGHT$(expr, LEN(expr)-pos)
                
                'If part1 and part2 are empty fill in an error message and return
                IF RTRIM$(part1)=""
                    errSet(msg, MSG_SYNTAXERROR, "Error: Missing value before operator " + operator)
                    RETURN 0
                ENDIF
                IF RTRIM$(part2)=""
                    errSet(msg, MSG_SYNTAXERROR, "Error: Missing value after operator " + operator)
                    RETURN 0
                ENDIF
                
                'calculate the left and the right part
                value1 = Eval(part1, msg)
                value2 = Eval(part2, msg)
                
                'now execute the operation between left and right
                'part, and return the answer
                SELECT operator
                    CASE "-"
                        RETURN value1 - value2
                    CASE "+"
                        RETURN value1 + value2
                    CASE "/"
                        'If value2 is zero, return an error message
                        IF value2=0
                            errSet(msg, MSG_DOMAINERROR, "Error: Divide by zero")
                            RETURN 0
                        ENDIF
                        RETURN value1 / value2
                    CASE "*"
                        RETURN value1 * value2
                    CASE "^"
                        RETURN value1 ^ value2
                    CASE "E"
                        RETURN value1 * 10 ^ value2
                ENDSELECT
            ENDIF
            
            'the found operator was no operator,
            'search if there is another operator before this one
            IF pos>0 THEN pos = instrP(UCASE$(expr), operator, pos-1)
        WEND
    NEXT n
    
    'check if the expression is a function, like "Sin(2+3)"
    pos = INSTR(expr,"(")
    IF pos>0 AND RIGHT$(expr,1)=")"
        'extract the function name, for example "Sin"
        function = LTRIM$(LEFT$(expr, pos-1))
        
        'extract the expression inside the parentheses, "2+3"
        'evaluate this expression
        part2 = MID$(expr, pos+1, LEN(expr)-pos-1)
        value2 = Eval(part2, msg)
        
        'evaluate the function
        SELECT UCASE$(function)
            'if function="", then the expression is just enclosed
            'with brackets, like "(2+3*4)"
            CASE ""     :RETURN value2
                
            'arithmetic functions
            CASE "ABS"  :RETURN ABS(value2)
            CASE "SQRT" :RETURN SQRT(value2)
            CASE "LOG"  :RETURN LOG(value2)
            CASE "LOG10":RETURN LOG10(value2)
            CASE "EXP"  :RETURN EXP(value2)
            CASE "INT"  :RETURN INT(value2)
            CASE "CEIL" :RETURN CEIL(value2)
            CASE "FLOOR":RETURN FLOOR(value2)
                
            'Trigonometric functions
            CASE "SIN"  :RETURN SIN(value2)
            CASE "COS"  :RETURN COS(value2)
            CASE "TAN"  :RETURN TAN(value2)
            CASE "ASIN" :RETURN ASIN(value2)
            CASE "ACOS" :RETURN ACOS(value2)
            CASE "ATAN" :RETURN ATAN(value2)
                
            'Hyperbolic functions
            CASE "SINH" :RETURN SINH(value2)
            CASE "COSH" :RETURN COSH(value2)
            CASE "TANH" :RETURN TANH(value2)
                
            'Unknown function
            DEFAULT     :errSet(msg, MSG_DOMAINERROR, "Error: Unknown function \"" + function + "\"")
        ENDSELECT
    ENDIF
    
    'The expression is not solved yet.
    'Check if the expression is a value. If so, return the value
    IF isValue(expr) THEN RETURN VAL(expr)
    
    'check if the expression is equal to a constant, for example "pi" or "e"
    'if so, return the value of this constant
    SELECT UCASE$(expr)
        CASE "PI"   :RETURN 3.141592654
        CASE "E"    :RETURN 2.718281828
    ENDSELECT
    
    'something unsupported happend. return an error message
    errSet(msg, MSG_SYNTAXERROR, "Error: Syntax error in part \"" + expr + "\"")
    RETURN 0
ENDSUB

'_____________________________________________________________________
SUB instrP(source:STRING, search:STRING, start:INT), INT
    'this sub searches for the LAST string search in the string
    'source, reverse of the function instr(). The sub returns 0 if
    'the search could not be found. Also this function neglects
    'content between parenthesis () in source
    INT n, bopen, bclose
    STRING sign
    
    n=start
    IF n>LEN(source) THEN n=LEN(source)-LEN(search)+1
    bopen=0     :'number of parenthesis open (
    bclose=0    :'number of parenthesis close )
    DO
        sign = MID$(source, n, LEN(search))
        'if search is found and outside parentheses then return n
        IF (sign=search) AND (bopen=bclose) THEN RETURN n
        IF LEFT$(sign,1)="(" THEN bopen++
        IF LEFT$(sign,1)=")" THEN bclose++
        n--
    UNTIL (n <= 0)
    
    'if the string search is not found, then return 0
    RETURN 0
ENDSUB

'_______________________________________________________________
SUB isOperator(expr:STRING, op:CHAR, n:INT), INT
    'This sub checks of the operator at postion n in expr is a
    'legal operator. For example the "+" in "2.3E+3" is no operator,
    'and the "-" in "3 * -2.5" is no operator but a unary minus
    STRING sign
    
    IF op="+"
        IF UCASE$(MID$(expr,n-1,1))="E"
            IF n>2
                IF INSTR("1234567890.", MID$(expr,n-2,1))>0
                    'for example "2.52E+4"
                    RETURN FALSE
                ENDIF
            ENDIF
        ENDIF
        RETURN TRUE
    ENDIF
    
    IF op="-"
        IF n=1
            'this is an unary minus, at the first position of the expr
            'for example "-3.4 / 6"
            RETURN FALSE
        ELSE
            'check for an unary minus (for example 2*-3  or  2.5E-6)
            'check the characters before the minus
            sign = LEFT$(expr,n-1)
            sign = RIGHT$(RTRIM$(sign),1)
            IF INSTR("+-/*^", sign)>0
                'For example "3.5 * -2"
                RETURN FALSE
            ENDIF
            IF UCASE$(MID$(expr,n-1,1))="E" AND n>2
                IF INSTR("1234567890.", MID$(expr,n-2,1))>0
                    'for example "2.3E-4"
                    RETURN FALSE
                ENDIF
            ENDIF
        ENDIF
        RETURN TRUE
    ENDIF
    
    'check if the E at position pos is an operator
    IF op="E" OR op="e"
        'if the E is at first or last postition in the expr, then it is no operator but the variable E
        'if left or right from E is no number or point, then it is no operator but the variable E
        IF n=1 THEN RETURN FALSE
        IF n=LEN(expr) THEN RETURN FALSE
        IF INSTR("1234567890.", MID$(expr,n-1,1))=0 THEN RETURN FALSE
        IF INSTR("1234567890.-+", MID$(expr,n+1,1))=0 THEN RETURN FALSE
        RETURN TRUE
    ENDIF
    
    'no problems found, the operator is indeed an operator.
    RETURN TRUE
ENDSUB

'_______________________________________________________________
SUB isValue(value:STRING), INT
    'This sub checks if value is a legal value. if so, returns True.
    'If not, returns False
    INT i
    STRING sign
    
    FOR i=1 TO LEN(value)
        'check each character one by one
        sign = UCASE$(MID$(value,i,1))
        
        'check for legal signs in the string
        IF INSTR("1234567890.-", sign)=0 THEN RETURN FALSE
        
        'check if there is max. 1 point in the string
        IF sign="." THEN IF INSTR(value,".",i+1)>0 THEN RETURN FALSE
        
        'check for correct use of minus: only at position 1
        IF sign="-" THEN IF i<>1 THEN RETURN FALSE
    NEXT i
    
    RETURN TRUE
ENDSUB

'_______________________________________________________________
SUB errSet(msg:MESSAGE, newType:INT, newMessage:STRING)
    'if msg is empty, then fill in the new type and message.
    'don't overwrite an existing message
    IF msg.t = MSG_EMPTY
        msg.t = newType
        msg.desc = newMessage
    ENDIF
    RETURN
ENDSUB

'_______________________________________________________________
SUB errClear(msg:MESSAGE)
    'clear the given message
    msg.t = MSG_EMPTY
    msg.desc = ""
    RETURN
ENDSUB



Copyright 2006 SpeQ Mathematics