编程语言解析器的Java字符串标记

时间:2014-08-17 05:29:24

标签: java string tokenize stringtokenizer

几个月前,我认为制作自己的编程语言会很酷。所以我做了。我把它命名为Pogo。如果你查看代码,你会看到大量的String#split。这是一些描述我如何解析方法头的伪代码:

鉴于method public void main(),我会查看是否String#startsWith("method")。如果是这样,我String#split(" ")并将第二个单词(公共)解析为可见性。我将第三个单词(void)视为返回类型,并检查它是原始类还是类。最后,我将最后一个单词视为名称并正确解析任何方法参数。

这种方法有效,但我知道这是错误的。你应该将所有东西都标记出来并像这样处理它。但是,我不太明白这是怎么回事。我知道我会使用Java的StringTokenizer,但是如何解析方法头?给定method public void main(),第一个标记是方法。我知道我正在使用一个方法,但我不能实例化我的Method类,因为我还没有所有的信息。似乎我必须为StringTokenizer声明一个循环,并且在它之外有大量变量,这几乎看起来比我现在的情况更糟。

tl; dr :我应该如何使用String标记化来解析自定义编程语言。我不是在寻找代码,更像是伪代码或想法。

谢谢!

2 个答案:

答案 0 :(得分:2)

看起来它并不是一个令人信服的问题。在一个相当标准的解析器中有不同的阶段,令人信服 - 将你的输入分解成块("方法",""公共"" void", " main","(",")")和解析 - 获取令牌列表并根据语言的语法将它们组合在一起。保持这两个单独的尝试在令牌化阶段进行任何语法分析是有意义的。

编程语言是复杂的野兽,解析是一项专门的任务,有专门的工具来完成这样的任务。在unix / C世界中,有用于标记的lex / flex和用于解析的yacc / bison。在java中有我使用过的JavaCC,可能还有很多其他的。您会发现使用这些通常为解析器生成代码的工具要容易得多。在您的语法灵活的早期阶段,这尤其值得。更改尝试和调试一些复杂的手写代码的语法定义文件会更容易。

有时候在很晚的阶段,编译器会转向手工编写的解析器代码,但这些是复杂的状态机,需要大量的理论才能正确。

答案 1 :(得分:1)

我想首先指出,无论你做什么都不一定是"错误"方式,并没有真正的"对"这样做的方式。


  

给定方法public void main(),第一个标记就是方法。我知道我正在使用某种方法,但我无法实例化我的Method类,因为我还没有获得所有信息。

是的,这是对的。您正在寻找解析方法声明的正确方法。

例如,你可以为方法声明编写一个语法(这是基于你的Pogo实现和你的问题):

MethodDeclaration: 
    "method" Access ReturnType Name
Access:
    "public"
    "private"
    "protected"
Return:
    "void"
    "integer"
    "string"
Name:
    alphabetic-only-string

我们会像这样逐步完成:

  1. Parsing method
  2. 下一个令牌是否是有效的访问修饰符?
  3. 下一个令牌是否为有效的返回类型?
  4. 下一个令牌是有效名称吗?
  5. 如果是,则解析方法。
  6. 这是一个实现。我不保证它没有bug。为了简洁起见,我故意错过了许多Java"良好的编码实践"。

    class Method { 
        @Override
        public String toString() {
            return "Method [access=" + access + ", type=" + type + ", name=" + name
                    + "]";
        }
    
        Access access; 
        ReturnType type; 
        String name;
    
        public Method (Access access, ReturnType type, String name) { 
            this.access = access; 
            this.type = type; 
            this.name = name;
        }
    }
    
    enum Access {
        Public("public"), 
        Private("private"), 
        Protected("protected");
    
        private String token;
    
        Access(String token) { 
            this.token = token; 
        }
    
        String token() { return token; }
    }
    
    enum ReturnType { 
        Void("void"), 
        Integer("integer"), 
        String("string");
    
        private String token;
    
        ReturnType(String token) { 
            this.token = token; 
        }
    
        String token() { return token; }
    }
    
    class InvalidCodeException extends Exception {
        private final String message; 
    
        public InvalidCodeException(String string, Object... params) {
            message = String.format(string, params);
        }
    
        @Override
        public String getMessage() { 
            return message; 
        }
    }
    
    public class MethodParse {
        public static void main(String[] args) throws IOException, InvalidCodeException { 
            System.out.println(methodDeclaration());
        }
    
        static String tokens = "method public void main()";  
        static StreamTokenizer stream = new StreamTokenizer(new StringReader(tokens));
    
        static String nameDeclaration() throws IOException, InvalidCodeException { 
            stream.nextToken();
            for (char c : stream.sval.toCharArray()) { 
                if (Character.getType(c) != Character.UPPERCASE_LETTER && 
                    Character.getType(c) != Character.LOWERCASE_LETTER) { 
                    throw new InvalidCodeException("name expected, found %s", stream.sval);
                }
            }
            return stream.sval;
        }
    
        static ReturnType returnTypeDeclaration() throws IOException, InvalidCodeException { 
            stream.nextToken();
            for (ReturnType rt : ReturnType.values()) {
                if (rt.token().equals(stream.sval)) {
                    return rt; 
                }
            }
            throw new InvalidCodeException("access modifier expected, found %s", stream.sval);
        }
    
        static Access accessDeclaration() throws IOException, InvalidCodeException { 
            stream.nextToken();
            for (Access a : Access.values()) {
                if (a.token().equals(stream.sval)) {
                    return a; 
                }
            }
            throw new InvalidCodeException("access modifier expected, found %s", stream.sval);
        }
    
        static Method methodDeclaration() throws IOException, InvalidCodeException {
            stream.nextToken(); 
    
            if (!stream.sval.equals("method")) { 
                throw new InvalidCodeException("method expected, found %s", stream.sval); 
            }
    
            return new Method(accessDeclaration(), returnTypeDeclaration(), nameDeclaration());
        }
    }
    

    设计解析Pogo的不同部分将遵循类似的思考过程。

    1. 设计语法,
    2. 逐步了解语言的解析方式,
    3. 编写代码。
    4. 在Bjarne Stroustrup的编程,原理和实践中使用C ++ 实现数学表达式解析器是一个很好的演练。将它改编为Java并使用其中的指南来实现其他类型的表达式解析器是合理的,就像编程语言那样。

      我希望这会有所帮助。