简单实现一个用Java来解释Java的解释器
听名字是不是有点儿脱裤子放屁的感觉,其实就是写一个允许你无需使用类或者方法包装来执行 Java 语句(类似Java 9中的Jshell),就像是一些脚本语言(js,python)的一些解释执行过程一样
目标
我们可以定义一个自己的文本文件,后缀名为j,比如Foo.j,在这个文件中,我们可以像python那样直接写执行语句,而不用去定义类或者方法,比如如下:
int m = 10; int n = 1; System.out.println(m+n); int x = getSum(1,2); System.out.println(x); //进行方法的定义 methods: int getSum(int a,int b) { //hh(); return a+b; } void hh() { System.out.println("hh"); }
可以看到,我们并没有声明类,这样当我们在用Java写一些算法,或者验证某些东西的时候就会方便一点点.
思路
其实我们可以将j文件内容提取出来,然后将过程化的语句包装在main方法里面,定义的方法放在外面,然后进行编译执行就ok了,把这整个过程用我们的代码实现下就可以了
实践
根据刚才的思路,我们需要写几个方法,第一将我们定义的j文件转成String,第二创建Java文件,最后编译并自动执行Java文件,代码如下,里面含有注释:
public class Main { private static String rootPath = System.getProperty("user.dir") + "\\"; private static String jName; public static void main(String[] args) throws IOException { //获取j文件名称,并将j文件转成String String jStr = jFileToString(args[0]); //创建Java文件 createJavaFile(jStr); //编译并加载 loadAndCompile(); } /** * 将j文件转成String * @param param * @return * @throws IOException */ private static String jFileToString(String param) throws IOException { jName = param.replace(".j", ""); String jFilePath = rootPath + param; File javaFile = new File(jFilePath); if (javaFile.isDirectory()) { throw new IOException("j file does not a folder"); } InputStreamReader isr = new InputStreamReader(new FileInputStream(javaFile), StandardCharsets.UTF_8); BufferedReader bReader = new BufferedReader(isr); StringBuilder sb = new StringBuilder(); String s; while ((s = bReader.readLine()) != null) { sb.append(s).append("\n"); } bReader.close(); return sb.toString(); } /** * 创建Java文件 * @param jStr * @throws IOException */ private static void createJavaFile(String jStr) throws IOException { jName = jName.replace(".j", ""); String jFilePath = rootPath + jName + ".java"; File javaFile = new File(jFilePath); if (javaFile.exists()) { javaFile.delete(); } if (!javaFile.createNewFile()) { throw new IOException("java file create fail"); } //抽取方法 String methods = jStr.substring(jStr.indexOf("methods:")); methods = methods.replaceFirst("methods:", ""); jStr = jStr.substring(0, jStr.indexOf("methods:")); String st = "void st(){" + jStr + "}"; String main = "import java.io.*;\npublic class " + jName + " \n{public static void main(String[] args) throws Exception { \n new " + jName + "().st();\n}" + methods + st + "\n}"; //输出到文件 OutputStreamWriter oStreamWriter = new OutputStreamWriter(new FileOutputStream(javaFile), StandardCharsets.UTF_8); oStreamWriter.append(main); oStreamWriter.close(); } /** * 编译并加载 * @throws IOException */ private static void loadAndCompile() throws IOException { //调用系统编译器 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); int results = compiler.run(null, null, null, rootPath + jName + ".java"); if (results != 0) { throw new RuntimeException("compiler exception"); } //使用命令执行 Process process = Runtime.getRuntime().exec("java " + jName, null, new File(rootPath)); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); //输出执行结果 String str; while ((str = bufferedReader.readLine()) != null) { System.out.println(str); } } }
写完后我们打成Jar包,清单文件里面配置好主方法,记住一个重点就是上面我们用到了一个Jar包-tools.jar,这个Jar包在jdk环境下是有的,也就是开发环境里面,但是在jre环境是没有的,所以我们要讲jdk里面lib文件夹下的tools.jar复制到jre文件夹下面的lib里面去,
就大功告成了!
效果展示
当前文件夹下有我们定义好的Java脚本文件Tes.j我们右键打开控制台执行:
可以看到我们的脚本已经被执行成功了!
总结
写这么个看似无用的小东西其实是为了让大伙懂一些编程语言执行的一些过程,有关解释器啥的,但是上面写的比较粗糙,可以不用去生成java文件,然后的话导包也没有导入很多其他的包,就如同python,是被其他语言所编写的解释器如CPython C语言编写的,Jython Java语言编写的,来解释执行的,你甚至可以自己定义一门语言,然后按照自己喜欢的语法,最后自己写一个解释器来执行!当然你会遇到很多困难.
一起加油,我也是一个小菜鸟,希望大家共同进步!