在 Ruby 2.7 和 3.1 中,无论 % 符号是否存在,此脚本都会执行相同的操作:
def count(str)
state = :start
tbr = []
str.each_char do
% %case state
when :start
tbr << 0
% %state = :symbol
% when :symbol
tbr << 1
% % state = :start
% end
end
tbr
end
p count("Foobar")
Run Code Online (Sandbox Code Playgroud)
这是如何解析的?您可以添加更多%或删除一些,它仍然有效,但不是任何组合。我通过反复试验找到了这个例子。
我正在教某人 Ruby,只有在他们的脚本运行后才注意到他们的边缘有一个随机的%。我把它推得更远一点,看看它能接受多少。
这是接收消息的百分比字符串文字%。
百分比字符串文字的形式如下:
\n%特点如果开始分隔符<是、[、(或之一{,则结束分隔符必须是相应的>、]、)或}。否则,开始分隔符可以是任意字符,结束分隔符必须是相同的字符。
所以,
\n% \nRun Code Online (Sandbox Code Playgroud)\n(那是,% SPACE SPACE)
是一个百分比字符串文字,作为SPACE分隔符,没有内容。即它相当于"".
a % ba % b\nRun Code Online (Sandbox Code Playgroud)\n相当于
\na.%(b)\nRun Code Online (Sandbox Code Playgroud)\n即,将消息发送%到计算表达式的结果a,将计算表达式的结果b作为单个参数传递。
意思是
\n% % b\nRun Code Online (Sandbox Code Playgroud)\n(大致)相当于
\n"".%(b)\nRun Code Online (Sandbox Code Playgroud)\n那么,然后呢b?嗯,它是%运算符后面的表达式(不要与Percent String Literal%的符号混淆)。
整个代码(大致)相当于:
\ndef count(str)\n state = :start\n tbr = []\n str.each_char do\n"".%(case state\n when :start\n tbr << 0\n "".%(state = :symbol)\n ""when :symbol\n tbr << 1\n "".%(state = :start)\n ""end)\n end\n tbr\nend\n\np count("Foobar")\nRun Code Online (Sandbox Code Playgroud)\n你可以通过询问 Ruby 自己解决这个问题:
\n# ruby --dump=parsetree_with_comment test.rb\n###########################################################\n## Do NOT use this node dump for any purpose other than ##\n## debug and research. Compatibility is not guaranteed. ##\n###########################################################\n\n# @ NODE_SCOPE (id: 62, line: 1, location: (1,0)-(17,17))\n# | # new scope\n# | # format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body\n# +- nd_tbl (local table): (empty)\n# +- nd_args (arguments):\n# | (null node)\n\n[\xe2\x80\xa6]\n\n# | | +- nd_body (body):\n# | | @ NODE_OPCALL (id: 48, line: 5, location: (5,0)-(12,7))*\n# | | | # method invocation\n# | | | # format: [nd_recv] [nd_mid] [nd_args]\n# | | | # example: foo + bar\n# | | +- nd_mid (method id): :%\n# | | +- nd_recv (receiver):\n# | | | @ NODE_STR (id: 12, line: 5, location: (5,0)-(5,3))\n# | | | | # string literal\n# | | | | # format: [nd_lit]\n# | | | | # example: \'foo\'\n# | | | +- nd_lit (literal): ""\n# | | +- nd_args (arguments):\n# | | @ NODE_LIST (id: 47, line: 5, location: (5,4)-(12,7))\n# | | | # list constructor\n# | | | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])\n# | | | # example: [1, 2, 3]\n# | | +- nd_alen (length): 1\n# | | +- nd_head (element):\n# | | | @ NODE_CASE (id: 46, line: 5, location: (5,4)-(12,7))\n# | | | | # case statement\n# | | | | # format: case [nd_head]; [nd_body]; end\n# | | | | # example: case x; when 1; foo; when 2; bar; else baz; end\n# | | | +- nd_head (case expr):\n# | | | | @ NODE_DVAR (id: 13, line: 5, location: (5,9)-(5,14))\n# | | | | | # dynamic variable reference\n# | | | | | # format: [nd_vid](dvar)\n# | | | | | # example: 1.times { x = 1; x }\n# | | | | +- nd_vid (local variable): :state\n\n[\xe2\x80\xa6]\nRun Code Online (Sandbox Code Playgroud)\n这里一些有趣的地方是(id: 12, line: 5, location: (5,0)-(5,3))第一个字符串文字所在的节点,以及发送的(id: 48, line: 5, location: (5,0)-(12,7))第一条%消息:
# | | +- nd_body (body):\n# | | @ NODE_OPCALL (id: 48, line: 5, location: (5,0)-(12,7))*\n# | | | # method invocation\n# | | | # format: [nd_recv] [nd_mid] [nd_args]\n# | | | # example: foo + bar\n# | | +- nd_mid (method id): :%\n# | | +- nd_recv (receiver):\n# | | | @ NODE_STR (id: 12, line: 5, location: (5,0)-(5,3))\n# | | | | # string literal\n# | | | | # format: [nd_lit]\n# | | | | # example: \'foo\'\n# | | | +- nd_lit (literal): ""\nRun Code Online (Sandbox Code Playgroud)\n注意:这只是获取解析树的最简单的方法,不幸的是,它包含许多内部细节,这些细节与弄清楚正在发生的事情并不真正相关。还有其他方法,例如parsergem或其同伴ast,可以产生更具可读性的结果:
# ruby-parse count.rb\n(begin\n (def :count\n (args\n (arg :str))\n (begin\n (lvasgn :state\n (sym :start))\n (lvasgn :tbr\n (array))\n (block\n (send\n (lvar :str) :each_char)\n (args)\n (send\n (dstr) :%\n (case\n (lvar :state)\n (when\n (sym :start)\n (begin\n (send\n (lvar :tbr) :<<\n (int 0))\n (send\n (dstr) :%\n (lvasgn :state\n (sym :symbol)))\n (dstr)))\n (when\n (sym :symbol)\n (begin\n (send\n (lvar :tbr) :<<\n (int 1))\n (send\n (dstr) :%\n (lvasgn :state\n (sym :start)))\n (dstr))) nil)))\n (lvar :tbr)))\n (send nil :p\n (send nil :count\n (str "Foobar"))))\nRun Code Online (Sandbox Code Playgroud)\n到目前为止,我们讨论的只是语法,即代码的语法结构。但是这是什么意思?
\n该方法String#%执行C 函数系列中的字符串格式化。但是,由于格式字符串(消息的接收者)是空字符串,因此消息发送的结果也是空字符串,因为没有任何可格式化的内容。printf%
如果 Ruby 是一种纯粹函数式、惰性、非严格的语言,结果将等同于:
\ndef count(str)\n state = :start\n tbr = []\n str.each_char do\n"".%(case state\n when :start\n tbr << 0\n ""\n ""when :symbol\n tbr << 1\n ""\n ""end)\n end\n tbr\nend\n\np count("Foobar")\nRun Code Online (Sandbox Code Playgroud)\n这又相当于这个
\ndef count(str)\n state = :start\n tbr = []\n str.each_char do\n"".%(case state\n when :start\n tbr << 0\n ""\n when :symbol\n tbr << 1\n ""\n end)\n end\n tbr\nend\n\np count("Foobar")\nRun Code Online (Sandbox Code Playgroud)\n相当于这个
\ndef count(str)\n state = :start\n tbr = []\n str.each_char do\n"".%(case state\n when :start\n ""\n when :symbol\n ""\n end)\n end\n tbr\nend\n\np count("Foobar")\nRun Code Online (Sandbox Code Playgroud)\n相当于这个
\ndef count(str)\n state = :start\n tbr = []\n str.each_char do\n"".%(case state\n when :start, :symbol\n ""\n end)\n end\n tbr\nend\n\np count("Foobar")\nRun Code Online (Sandbox Code Playgroud)\n相当于这个
\ndef count(str)\n state = :start\n tbr = []\n str.each_char do\n""\n end\n tbr\nend\n\np count("Foobar")\nRun Code Online (Sandbox Code Playgroud)\n相当于这个
\ndef count(str)\n state = :start\n tbr = []\n tbr\nend\n\np count("Foobar")\nRun Code Online (Sandbox Code Playgroud)\n相当于这个
\ndef count(str)\n []\nend\n\np count("Foobar")\nRun Code Online (Sandbox Code Playgroud)\n显然,事实并非如此,原因是 Ruby不是一种纯粹函数式、惰性、非严格的语言。%虽然传递给消息 send 的参数与消息 send 的结果无关,但它们仍然被评估(因为 Ruby 是严格且渴望的)并且它们有副作用(因为 Ruby 不是纯粹的函数式),即它们的副作用-重新分配变量和改变tbr结果数组的效果仍然会执行。
如果此代码以更类似于 Ruby 的风格编写,具有更少的突变和副作用,并且使用函数转换,那么任意用空字符串替换结果将立即破坏它。这里没有效果的唯一原因是因为副作用和突变的大量使用。
\n| 归档时间: |
|
| 查看次数: |
71 次 |
| 最近记录: |