'Numeric/String Eval function, supports all QBasic operators, as well as
' selected numeric and string functions, and numeric and string variables and
' assignment.
'
'String functions:
'chr$ date$ hex$ lcase$ left$ ltrim$ mid$ right$ rtrim$ space$ str$ string$ time$
'trim$ ucase$
'
'Numeric functions:
'abs atan cos exp fix int len log rnd sgn sin sqr tan val
'
'Simple to add additional single parameter functions
'Just a little busy work to add multiple parameter functions
'
'Compiles with QB64 and FreeBasic (-lang qb).
'
'Precedence - highest to lowest:
' ^
' unary -, +
' *, /
' \
' mod
' +, -
' =, <>, <, >, <=, >=
' unary not
' and
' or
' xor
' eqv
' imp
'
'Example usage:
'
'a$ = "abs(1+2*3+(5-1) + sin(42))"
'print eval$(a$)
'
'To Create variables:
'
'a$ = "num1 = 42"
'print eval$(a$)
'
'a$ = "st1$ = " + CHR$(34) + "hello" + CHR$(34)
'print eval$(a$)
'
'Written by Ed Davis. Contact: ed_davis2 at that yahoo place.
'Use at your own risk.
' required for FreeBasic, not for QB64
' code for running unit tests
i = 0
i = i + 1
n = i
' some test variables
call store_string_var
("s1$", "first") call store_string_var
("s2$", "last")
call store_numeric_var
("n1", 42) call store_numeric_var
("n2", 86)
const rightassoc
=0, leftassoc
=1 const tyunknown
=0,tystring
=1,tynum
=2
' used by expression parser
' variable store
print "Eval function, supports string and numeric variables and assignment" print "All QBasic operators are supported" print "String functions:" print "chr$ date$ hex$ lcase$ left$ ltrim$ mid$ right$ rtrim$ space$ str$ string$ time$" print "Numeric functions:" print "abs atan cos exp fix int len log rnd sgn sin sqr tan val" print "cls -- clear the screen" print "help -- this screen" print "list -- list assigned variables"
print "Variables: "; var_names_ndx
for i
= 1 to var_names_ndx
print var_names
(i
).vname
, " index: "; var_names
(i
).index;
print string_store
(var_names
(i
).index
) print numeric_store
(var_names
(i
).index
)
print "String store: "; str_store_ndx
for i
= 1 to str_store_ndx
print "Numeric store: "; num_store_ndx
for i
= 1 to num_store_ndx
print i
, numeric_store
(i
)
' get the value of a string variable
for i
= 1 to var_names_ndx
if var_names
(i
).vname
= vname
then get_string_var$ = string_store(var_names(i).index)
get_string_var$ = ""
' get the value of a numeric variable
for i
= 1 to var_names_ndx
if var_names
(i
).vname
= vname
then get_numeric_var# = numeric_store(var_names(i).index)
'print "get_numeric_var vname at: "; i; " data at: "; var_names(i).index; " data: "; numeric_store(var_names(i).index)
get_numeric_var# = 0
' Create or update a string variable
for i
= 1 to var_names_ndx
if var_names
(i
).vname
= vname
then string_store(var_names(i).index) = svalue
str_store_ndx = str_store_ndx + 1
var_names_ndx = var_names_ndx + 1
var_names(var_names_ndx).vname = vname
var_names(var_names_ndx).index = str_store_ndx
string_store(str_store_ndx) = svalue
' Create or update a numeric variable
for i
= 1 to var_names_ndx
if var_names
(i
).vname
= vname
then numeric_store(var_names(i).index) = nvalue
num_store_ndx = num_store_ndx + 1
var_names_ndx = var_names_ndx + 1
var_names(var_names_ndx).vname = vname
var_names(var_names_ndx).index = num_store_ndx
numeric_store(num_store_ndx) = nvalue
'print "store_numeric_var value: "; nvalue; " stored at: "; num_store_ndx
' lexical analyzer functions
isnumeric%
= isdigit
(ch
) or left$(ch
, 1) = "."
is_str_var%
= isalpha%
(s
) and right$(s
, 1) = "$"
sym
= sym
+ left$(userstr
, 1)
sym = ""
takechar userstr, sym
case "%", "(", ")", "*", "+", ",", "-", "/", "=", "\", "^" 'all set takechar userstr, sym
takechar userstr, sym
takechar userstr, sym
takechar userstr, sym
takechar userstr, sym
takechar userstr, sym
while isalpha%
(userstr
) or isdigit%
(userstr
) or left$(userstr
, 1) = "_" takechar userstr, sym
takechar userstr, sym
' literal strings keep the double quotes at begin/end
takechar userstr, sym
takechar userstr, sym
print "unrecognized character:", sym
sym = ""
' parser starts here
accept& = 0
if sym
= s
then accept&
= -1: nextsym userstr
, sym
if not accept&
(s
, userstr
, sym
) then print "expecting "; s;
" but found "; sym
case "+", "-": unaryprec%
= 13 case "not": unaryprec%
= 6 case else: unaryprec%
= 0 ' not a unary operator
case "^": binaryprec%
= 14 case "*", "/": binaryprec%
= 12 case "\" : binaryprec%
= 11 case "mod": binaryprec%
= 10 case "+", "-": binaryprec%
= 9 case "=", "<>", "<", ">", "<=", ">=": binaryprec%
= 7 case "and": binaryprec%
= 5 case "or": binaryprec%
= 4 case "xor": binaryprec%
= 3 case "eqv": binaryprec%
= 2 case "imp": binaryprec%
= 1 case else: binaryprec%
= 0 ' not a binary operator
' all QBasic operators are left associative
associativity% = leftassoc
' parse a parenthesized one argument numeric expression
getvalue# = 1
nextsym userstr, sym ' skip fun
expect "(", userstr, sym
getvalue# = numeric_expr#(0, userstr, sym)
expect ")", userstr, sym
' parse a parenthesized one argument string expression
getstring$ = ""
nextsym userstr, sym ' skip fun
expect "(", userstr, sym
getstring$ = str_expr$(userstr, sym)
expect ")", userstr, sym
' Handle string operands - functions, variables, literal strings
case "chr$": n
= getvalue#
(userstr
, sym
): strfactor$
= chr$(n
) case "date$": nextsym userstr
, sym: strfactor$
= date$ case "hex$": n
= getvalue#
(userstr
, sym
): strfactor$
= hex$(n
) case "lcase$": s
= getstring$
(userstr
, sym
): strfactor$
= lcase$(s
) nextsym userstr, sym
expect "(", userstr, sym
s = str_expr$(userstr, sym)
expect ",", userstr, sym
x = numeric_expr#(0, userstr, sym)
expect ")", userstr, sym
case "ltrim$": s
= getstring$
(userstr
, sym
): strfactor$
= ltrim$(s
) nextsym userstr, sym
expect "(", userstr, sym
s = str_expr$(userstr, sym)
expect ",", userstr, sym
x = numeric_expr#(0, userstr, sym)
if accept&
(",", userstr
, sym
) then y = numeric_expr#(0, userstr, sym)
strfactor$
= mid$(s
, x
, y
) expect ")", userstr, sym
nextsym userstr, sym
expect "(", userstr, sym
s = str_expr$(userstr, sym)
expect ",", userstr, sym
x = numeric_expr#(0, userstr, sym)
expect ")", userstr, sym
case "rtrim$": s
= getstring$
(userstr
, sym
): strfactor$
= rtrim$(s
) nextsym userstr, sym
expect "(", userstr, sym
strfactor$
= space$(numeric_expr#
(0, userstr
, sym
)) expect ")", userstr, sym
case "str$": n
= getvalue#
(userstr
, sym
): strfactor$
= str$(n
) nextsym userstr, sym ' string$(n [, strexpr])
expect "(", userstr, sym
x = numeric_expr#(0, userstr, sym)
expect ",", userstr, sym
if is_str_lit%
(sym
) or is_str_var%
(sym
) then strfactor$
= string$(x
, str_expr$
(userstr
, sym
)) strfactor$
= string$(x
, numeric_expr#
(0, userstr
, sym
)) expect ")", userstr, sym
case "time$": nextsym userstr
, sym: strfactor$
= time$ case "ucase$": s
= getstring$
(userstr
, sym
): strfactor$
= ucase$(s
)
strfactor$
= mid$(sym
, 2, len(sym
) - 2) nextsym userstr, sym
strfactor$ = get_string_var(sym)
nextsym userstr, sym
print "In strfactor, expecting an operand, found: "; sym
' handle numeric operands - numbers, variables, functions and unary operators
primary# = 0 'prepare for errors
prec = unaryprec%(sym)
op = sym
nextsym userstr, sym
case "-": primary#
= -numeric_expr#
(prec
, userstr
, sym
) case "+": primary#
= numeric_expr#
(prec
, userstr
, sym
) case "not": primary#
= not numeric_expr#
(prec
, userstr
, sym
) nextsym userstr, sym
case "abs": n
= getvalue#
(userstr
, sym
): primary#
= abs(n
) case "atan": n
= getvalue#
(userstr
, sym
): primary#
= atn(n
) case "cos": n
= getvalue#
(userstr
, sym
): primary#
= cos(n
) case "exp": n
= getvalue#
(userstr
, sym
): primary#
= exp(n
) case "fix": n
= getvalue#
(userstr
, sym
): primary#
= fix(n
) case "int": n
= getvalue#
(userstr
, sym
): primary#
= int(n
) case "len": s
= getstring$
(userstr
, sym
): primary#
= len(s
) case "log": n
= getvalue#
(userstr
, sym
): primary#
= log(n
) if peekch$
(userstr
) = "(" then n
= getvalue#
(userstr
, sym
): primary#
= rnd(n
) nextsym userstr, sym
case "sgn": n
= getvalue#
(userstr
, sym
): primary#
= sgn(n
) case "sin": n
= getvalue#
(userstr
, sym
): primary#
= sin(n
) case "sqr": n
= getvalue#
(userstr
, sym
): primary#
= sqr(n
) case "tan": n
= getvalue#
(userstr
, sym
): primary#
= tan(n
) case "val": s
= getstring$
(userstr
, sym
): primary#
= val(s
) primary# = get_numeric_var(sym)
nextsym userstr, sym
print "syntax error: expecting a primary, found:", sym
str_stk_ndx = str_stk_ndx + 1
str_stack(str_stk_ndx) = s
num_stk_ndx = num_stk_ndx + 1
num_stack(num_stk_ndx) = n
pop_str = str_stack(str_stk_ndx)
str_stk_ndx = str_stk_ndx - 1
pop_num = num_stack(num_stk_ndx)
num_stk_ndx = num_stk_ndx - 1
' evaluate binary string operators, operands on the stack
val_type = tynum ' most operators give a numeric result
s2 = pop_str$
s = pop_str$
case "+": s
= s
+ s2: val_type
= tystring
print "evalstrexpr: expecting a string operator, found: "; op
push_num(n)
evalstrexpr& = tynum
push_str(s)
evalstrexpr& = tystring
' evaluate binary numeric operators, operands on the stack
n2 = pop_num#
n = pop_num#
push_num(n)
evalnumericexpr& = tynum
' main expression parsing routine
' we need to decide which primary to call - numeric or string
' leading parens don't tell us which primary, so just do recursive call
if accept&
("(", userstr
, sym
) then left_type = any_expr&(0, userstr, sym)
expect ")", userstr, sym
push_str(strfactor$(userstr, sym))
left_type = tystring
push_num(primary#(userstr, sym))
left_type = tynum
print "In expr, unexpected end-of-input found: " print "In expr, unexpected symbol found: "; sym
do ' while binary operator and precedence of sym >= p prec = binaryprec%(sym)
op = sym
nextsym userstr, sym
case rightassoc : q
= binaryprec%
(op
) case leftassoc : q
= binaryprec%
(op
) + 1
right_type = any_expr&(q, userstr, sym)
if left_type
= tystring
and right_type
= tystring
then left_type = evalstrexpr&(op)
left_type = evalnumericexpr&(op)
print "type missmatch in expr - left_type:"; left_type;
" right_type:"; right_type
any_expr& = left_type
if any_expr&
(p
, userstr
, sym
) = tynum
then numeric_expr# = pop_num#
print "numeric expression expected" numeric_expr# = 0
if any_expr&
(0, userstr
, sym
) = tystring
then str_expr$ = pop_str$
print "string expression expected" str_expr$ = ""
eval$ = ""
' reset the stacks used during expression parsing
str_stk_ndx = 0: num_stk_ndx = 0
nextsym userstr, sym
' does it look like an assignment? "var = expression"
save_sym = ""
if peekch$
(userstr
) = "=" then save_sym = sym ' save the left side variable
nextsym userstr, sym ' skip the variable
nextsym userstr, sym ' skip "="
' evalualte the expression
val_type = any_expr&(0, userstr, sym)
print "error: extra symbols found: "; sym; userstr
sresult = pop_str
eval$ = sresult
nresult = pop_num
' was it an assignment?
if is_str_var%
(save_sym
) and val_type
= tystring
then call store_string_var
(save_sym
, sresult
) call store_numeric_var
(save_sym
, nresult
) print "Type mismatch in assignment"