Skip to content

Commit ae907a8

Browse files
committed
Working on more parity
1 parent 1807929 commit ae907a8

24 files changed

Lines changed: 542 additions & 17 deletions
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
module Twig
4+
module ExpressionParser
5+
module Infix
6+
class Assignment < InfixExpressionParser
7+
def parse(parser, left, token)
8+
right = parser.parse_expression(precedence)
9+
10+
case left
11+
when Node::Expression::Array
12+
return Node::Expression::Binary::SequenceDestructuringSetBinary.new(left, right, token.lineno)
13+
when Node::Expression::Hash
14+
return Node::Expression::Binary::ObjectDestructuringSetBinary.new(left, right, token.lineno)
15+
when Node::Expression::Variable::Context
16+
return Node::Expression::Binary::SetBinary.new(left, right, token.lineno)
17+
end
18+
19+
raise Error::Syntax.new(
20+
"Cannot assign to \"#{left.class}\", only variables can be assigned.",
21+
token.lineno,
22+
parser.stream.source
23+
)
24+
end
25+
26+
def name
27+
'='
28+
end
29+
30+
def description
31+
'Assignment operator'
32+
end
33+
34+
def precedence
35+
0
36+
end
37+
38+
def associativity
39+
RIGHT
40+
end
41+
42+
def aliases
43+
[]
44+
end
45+
end
46+
end
47+
end
48+
end

lib/twig/expression_parser/infix/dot.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def parse(parser, left, _token)
2424
attribute = Node::Expression::Constant.new(token.value, token.lineno)
2525
else
2626
raise Error::Syntax.new(
27-
"Expected name or number, got value \"#{token.value}\" of type #{token.type}.",
27+
"Expected name or number, got value \"#{token.value}\" of type \"#{token.to_english}\".",
2828
token.lineno,
2929
stream.source
3030
)

lib/twig/expression_parser/infix/parses_arguments.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,11 @@ def parse_named_arguments(parser, parse_open_parenthesis: true)
5050
end
5151

5252
name = nil
53-
if (token = stream.next_if(Token::OPERATOR_TYPE, '=')) ||
54-
(token = stream.next_if(Token::PUNCTUATION_TYPE, ':'))
53+
if value.is_a?(Node::Expression::Binary::SetBinary)
54+
name = value.nodes[:left].attributes[:name]
55+
value = value.nodes[:right]
56+
elsif (token = stream.next_if(Token::OPERATOR_TYPE, '=')) ||
57+
(token = stream.next_if(Token::PUNCTUATION_TYPE, ':'))
5558
# Allow quoted kwargs - form_with("data-turbo-stream": true)
5659
if value.is_a?(Node::Expression::Constant) && value.attributes[:value].is_a?(String)
5760
name = value.attributes[:value]

lib/twig/expression_parser/prefix/literal.rb

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,6 @@ def parse(parser, token)
7777
return Node::Expression::Variable::Context.new(token.value, token.lineno)
7878
end
7979

80-
if token.value == '=' && %w[== !=].include?(parser.stream.look(-1).value)
81-
raise Error::Syntax.new(
82-
"Unexpected operator of value \"#{token.value}\". Did you try to use \"===\" or \"!==\" for " \
83-
'strict comparison? Use "is same as(value)" instead.',
84-
token.lineno,
85-
parser.stream.source
86-
)
87-
end
8880
end
8981

9082
raise Error::Syntax.new(
@@ -155,7 +147,9 @@ def parse_sequence_expression(parser)
155147

156148
first = false
157149

158-
if stream.next_if(Token::OPERATOR_TYPE, '...')
150+
if stream.test(Token::PUNCTUATION_TYPE, ',') || stream.test(Token::PUNCTUATION_TYPE, ']')
151+
node.add_element(Node::Expression::EmptySlot.new(stream.current.lineno))
152+
elsif stream.next_if(Token::OPERATOR_TYPE, '...')
159153
expr = parser.parse_expression
160154
node.add_element(Node::Expression::Unary::ArraySpread.new(expr, expr.lineno))
161155
else

lib/twig/expression_parser/prefix/unary.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,19 @@ module Prefix
66
class Unary < PrefixExpressionParser
77
attr_reader :aliases, :precedence, :name
88

9-
def initialize(node_class, name, precedence, description: nil, aliases: [])
9+
def initialize(node_class, name, precedence, description: nil, aliases: [], operand_precedence: nil)
1010
super()
1111

1212
@node_class = node_class
1313
@name = name
1414
@precedence = precedence
15+
@operand_precedence = operand_precedence
1516
@description = description
1617
@aliases = aliases
1718
end
1819

1920
def parse(parser, token)
20-
@node_class.new(parser.parse_expression(precedence), token.lineno)
21+
@node_class.new(parser.parse_expression(@operand_precedence || precedence), token.lineno)
2122
end
2223

2324
def description

lib/twig/extension/core.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def expression_parsers
2525
[
2626
# Unary operators
2727
unary.new(Node::Expression::Unary::Not, 'not', 70),
28-
unary.new(Node::Expression::Unary::Spread, '...', 512, description: 'Spread Operator'),
28+
unary.new(Node::Expression::Unary::Spread, '...', 512, description: 'Spread Operator', operand_precedence: 0),
2929
unary.new(Node::Expression::Unary::Neg, '-', 500),
3030
unary.new(Node::Expression::Unary::Pos, '+', 500),
3131

@@ -46,6 +46,8 @@ def expression_parsers
4646
binary.new(Node::Expression::Binary::BitwiseAnd, 'b-and', 16),
4747
binary.new(Node::Expression::Binary::Equal, '==', 20),
4848
binary.new(Node::Expression::Binary::NotEqual, '!=', 20),
49+
binary.new(Node::Expression::Binary::SameAs, '===', 20),
50+
binary.new(Node::Expression::Binary::NotSameAs, '!==', 20),
4951
binary.new(Node::Expression::Binary::Spaceship, '<=>', 20),
5052
binary.new(Node::Expression::Binary::Less, '<', 20),
5153
binary.new(Node::Expression::Binary::Greater, '>', 20),
@@ -87,6 +89,9 @@ def expression_parsers
8789
# Arrow function
8890
ExpressionParser::Infix::Arrow.new,
8991

92+
# Assignment operator
93+
ExpressionParser::Infix::Assignment.new,
94+
9095
# All literals
9196
ExpressionParser::Prefix::Literal.new,
9297
]

lib/twig/lexer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ def lex_raw_data_regex
457457
def operator_regex
458458
return @operator_regex if defined?(@operator_regex)
459459

460-
expression_parsers = ['=']
460+
expression_parsers = []
461461

462462
environment.expression_parsers.each do |expression_parser|
463463
expression_parsers.concat([expression_parser.name], expression_parser.aliases)

lib/twig/node/expression/array.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ def compile(compiler)
2424
return compiler.repr(true)
2525
end
2626

27+
# Empty expressions are only valid in destructuring contexts
28+
values.each do |value|
29+
if value.is_a?(Expression::EmptySlot)
30+
raise Error::Syntax.new('Empty array elements are only allowed in destructuring assignments.',
31+
value.lineno)
32+
end
33+
end
34+
2735
compiler.
2836
raw('[').
2937
indent

lib/twig/node/expression/binary/base.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ def operator(compiler)
4949
Div: '/',
5050
Mod: '%',
5151
Power: '**',
52+
SameAs: '==',
53+
NotSameAs: '!=',
5254
}.freeze
5355

5456
# Lots of simple operator classes can just be generated dynamically
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# frozen_string_literal: true
2+
3+
module Twig
4+
module Node
5+
module Expression
6+
module Binary
7+
class ObjectDestructuringSetBinary < Binary::Base
8+
def initialize(left, right, lineno)
9+
@mappings = []
10+
11+
left.key_value_pairs.each do |key, value|
12+
unless value.is_a?(Variable::Context)
13+
raise Error::Syntax.new(
14+
"Cannot assign to \"#{value.class}\", only variables can be assigned in " \
15+
'object/mapping destructuring.',
16+
lineno
17+
)
18+
end
19+
20+
@mappings << {
21+
property: key.attributes[:value],
22+
variable: value.attributes[:name],
23+
}
24+
end
25+
26+
super
27+
end
28+
29+
def compile(compiler)
30+
compiler.add_debug_info(self)
31+
32+
@mappings.each_with_index do |mapping, i|
33+
compiler.raw(', ') if i.positive?
34+
compiler.raw('context[').string(mapping[:variable]).raw(']')
35+
end
36+
37+
compiler.raw(' = ')
38+
39+
@mappings.each_with_index do |mapping, i|
40+
compiler.raw(', ') if i.positive?
41+
compiler.
42+
raw('::Twig::Extension::Core.get_attribute(env, source_context, ').
43+
subcompile(nodes[:right]).
44+
raw(', ').
45+
repr(mapping[:property]).
46+
raw(', ').
47+
repr(Template::ANY_CALL).
48+
raw(', lineno: ').
49+
repr(nodes[:right].lineno).
50+
raw(')')
51+
end
52+
end
53+
54+
def operator(compiler)
55+
compiler.raw('=')
56+
end
57+
end
58+
end
59+
end
60+
end
61+
end

0 commit comments

Comments
 (0)