Author Topic: Numeric/String Eval function  (Read 6097 times)

0 Members and 1 Guest are viewing this topic.

Offline Ed Davis

  • Newbie
  • Posts: 40
    • View Profile
Numeric/String Eval function
« on: February 25, 2021, 01:24:12 pm »
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$)


Code: QB64: [Select]
  1. 'Numeric/String Eval function, supports all QBasic operators, as well as
  2. ' selected numeric and string functions, and numeric and string variables and
  3. ' assignment.
  4. '
  5. 'String functions:
  6. 'chr$ date$ hex$ lcase$ left$ ltrim$ mid$ right$ rtrim$ space$ str$ string$ time$
  7. 'trim$ ucase$
  8. '
  9. 'Numeric functions:
  10. 'abs atan cos exp fix int len log rnd sgn sin sqr tan val
  11. '
  12. 'Simple to add additional single parameter functions
  13. 'Just a little busy work to add multiple parameter functions
  14. '
  15. 'Compiles with QB64 and FreeBasic (-lang qb).
  16. '
  17. 'Precedence - highest to lowest:
  18. ' ^
  19. ' unary -, +
  20. ' *, /
  21. ' \
  22. ' mod
  23. ' +, -
  24. ' =, <>, <, >, <=, >=
  25. ' unary not
  26. ' and
  27. ' or
  28. ' xor
  29. ' eqv
  30. ' imp
  31. '
  32. 'Example usage:
  33. '
  34. 'a$ = "abs(1+2*3+(5-1) + sin(42))"
  35. 'print eval$(a$)
  36. '
  37. 'To Create variables:
  38. '
  39. 'a$ = "num1 = 42"
  40. 'print eval$(a$)
  41. '
  42. 'a$ = "st1$ = " + CHR$(34) + "hello" + CHR$(34)
  43. 'print eval$(a$)
  44. '
  45. 'Written by Ed Davis.  Contact: ed_davis2 at that yahoo place.
  46. 'Use at your own risk.
  47.  
  48. option _explicit ' underscore in QB64; none in FreeBasic.
  49.  
  50. declare function eval$(userstr as string)
  51. declare function numeric_expr#(p as integer, userstr as string, sym as string)
  52. declare function str_expr$(userstr as string, sym as string)
  53. ' required for FreeBasic, not for QB64
  54. declare sub store_numeric_var(vname as string, nvalue as double)
  55.  
  56. dim userstr as string
  57.  
  58.  
  59. ' code for running unit tests
  60. if command$(1) = "-t" then
  61.     dim cmds(500) as string, i as integer, fname as string, n as integer
  62.     fname = ltrim$(rtrim$(mid$(command$, 4)))
  63.  
  64.     i = 0
  65.     open fname for input as 1
  66.     while not eof(1)
  67.         i = i + 1
  68.         line input #1, cmds(i)
  69.     wend
  70.     n = i
  71.     if ltrim$(cmds(n)) = "" then n = n - 1
  72.     for i = 1 to n
  73.         print eval$(cmds(i))
  74.     next
  75.     system
  76.  
  77. ' some test variables
  78. call store_string_var("s1$", "first")
  79. call store_string_var("s2$", "last")
  80.  
  81. call store_numeric_var("n1", 42)
  82. call store_numeric_var("n2", 86)
  83.  
  84. call help
  85.     line input "Enter expression: ", userstr
  86.     select case userstr
  87.         case "list":  call dump_tables
  88.         case "help":  call help
  89.         case "cls":   cls
  90.              case "": exit do
  91.         case else
  92.             print eval$(userstr)
  93.     end select
  94.  
  95. const rightassoc=0, leftassoc=1
  96. const tyunknown=0,tystring=1,tynum=2
  97.  
  98. const max_store=256
  99.  
  100. type names_t
  101.     vname    as string
  102.     index   as integer
  103.  
  104. ' used by expression parser
  105. dim shared str_stack(1 to max_store) as string
  106. dim shared num_stack(1 to max_store) as double
  107. dim shared str_stk_ndx as integer, num_stk_ndx as integer
  108.  
  109. ' variable store
  110. dim shared string_store(1 to max_store) as string
  111. dim shared numeric_store(1 to max_store) as double
  112. dim shared var_names(1 to max_store) as names_t
  113. dim shared str_store_ndx as integer, num_store_ndx as integer, var_names_ndx as integer
  114.  
  115. sub help
  116.     print
  117.     print "Eval function, supports string and numeric variables and assignment"
  118.     print "All QBasic operators are supported"
  119.     print
  120.     print "String functions:"
  121.     print "chr$ date$ hex$ lcase$ left$ ltrim$ mid$ right$ rtrim$ space$ str$ string$ time$"
  122.     print "trim$ ucase$"
  123.     print
  124.     print "Numeric functions:"
  125.     print "abs atan cos exp fix int len log rnd sgn sin sqr tan val"
  126.     print
  127.     print "cls  -- clear the screen"
  128.     print "help -- this screen"
  129.     print "list -- list assigned variables"
  130.     print
  131.  
  132. sub dump_tables
  133.     dim i as integer
  134.  
  135.     print "Variables: "; var_names_ndx
  136.     for i = 1 to var_names_ndx
  137.         print var_names(i).vname, " index: "; var_names(i).index;
  138.         if right$(var_names(i).vname, 1) = "$" then
  139.             print string_store(var_names(i).index)
  140.         else
  141.             print numeric_store(var_names(i).index)
  142.         end if
  143.     next
  144.  
  145.     print "String store: "; str_store_ndx
  146.     for i = 1 to str_store_ndx
  147.         print i, string_store(i)
  148.     next
  149.  
  150.     print "Numeric store: "; num_store_ndx
  151.     for i = 1 to num_store_ndx
  152.         print i, numeric_store(i)
  153.     next
  154.  
  155. ' get the value of a string variable
  156. function get_string_var$(vname as string)
  157.     dim i as integer
  158.  
  159.     for i = 1 to var_names_ndx
  160.         if var_names(i).vname = vname then
  161.             get_string_var$ = string_store(var_names(i).index)
  162.             exit function
  163.         end if
  164.     next
  165.     get_string_var$ = ""
  166.  
  167. ' get the value of a numeric variable
  168. function get_numeric_var#(vname as string)
  169.     dim i as integer
  170.  
  171.     for i = 1 to var_names_ndx
  172.         if var_names(i).vname = vname then
  173.             get_numeric_var# = numeric_store(var_names(i).index)
  174.             'print "get_numeric_var vname at: "; i; " data at: "; var_names(i).index; " data: "; numeric_store(var_names(i).index)
  175.             exit function
  176.         end if
  177.     next
  178.     get_numeric_var# = 0
  179.  
  180. ' Create or update a string variable
  181. sub store_string_var(vname as string, svalue as string)
  182.     dim i as integer
  183.  
  184.     for i = 1 to var_names_ndx
  185.         if var_names(i).vname = vname then
  186.             string_store(var_names(i).index) = svalue
  187.             exit sub
  188.         end if
  189.     next
  190.  
  191.     str_store_ndx = str_store_ndx + 1
  192.     var_names_ndx = var_names_ndx + 1
  193.     var_names(var_names_ndx).vname = vname
  194.     var_names(var_names_ndx).index = str_store_ndx
  195.     string_store(str_store_ndx) = svalue
  196.  
  197. ' Create or update a numeric variable
  198. sub store_numeric_var(vname as string, nvalue as double)
  199.     dim i as integer
  200.  
  201.     for i = 1 to var_names_ndx
  202.         if var_names(i).vname = vname then
  203.             numeric_store(var_names(i).index) = nvalue
  204.             exit sub
  205.         end if
  206.     next
  207.  
  208.     num_store_ndx = num_store_ndx + 1
  209.     var_names_ndx = var_names_ndx + 1
  210.     var_names(var_names_ndx).vname = vname
  211.     var_names(var_names_ndx).index = num_store_ndx
  212.     numeric_store(num_store_ndx) = nvalue
  213.     'print "store_numeric_var value: "; nvalue; " stored at: "; num_store_ndx
  214.  
  215. ' lexical analyzer functions
  216.  
  217. function isdigit%(ch as string)
  218.     isdigit% = left$(ch, 1) >= "0" and left$(ch, 1) <= "9"
  219.  
  220. function isnumeric%(ch as string)
  221.     isnumeric% = isdigit(ch) or left$(ch, 1) = "."
  222.  
  223. function isalpha%(ch as string)
  224.     isalpha% = lcase$(left$(ch, 1)) >= "a" and lcase$(left$(ch, 1)) <= "z"
  225.  
  226. function is_str_var%(s as string)
  227.     is_str_var% = isalpha%(s) and right$(s, 1) = "$"
  228.  
  229. function is_str_lit%(s as string)
  230.     is_str_lit% = left$(s, 1) = chr$(34)
  231.  
  232. sub takechar(userstr as string, sym as string)
  233.     sym = sym + left$(userstr, 1)
  234.     userstr = right$(userstr, len(userstr) - 1)
  235.  
  236. function peekch$(userstr as string)
  237.     peekch$ = left$(ltrim$(userstr), 1)
  238.  
  239. sub nextsym(userstr as string, sym as string)
  240.     sym = ""
  241.     userstr = ltrim$(userstr)
  242.     takechar userstr, sym
  243.     select case sym
  244.         case "%", "(", ")", "*", "+", ",", "-", "/", "=", "\", "^"   'all set
  245.         case "0" to "9"
  246.             while isdigit%(userstr)
  247.                 takechar userstr, sym
  248.             wend
  249.             if left$(userstr, 1) = "." then
  250.                 takechar userstr, sym
  251.  
  252.                 while isdigit%(userstr)
  253.                     takechar userstr, sym
  254.                 wend
  255.             end if
  256.         case "."
  257.             while isdigit%(userstr)
  258.                 takechar userstr, sym
  259.             wend
  260.  
  261.         case "<"
  262.             if left$(userstr, 1) = "=" or left$(userstr, 1) = ">" then
  263.                 takechar userstr, sym
  264.             end if
  265.  
  266.         case ">"
  267.             if left$(userstr, 1) = "=" then
  268.                 takechar userstr, sym
  269.             end if
  270.  
  271.         case "a" to "z", "A" to "Z"
  272.             while isalpha%(userstr) or isdigit%(userstr) or left$(userstr, 1) = "_"
  273.                 takechar userstr, sym
  274.             wend
  275.             if left$(userstr, 1) = "$" then
  276.                 takechar userstr, sym
  277.             end if
  278.             sym = lcase$(sym)
  279.         ' literal strings keep the double quotes at begin/end
  280.         case chr$(34)
  281.             while left$(userstr, 1) <> chr$(34) and userstr <> ""
  282.                 takechar userstr, sym
  283.             wend
  284.             if left$(userstr, 1) <> chr$(34) then
  285.                 print "Closing: '"; chr$(34); "' expected"
  286.             else
  287.                 takechar userstr, sym
  288.             end if
  289.  
  290.         case ""
  291.         case else
  292.             print "unrecognized character:", sym
  293.             sym = ""
  294.     end select
  295.  
  296. ' parser starts here
  297.  
  298. function accept&(s as string, userstr as string, sym as string)
  299.   accept& = 0
  300.   if sym = s then accept& = -1: nextsym userstr, sym
  301.  
  302. sub expect(s as string, userstr as string, sym as string)
  303.   if not accept&(s, userstr, sym) then print "expecting "; s; " but found "; sym
  304.  
  305. function unaryprec%(op as string)
  306.     select case op
  307.         case "+", "-": unaryprec% = 13
  308.         case "not":    unaryprec% =  6
  309.         case else:     unaryprec% =  0  ' not a unary operator
  310.     end select
  311.  
  312. function binaryprec%(op as string)
  313.     select case op
  314.         case "^":                              binaryprec% = 14
  315.         case "*", "/":                         binaryprec% = 12
  316.         case "\" :                             binaryprec% = 11
  317.         case "mod":                            binaryprec% = 10
  318.         case "+", "-":                         binaryprec% =  9
  319.         case "=", "<>", "<", ">", "<=", ">=":  binaryprec% =  7
  320.         case "and":                            binaryprec% =  5
  321.         case "or":                             binaryprec% =  4
  322.         case "xor":                            binaryprec% =  3
  323.         case "eqv":                            binaryprec% =  2
  324.         case "imp":                            binaryprec% =  1
  325.         case else:                             binaryprec% =  0 ' not a binary operator
  326.     end select
  327.  
  328. ' all QBasic operators are left associative
  329. function associativity%(op as string)
  330.     if op = op then :
  331.     associativity% = leftassoc
  332.  
  333. ' parse a parenthesized one argument numeric expression
  334. function getvalue#(userstr as string, sym as string)
  335.     getvalue# = 1
  336.     nextsym userstr, sym     ' skip fun
  337.     expect "(", userstr, sym
  338.  
  339.     getvalue# = numeric_expr#(0, userstr, sym)
  340.  
  341.     expect ")", userstr, sym
  342.  
  343. ' parse a parenthesized one argument string expression
  344. function getstring$(userstr as string, sym as string)
  345.     getstring$ = ""
  346.     nextsym userstr, sym     ' skip fun
  347.     expect "(", userstr, sym
  348.  
  349.     getstring$ = str_expr$(userstr, sym)
  350.  
  351.     expect ")", userstr, sym
  352.  
  353. ' Handle string operands - functions, variables, literal strings
  354. function strfactor$(userstr as string, sym as string)
  355.  
  356.   select case sym
  357.     case "chr$":       n = getvalue#(userstr, sym): strfactor$ = chr$(n)
  358.     case "date$":      nextsym userstr, sym: strfactor$ = date$
  359.     case "hex$":       n = getvalue#(userstr, sym): strfactor$ = hex$(n)
  360.     case "lcase$":     s = getstring$(userstr, sym): strfactor$ = lcase$(s)
  361.     case "left$"
  362.         nextsym userstr, sym
  363.         expect "(", userstr, sym
  364.         s = str_expr$(userstr, sym)
  365.         expect ",", userstr, sym
  366.         x = numeric_expr#(0, userstr, sym)
  367.         strfactor$ = left$(s, x)
  368.         expect ")", userstr, sym
  369.     case "ltrim$":     s = getstring$(userstr, sym): strfactor$ = ltrim$(s)
  370.     case "mid$"
  371.         nextsym userstr, sym
  372.         expect "(", userstr, sym
  373.         s = str_expr$(userstr, sym)
  374.         expect ",", userstr, sym
  375.         x = numeric_expr#(0, userstr, sym)
  376.         if accept&(",", userstr, sym) then
  377.           y = numeric_expr#(0, userstr, sym)
  378.           strfactor$ = mid$(s, x, y)
  379.         else
  380.           strfactor$ = mid$(s, x)
  381.         end if
  382.         expect ")", userstr, sym
  383.     case "right$"
  384.       nextsym userstr, sym
  385.       expect "(", userstr, sym
  386.       s = str_expr$(userstr, sym)
  387.       expect ",", userstr, sym
  388.       x = numeric_expr#(0, userstr, sym)
  389.       strfactor$ = right$(s, x)
  390.       expect ")", userstr, sym
  391.     case "rtrim$":     s = getstring$(userstr, sym): strfactor$ = rtrim$(s)
  392.     case "space$"
  393.       nextsym userstr, sym
  394.       expect "(", userstr, sym
  395.       strfactor$ = space$(numeric_expr#(0, userstr, sym))
  396.       expect ")", userstr, sym
  397.     case "str$":       n = getvalue#(userstr, sym): strfactor$ = str$(n)
  398.     case "string$"
  399.       nextsym userstr, sym ' string$(n [, strexpr])
  400.       expect "(", userstr, sym
  401.       x = numeric_expr#(0, userstr, sym)
  402.       expect ",", userstr, sym
  403.       if is_str_lit%(sym) or is_str_var%(sym) then
  404.         strfactor$ = string$(x, str_expr$(userstr, sym))
  405.       else
  406.         strfactor$ = string$(x, numeric_expr#(0, userstr, sym))
  407.       end if
  408.       expect ")", userstr, sym
  409.     case "time$":      nextsym userstr, sym: strfactor$ = time$
  410.     case "trim$":      s = getstring$(userstr, sym): strfactor$ = ltrim$(rtrim$(s))
  411.     case "ucase$":     s = getstring$(userstr, sym): strfactor$ = ucase$(s)
  412.  
  413.     case else
  414.       if is_str_lit%(sym) then
  415.         strfactor$ = mid$(sym, 2, len(sym) - 2)
  416.         nextsym userstr, sym
  417.       elseif is_str_var%(sym) then
  418.         strfactor$ = get_string_var(sym)
  419.         nextsym userstr, sym
  420.       else
  421.         print "In strfactor, expecting an operand, found: "; sym
  422.       end if
  423.  
  424. ' handle numeric operands - numbers, variables, functions and unary operators
  425. function primary#(userstr as string, sym as string)
  426.     dim op as string, prec as integer, n as double, s as string
  427.  
  428.     primary# = 0                    'prepare for errors
  429.     prec = unaryprec%(sym)
  430.     if prec > 0 then
  431.         op = sym
  432.         nextsym userstr, sym
  433.         select case op
  434.             case "-":   primary# =     -numeric_expr#(prec, userstr, sym)
  435.             case "+":   primary# =      numeric_expr#(prec, userstr, sym)
  436.             case "not": primary# =  not numeric_expr#(prec, userstr, sym)
  437.         end select
  438.     elseif isnumeric%(sym) then
  439.         primary# = val(sym)
  440.         nextsym userstr, sym
  441.     else
  442.         select case sym
  443.             case "abs":  n = getvalue#(userstr, sym): primary# = abs(n)
  444.             case "atan": n = getvalue#(userstr, sym): primary# = atn(n)
  445.             case "cos":  n = getvalue#(userstr, sym): primary# = cos(n)
  446.             case "exp":  n = getvalue#(userstr, sym): primary# = exp(n)
  447.             case "fix":  n = getvalue#(userstr, sym): primary# = fix(n)
  448.             case "int":  n = getvalue#(userstr, sym): primary# = int(n)
  449.             case "len":  s = getstring$(userstr, sym): primary# = len(s)
  450.             case "log":  n = getvalue#(userstr, sym): primary# = log(n)
  451.             case "rnd"
  452.                 if peekch$(userstr) = "(" then
  453.                     n = getvalue#(userstr, sym): primary# = rnd(n)
  454.                 else
  455.                     nextsym userstr, sym
  456.                     primary# = rnd
  457.                 end if
  458.             case "sgn":  n = getvalue#(userstr, sym): primary# = sgn(n)
  459.             case "sin":  n = getvalue#(userstr, sym): primary# = sin(n)
  460.             case "sqr":  n = getvalue#(userstr, sym): primary# = sqr(n)
  461.             case "tan":  n = getvalue#(userstr, sym): primary# = tan(n)
  462.             case "val":  s = getstring$(userstr, sym): primary# = val(s)
  463.             case else
  464.                 if isalpha%(sym) then
  465.                     primary# = get_numeric_var(sym)
  466.                     nextsym userstr, sym
  467.                 else
  468.                     print "syntax error: expecting a primary, found:", sym
  469.                 end if
  470.         end select
  471.     end if
  472.  
  473. sub push_str(s as string)
  474.   str_stk_ndx = str_stk_ndx + 1
  475.   str_stack(str_stk_ndx) = s
  476.  
  477. sub push_num(n as double)
  478.   num_stk_ndx = num_stk_ndx + 1
  479.   num_stack(num_stk_ndx) = n
  480.  
  481. function pop_str$
  482.   pop_str = str_stack(str_stk_ndx)
  483.   str_stk_ndx = str_stk_ndx - 1
  484.  
  485. function pop_num#
  486.   pop_num = num_stack(num_stk_ndx)
  487.   num_stk_ndx = num_stk_ndx - 1
  488.  
  489. ' evaluate binary string operators, operands on the stack
  490. function evalstrexpr&(op as string)
  491.   dim s as string, s2 as string, n as double, val_type as integer
  492.  
  493.   val_type = tynum ' most operators give a numeric result
  494.   s2 = pop_str$
  495.   s = pop_str$
  496.   select case op
  497.     case "=":   n = s = s2
  498.     case "<>":  n = s <> s2
  499.     case "<":   n = s <  s2
  500.     case ">":   n = s >  s2
  501.     case "<=":  n = s <= s2
  502.     case ">=":  n = s >= s2
  503.     case "+":   s = s +  s2: val_type = tystring
  504.     case else
  505.       print "evalstrexpr: expecting a string operator, found: "; op
  506.   if val_type = tynum then
  507.     push_num(n)
  508.     evalstrexpr& = tynum
  509.   elseif val_type = tystring then
  510.     push_str(s)
  511.     evalstrexpr& = tystring
  512.   end if
  513.  
  514. ' evaluate binary numeric operators, operands on the stack
  515. function evalnumericexpr&(op as string)
  516.   dim n as double, n2 as double
  517.  
  518.   n2 = pop_num#
  519.   n = pop_num#
  520.   select case op
  521.     case "^":   n = n ^   n2
  522.     case "*":   n = n *   n2
  523.     case "/":   if n2 = 0 then print "division by 0" else n = n /   n2
  524.     case "\":   if n2 = 0 then print "division by 0" else n = n \   n2
  525.     case "mod": if n2 = 0 then print "division by 0" else n = n mod n2
  526.     case "+":   n = n +   n2
  527.     case "-":   n = n -   n2
  528.     case "=":   n = n =   n2
  529.     case "<>":  n = n <>  n2
  530.     case "<":   n = n <   n2
  531.     case ">":   n = n >   n2
  532.     case "<=":  n = n <=  n2
  533.     case ">=":  n = n >=  n2
  534.     case "and": n = n and n2
  535.     case "or":  n = n or  n2
  536.     case "xor": n = n xor n2
  537.     case "eqv": n = n eqv n2
  538.     case "imp": n = n imp n2
  539.     case else: print "evalnumericexpr: unexpected operator: "; op
  540.   push_num(n)
  541.   evalnumericexpr& = tynum
  542.  
  543. ' main expression parsing routine
  544. function any_expr&(p as integer, userstr as string, sym as string)
  545.     dim op as string, q as integer, prec as integer
  546.     dim left_type as integer, right_type as integer
  547.  
  548.     ' we need to decide which primary to call - numeric or string
  549.     ' leading parens don't tell us which primary, so just do recursive call
  550.     if accept&("(", userstr, sym) then
  551.       left_type = any_expr&(0, userstr, sym)
  552.       expect ")", userstr, sym
  553.     elseif is_str_lit%(sym) or is_str_var%(sym) then
  554.       push_str(strfactor$(userstr, sym))
  555.       left_type = tystring
  556.     elseif isnumeric%(sym) or sym = "-" or sym = "+" or sym = "not" or isalpha%(sym) then
  557.       push_num(primary#(userstr, sym))
  558.       left_type = tynum
  559.     elseif sym = "" then
  560.       print "In expr, unexpected end-of-input found: "
  561.     else
  562.       print "In expr, unexpected symbol found: "; sym
  563.     end if
  564.  
  565.     do  ' while binary operator and precedence of sym >= p
  566.         prec = binaryprec%(sym)
  567.         if prec = 0 or prec < p then exit do
  568.         op = sym
  569.  
  570.         nextsym userstr, sym
  571.         select case associativity%(op)
  572.             case rightassoc : q = binaryprec%(op)
  573.             case leftassoc  : q = binaryprec%(op) + 1
  574.         end select
  575.  
  576.         right_type = any_expr&(q, userstr, sym)
  577.  
  578.         if left_type = tystring and right_type = tystring then
  579.             left_type = evalstrexpr&(op)
  580.         elseif left_type = tynum and right_type = tynum then
  581.             left_type = evalnumericexpr&(op)
  582.         else
  583.             print "type missmatch in expr - left_type:"; left_type; " right_type:"; right_type
  584.         end if
  585.     loop
  586.  
  587.     any_expr& = left_type
  588.  
  589. function numeric_expr#(p as integer, userstr as string, sym as string)
  590.   if any_expr&(p, userstr, sym) = tynum then
  591.     numeric_expr# = pop_num#
  592.   else
  593.     print "numeric expression expected"
  594.     numeric_expr# = 0
  595.   end if
  596.  
  597. function str_expr$(userstr as string, sym as string)
  598.   if any_expr&(0, userstr, sym) = tystring then
  599.     str_expr$ = pop_str$
  600.   else
  601.     print "string expression expected"
  602.     str_expr$ = ""
  603.   end if
  604.  
  605. function eval$(userstr as string)
  606.     dim sym as string, save_sym as string, val_type as integer, nresult as double, sresult as string
  607.  
  608.     eval$ = ""
  609.     ' reset the stacks used during expression parsing
  610.     str_stk_ndx = 0: num_stk_ndx = 0
  611.     nextsym userstr, sym
  612.  
  613.     ' does it look like an assignment?  "var = expression"
  614.     save_sym = ""
  615.     if peekch$(userstr) = "=" then
  616.         save_sym = sym      ' save the left side variable
  617.         nextsym userstr, sym ' skip the variable
  618.         nextsym userstr, sym ' skip "="
  619.     end if
  620.  
  621.     ' evalualte the expression
  622.     val_type = any_expr&(0, userstr, sym)
  623.  
  624.     if sym <> "" then
  625.         print "error: extra symbols found: "; sym; userstr
  626.     elseif val_type = tystring then
  627.         sresult = pop_str
  628.         eval$   = sresult
  629.     elseif val_type = tynum then
  630.         nresult = pop_num
  631.         eval$   = str$(nresult)
  632.     end if
  633.  
  634.     ' was it an assignment?
  635.     if save_sym <> "" then
  636.         if is_str_var%(save_sym) and val_type = tystring then
  637.             call store_string_var(save_sym, sresult)
  638.         elseif isalpha%(save_sym) and val_type = tynum then
  639.             call store_numeric_var(save_sym, nresult)
  640.         else
  641.             print "Type mismatch in assignment"
  642.         end if
  643.     end if
  644.  

Below are the passed unit tests, using a modified version of flukiluke's unit testing program:
Code: QB64: [Select]
  1. $title: Eval Numeric Tests
  2. (5-7)* 2
  3. (-5--7)*2
  4. 2*(-5-7)
  5. (-1--2)+(-3--4)
  6. x = 5
  7. x*x-2*x-3
  8. 2*-3--4+-0.25
  9. 1+2*(3+((4*5)+(6*7*8))-9)/10
  10. 1+2*(3-2*(3-2)*((2-4)*5-22/(7+2*(3-1))-1))+1
  11. 1+1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+1/15)/14)/13)/12)/11)/10)/9)/8)/7)/6)/5)/4)/3)/2
  12. (1.4 + 2^(19 mod 4))/2
  13. -5 ^ 1.9
  14. e=exp(1)
  15. e^2
  16. log(2980.958)
  17. .3+2*10^-8
  18. ((((((((3 + 2) * ((((((2)))))))))))))
  19. 4*2.5 + 8.5+1.5 / 3.0
  20. 2 -4 +6 -1 -1- 0 +8
  21. 1 -1   + 2   - 2   +  4 - 4 +    6
  22. 2*3 - 4*5 + 6/3
  23. 2*3*4/8 -   5/2*4 +  6 + 0/3
  24. (5 + 2*3 - 1 + 7 * 8)
  25. (67 + 2 * 3 - 67 + 2/1 - 7)
  26. (2) + (17*2-30) * (5)+2 - (8/2)*4
  27. (( ((2)) + 4))*((5))
  28. (5-4)*(12-11)/((((5-4)*(12-11))))
  29. 2+4+-4+-2*10 mod 9*7
  30. 1/-2
  31. ((((((-99))))))-1
  32. 1+(2-5)*3+8/(5+3)^2
  33. (1+(2-5)*3+8/(5+3)^2)/sqr(5^2+3^2)
  34. (1+(2-5)*3+8/(5+3)^2)/sqr(4^2+3^2)
  35. sqr(4^2+3^2)
  36. 4*(1/1-1/3+1/5-1/7+1/9-1/11+1/13-1/15+1/17-1/19+10/401)
  37. $expect: stdout
  38. -4
  39. 4
  40. -24
  41. 2
  42. 5
  43. 12
  44. -2.25
  45. 71
  46. 60
  47. 2.718281828458994
  48. 4.7
  49. -21.28349806301961
  50. 2.718281828459045
  51. 7.38905609893065
  52. 8.000000004347015
  53.  .30000002
  54. 10
  55. 19
  56. 10
  57. 6
  58. -12
  59. -1
  60. 66
  61. 1
  62. 8
  63. 30
  64. 1
  65. -18
  66. -.5
  67. -100
  68. -7.875
  69. -1.350551357997257
  70. -1.575
  71. 5
  72. 3.1415902423708
  73. $finish
  74.  
  75. $title: Eval String Tests
  76. "a" + "b" > "a"
  77. "abc" + "def"
  78. "abc" + chr$(36) + "def"
  79. lcase$("ABC")
  80. left$(ltrim$("   ="), 1) = "="
  81. mid$("abcdef", sqr(2^2), 6/2)
  82. mid$("abcdef", 2^2)
  83. s$ = "abc" + space$(9/3) + "def"
  84. str$(123) + str$(456)
  85. ltrim$(str$(1+2*(3+((4*5)+(6*7*8))-9)/10)) + ltrim$(str$(1+2*(3-2*(3-2)*((2-4)*5-22/(7+2*(3-1))-1))+1))
  86. len(mid$("abcdef", sqr(2^2), 6/2)) + len(mid$("abcdef", 2^2))
  87. $expect: stdout
  88. -1
  89. abcdef
  90. abc$def
  91. abc
  92. -1
  93. bcd
  94. abc   def
  95. 123 456
  96. 7160
  97. 6
  98. $finish
  99.  
« Last Edit: February 27, 2021, 07:14:28 am by Ed Davis »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Numeric/String Eval function
« Reply #1 on: February 25, 2021, 02:00:56 pm »
Ah just the Super Evaluator I m stuck on! Thanks @Ed Davis  JIT!

Curious about the 2nd Code thing? Is that FB version?
« Last Edit: February 25, 2021, 02:03:59 pm by bplus »

Offline Ed Davis

  • Newbie
  • Posts: 40
    • View Profile
Re: Numeric/String Eval function
« Reply #2 on: February 25, 2021, 04:11:25 pm »
Curious about the 2nd Code thing? Is that FB version?

That is the unit test data i used, running a modified version of  flukiluke's unit test program.
I should have been more clear - sorry about that.

re: FB version.  Just change "_explicit" to "explicit", and then it compiles with FreeBasic:  fbc -lang qb eval.bas

Wait - was that supposed to be a joke? ("the 2nd Code thing").  Cause I'm pretty sure you know what FB code looks like.  If it was a joke, then it was a good one, and you got me :)
« Last Edit: February 25, 2021, 04:14:36 pm by Ed Davis »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Numeric/String Eval function
« Reply #3 on: February 25, 2021, 09:04:37 pm »
Quote
Wait - was that supposed to be a joke?

:) not meant as joke, just never saw anything that's all $ sign commands = instructions to compiler, now, I will guess.


Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Re: Numeric/String Eval function
« Reply #4 on: February 25, 2021, 09:54:08 pm »
:) not meant as joke, just never saw anything that's all $ sign commands = instructions to compiler, now, I will guess.

All our precompiler commands are using $.

$let
$if
$elseif
$else
$end if
$color
and so on...  ;D
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Numeric/String Eval function
« Reply #5 on: February 25, 2021, 10:55:23 pm »
ha! $let

What is $expect: stdout

FellippeHeitor

  • Guest
Re: Numeric/String Eval function
« Reply #6 on: February 25, 2021, 10:57:33 pm »
ha! $let

What is $expect: stdout

Quote
That is the unit test data i used, running a modified version of  flukiluke's unit test program.
I should have been more clear - sorry about that.

https://github.com/flukiluke/L-BASIC/blob/master/tools/test.bas

Offline Dav

  • Forum Resident
  • Posts: 792
    • View Profile
Re: Numeric/String Eval function
« Reply #7 on: February 26, 2021, 08:25:20 am »
Nice code, @Ed Davis.  Works solid on what I could throw at it.

Thanks for sharing.

- Dav

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Numeric/String Eval function
« Reply #8 on: February 26, 2021, 11:36:45 am »
Well I found early on you can't throw any caps at it eg Mid$ instead of mid$.

Not disappointed in least about that though.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Numeric/String Eval function
« Reply #9 on: February 26, 2021, 11:46:35 am »
https://github.com/flukiluke/L-BASIC/blob/master/tools/test.bas

Thanks @FellippeHeitor

I think the gist of that link is put all your test stuff in an array of UDT
Type
   testString as string
   expectedReturn as string
   comparedResults as string
End type

And have the tester report both results and say if they are exactly same or not plus add some reports from the stopwatch as well.

Did I miss take the understanding of the link?

Well anyway my take away is to build a smarter tester, ie test through an array of items you can build as bugs are reported.
« Last Edit: February 26, 2021, 12:18:17 pm by bplus »

Offline Dav

  • Forum Resident
  • Posts: 792
    • View Profile
Re: Numeric/String Eval function
« Reply #10 on: February 26, 2021, 11:56:27 am »
Ah. I didnt try changing caps. Played with odd things though, like doing  1--1.

 -Dav

Offline Ed Davis

  • Newbie
  • Posts: 40
    • View Profile
Re: Numeric/String Eval function
« Reply #11 on: February 27, 2021, 07:17:14 am »
Ah. I didnt try changing caps. Played with odd things though, like doing  1--1.

 -Dav

Thanks for trying the odd things - I'm using this code in another program, and every bit of testing helps!
« Last Edit: February 27, 2021, 07:18:18 am by Ed Davis »

Offline Ed Davis

  • Newbie
  • Posts: 40
    • View Profile
Re: Numeric/String Eval function
« Reply #12 on: February 27, 2021, 07:19:55 am »
Well I found early on you can't throw any caps at it eg Mid$ instead of mid$.

Not disappointed in least about that though.

Sorry about that.  I knew I'd forgotten something :)

I have updated the code in the original post.  It now accepts mid$, Mid$, MID$ and so on, and the same for other identifiers.  And I'll add that to my unit tests.


Offline Ed Davis

  • Newbie
  • Posts: 40
    • View Profile
Re: Numeric/String Eval function
« Reply #13 on: February 27, 2021, 07:37:28 am »
Thanks @FellippeHeitor

I think the gist of that link is put all your test stuff in an array of UDT
Type
   testString as string
   expectedReturn as string
   comparedResults as string
End type

And have the tester report both results and say if they are exactly same or not plus add some reports from the stopwatch as well.

Did I miss take the understanding of the link?

I believe you got the gist.

For instance, based on your earlier report, I have added the following unit tests:

Code: QB64: [Select]
  1. $title: Eval ignore case in identifiers
  2. num = 42
  3. NUM
  4. num2 = 86
  5. NUM3 = NUM + NUM2
  6. NUM3
  7. num3
  8. MID$("abcd", 2)
  9. mid$("ABCD", 2)
  10. $expect: stdout
  11. 42
  12. 42
  13. 86
  14. 128
  15. 128
  16. 128
  17. bcd
  18. BCD
  19. $finish
  20.  

I put that in a file called eval.case.test, and run:

test eval.exe eval.case.test

And I get:

eval.case:Eval ignore case in identifiers: OK
Total 1/1 OK in 3.8 seconds

Each time I find a bug, I try to create a new test case that covers it.
Each time I make changes to eval, after recompiling, I run the unit tests.  While passing the unit tests doesn't mean that Eval has no bugs, it pretty quickly helps me know if it still sorta/kinda works, or if I've horribly broken it :)
« Last Edit: February 27, 2021, 07:41:58 am by Ed Davis »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Numeric/String Eval function
« Reply #14 on: March 01, 2021, 10:40:19 pm »
Hi @Ed Davis

I am trying to understand how you are parsing in Eval and I start around Expect, a couple of lines then goto by Sub or Function someplace else, a couple of lines, then someplace else... and this isn't spaghetti code but the effect on following it for understanding seems the same.

I am naming the opposite of spaghetti code,  shattered code, so refactored, so splintered and broken up... it's a nightmare to follow.

Not saying it's a bad thing, in fact I am wondering if it is some sign of being better for computers and efficiencies even if human understanding is made difficult.

But I am not familiar with the variables yet, it's like watching for first time a sitcom or soap opera that has been going for years.