从零开始撸个计算器 基于goyacc 实现

   本示例主要作为备忘使用。
   目前考虑的计算器主要支持四则运算和括号,从实现的角度来说。本示例主要由三个部分组成,词法分
析模块、语法分析模块、回归测试模块。
   
   词法分析模块:词法分析模块主要将输入的表达式转化为一个个的 token 转交语法分析模块处理。
   比如表达式 “1”这个被词法分析解析成一个 “NUMBER” 类型,并将该类型的值 1 存储在 lval.num 中,
   这个变量定义在 语法分析模块中的 %union 域中。
// filename: calcLex.go

package simplelex

import (
    "text/scanner"
    "log"
    "strconv"
    "strings"
)

var LexPrintToken = false

type Token struct {
    Type int
    Str string
}

type simpleLex struct {
    scanner.Scanner
    value float64
}

// 这个接口必须实现,是词法分析的入口
func (s *simpleLex) Lex(lval *yySymType) int {
    r, lit := s.Scan(), s.TokenText()
    var token Token
    token.Str = lit

    switch r {
    case scanner.EOF:
        return 0
    case scanner.Int:
        i, _ := strconv.Atoi(lit)
        lval.num = float64(i)
        token.Type = scanner.Float
    case scanner.Float:
        lval.num, _ = strconv.ParseFloat(lit, 64)
        token.Type = scanner.Float
    default:
        token.Type = int(r)
    }

    // if LexPrintToken {
    //     fmt.Printf("<<token: %s, %s>>\r\n",scanner.TokenString(token.type), token.Str)
    // }
    if token.Type == scanner.Float {
        return NUMBER
    } else {
        return token.Type
    }
    // return token.Type
}

// 词法分析异常处理 该接口必须实现
func (s *simpleLex) Error(s1 string) {
    log.Printf("parse error: %s", s1)
}

// 计算入口
func Parse(code string) float64{
    s := new(simpleLex)
    s.Init(strings.NewReader(code))
    yyParse(s)
    return s.value
}
语法分析模块(filename: calcyacc.y):这个模块本质就是按照 yacc 的语法定义文法,goyacc 会将该文件转化为一
个分析引	擎。该文件中定义了 %type <num> 或 %token <num> 表示文法中出现了定义的这些非终结符或终结符将
默认访问 num字段。	若不这样写,在文法动作中需要使用 $1.num 使用这个变量。表达式的最终值通过 yylex 这个
接口强转类型为 simpleLex(该类型定义在词法分析文件中),并将值保存在 simpleLex 的 num 域中。
%{

package simplelex

%}

%union {
    num float64
}

%type <num> expression term factor
%token '+' '-' '*' '/' '(' ')'
%token <num> NUMBER

%%
top        : expression
                { 
                    if l, ok := yylex.(*simpleLex); ok {
                        l.value = $1
                    }
                }
           ;
expression : expression '+' term
                { $$ = $1 + $3 }
           | expression '-' term
                { $$ = $1 - $3 }
           | term
                { $$ = $1 }
           ;
term       : term '*' factor
                { $$ = $1 * $3 }
           | term '/' factor
                { $$ = $1 / $3 }
           | factor
                { $$ = $1 }
           ;
factor     : NUMBER
                { $$ = $1 }
           | '(' expression ')'
                { $$ = $2 }
           ;
%%

回归测试模块:这里只是利用 golang 语言自带的 testing 模块测试用例是否符合预期。在开展类似的分析项目的时候,回归测试的构建是很有必要的。原因是文法具备牵一发而动全身的特性,必须要谨慎的保证对文法的变更不影响已满足的特性。而对于大型的分析项目的构建,往往不能一次性达成目标。而是一个小目标的形式逐步推进。而回归测试时保证特定的唯一保证。


// filename: calcLex_test.go
package simplelex
import (
   "testing"
   )

func TestParse(t *testing.T) {
   tests := []struct {
       code string
       value float64
   }{
       {"1", 1},
       {"1+1", 2},
       {"1+2*3", 7},
       {"1+3/1", 4},
       {"1-1", 0},
       {"(1+2)*3", 9},
       {"(1+2)/3", 1},
       {"1+(2*3)", 7},
       {"3*2+1", 7},
       {"3*(2+1)", 9},
       {"1*2*3", 6},
       {"1+2+3", 6},
       {"1/2", 0.5},
       {"2/3", 0.6666666666666666},
   }

   for _, test := range tests {
       
       if value := Parse(test.code); value != test.value {
           t.Errorf("err Actual: %.16f Expect: %f", value, test.value)
       } else {
           t.Logf(" sucess %s = %f", test.code, test.value)
       }
   }
}

如何使用它: goyacc -o calcyacc.go calcyacc.y && go test -v
正常情况下你将会看到下面这个页面:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于委托实现的简单计算器示例,包含加、减、乘、除四种运算: ```csharp using System; namespace Calculator { class Program { delegate double Calculate(double x, double y); static void Main(string[] args) { Console.WriteLine("请输入第一个数:"); double num1 = double.Parse(Console.ReadLine()); Console.WriteLine("请输入第二个数:"); double num2 = double.Parse(Console.ReadLine()); Console.WriteLine("请选择运算符(+、-、*、/):"); string op = Console.ReadLine(); Calculate calc = null; switch (op) { case "+": calc = new Calculate(Add); break; case "-": calc = new Calculate(Subtract); break; case "*": calc = new Calculate(Multiply); break; case "/": calc = new Calculate(Divide); break; default: Console.WriteLine("非法运算符!"); break; } if (calc != null) { double result = calc(num1, num2); Console.WriteLine("计算结果为:" + result); } Console.ReadLine(); } static double Add(double x, double y) { return x + y; } static double Subtract(double x, double y) { return x - y; } static double Multiply(double x, double y) { return x * y; } static double Divide(double x, double y) { if (y == 0) { Console.WriteLine("除数不能为0!"); return 0; } else { return x / y; } } } } ``` 该示例中,定义了一个名为 `Calculate` 的委托类型,它接受两个 `double` 参数并返回一个 `double` 值。在 `Main` 方法中,通过用户输入的运算符选择不同的计算方法,并将其赋值给 `Calculate` 委托类型的变量。最后,通过调用委托变量可以执行相应的计算方法,并输出结果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值