Quantcast
Channel: VBForums - CodeBank - Visual Basic 6 and earlier
Viewing all articles
Browse latest Browse all 1466

Pratt parsers and the building blocks of a scripting language.

$
0
0


This sample application is an implementation of a Pratt parser written in pure VB6 code. It demonstrates the use of the Parser to validate syntax and construct an expression tree from a text expression. Now I want to preface this by saying that if your intention is to simply evaluate expressions, then this is not what you would use. There are far simpler and faster methods for evaluating expressions. For example:-
Code:

Option Explicit

Public Function Eval(ByVal Expr As String)
Dim L As String, R As String
  Do While HandleParentheses(Expr): Loop

  If 0 Then
    ElseIf Spl(Expr, "Or", L, R) Then:  Eval = Eval(L) Or Eval(R)
    ElseIf Spl(Expr, "And", L, R) Then:  Eval = Eval(L) And Eval(R)
    ElseIf Spl(Expr, ">=", L, R) Then:  Eval = Eval(L) >= Eval(R)
    ElseIf Spl(Expr, "<=", L, R) Then:  Eval = Eval(L) <= Eval(R)
    ElseIf Spl(Expr, "=", L, R) Then:    Eval = Eval(L) = Eval(R)
    ElseIf Spl(Expr, ">", L, R) Then:    Eval = Eval(L) > Eval(R)
    ElseIf Spl(Expr, "<", L, R) Then:    Eval = Eval(L) < Eval(R)
    ElseIf Spl(Expr, "Like", L, R) Then: Eval = Eval(L) Like Eval(R)
    ElseIf Spl(Expr, "&", L, R) Then:    Eval = Eval(L) & Eval(R)
    ElseIf Spl(Expr, "-", L, R) Then:    Eval = Eval(L) - Eval(R)
    ElseIf Spl(Expr, "+", L, R) Then:    Eval = Eval(L) + Eval(R)
    ElseIf Spl(Expr, "Mod", L, R) Then:  Eval = Eval(L) Mod Eval(R)
    ElseIf Spl(Expr, "\", L, R) Then:    Eval = Eval(L) \ Eval(R)
    ElseIf Spl(Expr, "*", L, R) Then:    Eval = Eval(L) * Eval(R)
    ElseIf Spl(Expr, "/", L, R) Then:    Eval = Eval(L) / Eval(R)
    ElseIf Spl(Expr, "^", L, R) Then:    Eval = Eval(L) ^ Eval(R)
    ElseIf Trim(Expr) >= "A" Then:      Eval = Fnc(Expr)
    ElseIf Len(Expr) Then:              Eval = IIf(InStr(Expr, "'"), _
                            Replace(Trim(Expr), "'", ""), Val(Expr))
  End If
End Function

Private Function HandleParentheses(Expr As String) As Boolean
Dim P As Long, i As Long, C As Long
  P = InStr(Expr, "(")
  If P Then HandleParentheses = True Else Exit Function

  For i = P To Len(Expr)
    If Mid(Expr, i, 1) = "(" Then C = C + 1
    If Mid(Expr, i, 1) = ")" Then C = C - 1
    If C = 0 Then Exit For
  Next i

  Expr = Left(Expr, P - 1) & Str(Eval(Mid(Expr, P + 1, i - P - 1))) & Mid(Expr, i + 1)
End Function

Private Function Spl(Expr As String, Op$, L$, R$) As Boolean
Dim P As Long
  P = InStrRev(Expr, Op, , 1)
  If P Then Spl = True Else Exit Function
  If P < InStrRev(Expr, "'") And InStr("*-", Op) Then P = InStrRev(Expr, "'", P) - 1

  R = Mid(Expr, P + Len(Op))
  L = Trim(Left$(Expr, IIf(P > 0, P - 1, 0)))

  Select Case Right(L, 1)
    Case "", "+", "*", "/", "A" To "z": Spl = False
    Case "-": R = "-" & R
  End Select
End Function

Private Function Fnc(Expr As String)
  Expr = LCase(Trim(Expr))

  Select Case Left(Expr, 3)
    Case "abs": Fnc = Abs(Val(Mid$(Expr, 4)))
    Case "sin": Fnc = Sin(Val(Mid$(Expr, 4)))
    Case "cos": Fnc = Cos(Val(Mid$(Expr, 4)))
    Case "atn": Fnc = Atn(Val(Mid$(Expr, 4)))
    Case "log": Fnc = Log(Val(Mid$(Expr, 4)))
    Case "exp": Fnc = Exp(Val(Mid$(Expr, 4)))
    'etc...
  End Select
End Function

The above is a very fast bottom up recursive parser written by Olaf Schmidt which is suitable for evaluating expressions. You can find more about it in this thread.

This is not what Pratt parsers are about. You could use them to evaluate expressions but that is like killing a mosquito with a nuke.

So what is a Pratt parser?

A Pratt parser belongs to a family of left to right recursive parsing algorithms called shift reduce parsers. Olaf's algorithm is also a kind of shift reduce parser though I have no idea what his one is called. These parsers specialize in parsing expressions and programming language grammars by breaking down or reducing the input into a series of simpler elements. The Pratt parser in particular was invented by a man named Vaughan Pratt. It is particularly specialized in allowing programmers to very easily control operator precedence in whatever scripting or programming language they are creating. It is extremely easy to implement and the algorithm itself can be modeled quite easily to be modular to the point where you can design the parser once and just plug in classes to it to extend whatever language it is you're writing. This is what makes Pratt parsers so appealing.

The typical approach for writing a scripting language is to use parser generators because programming language grammars can be quite complicated and it can be very tricky to get them right if they are written by hand. However, there are algorithms suited to hand written parsers. The one most often recommended is recursive descent parsing which is very easy to write by hand. However, it is extremely difficult to control operator precedence with a recursive descent parser if you're writing it by hand. This is where you want a Pratt parser because like a recursive descent parser, it is also suitable for hand written parsers and what's more, you can combine it with a recursive descent parser to parse elements of a language that don't require taking operator precedence into account. For example, If...Then statements or While loops.

So what is this project about?

It started as a simple implementation of a Pratt parser capable of parsing simple expressions into expression trees. I got carried away and went a bit beyond that. The core of it is a Pratt parser that converts an expression into an expression tree which can then be evaluated to produce a result. There are two methods of evaluation, a simpler method which involves simply traversing the tree to calculate the result. The more complicated method involves traversing the tree to produce a series of instructions that when executed will produce the result. In other words, what I made is a very basic compiler.

The instructions are meant for a virtual machine not unlike VB6's own P-code engine or Java's bytecode virtual machine. In this case the virtual machine is one of my own invention. It is far simpler than a real world virtual machine and it is no where near as fast. It also has a far simpler instruction set with no support for jumping which means it cannot support looping or conditional branching. It is not meant to be a fully fledged and capable virtual machine to be used in real world scenarios. It is meant to demonstrate the some of the important principles behind virtual CPUs and should be taken as such.

This demo is capable of parsing highly complex expressions which includes being able to handle multiple variables, deeply nested function calls, and it can detect any and all syntax errors and report them. All of these abilities can be used as a very solid foundation for building a scripting language. All it would take to implement a scripting language is adding parslets to the core Pratt parser to parse assignment statements, If...Then statements, loops like For..Next or Do..While loops, Goto statement and whatever else there is. Conditional jump instructions would also need to be added to the virtual machine to support most of these. I intend to release a more sophisticated version with all of this but this would be done in VB.Net.

So what kind of input can this parser evaluate?

I will give a sample list of some of the more complicated expressions it can parse but before I do that I want to give a clear list of the functions implemented in this project. They are as follows:-
  • Sin
  • Cos
  • Tan
  • Sqr
  • Round
  • Abs
  • Max
  • Min
  • Avg
  • Random


Here are examples of use of the functions:-
Sin(0.7) : Sine of 0.7
Cos(0.7) : Cosine of 0.7
Tan(0.7) : Tangent of 0.7
Sqr(16) : Square root of 16
Round(3.4) : Will round to 3.0
Abs(-90) : Absolute value of -90 which is 90
Max(7,10,23,100) : Returns the largest value which is 100. This function can take 0 to unlimited arguments
Min(7,10,23,100) : Returns the smallest value which is 7. This function can take 0 to unlimited arguments.
Avg(7,10,23,100) : Returns the average of all the numbers. This function can take 0 to unlimited arguments.
Random(10,20) : Returns a random number between 10 and 20 inclusive of both 10 and 20.

Here are some examples of expressions that can be parsed:-
  • sqr(Random(100,500))
  • sqr(sqr(sqr(sqr(sqr(10000)))))
  • min(10,89,max(9,2,3,12,sqr(round(22.5))),45)
  • round(abs(-200+-544)/19)


It also supports implicit multiplication with variables or bracketed sub expressions. For example 2a+3a+3b or 3(90+2*3).

There are many more samples in the project itself. Also note that some of you might find a mismatch between what your calculator or the VB6 debug window produces and what my program produces for the result of certain expressions. The standard operators addition, subtraction, division and multiplication all the have precedence relationships you'd expect and using only those operators, you should get the same results regardless of where you calculated the result. However, there might be some differences when it comes to other operators. The MOD operator for instance, I gave it the same precedence as multiplication and division. The exponent operator(^) has the highest precedence, even higher than the unary minus operator (-). Also, it is right associative. These things mean that -2^3 is evaluated as -(2^3) and 2^3^4 would be evaluated as 2^(3^4). These may differ from how other evaluators like Excel or VB6 would evaluate them. But this is extremely easy to change. It would only take the changing of one or two values to give them whatever precedence and associativity you want. The Pratt parser was invented for this very reason of controlling operator precedence easily in hand written parsers.

Final thoughts

I'm very new to this kind of thing so do not take me for an expert on this subject. Rather, what I want you to take away from this is that writing a programming or scripting language is very approachable and if I can get this far towards it then anyone can do it. I'm no one special.

I will do a full blown scripting language implementation in the future in pure VB.Net code and if the gods are willing to grant me the insight, that one would even be able to compile into x86 native code and not just into a instructions for virtual machine. I look forward to doing that one.

Lastly, here is a little video of me demonstrating this app by plugging a few expressions into it:-



Anyways, good day to you all.
Attached Files

Viewing all articles
Browse latest Browse all 1466

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>