2015-10-29 38 views
0

,以创建一个计算器,我使用了Java程序的调度场算法(https://en.wikipedia.org/wiki/Shunting-yard_algorithm)。我差不多完成了,但我仍然需要执行功能。我遇到了一个问题:我想让计算器在放到一起时自动乘以像x和y这样的变量 - 例如:计算器将xy转换为x * y。另外,我希望计算器将(x)(y)转换为(x)*(y)和x(y)为x *(y)。我已经做了所有的这种使用下面的代码:分流码功能

infix = infix.replaceAll("([a-zA-Z])([a-zA-Z])", "$1*$2"); 
infix = infix.replaceAll("([a-zA-Z])\\(", "$1*("); 
infix = infix.replaceAll("\\)\\(", ")*("); 
infix = infix.replaceAll("\\)([a-zA-Z])", ")*$1"); 

(在我的计算器,变量名总是单个字符)
这现在的伟大工程,但是当我实现的功能这个意愿,当然,不行。它会将“sin(1)”变成“s * i * n *(1)”。我怎样才能使这个代码做乘法转换只为运营商,而不是功能?

回答

3

预处理输入解析并不是实现你想要什么好办法。文本替换无法知道解析算法知道什么,并且您也丢失了原始输入,这对打印有用的错误消息很有用。

相反,你应该根据上下文决定做什么。保持先前解析的令牌的类型为输入开头的特殊类型。

如果前一个标记是一个值标记–一个数字,一个变量名称或一个子表示–的结束标记,当前一个是值标记也发出一个额外的乘法运算符。如果负的价值代币否则否定后,发现这是一个减法:

同样的逻辑也可以用来确定一个减号是否是一元的否定或二进制减法。

您的想法将x(y)转换为x * (y)当然会与函数调用语法冲突。

0

我们可以把它分成两部分。有一个用于括号表达式的规则,另一个用于乘法。

而不是维基百科文章,这是故意为了解释的目的而简化的文章,我会按照Parsing Expressions by Recursive Descent这个更详细的例子来处理括号内的表达式。

这是我用我的解析器可以与隐含乘法工作的代码。我有多字母变量名称,并使用空格来分隔不同的变量,所以你可以有“2 pi r”。

protected void expression() throws ParseException { 
    prefixSuffix(); 
    Token t = it.peekNext(); 
    while(t!=null) { 
     if(t.isBinary()) { 
      pushOp(t); 
      it.consume(); 
      prefixSuffix(); 
     } 
     else if(t.isImplicitMulRhs()) { 
      pushOp(implicitMul); 
      prefixSuffix(); 
     } 
     else 
      break; 
     t=it.peekNext(); 
    } 
    while(!sentinel.equals(ops.peek())) { 
     popOp(); 
    } 
} 

这需要一些其他功能。

我使用一个单独的标记化步骤,其破坏了输入到离散的令牌。 Tokens类有许多方法,尤其是Token.isBinary()测试如果运算符是二元运算符,如+,=,*,/。另一种方法Token.isImplicitMulRhs()测试令牌是否可以出现在隐式乘法的右侧,这对于数字,变量名称和左括号是成立的。

Iterator<Token>用于输入流。 it.peekNext()查看下一个令牌,并且it.consume()移至输入中的下一个令牌。

pushOp(Token)将令牌推入运算符堆栈,popOp删除一个和。pushOp具有处理不同运算符优先级的逻辑。弹出操作者如果它们具有较低的优先级特别值得注意

protected void pushOp(Token op) 
{ 
    while(compareOps(ops.peek(),op)) 
     popOp(); 
    ops.push(op); 
} 

的是implicitMul人工令牌具有相同优先级的乘法,其被压入操作栈。

prefixSuffix()处理表达式,它可以是数字和带有可选前缀的后缀运算符的变量。这将识别“2”,“x”,“-2”,“x ++”,从输入中删除令牌并将其添加到输出/运算符堆栈中。

我们可以认为这个程序在BNF作为

<expression> ::= 
      <prefixSuffix> (<binaryOp> <prefixSuffix>)* // normal binary ops x+y 
      | <prefixSuffix> (<prefixSuffix>)* // implicit multiplication x y 

处理括号prefixSuffix()完成。如果检测到左括号,则它将递归调用expression()。为了检测匹配的右括号,特殊的哨兵令牌被压入操作员堆栈。当在输入中遇到右括号时,主循环会中断,并且操作员堆栈上的所有操作员都会弹出,直到遇到标记并且控制返回到prefixSuffix()。代码可能类似于

void prefixSuffix() { 
    Token t = it.peekNext(); 
    if(t.equals('(')) { 
     it.consume(); // advance the input 
     operatorStack.push(sentinel); 
     expression(); // parse until ')' encountered 
     t = it.peekNext(); 
     if(t.equals(')')) { 
      it.consume(); // advance the input 
      return; 
     } else throw Exception("Unmatched ("); 
    } 
    // handle variable names, numbers etc 
} 
0

另一种方法可能是使用标记,类似于解析器的工作方式。

第一阶段是将输入文本转换为一个标记列表,这些标记列表是表示找到的实体类型及其值的对象。 例如,你可以有一个变量标记,它的值是变量的名字('x','y'等等),开放或闭括号的标记等。 因为我假设你知道提前计算器可以使用的函数的名称,您还将拥有一个函数令牌,其值是函数名称。 因此,令牌化阶段的输出会区分变量和函数。

实现,这不是太硬,只是一直尝试首先匹配函数名, 所以“罪”将被识别为一个功能,而不是作为三个变量。

现在第二阶段可以插入缺失的乘法运算符。这会不会现在是困难的,因为你知道你只需插入他们之间:

{VAR,RIGHT_PAREN}和{VAR,LEFT_PAREN,功能}

但从来没有功能和LEFT_PAREN之间。