MENU

《两周自制脚本语言》—— 第八天(关联 Java 语言)

December 21, 2018 • Read: 4076 • 编译原理阅读设置

笔记、源码同步在 github 上,欢迎 Star,蟹蟹~



我们还没有为 Stone 语言实现类似于 Java 语言中 system.out.print1n 的函数,因此程序还无法输出字符串显示。本章将继续扩展 Stone 语言,使它能够在程序中调用 Java 语言中的 static 方法

原生函数展开目录

Java 语言提供了名为原生方法的功能,用于调用 C 语言等其他一些语言写成的函数。我们将为 Stone 语言添加类似的功能,让他能够调用由 Java 语言写成的函数

原生函数将由 Arguments 类的 eval 方法调用。代码清单 8.1 是用于改写 Arguments 类的 eval 方法的修改器。这个名为 NativeArgEx 的修改器标有 extendsArgumentsEx 一句,它修改的是 Arguments 类。ArgumentsEx 是第 7 天代码清单 7.7 中定义的另一个修改器。这里的修改器继承了另一个修改器

通过这次修改,Arguments 类 eval 方法将首先判断参数 value 是否为 NativeFunction 对象。参数 value 是一个由函数调用表达式的函数名得到的对象。eval 方法之前返回的总是 Function 对象。如果参数是一个 NativeFunction 对象,eval 方法将在计算实参序列并保存至数组 args 之后,调用 NativeFunction 对象的 invoke 来执行目标函数。如果参数不是 NativeFunction 对象,解释器将执行通常的函数调用

代码清单 8.1 NativeEvaluator.java

  • package chap8;
  • import java.util.List;
  • import Stone.StoneException;
  • import Stone.ast.ASTree;
  • import chap6.Environment;
  • import chap6.BasicEvaluator.ASTreeEx;
  • import chap7.FuncEvaluator;
  • import javassist.gluonj.*;
  • @Require(FuncEvaluator.class)
  • @Reviser
  • public class NativeEvaluator {
  • @Reviser
  • public static class NativeArgEx extends FuncEvaluator.ArgumentsEx {
  • public NativeArgEx(List<ASTree> c) {
  • super(c);
  • }
  • public Object eval(Environment callerEnv, Object value) {
  • if (!(value instanceof NativeFunction))
  • return super.eval(callerEnv, value);
  • NativeFunction func = (NativeFunction) value;
  • int nparams = func.numOfParameters();
  • if (size() != nparams)
  • throw new StoneException("bad number of arguments", this);
  • Object[] args = new Object[nparams];
  • int num = 0;
  • for (ASTree a : this) {
  • ASTreeEx ae = (ASTreeEx) a;
  • args[num++] = ae.eval(callerEnv);
  • }
  • return func.invoke(args, this);
  • }
  • }
  • }

代码清单 8.2 是 NativeFunction 类。如果函数是一个原生函数,程序将在开始执行前创建 NativeFunction 类的对象,将由函数名与相应对象组成的名值对添加至环境中。该类的 invoke 方法将以参数 args 为参数调用 Java 语言的 static 方法

Method 对象的 invoke 方法用于执行它表示的 Java 语言方法。invoke 的第 1 个参数是执行该方法的对象。如果被执行的是一个 static 方法,该参数则为 null。invoke 的第 2 个参数用于保存传递给方法的实参序列

代码清单 8.2 NativeFunction.java

  • package chap8;
  • import java.lang.reflect.Method;
  • import Stone.StoneException;
  • import Stone.ast.ASTree;
  • public class NativeFunction {
  • protected Method method;
  • protected String name;
  • protected int numParams;
  • public NativeFunction(String n, Method m) {
  • name = n;
  • method = m;
  • numParams = m.getParameterTypes().length;
  • }
  • public String toString() {
  • return "<native:" + hashCode() + ">";
  • }
  • public int numOfParameters() {
  • return numParams;
  • }
  • public Object invoke(Object[] args, ASTree tree) {
  • try {
  • return method.invoke(null, args);
  • } catch (Exception e) {
  • throw new StoneException("bad native function call: " + name, tree);
  • }
  • }
  • }

代码清单 8.3 中的程序会在执行前创建 NativeFunction 对象,并添加至环境中。其中,Natives 类的 environment 方法将在调用后返回一个含有原生函数的环境

代码清单 8.3 向环境添加了 print 函数、read 函数、1ength 函数、toInt 函数以及 currentTime 函数。关于这些原生函数的用途,请参见 Natives 类中的同名 static 方法

代码清单 8.4 与代码清单 8.5 分别是解释器程序及其启动程序。代码清单 8.4 中的解释器将首先调用 Natives 类的 environment 方法,创建一个包含原生函数的环境

编写使用原生函数的程序展开目录

在支持使用原生函数之后,Stone 语言终于能够写出更加像样的程序了。例如代码清单 8.6 能够计算 15 的斐波那契数,并显示计算所花的时间

代码清单 8.3 Natives.java

  • package chap8;
  • import java.lang.reflect.Method;
  • import javax.swing.JOptionPane;
  • import Stone.StoneException;
  • import chap6.Environment;
  • public class Natives {
  • public Environment environment(Environment env) {
  • appendNatives(env);
  • return env;
  • }
  • protected void appendNatives(Environment env) {
  • append(env, "print", Natives.class, "print", Object.class);
  • append(env, "read", Natives.class, "read");
  • append(env, "length", Natives.class, "length", String.class);
  • append(env, "toInt", Natives.class, "toInt", Object.class);
  • append(env, "currentTime", Natives.class, "currentTime");
  • }
  • protected void append(Environment env, String name, Class<?> clazz,
  • String methodName, Class<?> ... params) {
  • Method m;
  • try {
  • m = clazz.getMethod(methodName, params);
  • } catch (Exception e) {
  • throw new StoneException("cannot find a native function: "
  • + methodName);
  • }
  • env.put(name, new NativeFunction(methodName, m));
  • }
  • // native methods
  • public static int print(Object obj) {
  • System.out.println(obj.toString());
  • return 0;
  • }
  • public static String read() {
  • return JOptionPane.showInputDialog(null);
  • }
  • public static int length(String s) { return s.length(); }
  • public static int toInt(Object value) {
  • if (value instanceof String)
  • return Integer.parseInt((String)value);
  • else if (value instanceof Integer)
  • return ((Integer)value).intValue();
  • else
  • throw new NumberFormatException(value.toString());
  • }
  • private static long startTime = System.currentTimeMillis();
  • public static int currentTime() {
  • return (int)(System.currentTimeMillis() - startTime);
  • }
  • }

代码清单 8.4 NativeInterpreter.java

  • package chap8;
  • import Stone.ClosureParser;
  • import Stone.ParseException;
  • import chap6.BasicInterpreter;
  • import chap7.NestedEnv;
  • public class NativeInterpreter extends BasicInterpreter {
  • public static void main(String[] args) throws ParseException {
  • run(new ClosureParser(),new Natives().environment(new NestedEnv()));
  • }
  • }

代码清单 8.5 NativeRunner.java

  • package chap8;
  • import javassist.gluonj.util.Loader;
  • import chap7.ClosureEvaluator;
  • public class NativeRunner {
  • public static void main(String[] args) throws Throwable {
  • Loader.run(NativeInterpreter.class, args, NativeEvaluator.class,
  • ClosureEvaluator.class);
  • }
  • }

代码清单 8.6 计算斐波那契数列所需的时间

  • def fib (n) {
  • if n < 2 {
  • n
  • } else {
  • fib (n - 1) + fib (n - 2)
  • }
  • }
  • t = currentTime()
  • fib 15
  • print currentTime() - t + " msec"

运行效果如下

从上面运行效果来开,一开始计算大约需要 9ms,之后变为 2ms。可见 Java 虚拟机的动态编译能够提高程序的执行效率

第八天的思维导图展开目录

Last Modified: February 2, 2020
Archives Tip
QR Code for this page
Tipping QR Code