diff --git a/Fold.tmPreferences b/Fold.tmPreferences new file mode 100644 index 0000000..206bb9a --- /dev/null +++ b/Fold.tmPreferences @@ -0,0 +1,51 @@ + + + + scope + text.html.ngx + settings + + foldScopes + + + begin + punctuation.definition.comment.begin + end + punctuation.definition.comment.end + excludeTrailingNewlines + + + + begin + punctuation.section.block.begin + end + punctuation.section.block.end + + + begin + punctuation.section.embedded.begin + end + punctuation.section.embedded.end + + + begin + punctuation.section.group.begin + end + punctuation.section.group.end + + + begin + punctuation.section.mapping.begin + end + punctuation.section.mapping.end + + + begin + punctuation.section.sequence.begin + end + punctuation.section.sequence.end + + + + + diff --git a/NgxHTML.sublime-syntax b/NgxHTML.sublime-syntax index 93099fb..7d90116 100644 --- a/NgxHTML.sublime-syntax +++ b/NgxHTML.sublime-syntax @@ -1,30 +1,39 @@ %YAML 1.2 --- name: Ngx HTML -file_extensions: - - component.html scope: text.html.ngx version: 2 extends: Packages/HTML/HTML.sublime-syntax +file_extensions: + - component.html + variables: - angular_blocks: (?xi:for | if | else | else if | switch | case | empty | placeholder | defer | default | error | loading | let)\b + + bin_digit: '[01_]' + oct_digit: '[0-7_]' + dec_digit: '[0-9_]' + hex_digit: '[\h_]' + dec_integer: (?:0|[1-9]{{dec_digit}}*) + dec_exponent: '[Ee](?:[-+]|(?![-+])){{dec_digit}}*' + + # JavaScript identifier + ident_name: (?:{{ident_start}}{{ident_part}}*{{ident_break}}) + ident_break: (?!{{ident_part}}) + ident_escape: (?:\\u(?:\h{4}|\{\h+\})) + ident_start: (?:[_$\p{L}\p{Nl}]|{{ident_escape}}) + ident_part: (?:[_$\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]|{{ident_escape}}) contexts: - main: - - meta_prepend: true - - include: interpolation - - include: ng-control-flow - interpolation: - - match: '{{' - scope: meta.interpolation.ngx.html punctuation.section.interpolation.begin.ngx.html - embed: scope:source.js - embed_scope: meta.interpolation.ngx.html source.js.embedded.ngx.html - escape: '}}' - escape_captures: - 0: meta.interpolation.ngx.html punctuation.section.interpolation.end.ngx.html +###[ HTML CUSTOMIZATIONS ]##################################################### + + tag: + - meta_prepend: true + - include: ng-declarations + - include: ng-statements + - include: ng-interpolations tag-attributes: - meta_prepend: true @@ -34,8 +43,25 @@ contexts: - include: tag-ng-on-attribute - include: tag-ng-bindon-attribute +###[ ANGULAR EXPRESSIONS ]##################################################### + + ng-expressions: + # https://angular.dev/guide/templates/expression-syntax + - include: ng-arrays + - include: ng-groups + - include: ng-objects + - include: ng-function-calls + - include: ng-filters + - include: ng-operators + - include: ng-constants + - include: ng-numbers + - include: ng-strings + - include: ng-variables + +###[ ANGULAR TAG ATTRIBUTES ]################################################## + tag-ng-template-attribute: - - match: '(\*)([a-zA-Z]\w*)' + - match: (\*)([a-zA-Z]\w*) scope: meta.attribute-with-value.template.html captures: 1: punctuation.definition.template.html @@ -45,7 +71,7 @@ contexts: - tag-event-attribute-assignment tag-ng-reference-attribute: - - match: '(\#)([a-zA-Z]\w*)' + - match: (\#)([a-zA-Z]\w*) scope: meta.attribute.reference.html captures: 1: punctuation.definition.reference.html @@ -55,7 +81,7 @@ contexts: - tag-event-attribute-assignment tag-ng-bind-attribute: - - match: '(\[)([a-zA-Z@][!\w.-]*)(\])' + - match: (\[)([a-zA-Z@][!\w.-]*)(\]) scope: meta.attribute-with-value.bind.html captures: 1: punctuation.section.bind.begin.html @@ -66,7 +92,7 @@ contexts: - tag-event-attribute-assignment tag-ng-on-attribute: - - match: '(\()([a-zA-Z@][\w:.]*)(\))' + - match: (\()([a-zA-Z@][\w:.]*)(\)) scope: meta.attribute-with-value.on.html captures: 1: punctuation.section.on.begin.html @@ -77,7 +103,7 @@ contexts: - tag-event-attribute-assignment tag-ng-bindon-attribute: - - match: '(\[\()([a-zA-Z][\w.]*)(\)\])\s*' + - match: (\[\()([a-zA-Z][\w.]*)(\)\]) scope: meta.attribute-with-value.bindon.html captures: 1: punctuation.section.bindon.begin.html @@ -87,40 +113,471 @@ contexts: - tag-event-attribute-meta - tag-event-attribute-assignment +###[ ANGULAR DECLARATIONS ]#################################################### + + ng-declarations: + - match: (@)let{{ident_break}} + scope: meta.let.ngx keyword.declaration.variable.ngx + captures: + 1: punctuation.definition.keyword.ngx + push: + - ng-let-assignment + - ng-let-variable + + ng-let-variable: + - match: '{{ident_name}}' + scope: variable.other.readwrite.ngx + pop: 1 + - include: else-pop - ng-control-flow: - - match: '(@){{angular_blocks}}' - scope: keyword.control.control-flow.html + ng-let-assignment: + - meta_content_scope: meta.let.identifier.ngx + - match: = + scope: meta.let.ngx keyword.operator.assignment.ngx + set: ng-let-value + - include: else-pop + + ng-let-value: + - meta_include_prototype: false + - meta_content_scope: meta.let.value.ngx + - match: ';' + scope: punctuation.terminator.expression.ngx + pop: 1 + - include: ng-expressions + +###[ ANGULAR STATEMENTS ]###################################################### + + ng-statements: + # conditionals + # https://angular.dev/guide/templates/control-flow#conditionally-display-content-with-if-else-if-and-else + - match: (@)if{{ident_break}} + scope: keyword.control.conditional.if.ngx + captures: + 1: punctuation.definition.keyword.ngx + push: + - ng-block + - ng-conditional-group + - match: (@)else\s+if{{ident_break}} + scope: keyword.control.conditional.elseif.ngx + captures: + 1: punctuation.definition.keyword.ngx + push: + - ng-block + - ng-conditional-group + - match: (@)else{{ident_break}} + scope: keyword.control.conditional.else.ngx + captures: + 1: punctuation.definition.keyword.ngx + push: ng-block + # https://angular.dev/guide/templates/control-flow#conditionally-display-content-with-the-switch-block + - match: (@)case{{ident_break}} + scope: keyword.control.conditional.case.ngx + captures: + 1: punctuation.definition.keyword.ngx + push: + - ng-block + - ng-conditional-group + - match: (@)default{{ident_break}} + scope: keyword.control.conditional.case.ngx + captures: + 1: punctuation.definition.keyword.ngx + push: ng-block + - match: (@)switch{{ident_break}} + scope: keyword.control.conditional.switch.ngx + captures: + 1: punctuation.definition.keyword.ngx + push: + - ng-block + - ng-conditional-group + # https://angular.dev/guide/templates/control-flow#providing-a-fallback-for-for-blocks-with-the-empty-block + - match: (@)for{{ident_break}} + scope: keyword.control.loop.for.ngx + captures: + 1: punctuation.definition.keyword.ngx + push: + - ng-block + - ng-for-group + - match: (@)empty{{ident_break}} + scope: keyword.control.loop.else.ngx captures: - 1: punctuation.definition.keyword.html + 1: punctuation.definition.keyword.ngx + push: ng-block + # https://angular.dev/guide/templates/defer + - match: (@)(?:defer|error|loading|placeholder){{ident_break}} + scope: keyword.control.flow.ngx + captures: + 1: punctuation.definition.keyword.ngx push: - - match: '=' - scope: punctuation.section.let.begin.ngx.html - push: - - meta_scope: meta.let.ngx.html - - match: ';' - scope: punctuation.section.let.end.ngx.html - pop: 2 - - match: \{ - scope: punctuation.section.block.begin.html - push: - - meta_scope: meta.block.html - - include: main - - match: \} - scope: punctuation.section.block.end.html - pop: 2 - - match: \( - scope: punctuation.section.group.begin.html - push: - - meta_scope: meta.group.html - - match: \b(async)\b - scope: keyword.control.html - - match: \b(track|of|prefetch on|on|prefetch when|when|as)\b - scope: keyword.operator.word.html - - match: \b(minimum|after)\b(\?) - captures: - 1: keyword.operator.word.html - 2: keyword.control.question.html - - match: \) - scope: punctuation.section.group.end.html - pop: 1 + - ng-block + - ng-defer-group + + ng-block: + - meta_include_prototype: false + - match: \{ + scope: punctuation.section.block.begin.ngx + set: ng-block-body + - include: else-pop + + ng-block-body: + - meta_scope: meta.block.ngx + - match: \} + scope: punctuation.section.block.end.ngx + pop: 1 + - include: main + + ng-conditional-group: + - meta_include_prototype: false + - match: \( + scope: punctuation.section.group.begin.ngx + set: ng-conditional-group-body + - include: else-pop + + ng-conditional-group-body: + - meta_scope: meta.group.ngx + - match: \) + scope: punctuation.section.group.end.ngx + pop: 1 + - match: as{{ident_break}} + scope: keyword.operator.assignment.as.ngx + - include: ng-expressions + + ng-defer-group: + - meta_include_prototype: false + - match: \( + scope: punctuation.section.group.begin.ngx + set: ng-defer-group-body + - include: else-pop + + ng-defer-group-body: + - meta_scope: meta.group.ngx + - match: \) + scope: punctuation.section.group.end.ngx + pop: 1 + - match: (?:idle|viewport|interaction|hover|immediatetimer){{ident_break}} + scope: constant.language.event.ngx + - match: (?:prefetch on|on|prefetch when|when){{ident_break}} + scope: keyword.control.flow.ngx + - match: (minimum|after)(\?) + captures: + 1: keyword.operator.word.ngx + 2: keyword.control.question.ngx + - include: ng-expressions + + ng-for-group: + - meta_include_prototype: false + - match: \( + scope: punctuation.section.group.begin.ngx + set: ng-for-group-body + - include: else-pop + + ng-for-group-body: + - meta_scope: meta.group.ngx + - match: \) + scope: punctuation.section.group.end.ngx + pop: 1 + - match: track{{ident_break}} + scope: keyword.control.flow.ngx + - match: let{{ident_break}} + scope: keyword.declation.variable.ngx + - match: of{{ident_break}} + scope: keyword.operator.iteration.ngx + - include: ng-expressions + +###[ ANGULAR INTERPOLATIONS ]################################################## + + ng-interpolations: + - match: '{{' + scope: meta.embedded.ngx.html punctuation.section.embedded.begin.ngx.html + push: ng-interpolation-body + + ng-interpolation-body: + - meta_include_prototype: false + - meta_content_scope: meta.embedded.ngx.html source.ngx.embedded.html + - match: '}}' + scope: meta.embedded.ngx.html punctuation.section.embedded.end.ngx.html + pop: 1 + - include: ng-expressions + +###[ ANGULAR ARRAYS ]########################################################### + + ng-arrays: + - match: \[ + scope: punctuation.section.sequence.begin.ngx + push: ng-array-body + + ng-array-body: + - meta_scope: meta.sequence.array.ngx + - match: \] + scope: punctuation.section.sequence.end.ngx + pop: 1 + - include: ng-expressions + +###[ ANGULAR GROUPS ]########################################################## + + ng-groups: + - match: \( + scope: punctuation.section.group.begin.ngx + push: ng-group-body + + ng-group-body: + - meta_scope: meta.group.ngx + - match: \) + scope: punctuation.section.group.end.ngx + pop: 1 + - include: ng-expressions + +###[ ANGULAR OBJECTS ]######################################################### + + ng-objects: + - match: \{ + scope: meta.mapping.ngx punctuation.section.mapping.begin.ngx + push: ng-object-body + + ng-object-body: + - meta_content_scope: meta.mapping.ngx + - match: \} + scope: meta.mapping.ngx punctuation.section.mapping.end.ngx + pop: 1 + - match: ',' + scope: punctuation.separator.mapping.pair.ngx + - match: ':' + scope: meta.mapping.ngx punctuation.separator.mapping.key-value.ngx + set: ng-object-value + - match: (?=\S) + set: ng-object-key + + ng-object-key: + - meta_include_prototype: false + - match: '{{ident_name}}(?=\s*:)' + scope: meta.mapping.key.ngx meta.string.ngx string.unquoted.ngx + set: ng-object-body + - match: '' + set: ng-object-key-expression + + ng-object-key-expression: + - meta_content_scope: meta.mapping.key.ngx + - match: (?=[,:}]) + set: ng-object-body + - include: ng-block-pop + - include: ng-expressions + + ng-object-value: + - meta_content_scope: meta.mapping.value.ngx + - match: (?=[,:}]) + set: ng-object-body + - include: ng-block-pop + - include: ng-expressions + +###[ ANGULAR FILTERS ]######################################################### + + ng-filters: + - match: \|(?!\|) + scope: keyword.operator.assignment.pipe.ngx + push: + - ng-filter-meta + - ng-filter-function + + ng-filter-function: + - match: (({{ident_name}})\s*)(\() + captures: + 1: meta.function-call.identifier.ngx + 2: variable.function.filter.ngx + 3: meta.function-call.arguments.ngx punctuation.section.arguments.begin.ngx + set: ng-function-call-arguments + - match: '{{ident_name}}' + scope: meta.function-call.identifier.ngx variable.function.filter.ngx + pop: 1 + - include: else-pop + + ng-filter-meta: + - meta_include_prototype: false + - meta_scope: meta.filter.ngx + - include: immediately-pop + +###[ ANGULAR FUNCTION CALLS ]################################################## + + ng-function-calls: + - match: (({{ident_name}})\s*)(\() + captures: + 1: meta.function-call.identifier.ngx + 2: variable.function.ngx + 3: meta.function-call.arguments.ngx punctuation.section.arguments.begin.ngx + push: ng-function-call-arguments + + ng-function-call-arguments: + - meta_content_scope: meta.function-call.arguments.ngx + - match: \) + scope: meta.function-call.arguments.ngx punctuation.section.arguments.end.ngx + pop: 1 + - match: ',' + scope: punctuation.separator.arguments.ngx + - include: ng-expressions + +###[ ANGULAR OPERATORS ]####################################################### + + ng-operators: + # https://angular.dev/guide/templates/expression-syntax#what-operators-are-supported + - match: ',' + scope: punctuation.separator.sequence.ngx + - match: ';' + scope: punctuation.terminator.expression.ngx + - match: '[!=]==?|[<>]=?' + scope: keyword.operator.comparison.ngx + - match: '[-+*/%]' + scope: keyword.operator.arithmetic.ngx + - match: '&&|\|\||!' + scope: keyword.operator.logical.ngx + - match: '=' + scope: keyword.operator.assignment.ngx + - match: \?\? + scope: keyword.operator.null-coalescing.ngx + - match: '\?' + scope: keyword.operator.ternary.ngx + push: ng-ternary-expression + + ng-ternary-expression: + - match: ':' + scope: keyword.operator.ternary.ngx + pop: 1 + - include: ng-block-pop + - include: ng-expressions + +###[ ANGULAR LITERALS ]######################################################## + + ng-constants: + - match: true{{ident_break}} + scope: constant.language.boolean.true.ngx + - match: false{{ident_break}} + scope: constant.language.boolean.false.ngx + - match: null{{ident_break}} + scope: constant.language.null.ngx + - match: undefined{{ident_break}} + scope: constant.language.undefined.ngx + + ng-numbers: + # floats + - match: |- + (?x: + # 1., 1.1, 1.1e1, 1.1e-1, 1.e1, 1.e-1 | 1e1, 1e-1 + {{dec_integer}} (?: (\.) {{dec_digit}}* (?:{{dec_exponent}})? | {{dec_exponent}} ) + # .1, .1e1, .1e-1 + | (\.) {{dec_digit}}+ (?:{{dec_exponent}})? + ){{ident_break}} + scope: meta.number.float.decimal.ngx constant.numeric.value.ngx + captures: + 1: punctuation.separator.decimal.ngx + 2: punctuation.separator.decimal.ngx + # integers + - match: (0)({{dec_digit}}+){{ident_break}} + scope: meta.number.integer.octal.ngx + captures: + 1: constant.numeric.base.ngx invalid.deprecated.numeric.octal.ngx + 2: constant.numeric.value.ngx invalid.deprecated.numeric.octal.ngx + - match: (0[Xx])({{hex_digit}}*)(n)?{{ident_break}} + scope: meta.number.integer.hexadecimal.ngx + captures: + 1: constant.numeric.base.ngx + 2: constant.numeric.value.ngx + 3: constant.numeric.suffix.ngx + - match: (0[Oo])({{oct_digit}}*)(n)?{{ident_break}} + scope: meta.number.integer.octal.ngx + captures: + 1: constant.numeric.base.ngx + 2: constant.numeric.value.ngx + 3: constant.numeric.suffix.ngx + - match: (0[Bb])({{bin_digit}}*)(n)?{{ident_break}} + scope: meta.number.integer.binary.ngx + captures: + 1: constant.numeric.base.ngx + 2: constant.numeric.value.ngx + 3: constant.numeric.suffix.ngx + - match: ({{dec_integer}})(n|(?!\.)){{ident_break}} + scope: meta.number.integer.decimal.ngx + captures: + 1: constant.numeric.value.ngx + 2: constant.numeric.suffix.ngx + + ng-strings: + - match: \" + scope: punctuation.definition.string.begin.ngx + push: ng-double-quoted-string-body + - match: \' + scope: punctuation.definition.string.begin.ngx + push: ng-single-quoted-string-body + + ng-double-quoted-string-body: + - meta_include_prototype: false + - meta_scope: meta.string.ngx string.quoted.double.ngx + - match: \" + scope: punctuation.definition.string.end.ngx + pop: 1 + - match: \n + scope: invalid.illegal.newline.ngx + pop: 1 + - include: ng-string-content + + ng-single-quoted-string-body: + - meta_include_prototype: false + - meta_scope: meta.string.ngx string.quoted.single.ngx + - match: \' + scope: punctuation.definition.string.end.ngx + pop: 1 + - match: \n + scope: invalid.illegal.newline.ngx + pop: 1 + - include: ng-string-content + + ng-string-content: + - match: \\x\h{2} + scope: constant.character.escape.hex.ngx + - match: \\u\h{4} + scope: constant.character.escape.unicode.16bit.ngx + - match: \\. + scope: constant.character.escape.ngx + +###[ ANGULAR VARIABLES ]####################################################### + + ng-variables: + - match: ({{ident_name}})\s*(\??\.) + captures: + 1: variable.other.object.ngx + 2: punctuation.accessor.ngx + push: ng-members + - match: '{{ident_name}}' + scope: variable.other.readwrite.ngx + push: ng-subscription + + ng-members: + - match: ({{ident_name}})\s*(\??\.) + captures: + 1: variable.other.object.ngx + 2: punctuation.accessor.ngx + - match: (({{ident_name}})\s*)(\() + captures: + 1: meta.function-call.identifier.ngx + 2: variable.function.ngx + 3: meta.function-call.arguments.ngx punctuation.section.arguments.begin.ngx + set: ng-function-call-arguments + - match: '{{ident_name}}' + scope: variable.other.member.ngx + set: ng-subscription + - include: else-pop + + ng-subscription: + - match: \[ + scope: punctuation.section.subscription.begin.ngx + push: ng-subscription-body + - include: else-pop + + ng-subscription-body: + - meta_scope: meta.subscription.ngx + - match: \] + scope: punctuation.section.subscription.end.ngx + pop: 1 + - include: ng-expressions + +###[ ANGULAR PROTOTYPES ]###################################################### + + ng-block-pop: + - match: (?=[)}]) + pop: 1 diff --git a/Symbol List - Variables.tmPreferences b/Symbol List - Variables.tmPreferences new file mode 100644 index 0000000..c028302 --- /dev/null +++ b/Symbol List - Variables.tmPreferences @@ -0,0 +1,14 @@ + + + + scope + meta.let.identifier.ngx variable + settings + + showInSymbolList + 1 + showInIndexedSymbolList + 1 + + + diff --git a/tests/syntax_test_control_flow.component.html b/tests/syntax_test_control_flow.component.html deleted file mode 100644 index 3b452bc..0000000 --- a/tests/syntax_test_control_flow.component.html +++ /dev/null @@ -1,139 +0,0 @@ - - -@if (loggedIn) { - - - - - The user is logged in -} @else { - - - - - - - - The user is not logged in -} - - -@if (a > b) { - {{ a }} is greater than {{ b }} - - - - - - - -} - -@if (a > b) { - {{ a }} is greater than {{ b }} -} @else if (b > a) { - {{ a }} is less than {{ b }} -} @else { - {{ a }} is equal to {{ b }} -} - -@switch (accessLevel) { - @case ('admin') { - - - - - - } - @case ('moderator') { - - } - @default { - - } -} - -@for (user of users; track user.id) { - {{ user.name }} -} @empty { - Empty list of users -} - -@defer { - -} - -@defer (on viewport) { - -} @placeholder { - - -} - -@defer (on viewport) { - -} @loading { - Loading… -} @error { - Loading failed :( -} @placeholder { - -} - -@defer (on viewport; prefetch on idle) { - -} - -@for (item of items; track item.id; let idx = $index, e = $even) { - Item #{{ idx }}: {{ item.name }} -} - -@for (item of items) { - {{ item.title }} -} - -@for (item of items) { - {{ item.title }} -} @empty { -

No Items

-} - -@for (item of items; track item.name) { -
  • {{ item.name }}
  • -} @empty { -
  • There are no items.
  • -} - -@if (users$ | async; as users) { - {{ users.length }} -} - -@for (item of items; track item.id) { - {{ item.name }} -} - -@switch (condition) { - @case (caseA) { - Case A. - } - @case (caseB) { - Case B. - } - @default { - Default case. - } -} - -@defer (on ; when ; prefetch on ; prefetch when ) { - - -} @placeholder (minimum? ) { - -

    Placeholder

    -} @loading (minimum? ; after? ) { - - loading image -} @error { - -

    An loading error occured

    -} diff --git a/tests/syntax_test_scopes.component.html b/tests/syntax_test_scopes.component.html new file mode 100644 index 0000000..d10da1b --- /dev/null +++ b/tests/syntax_test_scopes.component.html @@ -0,0 +1,598 @@ + + + + +@let name = user.name; + + + + + + + + + +@let greeting = 'Hello, ' + name; + + + + + + + + + + + +@let data = data$ | async; + + + + + + + + + + + + + +@if (a > b) { + + + + + + + + + + + {{ a }} is greater than {{ b }} + + + + + + + + + + + +} @else if (b > a()) { + + + + + + + + + + + + {{ a() }} is less than {{ b.c() }} + + + + + + + + + + + + + + + + +} @else { + + + + + {{ a }} is equal to {{ b }} + + + + + + + + + + +} + + +@if (user.profile.settings.startDate; as startDate) { + + + + + + + + + + + + + + + {{ startDate }} + + + + + +} + + + + + +@switch (accessLevel) { + + + + + + + @case ('admin') { + + + + + + + + + + + + + + + + } + + + + @case { + + + + + + + + + + + + } + + + @default { + + + + + + + } + + +} + + + + + +@for (user of users; track user.id) { + + + + + + + + + + + + + {{ user.name }} +} @empty { + + + + + Empty list of users +} + + +@for (item of items; track item.id; let idx = $index, e = $even) { + + + + + + + + + + + + + + + + + + + + + + Item #{{ idx }}: {{ item.name }} + + + + + + + + + + + + + +} + + +@if (users$ | async; as users) { + + + + + + + + + + + +

    {{ users.length }}

    + + + + + + + + + + + + + + + + +} + + + + + + @defer { } + + + + + + + + + + + @defer (on viewport) { + + + + + + + + + + } @loading { + + + + + Loading… + } @error { + + + + + Loading failed :( + } @placeholder { + + + + + + } + + + +@defer (on viewport; when $var prefetch on idle; prefetch when true) { + + + + + + + + + + + + + + + +} + + + + + + + {{ 'Hello', "World" }} + + + + + + + + + {{ "\xAF \u2AFF \n \"" }} + + + + + + + + + + + + + {{ true, false, null, undefined }} + + + + + + + + + + + {{ Number, Boolean, NaN, Infinity, parseInt }} + + + + {{ ['Onion', 'Cheese', 'Garlic'] }} + + + + + + + + + + + + + + + + + {{ { name: 'Alice', 'object': { array + "name": [0, 2, 3] } } }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ - + * / % === !== == != = < <= >= > && || ! ?? }} + + + + + + + + + + + + + + + + + + + + + {{ foo ? bar : baz }} + + + + + + + + + + + {{ foo = null ?? 'default' }} + + + + + + + + + + + + + {{ person['name'][0] = "Mirabel" }} + + + + + + + + + + + + + + + + + + + {{ obj?.member }} + + + + + + + + {{ obj.method() }} + + + + + + + + + + + {{ func(arg, "value") }} + + + + + + + + + + + + + + {{ var | filter | annimation ("forward") }} + + + + + + + + + + + + + + + + +