PDA

View Full Version : Simple math expression evaluator



paul_nicholls
22-07-2010, 11:03 AM
Hi all,
I have created a simple math expression evaluator class using code & ideas from this great compiler/interpreter creation site:

http://compilers.iecc.com/crenshaw/

and I thought I would share it :)

It evaluates standard maths equations containing '-', '+', '*', '/', '(' and ')', and returns a floating point (Single) result.

It says if it encountered an error, and what it was too.

It handles floating point number input in this form currently:


<integer part>[.<fractional part>]

Unit unit_Evaluator;

Interface

Type
{................................................. .............................}
TExpressionEvaluator = Class
private
FLook: AnsiChar;
FBuffer: AnsiString;
FErrorMsg: AnsiString;

Function IsAlpha(Const c: AnsiChar): Boolean;
Function IsAddOp(Const c: AnsiChar): Boolean;
Function IsMulOp(Const c: AnsiChar): Boolean;
Function IsDigit(Const c: AnsiChar): Boolean;
Function IsWhiteSpace(Const c: AnsiChar): Boolean;
Procedure Error(Const aErrorMsg: AnsiString);
Function Factor: Single;
Function Term: Single;
Procedure Expected(s: AnsiString);
Procedure Match(s: AnsiString);
Procedure GetChar;
Procedure SkipWhiteSpaces;
Function GetNumber: Single;
Function Expression: Single;
public
Function Evaluate(Const aExpression: AnsiString; Var aExpressionResult: Single): Boolean;
Property ErrorMsg: AnsiString Read FErrorMsg;
End;
{................................................. .............................}

Implementation

Uses
SysUtils;

Const
{................................................. .............................}
cCR = #13;
cLF = #10;
cSpace = ' ';
cTab = ^I;

{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.IsAlpha(Const c: AnsiChar): Boolean;
Begin
Result := c In['a'..'z','A'..'Z'];
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.IsDigit(Const c: AnsiChar): Boolean;
Begin
Result := c In['0'..'9'];
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.IsAddOp(Const c: AnsiChar): Boolean;
Begin
Result := c In['-','+'];
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.IsMulOp(Const c: AnsiChar): Boolean;
Begin
Result := c In['/','*'];
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.IsWhiteSpace(Const c: AnsiChar): Boolean;
Begin
Result := c In[cCR,cLF,cSpace,cTAB];
End;
{................................................. .............................}

{................................................. .............................}
Procedure TExpressionEvaluator.Error(Const aErrorMsg: AnsiString);
Begin
FErrorMsg := aErrorMsg;
Raise Exception.Create(aErrorMsg);
End;
{................................................. .............................}

{................................................. .............................}
Procedure TExpressionEvaluator.Expected(s: AnsiString);
Begin
Error(s + ' Expected');
End;
{................................................. .............................}

{................................................. .............................}
Procedure TExpressionEvaluator.Match(s: AnsiString);
Var
i: Integer;
Begin
For i := 1 To Length(s) Do Begin
If FLook = s[i] Then
GetChar
Else
Expected('''' + s + '''');
End;
SkipWhiteSpaces;
End;
{................................................. .............................}

{................................................. .............................}
Procedure TExpressionEvaluator.GetChar;
Begin
If FBuffer = '' Then
FLook := #0
Else Begin
FLook := FBuffer[1];
Delete(FBuffer,1,1);
End;
End;
{................................................. .............................}

{................................................. .............................}
Procedure TExpressionEvaluator.SkipWhiteSpaces;
Begin
While IsWhiteSpace(FLook) Do
GetChar;
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.GetNumber: Single;
Var
Number: AnsiString;
Code: Integer;
Begin
If Not IsDigit(FLook) Then Error('Number Expected');
Number := '';
// read integer part
While IsDigit(FLook) Do Begin
Number := Number + FLook;
GetChar;
End;
If FLook = '.' Then Begin
// read fractional part
Number := Number + FLook;
Match(FLook);
If Not IsDigit(FLook) Then Error('Digit Expected after "."');
While IsDigit(FLook) Do Begin
Number := Number + FLook;
GetChar;
End;
End;
Val(Number,Result,Code);
If Code <> 0 Then Error('Illegal Number "'+Number+'"');

SkipWhiteSpaces;
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.Factor: Single;
Begin
If FLook = '(' Then Begin
Match('(');
Result := Expression;
Match(')');
End
Else
Result := GetNumber;
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.Term: Single;
Begin
Result := Factor;
While IsMulOp(FLook) Do Begin
Case FLook of
'*': Begin
Match('*');
Result := Result * Term;
End;
'/': Begin
Match('/');
Result := Result / Term;
End;
End;
End;
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.Expression: Single;
begin
If IsAddOp(FLook) Then
Result := 0
Else
Result := Term;

While IsAddOp(FLook) Do Begin
Case FLook of
'+': Begin
Match('+');
Result := Result + Term;
End;
'-': Begin
Match('-');
Result := Result - Term;
End;
End;
End;
End;
{................................................. .............................}

{................................................. .............................}
Function TExpressionEvaluator.Evaluate(Const aExpression: AnsiString; Var aExpressionResult: Single): Boolean;
Begin
Result := True;

FBuffer := aExpression;
FErrorMsg := '';
GetChar;
Try
aExpressionResult := Expression;
Except
Result := False;
End;
End;
{................................................. .............................}

{................................................. .............................}
End.


Enjoy!

cheers,
Paul

jdarling
22-07-2010, 01:10 PM
Ewww... Crenshaw Compilers.... Gotta love em!

When I started looking at compiler theory and started working on my own languages I started with Crenshaws work too.

A step up from this is to build a FORTH style compiler and use a basic stack to handle the manipulations. This quickly leads you into basic variables with local, global, and static (constants) visibility.

Then you can move into optimizers, dynamic lexers, virtual machine implementation, and all the other fun things that come with compilers/theory.

My first attempt at something like this was "TSimpleMath" I still have it on my website at http://eonclash.com/ViewProduct.php?ProductID=9 if you want something for comparison. I'd do it completely different today, but it works well and I haven't seen a need to rewrite it :)

- Jeremy

PS: I've spent a LOT of time doing things like this when you get stuck, there is a reason the best book on the subject is call "The Dragon Book".

paul_nicholls
22-07-2010, 10:59 PM
Ewww... Crenshaw Compilers.... Gotta love em!


LOL! :)


When I started looking at compiler theory and started working on my own languages I started with Crenshaws work too.

A step up from this is to build a FORTH style compiler and use a basic stack to handle the manipulations. This quickly leads you into basic variables with local, global, and static (constants) visibility.

:) I have already done a pascal-like scripting language complete with 'op-codes' + virtual machine, and a "compiler" for a virtual computer called "RetroBox" (http://www.retrogamedev.org/retrobox/StartingtheRetroBoxProjec.php) using his work ;)

It is lots of fun :D


Then you can move into optimizers, dynamic lexers, virtual machine implementation, and all the other fun things that come with compilers/theory.

My first attempt at something like this was "TSimpleMath" I still have it on my website at http://eonclash.com/ViewProduct.php?ProductID=9 if you want something for comparison. I'd do it completely different today, but it works well and I haven't seen a need to rewrite it :)

Thanks :)
I might take a look at it



- Jeremy

PS: I've spent a LOT of time doing things like this when you get stuck, there is a reason the best book on the subject is call "The Dragon Book".


I've heard of it of course, but never seen it myself :)

cheers,
Paul

DarkBow
23-07-2010, 09:25 AM
While I was studying compilers in the faculty we used Aflex and Ayacc to create a lexic and sintactic parser, which we used in our own little compiler.

These two are version of the Lex and Yacc tools rewritten for Ada, but I discovered you can use them to parse any language you want as long as you provide them with a LALR grammatic.

URL is http://www.ics.uci.edu/~arcadia/Aflex-Ayacc/aflex-ayacc.html

If you want any more information on this I can try to dig it out from my notes. :)

paul_nicholls
23-07-2010, 09:46 AM
Thanks :)

I DID have a pascal version of lex and yacc lying around a while ago, but never used them - didn't understand how, and I'm not sure I would have liked trying to modify the created compilers to my own needs anyway (unlike a recursive descent parser hand-written)...

cheers,
Paul

jdarling
23-07-2010, 12:43 PM
Thanks :)

I DID have a pascal version of lex and yacc lying around a while ago, but never used them - didn't understand how, and I'm not sure I would have liked trying to modify the created compilers to my own needs anyway (unlike a recursive descent parser hand-written)...

cheers,
Paul


That would be TPLY4.1 and can be found at http://www.musikwissenschaft.uni-mainz.de/~ag/tply/. I started to updated/rewrite them both a while back and then I realized I don't like Lex and Yacc :). The hand written lexer and syntax tree generators I write are easier to understand and modify for me. So, never went anywhere with them. Some place on my HD I have a working copy and an IDE if anyone is interested.

EDIT: Found it http://www.eonclash.com/tply41a/tply41a.zip

- Jeremy

DarkBow
26-07-2010, 07:05 AM
I DID have a pascal version of lex and yacc lying around a while ago, but never used them - didn't understand how, and I'm not sure I would have liked trying to modify the created compilers to my own needs anyway (unlike a recursive descent parser hand-written)...



oh you do not need to rewrite Lex or Yacc. You only need to provide them with a lexic and grammar and they create a lexic and sintactic parser for you.

Creating the lexic file for Lex is quite simple. Basically you do 2 things:

- specify your tokens and give them an internal identifier
- specify the form your variable, constants and literals can take by giving Lex a regular expression.

This takes no more than 1 page of "code", believe me. :)

The grammar file for Yacc needs some more work since you need to make sure your grammar complies with the LALR conditions. In the university we made it for a subset of the Ada language and needed around 1 week including polishing, so for a simple grammar like yours it should not take more than 1 day. :)