几个月前,我认为制作自己的编程语言会很酷。所以我做了。我把它命名为Pogo。如果你查看代码,你会看到大量的String#split
。这是一些描述我如何解析方法头的伪代码:
鉴于method public void main()
,我会查看是否String#startsWith("method")
。如果是这样,我String#split(" ")
并将第二个单词(公共)解析为可见性。我将第三个单词(void)视为返回类型,并检查它是原始类还是类。最后,我将最后一个单词视为名称并正确解析任何方法参数。
这种方法有效,但我知道这是错误的。你应该将所有东西都标记出来并像这样处理它。但是,我不太明白这是怎么回事。我知道我会使用Java的StringTokenizer
,但是如何解析方法头?给定method public void main()
,第一个标记是方法。我知道我正在使用一个方法,但我不能实例化我的Method
类,因为我还没有所有的信息。似乎我必须为StringTokenizer
声明一个循环,并且在它之外有大量变量,这几乎看起来比我现在的情况更糟。
tl; dr :我应该如何使用String标记化来解析自定义编程语言。我不是在寻找代码,更像是伪代码或想法。
谢谢!
答案 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
我们会像这样逐步完成:
method
,这是一个实现。我不保证它没有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的不同部分将遵循类似的思考过程。
在Bjarne Stroustrup的编程,原理和实践中使用C ++ 实现数学表达式解析器是一个很好的演练。将它改编为Java并使用其中的指南来实现其他类型的表达式解析器是合理的,就像编程语言那样。
我希望这会有所帮助。