noprompt / meander

Tools for transparent data transformation
MIT License
921 stars 54 forks source link

Literal nodes should be converted to :lit nodes before compilation #37

Closed noprompt closed 5 years ago

noprompt commented 5 years ago

A pattern like

(X Y Z)

will be parsed as

{:tag :seq,
 :prt {:tag :prt,
       :left {:tag :cat,
              :elements ({:tag :lit, :value X}
                         {:tag :lit, :value Y}
                         {:tag :lit, :value Z})},
       :right {:tag :cat, :elements []}}}

which will be compiled to the IR

{:op :check-seq,
 :then
 {:op :branch,
  :arms
  [{:op :check-bounds,
    :kind :seq,
    :length 3,
    :then {:op :branch,
           :arms [{:op :check-equal,
                   :target-1 {:op :eval, :form target},
                   :then {:value 'X, :op :return},
                   :target-2 {:op :eval, :form ['X 'Y 'Z]}}]},
    :target {:op :eval, :form target}}]},
 :target {:op :eval, :form target}}

which seems wasteful.

This should compile to an equality test.

jimmyhmiller commented 5 years ago

So is the idea here that

{:tag :cat,
 :elements ({:tag :lit, :value X}
            {:tag :lit, :value Y}
            {:tag :lit, :value Z})}

should be turned into

{:tag :lit
 :value '(X Y Z)}

Before being turned into IR?

Or did you have some other implementation in mind?

noprompt commented 5 years ago

@jimmyhmiller Yeah that's the idea. I've created a branch for this work if you want to take a look at it. Essentially we need to change the final case in expand-node.

@@ -1700,10 +1739,12 @@
              :arguments [as (dissoc x :as)]})
          x)
        ;; else
-       x))
+       (if (literal? x)
+         {:tag :lit
+          :value (lit-form x)}
+         x)))
    (r.syntax/rename-refs node)))

Where lit-form converts a node into a literal.

(defn lit-form [node]
  (case (r.syntax/tag node)
    :cat
    (map lit-form (:elements node))

    :jsa
    #?(:clj
       (JSValue. (vec (lit-form (:prt node))))
       :cljs
       (into-array (lit-form (:prt node))))

    :lit
    (:value node)

    :map
    (into {}
          (map (fn [[k v]]
                 [(lit-form k)
                  (lit-form v)]))
          (:map node))

    :prt
    (concat (lit-form (:left node))
            (lit-form (:right node)))

    :quo
    (:form node)

    :vec
    (into [] (lit-form (:prt node)))

    :seq
    (if-some [l (seq (lit-form (:prt node)))]
      `(quote ~(apply list l))
      ())

    :set
    (into #{} (map lit-form (:elements node)))))

This is essentially the same as compile-ground but without the :unq clause.

The definition of literal? also needs to be changed to

@@ -120,7 +120,7 @@
   See also: compile-ground"
   [node]
   (and (r.syntax/ground? node)
-       (not-any? (comp #{:map :set} r.syntax/tag)
+       (not-any? (comp #{:map :unq :set} r.syntax/tag)
                  (r.syntax/subnodes node))))

because an unquote node is technically not a literal because is determined at runtime.

This does appear to break a few tests, however.