QB64.org Forum

Active Forums => Programs => Topic started by: eoredson on July 23, 2017, 10:45:01 pm

Title: The Recursive Descent Parser
Post by: eoredson on July 23, 2017, 10:45:01 pm
Find attached What64 the recursive descent parser.

Erik.
Title: Re: The Recursive Descent Parser
Post by: bplus on July 24, 2017, 02:21:16 pm
Hi Erik,

Did you use this in your Interpreter SICK64D1.zip that I just downloaded?
Title: Re: The Recursive Descent Parser
Post by: STxAxTIC on July 24, 2017, 04:06:12 pm
Hey eoredson,

I've always liked this project. I'm glad you still occasionally make noise about it! Just curious - is there any formal website or home for this? Does the project *.zip contain all documentation?

And a really off-the-wall question is, just how much of the code is from the underscore-ridden QB64 era, vs how much is mostly QB45 compatible?

(I could dig a bit and answer all this myself, but I figure the public questions make things easier.)

Thanks!
Title: Re: The Recursive Descent Parser
Post by: eoredson on July 24, 2017, 08:30:13 pm
Thanks!

@bplus: Yes, it is the SICK engine.

STxAxTIC:
There is no web site I own that distributes it, and the .zip contains the entire project, including .doc

Interestingly enough, it was ported from 16-bit QB45 and that was not easy.

Unless https://goo.gl/pkQug8 qualifies as a web site..

Erik.
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 06:55:54 am
SICK engine is large and filled with lot of stuff but i have have something smaller
well not written in qb64 but is quite universal ..
i hope that Mark(B+) can convert this to qb64...

Code: QB64: [Select]
  1. 'recursive descent token evaluator
  2. '#lookahead
  3. int tc=0 : string token
  4. string tokens[7]
  5. tokens[1] = "2"
  6. tokens[2] = "*"
  7. tokens[3] = "("
  8. tokens[4] = "3"
  9. tokens[5] = "+"
  10. tokens[6] = "4"
  11. tokens[7] = ")"
  12.  
  13. sub gettok()
  14. tc++ : token = tokens[tc]
  15.  
  16. sub expr() as float
  17. float v = term()
  18. if token = "+": gettok() : v = v + term(): end if
  19. if token = "-": gettok() : v = v - term(): end if
  20.  
  21. sub term() as float
  22. float v = factor()
  23. if token = "*": gettok() : v = v * factor(): end if
  24. if token = "/": gettok() : v = v / factor(): end if
  25.  
  26. sub factor() as float
  27. float v
  28. if asc(token)>47  and asc(token)<58 'nums
  29. v = val(token) : gettok()
  30. if asc(token)=40 and asc(token)<>41 'match (...)
  31. gettok() : v = expr() : gettok()
  32.  
  33. 'execute---------------
  34. gettok() 'start
  35. float res = expr()
  36. print str res
Title: Re: The Recursive Descent Parser
Post by: odin on March 19, 2019, 08:09:04 am
Posting code that's unusable in QB64 is not welcome. If the idea is awesome, adapt it to QB64 and post it or else just post it under off-topic.
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 08:52:51 am
sorry i know that...
but i simply don't know how to translate this code to qb64.
I use Davs IDE .
main problem i have is with SUB routines
in another words i cannot figured proper shape..
i am looking into help of qb64 but i simply cannot found where is what?
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 09:02:19 am
Here is what i transform but some things are wrong
Code: QB64: [Select]
  1. recursive descent token evaluator
  2. '
  3. dim tc as INTEGER : dim token as string : dim v as single
  4. DIM tokens() as string * 7
  5. tokens(1) = "2"
  6. tokens(2) = "*"
  7. tokens(3) = "("
  8. tokens(4) = "3"
  9. tokens(5) = "+"
  10. tokens(6) = "4"
  11. tokens(7) = ")"
  12.  
  13. sub gettok()
  14. tc=tc+1 : token = tokens(tc)
  15.  
  16. sub expr()
  17. v = term()
  18. if token = "+": gettok() : v = v + term(): end if
  19. if token = "-": gettok() : v = v - term(): end if
  20.  
  21. sub term()
  22. v = factor()
  23. if token = "*": gettok() : v = v * factor(): end if
  24. if token = "/": gettok() : v = v / factor(): end if
  25.  
  26. sub factor()
  27. if asc(token)>47  and asc(token)<58 'nums
  28. v = val(token) : gettok()
  29. if asc(token)=40 and asc(token)<>41 'match (...)
  30. gettok() : v = expr() : gettok()
  31.  
  32. 'execute---------------
  33. gettok() 'start
  34. float res = expr()
  35. print str(res)
Title: Re: The Recursive Descent Parser
Post by: _vince on March 19, 2019, 09:17:06 am
What language is it? It looks interesting
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 10:08:29 am
hi vince
it is o2( Oxygen basic)
all basic dialects are very similar and that is good
but i simply stuck with such a simple code here in qb64 ..my fault
or i become rusty .. :)
Title: Re: The Recursive Descent Parser
Post by: jack on March 19, 2019, 10:23:56 am
Hi Aurel
here's your code translated to QB64
Code: QB64: [Select]
  1. 'recursive descent token evaluator
  2. DIM SHARED tokens(8) AS STRING * 1
  3.  
  4. tc = 0
  5. tokens(1) = "2"
  6. tokens(2) = "*"
  7. tokens(3) = "("
  8. tokens(4) = "3"
  9. tokens(5) = "+"
  10. tokens(6) = "4"
  11. tokens(7) = ")"
  12.  
  13. 'execute---------------
  14. CALL gettok 'start
  15. res = expr#
  16. PRINT res
  17.  
  18. SUB gettok ()
  19.     tc = tc + 1
  20.     token = tokens(tc)
  21.  
  22. FUNCTION expr# ()
  23.     DIM v AS DOUBLE
  24.     v = term#
  25.     IF token = "+" THEN
  26.         gettok
  27.         v = v + term#
  28.     END IF
  29.     IF token = "-" THEN
  30.         gettok
  31.         v = v - term#
  32.     END IF
  33.     expr# = v
  34.  
  35. FUNCTION term# ()
  36.     DIM v AS DOUBLE
  37.     v = factor#
  38.     IF token = "*" THEN
  39.         gettok
  40.         v = v * factor#
  41.     END IF
  42.     IF token = "/" THEN
  43.         gettok
  44.         v = v / factor#
  45.     END IF
  46.     term# = v
  47.  
  48. FUNCTION factor# ()
  49.     DIM v AS DOUBLE
  50.     IF ASC(token) > 47 AND ASC(token) < 58 THEN 'nums
  51.         v = VAL(token)
  52.         gettok
  53.     END IF
  54.     IF ASC(token) = 40 AND ASC(token) <> 41 THEN 'match (...)
  55.         gettok
  56.         v = expr#
  57.         gettok
  58.     END IF
  59.     factor# = v
  60.  
output 14
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 10:31:21 am
hi Jack and thank you!
now i see where is difference.
qb require # suffix for floating point number
globals need directive SHERED...
nice !

As we can see this few functions are really simple.
Title: Re: The Recursive Descent Parser
Post by: bplus on March 19, 2019, 10:53:41 am
hi Jack and thank you!
now i see where is difference.
qb require # suffix for floating point number
globals need directive SHERED...
nice !

As we can see this few functions are really simple.

Aurel something else about QB64 subs and functions you should know:
The arguments passed are by default By Reference, NOT By Value so if you change a value in sub it might go back to main program changed.
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 11:22:27 am
Hey Mark
I see now that something is different ..
ok so default is byRef...mean pointered.
Title: Re: The Recursive Descent Parser
Post by: jack on March 19, 2019, 11:28:01 am
@Aurel
in your function factor the second if clause makes no sense, you can replace it with else, '(' and ')' are simply ignored by the function expr
Code: QB64: [Select]
  1. FUNCTION factor# ()
  2.     DIM v AS DOUBLE
  3.     IF ASC(token) > 47 AND ASC(token) < 58 THEN 'nums
  4.         v = VAL(token)
  5.         gettok
  6.     ELSE
  7.         gettok
  8.         v = expr#
  9.         gettok
  10.     END IF
  11.     factor# = v
  12.  
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 11:36:50 am
hi jack
Yes you have right IF we use this program as finished or completed
but if you wish to add more operation under factor() function then makes no sense to use
ELSE...
so probably should be better to use ELSEIF method
but that is another story.
In fact match just operate under parens () in another word if parens exists.
hmmm is that ok ?
Title: Re: The Recursive Descent Parser
Post by: Ed Davis on March 19, 2019, 12:09:06 pm
As we can see this few functions are really simple.

Try this expression: 1+2+3

The answer should be 6, but the evaluator returns 3.  I think recursion in qb64 is broken.

Hmmm.  Searching old messages, only broken if 0 arguments.  I changed expr# and term# to take a single argument, changed the calls to the same, and now it works fine.

Will this be fixed one day, or is it for backwards compatibility or something and thus will remain as it is?
Title: Re: The Recursive Descent Parser
Post by: jack on March 19, 2019, 12:51:19 pm
@Ed Davis
would you post the code please?
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 01:26:50 pm
Quote
I think recursion in qb64 is broken

no ..is not
it is problem in evaluator because i get same error in o2 version ..too
I don't understand exatly what you mean under single argument?
Title: Re: The Recursive Descent Parser
Post by: SMcNeill on March 19, 2019, 01:57:55 pm
Quote
I think recursion in qb64 is broken

no ..is not
it is problem in evaluator because i get same error in o2 version ..too
I don't understand exatly what you mean under single argument?

Read the issue and a fix here: https://www.qb64.org/forum/index.php?topic=704.msg5802#msg5802

Basically, the following is broken:

Code: QB64: [Select]
  1. X = 5
  2.  
  3. IF X = 0 THEN F = 1 ELSE PRINT "*": X = X - 1: F = F

Luke was going to try and sort out a solution to the issue in the evaltype routine, but has been busy with life in general and hasn’t gotten around to it yet.  If you use zero-argument recursion a lot, you might want to apply my patch to QB64.bas and recompile the EXE.  If not, and it’s a once-in-a-bluemoon issue, just pass a dummy parameter to the function.

Instead of:

FUNCTION foo
   Foo = foo
END FUNCTION

Use:

Function Foo (dummy)
   Foo = Foo (Whatever)
END FUNCTION
Title: Re: The Recursive Descent Parser
Post by: Ed Davis on March 19, 2019, 02:01:28 pm
Quote
I think recursion in qb64 is broken

no ..is not
it is problem in evaluator because i get same error in o2 version ..too
I don't understand exatly what you mean under single argument?

Original version, but with a different expression - should show 6, but it shows 3:
Code: QB64: [Select]
  1. 'recursive descent token evaluator - original version
  2. DIM SHARED tokens(8) AS STRING * 1
  3.  
  4. tc = 0
  5. tokens(1) = "1"
  6. tokens(2) = "+"
  7. tokens(3) = "2"
  8. tokens(4) = "+"
  9. tokens(5) = "3"
  10.  
  11. 'execute---------------
  12. CALL gettok 'start
  13. res = expr#
  14. PRINT res
  15.  
  16. SUB gettok ()
  17.     tc = tc + 1
  18.     token = tokens(tc)
  19.  
  20. FUNCTION expr# ()
  21.     DIM v AS DOUBLE
  22.     v = term#
  23.     IF token = "+" THEN
  24.         gettok
  25.         v = v + term#
  26.     END IF
  27.     IF token = "-" THEN
  28.         gettok
  29.         v = v - term#
  30.     END IF
  31.     expr# = v
  32.  
  33. FUNCTION term# ()
  34.     DIM v AS DOUBLE
  35.     v = factor#
  36.     IF token = "*" THEN
  37.         gettok
  38.         v = v * factor#
  39.     END IF
  40.     IF token = "/" THEN
  41.         gettok
  42.         v = v / factor#
  43.     END IF
  44.     term# = v
  45.  
  46. FUNCTION factor# ()
  47.     DIM v AS DOUBLE
  48.     IF ASC(token) > 47 AND ASC(token) < 58 THEN 'nums
  49.         v = VAL(token)
  50.         gettok
  51.     END IF
  52.     IF ASC(token) = 40 AND ASC(token) <> 41 THEN 'match (...)
  53.         gettok
  54.         v = expr#
  55.         gettok
  56.     END IF
  57.     factor# = v
  58.  

Updated version, but using one (dummy) argument for expr and term - gets the right answer:
Code: QB64: [Select]
  1. 'recursive descent token evaluator - added dummy arguments
  2. DIM SHARED tokens(8) AS STRING * 1
  3.  
  4. tc = 0
  5. tokens(1) = "1"
  6. tokens(2) = "+"
  7. tokens(3) = "2"
  8. tokens(4) = "+"
  9. tokens(5) = "3"
  10.  
  11. 'execute---------------
  12. CALL gettok 'start
  13. res = expr#(0)
  14. PRINT res
  15.  
  16. SUB gettok ()
  17.     tc = tc + 1
  18.     token = tokens(tc)
  19.  
  20. FUNCTION expr# (x as integer)
  21.     DIM v AS DOUBLE
  22.     v = term#(0)
  23.     IF token = "+" THEN
  24.         gettok
  25.         v = v + expr#(0)
  26.     ELSEIF token = "-" THEN
  27.         gettok
  28.         v = v - expr#(0)
  29.     END IF
  30.     expr# = v
  31.  
  32. FUNCTION term# (x as integer)
  33.     DIM v AS DOUBLE
  34.     v = factor#
  35.     IF token = "*" THEN
  36.         gettok
  37.         v = v * term#(0)
  38.     ELSEIF token = "/" THEN
  39.         gettok
  40.         v = v / term#(0)
  41.     END IF
  42.     term# = v
  43.  
  44. FUNCTION factor# ()
  45.     DIM v AS DOUBLE
  46.     IF ASC(token) > 47 AND ASC(token) < 58 THEN 'nums
  47.         v = VAL(token)
  48.         gettok
  49.     ELSEIF ASC(token) = 40 AND ASC(token) <> 41 THEN 'match (...)
  50.         gettok
  51.         v = expr#(0)
  52.         gettok
  53.     END IF
  54.     factor# = v
  55.  

See this message: https://www.qb64.org/forum/index.php?topic=676.msg5776#msg5776
for a confirmation of the recursion bug.

Interestingly, FreeBasic, in QB mode, also has the same problem.  However, when compiled in normal FreeBasic mode (some minor changes have to be made to the code), it works fine.


Title: Re: The Recursive Descent Parser
Post by: Aurel on March 19, 2019, 03:04:58 pm
Hmm that is interesting and as i said same problem i found in o2 version
I don't look first time what was going on using token one by one (in each step)
then when i look again into my old interpretr i figured that i use
while loop with operators +,-,*,/
so probably this loop add one turn more for "dummy" effect and
now everything work as it should be .
strange thing ..i must try with null too...
Title: Re: The Recursive Descent Parser
Post by: bplus on March 20, 2019, 10:14:36 am
I don't think you guys have it fixed yet:

Code: QB64: [Select]
  1. _TITLE "Recursive descent token evaluator by Aurel"
  2. 'trenslation by @jack 19.3.2019
  3. ' nope! now use Ed Davis fix: https://www.qb64.org/forum/index.php?topic=33.15
  4. 'mod by B+ 2019-03-20 so that can test any number of expressions
  5.  
  6. 'tc = 0
  7. 'tokens(1) = "2"
  8. 'tokens(2) = "*"
  9. 'tokens(3) = "("
  10. 'tokens(4) = "3"
  11. 'tokens(5) = "+"
  12. 'tokens(6) = "4"
  13. 'tokens(7) = ")"
  14.  
  15.     INPUT "Enter an expression to evaluate: ", eval$
  16.     le = LEN(eval$)
  17.     IF le THEN
  18.         tc = 0
  19.         REDIM SHARED tokens(1 TO le + 1) AS STRING * 1
  20.         FOR i = 1 TO le
  21.             tokens(i) = MID$(eval$, i, 1)
  22.         NEXT
  23.         'execute---------------
  24.         'CALL gettok 'start
  25.         gettok
  26.         res = expr#(0)
  27.         PRINT res
  28.     END IF
  29. LOOP UNTIL le = 0
  30.  
  31. 'recursive descent token evaluator - added dummy arguments  (fixed set? by Ed Davis)
  32. SUB gettok ()
  33.     tc = tc + 1
  34.     token = tokens(tc)
  35.  
  36. FUNCTION expr# (x AS INTEGER)
  37.     DIM v AS DOUBLE
  38.     v = term#(0)
  39.     IF token = "+" THEN
  40.         gettok
  41.         v = v + expr#(0)
  42.     ELSEIF token = "-" THEN
  43.         gettok
  44.         v = v - expr#(0)
  45.     END IF
  46.     expr# = v
  47.  
  48. FUNCTION term# (x AS INTEGER)
  49.     DIM v AS DOUBLE
  50.     v = factor#
  51.     IF token = "*" THEN
  52.         gettok
  53.         v = v * term#(0)
  54.     ELSEIF token = "/" THEN
  55.         gettok
  56.         v = v / term#(0)
  57.     END IF
  58.     term# = v
  59.  
  60. FUNCTION factor# ()
  61.     DIM v AS DOUBLE
  62.     IF ASC(token) > 47 AND ASC(token) < 58 THEN 'nums
  63.         v = VAL(token)
  64.         gettok
  65.     ELSEIF ASC(token) = 40 AND ASC(token) <> 41 THEN 'match (...)
  66.         gettok
  67.         v = expr#(0)
  68.         gettok
  69.     END IF
  70.     factor# = v
  71.  
  72.  


Wait this was limited to only single digits? :P
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 20, 2019, 10:31:28 am
Quote
Wait this was limited to only single digits?

No i think NOT limited to one digit.
token should use any number like "12345"
First i will check again o2 version then i try fix qb64 version ok?
Title: Re: The Recursive Descent Parser
Post by: Ed Davis on March 20, 2019, 10:38:07 am
I don't think you guys have it fixed yet:

Wait this was limited to only single digits? :P

Correct.  There isn't a real scanner - it just assumes single characters, no spaces allowed.  I guess Aurel posted it to "get his feet wet" with QB64?  So it is at best a simple demo, nothing more.

I like your colors, by the way!
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 20, 2019, 11:26:03 am
In another words
if we wish to enter expression and evaluate it , we need TOKENIZER
to split our expression into tokens( read numbers & operators )
is that ok.
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 20, 2019, 12:14:14 pm
well i tryed  this without dummy load and with additional while loop
like i do in o2 but nothing ...
token taken are
1
+
2
  0

Title: Re: The Recursive Descent Parser
Post by: bplus on March 20, 2019, 12:32:03 pm
Aurel your attempts here have me skimming over Erik's massive and successful undertaking and having me appreciate all over again Erik's effort. :)

Title: Re: The Recursive Descent Parser
Post by: Aurel on March 20, 2019, 01:59:57 pm
Yes Mark
Erik really do big work with his SICK engine ...
and is not very easy to follow all what he do in it.
It is great but little bit too complex for me.
He use large amount of string processing which confuses me..
of course i understand them all but as i said is not easy to get what is what(for me
even i have experience in that things)- of course not like Ed ;)
Title: Re: The Recursive Descent Parser
Post by: Aurel on March 21, 2019, 10:55:50 am
Hi..
I just forund in my old programs one created by author of qb64
i called that program GALEON BASIC.
This program is posted on old forum ..long time ago.
There is a interesting way used for evaluating expression with GOTO command..
so can i publish it here ?
Title: Re: The Recursive Descent Parser
Post by: Ed Davis 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.