usado - ruby wikipedia




Por que uma declaração como 1+n*= 3 é permitida no Ruby? (3)

A resposta simplificada é. Você só pode atribuir um valor a uma variável, não a uma expressão. Portanto, o pedido é 1 + (age *= 2) . A precedência só entra em jogo se várias opções forem possíveis. Por exemplo, age *= 2 + 1 pode ser vista como (age *= 2) + 1 ou age *= (2 + 1) , pois várias opções são possíveis e o + tem uma precedência maior que *= , age *= (2 + 1) é usado.

As tabelas de precedência em muitas documentações do Ruby listam as operações aritméticas binárias como tendo precedência mais alta do que seus operadores de atribuição composta correspondentes. Isso me leva a acreditar que um código como esse não deveria ser um código Ruby válido, mas é.

1 + age *= 2

Se as regras de precedência estivessem corretas, eu esperaria que o código acima estivesse entre parênteses assim:

((1 + age) *= 2) #ERROR: Doesn't compile

Mas isso não acontece.

Então, o que dá?


Ruby tem 3 fases antes de seu código ser realmente executado.

Tokenizar -> Analisar -> Compilar

Vejamos o AST (Abstract Syntax Tree) Ruby gera qual é a fase de análise.

# @ NODE_SCOPE (line: 1, location: (1,0)-(1,12))
# | # new scope
# | # format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body
# +- nd_tbl (local table): :age
# +- nd_args (arguments):
# |   (null node)
# +- nd_body (body):
#     @ NODE_OPCALL (line: 1, location: (1,0)-(1,12))*
#     | # method invocation
#     | # format: [nd_recv] [nd_mid] [nd_args]
#     | # example: foo + bar
#     +- nd_mid (method id): :+
#     +- nd_recv (receiver):
#     |   @ NODE_LIT (line: 1, location: (1,0)-(1,1))
#     |   | # literal
#     |   | # format: [nd_lit]
#     |   | # example: 1, /foo/
#     |   +- nd_lit (literal): 1
#     +- nd_args (arguments):
#         @ NODE_ARRAY (line: 1, location: (1,4)-(1,12))
#         | # array constructor
#         | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
#         | # example: [1, 2, 3]
#         +- nd_alen (length): 1
#         +- nd_head (element):
#         |   @ NODE_DASGN_CURR (line: 1, location: (1,4)-(1,12))
#         |   | # dynamic variable assignment (in current scope)
#         |   | # format: [nd_vid](current dvar) = [nd_value]
#         |   | # example: 1.times { x = foo }
#         |   +- nd_vid (local variable): :age
#         |   +- nd_value (rvalue):
#         |       @ NODE_CALL (line: 1, location: (1,4)-(1,12))
#         |       | # method invocation
#         |       | # format: [nd_recv].[nd_mid]([nd_args])
#         |       | # example: obj.foo(1)
#         |       +- nd_mid (method id): :*
#         |       +- nd_recv (receiver):
#         |       |   @ NODE_DVAR (line: 1, location: (1,4)-(1,7))
#         |       |   | # dynamic variable reference
#         |       |   | # format: [nd_vid](dvar)
#         |       |   | # example: 1.times { x = 1; x }
#         |       |   +- nd_vid (local variable): :age
#         |       +- nd_args (arguments):
#         |           @ NODE_ARRAY (line: 1, location: (1,11)-(1,12))
#         |           | # array constructor
#         |           | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
#         |           | # example: [1, 2, 3]
#         |           +- nd_alen (length): 1
#         |           +- nd_head (element):
#         |           |   @ NODE_LIT (line: 1, location: (1,11)-(1,12))
#         |           |   | # literal
#         |           |   | # format: [nd_lit]
#         |           |   | # example: 1, /foo/
#         |           |   +- nd_lit (literal): 2
#         |           +- nd_next (next element):
#         |               (null node)
#         +- nd_next (next element):
#             (null node)

Como você pode ver # +- nd_mid (method id): :+ onde 1 é tratado como o receptor e tudo à direita como argumentos. Agora, ele vai além e faz o possível para avaliar os argumentos.

Para apoiar ainda mais a grande resposta de Aleksei. O @ NODE_DASGN_CURR (line: 1, location: (1,4)-(1,12)) é a atribuição de age como uma variável local, pois a decodifica como age = age * 2 , e é por isso que +- nd_mid (method id): :* é tratado como a operação na age como o receptor e 2 como seu argumento.

Agora, quando ele compila, tenta como operação: age * 2 onde a age é nula porque já a analisou como uma variável local sem valor pré-atribuído, gera o undefined method '*' for nil:NilClass (NoMethodError) exceção undefined method '*' for nil:NilClass (NoMethodError) .

Funciona da maneira que funcionou, pois qualquer operação no receptor deve ter um argumento avaliado do RHO.


Nota: esta resposta não deve ser marcada como solução do problema. Veja a resposta de @Amadan para a explicação correta.

Não tenho certeza do que "muitas documentações Ruby" você mencionou, aqui está a oficial .

O analisador Ruby faz o possível para entender e analisar com êxito a entrada; quando vê um erro de sintaxe, tenta de outra maneira. Dito isto, os erros de sintaxe têm maior precedência em comparação com todas as regras de precedência do operador.

Como o LHO deve ser variável, ele começa com uma atribuição . Aqui está o caso em que a análise pode ser feita com uma ordem de precedência padrão e + é feito antes de *= :

age = 2
age *= age + 1
#⇒ 6




ruby