Skip to main content
  1. internet/

antlr4-减号和负号

·975 words·2 mins·

一切正常的正整数运算 #

grammar calculator;

stat : expr;

expr : expr op=('*'|'/') expr # MulDiv
     | expr op=('+'|'-') expr # AddSub
     | '(' expr ')' # parens
     | INT # num
     ;

MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;

INT  : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;

此时,正整数的加减乘除能够正常计算。然而如果计算负数,则不能正常计算。这是因为我们没有处理负号。

支持负数运算 #

负号和减号冲突 #

支持使用负号,则需要修改INT规则,修改为INT : '-'? [0-9]+ ;即可。

grammar calculator;

stat : expr;

expr : expr op=('*'|'/') expr # MulDiv
     | expr op=('+'|'-') expr # AddSub
     | '(' expr ')' # parens
     | INT # num
     ;

MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;

INT  : '-'? [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;

此时解析-1+1,一切正常:

但如果解析1-1,则会解析失败:

这是因为在词法解析的过程中,将1-1解析为了两个token1-1,并由于不符合语法规则而自动忽略了第二个token。

使用~表示负号 #

如果能够使用其他符号来表示负号,则能够解决负号和减号冲突的问题。

grammar calculator;

stat : expr;

expr :  expr op=('*'|'/') expr # MulDiv
     | expr op=('+'|'-') expr # AddSub
     | '(' expr ')' # parens
     | INT # num
     ;

MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;

INT  : '~'? [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;

当然,这不是一个优雅的解决方案,因为~会让用户或者开发者感到困惑。

使用括号或者空格 #

使用括号来将负数包括,或者使用空格都可以使得解析正常,然而使用上还是比较复杂,且很容易漏掉。

独立为语法规则 #

可以将负数处理为语法规则,如:

grammar calculator;

stat : expr;

expr : 
     expr op=('*'|'/') expr # MulDiv
     | expr op=('+'|'-') expr # AddSub
     | '(' expr ')' # parens
     | '-' INT # NegNum
     | INT # num
     ;

MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;

INT  : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;

这时候能够正常解析1-1,1--2等负数运算。如-1--1

但是对于-(2+3)则会解析失败:

所以需要修改下规则:

grammar calculator;

stat : expr;

expr :
     expr op=('*'|'/') expr # MulDiv
     | expr op=('+'|'-') expr # AddSub
     | '(' expr ')' # parens
     | '-' expr # NegNum
     | INT # num
     ;

MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;

INT  : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;

此时一切正常:

但是-2+3会解析为:

此时会先计算2+3,再将结果取负。

需要提高负数规则的优先级:

grammar calculator;

stat : expr;

expr : '-' expr # NegNum
     | expr op=('*'|'/') expr # MulDiv
     | expr op=('+'|'-') expr # AddSub
     | '(' expr ')' # parens
     | INT # num
     ;

MUL : '*' ;
DIV : '/' ;
ADD : '+' ;
SUB : '-' ;

INT  : [0-9]+ ;
WS : [ \t\r\n]+ -> skip ;

此时一切正常。

单元测试 #

用单元测试测一下,确保所有的场景都能够正常解析:

package main

import (
	. "antlr4-go-example/calculator/parser"
	"github.com/antlr/antlr4/runtime/Go/antlr/v4"
	"testing"
)

func TestNegativeNum(t *testing.T) {
	var tests = []struct {
		Input string
		Want  int
	}{
		{
			Input: "-1",
			Want:  -1,
		},
		{
			Input: "1-3",
			Want:  -2,
		},
		{
			Input: "1--3",
			Want:  4,
		},
		{
			Input: "1-2*3",
			Want:  -5,
		},
		{
			Input: "1-2*3",
			Want:  -5,
		},
		{
			Input: "-1+3",
			Want:  2,
		},
		{
			Input: "1---3",
			Want:  -2,
		},
		{
			Input: "-1-(2+3)",
			Want:  -6,
		},
	}
	for _, v := range tests {
		t.Run(v.Input, func(t *testing.T) {
			input := antlr.NewInputStream(v.Input)
			lexer := NewcalculatorLexer(input)
			stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)
			parser := NewcalculatorParser(stream)

			calculator := NewCalcListener()
			antlr.ParseTreeWalkerDefault.Walk(calculator, parser.Stat())
			result := calculator.pop()
			if result != v.Want {
				t.Errorf("want %d got %d", v.Want, result)
			}
		})
	}
}

相关文档 #

  1. ANTLR 4中处理负号

  2. 文中的代码地址