让我们让计算器程序支持变量的使用,这样程序就可以设置和获取变量的值了。从现在开始,我不会隐瞒我们要实现的是一种编程语言。它被命名为bkcalclang,因为它来自计算器。这次的代码是在上篇文章《使计算器支持语句块》的代码基础上写的。如果你发现你对当前的内容不熟悉,你可以回顾之前的章节。代码清单【go语言为例】packagemainimport("fmt""strconv""io/ioutil""./bklexer")varValueDictmap[string]float64typeNodeinterface{Eval()float64}typeBlockstruct{statements[]Node}funcNewBlock()*Block{return&Block{}}func(block*Block)AddStatement(statementNode){block.statements=append(block.statements,statement)}func(block*Block)Eval(){for_,statement:=rangeblock.statements{statement.Eval()}}typeNumberstruct{valuefloat64}funcNewNumber(token*BKLexer.Token)*Number{值,_:=strconv.ParseFloat(token.Source,64)return&Number{value:value}}func(number*Number)Eval()float64{returnnumber.value}typeNamestruct{namestring}funcNewName(token*BKLexer.Token)*Name{return&Name{name:令牌.Source}}func(name*Name)Eval()float64{ifvalue,found:=ValueDict[name.name];发现{返回值;}返回0.}类型BinaryOpt结构{optstringlhsNoderhsNode}funcNewBinaryOpt(token*BKLexer.Token,lhsNode,rhsNode)*BinaryOpt{return&BinaryOpt{opt:token.Source,lhs:lhs,rhs:rhs}}func(binaryOpt*BinaryOpt);Eval()float64{lhs,rhs:=binaryOpt.lhs,binaryOpt.rhsswitchbinaryOpt.opt{案例“+”:返回lhs.Eval()+rhs.Eval()案例“-”:返回lhs.Eval();;-returnlhs.Eval()case"*":returnlhs.Eval()*rhs.Eval()case"/":returnlhs.Eval()/rhs.Eval()}return0}typeAssignstruct{名称stringvalueNode}funcNewAssign(token*BKLexer.Token,valueNode)*Assign{return&Assign{name:token.来源,价值:价值}}func(分配*分配)Eval()float64{价值:=分配。价值。Eval()ValueDict[赋值。name]=valuereturnvalue}typeEchostruct{valueNode}funcNewEcho(valueNode)*Echo{return&Echo{value:value}}func(echo*Echo)Eval()float64{value:=echo.value.评估()Println(":=",value)返回值}funcparse(lexer*BKLexer.Lexer)*Block{block:=NewBlock()token:=lexer.NextToken()fortoken.TType==BKLexer.TOKEN_TYPE_NEWLINE{token=lexer.NextToken()}fortoken.TType!=BKLexer.TOKEN_TYPE_EOF{statement:=parse_statement(lexer)ifstatement==nil{returnnil;}token=lexer.GetToken()iftoken.TType!=BKLexer.TOKEN_TYPE_NEWLINE&&token.TType!=BKLexer.TOKEN_TYPE_EOF{返回nil;}block.AddStatement(statement)fortoken.TType==BKLexer.TOKEN_TYPE_NEWLINE{token=lexer.NextToken()}}returnblock}funcparse_statement(lexer*BKLexer.Lexer)Node{token:=lexer.GetToken()如果令牌.Name=="SET"{name:=lexer.NextToken()ifname.Name!="NAME"{returnnil}token=lexer.NextToken()iftoken。Name!="ASSIGN"{returnnil}lexer.NextToken()value:=parse_binary_add(lexer)ifvalue==nil{returnnil}returnNewAssign(name,value)}elseiftoken.Name=="ECHO"{};;词法分析器。NextToken()value:=parse_binary_add(lexer)if(value==nil){returnnil}returnNewEcho(value)}returnparse_binary_add(lexer)}funcparse_binary_add(lexer*BKLexer.Lexer)Node{lhs:=parse_binary_mul(词法分析器)iflhs==nil{returnnil}token:=lexer.GetToken()fortoken.Source=="+"||令牌。来源=="-"{词法分析器。NextToken()rhs:=parse_binary_mul(词法分析器)ifrhs==nil{returnnil}lhs=NewBinaryOpt(token,lhs,rhs)token=词法分析器GetToken()}returnlhs}funcparse_binary_mul(lexer*BKLexer.Lexer)Node{lhs:=factor(lexer)iflhs==nil{返回nnil}token:=lexer.GetToken()fortoken.Source=="*"||令牌。来源=="/"{词法分析器。NextToken()rhs:=factor(lexer)如果rhs==nil{returnnil}lhs=NewBinaryOpt(token,lhs,rhs)token=lexer。GetToken()}returnlhs}funcfactor(lexer*BKLexer.Lexer)Node{token:=lexer.GetToken()iftoken.Name=="LPAR"{lexer.NextToken()expr:=parse_binary_add(lexer)ifexpr==nil{returnnil}token:=lexer。GetToken()如果令牌。名称!=“RPAR”{返回零}词法分析器NextToken()返回expr}如果令牌。Name=="NUMBER"{number:=NewNumber(token)词法分析器。NextToken()returnnumber}iftoken.Name=="NAME"{name:=NewName(token)lexer.NextToken()returnname}returnnil}funcmain(){lexer:=BKLexer.NewLexer()lexer.AddRule();“\\d+\\.?\\d*","NUMBER")lexer.AddRule("[\\p{L}\\d_]+","NAME")lexer.AddRule("\\+","PLUS")lexer.AddRule("-","MINUS")lexer.AddRule("\\*","MUL")lexer.AddRule("/","DIV")lexer.AddRule("\\(","LPAR")lexer.AddRule("\\)","RPAR")lexer.AddRule("=","ASSIGN")lexer.AddIgnores("[\\f\\t]+")lexer.AddIgnores("#[^\\r\\n]*")lexer.AddReserve("set")lexer.AddReserve("echo")字节,err:=ioutil.ReadFile("../test.txt")如果出错!=nil{fmt.Println("readfaild")return}code:=string(bytes)lexer.Build(code)result:=parse(lexer)ifresult==nil{fmt.Println("nullresult")return}ValueDict=make(map[string]float64)result.Eval()}importrequiredpackageimport("fmt""strconv""io/ioutil""./bklexer")fmtprintoutputstrconv字符串转换io/ioutil读取files./bklexer用于词法分析,声明了一个存放变量值的字典varValueDictmap[string]float64我们将使用一个map类型的对象来访问值,并以此来实现变量赋值和值检索操作定义命名节点结构体typeNamestruct{namestring}funcNewName(token*BKLexer.Token)*Name{return&Name{name:token.Source}}Name结构体用于变量值相关操作,函数NewName接收参数*BKLexer.Token并实例化名称。定义命名节点的操作方法func(name*Name)Eval()float64{ifvalue,found:=ValueDict[name.name];发现{返回值;}return0.}原来Node的GetValue方法改名为Eval,这一点也适用于其他相关结构,需要注意。Name的Eval方法在ValueDict中查找对应的值并返回,不存在则返回0。定义赋值节点结构typeAssignstruct{namestringvalueNode}funcNewAssign(token*BKLexer.Token,valueNode)*Assign{return&Assign{name:token.Source,value:value}}定义存储赋值的Assign结构语句信息,name为变量名,value为对应值的节点结构。可以使用NewAssign函数实例化Assign结构。定义赋值节点的操作方法func(assign*Assign)Eval()float64{value:=assign.value.Eval()ValueDict[assign.name]=valuereturnvalue}该方法会执行该成员的执行结果value存储在ValueDict中并返回值。定义输出节点的结构体typeEchostruct{valueNode}funcNewEcho(valueNode)*Echo{return&Echo{value:value}}Echo结构体存储了一个Node类型的成员值,我们使用NewEcho对其进行实例化。定义输出节点的操作方法func(echo*Echo)Eval()float64{value:=echo.value.Eval()fmt.Println(":=",value)returnvalue}在这个方法中,我们首先获取echo然后打印出成员值的值,最后返回值。添加处理语句的函数。由于我们使用parse_statement函数作为处理语句的函数,所以需要在一些地方做相应的修改,比如语法分析入口的parse函数:fortoken.TType!=BKLexer.TOKEN_TYPE_EOF{statement:=parse_statement(词法分析器)ifstatement==nil{returnnil;}我们定义如下函数来处理语句funcparse_statement(lexer*BKLexer.Lexer)Node{token:=lexer.GetToken()iftoken.Name=="SET"{name:=lexer.NextToken()ifname.Name!="NAME"{returnnil}token=lexer.NextToken()iftoken.Name!="ASSIGN"{returnnil}lexer.NextToken()value:=parse_binary_add(lexer)ifvalue==nil{返回nil}returnNewAssign(name,value)}elseiftoken.Name=="ECHO"{lexer.NextToken()value:=parse_binary_add(lexer)if(value==nil){returnnil}returnNewEcho(value)}返回parse_binary_add(lexer)}如果发现初始token名称为SET,则判断为赋值操作,取下一个token作为变量名,取下一个判断是否为赋值符号。如果成功,则解析后面的内容,构造一个赋值节点.NextToken()iftoken.Name!="ASSIGN"{returnnil}lexer.NextToken()value:=parse_binary_add(lexer)ifvalue==nil{returnnil}returnNewAssign(name,value)如果当前令牌名称为ECHO,判断为打印输出,需要跳过当前token,解析表达式。如果解析成功,则用解析结果构建一个打印输出节点并返回,否则函数返回nil。}elseiftoken.Name=="ECHO"{lexer.NextToken()value:=parse_binary_add(lexer)if(value==nil){returnnil}returnNewEcho(value)}添加变量值的解析将替换previousparse_number函数重命名为factor【这不是必须的操作】:funcfactor(lexer*BKLexer.Lexer)Node{我们在factor函数中加入变量名解析代码:iftoken.Name=="NAME"{name:=NewName(token)lexer.NextToken()returnname}Definitionlexerruleslexer.AddRule("\\d+\\.?\\d*","NUMBER")lexer.AddRule("[\\p{L}\\d_]+","NAME")lexer.AddRule("\\+","PLUS")lexer.AddRule("-","MINUS")lexer.AddRule("\\*","MUL")lexer.AddRule("/","DIV")lexer.AddRule("\\(","LPAR")lexer.AddRule("\\)","RPAR")lexer.AddRule("=","ASSIGN")lexer.AddIgnores("[\\f\\t]+")lexer.AddIgnores("#[^\\r\\n]*")lexer.AddReserve("set")lexer.AddReserve("echo")这里需要添加变量名规则、赋值符号规则,并添加两个保留字set和echo。读取文件分析计算字节,err:=ioutil.ReadFile("../test.txt")iferr!=nil{fmt.Println("readfailed")return}code:=string(bytes)lexer.Build(code)result:=parse(lexer)ifresult==nil{fmt.Println("nullresult")return}ValueDict=make(map[string]float64)result.Eval()注意我们在执行结果ValueDict必须在.Eval()之前实例化。使用测试脚本测试测试内容:echo1+2#plusecho3-4#hereisacommentecho5*6#mulecho7/8echo1+(2-3)*4/5#compositesetpi=3.14setr=5echo2*pi*r*r运行结果:?goruncalc.go:=3:=-1:=30:=0.875:=0.199999999999999996:=157部分《支持If语句》,欢迎关注。
