几个月前,我认为创造自己的编程语言会很酷。于是我照做了。我给它起名叫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
, 下面就是这样一个实现。我不能保证它没有虫子。为了简洁起见,我有意错过了许多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的不同部分的解析将遵循类似的思维过程。
- 设计一个语法,
- 逐步了解如何解析语言,
- 编写代码。
在Bjarne Stroustrup的Programming, Principles and Practice Using c++ 中有一个很好的实现数学表达式解析器的介绍。将其适应于Java并使用那里的指导方针来实现其他类型的表达式解析器(如编程语言中的表达式解析器)是合理的。
我希望这对你有帮助。