2012-05-26 1 views
6

Я проектирую DSL в Clojure, который используется для управления генератором кода (в данном случае для синтеза процедурного изображения - clisk), и у меня возникают проблемы с разработкой лучшего представления для промежуточных значений ,Промежуточное представление для DSL Lisp/Clojure

Первоначально DSL состоял из функций, которые возвращали одну или несколько форм, например. (иллюстративный)

(v+ 1.0 [1.0 'y]) 
=> ['(+ 1.0 1.0) '(+ 1.0 y)] 

Эти функции затем могут быть скомпонованы для создания больших блоков кода.

Это было просто, и полученные формы могли быть поданы непосредственно в генератор кода. Однако теперь я определил, какие из этих слабых сторон можно использовать при таком подходе, например, если необходимо передать некоторые вспомогательные данные (например, объекты, которые не могут быть закодированы в таких формах, как BufferedImages, метаданные, полезные для оптимизации и т. Д.).

Я уверен, что это проблема решена в мире Lisp. Что обычно будет лучшим промежуточным представлением для такого рода DSL?

ответ

7

В любое время, когда вам нужно промежуточное представление, которое будет использоваться для генерации кода, наиболее очевидной из них, которая приходит мне на ум, является абстрактное синтаксическое дерево (AST). Представление вашего примера - это списки, которые по моему опыту не так гибки в форме. Для любой вещи, кроме тривиального генерации кода, я бы не стал бить вокруг куста и просто пошел с полномасштабным представлением AST. Используя списки, вы отжимаете больше работы на сторону поколения, чтобы анализировать информацию, такую ​​как типы и то, что означает первый элемент. Переход к представлению AST даст вам большую гибкость и развяжет большую часть системы за счет большей работы со стороны синтаксического анализа (или больше работы с функциями, которые генерируют формы). Сторона поколения будет делать больше работы, но многие из этих компонентов могут быть отделены, так как их входы будут более структурированными.

С точки зрения того, что должно АСТ выглядеть, я бы либо скопировать enlive Кристофа гранд, где он использует {:tag <tag name> :attrs <map of attrs> :content <some collection>}

или что Clojure скрипт использует {:op <some operator> :children <some collection>}.

Это делает его весьма общий характер, так как вы можете определить произвольные ходоков, что заглянуть в :children и может пересечь любую структуру, не зная точно о том, что в :op «s или :tag» s есть.

Затем для атомных компонентов вы можете обернуть его на карту и предоставить ему некоторую информацию о типе (относительно семантики вашего DSL), которая не зависит от фактического типа объекта. {:atom <the object> :type :background-image}.

На стороне генерации кода, когда вы сталкиваетесь с атомом, ваш код может отправляться на :type, а затем, если хотите, дальнейшая отправка по фактическому типу объекта. Поколение из форм коллекции также легко, отправьте на: op /: tag, а затем повторите с детьми. Для какой коллекции использовать для детей, я бы больше ознакомился с обсуждением групп google. Их выводы были для меня полезными.

https://groups.google.com/forum/#!topic/clojure-dev/vZLVKmKX0oc/discussion

Резюмируя, для детей, если бы семантическое значение упорядочения, например, в если заявление, а затем использовать карту {:conditional z :then y :else x}. Если это просто список аргументов, вы можете использовать вектор.

+0

большое спасибо - просто вид идей, которые я искал! – mikera

1

Думаю, я не понимаю. Я бы просто использовал списки или структуры.

В Lisp списки могут содержать, ну, что угодно. Я должен сказать, что ячейка CONS может указывать на что угодно, и, таким образом, список может содержать что угодно. Таким образом, в значительной степени можно использовать любую другую структуру данных (структуры, массивы, карты и т. Д.).

Теперь эти структуры не могут быть визуализированы с помощью ПЕЧАТИ или визуализированы в нечто читаемое (посредством READ), но это не означает, что они не могут быть сохранены и обработаны.

Есть ли причина, по которой вам необходимо экстернализировать это представление?

+0

В конечном счете, я хочу скомпилировать формы, используя, например, используя что-то вроде '(eval \' (fn [xyz] ~ form-to-compile)) - это не работает со встроенными объектами (см. http://stackoverflow.com/questions/10735701/embedding-arbitrary- object-in-clojure-code) – mikera

1

Не совсем ответ, потому что я не знаю, как работает Clojure в этом отношении, но в CL есть макросы читателей, специально разработанные для этого случая: т.е. вы определяете свою функцию для печати непечатаемых объектов + макрос читателя который читает их так, как вы их напечатали. Чтобы определить способ печати объектов, вы должны определить новый метод print-object, который специализируется на типе нужного вам объекта и set-macro-character, чтобы добавить функцию к чтению, которое знает, как читать объект вашего дизайна.

Есть много вещей, о которых нужно знать, но некоторые, которые обычно действуют как таймерные бомбы, являются случаями, когда объектам разрешено рекурсивно ссылаться на себя, и в этом случае печать должна учитывать ранее напечатанные объекты.