【问题标题】:Using __call to create a DSL with lua, how to implement subsections?使用__call创建带有lua的DSL,如何实现小节?
【发布时间】:2013-12-25 11:28:49
【问题描述】:

我是 lua 新手,我正在尝试创建一个配置 DSL,它允许拥有已经具有默认值的部分。

所以,java 表预定义了很多值

java = {
  source = 1.6,
  target = 1.6,
  directories = {
    sources = "src/main/java",
    output = "build/clases",
  },  
}

我有一个实现 __call 的 Config 原型,因此当使用表构造函数作为函数调用时,它只会覆盖默认值。像这样的东西:

Config.__call = function(t, props)
  for k,v in pairs(props) do
    t[k] = v
  end
end

这个想法是你可以调用 dsl 来指定你想要覆盖的内容:

java {
  source = 1.5,
  directories {
    sources = "customsrcdir",
  }
}

有一个 Config.new 方法允许将原型递归地应用于表,以便所有表都有一个带有 __call 方法集的元表。

我的问题在于“目录”小节。它是在全局上下文中评估的,因此唯一可行的方法是:

java {
  source = 1.5,
    java.directories {
      sources = "customsrcdir",
  }
}

这是没有意义的,因为这和做的一样:

java {
  source = 1.5
}

java.directories {
  sources = "customsrcdir",
}

我尝试了不同的方法来让所需的 DSL 工作。一个是使用 _ENV 设置自定义全局环境,但后来我意识到该表是在 __call 之前评估的。

我想知道是否有更多 lua 经验的人使用更高级的 table/metatable/_ENV 魔法实现了这样的 DSL。

【问题讨论】:

  • directories 之后添加= 会很痛吗?你不能真的说“这是一个函数调用”(你当然知道f ({...})f {...} 之间的相等性),“但现在不要评估它”。除非你用全局状态或类似的东西做一些疯狂的事情。
  • 不确定我是否完全理解您的问题。所以你基本上希望java.directories { "src/main/java2" }java.directories = { "src/main/java2" } 具有相同的效果?
  • 好吧,如果我使用 '=' 它会覆盖整个表格和默认值。函数调用的想法是我可以只添加不属于模型一部分的传递、验证和忽略键。
  • 那么当您执行java { directories{"src/main/java2"} } 时,您在 DSL 中寻找的语义行为是什么?它应该覆盖该字段还是添加到该字段?
  • directories 是一个不好的例子,但是如果目录有子键,我只希望在最初初始化的默认值中提到的每个键都被覆盖。我不确定当您像 {"src/main/java2"} 指定它时使用的密钥是什么。

标签: lua dsl


【解决方案1】:

可以按照自己的方式调用,但解决方案非常复杂,不值得省略=。如果您仍然想要表格合并/替换功能,那么这并不太难。

local function merge(t1, t2)
  for k, v in pairs(t2) do
    -- Merge tables with tables, unless the replacing table is an array,
    -- in which case, the array table overwrites the destination.
    if type(t1[k]) == 'table' and type(v) == 'table' and #v == 0 then
      merge(t1[k], v)
    else
      t1[k] = v
    end
  end
end

local data = {
  java = {
    source = 1.6,
    target = 1.6,
    directories = {
      sources = "src/main/java",
      output = "build/classes",
    },
  }
}

local dsl = {}
load( [[
  java = {
    source = 1.5,
    directories = {
      sources = "customsrcdir",
    },
  }
]], 'dsl-config', 't', dsl)()

merge(data, dsl)

转储data 将导致:

java = {
  directories = {
    output = "build/classes",
    sources = "customsrcdir"
  }
  source = 1.5,
  target = 1.6
}

【讨论】:

  • 我想这只有在 java 是唯一的顶级表时才有效,而且你不能有多个顶级部分,对吧?
  • 不,它适用于任意数量的顶级值、表或其他。试试看。它只是将data 表与沙盒脚本的环境表合并,沙盒值优先,即它们与旧值重叠。
  • 谢谢,您的解决方案更接近我的要求。但是,一旦开始使用配置中的功能,就需要付出更多的努力。例如。尝试设置 source = math.random()。它非常适合静态配置,但是 DSL 的意义何在,我只加载一个 yaml 文件或 json。我试图从这里的 gradle 中获得灵感,它使用了 groovy 闭包,但是那些有 this/that/owner 和 delegates,这使得这一切变得更容易。我想,我将不得不为 DSL 选择不同的路线,但我不确定它是否看起来很直观。
  • 如果要导入常用的全局变量,请将 local dsl = {} 行替换为 local dsl = setmetatable({}, {__index = _G})。请注意,这会限制您可以安全使用的顶级值的名称。即,math = 5 显然从那时起将无法访问实际的数学库。您可能想了解sandboxingmetamethods 的主题。
【解决方案2】:

看看 premake 是如何做到的...可能是一个比你现在正在做的更优雅的解决方案。 http://en.wikipedia.org/wiki/Premake#Sample_Script

【讨论】:

    猜你喜欢
    • 2010-11-27
    • 1970-01-01
    • 2011-08-28
    • 1970-01-01
    • 2019-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多