【问题标题】:Tokenizer for math expression数学表达式的分词器
【发布时间】:2018-07-02 16:40:40
【问题描述】:

使用 StreamTokenizer 我正在编写一个词法分析器,它将标记一个数学表达式。

作为输入,我给出表达式(1+π)²(1−π)²+(5.3−-2)/6。 我希望它被标记为
( 1 + π ) ² ( 1 - π ) ² + ( 5.3 − - 2 ) / 6
但我得到( 1 +π ) ² ( 1 -π ) ² + ( 5.3 −-2 ) / 6

我知道我需要在输出的某些地方插入乘法运算符,稍后再做。

/* s: The inputted expression */
public static String tokenize(String s)[] throws IOException
{
    StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(s));
    tokenizer.parseNumbers();
    tokenizer.wordChars('a', 'z');
    tokenizer.wordChars('A', 'Z');
    tokenizer.wordChars('A', 'Z');
    tokenizer.wordChars(SQUARED, SQUARED); // the superscript 2
    tokenizer.wordChars(PI, PI);
    tokenizer.wordChars(SUB.charAt(0), SUB.charAt(0)); // subtract (takeaway)
    tokenizer.wordChars(NEG.charAt(0), NEG.charAt(0)); // negate
    tokenizer.wordChars('/', '/');
    tokenizer.wordChars('*', '*');
    tokenizer.wordChars('+', '+');
    tokenizer.ordinaryChar(',');
    tokenizer.ordinaryChar('/');    // do not consider / as comment start

    ArrayList<String> tokBuf = new ArrayList<>();
    while (tokenizer.nextToken() != StreamTokenizer.TT_EOF) {
        switch (tokenizer.ttype) {
            case StreamTokenizer.TT_NUMBER:
                tokBuf.add(String.valueOf(tokenizer.nval));
                break;
            case StreamTokenizer.TT_WORD:
                tokBuf.add(tokenizer.sval);
                break;
            default:
                tokBuf.add(String.valueOf((char) tokenizer.ttype));
        }
    }
    String ret[] = new String[tokBuf.size()];
    ret = tokBuf.toArray(ret);

    return ret;
}

【问题讨论】:

  • StringTokenizer 会将-- 视为单个令牌。您为这项工作使用了错误的工具。
  • @EJP 我用了两种不同的减法符号,一个小一个大;它们是不同的代码点
  • 无论如何,@EJP 是正确的。 StreamTokenizer 是错误的工具。它可以处理的语法非常有限。此外,Javadoc 不是很清楚,并且有强烈的迹象表明它不会正确处理0x00FF 以上的代码点(或根本不处理?)。编写自己的简单状态驱动词法分析器会更好,您可以在其中根据您的要求调整行为。否则,您只是在需要精细木工凿子的地方尝试使用钝头螺丝刀。
  • 几乎要说同样的话。 StreamTokenizer 或 Java 中的其他两个标记器类太简单了。它们是可爱的想法,但仅适用于简单的问题,也许是学生或其他东西。如果您想使用工具而不是从头开始编写自己的工具,请查看 Antlr 4 antlr.org

标签: java lexer


【解决方案1】:
enum TokType {
    FIRST,
    OPERAND,
    OPERATOR,
    LPAREN,
    RPAREN,
}

boolean shouldMultBeEmitted(TokType tt)
{
    return tt == TokType.OPERAND || tt == TokType.RPAREN;
}

public ArrayList<String> tokenize(String in)
{
    TokType prevTok = TokType.FIRST; /* keep track of the type of the prev. tok, so
                                        we know when to insert a mult. sign */

    String regex = "(?<=[-−+*/()])|(?=[-−+*/()])";
    String toks[] = in.split(regex);

    /* the string has been tokenized; insert any needed multiplication signs */
    ArrayList<String> ret = new ArrayList<>();

    for (String x : toks) {
        if (isNumeric(x) || x.equals("Ans") || x.equals("π")) {
            if (shouldMultBeEmitted(prevTok))
                ret.add("*");

            prevTok = TokType.OPERAND;
        }
        else if (x.equals(LPAREN)) {
            if (shouldMultBeEmitted(prevTok))
                ret.add("*");

            prevTok = TokType.LPAREN;
        }
        else if (x.equals(RPAREN))
            prevTok = TokType.RPAREN;
        else if (isOperator(x))
            prevTok = TokType.OPERATOR;

        ret.add(x);
    }
    return ret;
}

感谢 https://stackoverflow.com/a/15983144/2469027 提供 RegEx。

给读者的练习:在平方 (²) 运算符之后插入乘号,因此您在问题中提供的示例是正确的。

【讨论】:

    猜你喜欢
    • 2019-02-27
    • 1970-01-01
    • 1970-01-01
    • 2016-07-21
    • 2015-05-02
    • 1970-01-01
    • 2010-12-20
    • 2014-06-02
    相关资源
    最近更新 更多