本计算器,首先分析一个计算表达式组成的语句串,然后计算出表达式的结果并输出。计算器中能实现以下功能:
ü 支持小数和负数的输入和运算,例如2.1,-1.73,2.3E-2(科学数),-3+(-90)*5.0等;
ü 支持常见的运算符,如+,-,*,/,^(乘幂)。例如3+2,4^2(42);
ü 支持常规的运算符优先结合关系,即先乘除,最后加减,同级运算符按照从左到右的顺序结合;
ü 支持括号及括号的嵌套;
ü 支持三角函数和对数函数以及幂运算(实现了Sin,Cos,Tan,Ln,Log,Lg,^等);
ü 表达式中支持变量的引入,可以对变量进行赋值,取值的操作比如a=1;b=2;a+b;
ü 变量名以字母打头,变量名中只能出现字母;
ü 对于语法匹配错误给出相应的出错提示,比如变量未定义。
2.1. Calc.g文件
grammar Calc;
options
{
output=AST;
ASTLabelType=CommonTree;
language=CSharp3;
}
tokens
{
NEGSYM;
}
public
prog: ( stat {System.Console.WriteLine($stat.tree==null?"null":$stat.tree.ToStringTree());} )+ ;
stat: expr NEWLINE -> expr
| ID '=' expr NEWLINE -> ^('=' ID expr)
| NEWLINE ->
;
expr: multExpr (('+'^ |'-' ^) multExpr)*;
multExpr: atomSYM (('*'^ |'/' ^) atomSYM)* ;
atomSYM :
'-' atom -> ^(NEGSYM atom)
| atom
;
atom: INT
| ID
| DOUBLE
| '(' ! expr ')' !
| 'Sin''(' ! expr')' !
| 'Cos''(' ! expr')' !
| 'Tan''(' ! expr')' !
| '^''(' ! expr ',' ! expr')' !
| 'Ln''(' ! expr')' !
| 'Lg''(' ! expr')' !
| 'Log''(' ! expr ',' ! expr')' !
;
ID : ('a'..'z'|'A'..'Z')+ ;
INT : '0'..'9'+ ;
DOUBLE :'_'?
(('0'..'9')+ '.' ('0'..'9')* EXPONENT?
| '.' ('0'..'9')+ EXPONENT?
| ('0'..'9')+ EXPONENT)
;
WS : (' '|'\t'|'\r'|'\n')+ { Skip(); } ;
NEWLINE: (';')+ ;
EXPONENT : ('e'|'E') ('+'|'-')? ('0'..'9')+ ;
2.2. CalcTree.g文件
tree grammar CalcTree;
options
{
tokenVocab=Calc;
ASTLabelType=CommonTree;
language=CSharp3;
}
@header
{
using System;
using System.Collections;
}
@members { Hashtable memory = new Hashtable();}
public
prog: stat+ ;
public
stat: expr {System.Console.WriteLine($expr.value);}
| ^('=' ID expr) {memory.Add($ID.text, $expr.value);}
;
public
expr returns [double value]
: ^('+' a=expr b=expr){$value = a+b;}
| ^('-' a=expr b=expr){$value = a-b;}
| ^('*' a=expr b=expr) {$value = a*b;}
| ^('/' a=expr b=expr) {$value = a/b;}
| ^(NEGSYM a=expr ) {$value = -1*a;}
| #('^' a=expr b=expr) {$value = Math.Pow(a,b);}
| #('Sin' x=expr) {$value=Math.Sin(x);}
| #('Cos' x=expr){$value=Math.Cos(x);}
| #('Tan' x=expr){$value=Math.Tan(x);}
| #('Ln' x=expr){$value=Math.Log(x);}
| #('Lg' x=expr){$value=Math.Log10(x);}
| #('Log' a=expr b=expr){$value=Math.Log(a)/Math.Log(b);}
| ID
{
$value = 0;
if ( memory.Contains($ID.text)) $value = (double)memory[$ID.text];
else System.Console.WriteLine("undefined variable "+$ID.text);
}
| INT
{
$value = (double)System.Convert.ToDouble($INT.text);
}
| DOUBLE
{
$value = System.Convert.ToDouble($DOUBLE.text);
}
;
使用antlrworks把g文件生成了相应的C#代码文件。可使用CTRL+SHIFT+G键生成,CTRL+R可检测语法错误。启动antlrworks 1.4.3使用java –jar antlrworks-1.4.3.jar,见下图:
Calc.g文件经编译生成了CalcLexer.cs,CalcParser.cs,Calc.tokens文件。
CalcTree.g文件经编译生成了CalcTree.cs,CalcTree.tokens文件。
然后在Visual Studio 2010中新建一个Calc项目,同时添加ANTLR运行库文件Antlr3.Runtime.dll。并且把生成的文件添加到Calc项目中,并且增加input.txt输入文件(存放输入数据)以及Calc.cs主文件(Main函数所在)。最终完成后如图所示:
为了不必每次都要重新添加g文件生成的代码到文件,让他们自动替换,我把antlrworks的代码生成目录设置为Calc项目代码所在文件。见下图
现在就该编写Calc.cs文件了,文件代码如下:
using System;
using System.Collections.Generic;
using System.Text;
using Antlr.Runtime;
using Antlr.Runtime.Tree;
using System.IO;
namespace Calc
{
class Calc
{
public static void Main(String[] args)
{
try
{
Console.WriteLine("CSharp计算器 V1.0 By wangyun 12.6.6\n");
Console.WriteLine("【输入】");
Console.WriteLine("从input.txt文件读取表达式...");
string filePath = "input.txt";
FileStream forOutput = File.Open(filePath, FileMode.Open);
StreamReader sr = new StreamReader(forOutput);
Console.WriteLine("读入表达式:");
Console.WriteLine(sr.ReadToEnd());
Console.WriteLine("表达式读入已完成");
sr.Close();
forOutput.Close();
FileStream fs = File.Open(filePath, FileMode.Open);
ANTLRInputStream input = new ANTLRInputStream(fs);
fs.Close();
CalcLexer lexer = new CalcLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
CalcParser parser = new CalcParser(tokens);
Console.WriteLine("\n【输出】");
Console.WriteLine("语法树:");
AstParserRuleReturnScope<CommonTree, IToken> t = parser.prog();//取得语法树
CommonTree ct = (CommonTree)t.Tree;
CommonTreeNodeStream nodes = new CommonTreeNodeStream(ct);
CalcTree walker = new CalcTree(nodes);
Console.WriteLine("计算结果:");
walker.prog();//计算结果
}
catch (System.Exception ex)
{
Console.Write("出现错误:");
Console.WriteLine(ex.Message);
}
Console.ReadKey();
}
}
}
然后在input.txt文件里面输入需要测试的语句,保存,然后生成Calc。之后运行就可查看结果。这里以3.0+(-4);为例,input文件及运行结果如下: