viv-compiler 0.1.1__py3-none-any.whl → 0.1.2__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/{utils/_version.py → _version.py} +1 -1
- viv_compiler/cli.py +5 -4
- viv_compiler/config/config.py +17 -18
- viv_compiler/core/core.py +2 -2
- viv_compiler/core/metadata.py +8 -6
- viv_compiler/core/validation.py +202 -64
- viv_compiler/core/visitor.py +143 -128
- viv_compiler/grammar/viv.peg +453 -226
- viv_compiler/types/content_public_schemas.py +32 -27
- viv_compiler/types/dsl_public_schemas.py +75 -79
- viv_compiler/utils/utils.py +93 -33
- {viv_compiler-0.1.1.dist-info → viv_compiler-0.1.2.dist-info}/METADATA +14 -14
- {viv_compiler-0.1.1.dist-info → viv_compiler-0.1.2.dist-info}/RECORD +17 -17
- {viv_compiler-0.1.1.dist-info → viv_compiler-0.1.2.dist-info}/WHEEL +0 -0
- {viv_compiler-0.1.1.dist-info → viv_compiler-0.1.2.dist-info}/entry_points.txt +0 -0
- {viv_compiler-0.1.1.dist-info → viv_compiler-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {viv_compiler-0.1.1.dist-info → viv_compiler-0.1.2.dist-info}/top_level.txt +0 -0
viv_compiler/core/visitor.py
CHANGED
@@ -9,8 +9,8 @@ import copy
|
|
9
9
|
import arpeggio
|
10
10
|
import viv_compiler.config
|
11
11
|
import viv_compiler.types
|
12
|
-
from viv_compiler.types import ExpressionDiscriminator, ReferencePathComponentDiscriminator
|
13
|
-
from typing import Any, Optional
|
12
|
+
from viv_compiler.types import ExpressionDiscriminator, ReferencePathComponentDiscriminator, LocalVariable
|
13
|
+
from typing import Any, Optional
|
14
14
|
|
15
15
|
|
16
16
|
class Visitor(arpeggio.PTNodeVisitor):
|
@@ -21,24 +21,47 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
21
21
|
"""Visit the <file> node, i.e., the root of the parse tree."""
|
22
22
|
ast = {"_includes": [], "tropes": [], "actions": []}
|
23
23
|
for child in children:
|
24
|
-
if child["
|
24
|
+
if child["_type"] == 'include':
|
25
25
|
ast['_includes'].append(child['value'])
|
26
|
-
elif child["
|
26
|
+
elif child["_type"] == 'action':
|
27
27
|
ast['actions'].append(child['value'])
|
28
|
-
elif child["
|
28
|
+
elif child["_type"] == 'trope':
|
29
29
|
ast['tropes'].append(child['value'])
|
30
30
|
return ast
|
31
31
|
|
32
32
|
@staticmethod
|
33
33
|
def visit_include(_, children: Any) -> dict[str, Any]:
|
34
34
|
"""Visit an <include> node."""
|
35
|
-
return {"
|
35
|
+
return {"_type": "include", "value": children[0]}
|
36
36
|
|
37
37
|
@staticmethod
|
38
38
|
def visit_filename(_, children: Any) -> str:
|
39
39
|
"""Visit a <filename> node."""
|
40
40
|
return ''.join(children)
|
41
41
|
|
42
|
+
@staticmethod
|
43
|
+
def visit_trope(_, children: Any) -> dict[str, Any]:
|
44
|
+
"""Visit a <trope> node."""
|
45
|
+
if len(children) == 3:
|
46
|
+
name, params, conditions = children
|
47
|
+
else:
|
48
|
+
name, conditions = children
|
49
|
+
params = []
|
50
|
+
trope_definition = {"name": name, "params": params, "conditions": conditions}
|
51
|
+
return {"_type": "trope", "value": trope_definition}
|
52
|
+
|
53
|
+
@staticmethod
|
54
|
+
def visit_trope_params(_, children: Any) -> list[str]:
|
55
|
+
"""Visit a <trope_params> node."""
|
56
|
+
return children
|
57
|
+
|
58
|
+
@staticmethod
|
59
|
+
def visit_trope_param(_, children: Any) -> viv_compiler.types.TropeParam:
|
60
|
+
"""Visit a <trope_param> node."""
|
61
|
+
role_type, name = children
|
62
|
+
component = {"name": name, "isEntityParam": role_type["_is_entity"]}
|
63
|
+
return component
|
64
|
+
|
42
65
|
@staticmethod
|
43
66
|
def visit_action(_, children: Any) -> dict[str, Any]:
|
44
67
|
"""Visit an <action> node."""
|
@@ -46,7 +69,7 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
46
69
|
header, body = children
|
47
70
|
action_definition.update(header)
|
48
71
|
action_definition.update(body)
|
49
|
-
return {"
|
72
|
+
return {"_type": "action", "value": action_definition}
|
50
73
|
|
51
74
|
@staticmethod
|
52
75
|
def visit_action_header(_, children: Any) -> dict[str, Any]:
|
@@ -134,6 +157,12 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
134
157
|
component = {"type": ExpressionDiscriminator.LIST, "value": children}
|
135
158
|
return component
|
136
159
|
|
160
|
+
@staticmethod
|
161
|
+
def visit_tag(_, children: Any) -> viv_compiler.types.StringField:
|
162
|
+
"""Visit a <tag> node."""
|
163
|
+
component = {"type": ExpressionDiscriminator.STRING, "value": children[0]}
|
164
|
+
return component
|
165
|
+
|
137
166
|
@staticmethod
|
138
167
|
def visit_roles(_, children: Any) -> dict[str, Any]:
|
139
168
|
"""Visit a <roles> node."""
|
@@ -170,12 +199,19 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
170
199
|
"precast": False,
|
171
200
|
"build": False,
|
172
201
|
}
|
202
|
+
marked_entity_role = False
|
173
203
|
for child in children:
|
174
|
-
if
|
204
|
+
if "_is_entity" in child:
|
205
|
+
marked_entity_role = child["_is_entity"]
|
206
|
+
elif "min" in child:
|
175
207
|
component['min'] = child['min']['value']
|
176
208
|
component['max'] = child['max']['value']
|
177
209
|
else:
|
178
210
|
component.update(child)
|
211
|
+
if marked_entity_role and component['symbol']:
|
212
|
+
raise ValueError(f"Role '@{component['name']}' has entity prefix but is marked symbol")
|
213
|
+
if not marked_entity_role and not component['symbol']:
|
214
|
+
raise ValueError(f"Role '&{component['name']}' has symbol prefix but is not marked symbol")
|
179
215
|
if not (component['item'] or component['action'] or component['location'] or component['symbol']):
|
180
216
|
component['character'] = True
|
181
217
|
if component['mean'] is not None:
|
@@ -205,6 +241,11 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
205
241
|
return {"min": children[0], "max": children[0]}
|
206
242
|
return {"min": children[0], "max": children[1]}
|
207
243
|
|
244
|
+
@staticmethod
|
245
|
+
def visit_binding_type(_, children: Any) -> dict[str, bool]:
|
246
|
+
"""Visit a <binding_type> node."""
|
247
|
+
return {"_is_entity": children[0] == "@"}
|
248
|
+
|
208
249
|
@staticmethod
|
209
250
|
def visit_role_name(_, children: Any) -> dict[str, str]:
|
210
251
|
"""Visit a <role_name> node."""
|
@@ -347,31 +388,6 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
347
388
|
# Move the 'bindings' field from the options component to the top level of the reaction value
|
348
389
|
reaction_object['bindings'] = reaction_object['options']['bindings']
|
349
390
|
del reaction_object['options']['bindings']
|
350
|
-
# Validate the shape of the reaction value
|
351
|
-
def _is_reaction_value(obj) -> "TypeGuard[viv_compiler.types.ReactionValue]":
|
352
|
-
if not isinstance(obj, dict):
|
353
|
-
return False
|
354
|
-
if not {"actionName", "bindings", "options"} <= set(obj):
|
355
|
-
return False
|
356
|
-
if not isinstance(obj.get("actionName"), str):
|
357
|
-
return False
|
358
|
-
bindings = obj.get("bindings")
|
359
|
-
if not isinstance(bindings, list):
|
360
|
-
return False
|
361
|
-
for b in bindings:
|
362
|
-
if not (isinstance(b, dict) and b.get("type") == "binding" and isinstance(b.get("value"), dict)):
|
363
|
-
return False
|
364
|
-
v = b["value"]
|
365
|
-
if not (isinstance(v.get("role"), str) and "entity" in v):
|
366
|
-
return False
|
367
|
-
options = obj.get("options")
|
368
|
-
if not isinstance(options, dict):
|
369
|
-
return False
|
370
|
-
if "bindings" in options:
|
371
|
-
return False
|
372
|
-
return True
|
373
|
-
if not _is_reaction_value(reaction_object):
|
374
|
-
raise ValueError(f"Malformed reaction generated by Visitor: {reaction_object!r}")
|
375
391
|
# Package up the component and return it
|
376
392
|
reaction_value: viv_compiler.types.ReactionValue = reaction_object # type: ignore[assignment]
|
377
393
|
component: viv_compiler.types.Reaction = {"type": ExpressionDiscriminator.REACTION, "value": reaction_value}
|
@@ -388,14 +404,14 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
388
404
|
return {"bindings": children}
|
389
405
|
|
390
406
|
@staticmethod
|
391
|
-
def visit_binding(_, children: Any) -> viv_compiler.types.
|
407
|
+
def visit_binding(_, children: Any) -> viv_compiler.types.ReactionRoleBindings:
|
392
408
|
"""Visit a <binding> node."""
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
409
|
+
role_type, role_name, candidate_expression = children
|
410
|
+
component = {
|
411
|
+
"role": role_name,
|
412
|
+
"isEntityRole": role_type["_is_entity"],
|
413
|
+
"candidates": candidate_expression,
|
414
|
+
}
|
399
415
|
return component
|
400
416
|
|
401
417
|
@staticmethod
|
@@ -524,21 +540,20 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
524
540
|
@staticmethod
|
525
541
|
def visit_time(_, children: Any) -> dict[str, Any]:
|
526
542
|
"""Visit a <time> node."""
|
527
|
-
period = children[-1] # AM or PM
|
528
543
|
raw_hour = int(children[0])
|
529
|
-
|
530
|
-
if
|
531
|
-
|
532
|
-
|
533
|
-
"hour
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
544
|
+
minute = int(children[1]) if len(children) > 1 and children[1].isdigit() else 0
|
545
|
+
if children[-1].upper() in ("AM", "PM"): # 12-hour form
|
546
|
+
period = children[-1].upper()
|
547
|
+
if not 1 <= raw_hour <= 12:
|
548
|
+
raise ValueError(f"Invalid hour in 12-hour time: {raw_hour}")
|
549
|
+
hour = (raw_hour % 12) + (12 if period == "PM" else 0)
|
550
|
+
else: # 24-hour form
|
551
|
+
if not 0 <= raw_hour <= 23:
|
552
|
+
raise ValueError(f"Invalid hour in 24-hour time: {raw_hour}")
|
553
|
+
hour = raw_hour
|
554
|
+
if not 0 <= minute <= 59:
|
555
|
+
raise ValueError(f"Invalid minute in time: {minute}")
|
556
|
+
component = {"type": "time", "hour": hour, "minute": minute}
|
542
557
|
return component
|
543
558
|
|
544
559
|
@staticmethod
|
@@ -559,12 +574,15 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
559
574
|
if children[0] == "join":
|
560
575
|
join_saliences = True
|
561
576
|
children = children[1:]
|
562
|
-
|
563
|
-
|
577
|
+
local_variable, body = None, []
|
578
|
+
if len(children) == 1: # Default only
|
579
|
+
default_value_expression = children[0]
|
580
|
+
else:
|
581
|
+
local_variable, default_value_expression, body = children
|
564
582
|
component = {
|
565
583
|
"saliences": {
|
566
584
|
"default": default_value_expression,
|
567
|
-
"variable":
|
585
|
+
"variable": local_variable,
|
568
586
|
"body": body,
|
569
587
|
}
|
570
588
|
}
|
@@ -589,12 +607,15 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
589
607
|
if children[0] == "join":
|
590
608
|
join_associations = True
|
591
609
|
children = children[1:]
|
592
|
-
|
593
|
-
|
610
|
+
local_variable, body = None, []
|
611
|
+
if len(children) == 1: # Default only
|
612
|
+
default_value_expression = children[0]
|
613
|
+
else:
|
614
|
+
local_variable, default_value_expression, body = children
|
594
615
|
component = {
|
595
616
|
"associations": {
|
596
617
|
"default": default_value_expression,
|
597
|
-
"variable":
|
618
|
+
"variable": local_variable,
|
598
619
|
"body": body,
|
599
620
|
}
|
600
621
|
}
|
@@ -757,10 +778,20 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
757
778
|
@staticmethod
|
758
779
|
def visit_loop(_, children: Any) -> viv_compiler.types.Loop:
|
759
780
|
"""Visit a <loop> node."""
|
760
|
-
iterable_reference,
|
781
|
+
iterable_reference, loop_variable, loop_body = children
|
761
782
|
component = {
|
762
783
|
"type": ExpressionDiscriminator.LOOP,
|
763
|
-
"value": {"iterable": iterable_reference, "variable":
|
784
|
+
"value": {"iterable": iterable_reference, "variable": loop_variable, "body": loop_body}
|
785
|
+
}
|
786
|
+
return component
|
787
|
+
|
788
|
+
@staticmethod
|
789
|
+
def visit_local_variable(_, children: Any) -> LocalVariable:
|
790
|
+
"""Visit a <local_variable> node."""
|
791
|
+
_, binding_type, name = children
|
792
|
+
component = {
|
793
|
+
"name": name,
|
794
|
+
"isEntityVariable": binding_type["_is_entity"]
|
764
795
|
}
|
765
796
|
return component
|
766
797
|
|
@@ -937,69 +968,76 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
937
968
|
return children
|
938
969
|
|
939
970
|
@staticmethod
|
940
|
-
def visit_reference(
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
"""Visit a <role_anchored_reference> node."""
|
948
|
-
# Determine the anchor role name
|
949
|
-
if "globalVariable" in children[0]:
|
950
|
-
# If the reference is anchored in a global variable, expand the `$` anchor. `$` is really
|
951
|
-
# just syntactic sugar for `@this.scratch.`, which means any reference anchored in a global
|
971
|
+
def visit_reference(_, children: Any) -> viv_compiler.types.EntityReference | viv_compiler.types.SymbolReference:
|
972
|
+
"""Visit a <reference> node."""
|
973
|
+
is_symbol_reference = False
|
974
|
+
anchor_is_local_variable = False
|
975
|
+
if children[0] == "$":
|
976
|
+
# If the reference is anchored in a scratch variable, expand the `$` anchor. `$` is really
|
977
|
+
# just syntactic sugar for `@this.scratch.`, which means any reference anchored in a scratch
|
952
978
|
# variable is in fact an entity reference anchored in a role name.
|
953
|
-
|
979
|
+
children = children[1:]
|
980
|
+
if children[0] == "&":
|
981
|
+
# We will ignore the symbol sigil here, because ultimately that refers to the type of
|
982
|
+
# the scratch variable, not the anchor, which is always entity data.
|
983
|
+
pass
|
984
|
+
children = children[1:]
|
985
|
+
anchor = viv_compiler.config.SCRATCH_VARIABLE_REFERENCE_ANCHOR
|
954
986
|
path = [
|
955
|
-
*viv_compiler.config.
|
987
|
+
*viv_compiler.config.SCRATCH_VARIABLE_REFERENCE_PATH_PREFIX,
|
956
988
|
{
|
957
989
|
"type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_PROPERTY_NAME,
|
958
|
-
"name": children[0]
|
990
|
+
"name": children[0],
|
959
991
|
}
|
960
992
|
]
|
961
993
|
path += children[1] if len(children) > 1 else []
|
962
|
-
|
963
|
-
|
994
|
+
elif children[0] == "_":
|
995
|
+
anchor_is_local_variable = True
|
996
|
+
children = children[1:]
|
997
|
+
if children[0] == "&":
|
998
|
+
is_symbol_reference = True
|
999
|
+
children = children[1:]
|
1000
|
+
anchor = children[0]
|
1001
|
+
path = children[1] if len(children) > 1 else []
|
1002
|
+
else: # Bare reference to a role, e.g., `@foo` or `&bar`
|
1003
|
+
if children[0] == "&":
|
1004
|
+
is_symbol_reference = True
|
1005
|
+
children = children[1:]
|
1006
|
+
anchor = children[0]
|
964
1007
|
path = children[1] if len(children) > 1 else []
|
1008
|
+
if is_symbol_reference:
|
1009
|
+
component_type = ExpressionDiscriminator.SYMBOL_REFERENCE
|
1010
|
+
else:
|
1011
|
+
component_type = ExpressionDiscriminator.ENTITY_REFERENCE
|
965
1012
|
component = {
|
966
|
-
"type":
|
1013
|
+
"type": component_type,
|
967
1014
|
"value": {
|
968
|
-
"
|
969
|
-
"
|
1015
|
+
"local": anchor_is_local_variable,
|
1016
|
+
"anchor": anchor,
|
1017
|
+
"path": path,
|
970
1018
|
}
|
971
1019
|
}
|
972
1020
|
return component
|
973
1021
|
|
974
1022
|
@staticmethod
|
975
|
-
def
|
976
|
-
"""Visit
|
977
|
-
|
978
|
-
path = children[1] if len(children) > 1 else []
|
979
|
-
component = {
|
980
|
-
"type": ExpressionDiscriminator.LOCAL_VARIABLE_REFERENCE,
|
981
|
-
"value": {
|
982
|
-
"anchor": anchor, # The name of the local variable anchoring this reference
|
983
|
-
"path": path, # Sequence of components constituting a property path
|
984
|
-
}
|
985
|
-
}
|
986
|
-
return component
|
1023
|
+
def visit_entity_sigil(node: Any, _) -> Any:
|
1024
|
+
"""Visit an <entity_sigil> node."""
|
1025
|
+
return node
|
987
1026
|
|
988
1027
|
@staticmethod
|
989
|
-
def
|
990
|
-
"""Visit a <
|
991
|
-
|
992
|
-
return {"name": name}
|
1028
|
+
def visit_symbol_sigil(node: Any, _) -> Any:
|
1029
|
+
"""Visit a <symbol_sigil> node."""
|
1030
|
+
return node
|
993
1031
|
|
994
1032
|
@staticmethod
|
995
|
-
def
|
996
|
-
"""Visit a <
|
997
|
-
return
|
1033
|
+
def visit_scratch_variable_sigil(node: Any, _) -> Any:
|
1034
|
+
"""Visit a <scratch_variable_sigil> node."""
|
1035
|
+
return node
|
998
1036
|
|
999
1037
|
@staticmethod
|
1000
|
-
def
|
1001
|
-
"""Visit a <
|
1002
|
-
return
|
1038
|
+
def visit_local_variable_sigil(node: Any, _) -> Any:
|
1039
|
+
"""Visit a <local_variable_sigil> node."""
|
1040
|
+
return node
|
1003
1041
|
|
1004
1042
|
@staticmethod
|
1005
1043
|
def visit_reference_path(_, children: Any) -> list[viv_compiler.types.ReferencePathComponent]:
|
@@ -1093,7 +1131,7 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
1093
1131
|
if len(children) > 1:
|
1094
1132
|
additive_inverse_present = children[0] == "-"
|
1095
1133
|
token = children[-1]
|
1096
|
-
unscaled = token[:2] == "##" # As opposed to '#', which yields
|
1134
|
+
unscaled = token[:2] == "##" # As opposed to '#', which yields a scaled enum
|
1097
1135
|
name = token.lstrip('#')
|
1098
1136
|
component = {
|
1099
1137
|
"type": ExpressionDiscriminator.ENUM,
|
@@ -1170,35 +1208,12 @@ class Visitor(arpeggio.PTNodeVisitor):
|
|
1170
1208
|
component = {"type": ExpressionDiscriminator.NULL_TYPE, "value": None}
|
1171
1209
|
return component
|
1172
1210
|
|
1173
|
-
@staticmethod
|
1174
|
-
def visit_tag(_, children: Any) -> viv_compiler.types.StringField:
|
1175
|
-
"""Visit a <tag> node."""
|
1176
|
-
component = {"type": ExpressionDiscriminator.STRING, "value": children[0]}
|
1177
|
-
return component
|
1178
|
-
|
1179
1211
|
@staticmethod
|
1180
1212
|
def visit_eval_fail_safe_marker(_, __: Any) -> viv_compiler.types.EvalFailSafeField:
|
1181
1213
|
"""Visit an <eval_fail_safe_marker> node."""
|
1182
1214
|
component = {"type": ExpressionDiscriminator.EVAL_FAIL_SAFE}
|
1183
1215
|
return component
|
1184
1216
|
|
1185
|
-
@staticmethod
|
1186
|
-
def visit_trope(_, children: Any) -> dict[str, Any]:
|
1187
|
-
"""Visit a <trope> node."""
|
1188
|
-
if len(children) == 3:
|
1189
|
-
name, role_names, conditions = children
|
1190
|
-
else:
|
1191
|
-
name, conditions = children
|
1192
|
-
role_names = []
|
1193
|
-
component_value = {"name": name, "params": role_names, "conditions": conditions}
|
1194
|
-
component = {"type": "trope", "value": component_value}
|
1195
|
-
return component
|
1196
|
-
|
1197
|
-
@staticmethod
|
1198
|
-
def visit_trope_role_names(_, children: Any) -> list[str]:
|
1199
|
-
"""Visit a <trope_role_names> node."""
|
1200
|
-
return children
|
1201
|
-
|
1202
1217
|
@staticmethod
|
1203
1218
|
def visit_trope_fit_expression(_, children: Any) -> viv_compiler.types.TropeFitExpression:
|
1204
1219
|
"""Visit a <trope_fit_expression> node."""
|