Author Topic: The Recursive Descent Parser  (Read 8933 times)

0 Members and 1 Guest are viewing this topic.

Offline Ed Davis

  • Newbie
  • Posts: 40
    • View Profile
Re: The Recursive Descent Parser
« Reply #30 on: March 29, 2019, 04:27:56 pm »
Here are a couple of numeric expression parsers I wrote a while back.  Both use all QB64 numeric/logical/boolean operators along with standard Basic precedence, as noted in the Microsoft BASIC manuals, namely:

Code: QB64: [Select]
  1. Operator Precedence in BASIC
  2.  
  3. Highest:   ^
  4.            + - (unary negation)
  5.            *  /
  6.            \ (integer division)
  7.           MOD
  8.            +  -
  9.            =  <>  <  >  <=  >=
  10.           NOT (unary)
  11.           AND
  12.           OR
  13.           XOR
  14.           EQV
  15. Lowest:   IMP
  16.  

The first uses a form of Recursive Descent called Precedence Climbing, which I find easier than traditional Recursive Descent.  The second uses traditional Recursive Descent.  Both support the operators listed above, along with the following functions: abs, atn, cos, exp, fix, int, log, rnd, sgn, sin, sqr, tan.

Hopefully, the code is easy enough to follow.  Feedback is of course appreciated.

Expression Parser using Precedence Climbing:
Code: QB64: [Select]
  1.  
  2.  
  3. 'Simple numeric calculator - uses BASIC precedence rules
  4. '
  5. 'Supports all QB numeric/logical operators, plus the following functions:
  6. '
  7. 'abs, atn, cos, exp, fix, int, log, rnd, sgn, sin, sqr, tan
  8. '
  9. 'Uses Precedence Climbing algorithm
  10. '
  11. 'Operator Precedence in BASIC
  12. '
  13. 'Highest:   ^
  14. '           + - (unary negation)
  15. '           *  /
  16. '           \ (integer division)
  17. '          MOD
  18. '           +  -
  19. '           =  <>  <  >  <=  >=
  20. '          NOT (unary)
  21. '          AND
  22. '          OR
  23. '          XOR
  24. '          EQV
  25. 'Lowest:   IMP
  26. '
  27.  
  28. dim shared input_st as string   'the string to evaluate
  29.  
  30. dim shared tok as integer       ' set by the scanner - one of the values listed below
  31. dim shared id_name as string    ' set by the scanner, if tok is an ID or NUMBER
  32.  
  33. dim shared prec(20) as integer  'array of precedences
  34.  
  35. CONST false = 0
  36. CONST true = not false
  37.  
  38. ' Values for tok - also indexes into prec array - code below depends on this ordering
  39. const POWER     =   1
  40. const NEGATE    =   2
  41. const MULTIPLY  =   3
  42. const DIVIDE    =   4
  43. const INT_DIV   =   5
  44. const MODULUS   =   6
  45. const ADD       =   7
  46. const SUBTRACT  =   8
  47. const EQUAL     =   9
  48. const NOT_EQUAL =  10
  49. const LSS_T     =  11
  50. const GTR_T     =  12
  51. const LSS_T_EQL =  13
  52. const GTR_T_EQL =  14
  53. const NOT_T     =  15
  54. const AND_T     =  16
  55. const OR_T      =  17
  56. const XOR_T     =  18
  57. const EQV_T     =  19
  58. const IMP_T     =  20
  59.  
  60. const ID        = 100
  61. const NUMBER    = 101
  62. const LPAREN    = 102
  63. const RPAREN    = 103
  64.  
  65. ' populate the precedence array - these conicide with the constants above
  66. ' we use the operator constants (above) as indexes into the precedence array
  67. '
  68. prec(POWER)    = 13
  69. prec(NEGATE)   = 12
  70. prec(DIVIDE)   = 11: prec(MULTIPLY) = 11
  71. prec(INT_DIV)  = 10
  72. prec(MODULUS)  = 9
  73. prec(SUBTRACT) = 8: prec(ADD) = 8
  74. prec(GTR_T_EQL)=7:prec(LSS_T_EQL)=7:prec(GTR_T)=7:prec(LSS_T)=7:prec(NOT_EQUAL)=7:prec(EQUAL)=7
  75. prec(NOT_T)    = 6
  76. prec(AND_T)    = 5
  77. prec(OR_T)     = 4
  78. prec(XOR_T)    = 3
  79. prec(EQV_T)    = 2
  80. prec(IMP_T)    = 1
  81.  
  82. input_st = "-1 - 2 - 3 - 4"
  83. print input_st + " = "; eval#
  84.  
  85. input_st = "3 + 6 / 12 * 3 - 2"
  86. print input_st + " = "; eval#
  87.  
  88. input_st = "4 ^ - 2"
  89. print input_st + " = "; eval#
  90.  
  91. input_st = "4 ^ 3 ^ 2"
  92. print input_st + " = "; eval#
  93.  
  94. input_st = "32 / 2 / 2"
  95. print input_st + " = "; eval#
  96.  
  97. input_st = "atn(1) * 4"
  98. print "PI = " + input_st + " = "; eval#
  99.  
  100.     input "Enter a string to evaluate > "; input_st
  101.     if input_st = "" then end
  102.     print "Expression = "; eval#
  103.     ?
  104.  
  105. function eval#
  106.     next_tok
  107.     eval# = evalr#(0)
  108.  
  109. function evalr#(pr as integer)
  110.     dim n as double
  111.  
  112.     n = primary#
  113.  
  114.     do while is_binary%(tok) 'need short circuit for this: and prec(tok) >= pr
  115.         if prec(tok) < pr then exit do
  116.         dim op as integer
  117.         op = tok
  118.         next_tok
  119.         select case op
  120.             case IMP_T     : n = n imp evalr#(prec(op) + 1)
  121.             case EQV_T     : n = n eqv evalr#(prec(op) + 1)
  122.             case XOR_T     : n = n xor evalr#(prec(op) + 1)
  123.             case OR_T      : n = n or  evalr#(prec(op) + 1)
  124.             case AND_T     : n = n and evalr#(prec(op) + 1)
  125.             case GTR_T     : n = n >   evalr#(prec(op) + 1)
  126.             case GTR_T_EQL : n = n >=  evalr#(prec(op) + 1)
  127.             case LSS_T_EQL : n = n <=  evalr#(prec(op) + 1)
  128.             case LSS_T     : n = n <   evalr#(prec(op) + 1)
  129.             case NOT_EQUAL : n = n <>  evalr#(prec(op) + 1)
  130.             case EQUAL     : n = n =   evalr#(prec(op) + 1)
  131.             case SUBTRACT  : n = n -   evalr#(prec(op) + 1)
  132.             case ADD       : n = n +   evalr#(prec(op) + 1)
  133.             case MODULUS   : n = n mod evalr#(prec(op) + 1)
  134.             case INT_DIV   : n = n \   evalr#(prec(op) + 1)
  135.             case DIVIDE    : n = n /   evalr#(prec(op) + 1)
  136.             case MULTIPLY  : n = n *   evalr#(prec(op) + 1)
  137.             case POWER     : n = n ^   evalr#(prec(op) + 1)
  138.             case else: print "Expecting an binary operator": evalr# = 0: exit do
  139.         end select
  140.         if is_relational(op) and is_relational(tok) then
  141.             print "consecutive relational operators not allowed"
  142.             evalr# = 0
  143.         end if
  144.     loop
  145.     evalr# = n
  146.  
  147. function is_binary%(op as integer)
  148.     is_binary% = false
  149.     if op >= MULTIPLY and op <= IMP_T then is_binary% = true: exit function
  150.     if op = POWER then is_binary% = true: exit function
  151.  
  152. function is_relational%(op as integer)
  153.     is_relational% = false
  154.     if op >= EQUAL and op <= GTR_T_EQL then is_relational% = true
  155.  
  156. function primary#
  157.     dim n as double
  158.     dim fun as string
  159.  
  160.     select case tok
  161.         case NUMBER:   n = val(id_name): next_tok
  162.         case ADD:      next_tok: n =     evalr#(prec(NEGATE))
  163.         case SUBTRACT: next_tok: n =    -evalr#(prec(NEGATE))
  164.         case NOT_T:    next_tok: n = not evalr#(prec(NOT_T))
  165.         case LPAREN
  166.             next_tok
  167.             n = evalr#(0)
  168.             call expect(RPAREN, ")")
  169.         case ID
  170.             fun = id_name
  171.             next_tok
  172.             call expect(LPAREN, "(")
  173.             n = evalr#(0)
  174.             call expect(RPAREN, ")")
  175.             n = builtin(fun, n)
  176.         case else: print "Expecting a primary": n = 0
  177.     end select
  178.     primary# = n
  179.  
  180. function builtin#(fun as string, arg as double)
  181.     dim n as double
  182.  
  183.     select case lcase$(fun)
  184.         case "abs": n = abs(arg)
  185.         case "atn": n = atn(arg)
  186.         case "cos": n = cos(arg)
  187.         case "exp": n = exp(arg)
  188.         case "fix": n = fix(arg)
  189.         case "int": n = int(arg)
  190.         case "log": n = log(arg)
  191.         case "rnd": n = rnd(arg)
  192.         case "sgn": n = sgn(arg)
  193.         case "sin": n = sin(arg)
  194.         case "sqr": n = sqr(arg)
  195.         case "tan": n = tan(arg)
  196.         case else: n = 0: print "Expecting a function, found: "; fun
  197.     end select
  198.     builtin# = n
  199.  
  200. sub expect(n as integer, s as string)
  201.     if tok = n then next_tok: exit sub
  202.     print "Expecting: "; s
  203.  
  204. sub next_tok
  205.     id_name = ""
  206.     tok = 0
  207.  
  208.     ' skip spaces
  209.     input_st = ltrim$(input_st)
  210.  
  211.     if input_st = "" then exit sub
  212.  
  213.     ' pick off a few multichar operators
  214.     select case lcase$(left$(input_st, 2))
  215.         case ">=": input_st = mid$(input_st, 3): tok = GTR_T_EQL
  216.         case "<=": input_st = mid$(input_st, 3): tok = LSS_T_EQL
  217.         case "<>": input_st = mid$(input_st, 3): tok = NOT_EQUAL
  218.         case else
  219.             ' now do the rest, based on first letter
  220.         select case lcase$(left$(input_st, 1))
  221.             case "0" to "9"
  222.                 do
  223.                     id_name = id_name + left$(input_st, 1)
  224.                     input_st = mid$(input_st, 2)
  225.                 loop while (left$(input_st, 1) >= "0" and left$(input_st, 1) <= "9") or left$(input_st, 1) = "."
  226.                 tok = NUMBER
  227.             case "a" to "z"
  228.                 do
  229.                     id_name = id_name + left$(input_st, 1)
  230.                     input_st = mid$(input_st, 2)
  231.                 loop while left$(input_st, 1) >= "a" and left$(input_st, 1) <= "z"
  232.                 select case lcase$(id_name)
  233.                     case "mod": tok = MODULUS:
  234.                     case "not": tok = NOT_T:
  235.                     case "and": tok = AND_T:
  236.                     case "or" : tok = OR_T:
  237.                     case "eqv": tok = EQV_T:
  238.                     case "imp": tok = IMP_T:
  239.                     case "xor": tok = XOR_T:
  240.                     case else:  tok = ID:
  241.                 end select
  242.  
  243.             case "^": input_st = mid$(input_st, 2): tok = POWER
  244.             case "*": input_st = mid$(input_st, 2): tok = MULTIPLY
  245.             case "/": input_st = mid$(input_st, 2): tok = DIVIDE
  246.             case "\": input_st = mid$(input_st, 2): tok = INT_DIV
  247.             case "+": input_st = mid$(input_st, 2): tok = ADD
  248.             case "-": input_st = mid$(input_st, 2): tok = SUBTRACT
  249.             case "=": input_st = mid$(input_st, 2): tok = EQUAL
  250.             case "(": input_st = mid$(input_st, 2): tok = LPAREN
  251.             case ")": input_st = mid$(input_st, 2): tok = RPAREN
  252.             case ">": input_st = mid$(input_st, 2): tok = GTR_T
  253.             case "<": input_st = mid$(input_st, 2): tok = LSS_T
  254.             case else: print "Unknown token encountered"
  255.         end select
  256.     end select
  257.  

Expression Parser using Recursive Descent:
Code: QB64: [Select]
  1.  
  2.  
  3. 'Simple numeric calculator - uses BASIC precedence rules
  4. '
  5. 'Supports all QB numeric/logical operators, plus the following functions:
  6. '
  7. 'abs, atn, cos, exp, fix, int, log, rnd, sgn, sin, sqr, tan
  8. '
  9. 'Uses Recursive Descent algorithm
  10. 'This requires one function per precedence level
  11. '
  12. 'Operator Precedence in BASIC
  13. '
  14. 'Highest:   ^
  15. '           + - (unary negation)
  16. '           *  /
  17. '           \ (integer division)
  18. '          MOD
  19. '           +  -
  20. '           =  <>  <  >  <=  >=
  21. '          NOT (unary)
  22. '          AND
  23. '          OR
  24. '          XOR
  25. '          EQV
  26. 'Lowest:   IMP
  27.  
  28. dim shared input_st as string   'the string to evaluate
  29.  
  30. dim shared tok as integer       ' set by the scanner - one of the values listed below
  31. dim shared id_name as string    ' set by the scanner, if tok is an ID or NUMBER
  32.  
  33. CONST false = 0
  34. CONST true = not false
  35.  
  36. ' Values for tok
  37. const POWER     =   1
  38. const NEGATE    =   2
  39. const MULTIPLY  =   3
  40. const DIVIDE    =   4
  41. const INT_DIV   =   5
  42. const MODULUS   =   6
  43. const ADD       =   7
  44. const SUBTRACT  =   8
  45. const EQUAL     =   9
  46. const NOT_EQUAL =  10
  47. const LSS_T     =  11
  48. const GTR_T     =  12
  49. const LSS_T_EQL =  13
  50. const GTR_T_EQL =  14
  51. const NOT_T     =  15
  52. const AND_T     =  16
  53. const OR_T      =  17
  54. const XOR_T     =  18
  55. const EQV_T     =  19
  56. const IMP_T     =  20
  57.  
  58. const ID        = 100
  59. const NUMBER    = 101
  60. const LPAREN    = 102
  61. const RPAREN    = 103
  62.  
  63. input_st = "-1 - 2 - 3 - 4"
  64. print input_st + " = "; eval#
  65.  
  66. input_st = "3 + 6 / 12 * 3 - 2"
  67. print input_st + " = "; eval#
  68.  
  69. input_st = "4 ^ - 2"
  70. print input_st + " = "; eval#
  71.  
  72. input_st = "4 ^ 3 ^ 2"
  73. print input_st + " = "; eval#
  74.  
  75. input_st = "32 / 2 / 2"
  76. print input_st + " = "; eval#
  77.  
  78. input_st = "atn(1) * 4"
  79. print "PI = " + input_st + " = "; eval#
  80.  
  81.     input "Enter a string to evaluate > "; input_st
  82.     if input_st = "" then end
  83.     print "Expression = "; eval#
  84.     ?
  85.  
  86. function eval#
  87.     next_tok
  88.     eval# = evalr#(0)
  89.  
  90. function evalr#(dummy)
  91.     dim n as double
  92.  
  93.     n = eval_eqv#(0)
  94.     do while tok = IMP_T
  95.         next_tok: n = n imp eval_eqv#(0)
  96.     loop
  97.     evalr# = n
  98.  
  99. function eval_eqv#(dummy)
  100.     dim n as double
  101.  
  102.     n = eval_xor#(0)
  103.     do while tok = EQV_T
  104.         next_tok: n = n eqv eval_xor#(0)
  105.     loop
  106.     eval_eqv# = n
  107.  
  108. function eval_xor#(dummy)
  109.     dim n as double
  110.  
  111.     n = eval_or#(0)
  112.     do while tok = XOR_T
  113.         next_tok: n = n xor eval_or#(0)
  114.     loop
  115.     eval_xor# = n
  116.  
  117. function eval_or#(dummy)
  118.     dim n as double
  119.  
  120.     n = eval_and#(0)
  121.     do while tok = OR_T
  122.         next_tok: n = n or eval_and#(0)
  123.     loop
  124.     eval_or# = n
  125.  
  126. function eval_and#(dummy)
  127.     dim n as double
  128.  
  129.     n = eval_relational#(0)
  130.     do while tok = AND_T
  131.         next_tok: n = n and eval_relational#(0)
  132.     loop
  133.     eval_and# = n
  134.  
  135. function eval_relational#(dummy)
  136.     dim n as double
  137.  
  138.     n = eval_add#(0)
  139.     select case tok
  140.         case GTR_T    : next_tok: n = n >  eval_add#(0)
  141.         case GTR_T_EQL: next_tok: n = n >= eval_add#(0)
  142.         case LSS_T_EQL: next_tok: n = n <= eval_add#(0)
  143.         case LSS_T    : next_tok: n = n <  eval_add#(0)
  144.         case NOT_EQUAL: next_tok: n = n <> eval_add#(0)
  145.         case EQUAL    : next_tok: n = n =  eval_add#(0)
  146.     end select
  147.     eval_relational# = n
  148.  
  149. function eval_add#(dummy)
  150.     dim n as double
  151.  
  152.     n = eval_mod#(0)
  153.     do
  154.         select case tok
  155.             case ADD:      next_tok: n = n + eval_mod#(0)
  156.             case SUBTRACT: next_tok: n = n - eval_mod#(0)
  157.             case else: exit do
  158.         end select
  159.     loop
  160.     eval_add# = n
  161.  
  162. function eval_mod#(dummy)
  163.     dim n as double
  164.  
  165.     n = eval_idiv#(0)
  166.     do while tok = MODULUS
  167.         next_tok: n = n mod eval_idiv#(0)
  168.     loop
  169.     eval_mod# = n
  170.  
  171. function eval_idiv#(dummy)
  172.     dim n as double
  173.  
  174.     n = eval_mul#(0)
  175.     do while tok = INT_DIV
  176.         next_tok: n = n \ eval_mul#(0)
  177.     loop
  178.     eval_idiv# = n
  179.  
  180. function eval_mul#(dummy)
  181.     dim n as double
  182.  
  183.     n = eval_pow#(0)
  184.     do
  185.         select case tok
  186.             case MULTIPLY: next_tok:  n = n * eval_pow#(0)
  187.             case DIVIDE:   next_tok:  n = n / eval_pow#(0)
  188.             case else: exit do
  189.         end select
  190.     loop
  191.     eval_mul# = n
  192.  
  193. function eval_pow#(dummy)
  194.     dim n as double
  195.  
  196.     n = factor#(0)
  197.     do while tok = POWER
  198.         next_tok:  n = n ^ factor#(0)
  199.     loop
  200.     eval_pow# = n
  201.  
  202. function factor#(dummy)
  203.     dim n as double
  204.     dim fun as string
  205.  
  206.     select case tok
  207.         case NUMBER:   n = val(id_name): next_tok
  208.         case ADD:      next_tok: n =     factor#(0)
  209.         case SUBTRACT: next_tok: n =    -factor#(0)
  210.         case NOT_T:    next_tok: n = not factor#(0)
  211.         case LPAREN
  212.             next_tok
  213.             n = evalr#(0)
  214.             call expect(RPAREN, ")")
  215.         case ID
  216.             fun = id_name
  217.             next_tok
  218.             call expect(LPAREN, "(")
  219.             n = evalr#(0)
  220.             call expect(RPAREN, ")")
  221.             n = builtin(fun, n)
  222.         case else: print "Expecting a factor": n = 0
  223.     end select
  224.     factor# = n
  225.  
  226. function builtin#(fun as string, arg as double)
  227.     dim n as double
  228.  
  229.     select case lcase$(fun)
  230.         case "abs": n = abs(arg)
  231.         case "atn": n = atn(arg)
  232.         case "cos": n = cos(arg)
  233.         case "exp": n = exp(arg)
  234.         case "fix": n = fix(arg)
  235.         case "int": n = int(arg)
  236.         case "log": n = log(arg)
  237.         case "rnd": n = rnd(arg)
  238.         case "sgn": n = sgn(arg)
  239.         case "sin": n = sin(arg)
  240.         case "sqr": n = sqr(arg)
  241.         case "tan": n = tan(arg)
  242.         case else: n = 0: print "Expecting a function, found: "; fun
  243.     end select
  244.     builtin# = n
  245.  
  246. sub expect(n as integer, s as string)
  247.     if tok = n then next_tok: exit sub
  248.     print "Expecting: "; s
  249.  
  250. sub next_tok
  251.     id_name = ""
  252.     tok = 0
  253.  
  254.     ' skip spaces
  255.     input_st = ltrim$(input_st)
  256.  
  257.     if input_st = "" then exit sub
  258.  
  259.     ' pick off a few multichar operators
  260.     select case lcase$(left$(input_st, 2))
  261.         case ">=": input_st = mid$(input_st, 3): tok = GTR_T_EQL
  262.         case "<=": input_st = mid$(input_st, 3): tok = LSS_T_EQL
  263.         case "<>": input_st = mid$(input_st, 3): tok = NOT_EQUAL
  264.         case else
  265.             ' now do the rest, based on first letter
  266.         select case lcase$(left$(input_st, 1))
  267.             case "0" to "9"
  268.                 do
  269.                     id_name = id_name + left$(input_st, 1)
  270.                     input_st = mid$(input_st, 2)
  271.                 loop while (left$(input_st, 1) >= "0" and left$(input_st, 1) <= "9") or left$(input_st, 1) = "."
  272.                 tok = NUMBER
  273.             case "a" to "z"
  274.                 do
  275.                     id_name = id_name + left$(input_st, 1)
  276.                     input_st = mid$(input_st, 2)
  277.                 loop while left$(input_st, 1) >= "a" and left$(input_st, 1) <= "z"
  278.                 select case lcase$(id_name)
  279.                     case "mod": tok = MODULUS:
  280.                     case "not": tok = NOT_T:
  281.                     case "and": tok = AND_T:
  282.                     case "or" : tok = OR_T:
  283.                     case "eqv": tok = EQV_T:
  284.                     case "imp": tok = IMP_T:
  285.                     case "xor": tok = XOR_T:
  286.                     case else:  tok = ID:
  287.                 end select
  288.  
  289.             case "^": input_st = mid$(input_st, 2): tok = POWER
  290.             case "*": input_st = mid$(input_st, 2): tok = MULTIPLY
  291.             case "/": input_st = mid$(input_st, 2): tok = DIVIDE
  292.             case "\": input_st = mid$(input_st, 2): tok = INT_DIV
  293.             case "+": input_st = mid$(input_st, 2): tok = ADD
  294.             case "-": input_st = mid$(input_st, 2): tok = SUBTRACT
  295.             case "=": input_st = mid$(input_st, 2): tok = EQUAL
  296.             case "(": input_st = mid$(input_st, 2): tok = LPAREN
  297.             case ")": input_st = mid$(input_st, 2): tok = RPAREN
  298.             case ">": input_st = mid$(input_st, 2): tok = GTR_T
  299.             case "<": input_st = mid$(input_st, 2): tok = LSS_T
  300.             case else: print "Unknown token encountered"
  301.         end select
  302.     end select
  303.