本章简介
本章承接上一章,上一章我们介绍了如何处理复杂类型,这一张我们详细的介绍一个如何从token流中分析出一个语法结构。
代码概述
分析简单结构
- SchemeAtom:第一个字母是英文字母,一直扫描到空格为止,里面只存在英文和数字以及一些合法字符。
- SchemeNumber:以数字开头,后面的内容只允许是数字,这里我们不分析浮点数,所以不包含小数点
- SchemeBoolean:只有两种情况可以分析成Boolean,分别是
#t
,#f
。 - SchemeString:以双引号开头和结尾,里卖弄是任意字符。
- SchemeQuoted:以
'
开头,之后的内容的合法性由其他的token解析来保证。
分析复杂结构
基本思路
所有的复杂结构都是用括号进行包含的,而括号里面即可以包含简单结构也可以包含复杂结构,比如
(+simple(∗simple2simple1simple)complex3simple)complex
所以每次我们开始分析复杂结构的时候,也就是遇到一个左括号的时候,就需要建立一个
List<SchemeToken>
数据结构,一直分析到对应的右括号,这两个括号之间的内容作为
List<SchemeToken>
的元素,这个数据结构也是之后用于初始化所有复杂结构类的构造函数的参数,比如下面的第一个构造函数。
public class SchemeArithmetic extends SchemeList
implements BuiltInOperation<SchemeNumber, SchemeNumber, SchemeNumber>{
private String OpName;
public SchemeArithmetic(List<SchemeToken> Tokens) {
this(((SchemeAtom)Tokens.get(0)).getContent(), Tokens.subList(1, Tokens.size()));
}
private SchemeArithmetic(String OpName, List<SchemeToken> Arguments) {
super();
this.OpName = OpName;
this.content.addAll(Arguments);
}
.....
}
逐步分析
为了能够成功地构造整个复杂结构,我们需要一个栈,类型为Stack<List<SchemeToken>>
, 栈顶存放的是当前括号内的元素,下面我将一步一步分析,以上面的(+ (* 2 1) 3)
为例,我还是以数学公式来表示,因为UML图我不是很会画。
- 初始化栈
表达式:
(+(∗21)3)
栈大小:
0
栈顶List内容:
- 分析到第一个括号
表达式:
(+(∗21)3)
栈大小:
1
栈顶List内容:
- 分析到
+
号
表达式:
(+(∗21)3)
栈大小:
1
栈顶List内容:
- 分析到第二个括号
表达式:
(+(∗21)3)
栈大小:
2
栈顶List内容:
- 分析到
*
号
表达式:
(+(∗21)3)
栈大小:
2
栈顶List内容:
- 分析数字
2
表达式:
(+(∗21)3)
栈大小:
2
栈顶List内容:
Atom(′∗′)−→−−−−Number(2)
- 分析数字
1
表达式:
(+(∗21)3)
栈大小:
2
栈顶List内容:
Atom(′∗′)−→−−−−Number(2)−→−−−−Number(1)
- 分析到第一个右括号
弹出栈顶的
List<SchemeToken>
构造出一个
SchemeArithmetic
再add到栈顶的
List<SchemeToken>
的内容中
表达式:
(+(∗21)3)
栈大小:
1
栈顶List内容:
Atom(′+′)−→−−−−Arithmetic(′∗′,2,1)
- 分析到数字
3
表达式:
(+(∗21)3)
栈大小:
1
栈顶List内容:
Atom(′+′)−→−−−−Arithmetic(′∗′,2,1)−→−−−−Number(3)
- 分析到最后一个右括号
表达式:
(+(∗21)3)
栈大小:
0
栈顶List内容:
面向对象:多态,以及反射的使用
问题提出
上面的过程很好地阐述了如何构建一个复杂类,这里存在一个问题。
每次我们分析的结构都是以List<SchemeToken>
进行存储的,那么程序该如何通过一个List<SchemeToken>
构造对应的子类呢
用上面的例子说明,最后的List中的内容看起来是下面这样
Arithmetic(Atom(′+′),Arithmetic(′∗′,2,1),Number(3))
但实际上是这样的
Token(Token(′+′),Token(′∗′,2,1),Token(3))
即在List看来都是一个一个的Token,那么如何通过这个
List<SchemeToken>
构造出一个
SchemeArithmetic
?构造出来以后,再作为子类对象被父类进行引用。
问题解决
这个问题本质上不难解决,但是如何才能优雅地解决。我使用的方法是Java的反射机制,其实不使用反射也能够解决,但是我觉得不够优雅,仅个人审美。
代码如下
public SchemeToken getReflectionClass(List<SchemeToken> Tokens) {
if (!(Tokens.get(0) instanceof SchemeAtom) && !(Tokens.get(0) instanceof SchemeProcedure)) {
return new SchemeList(Tokens);
}
if(Tokens.get(0) instanceof SchemeProcedure) {
return new SchemeCall(Tokens);
}
//通过前两步的判断,这里一定是一个Atom,此处获取Atom
SchemeAtom Atom = (SchemeAtom) Tokens.get(0);
String ClassName;
//通过Atom的内容使用getRelectionName方法获取对应的类,比如+对应 SchemeArithmetic类,lambda对应SchemeProcedure类。如果getReflectionName的返回值和Atom的内容不一样,比如,“+”.equals(“SchemeArithmetic”)是false,那么就用反射执行子类的构建,如果是一样的,比如,Atom的内容是fun,那么getReflectionName的返回值也是fun,此时,就直接用SchemeCall类来进行构建即可
if((ClassName = getReflectionName(Atom)) != Atom.getContent()) {
if(VerifyArgumentCount(Tokens)) {
try {
Constructor constructor = Class.forName(ClassName).asSubclass(SchemeToken.class).
getConstructor(List.class);
return (SchemeToken) constructor.newInstance(Tokens);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} else {
Error.Print("the argument size if wrong");
return null;
}
} else {
return new SchemeCall(Tokens);
}
return null;
}
public String getReflectionName(SchemeAtom Atom) {
switch(Atom.getContent()) {
default:
return Atom.getContent();
case KW.PRONAME.ADD:
case KW.PRONAME.SUB:
case KW.PRONAME.MUL:
case KW.PRONAME.DIV:
case KW.PRONAME.MOD:
return KW.CLASSNAME.SCHEME_ARITHMECIT;
case KW.PRONAME.NUMEQ:
case KW.PRONAME.NUMGE:
case KW.PRONAME.NUMGT:
case KW.PRONAME.NUMLE:
case KW.PRONAME.NUMLT:
return KW.CLASSNAME.SCHEME_NUMBERCOMPARE;
case KW.PRONAME.STREQ:
case KW.PRONAME.STRGE:
case KW.PRONAME.STRGT:
case KW.PRONAME.STRLE:
return KW.CLASSNAME.SCHEME_STRINGCOMPARE;
case KW.RESERVEDWORD.DEFINE:
return KW.CLASSNAME.SCHEME_DEFINE;
case KW.RESERVEDWORD.SET:
return KW.CLASSNAME.SCHEME_SET;
case KW.RESERVEDWORD.IF:
return KW.CLASSNAME.SCHEME_IF;
case KW.RESERVEDWORD.COND:
return KW.CLASSNAME.SCHEME_COND;
case KW.RESERVEDWORD.LAMBDA:
return KW.CLASSNAME.SCHEME_PROCEDURE;
case "map":
return "SchemeComplex.SchemeMap";
case KW.RESERVEDWORD.APPLY:
return KW.CLASSNAME.SCHEME_APPLY;
}
}
结语
本章介绍了如何构建一个复杂类,下一章将介绍编译器中最重要的设计模式,观察者模式。