如何使用宏在编译时从文件生成代码?



我有一个看起来像这样的CSV文件:

CountryCode,CountryName
AD,Andorra
AE,United Arab Emirates
AF,Afghanistan
AG,Antigua and Barbuda
// -- snip -- //

以及如下所示的类:

module OpenData
class Country
def initialize(@code : String, @name : String)
end
end
end

我想在编译时自动加载模块中的类变量,如下所示:

module OpenData
@@countries : Array(Country) = {{ run "./sources/country_codes.cr" }}
end

我尝试使用以下代码使用上面的"运行"宏:

require "csv"
require "./country"
content = File.read "#{__DIR__}/country-codes.csv"
result = [] of OpenData::Country
CSV.new(content, headers: true).each do |row|
result.push OpenData::Country.new(row["CountryCode"], row["CountryName"])
end
result

但这导致

@@countries : Array(Country) = {{ run "./sources/country_codes.cr" }}
^
Error: class variable '@@countries' of OpenData must be Array(OpenData::Country), not Nil

由于各种原因,我所有其他尝试都以某种方式失败了,例如无法在宏中调用.new或类似的东西。这是我经常在Elixir和其他支持宏的语言中做的事情,我怀疑Crystal也可以实现......我也会采取任何其他方式在编译时完成任务!

基本上还有几个我想以这种方式处理的文件,它们更长/更复杂......提前感谢!

编辑:

发现问题。似乎我必须返回一个字符串,其中包含来自"run"宏的实际晶体代码。因此,"run"文件中的代码变为:

require "csv"
content = File.read "#{__DIR__}/country-codes.csv"
lines = [] of String
CSV.new(content, headers: true).each do |row|
lines << "Country.new("#{row["CountryCode"]}", "#{row["CountryName"]}")"
end
puts "[#{lines.join(", ")}]"

一切正常!

您已经找到了答案,但为了完整起见,以下是来自以下文档的文档: https://crystal-lang.org/api/1.2.2/Crystal/Macros.html#run%28filename%2C%2Aargs%29%3AMacroId-instance-method

编译并执行 Crystal 程序并返回其输出 作为MacroId.

文件名表示的文件必须是有效的Crystal 程序。 此宏调用将参数作为常规传递给程序 程序参数。程序必须输出有效的晶体表达式。 此输出是此宏调用的结果,作为MacroId

当可用宏方法的子集时,run宏很有用 不足以满足您的目的,您需要更强大的东西。 使用run您可以在编译时读取文件,连接到互联网 或数据库。

一个简单的例子:

# read.cr
puts File.read(ARGV[0])
# main.cr
macro read_file_at_compile_time(filename)
{{ run("./read", filename).stringify }}
end
puts read_file_at_compile_time("some_file.txt")

上面生成一个程序,该程序将具有some_file.txt的内容。 但是,该文件是在编译时读取的,在运行时不需要。

相关内容

  • 没有找到相关文章

最新更新