interpreter and compiler for arithmetic integer expressions with instaparse and ASM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns interpret.core | |
(:require [instaparse.core :as insta]) | |
(:import (clojure.asm Opcodes Type ClassWriter) | |
(clojure.asm.commons Method GeneratorAdapter)) ) | |
(def parser-arith | |
(insta/parser | |
"<expr> = spaces add-sub spaces | |
<add-sub> = mul-div | add | sub | |
add = add-sub spaces <'+'> spaces mul-div | |
sub = add-sub spaces <'-'> spaces mul-div | |
<mul-div> = term | mul | div | |
mul = mul-div spaces <'*'> spaces term | |
div = mul-div spaces <'/'> spaces term | |
<term> = number | <'('> expr <')'> | |
<spaces> = <#'[ ]'*> | |
number = #'[0-9]+'")) | |
(def arith-interpret {:number #(Long/parseLong %) | |
:mul * | |
:div / | |
:add + | |
:sub -}) | |
(insta/transform arith-interpret (parser-arith "(2 + 1) * 3")) | |
(defn generate-instr [mv instr] | |
"Generate the method call to an org.objectweb.asm.MethodVisitor for a given instruction." | |
(do (print instr) | |
(condp = (first instr) | |
:load (.visitVarInsn mv Opcodes/ILOAD (int (second instr))) | |
:store (.visitVarInsn mv Opcodes/ISTORE (int (second instr))) | |
:loadi (.visitLdcInsn mv (int (second instr))) | |
:addi (.visitInsn mv Opcodes/IADD) | |
:subi (.visitInsn mv Opcodes/ISUB) | |
:multi (.visitInsn mv Opcodes/IMUL) | |
:divi (.visitInsn mv Opcodes/IDIV) | |
:reti (.visitInsn mv Opcodes/IRETURN) | |
)) | |
mv) | |
(defn compile-ast [name ast] | |
"Generate a class of given name with a run method implementing the given ast." | |
(let [op-fun (fn[op] | |
(fn[instrs-v0 instrs-v1] | |
(conj (into instrs-v0 instrs-v1) [op]))) | |
prog (insta/transform {:prog (fn[ & instrs] (conj (reduce into [[:loadi 0]] instrs) [:reti])) | |
:add (op-fun :addi) | |
:sub (op-fun :subi) | |
:mul (op-fun :multi) | |
:div (op-fun :divi) | |
:number #(vector [:loadi (Integer/parseInt %)])} | |
ast) | |
generate-prog #(reduce generate-instr % prog) | |
cw (ClassWriter. (+ ClassWriter/COMPUTE_FRAMES ClassWriter/COMPUTE_MAXS )) | |
init (Method/getMethod "void <init>()") | |
meth-name "run" | |
meth-sig "()I"] | |
(.visit cw Opcodes/V1_6 Opcodes/ACC_PUBLIC (.replace name \. \/) nil "java/lang/Object" nil) | |
(doto (GeneratorAdapter. Opcodes/ACC_PUBLIC init nil nil cw) | |
(.visitCode) | |
(.loadThis) | |
(.invokeConstructor (Type/getType Object) init) | |
(.returnValue) | |
(.endMethod)) | |
(doto (.visitMethod cw (+ Opcodes/ACC_PUBLIC Opcodes/ACC_STATIC) meth-name meth-sig nil nil ) | |
(.visitCode) | |
(generate-prog) | |
(.visitMaxs 0 0 ) | |
(.visitEnd)) | |
(.visitEnd cw) | |
(let [b (.toByteArray cw) | |
cl (clojure.lang.DynamicClassLoader.)] | |
(.defineClass cl name b nil)) | |
(fn [] (clojure.lang.Reflector/invokeStaticMethod name meth-name (into-array []))))) | |
(def compiled (compile-ast "test" (conj [:prog ] (first (parser-arith "(1 + 2) * 3"))))) | |
(compiled) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment