Vous êtes sur la page 1sur 21

Écriture d’une calculatrice en C# à

l’aide de SableCC
Écrire une calculatrice est une tâche simple - il suffit d’ajouter neuf boutons étiquetés de 1 à
9 et d’ajouter un bouton plus et moins et nous sommes presque prêts à partir. Dans cette
entrée, je vais écrire une calculatrice appelée SimpleCalc qui n’a pas d’interface graphique,
mais qui prendra une expression arbitraire et en calculera les résultats. L’entrée que
j’utiliserai comme objectif immédiat est la suivante:

25-37+2*(1.22+cos(5))*sin(5)*2+5%2*3*sqrt(5+2)

Selon Google,le résultat est -9.83033875. Certains des sujets délicats que nous devrons
traiter sont lapriorité des opérateurs (multiplication avant addition, etc.), les expressions
imbriquées (2*1.22+cos(5) != 2*(1.22+cos(5))) etl’associativité (5+7 == 7+5 & 7-5 != 5-7 etc.).

Analyse de l’entrée à l’aide de SableCC


Avant d’effectuer des calculs, nous devons analyser l’expression d’entrée afin d’avoir une
représentation en mémoire de l’entrée. Nous devons avoir l’entrée représentée sous la forme
d’unarbre syntaxique abstraitqui définit l’ordre des opérations et nous permet de parcourir les
différentes parties de l’expression individuellement. Pour effectuer cette tâche, nous
utiliseronsSableCC.

SableCC est un générateur d’analyseurs qui génère des frameworks


orientés objet complets pour la construction de compilateurs, d’interpréteurs
et d’autres analyseurs de texte. En particulier, les frameworks générés
incluent des arbres de syntaxe abstraits intuitifs strictement typés et des
marcheurs d’arbres. SableCC maintient également une séparation nette
entre le code généré par la machine et le code écrit par l’utilisateur, ce qui
conduit à un cycle de développement plus court.

En bref, SableCC peut être utilisé pour générer automatiquement le code de l’analyseur
utilisé dans n’importe quel compilateur, ainsi que dans de nombreux autres cas où l’entrée
doit être analysée - comme dans ce cas. SableCC lui-même est écrit en Java parEtienne M.
Gagnonet lecode sourceest disponible gratuitement.

La sortie standard de SableCC est du code Java. Ainsi, l’analyseur que nous sommes sur le
point de générer sera transformé en un certain nombre de fichiers Java que nous pourrons
incorporer dans notre propre code source et étendre. Comme je vais écrire la calculatrice en
C #, je préférerais de loin travailler directement avec les fichiers sources C #, plutôt que
d’avoir à porter la sortie Java ou à l’appeler par d’autres moyens. Heureusement,Indrek
Mandrea créé une variante de SableCC qui générera l’analyseur en Java, C #, C ++,
O’Caml, Python, C, Graphviz Dot ou XML. Tout ce que nous avons à faire est de télécharger
le fichier zipsablecc-3-beta.3.altgen.20041114à partir de la page d’accueil de la page
SableCC d’Indrek. Une fois téléchargé et décompressé, nous pouvons l’exécuter, tant que
Java est installé. Créez d’abord un fichier bat avec le contenu suivant :

java -jar "C:Program FilesSableCCsablecc-3-beta.3.altgen.20041114libsab

Assurez-vous de remplacer mon chemin par le chemin dans lequel vous avez extrait le
paquet altgen SableCC. Lorsque nous invoquerons SableCC à partir de maintenant, nous le
ferons via ce fichier bat que j’ai choisi d’appelersablecc_altgen.bat, juste pour simplifier la
syntaxe.

Définition de la grammaire
Pour que SableCC puisse générer notre analyseur, nous devons d’abord définir le langage
qu’il doit prendre en charge. La façon dont nous le faisons est de définir la langue au format
(E)BNF. Je n’écrirai pas de tutoriel générique sur la façon d’écrire des grammeurs dans
SableCC car il y en a déjà un certain nombre de bons sur la page de documentation
deSableCC, ainsiqu’un de Nat Pryce qui n’estpas sur la page de documentation. Enfin, il y a
aussi uneliste de diffusion, bien que l’activité soit limitée.

Nous allons commencer par créer un nouveau fichier texte appelésimplecalc.sablecc, c’est là
que nous allons définir notre grammaire SimpleCalc. De plus, j’ai créé un nouveau fichier bat
appelé simplecalc_sable.bat avec le contenu suivant:

cls

sablecc_altgen -d generated -t csharp simplecalc.sablecc

Le fichier bat ci-dessus appellera celui que nous avons créé précédemment. -d
généréspécifie le nom du répertoire de sortie. -t csharpspécifie le type de code source de
sortie, C# dans ce cas. Le dernier argument est le nom du fichier sablecc d’entrée. À partir
de maintenant, nous pouvons simplement lancersimplecalc_sablepour démarrer le
processus de compilation SableCC.

Je vais d’abord publier le contenu complet du fichier simplecalc.sablecc, puis parcourir les
sections spécifiques une par une.

Package SimpleCalc;

Helpers

digit = ['0' .. '9'];

Tokens

number = (digit+ | digit+ '.' digit+);

add = '+';

sub = '-';

mul = '*';

div = '/';

mod = '%';

sqrt = 'sqrt';

cos = 'cos';

sin = 'sin';

lparen = '(';

rparen = ')';

Productions

exp {-> exp}

= {add} [left]:exp add [right]:factor {-> New exp.add


| {sub} [left]:exp sub [right]:factor {-> New exp.sub
| {factor} factor {-> factor.exp}

factor {-> exp}

= {mul} [left]:factor mul [right]:unary {-> New exp.mul


| {div} [left]:factor div [right]:unary {-> New exp.div
| {mod} [left]:factor mod [right]:unary {-> New exp.mod
| {unary} unary {-> unary.exp}

unary {-> exp}

= {number} number {-> New exp.number(number)}


| {sqrt} sqrt lparen exp rparen {-> New exp.sqrt(ex
| {cos} cos lparen exp rparen {-> New exp.cos(exp
| {sin} sin lparen exp rparen {-> New exp.sin(exp
| {paren} lparen exp rparen {-> New exp.paren(exp)}
;

exp_list {-> exp*}

= {single} exp {-> [exp.exp]}

| {multi} exp [tail]:exp_list {-> [exp.exp, tail.exp]


;

Abstract Syntax Tree

exp

= {add} [left]:exp [right]:exp

| {sub} [left]:exp [right]:exp

| {mul} [left]:exp [right]:exp

| {div} [left]:exp [right]:exp

| {mod} [left]:exp [right]:exp

| {paren} exp

| {sqrt} exp

| {cos} exp

| {sin} exp

| {number} number

Dans la grammaire, nous utilisons 5 sections différentes, Package, Helpers, Tokens,


Productions et Abstract Syntax Tree.

Package SimpleCalc;

La déclaration Package définit simplement le nom du package global. Si cela est exclu (ce
qui est valide selon SableCC), nos namesapces dans le code C# généré seront vides et
donc invalides.

Helpers

digit = ['0' .. '9'];

Les assistants sont essentiellement des espaces réservés que vous pouvez configurer et
utiliser dans le fichier SableCC. Ils n’ont pas de signification ou de fonctionnalité plus
profonde, c’est juste un moyen de pouvoir facilement exprimer du code commun par son
nom. Comme nous ferons référence auxchiffresplusieurs fois, il est utile de le définir comme
un assistant au lieu de répliquer ['0' .. '9']plusieurs fois dans le code. ['0' .. '9']signifie tous
les chiffres compris entre 0 et 9.

Tokens

number = (digit+ | digit+ '.' digit+);

add = '+';

sub = '-';

mul = '*';

div = '/';

mod = '%';

sqrt = 'sqrt';

cos = 'cos';

sin = 'sin';

lparen = '(';

rparen = ')';

Notez que je saute en avant et ignore la sectionProductionspour l’instant, j’y reviendrai dans
un instant. L’arborescence syntaxique abstraitedéfinit les nœuds qui seront présents dans
notre AST analysé. Chaque type d’opération et de fonction a un nœud correspondant dans
l’AST. Ainsi, et l’opération add consistera en un nœud Ajouter avec deux enfants - une
expression gauche et une expression droite. Ces expressions peuvent elles-mêmes être des
nombres constants ou des expressions imbriquées - puisqu’elles sont définies comme exp
qui est une référence récursive au typeexpAST réel.

Add, sub, mul, divetmodsont des opérateursbinaireset ont donc deux expressions enfants.
Paren, sqrt, cosetsin (et à certains égards, nombre) sont des opérateurs unaires en ce sens
qu’ils n’ont qu’un seul enfant/paramètre - une expression. Le nombre est un nœud feuille qui
exprime une constante numérique réelle.

La sectionProductionsdéfinit notre mappage à partir de l’entrée réelle à l’AST que nous


venons de définir.

Productions

exp {-> exp}

= {add} [left]:exp add [right]:factor {-> New exp.add


| {sub} [left]:exp sub [right]:factor {-> New exp.sub
| {factor} factor {-> factor.exp}

La première production que nous définissons est une production générique pour exprimer
des expressions. Notez que la façon dont nous définissons la priorité des opérateurs
consiste d’abord à exprimer l’opérateur le moins prioritaire (add&sub), puis à référencer les
opérations factorielles (mul,div,mod&unary) et ainsi de suite. exp {-> exp}signifie que la
syntaxe concrète d’une expression est également mappée dans le nœud de l’arbre
syntaxique abstrait appelé « exp ». ProductionsetAbstract Syntax Treesont deux espaces
de noms différents et peuvent donc partager les mêmes noms.

= {add} [left]:exp add [right]:factor {-> New exp.add(left, r

L’opération add est définie par une expression gauche suivie du jeton add (défini comme '+'
précédemment), puis d’une expression de facteur à droite, définissant ainsi la relation de
priorité entre l’opérationaddet les opérations factor. Enfin, nous définissons que l’opération
add mappe dans une nouvelle instance du nœud exp AST, ayant les expressions gauche et
droite comme paramètres (enfants dans l’AST). La sous-opération est presque identique à
l’opérateur add.

| {factor} factor {-> factor.exp}

Toutes les expressions de facteurs sont simplement mappées sur la production factorielle
définie ultérieurement.

factor {-> exp}

= {mul} [left]:factor mul [right]:unary {-> New exp.mul(lef


| {div} [left]:factor div [right]:unary {-> New exp.div(lef
| {mod} [left]:factor mod [right]:unary {-> New exp.mod(lef
| {unary} unary {-> unary.exp}

Les expressionsmul, divetmodsont fondamentalement identiques aux expressions add et


sub, sauf qu’elles définissent unaire comme la prochaine production de la chaîne de priorité
des opérateurs.

unary {-> exp}

= {number} number {-> New exp.number(number)}

| {sqrt} sqrt lparen exp rparen {-> New exp.sqrt(exp)}

| {cos} cos lparen exp rparen {-> New exp.cos(exp)}

| {sin} sin lparen exp rparen {-> New exp.sin(exp)}

| {paren} lparen exp rparen {-> New exp.paren(exp)}

La plus simple de toutes les expressions estl’expression numériqueunaire qui définit une
constante numérique. L’expression number est mappée dans un nouveau nœud AST du type
exp.number, avec le nombre réel comme paramètre. Les fonctionssqrt, cosetsindéfinissent
toutes l’entrée comme le nom de la fonction et l’expression de paramètre entre parenthèses.
Enfin, nous définissons la fonction {paren}unaire qui est une expression arbitraire entre
parenthèses. Cela est mappé dans le type exp.paren AST, en prenant l’expression arbitraire
comme paramètre. La fonction {paren} nous permet de différencier des expressions comme
« 5*2-7 » et « 5*(2-7) ».

exp_list {-> exp*}

= {single} exp {-> [exp.exp]}

| {multi} exp [tail]:exp_list {-> [exp.exp, tail.exp]}

La production finale est ce qui nous permet d’enchaîner les expressions. Sans laproduction
exp_list, seules des opérations uniques seraient autorisées (5+2, 3*7 etc.), pas des chaînes
d’expressions (5+2+3, 5*2+3, etc.). exp_list {-> exp*}définit que la production exp_list est
mappée dans une liste d’exp dans l’AST.

Quiconque a fait de la programmation fonctionnelle reconnaîtra larécursionde la queue qui


se déroule ici. S’il n’y a qu’une seule expression, nous la mappons dans une liste
d’expressions contenant une seule expression. S’il y a une seule expression et une liste
d’expressions qui la suivent (qui peuvent être une ou plusieurs expressions), nous la
mappons dans une liste d’expressions contenant la première expression ainsi que le reste
des expressions représentées par le paramètre tail.

Génération de l’analyseur
Une fois que nous avons défini la grammaire, nous sommes prêts à exécuter lefichier
simplecalc_sablebat, ce qui, espérons-le, donnera le résultat suivant:

D:\Webmentor Projekter\Eclipse Projects\SableCC\>simplecalc_sable -d ge


sharp simplecalc.sablecc

D:\Webmentor Projekter\Eclipse Projects\SableCC\>java -jar "C:Program F


eCCsablecc-3-beta.3.altgen.20041114libsablecc.jar" -d generated -t csha
plecalc.sablecc

SableCC version 3-beta.3.altgen.20040327

Copyright (C) 1997-2003 Etienne M. Gagnon <etienne.gagnon@uqam.ca> and

others. All rights reserved.

This software comes with ABSOLUTELY NO WARRANTY. This is free software


and you are welcome to redistribute it under certain conditions.

Type 'sablecc -license' to view

the complete copyright notice and license.

-- Generating parser for simplecalc.sablecc in D:Webmentor ProjekterEc


rojectsSableCCgenerated

Verifying identifiers.

Verifying ast identifiers.

Adding empty productions and empty alternative transformation if necess


Adding productions and alternative transformation if necessary.

computing alternative symbol table identifiers.

Verifying production transform identifiers.

Verifying ast alternatives transform identifiers.

Generating token classes.

Generating production classes.

Generating alternative classes.

Generating analysis classes.

Generating utility classes.

Generating the lexer.

State: INITIAL

- Constructing NFA.

..............................

- Constructing DFA.

...................................................

....................

- resolving ACCEPT states.

Generating the parser.

..............................

..............................

..............................

..

..............................

Maintenant, si nous regardons dans le répertoire généré, il devrait y avoir six fichiers:
analysis.cs,lexer.cs,nodes.cs,parser.cs,prods.cs et tokens.cs. Les fichiers doivent
contenir des classes dans l’espace de nomsSimpleCalc.

Impression de l’arborescence syntaxique abstraite


Pour nous aider, la première tâche que nous allons faire est simplement d’imprimer l’AST
afin que nous puissions vérifier que ce qui est analysé est correct. Créez une solution
appeléeSimpleCalcet copiez les fichiers générés ou créez un lien de solution vers le dossier.
Ajoutez un nouveau fichier appeléAstPrinter.cset collez le contenu suivant :

using System;

using SimpleCalc.analysis;

using SimpleCalc.node;

namespace SimpleCalc

class AstPrinter : DepthFirstAdapter

int indent;

private void printIndent()

Console.Write("".PadLeft(indent, 't'));

private void printNode(Node node)

printIndent();

Console.ForegroundColor = ConsoleColor.White;

Console.Write(node.GetType().ToString().Replace("SimpleCalc

if (node is ANumberExp)

Console.ForegroundColor = ConsoleColor.DarkGray;
Console.WriteLine(" " + node.ToString());

else

Console.WriteLine();

public override void DefaultIn(Node node)

printNode(node);

indent++;

public override void DefaultOut(Node node)

indent--;

LeDepthFirstAdapterest une classe générée automatiquement par SableCC. Il nous permet


de parcourir facilement la profondeur AST générée en premier, tout en nous donnant divers
points d’accroche en cours de route. Chaque nœud de l’arborescence possède une méthode
d’entrée et de sortie que nous pouvons remplacer. In est appelé avant que les enfants ne
soient traversés tandis que Out est appelé après que les enfants ont été traversés. Notez
que nous pouvons changer l’arbre pendant la traversée - bien que nous n’allons pas le faire.

Dans la classe AstPrinter, j’ai remplacé les méthodesDefaultInetDefaultOutqui sont


appelées pour chaque nœud, sauf si nous avons remplacé leurs méthodes par défaut
spécifiques. Dans la méthode In, nous augmentons le retrait, et de même nous le diminuons
dans la méthode Out. En outre, dans la méthode In, nous imprimons également le contenu
réel du nœud sur la console. S’il s’agit d’un nœud ANumberExp (le nom du nœud
correspondant au type de numéro dans l’AST), nous imprimons le numéro réel, sinon nous
imprimons simplement le nom du nœud lui-même.

Dans le fichier programme principal, collez ce qui suit :

using System;

using System.IO;

using SimpleCalc.lexer;

using SimpleCalc.node;

using SimpleCalc.parser;

namespace SimpleCalc

class Program

private static void Main(string[] args)

if (args.Length != 1)

exit("Usage: Simplecalc.exe filename");

using (StreamReader sr = new StreamReader(File.Open(args[0]


{

// Read source

Lexer lexer = new Lexer(sr);

// Parse source

Parser parser = new Parser(lexer);

Start ast = null;

try

ast = parser.Parse();

catch (Exception ex)

exit(ex.ToString());

// Print tree

AstPrinter printer = new AstPrinter();

ast.Apply(printer);

exit("Done");

private static void exit(string msg)

if (msg != null)

Console.WriteLine(msg);

else

Console.WriteLine();

Console.WriteLine("Press any key to exit...");

Console.Read();

Environment.Exit(0);

J’ai fait en sorte que le programme prenne un seul argument, un nom de fichier où notre
expression de calcul est écrite. En prenant un fichier comme paramètre, il m’est plus facile
de modifier l’expression directement dans Visual Studio sans avoir à configurer les
paramètres de lancement. Le seul paramètre de lancement qui doit être défini est l’argument
file.

Le programme tente d’ouvrir le fichier, puis instancie lelexer et l’analyseurgénérés


automatiquement par SableCC.

Maintenant, créons un nouveau fichier appelétest.sset collons-y l’expression suivante:25-


37+2*(1.22+cos(5))*sin(5)*2+5%2*3*sqrt(5+2). Si vous exécutez l’application à ce stade,
vous devriez voir une sortie semblable à la suivante :

En comparant l’AST imprimé avec l’expression d’entrée, nous verrons qu’ils correspondent à
la fois dans le contenu et en ce qui concerne la priorité des opérateurs. Il ne reste plus qu’à
effectuer le calcul réel de l’expression.
Calcul de l’expression basé sur l’arborescence
syntaxique abstraite
Ajoutez un nouveau fichier appeléAstCalculator.cset collez le contenu suivant :

using System;

using System.Collections.Generic;

using System.Globalization;

using SimpleCalc.analysis;

using SimpleCalc.node;

namespace SimpleCalc

class AstCalculator : DepthFirstAdapter

private double? result;

private Stack<double> stack = new Stack<double>();

public double CalculatedResult

get

if (result == null)

throw new InvalidOperationException("Must apply Ast

return result.Value;

public override void OutStart(Start node)

if (stack.Count != 1)

throw new Exception("Stack should contain only one elem

result = stack.Pop();

// Associative operators

public override void OutAMulExp(AMulExp node)

stack.Push(stack.Pop() * stack.Pop());

public override void OutAAddExp(AAddExp node)

stack.Push(stack.Pop() + stack.Pop());

// Non associative operators

public override void OutASubExp(ASubExp node)

double numB = stack.Pop();

stack.Push(stack.Pop() - numB);

public override void OutAModExp(AModExp node)

double numB = stack.Pop();

stack.Push(stack.Pop() % numB);

public override void OutADivExp(ADivExp node)

double numB = stack.Pop();

stack.Push(stack.Pop() / numB);

// Unary

public override void OutASqrtExp(ASqrtExp node)

stack.Push(Math.Sqrt(stack.Pop()));

public override void OutACosExp(ACosExp node)

stack.Push(Math.Cos(stack.Pop()));

public override void OutASinExp(ASinExp node)

stack.Push(Math.Sin(stack.Pop()));

public override void InANumberExp(ANumberExp node)

stack.Push(Convert.ToDouble(node.GetNumber().Text.Trim(), n
}

Je ne vais pas passer en revue toutes les parties de la calculatrice car de nombreuses
fonctions sont très similaires. Je vais décrire les plus importants ci-dessous.

private double? result;

private Stack<double> stack = new Stack<double>();

public double CalculatedResult

get

if (result == null)

throw new InvalidOperationException("Must apply AstCalculat

return result.Value;

Comme tous les nombres sont traités comme des doubles, le résultat sera également un
double. Le résultat peut être récupéré via la propriétéCalculatedResult, mais seulement une
fois le calcul effectué - nous vérifions donc si le résultat est nul ou non.

Tout en parcourant l’AST pour effectuer les calculs, nous maintenons l’état grâce à
l’utilisation d’une pile générique de doubles.

public override void OutStart(Start node)

if (stack.Count != 1)

throw new Exception("Stack should contain only one element at e

result = stack.Pop();

Au début, la pile sera vide. Une fois que nous avons traversé l’arborescence, la pile ne
devrait contenir qu’un seul élément - le résultat. Pour nous assurer qu’il n’y a pas d’erreurs,
nous nous assurons que la pile ne contient qu’un seul élément, après quoi nous le renvoyons
en le faisant sortir de la pile.

public override void InANumberExp(ANumberExp node)

stack.Push(Convert.ToDouble(node.GetNumber().Text.Trim(), new Cultu


}

L’opérateur unaire le plus important est probablement le nombre constant. Chaque fois que
nous sommes dans un nœudANumberExp, nous lisons le numéro et le poussons sur la pile.

public override void OutASqrtExp(ASqrtExp node)

stack.Push(Math.Sqrt(stack.Pop()));

Les autres opérateurs unaires suivent le même schéma. Nous faisons sauter la pile et
effectuons une opération mathématique sur la valeur popped, après quoi nous repoussons le
résultat sur la pile.

public override void OutAMulExp(AMulExp node)

stack.Push(stack.Pop() * stack.Pop());

Les opérateurs associatifs sont simples en ce sens qu’ils n’ont aucune exigence quant à
l’ordre dans lequel se trouvent les paramètres d’entrée. En tant que tel, une multiplication
simple fait apparaître deux nombres de la pile et renvoie le résultat multiplié sur la pile.

public override void OutASubExp(ASubExp node)

double numB = stack.Pop();

stack.Push(stack.Pop() - numB);

Les administrateurs non associatifs doivent d’abord afficher un nombre et le stocker dans
une variable temporaire. La raison pour laquelle nous devons le faire est que nous travaillons
avec une pileFIFO, ce qui signifie que le deuxième nombre ne sera pas le plus haut de la pile
et que nous ne pouvons donc pas effectuer le calcul dans une seule expression.

Maintenant que nous avons créé la classe AstCalculator, il ne nous reste plus qu’à modifier
la méthode principale pour qu’elle exécute la calculatrice.

// Print tree

AstPrinter printer = new AstPrinter();

ast.Apply(printer);

// Calculate expression

AstCalculator calculator = new AstCalculator();

ast.Apply(calculator);

Console.WriteLine("Calculation result: " + calculator.CalculatedResult)

Il suffit d’instancier un nouvelAstCalculatoraprès avoir imprimé l’AST, puis de l’appliquer à


l’AST. Si vous effectuez la modification ci-dessus et exécutez le programme, vous devriez
voir une sortie similaire à celle-ci:

Et voilà, le résultat est identique à celui fourni par Google à l’origine!

Conclusion
J’ai maintenant montré comment nous pouvons définir une grammaire de langue dans
SableCC et lui faire générer automatiquement un analyseur pour nous. En utilisant
l’analyseur SableCC, nous pouvons lire une chaîne d’entrée et la transformer en un arbre
syntaxique abstrait. Une fois que nous avons l’arbre syntaxique abstrait, nous pouvons
facilement le parcourir et le modifier.

Alors que SableCC ne pouvait à l’origine générer que la sortie Java, nous avons maintenant
plusieurs options pour le langage de sortie. Malheureusement, les classes C # générées ne
sont pas partielles - une fonctionnalité qui aurait été très utile une fois que nous aurions
commencé à faire des choses plus avancées avec l’AST. Il est assez facile de modifier les
six fichiers sources manuellement, ainsi que de mettre en place un script automatisé pour le
faire pour nous.

Une fois que nous avons lu l’entrée et l’avons transformée en AST, nous avons utilisé une
approche basée sur la pile pour la parcourir et calculer les sous-résultats d’une manière très
simple, en émulant la façon dont la plupart des langages de niveau supérieur fonctionnent en
interne.

Je ferai un suivi de ce post plus tard en étendant la fonctionnalité de grammaire et


d’interprète.

Mark S. Rasmussen
Je suis le CTO cheziPaperoù je me blottis contre les bases de données, le code
de moule et maintiens la responsabilité technique et d’équipe globale. Je suis un
conférencier passionné lors de groupes d’utilisateurs et de conférences. J’aime la
vie, les motos, la photographie et tout ce qui est technique. Dites bonjour
surTwitter, écrivez-moi une-mailou recherchez-moi surLinkedIn.

7 Comments 
1 Login

Join the discussion…

LOG IN WITH
OR SIGN UP WITH DISQUS ?

Name

Sort by Oldest 

Chris Cavanagh • 13 years ago


Mark - Couple related things you might find useful (maybe already
seen):

LINQ Dynamic Expressions (parses string into LINQ expression


LINQ Dynamic Expressions (parses string into LINQ expression
tree) - weblogs.asp.net/...

TinyPG parser generator (creates C#; pretty easy to use too):


http://www.codeproject.com/KB/recipes/TinyPG.aspx
△ ▽ • Reply •

Mark S. Rasmussen Mod • 13 years ago


@Chris

Thanks for the links. The dynamic LINQ queries are interesting but
I don't think they allow for defining your own grammars and parsing
of the tree. That is, it may be hackable to do so but it's definitely no
parser generator :)

I didn't know of TinyPG, I'll definitely have a look at it. I'm hoping to
find the time for writing a comparison between different parsing
solutions like SableCC, TinyPG, Irony, Coco/R - preferably using
the same language grammar so a valid comparison can be made.
△ ▽ • Reply •

Jakob Andersen • 13 years ago


I have made a response on how to accomplish the same thing
using Irony:

intellect.dk/...
△ ▽ • Reply •

Etienne M. Gagnon • 13 years ago


Hi Mark,

Neat example!

Your AstCalculator class could be greatly simplified. You don't need


to manage a stack, for results: you could simply use the implicit
execution stack.

You could write:

class AstCalculator : DepthFirstAdapter {

private double? lastResult;

public double CalculatedResult {

get {

if (result == null)

throw new InvalidOperationException("Must apply AstCalculator to


the AST first.");

return result.Value;

}
}
}

public void Visit(Node node) {

node.Apply(this);

private double EvaluateExp(PExp node) {

Visit(node);

double result = lastResult;


lastResult = null;

return result;

// Associative operators

public override void OutAMulExp(AMulExp node) {

lastResult = EvaluateExp(node.GetLeft()) *
EvaluateExp(node.GetRight());

}
...

△ ▽ • Reply •

Etienne M. Gagnon • 13 years ago


A few corrections to my code above:

1- "result" should have been "lastResult" in:

public double CalculatedResult {

get {

if (lastResult == null)

throw new InvalidOperationException("Must apply AstCalculator to


the AST first.");

return lastResult.Value;

}
}

2- CaseXXX methods should be used, instead of OutXXX, when


using EvaluateExp:

// Associative operators

public override void CaseAMulExp(AMulExp node) {

lastResult = EvaluateExp(node.GetLeft()) *
EvaluateExp(node.GetRight());

3- You can now call your calculator with:

// Calculate expression

AstCalculator calculator = new AstCalculator();

calculator.Visit(ast);

Console.WriteLine("Calculation result: " +


calculator.CalculatedResult);

Vous aimerez peut-être aussi