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



几个月前,我认为创造自己的编程语言会很酷。于是我照做了。我给它起名叫Pogo。如果你看一下代码,你会看到大量的String#split。下面是一些描述如何解析方法头的伪代码:

给定method public void main(),我看String#startsWith("method")是否。如果是,我将String#split(" ")并将第二个单词(public)解析为可见性。我将第三个字(void)作为返回类型,并检查它是一个原语还是一个类。最后,我将最后一个单词作为名称,并正确解析任何方法参数。

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

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

谢谢!

看起来这不是一个真正的标记化问题。在相当标准的解析器中,有不同的阶段,标记化-将输入分解为块("method","public","void","main","(",")")和解析-获取标记列表并根据语言语法将它们组合起来。将这两者分开是有意义的,不要试图在标记化阶段进行任何语法分析。

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

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

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


给定public void main()方法,第一个标记将是method。我知道我正在使用一个方法,但是我不能实例化我的method类,因为我还没有所有的信息。

是的,那是正确的。您在正确的轨道上解析方法声明。

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

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

我们会像这样逐步执行:

    解析出现在method
  1. 下一个令牌是有效的访问修饰符吗?
  2. 下一个令牌是有效的返回类型吗?
  3. 下一个令牌是有效的名称吗?
  4. 如果是,解析该方法。

下面就是这样一个实现。我不能保证它没有虫子。为了简洁起见,我有意错过了许多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. 编写代码。

在Bjarne Stroustrup的Programming, Principles and Practice Using c++ 中有一个很好的实现数学表达式解析器的介绍。将其适应于Java并使用那里的指导方针来实现其他类型的表达式解析器(如编程语言中的表达式解析器)是合理的。

我希望这对你有帮助。

相关内容

  • 没有找到相关文章

最新更新