home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
DP Tool Club 24
/
CD_ASCQ_24_0995.iso
/
vrac
/
homonlib.zip
/
EVALUATE.BAS
< prev
next >
Wrap
BASIC Source File
|
1995-04-13
|
13KB
|
302 lines
DEFINT A-Z
' $INCLUDE: 'TRUEFALS.INC'
DECLARE FUNCTION Evaluate$ (formula$)
DECLARE FUNCTION EvalNum (n$) 'Used only by Evaluate$()
'Error handling:
DIM SHARED EvalCode
EvalHandler:
EvalCode = ERR
RESUME NEXT
FUNCTION EvalNum (n$) STATIC
'****************************************************************************
'This is a custom IsNum() just for Evaluate$(). Because the n$ argument will
' always be only 1 character in length, it can be simplified. Also,
' Evaluate() considers a decimal point numeric, unlike IsNum().
'****************************************************************************
IF INSTR("0123456789.", n$) THEN
EvalNum = TRUE
ELSE
EvalNum = FALSE
END IF
END FUNCTION
FUNCTION Evaluate$ (formula$)
'****************************************************************************
'This is a special function. It evaluates a "formula" and returns a string
' of the value. If an error is found within the formula (or Evaluate$ is
' just unable to handle it), Evaluate$ will return a string with a leading
' asterisk followed by a description of the error. The best way to see what
' it does is just to experiment. By no means am I sure that this function is
' completely bulletproof, but it will stand up to most expressions whose
' value doesn't exceed a few trillion. This function is a good example of
' recursion if you are interested.
'
'Example: formula$ = "10*4-(36/3)"
' newval$ = Evaluate$(formula$)
' IF left$(newval$,1)="*" then
' PRINT "An error occurred!"
' PRINT newval$ '(Error description)
' ELSE
' PRINT "The value of ";formula$;" is:"; VAL(newval$)
' END IF
'
'Note: MUST be compiled with the /X switch.
'
'****************************************************************************
' *** Preliminary Error Checking ***
f$ = formula$ 'Use a temp var for the formula.
x$ = " " 'A little optimizer.
DO WHILE INSTR(f$, x$) > 0 'Remove any spaces from it.
x = INSTR(f$, x$) '(See function Squeeze$())
y$ = LEFT$(f$, x - 1)
z$ = MID$(f$, x + 1)
f$ = y$ + z$
LOOP
IF f$ = "" THEN 'Evaluate a null string as zero.
Evaluate$ = "0"
EXIT FUNCTION
END IF
DO WHILE LEFT$(f$, 2) = "--" 'Check for leading double-minuses and
f$ = RIGHT$(f$, LEN(f$) - 2) 'remove them (because -- = +).
LOOP
DO WHILE LEFT$(f$, 1) = "+" 'Check for leading positive signs and
f$ = RIGHT$(f$, LEN(f$) - 1) 'remove them.
LOOP
y$ = "": z$ = ""
FOR x = 1 TO LEN(f$) 'Make sure the formula
x$ = MID$(f$, x, 1) 'contains only valid
SELECT CASE ASC(x$) 'characters by checking
CASE 48 TO 57 'each one's ASCII code.
'OK - 0123456789
CASE 45
'OK - subtraction/negation symbol: - 'Not allowed more
IF x$ = y$ AND z = 1 THEN EvalCode = 2 'than 2 in a row
IF x = LEN(f$) THEN EvalCode = 2 'or last.
CASE 40, 41
'OK - left & right parentheses: () 'Must have some-
IF x$ = ")" AND y$ = "(" THEN EvalCode = 3 'thing between!
CASE 46
'OK - decimal point: . 'Not allowed to have
IF x$ = y$ THEN EvalCode = 2 'two adjacent decimals
IF x = LEN(f$) THEN EvalCode = 2 'or in last position.
CASE 43
'OK - plus: + 'Not allowed adjacent
IF y = 1 THEN EvalCode = 2 'to another operator
IF x = LEN(f$) THEN EvalCode = 2 'or in last position.
CASE 37, 42, 43, 47, 92, 94
'OK - operators: % * / \ ^ 'Not allowed in first
IF x = 1 THEN EvalCode = 2 'position, adjacent to
IF y > 0 THEN EvalCode = 2 'another operator, or
IF x = LEN(f$) THEN EvalCode = 2 'in last position.
CASE ELSE
'NOT OK - is some other character!
EvalCode = 1
END SELECT
IF EvalCode > 0 THEN GOTO EvalErrorExit
z$ = y$ 'Record the two previous
z = y 'characters and whether
y$ = x$ 'they were an operator or
SELECT CASE ASC(y$) 'a left parentheses.
CASE 40 'Left parentheses
y = 2
CASE 45, 37, 42, 43, 47, 92, 94
y = 1 'An operator
CASE ELSE
y = 0 'Something else
END SELECT
NEXT x
y$ = "": y = 0: z = 0
FOR x = 1 TO LEN(f$) 'Check for mismatched
x$ = MID$(f$, x, 1) ' parentheses: unequal
IF x$ = "(" THEN ' numbers of each or
y = y + 1 ' ending with a left
y$ = x$ ' parentheses.
ELSEIF x$ = ")" THEN
z = z + 1
y$ = x$
END IF
NEXT x
IF y <> z OR y$ = "(" THEN EvalCode = 3: GOTO EvalErrorExit
' *** Evaluate between parentheses first ***
DO
start = 0
FOR x = 1 TO LEN(f$)
x$ = MID$(f$, x, 1)
IF x$ = "(" THEN 'Find a complete pair.
start = x
ELSEIF x$ = ")" THEN
IF start = 0 THEN 'Not allowed to have a )
EvalCode = 3 'without a ( !
GOTO EvalErrorExit
END IF
y = x - start - 1 'Extract the expression
mf$ = MID$(f$, start + 1, y) 'between the parentheses
lf$ = LEFT$(f$, start - 1) 'and recurse the function
rf$ = RIGHT$(f$, LEN(f$) - x) 'to get its value. Then
mf$ = Evaluate$(mf$) '(assuming no errors) put
IF LEFT$(mf$, 1) = "*" THEN 'the formula back together,
Evaluate$ = mf$ 'replacing the parentheses
EXIT FUNCTION 'with the value of the
END IF 'expression.
f$ = lf$ + mf$ + rf$
EXIT FOR 'Start at the beginning
END IF ' of the formula again.
NEXT x
LOOP UNTIL start = 0 'Loop until no more parentheses are found.
' *** Evaluate the rest of the formula ***
FOR pass = 1 TO 4 'Make four passes through the
SELECT CASE pass ' formula, performing calculations
CASE 1 ' in order of operator precedence.
op1$ = "^"
op2$ = "^" 'Exponentiation only first
CASE 2
op1$ = "*" 'Multiplication & Division second
op2$ = "/"
CASE 3
op1$ = "\" 'Integer and Modulus Division third
op2$ = "%"
CASE 4
op1$ = "+" 'Addition and Subtraction last
op2$ = "-"
END SELECT
DO
op = 0
FOR x = 1 TO LEN(f$) 'Search for desired operators.
x$ = MID$(f$, x, 1)
IF x$ = op1$ OR x$ = op2$ AND x > 1 THEN 'Beware of the
op = x ' leading minus!
GOSUB EvalCalcs 'Found one! Do the math and
EXIT FOR 'start from the beginning again.
END IF
NEXT x
LOOP UNTIL op = 0 'Go through the formula until none of
NEXT pass ' the specified operators are found.
Evaluate$ = f$ 'Return the boiled down formula.
EXIT FUNCTION
' *** The following section is where the values on either side of ***
' *** the operator are parsed out and the actual math occurs. ***
EvalCalcs:
operator$ = MID$(f$, op, 1) 'Pull the operator.
v1$ = "": lf = 1 'Pull the first value:
FOR y = (op - 1) TO 1 STEP -1 'Look to the left of the operator
y$ = MID$(f$, y, 1) ' one char at a time until the next
IF EvalNum(y$) THEN ' operator (or beginning) is found.
v1$ = y$ + v1$ 'Add the numeric character to the
lf = y ' first value and record position.
ELSEIF y$ <> "-" THEN
EXIT FOR 'Found a non-minus operator - stop.
ELSEIF y$ = "-" THEN
IF y = 1 THEN
v1$ = y$ + v1$ 'Leading minus in first position.
lf = 1 'Add it, record position and stop.
EXIT FOR
ELSEIF EvalNum(MID$(f$, y - 1, 1)) THEN
EXIT FOR 'Next char is a number - stop.
END IF '(We were checking for double negs.)
END IF
NEXT y
'Pull the second value:
v2$ = MID$(f$, op + 1, 1) 'Take the very next character in case
rf = op + 1 ' it is a leading minus sign
FOR y = (op + 2) TO LEN(f$) 'Look to the right of the operator
y$ = MID$(f$, y, 1) ' one char at a time until the next
IF EvalNum(y$) THEN ' operator (or the end) is found.
v2$ = v2$ + y$ 'Add the numeric character to the
rf = y 'second value and record position.
ELSE
EXIT FOR 'Next operator found - stop looking.
END IF
NEXT y
ecode = 0 'Prepare to trap any math errors.
ON ERROR GOTO EvalHandler
v1# = VAL(v1$) 'Convert the strings into double-
v2# = VAL(v2$) ' precision values.
SELECT CASE operator$ 'Perform the actual math depending on
CASE "+" ' the operator.
v# = v1# + v2#
CASE "-"
v# = v1# - v2#
CASE "*"
v# = v1# * v2#
CASE "/"
v# = v1# / v2#
CASE "\"
v# = v1# \ v2#
CASE "%"
v# = v1# MOD v2#
CASE "^"
v# = v1# ^ v2#
END SELECT
ON ERROR GOTO 0 'Disable error trapping.
IF EvalCode > 0 THEN 'Exit if any errors occurred.
GOTO EvalErrorExit
END IF
mf$ = LTRIM$(STR$(v#)) 'Turn the result back into a string
IF INSTR(mf$, "D") > 0 THEN 'Make sure value has not been
EvalCode = 6 ' converted into scientific notation
GOTO EvalErrorExit ' by QuickBasic's math routines
END IF ' becuase VAL() cant handle it (and I
' don't care to deal with it just
' yet!).
lf$ = LEFT$(f$, lf - 1) 'Pull the strings from around the
rf$ = RIGHT$(f$, LEN(f$) - rf) ' calculation and put them back
f$ = lf$ + mf$ + rf$ ' together, replacing the calculation
' with its value.
RETURN
' *** In case of an error, the error code is translated into a ***
' *** meaningful phrase and the function returns the message. ***
EvalErrorExit:
x$ = "* ERROR" + STR$(EvalCode) + " * "
SELECT CASE EvalCode
CASE 1
x$ = x$ + "Invalid character in position" + STR$(x) + ": " + f$
CASE 2
x$ = x$ + "Invalid placement of operator in position" + STR$(x) + ": " + f$
CASE 3
x$ = x$ + "Formula contains mismatched parentheses."
CASE 6
x$ = x$ + "Overflow - values too large or too small"
CASE 11
x$ = x$ + "Division by zero"
CASE ELSE
x$ = x$ + "Unexpected error"
END SELECT
Evaluate$ = x$ 'Return a string beginning with * so the
EXIT FUNCTION ' user can easily determine if an error
' occurred. i.e.: IF LEFT$(r$,1)="*"...
END FUNCTION