Create a gist now

Instantly share code, notes, and snippets.

@bhugueney /core.clj
Last active Oct 11, 2016

What would you like to do?
interpreter and compiler for arithmetic integer expressions with instaparse and ASM
(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