本章简介
本章主要介绍SchemeToken类的子类SchemeList,这个类是所有复杂类的父类,所有的SchemeList的子类包括自身都具有用()
包含的结构,比如
(+ 1 2 3)
(map + '(1 2 3) '(2 3 4))
类的设计
类图
看起来很复杂,下面我会一一介绍每个类的作用
子类介绍
这里为了展现子类中的每个元素所对应的成员变量,我就用数学公式表示,不用代码表示了,还未实现的先不进行列举,但是如果把下面的结构联系代码看懂了的话,不难推出其他的结构。
- SchemeArithmetic
- SchemeNumberCompare
- SchemeStringCompare
- SchemeDefine or SchemeSet
- SchemeCall
- SchemeApply
- SchemeProcedure
- SchemeIf
BuiltInOperation<T, U, R>
接口介绍
接口代码
import java.util.function.BiFunction;
/**
*
* @param <T> the type of arg1
* @param <U> the type of arg2
* @param <R> the type of result
*/
public interface BuiltInOperation<T, U, R> {
BiFunction<T, U, R> Operation();
}
实现了此接口的类
观察上面的类图,可以发现,有三个类实现了BuiltInOperation
接口,分别是
-SchemeArithmetic:+
, -
, *
, /
,%
-SchemeNumberCompare:>
, <
, <=
, >=
, =
-SchemeStringCompare:string?
, string>?
, string>=?
, string<=?
冒号前面是类名,后面是内置(BuiltInOperation)的操作,其实还有更多的内置的操作,这里就不一一列举了,如果全部列举,对于学习Java语言本身和解释器本身都没有好处。
这么设计的原因
其实说一个题外话,我的代码的设计风格都是模仿日本的一位程序员-青木峰郎的设计风格,感觉这位程序员在类的设计方面,即使有很少的共同之处都会对其进行抽象。我这里也是,即使只有三个类需要实现这个接口,我还是对其进行了抽象,之后只要有这个类的instance调用了Operation()
方法就可以获取对应的BuiltInOperation
代码举例
这里以SchemeArithmetic.java举例,配合完整的加法操作
public class SchemeArithmetic extends SchemeList
implements BuiltInOperation<SchemeNumber, SchemeNumber, SchemeNumber>{
private String OpName;
......
@Override
public BinaryOperator<SchemeNumber> Operation() {
switch(OpName) {
default:
System.out.println("illegal operation name");
System.exit(0);
case "+": return (SchemeNumber n1, SchemeNumber n2) ->
{return new SchemeNumber(n1.getContent() + n2.getContent());};
case "-": return (SchemeNumber n1, SchemeNumber n2) ->
{return new SchemeNumber(n1.getContent() - n2.getContent());};
case "*": return (SchemeNumber n1, SchemeNumber n2) ->
{return new SchemeNumber(n1.getContent() * n2.getContent());};
case "/":return (SchemeNumber n1, SchemeNumber n2) ->
{return new SchemeNumber(n1.getContent() / n2.getContent());};
case "%":return (SchemeNumber n1, SchemeNumber n2) ->
{return new SchemeNumber(n1.getContent() % n2.getContent());};
}
}
}
根据接口中的定义,如果调用Operation()
返回的是一个函数式接口BiFunction
,假设我们这个类中的OpName
是一个 +
,那么返回的函数式接口就是
(SchemeNumber n1, SchemeNumber n2) ->
{return new SchemeNumber(n1.getContent() + n2.getContent());}
这是一个lambda表达式,接受两个SchemeNumber类型的操作数,返回一个新的SchemeNumber对象。
此时如果我们要进行如下的加法操作要怎么办呢
(+ 1 2 3 4 5)
我们都知道,加法操作只能接受两个操作数,可是这个表达式后面有不定个数的操作数,从这里我们可以进行推断,这里其实是一个reduce
操作的语法糖,实际的语法在Scheme里面也可以表示:
(apply + '(1 2 3 4 5))
那在解释的过程中我们该如何进行表示才能最方便呢,这就需要用到Java8函数式编程风格了
//suppose token:SchemeArithmetic
List<SchemeNumber> Args = token.getArgs();
BiFunction<SchemeNumber, SchmemNumber, SchemeNumber> func = token.Operation();
SchemeNumber Result = Args.stream().reduce(func).get();
这里将所有的参数作为一个流进行看待,然后用func
对其进行reduce
即可.
SchemeList的意义
SchemeList是一个具体类而不是一个抽象类的原因
其实在这里我自认为SchemeList的地位是有点尴尬的,因为如果要表示一个列表数据,我们可以用'(1 2 3)
来进行表示,如果要表示一个调用,我们可以用(f 1 2)
来进行表示,但是,有没有一种情况是完全用(x y z)
来表示的呢,肯定是有的,所以这里我没有把这个类设计为一个抽象类,而是可以真真正正的表示一个数据结构
SchemeList的出现场景
这几种场景下,或者说类似的场景下,我们可以用SchemeList进行数据的包装,但是这个类不能用于计算,如果说出现了对于SchemeList类的计算,那么就会被视为非法。在我的代码中,有以下代码进行代码提示
public SchemeToken execute(SchemeList Token) {
Error.Print("cannot evaluate the raw list");
return null;
}
结语
本章介绍了SchemeList类及其子类的概况,下一张介绍语法分析中如何根据token流分析出一个SchemeList,或者是其子类。