Groovy 读取/运行 DSL
我正在寻找使用 groovy 创建一个新的 DSL,但我无法找出让 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 处理游戏,但我想知道这是否是最好的方法?好像有点马虎了。我真的很喜欢 ruby 处理这个问题的方式。您可以简单地创建一个 dsl 并在 dsl 中指定 require 'foo'。
I'm looking to create a new DSL using groovy, but I'm having trouble figuring out the best way to have groovy read the dsl. I want users to be able to create a dsl and actually run the dsl, without having to talk to the application code.
game.groovy (as the dsl):
import com.foo.groovygame.Game
go north 10
search
find ana
turn right
I want users to be able to say:
groovy game.groovy
and it will run the game. I realize that I could do something like:
groovy Game game.groovy
but I would prefer to be able to run the dsl directly.
I noticed on this blog the author is using
Ronald.init(this)
I could do something like this, and have the init method handle the game from the dsl, but I was wondering if this is the best approach? It seems a little sloppy. I really like the ruby way of handling this. You can simply create a dsl and specify a require 'foo' in the dsl.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我今天花了一些时间尝试不同的方法来完成你所描述的事情,我认为你链接的“完美风暴”博客上的建议可能是最直接、最简单的。
我的第一个想法是在 Game 类中创建一个静态初始化程序,它会自动负责初始化游戏,但不幸的是,即使 Game 类仅从“import”语句加载(通过使用 -verbose:class 运行即可证明)在 JAVA_OPTS 中设置),静态初始化程序在以某种方式引用 Game 类之前不会执行。这需要在您的
game.groovy
DSL 中添加一个new Game()
行。即使假设您对此表示同意,处理游戏 DSL 中的函数和属性的唯一方法是以某种方式添加对游戏类的支持。在静态初始化程序中,您无法直接访问游戏类,但可以访问其超类:
groovy.lang.Script
。您可以将go()
和search()
等方法添加到Script.metaClass
中,但随后您要为的所有实例添加它们。 code>Script
,这几乎肯定不是您想要的。这导致需要使用游戏 DSL 作为参数来调用
Game
的某些方法,例如Game.init (this)
。为了使其在视觉上更清晰,您可以做的一件事是静态导入 Game 的 init 方法:最后一点:您仍然需要在 DSL 中使用 Groovy 语法,这意味着方法参数之间使用逗号,方法调用时使用括号,而无需使用括号。参数等:
我非常感兴趣是否其他人能够提出更清晰的解决方案,甚至只是某种方法来导致执行静态初始化程序而无需引用封闭类。
I spent some time today experimenting with different ways of accomplishing what you describe, and I decided that the suggestion on the "Perfect Storm" blog you linked is probably the most straightforward and least convoluted.
My first thought was to create a static initializer in the Game class that would automatically take care of initializing the game, but unfortunately, even though the Game class is loaded just from the 'import' statement (as evidenced by running with -verbose:class set in JAVA_OPTS), the static initializers aren't executed until the Game class is referred to in some way. This requires a
new Game()
line in yourgame.groovy
DSL.Even assuming that you're okay with that, the only way to handle the functions and properties in your game DSL is to add support to the game class in some way. In a static initializer, you won't have access to the game class directly, but you'll have access to its superclass:
groovy.lang.Script
. You can add methods likego()
andsearch()
toScript.metaClass
, but you're then adding them for all instances ofScript
, which is almost certainly not what you want.This leads to needing to call some method of
Game
with the game DSL as an argument, a laGame.init (this)
. One thing that you can do to make it a little visually cleaner is to statically import Game's init method:One last note: you'll still need to use Groovy syntax in your DSL, which means commas between method arguments, parentheses for method calls without arguments, etc.:
I'm very interested if others are able to come up with cleaner solutions, or even just some way to cause static initializers to be executed without having to refer to the enclosing class.
我真的并不热衷于使用编译器语法来实现 DSL——抱歉,我知道有些人喜欢它,我也承认这是一个可爱的技巧,但是编写自己的解析器是如此容易,为什么不这样做呢?这样您的文本中就不会出现随机的逗号和下划线。
这是我用来实现像您描述的那样的简单语法的一个简单技巧:
首先,查看您的命令 - 请注意,大多数命令都采用“动词名词参数”的格式,
这很好地映射到 methodName、objectName , params
因此,一个不错的过程是:
这个简单的 5-10 行左右解析器将解决您的许多 DSL 类型问题。除此之外,您可以非常轻松地添加功能:
如果参数 (2...) 的形式为“名称=值”,则扫描名为“名称”的参数并为该特定参数传递“值”。这可能在这种特定情况下不起作用,但对于其他用途可能有好处。
如果您的单字命令需要参数,则尝试将 s[0] 实例化为类,即使有多个单词也是如此。如果失败,请恢复到上面的多字算法。
我遇到过一种情况,我需要在实例化对象后保留它们。我使用了语法:(
通过保留一个将 ana 映射到 person 的表并检查该表并尝试实例化对象,该语法可以修复回原来的语法)
,从那时起,ana 是 person 类的一个实例(在换句话说,在实例化“person”并对其调用“find”方法之后,我将 person 对象存储在名为“ana”的哈希中,下次他们使用如下命令时:
它将首先搜索哈希,抓取存储在其中的对象并对现有对象调用“talk”(此时它可能会检查 ana 是否设置了“found”标志,如果没有,它可能会返回不同的消息)。多个朋友,每个人都有自己的状态信息。
这个系统有一些局限性,但仍然比 Ruby 风格的 DSL 灵活得多,而且实现起来一点也不困难。
I really am not crazy about using a compilers syntax to implement a DSL--Sorry, I know some people like it and I readily admit that it's a cute trick, but it's so easy to write your own parser, why not do it? Then you don't have random commas and underscores scattered throughout your text.
Here's an easy trick I've used to implement a simple syntax like the one you describe:
First of all, look at your commands--note that most are in the format of "verb noun params"
This maps really well to methodName, objectName, params
So a nice procedure would be:
This simple 5-10 or so line parser will solve many of your DSL type problems. Beyond that you can add features pretty easily:
If the parameters (2...) are in the form "name=value", scan for a parameter named "name" and pass "value" for that specific parameter. This probably wouldn't work in this specific case, but can be good for other uses.
If your single-word commands require parameters, then attempt to instantiate s[0] as a class even if there are multiple words. If this fails, revert to the multi-word algorithm above.
I had one case where I needed to keep objects around after they were instantiated. I used the syntax:
(the syntax could be fixed back to your original syntax by keeping a table mapping ana to person and checking this table along with trying to instantiate objects)
and from then on, ana was an instance of the person class (in other words, after instantiating "person" and calling the method "find" on it, I stored the person object in a hash under the name "ana", the next time they used a command like:
It would search the hash first, grab the object stored in there and call "talk" on that existing object (at which point it might check to see if ana had a "found" flag set, if not it might return a different message). In this way, you could have multiple friends, each with all their own state information.
This system has some limitations, but is still much more flexible than a Ruby style DSL and really isn't at all difficult to implement.
RTBarnard 的答案似乎是最好的。执行脚本后进行某些处理的最佳方法是什么?
处理完所有 dsl 后,Ronald 应该做一些额外的工作,但用户不必说出诸如
Ronald.run()
之类的内容。RTBarnard's answer seems to be the best. What would be the best way to do some processing after the script was executed?
After all the dsl has been processed, then Ronald should do some additional work, but without the user having to say something such as
Ronald.run()
.