viv-compiler 0.1.0__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 +14 -12
- viv_compiler/config/config.py +17 -18
- viv_compiler/core/__init__.py +3 -3
- viv_compiler/core/core.py +7 -7
- viv_compiler/core/{importer.py → includes.py} +5 -5
- viv_compiler/core/metadata.py +71 -0
- viv_compiler/core/{postprocessor.py → postprocessing.py} +6 -32
- viv_compiler/core/{validator.py → validation.py} +203 -88
- viv_compiler/core/visitor.py +184 -139
- viv_compiler/grammar/viv.peg +450 -218
- viv_compiler/types/content_public_schemas.py +59 -31
- viv_compiler/types/dsl_public_schemas.py +84 -82
- viv_compiler/utils/utils.py +93 -33
- {viv_compiler-0.1.0.dist-info → viv_compiler-0.1.2.dist-info}/METADATA +120 -82
- viv_compiler-0.1.2.dist-info/RECORD +33 -0
- viv_compiler-0.1.0.dist-info/RECORD +0 -32
- {viv_compiler-0.1.0.dist-info → viv_compiler-0.1.2.dist-info}/WHEEL +0 -0
- {viv_compiler-0.1.0.dist-info → viv_compiler-0.1.2.dist-info}/entry_points.txt +0 -0
- {viv_compiler-0.1.0.dist-info → viv_compiler-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {viv_compiler-0.1.0.dist-info → viv_compiler-0.1.2.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
|
|
1
|
-
"""
|
1
|
+
"""Module that handles validation of preliminary action definitions and compiled content bundles.
|
2
2
|
|
3
3
|
The entrypoint functions are as follows:
|
4
4
|
* `validate_join_directives()`: Invoked prior to handling of action inheritance.
|
@@ -19,31 +19,8 @@ from importlib import import_module
|
|
19
19
|
from pydantic import TypeAdapter
|
20
20
|
|
21
21
|
|
22
|
-
# todo do we already ensure all references are anchored in a (valid) role name?
|
23
|
-
# todo warn or error if precondition references scaled enum?
|
24
|
-
# todo validate time of day to ensure 0-23 and 0-59
|
25
|
-
# todo technically you could inject major side effects (e.g. reaction queueing) into trope-fit expressions
|
26
|
-
# todo ensure closing time period is always later than opening time period
|
27
|
-
# todo ensure embargo is either permanent or has a declared time period
|
28
|
-
# todo don't allow reaction to precast same entity multiple times
|
29
|
-
# todo build roles:
|
30
|
-
# - they can cast exactly one entity, with no chance and no mean
|
31
|
-
# - no pool declarations
|
32
|
-
# - can never be precast
|
33
|
-
# - preconditions cannot reference build roles
|
34
|
-
# - don't allow them in embargoes (embargo would not be violable)
|
35
|
-
# todo apparently the grammar allows e.g. multiple roles fields in an action definition
|
36
|
-
# - test what happens (presumably last in wins)
|
37
|
-
# todo make sure times like 88:76 aren't allowed (grammar allows them)
|
38
|
-
# todo role renaming (make sure new name being duplicate of existing one is already caught)
|
39
|
-
# - just detect duplicate role names right away post-visitor
|
40
|
-
# todo prohibit entity references anchored in symbol roles
|
41
|
-
# - not ideal, but interpreter enforces this at runtime currently
|
42
|
-
# todo assignments only allowed in scratch and effects
|
43
|
-
|
44
|
-
|
45
22
|
def validate_join_directives(raw_action_definitions: list[viv_compiler.types.RawActionDefinition]) -> None:
|
46
|
-
"""
|
23
|
+
"""Make sure that the given action definition makes proper use of `join` directives.
|
47
24
|
|
48
25
|
If this validation check passes, the given action definitions are ready for inheritance to be handled.
|
49
26
|
|
@@ -88,10 +65,10 @@ def validate_preliminary_action_definitions(
|
|
88
65
|
Exception: At least one action definition did not pass validation.
|
89
66
|
"""
|
90
67
|
for action_definition in intermediate_action_definitions:
|
91
|
-
#
|
68
|
+
# Make sure that a 'roles' field is present
|
92
69
|
if "roles" not in action_definition:
|
93
70
|
raise KeyError(f"Action '{action_definition['name']}' is missing a 'roles' field, which is required")
|
94
|
-
#
|
71
|
+
# Make sure that there is a single initiator role. Note that during initial validation,
|
95
72
|
# the 'roles' field is still a list.
|
96
73
|
_detect_wrong_number_of_initiators(action_definition=action_definition)
|
97
74
|
# Detect duplicated role names
|
@@ -160,7 +137,9 @@ def _detect_reference_to_undefined_role(action_definition: viv_compiler.types.In
|
|
160
137
|
Exception: The action definition did not pass validation.
|
161
138
|
"""
|
162
139
|
# Retrieve the names of all defined roles ('roles' is still a list at this point in postprocessing)
|
163
|
-
all_defined_role_names =
|
140
|
+
all_defined_role_names = (
|
141
|
+
{role['name'] for role in action_definition['roles']} | viv_compiler.config.SPECIAL_ROLE_NAMES
|
142
|
+
)
|
164
143
|
# Validate report references
|
165
144
|
if action_definition["report"]:
|
166
145
|
for reference in viv_compiler.utils.get_all_referenced_roles(action_definition["report"]):
|
@@ -266,11 +245,25 @@ def _validate_trope_definitions(trope_definitions: dict[str, viv_compiler.types.
|
|
266
245
|
# Detect any reference to an undefined param
|
267
246
|
all_referenced_params = viv_compiler.utils.get_all_referenced_roles(ast_chunk=trope_definition)
|
268
247
|
for referenced_param in all_referenced_params:
|
269
|
-
if referenced_param not in trope_definition["params"]:
|
248
|
+
if referenced_param not in [trope_param["name"] for trope_param in trope_definition["params"]]:
|
270
249
|
raise KeyError(
|
271
250
|
f"Trope '{trope_definition['name']}' references undefined parameter: "
|
272
251
|
f"'{referenced_param}'"
|
273
252
|
)
|
253
|
+
# Detect assignments, which are not allowed in trope bodies
|
254
|
+
assignments_in_trope_body = viv_compiler.utils.get_all_expressions_of_type(
|
255
|
+
expression_type=ExpressionDiscriminator.ASSIGNMENT,
|
256
|
+
ast_chunk=trope_definition
|
257
|
+
)
|
258
|
+
if assignments_in_trope_body:
|
259
|
+
raise ValueError(f"Trope '{trope_definition['name']}' has assignment in body (not allowed)")
|
260
|
+
# Detect reactions, which are not allowed in trope bodies
|
261
|
+
reactions_in_trope_body = viv_compiler.utils.get_all_expressions_of_type(
|
262
|
+
expression_type=ExpressionDiscriminator.REACTION,
|
263
|
+
ast_chunk=trope_definition
|
264
|
+
)
|
265
|
+
if reactions_in_trope_body:
|
266
|
+
raise ValueError(f"Trope '{trope_definition['name']}' has reaction in body (not allowed)")
|
274
267
|
|
275
268
|
|
276
269
|
def _validate_action_definitions(
|
@@ -302,7 +295,10 @@ def _validate_action_definitions(
|
|
302
295
|
_validate_action_effects(action_definition=action_definition)
|
303
296
|
# Validate reactions
|
304
297
|
_validate_action_reactions(action_definition=action_definition, all_action_definitions=action_definitions)
|
298
|
+
# Validate saliences
|
299
|
+
_validate_action_saliences(action_definition=action_definition)
|
305
300
|
# Validate associations
|
301
|
+
_validate_action_associations(action_definition=action_definition)
|
306
302
|
# Validate trope-fit expressions
|
307
303
|
_validate_action_trope_fit_expressions(
|
308
304
|
action_definition=action_definition,
|
@@ -314,6 +310,8 @@ def _validate_action_definitions(
|
|
314
310
|
_validate_action_loops(action_definition=action_definition)
|
315
311
|
# Validate assignments
|
316
312
|
_validate_action_assignments(action_definition=action_definition)
|
313
|
+
# Validate references to scratch variables
|
314
|
+
_validate_scratch_variable_references(action_definition=action_definition)
|
317
315
|
# Validate negated expressions
|
318
316
|
_validate_negated_expressions(action_definition=action_definition)
|
319
317
|
# Validate chance expressions
|
@@ -353,13 +351,13 @@ def _validate_action_roles(action_definition: viv_compiler.types.ActionDefinitio
|
|
353
351
|
"""
|
354
352
|
# Validate the initiator role
|
355
353
|
_validate_action_initiator_role(action_definition=action_definition)
|
356
|
-
#
|
354
|
+
# Make sure all roles have proper 'min' and 'max' values
|
357
355
|
_validate_action_role_min_and_max_values(action_definition=action_definition)
|
358
|
-
#
|
356
|
+
# Make sure all roles have proper 'chance' and 'mean' values
|
359
357
|
_validate_action_role_chance_and_mean_values(action_definition=action_definition)
|
360
|
-
#
|
358
|
+
# Make sure all roles with binding pools have proper ones
|
361
359
|
_validate_action_role_pool_directives(action_definition=action_definition)
|
362
|
-
#
|
360
|
+
# Make sure that the 'precast' label is only used if this is a special action
|
363
361
|
_validate_action_role_precast_label_usages(action_definition=action_definition)
|
364
362
|
|
365
363
|
|
@@ -377,13 +375,13 @@ def _validate_action_initiator_role(action_definition: viv_compiler.types.Action
|
|
377
375
|
"""
|
378
376
|
# Retrieve the definition for the action's initiator role
|
379
377
|
initiator_role_definition = action_definition['initiator']
|
380
|
-
#
|
378
|
+
# Make sure that the initiator role has no pool directive
|
381
379
|
if initiator_role_definition["pool"]:
|
382
380
|
raise ValueError(
|
383
381
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
384
382
|
f"with a pool directive, which is not allowed for initiator roles"
|
385
383
|
)
|
386
|
-
#
|
384
|
+
# Make sure that the initiator role casts exactly one entity
|
387
385
|
if initiator_role_definition['min'] != 1:
|
388
386
|
raise ValueError(
|
389
387
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
@@ -394,13 +392,13 @@ def _validate_action_initiator_role(action_definition: viv_compiler.types.Action
|
|
394
392
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
395
393
|
f"with max other than 1 (there must be a single initiator)"
|
396
394
|
)
|
397
|
-
#
|
395
|
+
# Make sure that the initiator role has no specified binding mean
|
398
396
|
if initiator_role_definition['mean'] is not None:
|
399
397
|
raise ValueError(
|
400
398
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
401
399
|
f"with declared casting mean (there must be a single initiator)"
|
402
400
|
)
|
403
|
-
#
|
401
|
+
# Make sure that the initiator role has no specified binding chance
|
404
402
|
if initiator_role_definition['chance'] is not None:
|
405
403
|
raise ValueError(
|
406
404
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
@@ -502,7 +500,7 @@ def _validate_action_role_pool_directives(action_definition: viv_compiler.types.
|
|
502
500
|
f"Action '{action_definition['name']}' has role '{role_definition['name']}' "
|
503
501
|
f"that requires a pool declaration but does not have one"
|
504
502
|
)
|
505
|
-
#
|
503
|
+
# Make sure that all pool directives reference at most one other (valid) role
|
506
504
|
if role_definition["pool"]:
|
507
505
|
all_pool_references = viv_compiler.utils.get_all_referenced_roles(role_definition["pool"])
|
508
506
|
if len(all_pool_references) > 1:
|
@@ -627,25 +625,21 @@ def _validate_action_reactions(
|
|
627
625
|
Raises:
|
628
626
|
Exception: The action definition did not pass validation.
|
629
627
|
"""
|
630
|
-
#
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
for field_name, field_value in fields_to_check:
|
637
|
-
reactions_in_this_field = viv_compiler.utils.get_all_expressions_of_type(
|
638
|
-
expression_type="reaction",
|
639
|
-
ast_chunk=field_value
|
640
|
-
)
|
641
|
-
if reactions_in_this_field:
|
642
|
-
raise ValueError(
|
643
|
-
f"Action '{action_definition['name']}' has reaction in '{field_name}' field "
|
644
|
-
f"(only allowed in 'reactions' field)"
|
628
|
+
# Make sure that all reactions are housed in the proper fields
|
629
|
+
for field_name, field_value in action_definition.items():
|
630
|
+
if field_name not in viv_compiler.config.ACTION_FIELDS_PERMITTING_REACTIONS:
|
631
|
+
reactions_in_this_field = viv_compiler.utils.get_all_expressions_of_type(
|
632
|
+
expression_type=ExpressionDiscriminator.REACTION,
|
633
|
+
ast_chunk=field_value
|
645
634
|
)
|
635
|
+
if reactions_in_this_field:
|
636
|
+
raise ValueError(
|
637
|
+
f"Action '{action_definition['name']}' has reaction in '{field_name}' field "
|
638
|
+
f"(only allowed in 'reactions' field)"
|
639
|
+
)
|
646
640
|
# Collect all reactions
|
647
641
|
all_reactions = viv_compiler.utils.get_all_expressions_of_type(
|
648
|
-
expression_type=
|
642
|
+
expression_type=ExpressionDiscriminator.REACTION,
|
649
643
|
ast_chunk=action_definition['reactions']
|
650
644
|
)
|
651
645
|
# Validate each reaction in turn
|
@@ -663,7 +657,7 @@ def _validate_action_reactions(
|
|
663
657
|
queued_action_initiator_role_name = queued_action_definition['initiator']['name']
|
664
658
|
initiator_is_bound = False
|
665
659
|
for binding in reaction['bindings']:
|
666
|
-
if binding['
|
660
|
+
if binding['role'] == queued_action_initiator_role_name:
|
667
661
|
initiator_is_bound = True
|
668
662
|
break
|
669
663
|
if not initiator_is_bound:
|
@@ -674,12 +668,21 @@ def _validate_action_reactions(
|
|
674
668
|
# Make sure the reaction references only roles defined in the queued actions
|
675
669
|
all_queued_action_role_names = queued_action_definition['roles'].keys()
|
676
670
|
for binding in reaction['bindings']:
|
677
|
-
bound_role_name = binding['
|
671
|
+
bound_role_name = binding['role']
|
678
672
|
if bound_role_name not in all_queued_action_role_names:
|
679
673
|
raise KeyError(
|
680
674
|
f"Action '{action_definition['name']}' has '{queued_action_name}' reaction that "
|
681
675
|
f"references a role that is undefined for '{queued_action_name}': '{bound_role_name}'"
|
682
676
|
)
|
677
|
+
# Make sure that no role appears multiple times in the bindings
|
678
|
+
roles_already_precast = set()
|
679
|
+
for bindings in reaction['bindings']:
|
680
|
+
if bindings['role'] in roles_already_precast:
|
681
|
+
raise ValueError(
|
682
|
+
f"Action '{action_definition['name']}' has '{queued_action_name}' reaction that "
|
683
|
+
f"includes role '{queued_action_initiator_role_name}' in bindings more than once"
|
684
|
+
)
|
685
|
+
roles_already_precast.add(bindings['role'])
|
683
686
|
# Make sure the reaction precasts all 'precast' roles
|
684
687
|
for role_object in queued_action_definition['roles'].values():
|
685
688
|
if not role_object['precast']:
|
@@ -687,7 +690,7 @@ def _validate_action_reactions(
|
|
687
690
|
precast_role_name = role_object['name']
|
688
691
|
role_is_precast = False
|
689
692
|
for binding in reaction['bindings']:
|
690
|
-
bound_role_name = binding['
|
693
|
+
bound_role_name = binding['role']
|
691
694
|
if bound_role_name == precast_role_name:
|
692
695
|
role_is_precast = True
|
693
696
|
break
|
@@ -698,6 +701,50 @@ def _validate_action_reactions(
|
|
698
701
|
)
|
699
702
|
|
700
703
|
|
704
|
+
def _validate_action_saliences(action_definition: viv_compiler.types.ActionDefinition) -> None:
|
705
|
+
"""Validate the given action definition's 'saliences' field.
|
706
|
+
|
707
|
+
Args:
|
708
|
+
action_definition: An action definition from a compiled content bundle.
|
709
|
+
|
710
|
+
Returns:
|
711
|
+
None.
|
712
|
+
|
713
|
+
Raises:
|
714
|
+
Exception: The action definition did not pass validation.
|
715
|
+
"""
|
716
|
+
# Detect cases of a saliences variable shadowing the name of a role from the same action
|
717
|
+
all_role_names = viv_compiler.utils.get_all_role_names(action_definition=action_definition)
|
718
|
+
if action_definition['saliences']['variable']:
|
719
|
+
if action_definition['saliences']['variable']['name'] in all_role_names:
|
720
|
+
raise ValueError(
|
721
|
+
f"Action '{action_definition['name']}' has 'saliences' variable name that shadows role name "
|
722
|
+
f"'{action_definition['saliences']['variable']['name']}' (this is not allowed)"
|
723
|
+
)
|
724
|
+
|
725
|
+
|
726
|
+
def _validate_action_associations(action_definition: viv_compiler.types.ActionDefinition) -> None:
|
727
|
+
"""Validate the given action definition's 'associations' field.
|
728
|
+
|
729
|
+
Args:
|
730
|
+
action_definition: An action definition from a compiled content bundle.
|
731
|
+
|
732
|
+
Returns:
|
733
|
+
None.
|
734
|
+
|
735
|
+
Raises:
|
736
|
+
Exception: The action definition did not pass validation.
|
737
|
+
"""
|
738
|
+
# Detect cases of an associations variable shadowing the name of a role from the same action
|
739
|
+
all_role_names = viv_compiler.utils.get_all_role_names(action_definition=action_definition)
|
740
|
+
if action_definition['associations']['variable']:
|
741
|
+
if action_definition['associations']['variable']['name'] in all_role_names:
|
742
|
+
raise ValueError(
|
743
|
+
f"Action '{action_definition['name']}' has 'associations' variable name that shadows role name "
|
744
|
+
f"'{action_definition['associations']['variable']['name']}' (this is not allowed)"
|
745
|
+
)
|
746
|
+
|
747
|
+
|
701
748
|
def _validate_action_trope_fit_expressions(
|
702
749
|
action_definition: viv_compiler.types.ActionDefinition,
|
703
750
|
trope_definitions: dict[str, TropeDefinition]
|
@@ -715,7 +762,7 @@ def _validate_action_trope_fit_expressions(
|
|
715
762
|
Exception: The action definition did not pass validation.
|
716
763
|
"""
|
717
764
|
all_trope_fit_expressions = viv_compiler.utils.get_all_expressions_of_type(
|
718
|
-
expression_type=
|
765
|
+
expression_type=ExpressionDiscriminator.TROPE_FIT_EXPRESSION,
|
719
766
|
ast_chunk=action_definition
|
720
767
|
)
|
721
768
|
for trope_fit_expression in all_trope_fit_expressions:
|
@@ -748,21 +795,32 @@ def _validate_action_role_unpackings(action_definition: viv_compiler.types.Actio
|
|
748
795
|
Raises:
|
749
796
|
Exception: The action definition did not pass validation.
|
750
797
|
"""
|
751
|
-
#
|
798
|
+
# Retrieve all unpacked role names
|
752
799
|
all_unpacked_role_names = viv_compiler.utils.get_all_expressions_of_type(
|
753
|
-
expression_type=
|
800
|
+
expression_type=ExpressionDiscriminator.ROLE_UNPACKING,
|
754
801
|
ast_chunk=action_definition
|
755
802
|
)
|
756
803
|
for unpacked_role_name in all_unpacked_role_names:
|
804
|
+
if unpacked_role_name in viv_compiler.config.SPECIAL_ROLE_NAMES:
|
805
|
+
raise ValueError(
|
806
|
+
f"Action '{action_definition['name']}' unpacks a singleton role (one that is "
|
807
|
+
f"always bound to a single entity): '{unpacked_role_name}'"
|
808
|
+
)
|
809
|
+
# Make sure all role unpackings unpack roles that can cast multiple entities. Here, we'll also
|
810
|
+
# include all our special roles, since these are always singletons.
|
811
|
+
for unpacked_role_name in all_unpacked_role_names:
|
812
|
+
error_message = (
|
813
|
+
f"Action '{action_definition['name']}' unpacks a singleton role (one that is "
|
814
|
+
f"always bound to a single entity): '{unpacked_role_name}'"
|
815
|
+
)
|
816
|
+
if unpacked_role_name in viv_compiler.config.SPECIAL_ROLE_NAMES:
|
817
|
+
raise ValueError(error_message)
|
757
818
|
for role_definition in action_definition['roles'].values():
|
758
819
|
if role_definition['name'] == unpacked_role_name:
|
759
820
|
if role_definition['min'] == role_definition['max'] == 1:
|
760
|
-
raise ValueError(
|
761
|
-
f"Action '{action_definition['name']}' unpacks a singleton role (one that is "
|
762
|
-
f"always bound to a single entity): '{unpacked_role_name}'"
|
763
|
-
)
|
821
|
+
raise ValueError(error_message)
|
764
822
|
break
|
765
|
-
#
|
823
|
+
# Make sure that all other references to roles that can cast multiple entities use role unpackings
|
766
824
|
single_entity_role_references = viv_compiler.utils.get_all_referenced_roles( # I.e., using the '@role' notation
|
767
825
|
ast_chunk=action_definition,
|
768
826
|
ignore_role_unpackings=True
|
@@ -792,7 +850,10 @@ def _validate_action_loops(action_definition: viv_compiler.types.ActionDefinitio
|
|
792
850
|
Raises:
|
793
851
|
Exception: The action definition did not pass validation.
|
794
852
|
"""
|
795
|
-
all_loops = viv_compiler.utils.get_all_expressions_of_type(
|
853
|
+
all_loops = viv_compiler.utils.get_all_expressions_of_type(
|
854
|
+
expression_type=ExpressionDiscriminator.LOOP,
|
855
|
+
ast_chunk=action_definition
|
856
|
+
)
|
796
857
|
for loop in all_loops:
|
797
858
|
# Detect attempts to loop over single-entity role references (i.e., ones using '@role' notation)
|
798
859
|
if loop['iterable']['type'] == ExpressionDiscriminator.ENTITY_REFERENCE:
|
@@ -803,10 +864,10 @@ def _validate_action_loops(action_definition: viv_compiler.types.ActionDefinitio
|
|
803
864
|
f"role: '{role_name}' (perhaps use * instead of @)"
|
804
865
|
)
|
805
866
|
# Detect cases of a loop variable shadowing the name of a role from the same action
|
806
|
-
if loop['variable'] in viv_compiler.utils.get_all_role_names(action_definition=action_definition):
|
867
|
+
if loop['variable']['name'] in viv_compiler.utils.get_all_role_names(action_definition=action_definition):
|
807
868
|
raise ValueError(
|
808
869
|
f"Action '{action_definition['name']}' has loop with variable name that "
|
809
|
-
f"shadows role name '{loop['variable']}' (this is not allowed)"
|
870
|
+
f"shadows role name '{loop['variable']['name']}' (this is not allowed)"
|
810
871
|
)
|
811
872
|
|
812
873
|
|
@@ -822,27 +883,48 @@ def _validate_action_assignments(action_definition: viv_compiler.types.ActionDef
|
|
822
883
|
Raises:
|
823
884
|
Exception: The action definition did not pass validation.
|
824
885
|
"""
|
886
|
+
# Make sure that all assignments are housed in the proper fields
|
887
|
+
for field_name, field_value in action_definition.items():
|
888
|
+
if field_name not in viv_compiler.config.ACTION_FIELDS_PERMITTING_ASSIGNMENTS:
|
889
|
+
assignments_in_field = viv_compiler.utils.get_all_expressions_of_type(
|
890
|
+
expression_type=ExpressionDiscriminator.ASSIGNMENT,
|
891
|
+
ast_chunk=field_value
|
892
|
+
)
|
893
|
+
if assignments_in_field:
|
894
|
+
raise ValueError(
|
895
|
+
f"Action '{action_definition['name']}' has assignment in '{field_name}' field "
|
896
|
+
f"(only allowed in {', '.join(viv_compiler.config.ACTION_FIELDS_PERMITTING_ASSIGNMENTS)})"
|
897
|
+
)
|
898
|
+
# Collect all assignments
|
825
899
|
all_assignments = viv_compiler.utils.get_all_expressions_of_type(
|
826
|
-
expression_type=
|
900
|
+
expression_type=ExpressionDiscriminator.ASSIGNMENT,
|
827
901
|
ast_chunk=action_definition
|
828
902
|
)
|
903
|
+
# Validate each assignment in turn
|
829
904
|
for assignment in all_assignments:
|
830
905
|
anchor = assignment['left']['value']['anchor']
|
831
906
|
path = assignment['left']['value']['path']
|
832
|
-
|
907
|
+
# Detect attempts to set a local variable. Note that we do allow assignments that are anchored
|
908
|
+
# in local variables and also have a path, since this enables the common pattern of looping
|
909
|
+
# over a role unpacking to execute effects for each entity cast in the group role.
|
910
|
+
if assignment['left']['value']['local'] and not path:
|
911
|
+
raise ValueError(
|
912
|
+
f"Assignment expression in action '{action_definition['name']}' sets local variable '{anchor}', "
|
913
|
+
f"but local variables can only be set in loops, 'saliences' headers, and 'associations' headers"
|
914
|
+
)
|
915
|
+
if anchor != viv_compiler.config.ACTION_SELF_REFERENCE_ROLE_NAME and anchor in action_definition['roles']:
|
833
916
|
# Detect attempts to recast a role via assignment (not allowed)
|
834
917
|
if not path:
|
835
918
|
raise ValueError(
|
836
919
|
f"Assignment expression in action '{action_definition['name']}' recasts "
|
837
920
|
f"role '{anchor}' (this is prohibited)"
|
838
921
|
)
|
839
|
-
# Detect attempts to set data on
|
840
|
-
if anchor
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
)
|
922
|
+
# Detect attempts to set data on a complex symbol role (currently prohibited)
|
923
|
+
if action_definition['roles'][anchor]['symbol']:
|
924
|
+
raise ValueError(
|
925
|
+
f"Assignment expression in action '{action_definition['name']}' has "
|
926
|
+
f"symbol role on its left-hand side (this is currently prohibited): '{anchor}'"
|
927
|
+
)
|
846
928
|
# Detect a trailing eval fail-safe marker, which is bizarre and probably an authoring error
|
847
929
|
if path and path[-1].get('failSafe'):
|
848
930
|
raise ValueError(
|
@@ -851,8 +933,11 @@ def _validate_action_assignments(action_definition: viv_compiler.types.ActionDef
|
|
851
933
|
)
|
852
934
|
|
853
935
|
|
854
|
-
def
|
855
|
-
"""Validate the given action definition's
|
936
|
+
def _validate_scratch_variable_references(action_definition: viv_compiler.types.ActionDefinition) -> None:
|
937
|
+
"""Validate the given action definition's references to scratch variables.
|
938
|
+
|
939
|
+
This procedure ensures that any scratch variable that is referenced is assigned *somewhere* in
|
940
|
+
the action definition, but it does not ensure that the variable is assigned before it's used.
|
856
941
|
|
857
942
|
Args:
|
858
943
|
action_definition: An action definition from a compiled content bundle.
|
@@ -863,16 +948,22 @@ def _validate_chance_expressions(action_definition: viv_compiler.types.ActionDef
|
|
863
948
|
Raises:
|
864
949
|
Exception: The action definition did not pass validation.
|
865
950
|
"""
|
866
|
-
|
867
|
-
|
951
|
+
# Retrieve the names of all scratch variables that are set anywhere in the action definition
|
952
|
+
all_assigned_scratch_variable_names = set(viv_compiler.utils.get_all_assigned_scratch_variable_names(
|
868
953
|
ast_chunk=action_definition
|
869
|
-
)
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
954
|
+
))
|
955
|
+
# Retrieve the names of all scratch variables that are referenced anywhere in the action definition
|
956
|
+
all_referenced_scratch_variable_names = set(viv_compiler.utils.get_all_referenced_scratch_variable_names(
|
957
|
+
ast_chunk=action_definition
|
958
|
+
))
|
959
|
+
# Flag any cases of a scratch variable being referenced without being assigned anywhere
|
960
|
+
referenced_but_not_assigned = all_referenced_scratch_variable_names - all_assigned_scratch_variable_names
|
961
|
+
if referenced_but_not_assigned:
|
962
|
+
snippet = "'" + "', '".join(sorted(referenced_but_not_assigned)) + "'"
|
963
|
+
raise ValueError(
|
964
|
+
f"Action '{action_definition['name']}' references the following scratch variables without ever "
|
965
|
+
f"assigning them: {snippet}"
|
966
|
+
)
|
876
967
|
|
877
968
|
|
878
969
|
def _validate_negated_expressions(action_definition: viv_compiler.types.ActionDefinition) -> None:
|
@@ -896,6 +987,30 @@ def _validate_negated_expressions(action_definition: viv_compiler.types.ActionDe
|
|
896
987
|
)
|
897
988
|
|
898
989
|
|
990
|
+
def _validate_chance_expressions(action_definition: viv_compiler.types.ActionDefinition) -> None:
|
991
|
+
"""Validate the given action definition's usage of chance expressions.
|
992
|
+
|
993
|
+
Args:
|
994
|
+
action_definition: An action definition from a compiled content bundle.
|
995
|
+
|
996
|
+
Returns:
|
997
|
+
None.
|
998
|
+
|
999
|
+
Raises:
|
1000
|
+
Exception: The action definition did not pass validation.
|
1001
|
+
"""
|
1002
|
+
all_chance_expression_values = viv_compiler.utils.get_all_expressions_of_type(
|
1003
|
+
expression_type=ExpressionDiscriminator.CHANCE_EXPRESSION,
|
1004
|
+
ast_chunk=action_definition
|
1005
|
+
)
|
1006
|
+
for chance_value in all_chance_expression_values:
|
1007
|
+
if chance_value < 0.0 or chance_value > 1.0:
|
1008
|
+
raise ValueError(
|
1009
|
+
f"Chance expression in action '{action_definition['name']}' has "
|
1010
|
+
f"chance value outside of the range [0, 1]: '{chance_value * 100}%'"
|
1011
|
+
)
|
1012
|
+
|
1013
|
+
|
899
1014
|
def _validate_compiled_content_bundle_against_schema(content_bundle: viv_compiler.types.CompiledContentBundle) -> None:
|
900
1015
|
"""Validate a compiled content bundle against its public schema.
|
901
1016
|
|