-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathAlan IF.sublime-syntax
700 lines (638 loc) · 26 KB
/
Alan IF.sublime-syntax
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
%YAML 1.2
---
# "Alan IF.sublime-syntax" v0.0.31 (2021-06-19) | Alan v3 beta8
# ==============================================================================
# Alan Syntax
# ==============================================================================
# Syntax definition for Alan IF Adventure Language:
# -- https://www.alanif.se
# ------------------------------------------------------------------------------
# Copyright (c) Tristano Ajmone, 2018. MIT License.
# -- https://github.com/tajmone/sublime-alan-if
# ------------------------------------------------------------------------------
name: Alan IF
comment: Alan IF Adventure Language
file_extensions:
- alan
- i
scope: source.alan
variables:
# Valid identifier letters are: 'a-z', 'A-Z' and 'U+00E0-U+00FE' ~'U+00F7' (÷)
LETTER: 'a-zA-Zà-þ&&[^÷]' # <= [a-zA-Z\x{00E0}-\x{00FE}&&[^\x{00F7}]]
ID: '(\b[{{LETTER}}][0-9_{{LETTER}}]*\b)'
contexts:
prototype:
- include: comments
- include: integers
- include: operators
- include: strings
# - include: keywords
main:
#=== TOP-LEVEL SYNTAX CONSTRUCTS ===========================================
# options
#---------------------------------------------------------------------------
- include: import # IMPORT '<filename>'.
- include: addition_declaration # ADD TO EVERY <id> ...(must precede class!)
- include: class_declaration # EVERY <id> ... END EVERY.
- include: instance_declaration # THE <id> ... END THE.
# syntax
# verb
# rule
# synonyms
# event
# messages
# prompt
#---------------------------------------------------------------------------
# start_section
#=== MISC GLOBALLY INCLUDED CONTEXTS =======================================
# - include: strings
- include: keywords
- include: quoted_identifiers
##############################################################################
# #
# ALAN SYNTAX-SPECIFIC CONTEXTS #
# #
##############################################################################
# ============================================================================
# TOP-LEVEL DEFINITIONS
# ============================================================================
######################
# OPTIONS
######################
######################
# IMPORT '<filename>'.
######################--------------------+
# import = 'import' quoted_identifier '.' |
#-----------------------------------------+
import:
- match: (?i)\bIMPORT\b
scope: keyword.control.import.include.alan
push:
- meta_scope: meta.preprocessor.include.alan
# Quoted ID (scoped as string here)
- match: "(')((?:''|[^'])*)(')"
captures:
1: punctuation.definition.string.begin.alan
string.quoted.single.alan
2: string.quoted.single.alan
3: punctuation.definition.string.end.alan
string.quoted.single.alan
- match: '\.'
scope: punctuation.terminator.alan
pop: true
- include: eol_POP # <= If EOL is reached, bail-out!
###########
# ADDITIONS
###########------------------------------+
# addition = 'ADD' 'TO' 'EVERY' id |
# [inheritance] | <= ??? 341 E : Cannot Add heritage to classes
# {property} |
# 'END' 'ADD' ['TO'] [id] '.' |
#----------------------------------------+
addition_declaration:
- match: (?i)(?=\bADD\b)
push: addition_decl_context
addition_decl_context:
- meta_scope: meta.extension.alan
meta.class.alan
- include: addition_decl_head
- include: addition_decl_tail
# TODO: Include addition Body here...
- include: property_context
- include: else_POP # <= Bail out
addition_decl_head: # `ADD TO EVERY class_Id .`
- match: (?i)(\bADD\s*TO\s*EVERY\b)(\s*) # <= consume extra WS!
captures:
1: storage.modifier.extends.alan
push:
- meta_include_prototype: false
- meta_content_scope: entity.name.class.alan # <= maybe: name.class.externd?
- include: generic_identifier
- include: force_POP
addition_decl_tail: # `END ADD TO class_Id .`
# ==============================
# END ADD [TO] => no ID & no `.`
# ==============================
# When END ADD TO is not followed by neither ID or dot, we must capture it
# separately to avoid stray scopes after it...
# Check if END ADD is followed by WS* and either EOL or a comment:
- match: (?i)\b(END\s*ADD(\s*TO)?)\b(?=\s*(--|$))
captures:
1: keyword.control.alan
pop: true
# =============================
# END ADD [TO] => ID and/or `.`
# =============================
- match: (?i)\b(END\s*ADD(\s*TO)?)\b\s* # <= must consume all whitespace!
captures:
1: keyword.control.alan
set: tail_class_&_terminator
####################
# CLASS DECLARATIONS
####################----------------+
# class = 'EVERY' id | <= class_decl_head
# [inheritance] | <= '' ''
# {property} |
# 'END' 'EVERY' [id] ['.'] | <= class_decl_tail
#-----------------------------------+
class_declaration:
- match: (?i)(?=\bEVERY\b)
# scope: storage.type.class.alan
push: class_decl_context
class_decl_context:
- meta_scope: meta.class.alan
- include: class_decl_head
- include: class_decl_tail
# TODO: Include Class Body here...
- include: property_context
- include: else_POP # <= Bail out
class_decl_head: # `EVERY class_Id IsA class_inheritance_Id .`
- match: (?i)(\bEVERY\b)(\s*) # <= consume extra WS!
captures:
1: storage.type.class.alan
push:
- meta_include_prototype: false
- meta_content_scope: entity.name.class.alan
- match: (?i)(?=\bIsA\b) # <= Failsafe to handle mmissing class ID
pop: true
- include: generic_identifier
- include: force_POP
- include: class_inheritance
class_decl_tail: # `END EVERY class_Id .`
# ===========================
# END EVERY => no ID & no `.`
# ===========================
# When END EVERY is not followed by neither ID or dot, we must capture it
# separately to avoid stray scopes after it...
# Check if END EVERY is followed by WS* and either EOL or a comment:
- match: (?i)\b(END\s*EVERY)\b(?=\s*(--|$))
captures:
1: keyword.control.alan
pop: true
# ==========================
# END EVERY => ID and/or `.`
# ==========================
- match: (?i)\b(END\s*EVERY)\b\s* # <= must consume all whitespace!
captures:
1: keyword.control.alan
set: tail_class_&_terminator
# ---> ReUsable Class SubContexts <-------------------------------------------
class_inheritance: # `IsA class_inheritance_Id [.]`
- match: (?i)(\bIsA\b)(\s*)
captures:
1: storage.modifier.extends
push:
- match: '(?=[^\s\.])'
push:
- meta_content_scope: entity.other.inherited-class.alan
- include: generic_identifier
- include: force_POP
- include: terminator
# CHANGED: force POP to preserve any AT/IN properties on same line!
- include: force_POP
# - include: eol_POP # <= If EOL is reached, bail-out!
tail_class_&_terminator:
- meta_include_prototype: false
- meta_content_scope: meta.extension.alan
meta.class.alan
- match: '(?=[^\s\.])'
push:
- meta_content_scope: entity.name.class.tail.alan
- include: generic_identifier
- include: force_POP
- include: terminator
- include: force_POP
###########
# INSTANCES
###########--------------------------+
# instance = 'THE' id |
# [inheritance] | <= (sic) How can instances inherit?
# {property} |
# 'END' 'THE' [id] ['.'] |
#------------------------------------+
instance_declaration:
- match: (?i)(?=\bTHE\b)
push: instance_decl_context
instance_decl_context:
- meta_scope: meta.instance.alan
- include: instance_decl_head
- include: instance_decl_tail
# TODO: Include instance Body here...
- include: property_context
- include: else_POP # <= Bail out
instance_decl_head: # `THE class_Id IsA class_inheritance_Id .`
- match: (?i)(\bTHE\b)(\s*) # <= consume extra WS!
captures:
1: storage.type.instance.alan
push:
- meta_include_prototype: false
- meta_content_scope: entity.name.instance.alan # <= arbitrary scope name!!! (check)
- match: (?i)(?=\bIsA\b) # <= Failsafe to handle mmissing class ID
pop: true
- include: generic_identifier
- include: force_POP
- include: class_inheritance
instance_decl_tail: # `END THE class_Id .`
# ===========================
# END THE => no ID & no `.`
# ===========================
# When END THE is not followed by neither ID or dot, we must capture it
# separately to avoid stray scopes after it...
# Check if END THE is followed by WS* and either EOL or a comment:
- match: (?i)\b(END\s*THE)\b(?=\s*(--|$))
captures:
1: keyword.control.alan
pop: true
# ==========================
# END THE => ID and/or `.`
# ==========================
- match: (?i)\b(END\s*THE)\b\s* # <= must consume all whitespace!
captures:
1: keyword.control.alan
set:
- meta_include_prototype: false
- meta_content_scope: meta.instance.alan # <= arbitrary scope name!!! (check)
- match: '(?=[^\s\.])'
push:
- meta_content_scope: entity.name.instance.tail.alan # <= arbitrary scope name!!! (check)
- include: generic_identifier
- include: force_POP
- include: terminator
- include: force_POP
##########
# SYNONYMS
##########-------------------------------------------+
# synonyms = 'SYNONYMS' {synonym_declaration} |
# |
# synonym_declaration = word {',' word} '=' word '.' |
#----------------------------------------------------+
# ============================================================================
# NESTED DEFINITIONS
# ============================================================================
# Ie, definition which can occur only/also inside a top level definition.
##########
# PROPERTY
##########------------------------+
# property = initial_location | *
# | name |
# | pronouns |
# | attributes | + (missing: sets)
# | initialization |
# | description |
# | articles |
# | mentioned |
# | container_properties |
# | verb | (also top-level)
# | script |
# | entered |
# | exit |
#---------------------------------+
# Stack 1 [class_decl_context]
property_context:
- match: ''
push:
- include: initial_location
- include: attribute_declaration
- include: force_POP
##################
# INITIAL LOCATION
##################------------------------------------------+
# initial_location = ( AT locationID | IN containerID ) [.] |
#-----------------------------------------------------------+-----------------
# TODO: Could be improved to prevent small spills. The sequence is fixed:
# [AT|IN] ID [.], so lookaheads could be used to bail out if not `.` is
# ahead, and prevent WS being scoped (currently caused by consuming it)
#-----------------------------------------------------------+-----------------
initial_location:
- match: (?i)\b(AT|IN)\b(\s*) # <- eat trailing WS
captures:
1: storage.type.attribute.alan # <~ Scope name needs checking!!!
push:
- meta_scope: meta.attributes.alan
- meta_include_prototype: false
# The following 2 failsafes handle code fragments:
- include: keyword_POP # <= Failsafe, bail-out on KWD
- include: terminator_POP # <= Failsafe, bail-out on `.` terminator
- match: ''
set:
- meta_include_prototype: false
- meta_content_scope: meta.attributes.alan
variable.other.member.alan
- include: generic_identifier
# Wether an ID was found or not, we'll terminate this context with
# the optional `.` terminator...
- match: ''
set:
- meta_scope: meta.attributes.alan
- match: \s+ # <= must consume whitespace!
- include: terminator_POP
- include: force_POP
############
# ATTRIBUTES
############
# Stack 2 [class_decl_context > property_context]
attribute_declaration:
- match: (?i)\b((?:IS|ARE|HAS|CAN)(?:\s+NOT)?)\b(\s*)
captures:
1: storage.type.attribute.alan # <~ Scope name needs checking!!!
push: attribute_decl_context
- include: keyword_POP
# Stack 3 [class_decl_context > property_context > attribute_declaration]
attribute_decl_context:
- meta_scope: meta.attributes.alan
- meta_include_prototype: false
- include: keyword_POP # <= Failsafe, bail-out on KWD
- include: terminator_POP
- match: ''
push: attributes
- include: else_POP
# Stack 4 [class_decl_context > property_context > attribute_declaration
# > attribute_decl_context]
attributes: # `IS [NOT] ID.`
- meta_include_prototype: false
- include: keyword_POP # <= Failsafe, bail-out on KWD
- match: (?i)\b(NOT)\b(\s*) # <= capture isolate NOT in attributes blocks.
captures:
1: storage.type.attribute.alan # <~ Scope name needs checking!!!
- include: terminator_POP
- include: comments
- include: integers
- include: strings
- match: (?=\S)
push:
- meta_include_prototype: false
- meta_content_scope: variable.other.member.alan
- include: generic_identifier
# -------------------------------
# Check if followed by another ID
# -------------------------------
- match: ''
set:
- match: \s+
- match: (?=\S)
set:
- meta_content_scope: variable.other.identifier.alan
- meta_include_prototype: false
- include: keyword_POP # <= Failsafe, bail-out on KWD
- include: generic_identifier
- include: force_POP
- include: else_POP
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# TODO: These KWDs are just the most obvious ones in the context, but the list
# is not complete...
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
keyword_POP: # Bail out if a kwyword is encounterd
- match: (?i)(?=\s*\b(IS|ARE|HAS|CAN|END|NAME)\b) # <= Must eat leading WS!
pop: true
##############################################################################
# #
# REUSABLE ALAN SYNTAX CONTEXTS #
# #
##############################################################################
#=============================================================================
# IDENTIFIERS
#=============================================================================
# Identifiers can always be either unquoted or quoted.
# Sometimes we need to create identifiers context variants in order to scope
# them semantically (ie: class, inherited class, verb, etc.). Other times we
# can use the `generic_identifier` and let the importing context set it scope.
#-----------------------------------------------------------------------------
####################
# Generic identifier
####################
# This is a scopeless identifier, reusable in context that can set its scope
# via inclusion. Ideally, the goal is to end up using only this one.
generic_identifier:
# Unquoted ID
- match: '{{ID}}'
# Quoted ID
- match: "(')((?:''|[^'])*)(')"
captures:
1: punctuation.definition.identifier.alan
3: punctuation.definition.identifier.alan
#-----------------------------------------------------------------------------
# CLASS IDENTIFIERS
#-----------------------------------------------------------------------------
##################
# class identifier # <= Currently NOT USED (hopefully can do without it)
##################
# Captures a class identifier, either quoted or unquoted.
class_identifier:
# Unquoted ID
- match: '{{ID}}'
scope: entity.name.class.alan
pop: true
# Quoted ID
- match: "(')((?:''|[^'])*)(')"
captures:
1: punctuation.definition.identifier.alan
entity.name.class.alan
2: entity.name.class.alan
3: punctuation.definition.identifier.alan
entity.name.class.alan
pop: true
#-----------------------------------------------------------------------------
############################
# identifier without context (CURRENLTY UNUSED)
############################
# This is intended for generic capturing of identifiers, outside syntax specific
# contexts, so it can't be determined if the entity is a class, inherited class,
# or whatever else ...
identifier:
# Unquoted ID
- match: '{{ID}}'
scope: entity.name.other
pop: true
# Quoted ID
- match: "(')((?:''|[^'])*)(')"
captures:
1: punctuation.definition.identifier.alan
entity.name.other.alan
2: entity.name.other.alan # <= temporary solution
3: punctuation.definition.identifier.alan
entity.name.other.alan
pop: true
#########################
# Loose quoted identifier
#########################
# Until the syntax covers all constructs, this will capture quoted identifiers
# out of syntax defined contexts...
quoted_identifiers:
- match: "(')((?:''|[^'])*)(')"
captures:
1: punctuation.definition.identifier.alan
entity.name.other.alan
2: entity.name.other.alan # <= temporary solution
3: punctuation.definition.identifier.alan
entity.name.other.alan
##############################################################################
# #
# PROTOTYPE CONTEXTS #
# #
##############################################################################
##########
# COMMENTS
##########
comments:
- include: line-comment
- include: block-comment
line-comment:
- meta_include_prototype: false
- match: '(--)(.*)$\n?'
scope: comment.line.double-dash.alan
captures:
1: punctuation.definition.comment.line.double-dash.alan
2: meta.line.comment.content.alan
block-comment:
- meta_include_prototype: false
- match: '^(/{4})(.*)$\n?'
scope: meta.block.comment.alan
captures:
1: punctuation.definition.comment.block.begin.alan
2: comment.block.content.alan
set:
- meta_content_scope: meta.block.comment.alan
comment.block.content.alan
- match: '^(/{4,})$\n?'
captures:
1: meta.block.comment.alan
punctuation.definition.comment.block.end.alan
pop: true
######################
# SPECIAL STRING CHARS
######################
# Q: Are they case sensitive???
# Q: Do they occure only inside strings?
# Q: Do they occur in single-quoted strings?
# -------------------------------------------------------------
# Character combinations with special meaning for the printout:
# $p => New paragraph (usually one empty line)
# $n => New line
# $i => Indent on a new line
# $t => Insert a tabulation
# $$ => Do not insert a space
# $a => The name of the actor that is executing
# $l => The name of the current location
# $v => The verb the player used (the first word)
# $ => Print a dollar sign
#
# These refer to parameters while executing a verb:
# $<n> => The parameter <n> (<n> is a digit > 0, e.g. “$1”)
# $+<n> => Definite form of parameter <n>
# $0<n> => Indefinite form of parameter <n>
# $-<n> => Negative form of parameter <n>
# $!<n> => Pronoun for the parameter <n>
# $o => The current object (first parameter) *DEPRECATED*
# -------------------------------------------------------------
special_chars:
- match: '\$([pnit$alv]|[+\-!0]?[1-9])'
scope: constant.character.escape.alan # What else?
- match: '\$o'
scope: constant.character.escape.alan
invalid.deprecated
######################
# KEWYWORDS
######################
# Currently all keywords are stored here, waiting to be moved in better scope...
# REMOVED: import|every|isa
keywords:
- include: kwd_conditional
# This captures all keywords that are not (yet) assigned to specific scopes.
# Keywords list fixed according to Issue #15 of alan-if/alan-docs:
# https://github.com/alan-if/alan-docs/issues/15#issuecomment-420010103
- match: (?i)\b(add|after|an|and|are|article|at|attributes|before|between|by|can|cancel|character|characters|check|container|contains|count|current|decrease|definite|depend|depending|describe|description|directly|do|does|each|empty|end|entered|event|every|exclude|exit|extract|first|for|form|from|has|header|here|import|in|include|increase|indefinite|indirectly|initialize|into|is|isa|it|last|limits|list|locate|look|make|max|mentioned|message|meta|min|name|near|nearby|negative|no|not|of|off|on|only|opaque|option|options|or|play|prompt|pronoun|quit|random|restart|restore|save|say|schedule|score|script|set|show|start|step|stop|strip|style|sum|synonyms|syntax|system|taking|the|this|to|transcript|transitively|until|use|verb|visits|wait|when|where|with|word|words)\b
scope: keyword.other.alan # Temporary
kwd_conditional:
- match: (?i)\b(end +if|else|elsif|then|if)\b|=> # '=>' stands for THEN
scope: keyword.control.conditional.alan
kwd_inheritable_classes: # <= NOT USED YET!
- match: (?i)\b(entity|thing|object|actor|location)\b
scope: keyword.other.alan
kwd_predefined_classes: # <= NOT USED YET!
- match: (?i)\b(actor|entity|integer|literal|location|object|string|thing)\b
scope: keyword.other.alan
#########
# NUMBERS
#########
integers:
- match: (-)?\b(\d+)\b
captures:
1: keyword.operator.arithmetic
2: constant.numeric.alan
###########
# OPERATORS
###########
operators:
# =================================
# Relational and Equality Operators
# =================================
# Must not capture '=>' (THEN symbol) as if it was "equal or greater than",
# hence the lookarounds in the following RegEx...
- match: (<=|>=|(?<!=)>|<|==?(?!>))
scope: keyword.operator.comparison.alan
# ================
# Binary Operators
# ================
- match: \+|\-|\*|/
scope: keyword.operator.arithmetic.alan
#########
# STRINGS
#########
# Strings in Alan are only double quoted (single quoted strings are considered
# quoted identifiers).
strings:
# =========================
# String Beginning with """ (Delimter + Escpaed ")
# =========================
- match: '"(?="")'
scope: punctuation.definition.string.begin.alan
push: inside_DQString
# =============
# Empty Strings
# =============
- match: '(")(")'
scope: string.quoted.double.empty.alan
captures:
1: punctuation.definition.string.begin.alan
2: punctuation.definition.string.end.alan
# ====================
# Double Quoted String
# ====================
- match: '"'
scope: punctuation.definition.string.begin.alan
push: inside_DQString
# -------------------------------------------------
# Common context once inside a double quoted string
# -------------------------------------------------
inside_DQString:
- meta_scope: string.quoted.double.alan
- meta_include_prototype: false
- include: special_chars
- match: '("")+' # <= Escaped "
scope: string.quoted.double.alan
- match: '"'
scope: punctuation.definition.string.end.alan
pop: true
##############################################################################
# #
# GENERIC REUSABLE CONTEXTS #
# #
##############################################################################
terminator:
- match: '(\s*)(\.)' # <= consume all leading whitespace
captures:
2: punctuation.terminator.alan
terminator_POP:
- match: '(\s*)(\.)' # <= consume all leading whitespace
captures:
2: punctuation.terminator.alan
pop: true
else_POP:
- match: (?=\S)
pop: true
force_POP:
- match: ''
pop: true
eol_POP: # <= CURRENTLY UNUSED!! But already usable to clean-up code
- match: '$'
pop: true