summaryrefslogtreecommitdiff
path: root/tests/examplefiles/genclass.clj
diff options
context:
space:
mode:
authorGeorg Brandl <georg@python.org>2016-01-17 17:00:26 +0100
committerGeorg Brandl <georg@python.org>2016-01-17 17:00:26 +0100
commitfc55dc2e95bcea03fbc0d8e1d130c9e53f3f1dad (patch)
tree2a06d2fac7321452513bda7739e297a7e9848ec4 /tests/examplefiles/genclass.clj
downloadpygments-git-fc55dc2e95bcea03fbc0d8e1d130c9e53f3f1dad.tar.gz
merge default into stable
Diffstat (limited to 'tests/examplefiles/genclass.clj')
-rw-r--r--tests/examplefiles/genclass.clj510
1 files changed, 510 insertions, 0 deletions
diff --git a/tests/examplefiles/genclass.clj b/tests/examplefiles/genclass.clj
new file mode 100644
index 00000000..c63da8fd
--- /dev/null
+++ b/tests/examplefiles/genclass.clj
@@ -0,0 +1,510 @@
+; Copyright (c) Rich Hickey. All rights reserved.
+; The use and distribution terms for this software are covered by the
+; Common Public License 1.0 (http://opensource.org/licenses/cpl.php)
+; which can be found in the file CPL.TXT at the root of this distribution.
+; By using this software in any fashion, you are agreeing to be bound by
+; the terms of this license.
+; You must not remove this notice, or any other, from this software.
+
+(in-ns 'clojure)
+
+(import '(java.lang.reflect Modifier Constructor)
+ '(clojure.asm ClassWriter ClassVisitor Opcodes Type)
+ '(clojure.asm.commons Method GeneratorAdapter)
+ '(clojure.lang IPersistentMap))
+
+;(defn method-sig [#^java.lang.reflect.Method meth]
+; [(. meth (getName)) (seq (. meth (getParameterTypes)))])
+
+(defn- non-private-methods [#^Class c]
+ (loop [mm {}
+ considered #{}
+ c c]
+ (if c
+ (let [[mm considered]
+ (loop [mm mm
+ considered considered
+ meths (concat
+ (seq (. c (getDeclaredMethods)))
+ (seq (. c (getMethods))))]
+ (if meths
+ (let [#^Method meth (first meths)
+ mods (. meth (getModifiers))
+ mk (method-sig meth)]
+ (if (or (considered mk)
+ (. Modifier (isPrivate mods))
+ (. Modifier (isStatic mods))
+ (. Modifier (isFinal mods)))
+ (recur mm (conj considered mk) (rest meths))
+ (recur (assoc mm mk meth) (conj considered mk) (rest meths))))
+ [mm considered]))]
+ (recur mm considered (. c (getSuperclass))))
+ mm)))
+
+(defn- ctor-sigs [super]
+ (for [#^Constructor ctor (. super (getDeclaredConstructors))
+ :when (not (. Modifier (isPrivate (. ctor (getModifiers)))))]
+ (apply vector (. ctor (getParameterTypes)))))
+
+(defn- escape-class-name [c]
+ (.. (.getSimpleName c)
+ (replace "[]" "<>")))
+
+(defn- overload-name [mname pclasses]
+ (if (seq pclasses)
+ (apply str mname (interleave (repeat \-)
+ (map escape-class-name pclasses)))
+ (str mname "-void")))
+
+;(distinct (map first(keys (mapcat non-private-methods [Object IPersistentMap]))))
+
+(defn gen-class
+ "Generates compiled bytecode for a class with the given
+ package-qualified cname (which, as all names in these parameters, can
+ be a string or symbol). The gen-class construct contains no
+ implementation, as the implementation will be dynamically sought by
+ the generated class in functions in a corresponding Clojure
+ namespace. Given a generated class org.mydomain.MyClass, methods
+ will be implemented that look for same-named functions in a Clojure
+ namespace called org.domain.MyClass. The init and main
+ functions (see below) will be found similarly. The static
+ initializer for the generated class will attempt to load the Clojure
+ support code for the class as a resource from the claspath, e.g. in
+ the example case, org/mydomain/MyClass.clj
+
+ Returns a map containing :name and :bytecode. Most uses will be
+ satisfied by the higher-level gen-and-load-class and
+ gen-and-store-class functions, which generate and immediately load,
+ or generate and store to disk, respectively.
+
+ Options should be a set of key/value pairs, all of which are optional:
+
+ :extends aclass
+
+ Specifies the superclass, the non-private methods of which will be
+ overridden by the class. If not provided, defaults to Object.
+
+ :implements [interface ...]
+
+ One or more interfaces, the methods of which will be implemented by the class.
+
+ :init name
+
+ If supplied, names a function that will be called with the arguments
+ to the constructor. Must return [[superclass-constructor-args] state]
+ If not supplied, the constructor args are passed directly to
+ the superclass constructor and the state will be nil
+
+ :constructors {[param-types] [super-param-types], ...}
+
+ By default, constructors are created for the generated class which
+ match the signature(s) of the constructors for the superclass. This
+ parameter may be used to explicitly specify constructors, each entry
+ providing a mapping from a constructor signature to a superclass
+ constructor signature. When you supply this, you must supply an :init
+ specifier.
+
+ :methods [[name [param-types] return-type], ...]
+
+ The generated class automatically defines all of the non-private
+ methods of its superclasses/interfaces. This parameter can be used
+ to specify the signatures of additional methods of the generated
+ class. Do not repeat superclass/interface signatures here.
+
+ :main boolean
+
+ If supplied and true, a static public main function will be
+ generated. It will pass each string of the String[] argument as a
+ separate argument to a function called 'main.
+
+ :factory name
+
+ If supplied, a (set of) public static factory function(s) will be
+ created with the given name, and the same signature(s) as the
+ constructor(s).
+
+ :state name
+
+ If supplied, a public final instance field with the given name will be
+ created. You must supply an :init function in order to provide a
+ value for the state. Note that, though final, the state can be a ref
+ or agent, supporting the creation of Java objects with transactional
+ or asynchronous mutation semantics.
+
+ :exposes {protected-field-name {:get name :set name}, ...}
+
+ Since the implementations of the methods of the generated class
+ occur in Clojure functions, they have no access to the inherited
+ protected fields of the superclass. This parameter can be used to
+ generate public getter/setter methods exposing the protected field(s)
+ for use in the implementation."
+
+ [cname & options]
+ (let [name (str cname)
+ {:keys [extends implements constructors methods main factory state init exposes]} (apply hash-map options)
+ super (or extends Object)
+ interfaces implements
+ supers (cons super (seq interfaces))
+ ctor-sig-map (or constructors (zipmap (ctor-sigs super) (ctor-sigs super)))
+ cv (new ClassWriter (. ClassWriter COMPUTE_MAXS))
+ cname (. name (replace "." "/"))
+ ctype (. Type (getObjectType cname))
+ iname (fn [c] (.. Type (getType c) (getInternalName)))
+ totype (fn [c] (. Type (getType c)))
+ to-types (fn [cs] (if (pos? (count cs))
+ (into-array (map totype cs))
+ (make-array Type 0)))
+ obj-type (totype Object)
+ arg-types (fn [n] (if (pos? n)
+ (into-array (replicate n obj-type))
+ (make-array Type 0)))
+ super-type (totype super)
+ init-name (str init)
+ factory-name (str factory)
+ state-name (str state)
+ main-name "main"
+ var-name (fn [s] (str s "__var"))
+ rt-type (totype clojure.lang.RT)
+ var-type (totype clojure.lang.Var)
+ ifn-type (totype clojure.lang.IFn)
+ iseq-type (totype clojure.lang.ISeq)
+ ex-type (totype java.lang.UnsupportedOperationException)
+ all-sigs (distinct (concat (map #(let[[m p] (key %)] {m [p]}) (mapcat non-private-methods supers))
+ (map (fn [[m p]] {(str m) [p]}) methods)))
+ sigs-by-name (apply merge-with concat {} all-sigs)
+ overloads (into {} (filter (fn [[m s]] (rest s)) sigs-by-name))
+ var-fields (concat (and init [init-name])
+ (and main [main-name])
+ (distinct (concat (keys sigs-by-name)
+ (mapcat (fn [[m s]] (map #(overload-name m %) s)) overloads)
+ (mapcat (comp (partial map str) vals val) exposes))))
+ emit-get-var (fn [gen v]
+ (let [false-label (. gen newLabel)
+ end-label (. gen newLabel)]
+ (. gen getStatic ctype (var-name v) var-type)
+ (. gen dup)
+ (. gen invokeVirtual var-type (. Method (getMethod "boolean isBound()")))
+ (. gen ifZCmp (. GeneratorAdapter EQ) false-label)
+ (. gen invokeVirtual var-type (. Method (getMethod "Object get()")))
+ (. gen goTo end-label)
+ (. gen mark false-label)
+ (. gen pop)
+ (. gen visitInsn (. Opcodes ACONST_NULL))
+ (. gen mark end-label)))
+ emit-forwarding-method
+ (fn [mname pclasses rclass else-gen]
+ (let [ptypes (to-types pclasses)
+ rtype (totype rclass)
+ m (new Method mname rtype ptypes)
+ is-overload (overloads mname)
+ gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)
+ found-label (. gen (newLabel))
+ else-label (. gen (newLabel))
+ end-label (. gen (newLabel))]
+ (. gen (visitCode))
+ (when is-overload
+ (emit-get-var gen (overload-name mname pclasses))
+ (. gen (dup))
+ (. gen (ifNonNull found-label))
+ (. gen (pop)))
+ (emit-get-var gen mname)
+ (. gen (dup))
+ (. gen (ifNull else-label))
+ (when is-overload
+ (. gen (mark found-label)))
+ ;if found
+ (. gen (loadThis))
+ ;box args
+ (dotimes i (count ptypes)
+ (. gen (loadArg i))
+ (. clojure.lang.Compiler$HostExpr (emitBoxReturn nil gen (nth pclasses i))))
+ ;call fn
+ (. gen (invokeInterface ifn-type (new Method "invoke" obj-type
+ (into-array (cons obj-type
+ (replicate (count ptypes) obj-type))))))
+ ;unbox return
+ (. gen (unbox rtype))
+ (when (= (. rtype (getSort)) (. Type VOID))
+ (. gen (pop)))
+ (. gen (goTo end-label))
+
+ ;else call supplied alternative generator
+ (. gen (mark else-label))
+ (. gen (pop))
+
+ (else-gen gen m)
+
+ (. gen (mark end-label))
+ (. gen (returnValue))
+ (. gen (endMethod))))
+ ]
+ ;start class definition
+ (. cv (visit (. Opcodes V1_5) (. Opcodes ACC_PUBLIC)
+ cname nil (iname super)
+ (when interfaces
+ (into-array (map iname interfaces)))))
+
+ ;static fields for vars
+ (doseq v var-fields
+ (. cv (visitField (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_FINAL) (. Opcodes ACC_STATIC))
+ (var-name v)
+ (. var-type getDescriptor)
+ nil nil)))
+
+ ;instance field for state
+ (when state
+ (. cv (visitField (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_FINAL))
+ state-name
+ (. obj-type getDescriptor)
+ nil nil)))
+
+ ;static init to set up var fields and load clj
+ (let [gen (new GeneratorAdapter (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_STATIC))
+ (. Method getMethod "void <clinit> ()")
+ nil nil cv)]
+ (. gen (visitCode))
+ (doseq v var-fields
+ (. gen push name)
+ (. gen push v)
+ (. gen (invokeStatic rt-type (. Method (getMethod "clojure.lang.Var var(String,String)"))))
+ (. gen putStatic ctype (var-name v) var-type))
+
+ (. gen push ctype)
+ (. gen push (str (. name replace \. (. java.io.File separatorChar)) ".clj"))
+ (. gen (invokeStatic rt-type (. Method (getMethod "void loadResourceScript(Class,String)"))))
+
+ (. gen (returnValue))
+ (. gen (endMethod)))
+
+ ;ctors
+ (doseq [pclasses super-pclasses] ctor-sig-map
+ (let [ptypes (to-types pclasses)
+ super-ptypes (to-types super-pclasses)
+ m (new Method "<init>" (. Type VOID_TYPE) ptypes)
+ super-m (new Method "<init>" (. Type VOID_TYPE) super-ptypes)
+ gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)
+ no-init-label (. gen newLabel)
+ end-label (. gen newLabel)
+ nth-method (. Method (getMethod "Object nth(Object,int)"))
+ local (. gen newLocal obj-type)]
+ (. gen (visitCode))
+
+ (if init
+ (do
+ (emit-get-var gen init-name)
+ (. gen dup)
+ (. gen ifNull no-init-label)
+ ;box init args
+ (dotimes i (count pclasses)
+ (. gen (loadArg i))
+ (. clojure.lang.Compiler$HostExpr (emitBoxReturn nil gen (nth pclasses i))))
+ ;call init fn
+ (. gen (invokeInterface ifn-type (new Method "invoke" obj-type
+ (arg-types (count ptypes)))))
+ ;expecting [[super-ctor-args] state] returned
+ (. gen dup)
+ (. gen push 0)
+ (. gen (invokeStatic rt-type nth-method))
+ (. gen storeLocal local)
+
+ (. gen (loadThis))
+ (. gen dupX1)
+ (dotimes i (count super-pclasses)
+ (. gen loadLocal local)
+ (. gen push i)
+ (. gen (invokeStatic rt-type nth-method))
+ (. clojure.lang.Compiler$HostExpr (emitUnboxArg nil gen (nth super-pclasses i))))
+ (. gen (invokeConstructor super-type super-m))
+
+ (if state
+ (do
+ (. gen push 1)
+ (. gen (invokeStatic rt-type nth-method))
+ (. gen (putField ctype state-name obj-type)))
+ (. gen pop))
+
+ (. gen goTo end-label)
+ ;no init found
+ (. gen mark no-init-label)
+ (. gen (throwException ex-type (str init-name " not defined")))
+ (. gen mark end-label))
+ (if (= pclasses super-pclasses)
+ (do
+ (. gen (loadThis))
+ (. gen (loadArgs))
+ (. gen (invokeConstructor super-type super-m)))
+ (throw (new Exception ":init not specified, but ctor and super ctor args differ"))))
+
+ (. gen (returnValue))
+ (. gen (endMethod))
+ ;factory
+ (when factory
+ (let [fm (new Method factory-name ctype ptypes)
+ gen (new GeneratorAdapter (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_STATIC))
+ fm nil nil cv)]
+ (. gen (visitCode))
+ (. gen newInstance ctype)
+ (. gen dup)
+ (. gen (loadArgs))
+ (. gen (invokeConstructor ctype m))
+ (. gen (returnValue))
+ (. gen (endMethod))))))
+
+ ;add methods matching supers', if no fn -> call super
+ (let [mm (non-private-methods super)]
+ (doseq #^java.lang.reflect.Method meth (vals mm)
+ (emit-forwarding-method (.getName meth) (.getParameterTypes meth) (.getReturnType meth)
+ (fn [gen m]
+ (. gen (loadThis))
+ ;push args
+ (. gen (loadArgs))
+ ;call super
+ (. gen (visitMethodInsn (. Opcodes INVOKESPECIAL)
+ (. super-type (getInternalName))
+ (. m (getName))
+ (. m (getDescriptor)))))))
+ ;add methods matching interfaces', if no fn -> throw
+ (doseq #^Class iface interfaces
+ (doseq #^java.lang.reflect.Method meth (. iface (getMethods))
+ (when-not (contains? mm (method-sig meth))
+ (emit-forwarding-method (.getName meth) (.getParameterTypes meth) (.getReturnType meth)
+ (fn [gen m]
+ (. gen (throwException ex-type (. m (getName)))))))))
+ ;extra methods
+ (doseq [mname pclasses rclass :as msig] methods
+ (emit-forwarding-method (str mname) pclasses rclass
+ (fn [gen m]
+ (. gen (throwException ex-type (. m (getName))))))))
+
+ ;main
+ (when main
+ (let [m (. Method getMethod "void main (String[])")
+ gen (new GeneratorAdapter (+ (. Opcodes ACC_PUBLIC) (. Opcodes ACC_STATIC))
+ m nil nil cv)
+ no-main-label (. gen newLabel)
+ end-label (. gen newLabel)]
+ (. gen (visitCode))
+
+ (emit-get-var gen main-name)
+ (. gen dup)
+ (. gen ifNull no-main-label)
+ (. gen loadArgs)
+ (. gen (invokeStatic rt-type (. Method (getMethod "clojure.lang.ISeq seq(Object)"))))
+ (. gen (invokeInterface ifn-type (new Method "applyTo" obj-type
+ (into-array [iseq-type]))))
+ (. gen pop)
+ (. gen goTo end-label)
+ ;no main found
+ (. gen mark no-main-label)
+ (. gen (throwException ex-type (str main-name " not defined")))
+ (. gen mark end-label)
+ (. gen (returnValue))
+ (. gen (endMethod))))
+ ;field exposers
+ (doseq [f {getter :get setter :set}] exposes
+ (let [fld (.getField super (str f))
+ ftype (totype (.getType fld))]
+ (when getter
+ (let [m (new Method (str getter) ftype (to-types []))
+ gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
+ (. gen (visitCode))
+ (. gen loadThis)
+ (. gen getField ctype (str f) ftype)
+ (. gen (returnValue))
+ (. gen (endMethod))))
+ (when setter
+ (let [m (new Method (str setter) (. Type VOID_TYPE) (into-array [ftype]))
+ gen (new GeneratorAdapter (. Opcodes ACC_PUBLIC) m nil nil cv)]
+ (. gen (visitCode))
+ (. gen loadThis)
+ (. gen loadArgs)
+ (. gen putField ctype (str f) ftype)
+ (. gen (returnValue))
+ (. gen (endMethod))))))
+ ;finish class def
+ (. cv (visitEnd))
+ {:name name :bytecode (. cv (toByteArray))}))
+
+(defn gen-and-load-class
+ "Generates and immediately loads the bytecode for the specified
+ class. Note that a class generated this way can be loaded only once
+ - the JVM supports only one class with a given name per
+ classloader. Subsequent to generation you can import it into any
+ desired namespaces just like any other class. See gen-class for a
+ description of the options."
+
+ [name & options]
+ (let [{:keys [name bytecode]}
+ (apply gen-class (str name) options)]
+ (.. clojure.lang.RT ROOT_CLASSLOADER (defineClass (str name) bytecode))))
+
+(defn gen-and-save-class
+ "Generates the bytecode for the named class and stores in a .class
+ file in a subpath of the supplied path, the directories for which
+ must already exist. See gen-class for a description of the options"
+
+ [path name & options]
+ (let [{:keys [name bytecode]} (apply gen-class (str name) options)
+ file (java.io.File. path (str (. name replace \. (. java.io.File separatorChar)) ".class"))]
+ (.createNewFile file)
+ (with-open f (java.io.FileOutputStream. file)
+ (.write f bytecode))))
+
+(comment
+;usage
+(gen-class
+ package-qualified-name
+ ;all below are optional
+ :extends aclass
+ :implements [interface ...]
+ :constructors {[param-types] [super-param-types], }
+ :methods [[name [param-types] return-type], ]
+ :main boolean
+ :factory name
+ :state name
+ :init name
+ :exposes {protected-field {:get name :set name}, })
+
+;(gen-and-load-class
+(clojure/gen-and-save-class
+ "/Users/rich/Downloads"
+ 'fred.lucy.Ethel
+ :extends clojure.lang.Box ;APersistentMap
+ :implements [clojure.lang.IPersistentMap]
+ :state 'state
+ ;:constructors {[Object] [Object]}
+ ;:init 'init
+ :main true
+ :factory 'create
+ :methods [['foo [Object] Object]
+ ['foo [] Object]]
+ :exposes {'val {:get 'getVal :set 'setVal}})
+
+(in-ns 'fred.lucy.Ethel__2276)
+(clojure/refer 'clojure :exclude '(assoc seq count cons))
+(defn init [n] [[] n])
+(defn foo
+ ([this] :foo)
+ ([this x] x))
+(defn main [x y] (println x y))
+(in-ns 'user)
+(def ethel (new fred.lucy.Ethel__2276 42))
+(def ethel (fred.lucy.Ethel__2276.create 21))
+(fred.lucy.Ethel__2276.main (into-array ["lucy" "ricky"]))
+(.state ethel)
+(.foo ethel 7)
+(.foo ethel)
+(.getVal ethel)
+(.setVal ethel 12)
+
+(gen-class org.clojure.MyComparator :implements [Comparator])
+(in-ns 'org.clojure.MyComparator)
+(defn compare [this x y] ...)
+
+(load-file "/Users/rich/dev/clojure/src/genclass.clj")
+
+(clojure/gen-and-save-class "/Users/rich/dev/clojure/gen/"
+ 'org.clojure.ClojureServlet
+ :extends javax.servlet.http.HttpServlet)
+
+)