晶体内的ecr范围:或我如何在ECR模板中传递变量和对象?
作为一个水晶语言的初学者,我仍然在努力理解其中的一些概念,并培养对水晶语言编码的感觉。
当我遇到困难的问题,我解决了或开始理解了,我就把它们写到博客上,这样别人就能受益--因为有时会严重感受到缺乏文件的问题(由我来)。
继续前进!
这里是ECR的文档(在v.0.27,目前Crystal的最新版本)。
https://crystal-lang.org/api/0.27.0/ECR.html
你可以在这里看一下ECR的源代码,即嵌入式晶体模板。
https://github.com/crystal-lang/crystal/tree/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr
ECR是一种编译时的模板语言。你不能用ECR在运行时处理模板!
统治世界的宏程序
当你在Crystal中使用ECR模板时(如果你通过使用Kemal的方式来使用它们),你是在使用宏。
Crystal有一种宏语言,允许你抽象和重用代码;作为编译的第一步,宏被评估,结果代码被 "粘贴 "到宏调用过的地方。
然后Crystal继续编译代码。
这对ECR来说是怎样的?
例如,我们以 def_to_s(文件名) 例子中的代码中的宏。
要求 "ecr" 类别 问候语 def initialize(@name : String) 结束 ECR.def_to_s "greeting.ecr" 结束 # greeting.ecr 问候,! 问候语.new("John").to_s #=> 问候语, John!
Crystal将调用宏 辩护律师 在编译时,"greeting.ecr "被传递到它里面。
该宏在此定义。
https://github.com/crystal-lang/crystal/blob/c9d1eef8fde5c7a03a029d64c8483ed7b4f2fe86/src/ecr/macros.cr
宏def_to_s(文件名)
def to_s(__io__).
ECR.embed {{filename}}, "__io__"
结束
结束
你的类在第一步将被重写成这样。
要求 "ecr" 类别 问候语 def initialize(@name : String) 结束 def to_s(__io__)
ECR.embed "greeting.ecr", "__io__" 结束
你看到刚才发生了什么吗?一个to_s方法被添加到你的类中,它本身包含一个宏。让我们来看看这个宏的作用:
宏嵌入(文件名,io_name)。
\{{run("ecr/process", {{filename}, {{io_name.id.stringify}) }}。
结束
这是ECR的核心调用。它所做的,是编译(/执行)一个不同的应用程序 ecr/process 并将文件名和io_name作为参数传给它。
返回是该应用程序的输出结果。
反斜杠是什么意思?
"有可能定义一个 生成一个或多个宏定义的宏.你必须在内层宏的表达式前面加上反斜杠字符(backslash character)来转义,以防止它们被外层宏评估。"
它本质上是一个嵌套的宏!
这里定义了ecr/process。
它基本上是ECR.process_file的一个包装器(记住,这不再是一个宏了--这是一个应用程序,其输出最终将被粘贴到你的Crystal代码中!)。
ecr/processor在此定义。
它对文件进行处理,并创建一个字符串,将其反馈给对方。
这里有一小段摘录。
str << buffer_name
str << " << "
string.inspection(str)
str << '\n'
buffer_name 是我们上面传给 Crystal 的东西 (__io__ - 由 io_name.id.stringify 中的 id 识别)。
输出和控制值也被处理。此外,调试输出也会为你粘贴进去(用#作为注释)。
append_loc(str, filename, line_number, column_number)
基本上,你放在ECR文件中的代码被直接粘贴到你的代码中--在你指定的地方。这都是通过宏和运行一个特殊的ECR分析器来完成的。
ECR代码并不知道你的变量--如果你得到范围故障、未定义变量故障等,这不是由于你没有 "传入ECR",而是由于没有在正确的范围内。
试试你在主代码中直接用ECR代码做的事情。
示范
这里展示了如何在Kemal的ECR代码中 "使用 "一个类。该类嵌套了一个额外的ECR片段,以便在类的背景下进行渲染。
文件debug.cr。
要求 "kemal"
要求"././macros.cr"
调试模块
包括宏程序类DClass
@测试:字符串
def initialize()
@test = "测试字符串"
结束
ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
结束get "/debug" do |env|
loggedin = false
mrender "debug"
结束结束
文件debug.ecr。
。
调试信息
。。
纳入装饰的片段
。。
结束 %
宏的相关代码mrender,定义在macros.cr中。
宏指令mrender(文件名)
渲染 "src/views/#{{filename}}.ecr","src/views/layout.ecr"
结束
它使用Kemal的宏渲染,允许你为你的视图指定一个布局。(你不一定需要在你的代码中这样做,这里只是为了完整地给出它)
文件 src/views/snippets/debug_snippet.ecr。
调试片段
产出。
把这一切放在一起。
- 用户在他们的网页浏览器中调用/debug
- Kemal's macro get将与路由"/debug "匹配。它被定义在我的debug.cr中
- 本地变量 登录 将被设置为假
- debug.ecr将被ECR宏处理,并被粘贴到 "debug.cr"(AST表示法)中。(AST表示法),就像你直接输入的一样
- 本地变量 登录 将被评估为假(在运行时)。
- 我们称之为 DClass.new(),并要求它提供它的字符串表示法--这是由def to_s方法定义的。
- 我们可以调用DClass.new(),因为它是在同一个模块中定义的,就像我们正在执行的一样。再一次,简单地认为你的ECR代码就粘贴在那里,在类定义的下面。
- 我们要求它的字符串表示,因为我们使用语法。
好吧,让我们看看在DClass.new调用中发生了什么。
类DClass
@测试:字符串
def initialize()
@test = "测试字符串"
结束
ECR.def_to_s "src/views/snippets/debug_snippet.ecr"
结束
在初始化时,一个字符串@test被设置。这是一个 实例 我们刚刚创建的DClass实例的变量。(你可以看到它是一个实例变量,因为它前面有一个"@"。两个"@@"则是一个类变量)
这个实例变量在debug_snippet.ecr中使用/显示。
我们之前讨论过ECR.def_to_s的工作方式。
有效地,在通过宏阶段后,这个类看起来会是这样的。
类DClass
@测试:字符串
def initialize()
@test = "测试字符串"
结束
def to_s(__io__).
__io__ << "Debug snippet\n"
__io__ << ""
__io__ << @test
__io__ << ""
结束
结束
使用这种技术,你可以定义类来渲染ECR代码的片段,而不是手动设置和传递每一个变量名。
我希望这篇文章能帮助你,希望你能找到它,而不是越来越沮丧--我希望我有这样的东西来指导我开始工作
参考文献
凯末尔参考。
关于宏的更多信息,请参考本页面。
NB:AST节点:抽象语法树节点
- https://crystal-lang.org/reference/syntax_and_semantics/methods_and_instance_variables.html
- https://crystal-lang.org/reference/syntax_and_semantics/class_variables.html
这给我指出了正确的方向,赞一个!。