viv-compiler 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- viv_compiler/__init__.py +14 -0
- viv_compiler/__main__.py +3 -0
- viv_compiler/_samples/__init__.py +0 -0
- viv_compiler/_samples/smoke-test.viv +5 -0
- viv_compiler/api.py +58 -0
- viv_compiler/backports/__init__.py +1 -0
- viv_compiler/backports/backports.py +12 -0
- viv_compiler/cli.py +237 -0
- viv_compiler/config/__init__.py +1 -0
- viv_compiler/config/config.py +88 -0
- viv_compiler/core/__init__.py +5 -0
- viv_compiler/core/core.py +185 -0
- viv_compiler/core/importer.py +111 -0
- viv_compiler/core/postprocessor.py +749 -0
- viv_compiler/core/validator.py +915 -0
- viv_compiler/core/visitor.py +1188 -0
- viv_compiler/grammar/__init__.py +0 -0
- viv_compiler/grammar/viv.peg +228 -0
- viv_compiler/py.typed +1 -0
- viv_compiler/types/__init__.py +3 -0
- viv_compiler/types/content_public_schemas.py +420 -0
- viv_compiler/types/dsl_public_schemas.py +566 -0
- viv_compiler/types/internal_types.py +167 -0
- viv_compiler/utils/__init__.py +1 -0
- viv_compiler/utils/_version.py +2 -0
- viv_compiler/utils/utils.py +171 -0
- viv_compiler-0.1.0.dist-info/METADATA +284 -0
- viv_compiler-0.1.0.dist-info/RECORD +32 -0
- viv_compiler-0.1.0.dist-info/WHEEL +5 -0
- viv_compiler-0.1.0.dist-info/entry_points.txt +3 -0
- viv_compiler-0.1.0.dist-info/licenses/LICENSE +21 -0
- viv_compiler-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1188 @@
|
|
1
|
+
"""Visitor component of the Viv DSL compiler.
|
2
|
+
|
3
|
+
Defines a single `Visitor` class that traverses and modifies a Viv AST, in
|
4
|
+
accordance with the structure prescribed by Arpeggio, our PEG parser solution.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import math
|
8
|
+
import copy
|
9
|
+
import arpeggio
|
10
|
+
import viv_compiler.config
|
11
|
+
import viv_compiler.types
|
12
|
+
from viv_compiler.types import ExpressionDiscriminator, ReferencePathComponentDiscriminator
|
13
|
+
from typing import Any, Optional, TypeGuard
|
14
|
+
|
15
|
+
|
16
|
+
class Visitor(arpeggio.PTNodeVisitor):
|
17
|
+
"""A visitor for Viv abstract syntax trees (AST), following the visitor pattern in parsing."""
|
18
|
+
|
19
|
+
@staticmethod
|
20
|
+
def visit_file(_, children: Any) -> viv_compiler.types.AST:
|
21
|
+
"""Visit the <file> node, i.e., the root of the parse tree."""
|
22
|
+
ast = {"_includes": [], "tropes": [], "actions": []}
|
23
|
+
for child in children:
|
24
|
+
if child["type"] == 'include':
|
25
|
+
ast['_includes'].append(child['value'])
|
26
|
+
elif child["type"] == 'action':
|
27
|
+
ast['actions'].append(child['value'])
|
28
|
+
elif child["type"] == 'trope':
|
29
|
+
ast['tropes'].append(child['value'])
|
30
|
+
return ast
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
def visit_include(_, children: Any) -> dict[str, Any]:
|
34
|
+
"""Visit an <include> node."""
|
35
|
+
return {"type": "include", "value": children[0]}
|
36
|
+
|
37
|
+
@staticmethod
|
38
|
+
def visit_filename(_, children: Any) -> str:
|
39
|
+
"""Visit a <filename> node."""
|
40
|
+
return ''.join(children)
|
41
|
+
|
42
|
+
@staticmethod
|
43
|
+
def visit_action(_, children: Any) -> dict[str, Any]:
|
44
|
+
"""Visit an <action> node."""
|
45
|
+
action_definition = {"special": False, "parent": None}
|
46
|
+
header, body = children
|
47
|
+
action_definition.update(header)
|
48
|
+
action_definition.update(body)
|
49
|
+
return {"type": "action", "value": action_definition}
|
50
|
+
|
51
|
+
@staticmethod
|
52
|
+
def visit_action_header(_, children: Any) -> dict[str, Any]:
|
53
|
+
"""Visit a <action_header> node."""
|
54
|
+
component = {"special": False}
|
55
|
+
for child in children:
|
56
|
+
if isinstance(child, str):
|
57
|
+
component['name'] = child
|
58
|
+
else:
|
59
|
+
component.update(child)
|
60
|
+
return component
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def visit_special_action_tag(_, __: Any) -> dict[str, bool]:
|
64
|
+
"""Visit a <special_action_tag> node."""
|
65
|
+
return {"special": True}
|
66
|
+
|
67
|
+
@staticmethod
|
68
|
+
def visit_parent_action_declaration(_, children: Any) -> dict[str, str]:
|
69
|
+
"""Visit a <parent_action_declaration> node."""
|
70
|
+
return {"parent": children[0]}
|
71
|
+
|
72
|
+
@staticmethod
|
73
|
+
def visit_action_body(_, children: Any) -> dict[str, Any]:
|
74
|
+
"""Visit a <action_body> node."""
|
75
|
+
# Prepare with base data including optional fields
|
76
|
+
component = {}
|
77
|
+
for child in children:
|
78
|
+
component.update(child)
|
79
|
+
return component
|
80
|
+
|
81
|
+
@staticmethod
|
82
|
+
def visit_gloss(
|
83
|
+
_, children: Any
|
84
|
+
) -> dict[str, viv_compiler.types.TemplateStringField | viv_compiler.types.StringField]:
|
85
|
+
"""Visit a <gloss> node."""
|
86
|
+
gloss = children[0]
|
87
|
+
return {"gloss": gloss}
|
88
|
+
|
89
|
+
@staticmethod
|
90
|
+
def visit_report(_, children: Any) -> dict[str, viv_compiler.types.TemplateStringField]:
|
91
|
+
"""Visit a <report> node."""
|
92
|
+
report = children[0]
|
93
|
+
return {"report": report}
|
94
|
+
|
95
|
+
@staticmethod
|
96
|
+
def visit_templated_text(_, children: Any) -> viv_compiler.types.TemplateStringField:
|
97
|
+
"""Visit a <templated_text> node."""
|
98
|
+
template = []
|
99
|
+
for child in children:
|
100
|
+
if isinstance(child, dict):
|
101
|
+
template.append(child)
|
102
|
+
else:
|
103
|
+
template.append({"type": "string", "value": child})
|
104
|
+
component = {"type": ExpressionDiscriminator.TEMPLATE_STRING, "value": template}
|
105
|
+
return component
|
106
|
+
|
107
|
+
@staticmethod
|
108
|
+
def visit_template_frame_component_double_quote(node: Any, _) -> str:
|
109
|
+
"""Visit a <template_frame_component_double_quote> node."""
|
110
|
+
return str(node)
|
111
|
+
|
112
|
+
@staticmethod
|
113
|
+
def visit_template_frame_component_single_quote(node: Any, _) -> str:
|
114
|
+
"""Visit a <template_frame_component_single_quote> node."""
|
115
|
+
return str(node)
|
116
|
+
|
117
|
+
@staticmethod
|
118
|
+
def visit_template_gap(_, children: Any) -> viv_compiler.types.Expression:
|
119
|
+
"""Visit a <template_gap> node."""
|
120
|
+
return children[0]
|
121
|
+
|
122
|
+
@staticmethod
|
123
|
+
def visit_tags_field(_, children: Any) -> dict[str, Any]:
|
124
|
+
"""Visit a <tags_field> node."""
|
125
|
+
if len(children) == 2:
|
126
|
+
component = {"tags": children[1], "_join_tags": True}
|
127
|
+
else:
|
128
|
+
component = {"tags": children[0]}
|
129
|
+
return component
|
130
|
+
|
131
|
+
@staticmethod
|
132
|
+
def visit_tags(_, children: Any) -> viv_compiler.types.ListField:
|
133
|
+
"""Visit a <tags> node."""
|
134
|
+
component = {"type": ExpressionDiscriminator.LIST, "value": children}
|
135
|
+
return component
|
136
|
+
|
137
|
+
@staticmethod
|
138
|
+
def visit_roles(_, children: Any) -> dict[str, Any]:
|
139
|
+
"""Visit a <roles> node."""
|
140
|
+
if children[0] == "join":
|
141
|
+
component = {"roles": children[1:], "_join_roles": True}
|
142
|
+
else:
|
143
|
+
component = {"roles": children}
|
144
|
+
return component
|
145
|
+
|
146
|
+
@staticmethod
|
147
|
+
def visit_role(_, children: Any) -> viv_compiler.types.RoleDefinition:
|
148
|
+
"""Visit a <role> node."""
|
149
|
+
if "_role_renaming" in children[0]:
|
150
|
+
return children[0]
|
151
|
+
component = {
|
152
|
+
"min": 1,
|
153
|
+
"max": 1,
|
154
|
+
"chance": None,
|
155
|
+
"mean": None,
|
156
|
+
"sd": None,
|
157
|
+
"pool": None,
|
158
|
+
"parent": None,
|
159
|
+
"children": [],
|
160
|
+
"character": False,
|
161
|
+
"item": False,
|
162
|
+
"action": False,
|
163
|
+
"location": False,
|
164
|
+
"symbol": False,
|
165
|
+
"initiator": False,
|
166
|
+
"partner": False,
|
167
|
+
"recipient": False,
|
168
|
+
"bystander": False,
|
169
|
+
"subject": False,
|
170
|
+
"absent": False,
|
171
|
+
"precast": False,
|
172
|
+
"build": False,
|
173
|
+
}
|
174
|
+
for child in children:
|
175
|
+
if 'min' in child:
|
176
|
+
component['min'] = child['min']['value']
|
177
|
+
component['max'] = child['max']['value']
|
178
|
+
else:
|
179
|
+
component.update(child)
|
180
|
+
if not (component['item'] or component['action'] or component['location'] or component['symbol']):
|
181
|
+
component['character'] = True
|
182
|
+
if component['mean'] is not None:
|
183
|
+
# Some initial experimentation has shown that taking the log of max - min produces a solid
|
184
|
+
# standard deviation (for a normal distribution centered on the author-supplied mean), but
|
185
|
+
# only for smaller values. Once the span eclipses 20 or so, we need a bigger SD to allow
|
186
|
+
# for the tails to get better coverage (since otherwise the min and max are effectively
|
187
|
+
# ignored). Empirically, it appears that dividing the span by 7 works for bigger spans.
|
188
|
+
span = component['max'] - component['min']
|
189
|
+
if span != 0:
|
190
|
+
sd = max(math.log(span), span / 7)
|
191
|
+
else:
|
192
|
+
sd = 0
|
193
|
+
component['sd'] = round(sd, 2)
|
194
|
+
return component
|
195
|
+
|
196
|
+
@staticmethod
|
197
|
+
def visit_role_renaming(_, children: Any) -> dict[str, Any]:
|
198
|
+
"""Visit a <role_renaming> node."""
|
199
|
+
component = {"_role_renaming": True, "_source_name": children[1]['name'], "_target_name": children[0]['name']}
|
200
|
+
return component
|
201
|
+
|
202
|
+
@staticmethod
|
203
|
+
def visit_number_range(_, children: Any) -> dict[str, Any]:
|
204
|
+
"""Visit a <number_range> node."""
|
205
|
+
if len(children) == 1:
|
206
|
+
return {"min": children[0], "max": children[0]}
|
207
|
+
return {"min": children[0], "max": children[1]}
|
208
|
+
|
209
|
+
@staticmethod
|
210
|
+
def visit_role_name(_, children: Any) -> dict[str, str]:
|
211
|
+
"""Visit a <role_name> node."""
|
212
|
+
name = children[0]
|
213
|
+
return {"name": name}
|
214
|
+
|
215
|
+
@staticmethod
|
216
|
+
def visit_binding_pool_directive(_, children: Any) -> dict[str, Any]:
|
217
|
+
"""Visit a <binding_pool_directive> node."""
|
218
|
+
return children[0]
|
219
|
+
|
220
|
+
@staticmethod
|
221
|
+
def visit_binding_pool_from_directive(_, children: Any) -> dict[str, Any]:
|
222
|
+
"""Visit a <binding_pool_from_directive> node."""
|
223
|
+
component = {"pool": {"body": children[0]}}
|
224
|
+
return component
|
225
|
+
|
226
|
+
@staticmethod
|
227
|
+
def visit_binding_pool_is_directive(_, children: Any) -> dict[str, Any]:
|
228
|
+
"""Visit a <binding_pool_is_directive> node."""
|
229
|
+
binding_pool = {"type": ExpressionDiscriminator.LIST, "value": [children[0]]}
|
230
|
+
component = {"pool": {"body": binding_pool}}
|
231
|
+
return component
|
232
|
+
|
233
|
+
@staticmethod
|
234
|
+
def visit_role_labels(_, children: Any) -> dict[str, Any]:
|
235
|
+
"""Visit a <role_labels> node."""
|
236
|
+
role_labels = []
|
237
|
+
entity_recipe = None
|
238
|
+
for role_label in children:
|
239
|
+
if isinstance(role_label, dict): # Build directive
|
240
|
+
role_labels.append("build")
|
241
|
+
entity_recipe = role_label["build"]
|
242
|
+
else:
|
243
|
+
role_labels.append(role_label)
|
244
|
+
role_labels_component = {tag: True for tag in role_labels}
|
245
|
+
if entity_recipe:
|
246
|
+
role_labels_component["entityRecipe"] = entity_recipe
|
247
|
+
return role_labels_component
|
248
|
+
|
249
|
+
@staticmethod
|
250
|
+
def visit_build_directive(_, children: Any) -> dict[str, Any]:
|
251
|
+
"""Visit a <build_directive> node."""
|
252
|
+
return {"build": children[0]}
|
253
|
+
|
254
|
+
@staticmethod
|
255
|
+
def visit_build_directive_entity_recipe(_, children: Any) -> Any:
|
256
|
+
"""Visit a <build_directive_entity_recipe> node."""
|
257
|
+
return children[0]
|
258
|
+
|
259
|
+
@staticmethod
|
260
|
+
def visit_binding_rate_directive(_, children: Any) -> Any:
|
261
|
+
"""Visit a <binding_rate_directive> node."""
|
262
|
+
return children[0]
|
263
|
+
|
264
|
+
@staticmethod
|
265
|
+
def visit_chance_directive(_, children: Any) -> dict[str, float]:
|
266
|
+
"""Visit a <chance_directive> node."""
|
267
|
+
# Children are the numeric tokens only (the `%` is a literal in the grammar)
|
268
|
+
number = ''.join(str(child) for child in children)
|
269
|
+
# Guard against empty or degenerate number strings
|
270
|
+
if number in ('', '-', '.', '-.'):
|
271
|
+
chance = -1.0 # There's no number preceding the `%`, so just trigger an error reliably
|
272
|
+
else:
|
273
|
+
try:
|
274
|
+
chance = float(number) / 100.0
|
275
|
+
except ValueError:
|
276
|
+
chance = -1.0 # Same idea here
|
277
|
+
# Preserve any negative we just created for the validator, and otherwise clamp tiny positives for stability
|
278
|
+
if chance > 0:
|
279
|
+
chance = max(round(chance, 3), 0.001)
|
280
|
+
return {"chance": chance}
|
281
|
+
|
282
|
+
@staticmethod
|
283
|
+
def visit_mean_directive(_, children: Any) -> dict[str, float]:
|
284
|
+
"""Visit a <mean_directive> node."""
|
285
|
+
# Children are the numeric tokens only (the `~` is a literal in the grammar)
|
286
|
+
number = ''.join(str(child) for child in children)
|
287
|
+
# Guard against empty or degenerate number strings
|
288
|
+
if number in ('', '-', '.', '-.'):
|
289
|
+
mean = -1.0 # There's no number succeeding the `~`, so just trigger an error reliably
|
290
|
+
else:
|
291
|
+
try:
|
292
|
+
mean = float(number)
|
293
|
+
except ValueError:
|
294
|
+
mean = -1.0 # Same idea here
|
295
|
+
mean = round(mean, 2)
|
296
|
+
return {"mean": mean}
|
297
|
+
|
298
|
+
@staticmethod
|
299
|
+
def visit_preconditions(_, children: Any) -> dict[str, Any]:
|
300
|
+
"""Visit a <preconditions> node."""
|
301
|
+
if len(children) == 2:
|
302
|
+
component = {"preconditions": children[1], "_join_preconditions": True}
|
303
|
+
else:
|
304
|
+
component = {"preconditions": children[0]}
|
305
|
+
return component
|
306
|
+
|
307
|
+
@staticmethod
|
308
|
+
def visit_child_join_operator(node: Any, __: Any) -> Any:
|
309
|
+
"""Visit a <child_join_operator> node."""
|
310
|
+
return node
|
311
|
+
|
312
|
+
@staticmethod
|
313
|
+
def visit_scratch(_, children: Any) -> dict[str, Any]:
|
314
|
+
"""Visit a <scratch> node."""
|
315
|
+
if len(children) == 2:
|
316
|
+
component = {"scratch": children[1], "_join_scratch": True}
|
317
|
+
else:
|
318
|
+
component = {"scratch": children[0]}
|
319
|
+
return component
|
320
|
+
|
321
|
+
@staticmethod
|
322
|
+
def visit_effects(_, children: Any) -> dict[str, Any]:
|
323
|
+
"""Visit a <effects> node."""
|
324
|
+
if len(children) == 2:
|
325
|
+
component = {"effects": children[1], "_join_effects": True}
|
326
|
+
else:
|
327
|
+
component = {"effects": children[0]}
|
328
|
+
return component
|
329
|
+
|
330
|
+
@staticmethod
|
331
|
+
def visit_reactions(_, children: Any) -> dict[str, Any]:
|
332
|
+
"""Visit a <reactions> node."""
|
333
|
+
if children[0] == "join":
|
334
|
+
component = {"reactions": children[1:], "_join_reactions": True}
|
335
|
+
else:
|
336
|
+
component = {"reactions": children}
|
337
|
+
return component
|
338
|
+
|
339
|
+
@staticmethod
|
340
|
+
def visit_reaction(_, children: Any):
|
341
|
+
"""Visit a <reaction> node."""
|
342
|
+
# Prepare default reaction settings
|
343
|
+
reaction_object = {
|
344
|
+
"options": copy.deepcopy(viv_compiler.config.REACTION_FIELD_DEFAULT_OPTIONS),
|
345
|
+
}
|
346
|
+
for child in children:
|
347
|
+
reaction_object.update(child)
|
348
|
+
# Move the 'bindings' field from the options component to the top level of the reaction value
|
349
|
+
reaction_object['bindings'] = reaction_object['options']['bindings']
|
350
|
+
del reaction_object['options']['bindings']
|
351
|
+
# Validate the shape of the reaction value
|
352
|
+
def _is_reaction_value(obj) -> "TypeGuard[viv_compiler.types.ReactionValue]":
|
353
|
+
if not isinstance(obj, dict):
|
354
|
+
return False
|
355
|
+
if not {"actionName", "bindings", "options"} <= set(obj):
|
356
|
+
return False
|
357
|
+
if not isinstance(obj.get("actionName"), str):
|
358
|
+
return False
|
359
|
+
bindings = obj.get("bindings")
|
360
|
+
if not isinstance(bindings, list):
|
361
|
+
return False
|
362
|
+
for b in bindings:
|
363
|
+
if not (isinstance(b, dict) and b.get("type") == "binding" and isinstance(b.get("value"), dict)):
|
364
|
+
return False
|
365
|
+
v = b["value"]
|
366
|
+
if not (isinstance(v.get("role"), str) and "entity" in v):
|
367
|
+
return False
|
368
|
+
options = obj.get("options")
|
369
|
+
if not isinstance(options, dict):
|
370
|
+
return False
|
371
|
+
if "bindings" in options:
|
372
|
+
return False
|
373
|
+
return True
|
374
|
+
if not _is_reaction_value(reaction_object):
|
375
|
+
raise ValueError(f"Malformed reaction generated by Visitor: {reaction_object!r}")
|
376
|
+
# Package up the component and return it
|
377
|
+
reaction_value: viv_compiler.types.ReactionValue = reaction_object # type: ignore[assignment]
|
378
|
+
component: viv_compiler.types.Reaction = {"type": ExpressionDiscriminator.REACTION, "value": reaction_value}
|
379
|
+
return component
|
380
|
+
|
381
|
+
@staticmethod
|
382
|
+
def visit_reaction_action_name(_, children: Any) -> dict[str, Any]:
|
383
|
+
"""Visit a <reaction_action_name> node."""
|
384
|
+
return {"actionName": children[0]}
|
385
|
+
|
386
|
+
@staticmethod
|
387
|
+
def visit_bindings(_, children: Any) -> dict[str, Any]:
|
388
|
+
"""Visit a <bindings> node."""
|
389
|
+
return {"bindings": children}
|
390
|
+
|
391
|
+
@staticmethod
|
392
|
+
def visit_binding(_, children: Any) -> viv_compiler.types.ReactionBinding | viv_compiler.types.Loop:
|
393
|
+
"""Visit a <binding> node."""
|
394
|
+
# Support both (name ":" reference) and the 'loop' alternative per the grammar
|
395
|
+
if len(children) == 1:
|
396
|
+
if isinstance(children[0], dict) and children[0].get("type") == ExpressionDiscriminator.LOOP:
|
397
|
+
return children[0]
|
398
|
+
role_name, entity_to_bind = children
|
399
|
+
component = {"type": ExpressionDiscriminator.BINDING, "value": {"role": role_name, "entity": entity_to_bind}}
|
400
|
+
return component
|
401
|
+
|
402
|
+
@staticmethod
|
403
|
+
def visit_reaction_options(_, children: Any) -> dict[str, Any]:
|
404
|
+
"""Visit a <reaction_options> node."""
|
405
|
+
component = {"options": copy.deepcopy(viv_compiler.config.REACTION_FIELD_DEFAULT_OPTIONS)}
|
406
|
+
for child in children:
|
407
|
+
component['options'].update(child)
|
408
|
+
return component
|
409
|
+
|
410
|
+
@staticmethod
|
411
|
+
def visit_urgent(_, children: Any) -> dict[str, Any]:
|
412
|
+
"""Visit a <urgent> node."""
|
413
|
+
component = {"urgent": children[0]}
|
414
|
+
return component
|
415
|
+
|
416
|
+
@staticmethod
|
417
|
+
def visit_priority(_, children: Any) -> dict[str, Any]:
|
418
|
+
"""Visit a <priority> node."""
|
419
|
+
component = {"priority": children[0]}
|
420
|
+
return component
|
421
|
+
|
422
|
+
@staticmethod
|
423
|
+
def visit_kill_code(_, children: Any) -> dict[str, Any]:
|
424
|
+
"""Visit a <kill_code> node."""
|
425
|
+
component = {"killCode": children[0]}
|
426
|
+
return component
|
427
|
+
|
428
|
+
@staticmethod
|
429
|
+
def visit_where(_, children: Any) -> dict[str, Any]:
|
430
|
+
"""Visit a <where> node."""
|
431
|
+
component = {"where": children[0]}
|
432
|
+
return component
|
433
|
+
|
434
|
+
@staticmethod
|
435
|
+
def visit_when(_, children: Any) -> dict[str, viv_compiler.types.TemporalConstraints]:
|
436
|
+
"""Visit a <when> node."""
|
437
|
+
when_value = {}
|
438
|
+
for child in children:
|
439
|
+
when_value.update(child)
|
440
|
+
component = {"when": when_value}
|
441
|
+
return component
|
442
|
+
|
443
|
+
@staticmethod
|
444
|
+
def visit_when_action_timestamp_anchor(_, __: Any) -> dict[str, bool]:
|
445
|
+
"""Visit a <when_action_timestamp_anchor> node."""
|
446
|
+
component = {"useActionTimestamp": True}
|
447
|
+
return component
|
448
|
+
|
449
|
+
@staticmethod
|
450
|
+
def visit_when_hearing_timestamp_anchor(_, __: Any) -> dict[str, bool]:
|
451
|
+
"""Visit a <when_hearing_timestamp_anchor> node."""
|
452
|
+
component = {"useActionTimestamp": False}
|
453
|
+
return component
|
454
|
+
|
455
|
+
@staticmethod
|
456
|
+
def visit_time_expression(_, children: Any) -> viv_compiler.types.TemporalConstraints:
|
457
|
+
"""Visit a <time_expression> node."""
|
458
|
+
time_constraints: dict[str, Optional[dict[str, Any]]] = {"timePeriod": None, "timeOfDay": None}
|
459
|
+
for child in children:
|
460
|
+
if child["operator"] == "between":
|
461
|
+
if child["open"]["type"] == "timePeriod":
|
462
|
+
time_constraints["timePeriod"] = {
|
463
|
+
"open": {"amount": child["open"]["amount"], "unit": child["open"]["unit"]},
|
464
|
+
"close": {"amount": child["close"]["amount"], "unit": child["close"]["unit"]},
|
465
|
+
}
|
466
|
+
else: # child["open"]["type"] == "time"
|
467
|
+
time_constraints["timeOfDay"] = {
|
468
|
+
"open": {"hour": child["open"]["hour"], "minute": child["open"]["minute"]},
|
469
|
+
"close": {"hour": child["close"]["hour"], "minute": child["close"]["minute"]},
|
470
|
+
}
|
471
|
+
elif child["operator"] == "before":
|
472
|
+
anchor = child["anchor"]
|
473
|
+
if anchor["type"] == "timePeriod":
|
474
|
+
time_constraints["timePeriod"] = {
|
475
|
+
"open": None,
|
476
|
+
"close": {"amount": anchor["amount"], "unit": anchor["unit"]},
|
477
|
+
}
|
478
|
+
else: # anchor["type"] == "time"
|
479
|
+
time_constraints["timeOfDay"] = {
|
480
|
+
"open": None,
|
481
|
+
"close": {"hour": anchor["hour"], "minute": anchor["minute"]}
|
482
|
+
}
|
483
|
+
else: # operator == "after":
|
484
|
+
anchor = child["anchor"]
|
485
|
+
if anchor["type"] == "timePeriod":
|
486
|
+
time_constraints["timePeriod"] = {
|
487
|
+
"open": {"amount": anchor["amount"], "unit": anchor["unit"]},
|
488
|
+
"close": None,
|
489
|
+
}
|
490
|
+
else: # anchor["type"] == "time"
|
491
|
+
time_constraints["timeOfDay"] = {
|
492
|
+
"open": {"hour": anchor["hour"], "minute": anchor["minute"]},
|
493
|
+
"close": None,
|
494
|
+
}
|
495
|
+
return time_constraints
|
496
|
+
|
497
|
+
@staticmethod
|
498
|
+
def visit_time_statement(_, children: Any) -> dict[str, Any]:
|
499
|
+
"""Visit a <time_statement> node."""
|
500
|
+
return children[0]
|
501
|
+
|
502
|
+
@staticmethod
|
503
|
+
def visit_before_time_statement(_, children: Any) -> dict[str, Any]:
|
504
|
+
"""Visit a <before_time_statement> node."""
|
505
|
+
return {"operator": "before", "anchor": children[0]}
|
506
|
+
|
507
|
+
@staticmethod
|
508
|
+
def visit_after_time_statement(_, children: Any) -> dict[str, Any]:
|
509
|
+
"""Visit a <after_time_statement> node."""
|
510
|
+
return {"operator": "after", "anchor": children[0]}
|
511
|
+
|
512
|
+
@staticmethod
|
513
|
+
def visit_between_time_statement(_, children: Any) -> dict[str, Any]:
|
514
|
+
"""Visit a <between_time_statement> node."""
|
515
|
+
return {"operator": "between", "open": children[0], "close": children[1]}
|
516
|
+
|
517
|
+
@staticmethod
|
518
|
+
def visit_time_period(_, children: Any) -> dict[str, Any]:
|
519
|
+
"""Visit a <time_period> node."""
|
520
|
+
number = children[0]['value']
|
521
|
+
unit = children[1] + 's' if children[1][-1] != 's' else children[1]
|
522
|
+
component = {"type": "timePeriod", "amount": number, "unit": unit}
|
523
|
+
return component
|
524
|
+
|
525
|
+
@staticmethod
|
526
|
+
def visit_time(_, children: Any) -> dict[str, Any]:
|
527
|
+
"""Visit a <time> node."""
|
528
|
+
period = children[-1] # AM or PM
|
529
|
+
raw_hour = int(children[0])
|
530
|
+
hour = (raw_hour % 12) + (12 if period.upper() == 'PM' else 0)
|
531
|
+
if len(children) == 2: # Only the hour was provided
|
532
|
+
component = {
|
533
|
+
"type": "time",
|
534
|
+
"hour": hour,
|
535
|
+
"minute": 0
|
536
|
+
}
|
537
|
+
else:
|
538
|
+
component = {
|
539
|
+
"type": "time",
|
540
|
+
"hour": hour,
|
541
|
+
"minute": int(children[1])
|
542
|
+
}
|
543
|
+
return component
|
544
|
+
|
545
|
+
@staticmethod
|
546
|
+
def visit_digits(_, children: Any) -> str:
|
547
|
+
"""Visit a <digits> node."""
|
548
|
+
return ''.join(children)
|
549
|
+
|
550
|
+
@staticmethod
|
551
|
+
def visit_abandonment_conditions(_, children: Any) -> dict[str, list[viv_compiler.types.Expression]]:
|
552
|
+
"""Visit a <abandonment_conditions> node."""
|
553
|
+
component = {"abandonmentConditions": children}
|
554
|
+
return component
|
555
|
+
|
556
|
+
@staticmethod
|
557
|
+
def visit_saliences(_, children: Any) -> dict[str, Any]:
|
558
|
+
"""Visit a <saliences> node."""
|
559
|
+
join_saliences = False
|
560
|
+
if children[0] == "join":
|
561
|
+
join_saliences = True
|
562
|
+
children = children[1:]
|
563
|
+
default_value_expression = children[0]
|
564
|
+
body = children[1] if len(children) > 1 else []
|
565
|
+
component = {
|
566
|
+
"saliences": {
|
567
|
+
"default": default_value_expression,
|
568
|
+
"variable": viv_compiler.config.SALIENCES_VARIABLE_NAME,
|
569
|
+
"body": body,
|
570
|
+
}
|
571
|
+
}
|
572
|
+
if join_saliences:
|
573
|
+
component["_join_saliences"] = True
|
574
|
+
return component
|
575
|
+
|
576
|
+
@staticmethod
|
577
|
+
def visit_saliences_default_value(_, children: Any) -> viv_compiler.types.Expression:
|
578
|
+
"""Visit a <saliences_default_value> node."""
|
579
|
+
return children[0]
|
580
|
+
|
581
|
+
@staticmethod
|
582
|
+
def visit_saliences_body(_, children: Any) -> list[viv_compiler.types.Expression]:
|
583
|
+
"""Visit a <saliences_body> node."""
|
584
|
+
return children
|
585
|
+
|
586
|
+
@staticmethod
|
587
|
+
def visit_associations(_, children: Any) -> dict[str, Any]:
|
588
|
+
"""Visit an <associations> node."""
|
589
|
+
join_associations = False
|
590
|
+
if children[0] == "join":
|
591
|
+
join_associations = True
|
592
|
+
children = children[1:]
|
593
|
+
default_value_expression = children[0]
|
594
|
+
body = children[1] if len(children) > 1 else []
|
595
|
+
component = {
|
596
|
+
"associations": {
|
597
|
+
"default": default_value_expression,
|
598
|
+
"variable": viv_compiler.config.ASSOCIATIONS_VARIABLE_NAME,
|
599
|
+
"body": body,
|
600
|
+
}
|
601
|
+
}
|
602
|
+
if join_associations:
|
603
|
+
component["_join_associations"] = True
|
604
|
+
return component
|
605
|
+
|
606
|
+
@staticmethod
|
607
|
+
def visit_associations_default_value(_, children: Any) -> viv_compiler.types.Expression:
|
608
|
+
"""Visit a <associations_default_value> node."""
|
609
|
+
return children[0]
|
610
|
+
|
611
|
+
@staticmethod
|
612
|
+
def visit_associations_body(_, children: Any) -> list[viv_compiler.types.Expression]:
|
613
|
+
"""Visit a <associations_body> node."""
|
614
|
+
return children[0]
|
615
|
+
|
616
|
+
@staticmethod
|
617
|
+
def visit_associations_statements(_, children: Any) -> list[viv_compiler.types.Expression]:
|
618
|
+
"""Visit a <associations_statements> node."""
|
619
|
+
return children
|
620
|
+
|
621
|
+
@staticmethod
|
622
|
+
def visit_associations_statement(_, children: Any) -> list[viv_compiler.types.Expression]:
|
623
|
+
"""Visit a <associations_statement> node."""
|
624
|
+
return children[0]
|
625
|
+
|
626
|
+
@staticmethod
|
627
|
+
def visit_associations_loop(_, children: Any) -> viv_compiler.types.Loop:
|
628
|
+
"""Visit a <associations_loop> node."""
|
629
|
+
iterable_reference, variable_name, loop_body = children
|
630
|
+
component = {
|
631
|
+
"type": ExpressionDiscriminator.LOOP,
|
632
|
+
"value": {"iterable": iterable_reference, "variable": variable_name['name'], "body": loop_body}
|
633
|
+
}
|
634
|
+
return component
|
635
|
+
|
636
|
+
@staticmethod
|
637
|
+
def visit_associations_conditional(_, children: Any) -> viv_compiler.types.Conditional:
|
638
|
+
"""Visit a <associations_conditional> node."""
|
639
|
+
conditional_object = {"type": ExpressionDiscriminator.CONDITIONAL, "value": {}}
|
640
|
+
for child in children:
|
641
|
+
conditional_object['value'].update(child)
|
642
|
+
return conditional_object
|
643
|
+
|
644
|
+
@staticmethod
|
645
|
+
def visit_associations_conditional_consequent(_, children: Any) -> dict[str, list[viv_compiler.types.Expression]]:
|
646
|
+
"""Visit a <associations_conditional_consequent> node."""
|
647
|
+
return {"consequent": children[0]}
|
648
|
+
|
649
|
+
@staticmethod
|
650
|
+
def visit_associations_conditional_alternative(_, children: Any) -> dict[str, list[viv_compiler.types.Expression]]:
|
651
|
+
"""Visit a <associations_conditional_alternative> node."""
|
652
|
+
return {"alternative": children[0]}
|
653
|
+
|
654
|
+
@staticmethod
|
655
|
+
def visit_associations_scoped_statements(_, children: Any) -> list[viv_compiler.types.Expression]:
|
656
|
+
"""Visit a <associations_scoped_statements> node."""
|
657
|
+
return children
|
658
|
+
|
659
|
+
@staticmethod
|
660
|
+
def visit_associations_scoped_statement(_, children: Any) -> list[viv_compiler.types.Expression]:
|
661
|
+
"""Visit a <associations_scoped_statement> node."""
|
662
|
+
return children[0]
|
663
|
+
|
664
|
+
@staticmethod
|
665
|
+
def visit_embargoes(_, children: Any) -> dict[str, Any]:
|
666
|
+
"""Visit an <embargoes> node."""
|
667
|
+
if children[0] == "join":
|
668
|
+
component = {"embargoes": children[1:], "_join_embargoes": True}
|
669
|
+
else:
|
670
|
+
component = {"embargoes": children}
|
671
|
+
return component
|
672
|
+
|
673
|
+
@staticmethod
|
674
|
+
def visit_embargo(_, children: Any) -> dict[str, Any]:
|
675
|
+
"""Visit an <embargo> node."""
|
676
|
+
# Prepare default values that we'll override below or in post-processing
|
677
|
+
component = {"roles": None, "permanent": False, "period": None, "here": False}
|
678
|
+
for child in children:
|
679
|
+
component.update(child)
|
680
|
+
return component
|
681
|
+
|
682
|
+
@staticmethod
|
683
|
+
def visit_embargo_roles(_, children: Any) -> dict[str, list[str]]:
|
684
|
+
"""Visit an <embargo_roles> node."""
|
685
|
+
component = {"roles": [child['name'] for child in children]}
|
686
|
+
return component
|
687
|
+
|
688
|
+
@staticmethod
|
689
|
+
def visit_embargo_time_period(_, children: Any) -> dict[str, Any]:
|
690
|
+
"""Visit an <embargo_time_period> node."""
|
691
|
+
if children == ["forever"]:
|
692
|
+
component = {"permanent": True, "period": None}
|
693
|
+
else:
|
694
|
+
component = {
|
695
|
+
"permanent": False,
|
696
|
+
"period": {
|
697
|
+
"amount": children[0]["amount"],
|
698
|
+
"unit": children[0]["unit"]
|
699
|
+
}
|
700
|
+
}
|
701
|
+
return component
|
702
|
+
|
703
|
+
@staticmethod
|
704
|
+
def visit_embargo_location(_, __: Any) -> dict[str, bool]:
|
705
|
+
"""Visit an <embargo_location> node."""
|
706
|
+
component = {"here": True}
|
707
|
+
return component
|
708
|
+
|
709
|
+
@staticmethod
|
710
|
+
def visit_conditional(_, children: Any) -> viv_compiler.types.Conditional:
|
711
|
+
"""Visit a <conditional> node."""
|
712
|
+
conditional_object = {"type": ExpressionDiscriminator.CONDITIONAL, "value": {}}
|
713
|
+
for child in children:
|
714
|
+
conditional_object['value'].update(child)
|
715
|
+
return conditional_object
|
716
|
+
|
717
|
+
@staticmethod
|
718
|
+
def visit_condition(_, children: Any) -> dict[str, viv_compiler.types.Expression]:
|
719
|
+
"""Visit a <condition> node."""
|
720
|
+
return {"condition": children[0]}
|
721
|
+
|
722
|
+
@staticmethod
|
723
|
+
def visit_consequent(_, children: Any) -> dict[str, list[viv_compiler.types.Expression]]:
|
724
|
+
"""Visit a <condition> node."""
|
725
|
+
return {"consequent": children[0]}
|
726
|
+
|
727
|
+
@staticmethod
|
728
|
+
def visit_alternative(_, children: Any) -> dict[str, list[viv_compiler.types.Expression]]:
|
729
|
+
"""Visit a <condition> node."""
|
730
|
+
return {"alternative": children[0]}
|
731
|
+
|
732
|
+
@staticmethod
|
733
|
+
def visit_loop(_, children: Any) -> viv_compiler.types.Loop:
|
734
|
+
"""Visit a <loop> node."""
|
735
|
+
iterable_reference, variable_name, loop_body = children
|
736
|
+
component = {
|
737
|
+
"type": ExpressionDiscriminator.LOOP,
|
738
|
+
"value": {"iterable": iterable_reference, "variable": variable_name['name'], "body": loop_body}
|
739
|
+
}
|
740
|
+
return component
|
741
|
+
|
742
|
+
@staticmethod
|
743
|
+
def visit_statements(_, children: Any) -> list[viv_compiler.types.Expression]:
|
744
|
+
"""Visit a <statement> node."""
|
745
|
+
return children
|
746
|
+
|
747
|
+
@staticmethod
|
748
|
+
def visit_statement(_, children: Any) -> viv_compiler.types.Expression:
|
749
|
+
"""Visit a <statement> node."""
|
750
|
+
return children[0]
|
751
|
+
|
752
|
+
@staticmethod
|
753
|
+
def visit_scoped_statements(_, children: Any) -> list[viv_compiler.types.Expression]:
|
754
|
+
"""Visit a <scoped_statements> node."""
|
755
|
+
return children
|
756
|
+
|
757
|
+
@staticmethod
|
758
|
+
def visit_expression(_, children: Any) -> viv_compiler.types.Expression:
|
759
|
+
"""Visit an <expression> node."""
|
760
|
+
return children[0]
|
761
|
+
|
762
|
+
@staticmethod
|
763
|
+
def visit_logical_expression(_, children: Any) -> viv_compiler.types.Expression:
|
764
|
+
"""Visit a <logical_expression> node."""
|
765
|
+
# The <logical_expression> nonterminal is just a wrapper around disjunction,
|
766
|
+
# and in any event all the work to package up the expression will have already
|
767
|
+
# been done by the time it gets to here, so we simply return the sole child.
|
768
|
+
return children[0]
|
769
|
+
|
770
|
+
@staticmethod
|
771
|
+
def visit_disjunction(_, children: Any) -> viv_compiler.types.Expression:
|
772
|
+
"""Visit a <disjunction> node."""
|
773
|
+
# The elimination of left recursion in the grammar allows for a child expression to
|
774
|
+
# work its way up to here, in which case we just want to return that, since it's
|
775
|
+
# not a true disjunction.
|
776
|
+
if len(children) == 1:
|
777
|
+
return children[0]
|
778
|
+
negated = False
|
779
|
+
if children[0] == "!":
|
780
|
+
negated = True
|
781
|
+
children = children[1:]
|
782
|
+
component = {"type": ExpressionDiscriminator.DISJUNCTION, "value": {"operands": children}}
|
783
|
+
if negated:
|
784
|
+
component["negated"] = True
|
785
|
+
return component
|
786
|
+
|
787
|
+
@staticmethod
|
788
|
+
def visit_conjunction(_, children: Any) -> viv_compiler.types.Expression:
|
789
|
+
"""Visit a <conjunction> node."""
|
790
|
+
# The elimination of left recursion in the grammar allows for a child expression to
|
791
|
+
# work its way up to here, in which case we just want to return that, since it's
|
792
|
+
# not a true conjunction.
|
793
|
+
if len(children) == 1:
|
794
|
+
return children[0]
|
795
|
+
negated = False
|
796
|
+
if children[0] == "!":
|
797
|
+
negated = True
|
798
|
+
children = children[1:]
|
799
|
+
component = {"type": ExpressionDiscriminator.CONJUNCTION, "value": {"operands": children}}
|
800
|
+
if negated:
|
801
|
+
component["negated"] = True
|
802
|
+
return component
|
803
|
+
|
804
|
+
@staticmethod
|
805
|
+
def visit_relational_expression(_, children: Any) -> viv_compiler.types.Expression:
|
806
|
+
"""Visit a <relational_expression> node."""
|
807
|
+
# The elimination of left recursion in the grammar allows for a child expression to
|
808
|
+
# work its way up to here, in which case we just want to return that, since it's
|
809
|
+
# not a true comparison.
|
810
|
+
if len(children) == 1:
|
811
|
+
return children[0]
|
812
|
+
negated = False
|
813
|
+
if children[0] == "!":
|
814
|
+
negated = True
|
815
|
+
children = children[1:]
|
816
|
+
left, operator, right = children
|
817
|
+
component = {
|
818
|
+
"type": ExpressionDiscriminator.MEMBERSHIP_TEST if operator == "in" else ExpressionDiscriminator.COMPARISON,
|
819
|
+
"value": {
|
820
|
+
"left": left,
|
821
|
+
"operator": operator,
|
822
|
+
"right": right
|
823
|
+
}
|
824
|
+
}
|
825
|
+
if negated:
|
826
|
+
component["negated"] = True
|
827
|
+
return component
|
828
|
+
|
829
|
+
@staticmethod
|
830
|
+
def visit_assignment_expression(_, children: Any) -> viv_compiler.types.Assignment:
|
831
|
+
"""Visit an <assignment_expression> node."""
|
832
|
+
left, operator, right = children
|
833
|
+
component = {
|
834
|
+
"type": ExpressionDiscriminator.ASSIGNMENT,
|
835
|
+
"value": {
|
836
|
+
"left": left,
|
837
|
+
"operator": operator,
|
838
|
+
"right": right
|
839
|
+
}
|
840
|
+
}
|
841
|
+
return component
|
842
|
+
|
843
|
+
@staticmethod
|
844
|
+
def visit_assignment_lvalue(_, children: Any) -> dict[str, Any]:
|
845
|
+
"""Visit an <assignment_lvalue> node."""
|
846
|
+
return children[0]
|
847
|
+
|
848
|
+
@staticmethod
|
849
|
+
def visit_arithmetic_expression(_, children: Any) -> viv_compiler.types.ArithmeticExpression:
|
850
|
+
"""Visit an <arithmetic_expression> node."""
|
851
|
+
left, operator, right = children
|
852
|
+
component = {
|
853
|
+
"type": ExpressionDiscriminator.ARITHMETIC_EXPRESSION,
|
854
|
+
"value": {
|
855
|
+
"left": left,
|
856
|
+
"operator": operator,
|
857
|
+
"right": right
|
858
|
+
}
|
859
|
+
}
|
860
|
+
return component
|
861
|
+
|
862
|
+
@staticmethod
|
863
|
+
def visit_unary_expression(_, children: Any) -> viv_compiler.types.Expression:
|
864
|
+
"""Visit a <unary_expression> node."""
|
865
|
+
component = children[-1]
|
866
|
+
if len(children) > 1:
|
867
|
+
component['negated'] = True
|
868
|
+
return component
|
869
|
+
|
870
|
+
@staticmethod
|
871
|
+
def visit_object(_, children: Any) -> viv_compiler.types.ObjectField:
|
872
|
+
"""Visit a <object> node."""
|
873
|
+
component = {"type": ExpressionDiscriminator.OBJECT, "value": {}}
|
874
|
+
for child in children:
|
875
|
+
component['value'].update(child)
|
876
|
+
return component
|
877
|
+
|
878
|
+
@staticmethod
|
879
|
+
def visit_key_value_pair(_, children: Any) -> dict[str, viv_compiler.types.Expression]:
|
880
|
+
"""Visit a <key_value_pair> node."""
|
881
|
+
key, value = children
|
882
|
+
if isinstance(key, dict): # The author formatted the key as a string
|
883
|
+
key = key['value']
|
884
|
+
return {key: value}
|
885
|
+
|
886
|
+
@staticmethod
|
887
|
+
def visit_adapter_function_call(_, children: Any) -> viv_compiler.types.AdapterFunctionCall:
|
888
|
+
"""Visit a <adapter_function_call> node."""
|
889
|
+
function_result_fail_safe = False
|
890
|
+
if children[-1] == {"type": ExpressionDiscriminator.EVAL_FAIL_SAFE}:
|
891
|
+
function_result_fail_safe = True
|
892
|
+
children = children[:-1]
|
893
|
+
try:
|
894
|
+
function_name, function_args = children
|
895
|
+
except ValueError: # No arguments passed in the function call, which is syntactically fine
|
896
|
+
function_name = children[0]
|
897
|
+
function_args = []
|
898
|
+
function_call_object = {
|
899
|
+
"type": ExpressionDiscriminator.ADAPTER_FUNCTION_CALL,
|
900
|
+
"value": {
|
901
|
+
"name": function_name,
|
902
|
+
"args": function_args,
|
903
|
+
}
|
904
|
+
}
|
905
|
+
if function_result_fail_safe:
|
906
|
+
function_call_object["value"]["resultFailSafe"] = True
|
907
|
+
return function_call_object
|
908
|
+
|
909
|
+
@staticmethod
|
910
|
+
def visit_args(_, children: Any) -> list[viv_compiler.types.Expression]:
|
911
|
+
"""Visit a <args> node."""
|
912
|
+
return children
|
913
|
+
|
914
|
+
@staticmethod
|
915
|
+
def visit_reference(
|
916
|
+
_, children: Any
|
917
|
+
) -> viv_compiler.types.EntityReference | viv_compiler.types.LocalVariableReference:
|
918
|
+
return children[0]
|
919
|
+
|
920
|
+
@staticmethod
|
921
|
+
def visit_role_anchored_reference(_, children: Any) -> viv_compiler.types.EntityReference:
|
922
|
+
"""Visit a <role_anchored_reference> node."""
|
923
|
+
# Determine the anchor role name
|
924
|
+
if "globalVariable" in children[0]:
|
925
|
+
# If the reference is anchored in a global variable, expand the `$` anchor. `$` is really
|
926
|
+
# just syntactic sugar for `@this.scratch.`, which means any reference anchored in a global
|
927
|
+
# variable is in fact an entity reference anchored in a role name.
|
928
|
+
anchor = viv_compiler.config.GLOBAL_VARIABLE_REFERENCE_ANCHOR
|
929
|
+
path = viv_compiler.config.GLOBAL_VARIABLE_REFERENCE_PATH_PREFIX
|
930
|
+
# Add in the global variable itself, as a property name
|
931
|
+
path.append({
|
932
|
+
"type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_PROPERTY_NAME,
|
933
|
+
"propertyName": children[0]["name"],
|
934
|
+
})
|
935
|
+
path += children[1] if len(children) > 1 else []
|
936
|
+
else:
|
937
|
+
anchor = children[0]["name"]
|
938
|
+
path = children[1] if len(children) > 1 else []
|
939
|
+
component = {
|
940
|
+
"type": ExpressionDiscriminator.ENTITY_REFERENCE,
|
941
|
+
"value": {
|
942
|
+
"anchor": anchor, # The name of the role anchoring this entity reference
|
943
|
+
"path": path, # Sequence of components constituting a property path
|
944
|
+
}
|
945
|
+
}
|
946
|
+
return component
|
947
|
+
|
948
|
+
@staticmethod
|
949
|
+
def visit_local_variable_anchored_reference(_, children: Any) -> viv_compiler.types.LocalVariableReference:
|
950
|
+
"""Visit a <local_variable_anchored_reference> node."""
|
951
|
+
anchor = children[0]["name"]
|
952
|
+
path = children[1] if len(children) > 1 else []
|
953
|
+
component = {
|
954
|
+
"type": ExpressionDiscriminator.LOCAL_VARIABLE_REFERENCE,
|
955
|
+
"value": {
|
956
|
+
"anchor": anchor, # The name of the local variable anchoring this reference
|
957
|
+
"path": path, # Sequence of components constituting a property path
|
958
|
+
}
|
959
|
+
}
|
960
|
+
return component
|
961
|
+
|
962
|
+
@staticmethod
|
963
|
+
def visit_role_reference(_, children: Any) -> dict[str, str]:
|
964
|
+
"""Visit a <role_reference> node."""
|
965
|
+
name = children[0]
|
966
|
+
return {"name": name}
|
967
|
+
|
968
|
+
@staticmethod
|
969
|
+
def visit_local_variable_reference(_, children: Any):
|
970
|
+
"""Visit a <local_variable_reference> node."""
|
971
|
+
return {"name": children[0]}
|
972
|
+
|
973
|
+
@staticmethod
|
974
|
+
def visit_global_variable_reference(_, children: Any):
|
975
|
+
"""Visit a <global_variable_reference> node."""
|
976
|
+
return {"globalVariable": True, "name": children[0]}
|
977
|
+
|
978
|
+
@staticmethod
|
979
|
+
def visit_reference_path(_, children: Any) -> list[viv_compiler.types.ReferencePathComponent]:
|
980
|
+
"""Visit a <reference_path> node."""
|
981
|
+
return children
|
982
|
+
|
983
|
+
@staticmethod
|
984
|
+
def visit_reference_path_property_name(_, children: Any) -> viv_compiler.types.ReferencePathComponentPropertyName:
|
985
|
+
"""Visit a <reference_path_property_name> node."""
|
986
|
+
property_name = children[0]
|
987
|
+
component = {
|
988
|
+
"type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_PROPERTY_NAME,
|
989
|
+
"name": property_name,
|
990
|
+
}
|
991
|
+
if len(children) > 1:
|
992
|
+
component["failSafe"] = True
|
993
|
+
return component
|
994
|
+
|
995
|
+
@staticmethod
|
996
|
+
def visit_reference_path_pointer(_, children: Any) -> viv_compiler.types.ReferencePathComponentPointer:
|
997
|
+
"""Visit a <reference_path_pointer> node."""
|
998
|
+
property_name = children[0]
|
999
|
+
component = {
|
1000
|
+
"type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_POINTER,
|
1001
|
+
"propertyName": property_name,
|
1002
|
+
}
|
1003
|
+
if len(children) > 1:
|
1004
|
+
component["failSafe"] = True
|
1005
|
+
return component
|
1006
|
+
|
1007
|
+
@staticmethod
|
1008
|
+
def visit_reference_path_lookup(_, children: Any) -> viv_compiler.types.ReferencePathComponentLookup:
|
1009
|
+
"""Visit a <reference_path_lookup> node."""
|
1010
|
+
key = children[0]
|
1011
|
+
component = {
|
1012
|
+
"type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_LOOKUP,
|
1013
|
+
"key": key,
|
1014
|
+
}
|
1015
|
+
if len(children) > 1:
|
1016
|
+
component["failSafe"] = True
|
1017
|
+
return component
|
1018
|
+
|
1019
|
+
@staticmethod
|
1020
|
+
def visit_role_unpacking(_, children: Any) -> viv_compiler.types.RoleUnpacking:
|
1021
|
+
"""Visit a <role_unpacking> node."""
|
1022
|
+
component = {"type": ExpressionDiscriminator.ROLE_UNPACKING, "value": children[0]}
|
1023
|
+
return component
|
1024
|
+
|
1025
|
+
@staticmethod
|
1026
|
+
def visit_relational_operator(_, children: Any) -> str:
|
1027
|
+
"""Visit a <relational_operator> node."""
|
1028
|
+
return children[0]
|
1029
|
+
|
1030
|
+
@staticmethod
|
1031
|
+
def visit_assignment_operator(_, children: Any) -> str:
|
1032
|
+
"""Visit a <assignment_operator> node."""
|
1033
|
+
return children[0]
|
1034
|
+
|
1035
|
+
@staticmethod
|
1036
|
+
def visit_negation(_, __: Any) -> str:
|
1037
|
+
"""Visit a <negation> node."""
|
1038
|
+
return "!"
|
1039
|
+
|
1040
|
+
@staticmethod
|
1041
|
+
def visit_list(_, children: Any) -> viv_compiler.types.ListField:
|
1042
|
+
"""Visit a <list> node."""
|
1043
|
+
component = {"type": ExpressionDiscriminator.LIST, "value": children}
|
1044
|
+
return component
|
1045
|
+
|
1046
|
+
@staticmethod
|
1047
|
+
def visit_chance_expression(_, children: Any) -> viv_compiler.types.ChanceExpression:
|
1048
|
+
"""Visit a <chance_expression> node."""
|
1049
|
+
# Children may include a leading '-' and the numeric fragments; '%' is a literal in the grammar
|
1050
|
+
chance_str = ''.join(str(tok) for tok in children if tok != '%')
|
1051
|
+
if not chance_str or chance_str == '-':
|
1052
|
+
chance = -1 # signal for later validation
|
1053
|
+
else:
|
1054
|
+
chance = float(chance_str) / 100
|
1055
|
+
component = {"type": ExpressionDiscriminator.CHANCE_EXPRESSION, "value": chance}
|
1056
|
+
return component
|
1057
|
+
|
1058
|
+
@staticmethod
|
1059
|
+
def visit_literal(_, children: Any) -> viv_compiler.types.Expression:
|
1060
|
+
"""Visit a <literal> node."""
|
1061
|
+
return children[0]
|
1062
|
+
|
1063
|
+
@staticmethod
|
1064
|
+
def visit_enum(_, children: Any) -> viv_compiler.types.Enum:
|
1065
|
+
"""Visit an <enum> node."""
|
1066
|
+
scaled = "#" in children # As opposed to ##, which yields an unscaled enum
|
1067
|
+
additive_inverse_present = "-" in children
|
1068
|
+
component = {
|
1069
|
+
"type": ExpressionDiscriminator.ENUM,
|
1070
|
+
"value": {
|
1071
|
+
"name": children[-1],
|
1072
|
+
"scaled": scaled,
|
1073
|
+
"minus": additive_inverse_present
|
1074
|
+
}
|
1075
|
+
}
|
1076
|
+
return component
|
1077
|
+
|
1078
|
+
@staticmethod
|
1079
|
+
def visit_string(_, children: Any) -> viv_compiler.types.TemplateStringField | viv_compiler.types.StringField:
|
1080
|
+
"""Visit a <string> node."""
|
1081
|
+
string = [] # A mix of strings and string-producing references
|
1082
|
+
injected_string = False
|
1083
|
+
partial_string = ""
|
1084
|
+
for child in children:
|
1085
|
+
if isinstance(child, str):
|
1086
|
+
partial_string += child
|
1087
|
+
else: # A reference, making this an injected string
|
1088
|
+
injected_string = True
|
1089
|
+
if partial_string:
|
1090
|
+
string.append(partial_string)
|
1091
|
+
partial_string = " " # Needed because the parser gobbles whitespace between nonterminals
|
1092
|
+
string.append(child)
|
1093
|
+
if partial_string.strip():
|
1094
|
+
string.append(partial_string)
|
1095
|
+
if injected_string:
|
1096
|
+
component = {"type": ExpressionDiscriminator.TEMPLATE_STRING, "value": string}
|
1097
|
+
else:
|
1098
|
+
component = {"type": ExpressionDiscriminator.STRING, "value": partial_string}
|
1099
|
+
return component
|
1100
|
+
|
1101
|
+
@staticmethod
|
1102
|
+
def visit_character(node: Any, _) -> str:
|
1103
|
+
"""Visit a <character> node."""
|
1104
|
+
return str(node)
|
1105
|
+
|
1106
|
+
@staticmethod
|
1107
|
+
def visit_space(node: Any, _) -> str:
|
1108
|
+
"""Visit a <space> node."""
|
1109
|
+
return str(node)
|
1110
|
+
|
1111
|
+
@staticmethod
|
1112
|
+
def visit_number(_, children: Any) -> viv_compiler.types.FloatField | viv_compiler.types.IntField:
|
1113
|
+
"""Visit a <number> node."""
|
1114
|
+
number_str = ''.join(children)
|
1115
|
+
if any(child for child in children if child == '.'):
|
1116
|
+
component = {"type": ExpressionDiscriminator.FLOAT, "value": float(number_str)}
|
1117
|
+
else:
|
1118
|
+
component = {"type": ExpressionDiscriminator.INT, "value": int(number_str)}
|
1119
|
+
return component
|
1120
|
+
|
1121
|
+
@staticmethod
|
1122
|
+
def visit_number_word(_, children: Any) -> viv_compiler.types.IntField:
|
1123
|
+
"""Visit a <number_word> node."""
|
1124
|
+
number_word_to_value = {
|
1125
|
+
"one": 1, "two": 2, "three": 3, "four": 4, "five": 5, "six": 6, "seven": 7,
|
1126
|
+
"eight": 8, "nine": 9, "ten": 10, "eleven": 11, "twelve": 12,
|
1127
|
+
}
|
1128
|
+
component = {"type": ExpressionDiscriminator.INT, "value": number_word_to_value[children[0]]}
|
1129
|
+
return component
|
1130
|
+
|
1131
|
+
@staticmethod
|
1132
|
+
def visit_boolean(node: Any, __: Any) -> viv_compiler.types.BoolField:
|
1133
|
+
"""Visit a <boolean> node."""
|
1134
|
+
component = {"type": ExpressionDiscriminator.BOOL, "value": True if node.value == "true" else False}
|
1135
|
+
return component
|
1136
|
+
|
1137
|
+
@staticmethod
|
1138
|
+
def visit_null_type(_, __: Any) -> viv_compiler.types.NullField:
|
1139
|
+
"""Visit a <null_type> node."""
|
1140
|
+
component = {"type": ExpressionDiscriminator.NULL_TYPE, "value": None}
|
1141
|
+
return component
|
1142
|
+
|
1143
|
+
@staticmethod
|
1144
|
+
def visit_tag(_, children: Any) -> viv_compiler.types.StringField:
|
1145
|
+
"""Visit a <tag> node."""
|
1146
|
+
component = {"type": ExpressionDiscriminator.STRING, "value": children[0]}
|
1147
|
+
return component
|
1148
|
+
|
1149
|
+
@staticmethod
|
1150
|
+
def visit_eval_fail_safe_marker(_, __: Any) -> viv_compiler.types.EvalFailSafeField:
|
1151
|
+
"""Visit an <eval_fail_safe_marker> node."""
|
1152
|
+
component = {"type": ExpressionDiscriminator.EVAL_FAIL_SAFE}
|
1153
|
+
return component
|
1154
|
+
|
1155
|
+
@staticmethod
|
1156
|
+
def visit_trope(_, children: Any) -> dict[str, Any]:
|
1157
|
+
"""Visit a <trope> node."""
|
1158
|
+
if len(children) == 3:
|
1159
|
+
name, role_names, conditions = children
|
1160
|
+
else:
|
1161
|
+
name, conditions = children
|
1162
|
+
role_names = []
|
1163
|
+
component_value = {"name": name, "params": role_names, "conditions": conditions}
|
1164
|
+
component = {"type": "trope", "value": component_value}
|
1165
|
+
return component
|
1166
|
+
|
1167
|
+
@staticmethod
|
1168
|
+
def visit_trope_role_names(_, children: Any) -> list[str]:
|
1169
|
+
"""Visit a <trope_role_names> node."""
|
1170
|
+
return children
|
1171
|
+
|
1172
|
+
@staticmethod
|
1173
|
+
def visit_trope_fit_expression(_, children: Any) -> viv_compiler.types.TropeFitExpression:
|
1174
|
+
"""Visit a <trope_fit_expression> node."""
|
1175
|
+
arguments, trope_name = children
|
1176
|
+
component = {
|
1177
|
+
"type": ExpressionDiscriminator.TROPE_FIT_EXPRESSION,
|
1178
|
+
"value": {
|
1179
|
+
"tropeName": trope_name,
|
1180
|
+
"args": arguments
|
1181
|
+
}
|
1182
|
+
}
|
1183
|
+
return component
|
1184
|
+
|
1185
|
+
@staticmethod
|
1186
|
+
def visit_trope_fit_expression_args(_, children: Any) -> list[viv_compiler.types.Expression]:
|
1187
|
+
"""Visit a <trope_fit_expression_args> node."""
|
1188
|
+
return children
|