用值替换表达式中的各种变量?

时间:2015-10-14 21:31:02

标签: java

在字符串s中,有变量和数组。例如,s可以是a - (b+A[B[2]])*d + 3,这些变量的值存储在单独的文件中。这是我的代码的一部分,我试图将当前字符串s中的任何变量替换为来自其他文件的相应值。

我只想替换标量变量而不是数组的变量,并且我有一个名为scalars的ArrayList,它存储了所有标量变量。因此,对于给定的示例,a=1b=2d=3我想将s变为1 - (2+A[B[2]])*3 + 3scalars也不包含重复变量,因此我的代码仅适用于单个非重复变量,例如a,b,c,而不适用于varx等变量。如何改进我的代码以适应各种情况,还是有更好的方法?

String s = expr; 

    if(scalars.size()>0){
        int j = 0; //make so duplicates can be used , so var can be used, so it doesn't detect arrays
        for(int k = 0; k<s.length(); k++){
            if(Character.isLetter(s.charAt(k))){
                s= s.substring(0,k) + this.scalars.get(j).value + s.substring(k+1,s.length());
                j++;
            }
        }
    }

编辑:完整评估代码(尚未完成)。我试图将它转换为没有变量的String,所以当我引入括号/括号时,我最终可以递归调用evalNoPB

    public float evaluate() { 

    String s = expr; 

    if(scalars.size()>0){
        int j = 0; //make so duplicates can be used , so var can be used, so it doesn't detect arrays
        for(int k = 0; k<s.length(); k++){
            if(Character.isLetter(s.charAt(k))){
                s= s.substring(0,k) + this.scalars.get(j).value + s.substring(k+1,s.length());
                j++;
            }
        }
    }
    System.out.println(s);
    float answer = 0;

    if(s==null || s.length() == 0){
        return 0;
    }

    //one single variable or just a number
    if(s.contains("+") == false && s.contains("-") == false && s.contains("*") == false && s.contains("/") == false && s.contains("[") == false &&s.contains("]") == false && s.contains("(") == false && s.contains(")") == false){ 
        if(scalars.size() == 0){
            answer = Float.parseFloat(s);
            return answer;
        }
        answer = this.scalars.get(0).value;
        System.out.println("one var/number loop");
        return answer;
    }


    //no parentheses/brackets
    if(s.contains("(") == false && s.contains(")") == false && s.contains("[") == false && s.contains("]") == false && (s.contains("+") == true || s.contains("-") == true || s.contains("*") == true || s.contains("/") == true)){
        System.out.println("no parens loop");
        answer = evalNoPB(s);
        return answer;
    }
    //make compiler happy 
    System.out.println("no loop");
    return 0;
    }

    //no parentheses/brackets
    private float evalNoPB(String s){ 

        float tempAns= 0;
        if(s.contains("*") == false && s.contains("/") == false && s.contains("+") == false && s.contains("-") == false){
            return Float.parseFloat(s);
        }

    if(s.length()-1>0){
        int i;
        boolean foundPlusMinus = false;
        for(i=s.length()-1; i>0; i--){
            if(s.charAt(i) == '+' || s.charAt(i) == '-'){
                System.out.println(i);
                foundPlusMinus = true;
                break; // keep value of i for substrings 
            }
            foundPlusMinus = false;
        } 

        if (foundPlusMinus == false) { // for loop went through and did not find + or -
            for(i=s.length()-1; i>0; i--){
                if(s.charAt(i) == '*' || s.charAt(i) == '/'){
                    System.out.println(i);
                    break; // keep value of i for substrings
        }
    }
    }

    String sub1 = s.substring(0,i);
    System.out.println(sub1);
    String sub2 = s.substring(i+1, s.length());
    System.out.println(sub2);

    if(s.charAt(i) == '+'){
        tempAns = evalNoPB(sub1) + evalNoPB(sub2);
    } else if(s.charAt(i) == '-'){
        tempAns = evalNoPB(sub1) - evalNoPB(sub2);
    }else if(s.charAt(i) == '*'){
        tempAns = evalNoPB(sub1) * evalNoPB(sub2);
    }else if (s.charAt(i) == '/'){
        float divisorCheck = evalNoPB(sub2);
        if(divisorCheck!= 0){
        tempAns = evalNoPB(sub1) / evalNoPB(sub2);
        }else { // cannot divide by 0 
            throw new IllegalArgumentException("cannot divide by 0");
        }
}
}
    return tempAns;

}  

1 个答案:

答案 0 :(得分:0)

您要求的是一个编译器,一个相当复杂的程序,它以某种编程语言解析输入并将其转换为另一种编程语言。

在这种情况下,输入语言将是包含数字,标量变量,数组变量,总和,乘法和括号的表达式。
输出语言是带有数字,数组变量,求和,乘法和括号的表达式(注意它不包含标量变量)。

有很多关于编译器的文献。这是一门跨越计算机科学大学学位课程的学科 如果你定义一种非常有限的语言,你可以通过简单的替换来逃避,就像在OP中一样。但是一旦你开始处理更多通用语言,比如长度大于1的变量,你就开始需要整个架构来构建编译器。

编译器有几个部分:

  1. 一个词法解析器。这打破了TOKENS中的输入。每个令牌都是标识符,操作符和类似的东西。词法分析器还可以从输入中删除空白。
  2. 语法分析器。一组用TOKENS定义语言的规则。通常使用context-free grammars完成。
  3. 语义翻译。这可以采用在语法分析器中构建的结构,也可以在语法分析器中交织。它将输入语言转换为输出语言。
  4. 以及其他一些可选部分,排列优化器,超出了本问题的范围。
  5. 词法分析器

    词法解析器是将输入分解为标记 例如,考虑输入&#34; 3 + a&#34;。
    预期的代币是:NUMBER PLUS IDENTIFIER

    我将根据正则表达式定义令牌:

    • 加:[+]
    • 减: -
    • MULTIPLY:[*]
    • DIVIDE:/
    • NUMBER:[0-9] +
    • IDENTIFIER:[a-zA-Z] +
    • OPEN_PARENTHESIS:[(]
    • CLOSE_PARENTHESIS:[)]
    • OPEN_BRACKET:\ [
    • CLOSE_BRACKET:\]
    • EOL:End Of Line没有正则表达式。当输入用尽时,词法解析器将返回此标记。

    这是词法解析器的代码 首先是一个包含令牌的令牌类,其类型和生成令牌的输入的子串。

    public class Token
    {
        public static enum TokenType
        {
            PLUS,
            MINUS,
            MULTIPLY,
            DIVIDE,
            NUMBER,
            IDENTIFIER,
            OPEN_PARENTHESIS,
            CLOSE_PARENTHESIS,
            OPEN_BRACKET,
            CLOSE_BRACKET,
            EOL // End of line
        }
    
        public Token( TokenType type, String value)
        {
            this.type = type;
            this.value = value;
        }
    
        private TokenType type;
        public String value;
    
        public TokenType getType()
        {
            return type;
        }
    
        @Override
        public String toString()
        {
            return value;
        }
    }
    

    接下来是词法分析器。

    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    import expressionparser.Token.TokenType;
    
    class Tokenizer
    {
        private final String input;
        private Matcher matcher;
        private Token lastToken;
    
        public Tokenizer( String input )
        {
            this.input = input;
            Pattern pattern =
                    Pattern.compile( "[+]|-|[*]|/|[0-9]+|[a-zA-Z]+|[(]|[)]|\\[|\\]|[ ]");
            matcher = pattern.matcher( input );
            lastToken = null;
        }
    
        public Token readAndComsumeToken( TokenType type ) throws ExpressionException
        {
            Token result = readToken();
            if ( result.getType()!=type )
                throw new ExpressionException("Erroneous exception" );
            lastToken = null;
            return result;
        }
    
        public Token readToken() throws ExpressionException
        {
            if (lastToken!=null)
                return lastToken;
    
            String value;
            // Read till a non blank is received
            do
            {
                if ( matcher==null )
                {
                    lastToken = new Token(TokenType.EOL, "");
                    return lastToken;
                }
                if ( !matcher.find() )
                  throw new ExpressionException("Erroneous exception" );
                value = matcher.group();
                if ( matcher.end() >= input.length() )
                {
                    // End of String
                    matcher = null;
                }
                if ( value.length()==0 )
                  throw new ExpressionException("Erroneous exception" );
            } while ( value.equals( " " ) ||
                     value.equals("\t") || value.equals("\n") ||
                     value.equals("\f") || value.equals("\r") );
            // Identify read token
            TokenType type;
            if ( value.equals("+") )
                type = TokenType.PLUS;
            else if ( value.equals("-") )
                type = TokenType.MINUS;
            else if ( value.equals("*") )
                type = TokenType.MULTIPLY;
            else if ( value.equals("/") )
                type = TokenType.DIVIDE;
            else if ( value.equals("(") )
                type = TokenType.OPEN_PARENTHESIS;
            else if ( value.equals(")") )
                type = TokenType.CLOSE_PARENTHESIS;
            else if ( value.equals("[") )
                type = TokenType.OPEN_BRACKET;
            else if ( value.equals("]") )
                type = TokenType.CLOSE_BRACKET;
            else
            {
                char firstChar = value.charAt(0);
                if ( firstChar>='0' && firstChar<='9' )
                    type = TokenType.NUMBER;
                else
                    type = TokenType.IDENTIFIER;
            }
            lastToken = new Token( type, value );
            return lastToken;
        }
    
        public void consumeToken() throws IllegalStateException
        {
            if ( lastToken==null )
                throw new IllegalStateException();
            lastToken = null;
        }
    }
    

    Tokenizer基于Pattern一个标准的java类,它可以按照正则表达式的规定分解字符串 构造函数为输入表达式创建Pattern匹配器 Pattern具有一个额外的正则表达式[ ],不包含在上一个令牌列表中。这会读取空格,但它们不会被Tokenizer转换为令牌,它们会被忽略。

    readToken从匹配器中获取下一个标记,忽略空格。它将其保存在lastToken中,因为语法分析器可能需要为同一个令牌多次调用readTokenreadToken需要始终返回相同的令牌,直到它被消耗为止。如果之前的呼叫中已经存在未消耗的令牌,则返回该令牌而不获取新的令牌。

    consumeToken通过将lastToken设置为null来消耗上次读取的令牌。在没有在readToken之前调用的情况下调用它是错误的。

    readAndComsumeToken是一种便捷方法。在某些情况下,语法分析器在读取之前会知道下一个标记是什么。在这种情况下,它需要读取它,验证它是预期的并消耗它。 readAndConsumeToken只需一次通话即可完成。

    语法分析器。

    对于语法分析器,我们首先创建一个定义输入语言的无上下文语法 我将使用Backus-Naur Form

    <wholeExpression>    ::= <expression> EOL
    <expression>         ::= <summand> <tailSum>
    <summand>            ::= <factor> <tailMultiplication>
    <factor>             ::= OPEN_PARENTHESIS <expression> CLOSE PARENTHESIS |
                             NUMBER |
                             IDENTIFIER <tailArray>
    <tailSum>            ::= PLUS <summand> <tailSum> |
                             MINUS <summand> <tailSum> |
                             ""
    <tailMultiplication> ::= MULTIPLY <factor> <tailMultiplication> |
                             DIVIDE <factor> <tailMultiplication> |
                             ""
    <tailArray>          ::= OPEN_BRACKET <expression> CLOSE_BRACKET <tailArray> |
                             ""
    

    有几种方法可以实现这种语法。甚至还有自动完成工具的工具 我将使用Java方法为每个语法的非终端实现它。它在语义翻译器中呈现,因为它与解析器交织在一起。

    语义翻译。

    ExpressionParser是语法分析器和语义翻译器的组合。它使用Tokenizer进行词法分析。

    import expressionparser.Token.TokenType;
    import java.util.List;
    
    public class ExpressionParser
    {
        private Tokenizer lex;
        private List<Scalar> scalars;
    
        public String compile( String expression, List<Scalar> scalars ) throws ExpressionException
        {
            lex = new Tokenizer(expression);
            this.scalars = scalars;
            return evalWholeExpression();
        }
    
        private String evalWholeExpression( ) throws ExpressionException
        {
            String result = evalExpression();
            lex.readAndComsumeToken(Token.TokenType.EOL);
            return result;
        }
    
        private String evalExpression() throws ExpressionException
        {
            String left = evalSummand();
            return evalTailSum( left );
        }
    
        private String evalSummand( ) throws ExpressionException
        {
            String left = evalFactor();
            return evalTailMultiplication( left );
        }
    
        private String evalFactor() throws ExpressionException
        {
            Token token = lex.readToken();
            if ( token.getType() == TokenType.OPEN_PARENTHESIS )
            {
                lex.consumeToken();
                String result = evalExpression();
                lex.readAndComsumeToken(TokenType.CLOSE_PARENTHESIS);
                return "(" + result + ")";
            }
            else if ( token.getType() == TokenType.NUMBER )
            {
                lex.consumeToken();
                return token.toString();
            }
            else if ( token.getType()==TokenType.IDENTIFIER )
            {
                lex.consumeToken();
                String tailArray = evalTailArray();
                if ( "".equals(tailArray) )
                {
                    String scalarValue = evaluateScalar( token.toString() );
                    return scalarValue;
                }
                else
                {
                    verifyIsNotScalar( token.toString() );
                    return token + tailArray;
                }
            }
            else
                throw new ExpressionException( "Incorrect expression" );
        }
    
        private String evalTailSum( String left ) throws ExpressionException
        {
            Token token = lex.readToken();
            if ( token.getType()==TokenType.PLUS )
            {
                lex.consumeToken();
                String right = evalSummand();
                return evalTailSum( left + "+" + right );
            }
            else if ( token.getType()==TokenType.MINUS )
            {
                lex.consumeToken();
                String right = evalSummand();
                return evalTailSum( left + "-" + right );
            }
            else
                return left;
        }
    
        private String evalTailMultiplication( String left ) throws ExpressionException
        {
            Token token = lex.readToken();
            if ( token.getType()==TokenType.MULTIPLY )
            {
                lex.consumeToken();
                String right = evalFactor();
                return evalTailSum( left + "*" + right );
            }
            else if ( token.getType()==TokenType.DIVIDE )
            {
                lex.consumeToken();
                String right = evalFactor();
                return evalTailSum( left + "/" + right );
            }
            else
                return left;
        }
    
        private String evalTailArray() throws ExpressionException
        {
            Token token = lex.readToken();
            if ( token.getType() == TokenType.OPEN_BRACKET )
            {
                lex.consumeToken();
                String result = evalExpression();
                lex.readAndComsumeToken(TokenType.CLOSE_BRACKET);
                return "[" + result + "]" + evalTailArray();
            }
            else
                return "";
        }
    
        private String evaluateScalar( String text ) throws ExpressionException
        {
            assert text!=null;
            for ( Scalar s : scalars )
            {
                if ( text.equals( s.identifier ) )
                    return "" + s.value;
            }
            throw new ExpressionException( "Incorrect expression" );
        }
    
        private void verifyIsNotScalar( String text ) throws ExpressionException
        {
            assert text!=null;
            for ( Scalar s : scalars )
            {
                if ( text.equals( s.identifier ) )
                    throw new ExpressionException( "Incorrect expression" );
            }
        }
    
    }
    

    compile为输入创建一个Tokenizer词法分析器并保存标量列表。然后它调用evalWholeExpression,它返回输入的翻译,并返回这样的翻译 如果输入无效,则抛出ExpressionException。此类异常将包含错误消息。我没有努力使其有用。处理错误信息本身就是一个完整的主题 有一种方法eval&lt; non-terminal&gt;对于我们无语境语法的每个非终端 语法的编写方式使得检查第一个令牌足以确定哪个分支可以用于具有多个选项的非终端。这样可以轻松创建eval方法。

    在示例中,请考虑evalTailSum。此方法实现此规则:

    <tailSum>            ::= PLUS <summand> <tailSum> |
                             MINUS <summand> <tailSum> |
                             ""
    

    本身和本规则使用的是:

    <expression>         ::= <summand> <tailSum>
    

    evalExpression调用evalSummand并获取可能求和(或减法)左侧的平移。然后它调用evalTailSum将此翻译传递给它;最后归还它的结果。

    &LT; tailSum&GT;有三个分支。

    1. 一个始终以SUM令牌开头的sumation。
    2. 一个总是以MINUS令牌开头的减法。
    3. 否则为空字符串。对于一组固定的可能令牌也会发生这种情况。但是这里没有必要处理这个问题。
    4. 因此evalTailSum获得lex.readToken()的下一个标记。然后......

      1. 如果它是PLUS则消耗它。调用evalSummand将其结果存储为right。并致电&#39; evalTailSum&#39;传递给它作为左值left + "+" + right。注意令牌消费和呼叫的序列如何匹配&lt; tailSum&gt;右侧的终端和非终端。规则。
      2. 如果是MINUS,则遵循与PLUS案件类似的程序。
      3. 否则将采用空字符串分支,并返回收到的left转换。
      4. 遵循相同的模式,为无上下文语法中的每个非终端创建方法。

        所有这些方法实际上并没有太多翻译。它们的输出与输入时的输出相同 但对于一种重要方法而言并非如此:evalFactor
        当它处理IDENTIFIER分支时,我们需要将变量转换为Scalars列表指定的值。

        实现标量很简单:

        public class Scalar
        {
            public Scalar( String identifier, int value )
            {
                this.identifier = identifier;
                this.value = value;
            }
            public final String identifier;
            public final int value;
        }
        

        在IDENTIFIER分支中evalFactor使用标识符,并根据&lt; factor&gt;调用evalTailArray。在无上下文语法中统治 如果没有数组子队列,则evalTailArray返回空字符串,因此IDENTIFIER必须是标量变量。在这种情况下,它调用evaluateScalar在标量列表中搜索该标量并返回其值以便它被翻译,或者如果没有找到它将抛出一个ExpressionException。 如果evalTailArray的结果不为空,则它必须是数组。我们根据OP没有翻译。但是我们通过调用verifyIsNotScalar来确认没有具有该名称的标量。如果这样的验证失败,那将是语义错误(如果我们有一个未列出的非数组变量也是如此),类似于&#34;未定义的变量&#34;或&#34;不兼容的类型&#34;您可能从C ++或Java编译器获得的错误。虽然我们的编译器抛出的所有其他错误都是类似于&#34;语法错误的语法错误:;预期&#34;或者#34;非法字符@&#34;等词汇错误。

        使用

        这个小程序使用以前的解析器并使用多个表达式对其进行测试。最后一个是不正确的表达。

        import java.util.ArrayList;
        import java.util.List;
        
        public class Main
        {
            public static void main(String[] args)
            {
                try
                {
                    final List<Scalar> scalars = new ArrayList<>();
                    scalars.add( new Scalar( "a", 1 ) );
                    scalars.add( new Scalar( "b", 2 ) );
                    scalars.add( new Scalar( "d", 3 ) );
                    scalars.add( new Scalar( "varx", 5  ) );
        
                    ExpressionParser parser = new ExpressionParser();
        
                    System.out.println( parser.compile("a - (b+A[B[2]])*d + 3", scalars));
                    System.out.println( parser.compile("5*(2+varx)+a", scalars));
                    System.out.println( parser.compile("B[a*384+(5+(5*(varx+3)))]+varx", scalars));
                    System.out.println( parser.compile("34+", scalars ) );
                }
                catch (ExpressionException ex)
                {
                    System.out.println( ex.getMessage() );
                }
            }
        }
        

        输出符合预期:

        1-(2+A[B[2]])*3+3
        5*(2+5)+1
        B[1*384+(5+(5*(5+3)))]+5
        Incorrect expression