Groovy Read / Run DSL - PullRequest
       13

Groovy Read / Run DSL

2 голосов
/ 23 февраля 2010

Я пытаюсь создать новый DSL с использованием groovy, но у меня возникают проблемы с поиском лучшего способа, чтобы groovy читал dsl. Я хочу, чтобы пользователи могли создавать dsl и фактически запускать dsl, не обращаясь к коду приложения.

game.groovy (как dsl):

import com.foo.groovygame.Game

go north 10
search
find ana
turn right

Я хочу, чтобы пользователи могли сказать:

groovy game.groovy

и он запустит игру. Я понимаю, что могу сделать что-то вроде:

groovy Game game.groovy

но я бы предпочел иметь возможность запускать dsl напрямую.

Я заметил на этот блог автор использует

Ronald.init(this)

Я мог бы сделать что-то вроде этого, и метод init справился бы с игрой из dsl, но мне было интересно, лучший ли это подход? Кажется, немного небрежно. Мне действительно нравится рубиновый способ справиться с этим. Вы можете просто создать dsl и указать требуемый 'foo' в dsl.

Ответы [ 3 ]

1 голос
/ 26 февраля 2010

Я действительно не в восторге от использования синтаксиса компилятора для реализации DSL - извините, я знаю, что некоторым это нравится, и я с готовностью признаю, что это симпатичный трюк, но так легко написать свой собственный парсер, почему бы не сделать Это? Тогда у вас не будет случайных запятых и подчеркиваний, разбросанных по всему тексту.

Вот простой трюк, который я использовал для реализации простого синтаксиса, подобного описанному вами:

Прежде всего, посмотрите на ваши команды - обратите внимание, что большинство из них в формате "param имя существительного params"

Это очень хорошо отображается на methodName, objectName, params

Так что хорошей процедурой будет:

split sentence into string array s

for a line with a single word (if s.length == 1):
    instantiate an object with that name
    call a default method on that object
    done
for a line with more than one word
    instantiate the object s[1]
    call method s[0] with s[2...] as parameters
    done

Этот простой 5-10 или около того анализатор строк решит многие проблемы с типами DSL. Кроме того, вы можете легко добавлять функции:

Если параметры (2 ...) имеют форму «имя = значение», найдите параметр с именем «имя» и передайте «значение» для этого конкретного параметра. Это, вероятно, не будет работать в данном конкретном случае, но может быть полезно для других целей.

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

У меня был один случай, когда мне нужно было хранить объекты после того, как они были созданы. Я использовал синтаксис:

find person:ana 

(синтаксис может быть возвращен к исходному синтаксису путем сохранения таблицы сопоставления ана с человеком и проверки этой таблицы вместе с попыткой создания объектов)

и с тех пор ana была экземпляром класса person (другими словами, после создания экземпляра person и вызова для него метода find) я сохранил объект person в хэше под именем ana в следующий раз они использовали команду вроде:

talk ana

Сначала он будет искать в хеш-памяти, захватывать сохраненный там объект и вызывать «talk» для этого существующего объекта (в этот момент он может проверить, не установлен ли ana флаг «found», если нет, он может вернуть другое сообщение). Таким образом, у вас может быть несколько друзей, каждый со своей информацией о состоянии.

Эта система имеет некоторые ограничения, но все же она гораздо более гибкая, чем DSL в стиле Ruby, и ее совсем не сложно реализовать.

1 голос
/ 24 февраля 2010

Я провел сегодня некоторое время, экспериментируя с различными способами выполнения того, что вы описываете, и я решил, что предложение в блоге "Perfect Storm", которое вы указали, является, вероятно, самым простым и наименее запутанным.

Моей первой мыслью было создание статического инициализатора в классе Game, который автоматически позаботился бы об инициализации игры, но, к сожалению, даже если класс Game загружается только из оператора import (о чем свидетельствует запуск с - verbose: класс, установленный в JAVA_OPTS), статические инициализаторы не выполняются до тех пор, пока класс Game не будет каким-либо образом указан. Для этого требуется строка new Game() в вашем game.groovy DSL.

Даже если предположить, что с вами все в порядке, единственный способ справиться с функциями и свойствами в вашем игровом DSL - это каким-то образом добавить поддержку классу игры. В статическом инициализаторе у вас не будет доступа к игровому классу напрямую, но у вас будет доступ к его суперклассу: groovy.lang.Script. Вы можете добавить методы, такие как go() и search() к Script.metaClass, но затем добавляете их для всех экземпляров Script, что почти наверняка не то, что вам нужно.

Это приводит к необходимости вызова некоторого метода Game с игровым DSL в качестве аргумента, например, Game.init (this). Одна вещь, которую вы можете сделать, чтобы сделать его немного визуально чище, - это статический импорт метода инициализации Game:

import static com.foo.groovygame.Game.init

init (this);
go north, 10
...

Последнее замечание: вам все равно нужно использовать синтаксис Groovy в DSL, что означает запятые между аргументами методов, скобки для вызовов методов без аргументов и т. Д. :

go north, 10
search ()
find ana
turn right

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

0 голосов
/ 28 февраля 2010

Ответ RTBarnard кажется лучшим. Как лучше всего выполнить некоторую обработку после выполнения скрипта?

Ronald.init this

go right
back ten
...etc...

После того, как все dsl были обработаны, тогда Рональд должен сделать некоторую дополнительную работу, но без необходимости пользователю говорить что-то вроде Ronald.run().

...