令人惊讶的有效 Ruby 语法:% 无处不在

Max*_*Max 4 ruby

在 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,只有在他们的脚本运行后才注意到他们的边缘有一个随机的%。我把它推得更远一点,看看它能接受多少。

Jör*_*tag 8

句法

\n

百分比字符串文字

\n

这是接收消息的百分比字符串文字%

\n

百分比字符串文字的形式如下:

\n
    \n
  • %特点
  • \n
  • 开始分隔符
  • \n
  • 字符串内容
  • \n
  • 结束分隔符
  • \n
\n

如果开始分隔符<是、[(或之一{,则结束分隔符必须是相应的>])}。否则,开始分隔符可以是任意字符,结束分隔符必须是相同的字符。

\n

所以,

\n
%  \n
Run Code Online (Sandbox Code Playgroud)\n

(那是,% SPACE SPACE

\n

是一个百分比字符串文字,作为SPACE分隔符,没有内容。即它相当于"".

\n

操作员消息发送a % b

\n
a % b\n
Run Code Online (Sandbox Code Playgroud)\n

相当于

\n
a.%(b)\n
Run Code Online (Sandbox Code Playgroud)\n

即,将消息发送%到计算表达式的结果a,将计算表达式的结果b作为单个参数传递。

\n

意思是

\n
%  % b\n
Run Code Online (Sandbox Code Playgroud)\n

(大致)相当于

\n
"".%(b)\n
Run Code Online (Sandbox Code Playgroud)\n

参数列表

\n

那么,然后呢b?嗯,它是%运算符后面的表达式(不要与Percent String Literal%的符号混淆)。

\n

整个代码(大致)相当于:

\n
def 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")\n
Run Code Online (Sandbox Code Playgroud)\n

谷草转氨酶

\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]\n
Run Code Online (Sandbox Code Playgroud)\n

这里一些有趣的地方是(id: 12, line: 5, location: (5,0)-(5,3))第一个字符串文字所在的节点,以及发送的(id: 48, line: 5, location: (5,0)-(12,7))第一条%消息:

\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
Run Code Online (Sandbox Code Playgroud)\n

注意:这只是获取解析树的最简单的方法,不幸的是,它包含许多内部细节,这些细节与弄清楚正在发生的事情并不真正相关。还有其他方法,例如parsergem或其同伴ast,可以产生更具可读性的结果:

\n
# 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"))))\n
Run Code Online (Sandbox Code Playgroud)\n

语义学

\n

到目前为止,我们讨论的只是语法,即代码的语法结构。但是这是什么意思

\n

该方法String#%执行C 函数系列中的字符串格式化。但是,由于格式字符串(消息的接收者)是空字符串,因此消息发送的结果也是空字符串,因为没有任何可格式化的内容。printf%

\n

如果 Ruby 是一种纯粹函数式、惰性、非严格的语言,结果将等同于:

\n
def 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")\n
Run Code Online (Sandbox Code Playgroud)\n

这又相当于这个

\n
def 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")\n
Run Code Online (Sandbox Code Playgroud)\n

相当于这个

\n
def 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")\n
Run Code Online (Sandbox Code Playgroud)\n

相当于这个

\n
def 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")\n
Run Code Online (Sandbox Code Playgroud)\n

相当于这个

\n
def count(str)\n  state = :start\n  tbr = []\n  str.each_char do\n""\n  end\n  tbr\nend\n\np count("Foobar")\n
Run Code Online (Sandbox Code Playgroud)\n

相当于这个

\n
def count(str)\n  state = :start\n  tbr = []\n  tbr\nend\n\np count("Foobar")\n
Run Code Online (Sandbox Code Playgroud)\n

相当于这个

\n
def count(str)\n  []\nend\n\np count("Foobar")\n
Run Code Online (Sandbox Code Playgroud)\n

显然,事实并非如此,原因是 Ruby不是一种纯粹函数式、惰性、非严格的语言。%虽然传递给消息 send 的参数与消息 send 的结果无关,但它们仍然被评估(因为 Ruby 是严格且渴望的)并且它们有副作用(因为 Ruby 不是纯粹的函数式),即它们的副作用-重新分配变量和改变tbr结果数组的效果仍然会执行。

\n

如果此代码以更类似于 Ruby 的风格编写,具有更少的突变和副作用,并且使用函数转换,那么任意用空字符串替换结果将立即破坏它。这里没有效果的唯一原因是因为副作用和突变的大量使用。

\n