Compare commits
155 Commits
main
...
GH-1194-fo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e67623cbf | ||
|
|
7151d778c4 | ||
|
|
0581e3d8bd | ||
|
|
a3aa821a5f | ||
|
|
140297e547 | ||
|
|
94efd92a59 | ||
|
|
c97786d9dd | ||
|
|
f872dcf65b | ||
|
|
f11698df16 | ||
|
|
adc2dce109 | ||
|
|
61060096bd | ||
|
|
143517e89f | ||
|
|
9f3853815a | ||
|
|
d760329966 | ||
|
|
32a5f9312a | ||
|
|
b7c80abdac | ||
|
|
9bb793c190 | ||
|
|
f86503d13a | ||
|
|
639e8d2dff | ||
|
|
1907179e06 | ||
|
|
d8ad01caee | ||
|
|
a0364dd019 | ||
|
|
52a618e587 | ||
|
|
4ebdaf162e | ||
|
|
fca9030c92 | ||
|
|
591b250f71 | ||
|
|
4d39c1693f | ||
|
|
2c53564fa1 | ||
|
|
1a6bc1cec5 | ||
|
|
5c5b819434 | ||
|
|
27d6eeba57 | ||
|
|
9d473e0e8a | ||
|
|
23a145dc2d | ||
|
|
e5d018fdef | ||
|
|
06bcc5f9ac | ||
|
|
67e082fdf3 | ||
|
|
7a34fa03b4 | ||
|
|
99947cf39a | ||
|
|
46da157031 | ||
|
|
9e20ca14bf | ||
|
|
1a776bf469 | ||
|
|
7ea592b69a | ||
|
|
fa7c886eaf | ||
|
|
d12c147d56 | ||
|
|
cb6d4938e5 | ||
|
|
05a3ad7be1 | ||
|
|
fcea3e0de4 | ||
|
|
ea76328cfb | ||
|
|
60bcc59151 | ||
|
|
658dbaaf12 | ||
|
|
c9a65a9e7f | ||
|
|
4ac2db7a16 | ||
|
|
e8689ca98c | ||
|
|
00e950e831 | ||
|
|
9cf1c578d4 | ||
|
|
66908d0c52 | ||
|
|
4f194fcf41 | ||
|
|
3f8bcd5f0b | ||
|
|
9d408a7d5c | ||
|
|
dadb931c84 | ||
|
|
487fa26d79 | ||
|
|
05d348e29d | ||
|
|
72a327ae4e | ||
|
|
8c5a124c5c | ||
|
|
b948be4625 | ||
|
|
cf8262bd47 | ||
|
|
9b2bee5f5e | ||
|
|
6c179cf714 | ||
|
|
983f480532 | ||
|
|
83665a1706 | ||
|
|
07a3d8988f | ||
|
|
64d169038b | ||
|
|
a877716f53 | ||
|
|
5d2e372899 | ||
|
|
0d9afd99ed | ||
|
|
33d66c4003 | ||
|
|
650eb73721 | ||
|
|
826ad27552 | ||
|
|
374f1ed29f | ||
|
|
fb82cba2c8 | ||
|
|
2f9cca0b75 | ||
|
|
9e94e24afd | ||
|
|
53c8bdba35 | ||
|
|
6ddfd8c565 | ||
|
|
c5bcac08bd | ||
|
|
b091d41a54 | ||
|
|
00d9c0cfce | ||
|
|
f0f03f745b | ||
|
|
70227394b0 | ||
|
|
90f036a934 | ||
|
|
6116df58b6 | ||
|
|
2c2a04f3c6 | ||
|
|
272a7e9b7f | ||
|
|
9b29c5e1c3 | ||
|
|
77147d8c10 | ||
|
|
807b6effbc | ||
|
|
1e98d2b19c | ||
|
|
d54d93ef2f | ||
|
|
51f4815dde | ||
|
|
a221d0a387 | ||
|
|
f524b56a42 | ||
|
|
f38c803529 | ||
|
|
58d0b06100 | ||
|
|
ae6773932a | ||
|
|
38a2839dd0 | ||
|
|
ba5b4ec205 | ||
|
|
30505b5df6 | ||
|
|
1bc1068258 | ||
|
|
cf6c74b8ab | ||
|
|
e0c739aae5 | ||
|
|
a2d262fde4 | ||
|
|
80a6e5b3e7 | ||
|
|
e9f327c2ee | ||
|
|
ff7b3202fd | ||
|
|
17945c23a5 | ||
|
|
1a2512a1bc | ||
|
|
ebf4fea5e9 | ||
|
|
266caac0ee | ||
|
|
ae85164da6 | ||
|
|
41c23c37cc | ||
|
|
060a6f301f | ||
|
|
f9acfe01dd | ||
|
|
fdc7c285d4 | ||
|
|
9174140c79 | ||
|
|
dccbd0ba48 | ||
|
|
35c9bc10c2 | ||
|
|
f96c34565f | ||
|
|
bcf99099b6 | ||
|
|
5799223303 | ||
|
|
b030045a7a | ||
|
|
c098a6089d | ||
|
|
6a80dfd6fb | ||
|
|
711e47bf5c | ||
|
|
7949f22fd1 | ||
|
|
7ac9e2d978 | ||
|
|
e7ff13e6e1 | ||
|
|
f69ad20fb5 | ||
|
|
caf8992e99 | ||
|
|
a9d8449fa1 | ||
|
|
5fdbdec06c | ||
|
|
e9e098cc48 | ||
|
|
d8dafa2e18 | ||
|
|
ba6e96207d | ||
|
|
080489c63c | ||
|
|
3480d1fa0e | ||
|
|
611bdcfcc4 | ||
|
|
4a610f5357 | ||
|
|
f12d76704d | ||
|
|
9f92f5c9e8 | ||
|
|
bf331cafba | ||
|
|
22764094bb | ||
|
|
ca3ecffd9a | ||
|
|
1604a0c87b | ||
|
|
8bebf88507 | ||
|
|
db4ad51426 |
5
.gitignore
vendored
5
.gitignore
vendored
@ -18,3 +18,8 @@ venv-*/
|
||||
htmlcov
|
||||
.pytest_cache/
|
||||
/.vscode/
|
||||
|
||||
tatsu_jinja.json
|
||||
tatsu_jinja.py
|
||||
parsed_jinja.py
|
||||
test_template.jinja
|
||||
664
grammar.ebnf
Normal file
664
grammar.ebnf
Normal file
@ -0,0 +1,664 @@
|
||||
start
|
||||
=
|
||||
expressions
|
||||
$
|
||||
;
|
||||
|
||||
expressions
|
||||
=
|
||||
{expression}*
|
||||
;
|
||||
|
||||
expression
|
||||
=
|
||||
| content
|
||||
| raw_block_expression
|
||||
| block_expression
|
||||
| line_block_expression
|
||||
| variable_expression
|
||||
| comment_expression
|
||||
| line_comment_expression
|
||||
;
|
||||
|
||||
raw_block_expression
|
||||
=
|
||||
raw_block_start
|
||||
raw:{ !raw_block_end CHAR }*
|
||||
raw_block_end
|
||||
;
|
||||
|
||||
raw_block_start
|
||||
=
|
||||
block_open "raw" {SP}* block_close
|
||||
;
|
||||
|
||||
raw_block_end
|
||||
=
|
||||
block_open "endraw" {SP}* block_close
|
||||
;
|
||||
|
||||
block_expression
|
||||
=
|
||||
| block_expression_pair
|
||||
| block_expression_single
|
||||
;
|
||||
|
||||
block_expression_pair
|
||||
=
|
||||
start:block_start contents:expressions end:block_end
|
||||
;
|
||||
|
||||
block_expression_single
|
||||
=
|
||||
block:block_start
|
||||
;
|
||||
|
||||
block_start
|
||||
=
|
||||
block_open !("end") name:IDENTIFIER [ "(" name_call_parameters:variable_accessor_call_parameters ")" ]
|
||||
[ {SP}+ parameters:block_parameters ]
|
||||
{SP}* block_close
|
||||
;
|
||||
|
||||
block_end
|
||||
=
|
||||
block_open "end" name:IDENTIFIER {SP}* block_close
|
||||
;
|
||||
|
||||
block_open
|
||||
=
|
||||
| ( {SP}* block_open_symbol "-" {SP}* )
|
||||
| block_open_symbol {SP}*
|
||||
;
|
||||
|
||||
block_open_symbol
|
||||
=
|
||||
"{%"
|
||||
;
|
||||
|
||||
block_close
|
||||
=
|
||||
| ( "-" block_close_symbol {SP}* )
|
||||
| block_close_symbol
|
||||
;
|
||||
|
||||
block_close_symbol
|
||||
=
|
||||
"%}"
|
||||
;
|
||||
|
||||
line_block_expression
|
||||
=
|
||||
| line_block_expression_pair
|
||||
| line_block_expression_single
|
||||
;
|
||||
|
||||
line_block_expression_pair
|
||||
=
|
||||
start:line_block_start contents:expressions end:line_block_end
|
||||
;
|
||||
|
||||
line_block_expression_single
|
||||
=
|
||||
block:line_block_start
|
||||
;
|
||||
|
||||
line_block_start
|
||||
=
|
||||
line_block_open !("end") name:IDENTIFIER { !"\n" SP}* parameters:[ line_block_parameters ] [ { !"\n" SP }* ":" ] line_block_close
|
||||
;
|
||||
|
||||
line_block_end
|
||||
=
|
||||
line_block_open "end" name:IDENTIFIER line_block_close
|
||||
;
|
||||
|
||||
line_block_open
|
||||
=
|
||||
{ !"\n" SP }* line_block_open_symbol { !"\n" SP }*
|
||||
;
|
||||
|
||||
line_block_open_symbol
|
||||
=
|
||||
"#"
|
||||
;
|
||||
|
||||
line_block_close
|
||||
=
|
||||
| ( {SP}* $ )
|
||||
| ( { !"\n" SP }* "\n" )
|
||||
;
|
||||
|
||||
line_block_parameters
|
||||
=
|
||||
@+:block_parameter { { !"\n" SP }+ @+:block_parameter }*
|
||||
;
|
||||
|
||||
block_parameters
|
||||
=
|
||||
@+:block_parameter
|
||||
{
|
||||
block_parameter_separator
|
||||
@+:block_parameter
|
||||
}*
|
||||
;
|
||||
|
||||
block_parameter_separator
|
||||
=
|
||||
| ( {SP}* "," {SP}* )
|
||||
| ( {SP}+ )
|
||||
;
|
||||
|
||||
block_parameter
|
||||
=
|
||||
| block_parameter_key_value
|
||||
| block_parameter_value_only
|
||||
;
|
||||
|
||||
block_parameter_key_value
|
||||
=
|
||||
key:block_parameter_key {SP}* "=" {SP}* value:variable_accessor_call_parameter_value
|
||||
;
|
||||
|
||||
block_parameter_key
|
||||
=
|
||||
variable_accessor_call_parameter_key
|
||||
;
|
||||
|
||||
block_parameter_value_only
|
||||
=
|
||||
| value:variable_identifier_with_alias
|
||||
| value:variable_accessor_call_parameter_value
|
||||
| value:conditional_expression
|
||||
;
|
||||
|
||||
variable_expression
|
||||
=
|
||||
variable_open type:`variable` name:variable_expression_name variable_close
|
||||
;
|
||||
|
||||
variable_open
|
||||
=
|
||||
| ( {SP}* variable_open_symbol "-" {SP}* )
|
||||
| ( variable_open_symbol {SP}* )
|
||||
;
|
||||
|
||||
variable_open_symbol
|
||||
=
|
||||
"{{"
|
||||
;
|
||||
|
||||
variable_close
|
||||
=
|
||||
| ( {SP}* "-" variable_close_symbol {SP}* )
|
||||
| ( {SP}* variable_close_symbol )
|
||||
;
|
||||
|
||||
variable_close_symbol
|
||||
=
|
||||
"}}"
|
||||
;
|
||||
|
||||
variable_expression_name
|
||||
=
|
||||
| TUPLE_LITERAL
|
||||
| conditional_expression
|
||||
;
|
||||
|
||||
variable_identifier
|
||||
=
|
||||
| variable_identifier_parentheses
|
||||
| variable_identifier_raw
|
||||
;
|
||||
|
||||
variable_identifier_parentheses
|
||||
=
|
||||
"(" @:conditional_expression ")"
|
||||
;
|
||||
|
||||
variable_identifier_raw
|
||||
=
|
||||
[ signed:( "-" | "+" ) ]
|
||||
variable:( LITERAL | IDENTIFIER )
|
||||
accessors:{ variable_accessor }*
|
||||
{ {SP}* filters+:variable_filter }*
|
||||
;
|
||||
|
||||
variable_identifier_with_alias
|
||||
=
|
||||
variable:IDENTIFIER
|
||||
{SP}* "as" {SP}*
|
||||
alias:IDENTIFIER
|
||||
;
|
||||
|
||||
variable_accessor
|
||||
=
|
||||
| variable_accessor_brackets
|
||||
| variable_accessor_call
|
||||
| variable_accessor_dot
|
||||
;
|
||||
|
||||
variable_accessor_brackets
|
||||
=
|
||||
accessor_type:`brackets`
|
||||
"[" parameter:variable_identifier "]"
|
||||
;
|
||||
|
||||
variable_accessor_call
|
||||
=
|
||||
accessor_type:`call`
|
||||
"(" parameters:[variable_accessor_call_parameters] ")"
|
||||
;
|
||||
|
||||
variable_accessor_dot
|
||||
=
|
||||
accessor_type:`dot`
|
||||
"." parameter:( IDENTIFIER | NUMBER_LITERAL )
|
||||
;
|
||||
|
||||
variable_accessor_call_parameters
|
||||
=
|
||||
@+:variable_accessor_call_parameter
|
||||
{ {SP}* "," {SP}* @+:variable_accessor_call_parameter }*
|
||||
;
|
||||
|
||||
variable_accessor_call_parameter
|
||||
=
|
||||
| variable_accessor_call_parameter_key_value
|
||||
| variable_accessor_call_parameter_value_only
|
||||
| variable_accessor_call_parameter_vararg
|
||||
| variable_accessor_call_parameter_varkwarg
|
||||
;
|
||||
|
||||
variable_accessor_call_parameter_vararg
|
||||
=
|
||||
"*" dynamic_argument:variable_identifier
|
||||
;
|
||||
|
||||
variable_accessor_call_parameter_varkwarg
|
||||
=
|
||||
"**" dynamic_keyword_argument:variable_identifier
|
||||
;
|
||||
|
||||
variable_accessor_call_parameter_key_value
|
||||
=
|
||||
key:variable_accessor_call_parameter_key {SP}* "=" {SP}* value:variable_accessor_call_parameter_value
|
||||
;
|
||||
|
||||
variable_accessor_call_parameter_value_only
|
||||
=
|
||||
value:variable_accessor_call_parameter_value
|
||||
;
|
||||
|
||||
variable_accessor_call_parameter_key
|
||||
=
|
||||
IDENTIFIER
|
||||
;
|
||||
|
||||
variable_accessor_call_parameter_value
|
||||
=
|
||||
conditional_expression
|
||||
;
|
||||
|
||||
conditional_expression
|
||||
=
|
||||
| conditional_expression_not
|
||||
| conditional_expression_if
|
||||
| conditional_expression_logical
|
||||
| conditional_expression_operator
|
||||
| conditional_expression_test
|
||||
| complex_expression
|
||||
| variable_identifier
|
||||
| conditional_expression_parentheses
|
||||
;
|
||||
|
||||
complex_expression
|
||||
=
|
||||
| complex_expression_powers
|
||||
| complex_expression_math2
|
||||
| concatenate_expression
|
||||
| complex_expression_math1
|
||||
| complex_expression_parentheses
|
||||
| variable_identifier
|
||||
;
|
||||
|
||||
complex_expression_powers
|
||||
=
|
||||
left:variable_identifier {SP}* math_operator:"**" {SP}* right:variable_identifier
|
||||
;
|
||||
|
||||
complex_expression_math2
|
||||
=
|
||||
left:variable_identifier
|
||||
{SP}* math_operator:complex_expression_math2_operations {SP}*
|
||||
right:variable_identifier
|
||||
;
|
||||
|
||||
complex_expression_math2_operations
|
||||
=
|
||||
| "*"
|
||||
| "/"
|
||||
| "//"
|
||||
| "%"
|
||||
;
|
||||
|
||||
complex_expression_math1
|
||||
=
|
||||
left:variable_identifier
|
||||
{SP}* math_operator:complex_expression_math1_operations {SP}*
|
||||
right:complex_expression
|
||||
;
|
||||
|
||||
complex_expression_math1_operations
|
||||
=
|
||||
| "+"
|
||||
| "-"
|
||||
;
|
||||
|
||||
complex_expression_parentheses
|
||||
=
|
||||
"(" {SP}*
|
||||
complex_expression
|
||||
{SP}* ")"
|
||||
;
|
||||
|
||||
conditional_expression_parentheses
|
||||
=
|
||||
"(" {SP}* @:conditional_expression {SP}* ")"
|
||||
;
|
||||
|
||||
conditional_expression_not
|
||||
=
|
||||
"not" {SP}+ not:conditional_expression
|
||||
;
|
||||
|
||||
conditional_expression_if
|
||||
=
|
||||
true_value:variable_identifier
|
||||
{SP}* "if" {SP}*
|
||||
test_expression:conditional_expression
|
||||
[ {SP}* "else" {SP}* false_value:conditional_expression ]
|
||||
;
|
||||
|
||||
conditional_expression_logical
|
||||
=
|
||||
left:conditional_expression
|
||||
{SP}* logical_operator:variable_tests_logical_operator {SP}*
|
||||
right:conditional_expression
|
||||
;
|
||||
|
||||
conditional_expression_operator
|
||||
=
|
||||
conditional_expression_operator_in
|
||||
| (
|
||||
left:complex_expression
|
||||
{SP}* operator:conditional_expression_operator_operations {SP}*
|
||||
right:conditional_expression
|
||||
)
|
||||
;
|
||||
|
||||
conditional_expression_operator_in
|
||||
=
|
||||
| (
|
||||
"not"
|
||||
left:variable_identifier
|
||||
{SP}* operator:`"notin"` "in" {SP}*
|
||||
right:variable_identifier
|
||||
)
|
||||
| (
|
||||
left:variable_identifier
|
||||
{SP}+
|
||||
(
|
||||
| ( "not" {SP}* "in" operator:`"notin"` )
|
||||
| operator:"in"
|
||||
)
|
||||
{SP}+
|
||||
right:variable_identifier
|
||||
)
|
||||
;
|
||||
|
||||
conditional_expression_test
|
||||
=
|
||||
test_variable:variable_identifier
|
||||
{SP}* "is"
|
||||
[ {SP}+ "not" {SP} negated:`True` ]
|
||||
{SP}*
|
||||
test_function:variable_identifier
|
||||
[
|
||||
{SP}*
|
||||
!( (variable_tests_logical_operator | "is" | "else" ) {SP}* )
|
||||
test_function_parameter:variable_identifier
|
||||
]
|
||||
;
|
||||
|
||||
conditional_expression_operator_operations
|
||||
=
|
||||
| "=="
|
||||
| "!="
|
||||
| ">"
|
||||
| ">="
|
||||
| "<"
|
||||
| "<="
|
||||
;
|
||||
|
||||
variable_tests_logical_operator
|
||||
=
|
||||
| "and"
|
||||
| "or"
|
||||
;
|
||||
|
||||
concatenate_expression
|
||||
=
|
||||
concatenate+:variable_identifier
|
||||
{ {SP}* "~" {SP}* concatenate+:variable_identifier }+
|
||||
;
|
||||
|
||||
variable_filter
|
||||
=
|
||||
"|" {SP}* @:filter
|
||||
;
|
||||
filter =
|
||||
variable:IDENTIFIER
|
||||
accessors:{ variable_accessor_call }*
|
||||
;
|
||||
|
||||
comment_expression
|
||||
=
|
||||
comment_open comment:comment_content comment_close
|
||||
;
|
||||
|
||||
comment_open =
|
||||
comment_open_symbol
|
||||
;
|
||||
|
||||
comment_open_symbol
|
||||
=
|
||||
"{#"
|
||||
;
|
||||
|
||||
comment_close
|
||||
=
|
||||
comment_close_symbol
|
||||
;
|
||||
|
||||
comment_close_symbol
|
||||
=
|
||||
"#}"
|
||||
;
|
||||
|
||||
comment_content
|
||||
=
|
||||
{ !comment_close CHAR }*
|
||||
;
|
||||
|
||||
line_comment_expression
|
||||
=
|
||||
line_comment_open comment:line_comment_content &"\n"
|
||||
;
|
||||
|
||||
line_comment_open
|
||||
=
|
||||
{SP}* line_comment_open_symbol
|
||||
;
|
||||
|
||||
line_comment_open_symbol
|
||||
=
|
||||
'##'
|
||||
;
|
||||
|
||||
line_comment_content
|
||||
=
|
||||
{ !"\n" CHAR }*
|
||||
;
|
||||
|
||||
content
|
||||
=
|
||||
!(
|
||||
| line_block_open
|
||||
| block_open
|
||||
| variable_open
|
||||
| comment_open
|
||||
| line_comment_open
|
||||
) CHAR ;
|
||||
|
||||
LITERAL
|
||||
=
|
||||
| NONE_LITERAL
|
||||
| STRING_LITERAL
|
||||
| NUMBER_LITERAL
|
||||
| BOOLEAN_LITERAL
|
||||
| DICTIONARY_LITERAL
|
||||
| LIST_LITERAL
|
||||
| EXPLICIT_TUPLE_LITERAL
|
||||
;
|
||||
|
||||
DICTIONARY_LITERAL
|
||||
=
|
||||
literal_type:`dictionary`
|
||||
(
|
||||
| ( "{" {SP}* value+:dictionary_key_value { {SP}* "," {SP}* value+:dictionary_key_value } {SP}* "}" )
|
||||
| ( "{" {SP}* "}" )
|
||||
)
|
||||
;
|
||||
|
||||
dictionary_key_value
|
||||
=
|
||||
key:STRING_LITERAL {SP}* ":" {SP}* value:variable_identifier
|
||||
;
|
||||
|
||||
LIST_LITERAL
|
||||
=
|
||||
literal_type:`list`
|
||||
(
|
||||
| ( "[" {SP}* value+:variable_identifier {SP}* { "," {SP}* value+:variable_identifier }* {SP}* "]" )
|
||||
| ( "[" {SP}* "]" )
|
||||
)
|
||||
;
|
||||
|
||||
TUPLE_LITERAL
|
||||
=
|
||||
| EXPLICIT_TUPLE_LITERAL
|
||||
| IMPLICIT_TUPLE_LITERAL
|
||||
| EMPTY_TUPLE_LITERAL
|
||||
;
|
||||
|
||||
EXPLICIT_TUPLE_LITERAL
|
||||
=
|
||||
literal_type:`tuple`
|
||||
"(" value:TUPLE_LITERAL_CONTENTS ")"
|
||||
;
|
||||
|
||||
IMPLICIT_TUPLE_LITERAL
|
||||
=
|
||||
literal_type:`tuple`
|
||||
value:TUPLE_LITERAL_CONTENTS
|
||||
;
|
||||
|
||||
TUPLE_LITERAL_CONTENTS
|
||||
=
|
||||
| ( @+:variable_identifier {SP}* { "," {SP}* @+:variable_identifier {SP}* }+ )
|
||||
| ( @+:variable_identifier {SP}* "," {SP}* )
|
||||
;
|
||||
|
||||
EMPTY_TUPLE_LITERAL
|
||||
=
|
||||
literal_type:`tuple`
|
||||
"(" {SP}* ")"
|
||||
;
|
||||
|
||||
INTEGER_LITERAL
|
||||
=
|
||||
/[\d_]*\d+/
|
||||
;
|
||||
|
||||
SIGNED_INTEGER_LITERAL
|
||||
=
|
||||
/[+-]?[\d_]*\d+/
|
||||
;
|
||||
|
||||
NUMBER_LITERAL
|
||||
=
|
||||
literal_type:`number`
|
||||
whole:INTEGER_LITERAL
|
||||
[ "." fractional:INTEGER_LITERAL ]
|
||||
[ ( "e" | "E" ) exponent:SIGNED_INTEGER_LITERAL ]
|
||||
;
|
||||
|
||||
STRING_LITERAL
|
||||
=
|
||||
| STRING_LITERAL_SINGLE_QUOTE
|
||||
| STRING_LITERAL_DOUBLE_QUOTE
|
||||
;
|
||||
|
||||
STRING_LITERAL_SINGLE_QUOTE
|
||||
=
|
||||
literal_type:`string`
|
||||
"'" value:{ !"'" /./ }* "'"
|
||||
;
|
||||
|
||||
STRING_LITERAL_DOUBLE_QUOTE
|
||||
=
|
||||
literal_type:`string`
|
||||
'"' value:{ !'"' /./ }* '"'
|
||||
;
|
||||
|
||||
BOOLEAN_LITERAL
|
||||
=
|
||||
literal_type:`boolean`
|
||||
(
|
||||
| ( ("true" | "True") value:`True`)
|
||||
| ( ("false" | "False") value:`False`)
|
||||
)
|
||||
;
|
||||
|
||||
NONE_LITERAL
|
||||
=
|
||||
literal_type:`none`
|
||||
( "none" | "None" ) value:`None`
|
||||
;
|
||||
|
||||
IDENTIFIER
|
||||
=
|
||||
/[a-zA-Z_][a-zA-Z0-9_]*/
|
||||
;
|
||||
|
||||
ALPHA
|
||||
=
|
||||
/[a-zA-Z]/
|
||||
;
|
||||
|
||||
DIGIT
|
||||
=
|
||||
/[0-9]/
|
||||
;
|
||||
|
||||
SP
|
||||
=
|
||||
/\s/
|
||||
;
|
||||
|
||||
CHAR
|
||||
=
|
||||
| ?'.'
|
||||
| ?'\s'
|
||||
;
|
||||
2
setup.py
2
setup.py
@ -34,7 +34,7 @@ setup(
|
||||
package_dir={"": "src"},
|
||||
include_package_data=True,
|
||||
python_requires=">=3.6",
|
||||
install_requires=["MarkupSafe>=1.1"],
|
||||
install_requires=["MarkupSafe>=1.1", "TatSu"],
|
||||
extras_require={"i18n": ["Babel>=2.1"]},
|
||||
entry_points={"babel.extractors": ["jinja2 = jinja2.ext:babel_extract[i18n]"]},
|
||||
)
|
||||
|
||||
@ -50,6 +50,7 @@ from .utils import missing
|
||||
|
||||
# for direct template usage we have up to ten living environments
|
||||
_spontaneous_environments = LRUCache(10)
|
||||
_grammar_cache = LRUCache(10)
|
||||
|
||||
|
||||
def get_spontaneous_environment(cls, *args):
|
||||
@ -527,6 +528,69 @@ class Environment:
|
||||
"""Internal parsing function used by `parse` and `compile`."""
|
||||
return Parser(self, source, name, filename).parse()
|
||||
|
||||
def get_grammar(self):
|
||||
import tatsu
|
||||
|
||||
grammar_extensions = ''
|
||||
|
||||
with open('grammar.ebnf', 'r') as grammar_file:
|
||||
base_grammar = grammar_file.read()
|
||||
|
||||
if self.block_start_string:
|
||||
grammar_extensions += '''
|
||||
@override
|
||||
block_open_symbol = %r;
|
||||
''' % (self.block_start_string)
|
||||
|
||||
if self.block_end_string:
|
||||
grammar_extensions += '''
|
||||
@override
|
||||
block_close_symbol = %r;
|
||||
''' % (self.block_end_string)
|
||||
|
||||
if self.variable_start_string:
|
||||
grammar_extensions += '''
|
||||
@override
|
||||
variable_open_symbol = %r;
|
||||
''' % (self.variable_start_string)
|
||||
|
||||
if self.variable_end_string:
|
||||
grammar_extensions += '''
|
||||
@override
|
||||
variable_close_symbol = %r;
|
||||
''' % (self.variable_end_string)
|
||||
|
||||
if self.comment_start_string:
|
||||
grammar_extensions += '''
|
||||
@override
|
||||
comment_open_symbol = %r;
|
||||
''' % (self.comment_start_string)
|
||||
|
||||
if self.comment_end_string:
|
||||
grammar_extensions += '''
|
||||
@override
|
||||
comment_close_symbol = %r;
|
||||
''' % (self.comment_end_string)
|
||||
|
||||
if self.line_statement_prefix:
|
||||
grammar_extensions += '''
|
||||
@override
|
||||
line_block_open_symbol = %r;
|
||||
''' % (self.line_statement_prefix)
|
||||
|
||||
if self.line_comment_prefix:
|
||||
grammar_extensions += '''
|
||||
@override
|
||||
line_comment_open_symbol = %r;
|
||||
''' % (self.line_comment_prefix)
|
||||
|
||||
final_grammar = base_grammar + grammar_extensions
|
||||
|
||||
if final_grammar not in _grammar_cache:
|
||||
_grammar_cache[final_grammar] = tatsu.compile(final_grammar)
|
||||
|
||||
return _grammar_cache[final_grammar]
|
||||
|
||||
def lex(self, source, name=None, filename=None):
|
||||
"""Lex the given sourcecode and return a generator that yields
|
||||
tokens as tuples in the form ``(lineno, token_type, value)``.
|
||||
|
||||
998
src/jinja2/new_parser.py
Normal file
998
src/jinja2/new_parser.py
Normal file
@ -0,0 +1,998 @@
|
||||
from tatsu.exceptions import FailedSemantics
|
||||
from . import nodes
|
||||
from .exceptions import TemplateSyntaxError
|
||||
|
||||
|
||||
class JinjaSemantics(object):
|
||||
|
||||
def block_expression_pair(self, ast):
|
||||
start_block = ast['start']
|
||||
end_block = ast['end']
|
||||
|
||||
if start_block['name'] != end_block['name']:
|
||||
raise FailedSemantics()
|
||||
|
||||
return ast
|
||||
|
||||
def line_block_expression_pair(self, ast):
|
||||
return self.block_expression_pair(ast)
|
||||
|
||||
|
||||
def lineno_from_parseinfo(parseinfo):
|
||||
return parseinfo.line + 1
|
||||
|
||||
def parse(ast):
|
||||
def merge_output(blocks):
|
||||
if len(blocks) < 2:
|
||||
return blocks
|
||||
|
||||
for idx in range(len(blocks) - 1, 0, -1):
|
||||
block = blocks[idx]
|
||||
previous_block = blocks[idx - 1]
|
||||
|
||||
if isinstance(block, nodes.Output) and isinstance(previous_block, nodes.Output):
|
||||
previous_block.nodes += block.nodes
|
||||
del blocks[idx]
|
||||
|
||||
return blocks
|
||||
|
||||
def merge_template_data(blocks):
|
||||
for block in blocks:
|
||||
if isinstance(block, nodes.Output):
|
||||
if len(block.nodes) < 2:
|
||||
continue
|
||||
|
||||
outputs = block.nodes
|
||||
|
||||
for idx in range(len(outputs) - 1, 0, -1):
|
||||
output = outputs[idx]
|
||||
previous_output = outputs[idx - 1]
|
||||
|
||||
if isinstance(output, nodes.TemplateData) and isinstance(previous_output, nodes.TemplateData):
|
||||
previous_output.data += output.data
|
||||
del outputs[idx]
|
||||
|
||||
return blocks
|
||||
|
||||
def remove_none(blocks):
|
||||
return [block for block in blocks if block is not None]
|
||||
|
||||
if isinstance(ast, list):
|
||||
blocks = [parse(item) for item in ast]
|
||||
return merge_template_data(merge_output(remove_none(blocks)))
|
||||
|
||||
if isinstance(ast, str):
|
||||
return parse_output(ast)
|
||||
|
||||
if 'type' in ast and ast['type'] == 'variable':
|
||||
return parse_print(ast)
|
||||
|
||||
if 'block' in ast:
|
||||
return parse_block(ast)
|
||||
|
||||
if 'start' in ast and 'end' in ast:
|
||||
return parse_block_pair(ast)
|
||||
|
||||
if 'raw' in ast:
|
||||
return parse_raw(ast)
|
||||
|
||||
if 'comment' in ast:
|
||||
return parse_comment(ast)
|
||||
|
||||
return None
|
||||
|
||||
def parse_block(ast):
|
||||
block_name = ast['block']['name']
|
||||
|
||||
if block_name == 'extends':
|
||||
return parse_block_extends(ast)
|
||||
|
||||
if block_name == 'from':
|
||||
return parse_block_from(ast)
|
||||
|
||||
if block_name == 'import':
|
||||
return parse_block_import(ast)
|
||||
|
||||
if block_name == 'include':
|
||||
return parse_block_include(ast)
|
||||
|
||||
if block_name == 'print':
|
||||
return parse_block_print(ast)
|
||||
|
||||
if block_name == 'set':
|
||||
return parse_block_set(ast)
|
||||
|
||||
return None
|
||||
|
||||
def parse_block_pair(ast):
|
||||
block_name = ast['start']['name']
|
||||
|
||||
if block_name == 'autoescape':
|
||||
return parse_block_autoescape(ast)
|
||||
|
||||
if block_name == 'block':
|
||||
return parse_block_block(ast)
|
||||
|
||||
if block_name == 'call':
|
||||
return parse_block_call(ast)
|
||||
|
||||
if block_name == 'filter':
|
||||
return parse_block_filter(ast)
|
||||
|
||||
if block_name == 'for':
|
||||
return parse_block_for(ast)
|
||||
|
||||
if block_name == 'if':
|
||||
return parse_block_if(ast)
|
||||
|
||||
if block_name == 'macro':
|
||||
return parse_block_macro(ast)
|
||||
|
||||
if block_name == 'set':
|
||||
return parse_block_set(ast)
|
||||
|
||||
if block_name == 'with':
|
||||
return parse_block_with(ast)
|
||||
|
||||
return None
|
||||
|
||||
def parse_block_autoescape(ast):
|
||||
return nodes.Scope(
|
||||
[nodes.ScopedEvalContextModifier(
|
||||
[nodes.Keyword(
|
||||
'autoescape',
|
||||
parse_variable(ast['start']['parameters'][0]['value']),
|
||||
lineno=lineno_from_parseinfo(ast['start']['parameters'][0]['parseinfo'])
|
||||
)],
|
||||
parse(ast['contents']),
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)],
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_block(ast):
|
||||
name = parse_variable(ast['start']['parameters'][0]['value']).name
|
||||
scoped = False
|
||||
|
||||
if len(ast['start']['parameters']) > 1:
|
||||
scoped = ast['start']['parameters'][-1]['value']['variable'] == 'scoped'
|
||||
|
||||
return nodes.Block(
|
||||
name,
|
||||
parse(ast['contents']),
|
||||
scoped,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_call(ast):
|
||||
parameters = ast['start']['parameters']
|
||||
|
||||
call = parse_variable(parameters[0]['value'])
|
||||
args = []
|
||||
defaults = []
|
||||
body = parse(ast['contents'])
|
||||
|
||||
if 'name_call_parameters' in ast['start']:
|
||||
for arg in ast['start']['name_call_parameters']:
|
||||
args.append(parse_variable(arg['value'], variable_context='param'))
|
||||
|
||||
return nodes.CallBlock(
|
||||
call,
|
||||
args,
|
||||
defaults,
|
||||
body,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_extends(ast):
|
||||
return nodes.Extends(
|
||||
parse_conditional_expression(ast['block']['parameters'][0]['value'])
|
||||
)
|
||||
|
||||
def parse_block_filter(ast):
|
||||
body = parse(ast['contents'])
|
||||
filter_parameter = ast['start']['parameters'][0]['value']
|
||||
|
||||
filter_base = parse_variable(filter_parameter)
|
||||
|
||||
if isinstance(filter_base, nodes.Filter):
|
||||
filter = filter_base
|
||||
while isinstance(filter.node, nodes.Filter):
|
||||
filter = filter.node
|
||||
|
||||
args = []
|
||||
kwargs = []
|
||||
dynamic_args = None
|
||||
dynamic_kwargs = None
|
||||
|
||||
inner_filter = filter.node
|
||||
|
||||
if isinstance(inner_filter, nodes.Call):
|
||||
args = inner_filter.args
|
||||
kwargs = inner_filter.kwargs
|
||||
dynamic_args = inner_filter.dyn_args
|
||||
dynamic_kwargs = inner_filter.dyn_kwargs
|
||||
|
||||
inner_filter = inner_filter.node
|
||||
|
||||
inner_filter = nodes.Filter(
|
||||
None,
|
||||
inner_filter.name,
|
||||
args,
|
||||
kwargs,
|
||||
dynamic_args,
|
||||
dynamic_kwargs,
|
||||
lineno=inner_filter.lineno
|
||||
)
|
||||
filter.node = inner_filter
|
||||
|
||||
return nodes.FilterBlock(
|
||||
body,
|
||||
filter_base,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_for(ast):
|
||||
iter = None
|
||||
body = ast['contents']
|
||||
else_ = []
|
||||
test = None
|
||||
recursive = False
|
||||
|
||||
block_parameters = ast['start']['parameters']
|
||||
|
||||
target = []
|
||||
for param_number, param in enumerate(block_parameters):
|
||||
if param['value']['variable'] == 'in':
|
||||
break
|
||||
|
||||
if param['value']['operator'] == 'in':
|
||||
block_parameters[param_number:param_number + 1] = [
|
||||
{
|
||||
"value": param['value']['left']
|
||||
},
|
||||
{
|
||||
"value": {
|
||||
"variable": "in"
|
||||
}
|
||||
},
|
||||
{
|
||||
"value": param['value']['right']
|
||||
},
|
||||
]
|
||||
|
||||
target.append(
|
||||
parse_variable(
|
||||
param['value']['left'],
|
||||
variable_context='store'
|
||||
)
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
target.append(parse_variable(param['value'], variable_context='store'))
|
||||
|
||||
if len(target) == 0:
|
||||
raise TemplateSyntaxError(
|
||||
"expected token 'in'",
|
||||
lineno=lineno_from_parseinfo(ast['start']['parseinfo'])
|
||||
)
|
||||
|
||||
if len(target) == len(block_parameters):
|
||||
raise TemplateSyntaxError(
|
||||
"expected token 'in'",
|
||||
lineno=target[1].lineno
|
||||
)
|
||||
|
||||
if len(target) == 1:
|
||||
target = target[0]
|
||||
else:
|
||||
target = nodes.Tuple(
|
||||
target,
|
||||
'store',
|
||||
lineno=target[0].lineno
|
||||
)
|
||||
param_number += 2
|
||||
|
||||
iter = parse_variable(block_parameters[param_number]['value'])
|
||||
param_number += 1
|
||||
|
||||
if len(block_parameters) > param_number + 1:
|
||||
if block_parameters[param_number]['value']['variable'] == 'if':
|
||||
param_number += 1
|
||||
|
||||
test = parse_conditional_expression(
|
||||
block_parameters[param_number]['value']
|
||||
)
|
||||
param_number += 1
|
||||
|
||||
if len(block_parameters) > param_number + 2:
|
||||
raise
|
||||
|
||||
if len(block_parameters) == param_number + 1:
|
||||
recursive = block_parameters[param_number]['value']['variable'] == 'recursive'
|
||||
|
||||
else_ = _split_contents_at_block(ast['contents'], 'else')
|
||||
|
||||
if else_ is not None:
|
||||
body, _, else_ = else_
|
||||
else:
|
||||
else_ = []
|
||||
|
||||
return nodes.For(
|
||||
target, iter, parse(body), parse(else_), test, recursive,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_from(ast):
|
||||
parameters = ast['block']['parameters']
|
||||
|
||||
template = parse_variable(parameters[0]['value'])
|
||||
names = []
|
||||
with_context = _parse_import_context(parameters)
|
||||
|
||||
if with_context is None:
|
||||
with_context = False
|
||||
else:
|
||||
del parameters[-2:]
|
||||
|
||||
if len(parameters) > 1 and parameters[1]['value']['variable'] != 'import':
|
||||
raise TemplateSyntaxError(
|
||||
"Expecting 'import' but did not find it",
|
||||
lineno=lineno_from_parseinfo(parameters[1]['parseinfo'])
|
||||
)
|
||||
|
||||
if len(parameters) == 2:
|
||||
raise TemplateSyntaxError(
|
||||
"expected token 'name', got 'end of statement block'",
|
||||
lineno=lineno_from_parseinfo(parameters[1]['parseinfo'])
|
||||
)
|
||||
|
||||
def _variable_to_name(variable):
|
||||
if isinstance(variable, str):
|
||||
return variable
|
||||
|
||||
if 'alias' in variable:
|
||||
return (
|
||||
variable['variable'],
|
||||
variable['alias']
|
||||
)
|
||||
|
||||
return variable['variable']
|
||||
|
||||
for parameter in parameters[2:]:
|
||||
if 'tuple' in parameter['value']:
|
||||
for variable in parameter['value']['tuple']:
|
||||
names.append(_variable_to_name(variable))
|
||||
else:
|
||||
names.append(_variable_to_name(parameter['value']))
|
||||
|
||||
from_import = nodes.FromImport(
|
||||
template,
|
||||
names,
|
||||
with_context,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
return from_import
|
||||
|
||||
def parse_block_if(ast):
|
||||
test = parse_conditional_expression(ast['start']['parameters'][0]['value'])
|
||||
body = ast['contents']
|
||||
elif_ = []
|
||||
|
||||
else_ = _split_contents_at_block(body, 'else')
|
||||
|
||||
if else_ is not None:
|
||||
body, _, else_ = else_
|
||||
else:
|
||||
else_ = []
|
||||
|
||||
elif_contents = _split_contents_at_block(body, 'elif')
|
||||
|
||||
if elif_contents is not None:
|
||||
body, _, _ = elif_contents
|
||||
|
||||
while elif_contents is not None:
|
||||
_, elif_condition, elif_contents = elif_contents
|
||||
|
||||
elif_parsed = _split_contents_at_block(elif_contents, 'elif')
|
||||
|
||||
if elif_parsed is not None:
|
||||
elif_body, _, _ = elif_parsed
|
||||
else:
|
||||
elif_body = elif_contents
|
||||
|
||||
elif_.append(
|
||||
nodes.If(
|
||||
parse_conditional_expression(elif_condition['block']['parameters'][0]['value']),
|
||||
parse(elif_body),
|
||||
[],
|
||||
[],
|
||||
lineno=lineno_from_parseinfo(elif_condition['parseinfo'])
|
||||
)
|
||||
)
|
||||
|
||||
elif_contents = elif_parsed
|
||||
|
||||
return nodes.If(
|
||||
test,
|
||||
parse(body),
|
||||
elif_,
|
||||
parse(else_),
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_import(ast):
|
||||
block_parameters = ast['block']['parameters']
|
||||
|
||||
template = parse_variable(block_parameters[0]['value'])
|
||||
target = None
|
||||
with_context = _parse_import_context(block_parameters) or False
|
||||
|
||||
if len(block_parameters) > 2 and block_parameters[1]['value']['variable'] == 'as':
|
||||
target = parse_variable(block_parameters[2]['value']).name
|
||||
|
||||
return nodes.Import(
|
||||
template,
|
||||
target,
|
||||
with_context,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_include(ast):
|
||||
block_parameters = ast['block']['parameters']
|
||||
|
||||
template = parse_conditional_expression(block_parameters[0]['value'])
|
||||
with_context = _parse_import_context(block_parameters)
|
||||
ignore_missing = False
|
||||
|
||||
if with_context is None:
|
||||
with_context = True
|
||||
else:
|
||||
del block_parameters[-2:]
|
||||
|
||||
if len(block_parameters) == 3:
|
||||
ignore_missing = True
|
||||
|
||||
if block_parameters[1]['value']['variable'] != 'ignore' and block_parameters[2]['value']['variable'] != 'missing':
|
||||
raise
|
||||
|
||||
return nodes.Include(
|
||||
template,
|
||||
with_context,
|
||||
ignore_missing,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_macro(ast):
|
||||
definition = parse_variable(ast['start']['parameters'][0]['value'])
|
||||
name = definition.node.name
|
||||
params = []
|
||||
defaults = []
|
||||
body = parse(ast['contents'])
|
||||
|
||||
for arg in definition.args:
|
||||
arg.set_ctx('param')
|
||||
params.append(arg)
|
||||
|
||||
for kwarg in definition.kwargs:
|
||||
params.append(
|
||||
nodes.Name(kwarg.key, 'param')
|
||||
)
|
||||
defaults.append(kwarg.value)
|
||||
|
||||
return nodes.Macro(
|
||||
name,
|
||||
params,
|
||||
defaults,
|
||||
body,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_block_print(ast):
|
||||
node = parse_variable(ast['block']['parameters'][0]['value'])
|
||||
|
||||
return nodes.Output([node])
|
||||
|
||||
def parse_block_set(ast):
|
||||
if 'block' in ast:
|
||||
assignment = ast['block']['parameters'][0]
|
||||
|
||||
if isinstance(assignment['key'], str):
|
||||
key = nodes.Name(assignment['key'], 'store')
|
||||
else:
|
||||
key = parse_variable(assignment['key'], variable_context="store")
|
||||
|
||||
return nodes.Assign(
|
||||
key,
|
||||
parse_conditional_expression(assignment['value']),
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
elif 'start' in ast:
|
||||
key = parse_variable(ast['start']['parameters'][0]['value'], variable_context="store")
|
||||
filter = None
|
||||
|
||||
if isinstance(key, nodes.Filter):
|
||||
filter = key
|
||||
key = key.node
|
||||
filter.node = None
|
||||
|
||||
return nodes.AssignBlock(
|
||||
key,
|
||||
filter,
|
||||
parse(ast['contents']),
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
return None
|
||||
|
||||
def parse_block_with(ast):
|
||||
with_node = nodes.With(
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
targets = []
|
||||
values = []
|
||||
|
||||
for parameter in ast['start']['parameters']:
|
||||
if 'key' not in parameter:
|
||||
raise
|
||||
|
||||
targets.append(nodes.Name(parameter['key'], 'param'))
|
||||
values.append(parse_variable(parameter['value']))
|
||||
|
||||
with_node.targets = targets
|
||||
with_node.values = values
|
||||
with_node.body = parse(ast['contents'])
|
||||
|
||||
return with_node
|
||||
|
||||
def parse_comment(ast):
|
||||
return
|
||||
|
||||
def parse_concatenate_expression(ast):
|
||||
vars = [
|
||||
parse_variable(var) for var in ast['concatenate']
|
||||
]
|
||||
|
||||
return nodes.Concat(
|
||||
vars,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_conditional_expression(ast):
|
||||
if 'variable' in ast:
|
||||
return parse_variable(ast)
|
||||
|
||||
if 'literal_type' in ast:
|
||||
return parse_literal(ast)
|
||||
|
||||
if 'concatenate' in ast:
|
||||
return parse_concatenate_expression(ast)
|
||||
|
||||
if 'logical_operator' in ast:
|
||||
return parse_conditional_expression_logical(ast)
|
||||
|
||||
if 'math_operator' in ast:
|
||||
return parse_conditional_expression_math(ast)
|
||||
|
||||
if 'not' in ast:
|
||||
return parse_conditional_expression_not(ast)
|
||||
|
||||
if 'operator' in ast:
|
||||
return parse_conditional_expression_operator(ast)
|
||||
|
||||
if 'test_expression' in ast:
|
||||
return parse_conditional_expression_if(ast)
|
||||
|
||||
if 'test_function' in ast:
|
||||
return parse_conditional_expression_test(ast)
|
||||
|
||||
return None
|
||||
|
||||
def parse_conditional_expression_if(ast):
|
||||
test = parse_conditional_expression(ast['test_expression'])
|
||||
expr1 = parse_variable(ast['true_value'])
|
||||
expr2 = None
|
||||
|
||||
if 'false_value' in ast:
|
||||
expr2 = parse_variable(ast['false_value'])
|
||||
|
||||
return nodes.CondExpr(
|
||||
test,
|
||||
expr1,
|
||||
expr2,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_conditional_expression_logical(ast):
|
||||
node_class_map = {
|
||||
'and': nodes.And,
|
||||
'or': nodes.Or,
|
||||
}
|
||||
|
||||
node_class = node_class_map[ast['logical_operator']]
|
||||
|
||||
return node_class(
|
||||
parse_conditional_expression(ast['left']),
|
||||
parse_conditional_expression(ast['right']),
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_conditional_expression_math(ast):
|
||||
node_class_map = {
|
||||
'+': nodes.Add,
|
||||
'-': nodes.Sub,
|
||||
'*': nodes.Mul,
|
||||
'**': nodes.Pow,
|
||||
'/': nodes.Div,
|
||||
'//': nodes.FloorDiv,
|
||||
'%': nodes.Mod,
|
||||
}
|
||||
|
||||
node_class = node_class_map[ast['math_operator']]
|
||||
|
||||
return node_class(
|
||||
parse_conditional_expression(ast['left']),
|
||||
parse_conditional_expression(ast['right']),
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_conditional_expression_not(ast):
|
||||
return nodes.Not(
|
||||
parse_conditional_expression(ast['not']),
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_conditional_expression_operator(ast):
|
||||
operand_map = {
|
||||
'>': 'gt',
|
||||
'>=': 'gteq',
|
||||
'==': 'eq',
|
||||
'!=': 'ne',
|
||||
'<': 'lt',
|
||||
'<=': 'lteq',
|
||||
}
|
||||
|
||||
expr = parse_conditional_expression(ast['left'])
|
||||
operator = operand_map.get(ast['operator'], ast['operator'])
|
||||
operands = []
|
||||
|
||||
right = parse_conditional_expression(ast['right'])
|
||||
|
||||
if isinstance(right, nodes.Compare):
|
||||
operands.append(
|
||||
nodes.Operand(
|
||||
operator,
|
||||
right.expr
|
||||
)
|
||||
)
|
||||
operands.extend(right.ops)
|
||||
else:
|
||||
|
||||
operands.append(
|
||||
nodes.Operand(
|
||||
operator,
|
||||
right
|
||||
)
|
||||
)
|
||||
|
||||
return nodes.Compare(
|
||||
expr,
|
||||
operands,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
def parse_conditional_expression_test(ast):
|
||||
node = parse_conditional_expression(ast['test_variable'])
|
||||
test_function = parse_variable(ast['test_function'])
|
||||
|
||||
args = []
|
||||
kwargs = []
|
||||
dynamic_args = None
|
||||
dynamic_kwargs = None
|
||||
|
||||
if isinstance(test_function, nodes.Call):
|
||||
call = test_function
|
||||
|
||||
name = call.node.name
|
||||
args = call.args
|
||||
kwargs = call.kwargs
|
||||
dynamic_args = call.dyn_args
|
||||
dynamic_kwargs = call.dyn_kwargs
|
||||
elif isinstance(test_function, nodes.Const):
|
||||
const_map = {
|
||||
None: 'none',
|
||||
True: 'true',
|
||||
False: 'false',
|
||||
}
|
||||
|
||||
name = const_map[test_function.value]
|
||||
else:
|
||||
name = test_function.name
|
||||
|
||||
|
||||
if ast['test_function_parameter']:
|
||||
args = [
|
||||
parse_conditional_expression(ast['test_function_parameter'])
|
||||
]
|
||||
|
||||
test_node = nodes.Test(
|
||||
node,
|
||||
name,
|
||||
args,
|
||||
kwargs,
|
||||
dynamic_args,
|
||||
dynamic_kwargs,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
if 'negated' in ast and ast['negated']:
|
||||
test_node = nodes.Not(
|
||||
test_node,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
return test_node
|
||||
|
||||
def parse_literal(ast):
|
||||
if 'literal_type' not in ast:
|
||||
raise
|
||||
|
||||
literal_type = ast['literal_type']
|
||||
|
||||
if literal_type == 'boolean':
|
||||
return nodes.Const(
|
||||
ast['value'],
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
elif literal_type == 'string':
|
||||
return nodes.Const(
|
||||
''.join(ast['value']),
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
elif literal_type == 'number':
|
||||
if 'fractional' not in ast and 'exponent' not in ast:
|
||||
const = int(ast['whole'])
|
||||
else:
|
||||
number = ast['whole']
|
||||
|
||||
if 'fractional' in ast:
|
||||
number += '.' + ast['fractional']
|
||||
|
||||
if 'exponent' in ast:
|
||||
number += 'e' + ast['exponent']
|
||||
|
||||
const = float(number)
|
||||
|
||||
return nodes.Const(
|
||||
const,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
elif literal_type == 'dictionary':
|
||||
if not ast['value']:
|
||||
ast['value'] = []
|
||||
|
||||
|
||||
items = [
|
||||
nodes.Pair(
|
||||
parse_literal(item['key']),
|
||||
parse_variable(item['value']),
|
||||
lineno=lineno_from_parseinfo(item['parseinfo'])
|
||||
)
|
||||
for item in ast['value']
|
||||
]
|
||||
|
||||
return nodes.Dict(
|
||||
items,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
elif literal_type == 'none':
|
||||
return nodes.Const(
|
||||
None,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
elif literal_type == 'list':
|
||||
if not ast['value']:
|
||||
ast['value'] = []
|
||||
|
||||
items = [
|
||||
parse_variable(item) for item in ast['value']
|
||||
]
|
||||
|
||||
return nodes.List(
|
||||
items,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
elif literal_type == 'tuple':
|
||||
if not ast['value']:
|
||||
ast['value'] = []
|
||||
|
||||
items = [
|
||||
parse_variable(item) for item in ast['value']
|
||||
]
|
||||
|
||||
return nodes.Tuple(
|
||||
items,
|
||||
'load',
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
return None
|
||||
|
||||
def parse_output(ast):
|
||||
return nodes.Output(
|
||||
[nodes.TemplateData(ast)]
|
||||
)
|
||||
|
||||
def parse_print(ast):
|
||||
variable = ast['name']
|
||||
|
||||
node = parse_conditional_expression(variable)
|
||||
|
||||
return nodes.Output([node])
|
||||
|
||||
def parse_raw(ast):
|
||||
return parse_output(
|
||||
''.join(ast['raw'])
|
||||
)
|
||||
|
||||
def parse_template(ast):
|
||||
return nodes.Template(parse(ast), lineno=1)
|
||||
|
||||
def parse_variable(ast, variable_context='load'):
|
||||
name = ast['variable']
|
||||
|
||||
if 'literal_type' in name:
|
||||
node = parse_literal(name)
|
||||
else:
|
||||
node = nodes.Name(
|
||||
name,
|
||||
variable_context,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
for accessor_ast in ast['accessors']:
|
||||
node = parse_variable_accessor(node, accessor_ast)
|
||||
|
||||
signed_node_map = {
|
||||
'-': nodes.Neg,
|
||||
'+': nodes.Pos,
|
||||
}
|
||||
|
||||
if 'signed' in ast:
|
||||
node_class = signed_node_map[ast['signed']]
|
||||
|
||||
node = node_class(node)
|
||||
|
||||
if ast['filters']:
|
||||
for filter_ast in ast['filters']:
|
||||
node = parse_variable_filter(node, filter_ast)
|
||||
|
||||
return node
|
||||
|
||||
def parse_variable_accessor(node, ast):
|
||||
accessor_type = ast['accessor_type']
|
||||
|
||||
if accessor_type == 'brackets':
|
||||
accessor_node = nodes.Getitem()
|
||||
accessor_node.arg = parse_variable(ast['parameter'])
|
||||
elif accessor_type == 'dot':
|
||||
if isinstance(ast['parameter'], str):
|
||||
accessor_node = nodes.Getattr()
|
||||
accessor_node.attr = ast['parameter']
|
||||
else:
|
||||
accessor_node = nodes.Getitem()
|
||||
accessor_node.arg = parse_literal(ast['parameter'])
|
||||
elif accessor_type == 'call':
|
||||
accessor_node = parse_variable_accessor_call(ast)
|
||||
|
||||
accessor_node.node = node
|
||||
accessor_node.ctx = "load"
|
||||
accessor_node.lineno = lineno_from_parseinfo(ast['parseinfo'])
|
||||
|
||||
return accessor_node
|
||||
|
||||
def parse_variable_accessor_call(ast):
|
||||
args = []
|
||||
kwargs = []
|
||||
dynamic_args = None
|
||||
dynamic_kwargs = None
|
||||
|
||||
if ast['parameters']:
|
||||
for argument in ast['parameters']:
|
||||
if dynamic_kwargs is not None:
|
||||
raise TemplateSyntaxError(
|
||||
'invalid syntax for function call expression',
|
||||
lineno=lineno_from_parseinfo(argument['parseinfo'])
|
||||
)
|
||||
|
||||
if 'dynamic_keyword_argument' in argument:
|
||||
|
||||
dynamic_kwargs = parse_variable(argument['dynamic_keyword_argument'])
|
||||
|
||||
continue
|
||||
|
||||
if dynamic_args is not None:
|
||||
raise TemplateSyntaxError(
|
||||
'invalid syntax for function call expression',
|
||||
lineno=lineno_from_parseinfo(argument['parseinfo'])
|
||||
)
|
||||
|
||||
if 'dynamic_argument' in argument:
|
||||
dynamic_args = parse_variable(argument['dynamic_argument'])
|
||||
|
||||
continue
|
||||
|
||||
value = parse_variable(argument['value'])
|
||||
|
||||
if 'key' in argument:
|
||||
kwargs.append(
|
||||
nodes.Keyword(argument['key'], value)
|
||||
)
|
||||
else:
|
||||
args.append(value)
|
||||
|
||||
node = nodes.Call()
|
||||
node.args = args
|
||||
node.kwargs = kwargs
|
||||
node.dyn_args = dynamic_args
|
||||
node.dyn_kwargs = dynamic_kwargs
|
||||
|
||||
return node
|
||||
|
||||
def parse_variable_filter(node, ast):
|
||||
args = []
|
||||
kwargs = []
|
||||
dynamic_args = None
|
||||
dynamic_kwargs = None
|
||||
|
||||
variable = parse_variable(ast)
|
||||
|
||||
filter_node = None
|
||||
last_filter = None
|
||||
start_variable = variable
|
||||
|
||||
while not isinstance(variable, nodes.Name):
|
||||
if isinstance(variable, nodes.Call):
|
||||
last_filter = filter_node
|
||||
filter_node = variable
|
||||
|
||||
variable = variable.node
|
||||
|
||||
new_filter = nodes.Filter(
|
||||
node,
|
||||
variable.name,
|
||||
args,
|
||||
kwargs,
|
||||
dynamic_args,
|
||||
dynamic_kwargs,
|
||||
lineno=lineno_from_parseinfo(ast['parseinfo'])
|
||||
)
|
||||
|
||||
if filter_node is not None:
|
||||
new_filter.args = filter_node.args
|
||||
new_filter.kwargs = filter_node.kwargs
|
||||
|
||||
if last_filter is None:
|
||||
return new_filter
|
||||
|
||||
last_filter.node = new_filter
|
||||
|
||||
return last_filter
|
||||
|
||||
def _parse_import_context(block_parameters):
|
||||
if block_parameters[-1]['value']['variable'] != 'context':
|
||||
return None
|
||||
|
||||
if block_parameters[-2]['value']['variable'] not in ['with', 'without']:
|
||||
return None
|
||||
|
||||
return block_parameters[-2]['value']['variable'] == 'with'
|
||||
|
||||
def _split_contents_at_block(contents, block_name):
|
||||
for index, expression in enumerate(contents):
|
||||
if 'block' in expression:
|
||||
if expression['block']['name'] == block_name:
|
||||
return (contents[:index], expression, contents[index + 1:])
|
||||
|
||||
return None
|
||||
@ -5,6 +5,7 @@ from .exceptions import TemplateSyntaxError
|
||||
from .lexer import describe_token
|
||||
from .lexer import describe_token_expr
|
||||
|
||||
|
||||
_statement_keywords = frozenset(
|
||||
[
|
||||
"for",
|
||||
@ -40,6 +41,8 @@ class Parser:
|
||||
|
||||
def __init__(self, environment, source, name=None, filename=None, state=None):
|
||||
self.environment = environment
|
||||
self.source = source
|
||||
self.grammar = environment.get_grammar()
|
||||
self.stream = environment._tokenize(source, name, filename, state)
|
||||
self.name = name
|
||||
self.filename = filename
|
||||
@ -927,8 +930,23 @@ class Parser:
|
||||
|
||||
return body
|
||||
|
||||
def parse(self):
|
||||
"""Parse the whole template into a `Template` node."""
|
||||
def parse_old(self):
|
||||
result = nodes.Template(self.subparse(), lineno=1)
|
||||
result.set_environment(self.environment)
|
||||
return result
|
||||
|
||||
def parse(self):
|
||||
"""Parse the whole template into a `Template` node."""
|
||||
from .new_parser import JinjaSemantics, parse_template
|
||||
|
||||
result = parse_template(
|
||||
self.grammar.parse(
|
||||
self.source.rstrip('\n'),
|
||||
whitespace='',
|
||||
parseinfo=True,
|
||||
semantics=JinjaSemantics(),
|
||||
)
|
||||
)
|
||||
result.set_environment(self.environment)
|
||||
|
||||
return result
|
||||
|
||||
33
test_tatsu.py
Normal file
33
test_tatsu.py
Normal file
@ -0,0 +1,33 @@
|
||||
from datetime import datetime
|
||||
import pprint
|
||||
from jinja2.environment import Environment
|
||||
from jinja2.parser import Parser
|
||||
|
||||
|
||||
with open('grammar.ebnf', 'r') as tatsu_grammar:
|
||||
with open('test_template.jinja', 'r') as test_template:
|
||||
template_string = test_template.read()
|
||||
|
||||
env = Environment(line_statement_prefix='#', line_comment_prefix='##')
|
||||
parser = Parser(env, template_string)
|
||||
|
||||
new_parse_start = datetime.now()
|
||||
|
||||
new_ast = parser.parse()
|
||||
|
||||
new_parse_end = datetime.now()
|
||||
|
||||
with open('tatsu_jinja.py', 'w') as new_ast_file:
|
||||
pprint.pprint(new_ast, indent=2, stream=new_ast_file)
|
||||
|
||||
jinja_parse_start = datetime.now()
|
||||
|
||||
jinja_ast = parser.parse_old()
|
||||
|
||||
jinja_parse_end = datetime.now()
|
||||
|
||||
with open('parsed_jinja.py', 'w') as jinja_ast_file:
|
||||
pprint.pprint(jinja_ast, indent=2, stream=jinja_ast_file)
|
||||
|
||||
print("New Parser", new_parse_end - new_parse_start)
|
||||
print("Jinja Parser", jinja_parse_end - jinja_parse_start)
|
||||
@ -194,22 +194,19 @@ class TestMeta:
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ["layout.html", "test.html", "meh.html", "muh.html"]
|
||||
|
||||
def test_find_included_templates(self, env):
|
||||
ast = env.parse('{% include ["foo.html", "bar.html"] %}')
|
||||
@pytest.mark.parametrize(
|
||||
"include,templates",
|
||||
(
|
||||
('{% include ["foo.html", "bar.html"] %}', ["foo.html", "bar.html"]),
|
||||
('{% include ("foo.html", "bar.html") %}', ["foo.html", "bar.html"]),
|
||||
('{% include ["foo.html", "bar.html", foo] %}', ["foo.html", "bar.html", None]),
|
||||
('{% include ("foo.html", "bar.html", foo) %}', ["foo.html", "bar.html", None])
|
||||
)
|
||||
)
|
||||
def test_find_included_templates(self, env, include, templates):
|
||||
ast = env.parse(include)
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ["foo.html", "bar.html"]
|
||||
|
||||
ast = env.parse('{% include ("foo.html", "bar.html") %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ["foo.html", "bar.html"]
|
||||
|
||||
ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ["foo.html", "bar.html", None]
|
||||
|
||||
ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
|
||||
i = meta.find_referenced_templates(ast)
|
||||
assert list(i) == ["foo.html", "bar.html", None]
|
||||
assert list(i) == templates
|
||||
|
||||
|
||||
class TestStreaming:
|
||||
|
||||
@ -449,8 +449,9 @@ class TestSyntax:
|
||||
tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}')
|
||||
assert tmpl.render() == "FOOBAR"
|
||||
|
||||
def test_function_calls(self, env):
|
||||
tests = [
|
||||
@pytest.mark.parametrize(
|
||||
"should_fail,sig",
|
||||
(
|
||||
(True, "*foo, bar"),
|
||||
(True, "*foo, *bar"),
|
||||
(True, "**foo, *bar"),
|
||||
@ -466,16 +467,18 @@ class TestSyntax:
|
||||
(False, "*foo, **bar"),
|
||||
(False, "*foo, bar=42, **baz"),
|
||||
(False, "foo, *args, bar=23, **baz"),
|
||||
]
|
||||
for should_fail, sig in tests:
|
||||
if should_fail:
|
||||
with pytest.raises(TemplateSyntaxError):
|
||||
env.from_string(f"{{{{ foo({sig}) }}}}")
|
||||
else:
|
||||
env.from_string(f"foo({sig})")
|
||||
)
|
||||
)
|
||||
def test_function_calls(self, env, should_fail, sig):
|
||||
if should_fail:
|
||||
with pytest.raises(TemplateSyntaxError):
|
||||
env.from_string(f"{{{{ foo({sig}) }}}}")
|
||||
else:
|
||||
env.from_string(f"foo({sig})")
|
||||
|
||||
def test_tuple_expr(self, env):
|
||||
for tmpl in [
|
||||
@pytest.mark.parametrize(
|
||||
"tmpl",
|
||||
(
|
||||
"{{ () }}",
|
||||
"{{ (1, 2) }}",
|
||||
"{{ (1, 2,) }}",
|
||||
@ -484,8 +487,10 @@ class TestSyntax:
|
||||
"{% for foo, bar in seq %}...{% endfor %}",
|
||||
"{% for x in foo, bar %}...{% endfor %}",
|
||||
"{% for x in foo, %}...{% endfor %}",
|
||||
]:
|
||||
assert env.from_string(tmpl)
|
||||
)
|
||||
)
|
||||
def test_tuple_expr(self, env, tmpl):
|
||||
assert env.from_string(tmpl)
|
||||
|
||||
def test_trailing_comma(self, env):
|
||||
tmpl = env.from_string("{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}")
|
||||
|
||||
@ -110,19 +110,25 @@ class TestSandbox:
|
||||
with pytest.raises(TemplateRuntimeError):
|
||||
t.render(ctx)
|
||||
|
||||
def test_unary_operator_intercepting(self, env):
|
||||
@pytest.mark.parametrize(
|
||||
"expr,ctx,rv",
|
||||
(
|
||||
("-1", {}, "-1"),
|
||||
("-a", {"a": 2}, "-2")
|
||||
)
|
||||
)
|
||||
def test_unary_operator_intercepting(self, env, expr, ctx, rv):
|
||||
def disable_op(arg):
|
||||
raise TemplateRuntimeError("that operator so does not work")
|
||||
|
||||
for expr, ctx, rv in ("-1", {}, "-1"), ("-a", {"a": 2}, "-2"):
|
||||
env = SandboxedEnvironment()
|
||||
env.unop_table["-"] = disable_op
|
||||
t = env.from_string(f"{{{{ {expr} }}}}")
|
||||
assert t.render(ctx) == rv
|
||||
env.intercepted_unops = frozenset(["-"])
|
||||
t = env.from_string(f"{{{{ {expr} }}}}")
|
||||
with pytest.raises(TemplateRuntimeError):
|
||||
t.render(ctx)
|
||||
env = SandboxedEnvironment()
|
||||
env.unop_table["-"] = disable_op
|
||||
t = env.from_string(f"{{{{ {expr} }}}}")
|
||||
assert t.render(ctx) == rv
|
||||
env.intercepted_unops = frozenset(["-"])
|
||||
t = env.from_string(f"{{{{ {expr} }}}}")
|
||||
with pytest.raises(TemplateRuntimeError):
|
||||
t.render(ctx)
|
||||
|
||||
|
||||
class TestStringFormat:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user