在Clojure代码中嵌入任意对象

时间:2012-05-24 10:30:24

标签: clojure metaprogramming eval

我想在Clojure代码中嵌入一个Java对象(在本例中为BufferedImage),以后可以eval

创建代码可以正常工作:

(defn f [image]
     `(.getRGB ~image 0 0))
=> #'user/f

(f some-buffered-image)
=> (.getRGB #<BufferedImage BufferedImage@5527f4f9: type = 2 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=ff000000 IntegerInterleavedRaster: width = 256 height = 256 #Bands = 4 xOff = 0 yOff = 0 dataOffset[0] 0> 0 0)

但是在尝试eval时会出现异常:

(eval (f some-buffered-image))
=> CompilerException java.lang.RuntimeException: Can't embed object in code, maybe print-dup not defined: BufferedImage@612dcb8c: type = 2 DirectColorModel: rmask=ff0000 gmask=ff00 bmask=ff amask=ff000000 IntegerInterleavedRaster: width = 256 height = 256 #Bands = 4 xOff = 0 yOff = 0 dataOffset[0] 0, compiling:(NO_SOURCE_PATH:1) 

有没有办法制作类似这样的作品?

修改

我尝试这样做的原因是我希望能够生成从图像中获取样本的代码。图像被传递给执行代码生成的函数(相当于上面的f),但是(由于各种原因)以后不能作为参数传递给编译代码。

我需要生成带引号的代码,因为这是一个更大的代码生成库的一部分,它将进一步转换为生成的代码,因此我不能只做类似的事情:

(defn f [image] 
  (fn [] (.getRGB image 0 0)))

3 个答案:

答案 0 :(得分:3)

我想你需要在编译时编写一个获取对象(或创建所需对象的方法)的宏,以二进制格式(字节数组)序列化该对象,并且宏的输出应为 - a引用字节数组的符号和可用于通过反序列化从序列化数据中获取对象的函数。

答案 1 :(得分:2)

不确定您需要它,但您可以使用以下作弊创建可以攻击任意对象的代码:

(def objs (atom []))


(defn make-code-that-evals-to [x] 
    (let [
         nobjs (swap! objs #(conj % x)) 
         i (dec (count nobjs))]
     `(nth ~i @objs)))

然后你可以:

> (eval (make-code-that-evals-to *out*))
#<PrintWriter java.io.PrintWriter@14aed2c>

这只是一个概念验证,它泄漏了正在生成的对象 - 你可以生成代码来删除eval上的引用,但是你只能将它评估一次。

编辑:以下可以防止泄密(邪恶!)黑客攻击:

上面的代码通过在生成代码时在外部存储对象引用来绕过eval的编译器。这可以推迟。对象引用可以存储在生成的代码中,编译器被宏绕过。在代码中存储引用意味着垃圾收集器正常工作。

键是包装对象的宏。它执行原始解决方案所做的事情(即将对象存储在外部以绕过编译器),但就在编译之前。生成的表达式检索外部引用,然后删除以防止泄漏。

注意:这是邪恶的。宏的扩展时间是发生全局副作用的最不理想的地方,这正是这个解决方案所做的。

现在代码:

(def objs (atom {}))

这里将临时存储对象,由唯一键(字典)键入。

(defmacro objwrap [x sym]
  (do
   (swap! objs #(assoc % sym x) ) ; Global side-effect in macro expansion
   `(let [o# (@objs ~sym)]
      (do
        (swap! objs #(dissoc % ~sym))
        o#))))

这是生成的代码中的邪恶宏,保留x中的对象引用和sym中的唯一键。在编译之前,它将对象存储在键sym下的外部字典中,并生成检索它的代码,删除外部引用并返回检索到的对象。

(defn make-code-that-evals-to [x]
  (let [s 17]    ; please replace 17 with a generated unique key. I was lazy here.
  `(objwrap ~x ~s)))

没什么好看的,只需将对象包裹在邪恶的宏中,再加上一个唯一的密钥。

当然,如果你只是在不评估结果的情况下展开宏,你仍然会泄漏。

答案 2 :(得分:0)

为什么不:   (defmacro m [img]`(。getRGB~img 0 0)) 然后你可以写:( m some-buffered-image)

不同之处在于f是一个函数,因此在评估其正文之前将对其参数进行求值。因此,图像对象本身将被放置在生成的代码中。但是对于宏,它们的参数将不会被评估。因此,只有符号some-buffered-image将被放置在代码中。生成的代码将是:(。getRGB some-buffered-image 0 0)。就像你直接编写源代码一样。我想这就是你想要的。

但为什么我不能在生成的代码中放置对象?答案是:是的,你可以。异常消息所说的不是事实。你可以在生成的代码中嵌入某些类型的对象,但不是所有类型的对象。它们包括符号,数字,字符,字符串,正则表达式,关键字,布尔值,列表,映射,集等.Clojure编译器将理解所有这些对象。它们就像关键词,运算符和其他语言的文字。你不能要求Clojure编译器知道所有类型的对象,就像你不能要求C或Java编译器知道其语法中没有包含的所有单词。