在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")

这是如何解析的?您可以添加更多的%或删除一些,它仍然可以工作,但不是任何组合.我通过反复试验找到了这个例子.

我在教某人Ruby,只有在他们的脚本起作用后,我才注意到他们的页边距有一个随机的%.我把它推得更远一点,看看它能接受多少人.

推荐答案

Syntax

字符串文字百分比

这是一台正在接收%报文的字符串文字百分比.

A 字符串文字百分比的形式为:

  • %个字符
  • opening-delimiter
  • 字符串内容
  • closing-delimiter

如果opening-delimiter个是<[({之一,则closing-delimiter个必须是对应的>])}.否则,opening-delimiter个可以是任意字符,而closing-delimiter个必须是相同的字符.

我们开始吧.

%  

(即,%空间空间)

是一个字符串文字百分比,以空格为分隔符,没有内容.即等于"".

Operator Message Send a % b

a % b

相当于

a.%(b)

sending the message %赋给表达式a的求值结果,将表达式b求值的结果作为单个自变量传递.

这意味着

%  % b

(大致)相当于

"".%(b)

参数列表

我们开始吧. what's b then? Well, it's the expression following the % operator (not to be confused with the % sigil of the 字符串文字百分比).

The entire code (大致)相当于 this:

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")

天冬氨酸

您只需问Ruby就可以自己解决这个问题:

# ruby --dump=parsetree_with_comment test.rb
###########################################################
## Do NOT use this node dump for any purpose other than  ##
## debug and research.  Compatibility is not guaranteed. ##
###########################################################

# @ NODE_SCOPE (id: 62, line: 1, location: (1,0)-(17,17))
# | # new scope
# | # format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body
# +- nd_tbl (local table): (empty)
# +- nd_args (arguments):
# |   (null node)

[…]

#     |           |       +- nd_body (body):
#     |           |           @ NODE_OPCALL (id: 48, line: 5, location: (5,0)-(12,7))*
#     |           |           | # method invocation
#     |           |           | # format: [nd_recv] [nd_mid] [nd_args]
#     |           |           | # example: foo + bar
#     |           |           +- nd_mid (method id): :%
#     |           |           +- nd_recv (receiver):
#     |           |           |   @ NODE_STR (id: 12, line: 5, location: (5,0)-(5,3))
#     |           |           |   | # string literal
#     |           |           |   | # format: [nd_lit]
#     |           |           |   | # example: 'foo'
#     |           |           |   +- nd_lit (literal): ""
#     |           |           +- nd_args (arguments):
#     |           |               @ NODE_LIST (id: 47, line: 5, location: (5,4)-(12,7))
#     |           |               | # list constructor
#     |           |               | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
#     |           |               | # example: [1, 2, 3]
#     |           |               +- nd_alen (length): 1
#     |           |               +- nd_head (element):
#     |           |               |   @ NODE_CASE (id: 46, line: 5, location: (5,4)-(12,7))
#     |           |               |   | # case statement
#     |           |               |   | # format: case [nd_head]; [nd_body]; end
#     |           |               |   | # example: case x; when 1; foo; when 2; bar; else baz; end
#     |           |               |   +- nd_head (case expr):
#     |           |               |   |   @ NODE_DVAR (id: 13, line: 5, location: (5,9)-(5,14))
#     |           |               |   |   | # dynamic variable reference
#     |           |               |   |   | # format: [nd_vid](dvar)
#     |           |               |   |   | # example: 1.times { x = 1; x }
#     |           |               |   |   +- nd_vid (local variable): :state

[…]

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

#     |           |       +- nd_body (body):
#     |           |           @ NODE_OPCALL (id: 48, line: 5, location: (5,0)-(12,7))*
#     |           |           | # method invocation
#     |           |           | # format: [nd_recv] [nd_mid] [nd_args]
#     |           |           | # example: foo + bar
#     |           |           +- nd_mid (method id): :%
#     |           |           +- nd_recv (receiver):
#     |           |           |   @ NODE_STR (id: 12, line: 5, location: (5,0)-(5,3))
#     |           |           |   | # string literal
#     |           |           |   | # format: [nd_lit]
#     |           |           |   | # example: 'foo'
#     |           |           |   +- nd_lit (literal): ""

注意:这只是获取解析树的最简单的方法,不幸的是,它包含了许多内部细节,与弄清楚发生了什么并不真正相关.还有其他方法,如parser gem或其配套的ast,可以产生更具可读性的结果:

# ruby-parse count.rb
(begin
  (def :count
    (args
      (arg :str))
    (begin
      (lvasgn :state
        (sym :start))
      (lvasgn :tbr
        (array))
      (block
        (send
          (lvar :str) :each_char)
        (args)
        (send
          (dstr) :%
          (case
            (lvar :state)
            (when
              (sym :start)
              (begin
                (send
                  (lvar :tbr) :<<
                  (int 0))
                (send
                  (dstr) :%
                  (lvasgn :state
                    (sym :symbol)))
                (dstr)))
            (when
              (sym :symbol)
              (begin
                (send
                  (lvar :tbr) :<<
                  (int 1))
                (send
                  (dstr) :%
                  (lvasgn :state
                    (sym :start)))
                (dstr))) nil)))
      (lvar :tbr)))
  (send nil :p
    (send nil :count
      (str "Foobar"))))

Semantics

到目前为止,我们所讨论的都是Syntax,即代码的语法 struct .但它是做什么的mean

方法String#%执行String Formattingprintf family of functions.然而,由于格式字符串(%消息的接收者)是空字符串,因此消息发送的结果也是空字符串,因为没有要格式化的内容.

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

def count(str)
  state = :start
  tbr = []
  str.each_char do
"".%(case state
    when :start
      tbr << 0
  ""
 ""when :symbol
      tbr << 1
 ""
 ""end)
  end
  tbr
end

p count("Foobar")

which in turn 相当于 this

def count(str)
  state = :start
  tbr = []
  str.each_char do
"".%(case state
    when :start
      tbr << 0
  ""
 when :symbol
      tbr << 1
 ""
 end)
  end
  tbr
end

p count("Foobar")

which 相当于 this

def count(str)
  state = :start
  tbr = []
  str.each_char do
"".%(case state
    when :start
  ""
 when :symbol
 ""
 end)
  end
  tbr
end

p count("Foobar")

which 相当于 this

def count(str)
  state = :start
  tbr = []
  str.each_char do
"".%(case state
    when :start, :symbol
 ""
 end)
  end
  tbr
end

p count("Foobar")

which 相当于 this

def count(str)
  state = :start
  tbr = []
  str.each_char do
""
  end
  tbr
end

p count("Foobar")

which 相当于 this

def count(str)
  state = :start
  tbr = []
  tbr
end

p count("Foobar")

which 相当于 this

def count(str)
  []
end

p count("Foobar")

显然,这不是正在发生的事情,原因是Ruby isn't是一种纯粹的函数式、懒惰、非严格的语言.虽然传递给%个消息发送的参数是irrelevant to the result of the message send个,但它们仍然会被计算(因为Ruby是严格的和Eager 的),并且它们有副作用(因为Ruby不是纯粹的函数式的),即它们的重新赋值变量和改变tbr结果数组的副作用仍然会被执行.

如果这段代码是以一种更像Ruby的风格编写的,Mutations 更少,副作用更少,而是使用函数转换,那么任意用空字符串替换结果会立即 destruct 它.这里没有效果的唯一原因是副作用和Mutations 的大量使用.

Ruby相关问答推荐

如何使用 gsub 删除返回字符串中的/和/i?

Ruby open-uri 重定向被禁止

获取字符串中的最后一个字符

如何从 url 获取文件扩展名?

如何通过 Rack 提供静态文件?

Ruby 哈希文字的顺序是否得到保证?

如何在不使用 Ruby 保存到磁盘的情况下生成 zip 文件?

在 Ruby 早期转义 .each { } 迭代

您如何将 Cucumber 场景标记为待处理

Ruby 1.9 - 无效的多字节字符(US-ASCII)

RSpec:如何测试文件操作和文件内容

无法正确自动生成 Ruby DevKit 配置文件

使用 Ruby,我如何迭代一个 for 循环 n.times

Ruby 中的 %w{} 和 %W{} 大写和小写百分比 W 数组文字有什么区别?

在命名包含多个单词的Ruby 时,是否应该使用破折号或下划线?

是否有更简单的(单行)语法来别名一个类方法?

错误数量的参数(1 代表 0)在 Ruby 中是什么意思?

如何在 jekyll markdown 博客中包含视频

Python 中的一切都像 Ruby 一样是对象吗?

Ruby 疯狂:类与对象?