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
@@ -1,2 +1,2 @@
|
|
1
1
|
# This is programmatically updated by viv/scripts/bump_version.sh
|
2
|
-
__version__ = "0.1.
|
2
|
+
__version__ = "0.1.2"
|
viv_compiler/cli.py
CHANGED
@@ -10,6 +10,8 @@ import traceback
|
|
10
10
|
import viv_compiler.config
|
11
11
|
from pathlib import Path
|
12
12
|
from importlib import resources
|
13
|
+
|
14
|
+
from viv_compiler import VivCompileError
|
13
15
|
from .api import compile_from_path, get_version
|
14
16
|
from viv_compiler.types import CompiledContentBundle
|
15
17
|
|
@@ -189,10 +191,9 @@ def _invoke_compiler(args: argparse.Namespace) -> CompiledContentBundle:
|
|
189
191
|
except Exception:
|
190
192
|
pass
|
191
193
|
sys.exit(1)
|
192
|
-
except
|
193
|
-
|
194
|
-
|
195
|
-
traceback.print_exc()
|
194
|
+
except VivCompileError as e:
|
195
|
+
cause = e.__cause__ or e
|
196
|
+
print(f"Error encountered during compilation:\n\n{cause}\n", file=sys.stderr)
|
196
197
|
sys.exit(1)
|
197
198
|
|
198
199
|
|
viv_compiler/config/config.py
CHANGED
@@ -25,9 +25,9 @@ NEGATABLE_EXPRESSION_TYPES = {
|
|
25
25
|
ExpressionDiscriminator.CONJUNCTION,
|
26
26
|
ExpressionDiscriminator.DISJUNCTION,
|
27
27
|
ExpressionDiscriminator.ENTITY_REFERENCE,
|
28
|
-
ExpressionDiscriminator.LOCAL_VARIABLE_REFERENCE,
|
29
28
|
ExpressionDiscriminator.LOOP,
|
30
29
|
ExpressionDiscriminator.MEMBERSHIP_TEST,
|
30
|
+
ExpressionDiscriminator.SYMBOL_REFERENCE,
|
31
31
|
ExpressionDiscriminator.TROPE_FIT_EXPRESSION,
|
32
32
|
}
|
33
33
|
|
@@ -56,28 +56,21 @@ REACTION_FIELD_DEFAULT_OPTIONS = {
|
|
56
56
|
"abandonmentConditions": [],
|
57
57
|
}
|
58
58
|
|
59
|
-
#
|
59
|
+
# Name for the special action self-reference role, which is always bound to the action itself
|
60
|
+
ACTION_SELF_REFERENCE_ROLE_NAME = 'this'
|
61
|
+
|
62
|
+
# A set containing the special role names that are automatically created by Viv at various points
|
63
|
+
SPECIAL_ROLE_NAMES = {'hearer', ACTION_SELF_REFERENCE_ROLE_NAME}
|
64
|
+
|
65
|
+
# The path to which the scratch-variable sigil `$` expands. This sigil is really just syntactic sugar for
|
60
66
|
# the path `@this.scratch`, which stores a blackboard local to a performed action. For instance, the scratch
|
61
|
-
# operation
|
62
|
-
|
63
|
-
|
67
|
+
# operation `$@foo.bar = 99` is syntactic sugar for the expression `@this.scratch.foo.bar = 99`.
|
68
|
+
SCRATCH_VARIABLE_REFERENCE_ANCHOR = ACTION_SELF_REFERENCE_ROLE_NAME
|
69
|
+
SCRATCH_VARIABLE_REFERENCE_PATH_PREFIX = [{
|
64
70
|
"type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_PROPERTY_NAME,
|
65
71
|
"name": "scratch",
|
66
72
|
}]
|
67
73
|
|
68
|
-
# The path to which the local-variable sigil `$$` expands. This sigil is really just a property lookup in
|
69
|
-
# the special `__locals__` field of an evaluation context, which is a temporary store for scoped local
|
70
|
-
# variables. For instance, the local-variable reference `$$c` defines an attempt to access `__locals__.c`
|
71
|
-
# in an evaluation context. Unlike the `$` sigil, this is not syntactic sugar, since the Viv author has
|
72
|
-
# no other way to reference local variables.
|
73
|
-
LOCAL_VARIABLE_REFERENCE_PATH = ["__locals__"]
|
74
|
-
|
75
|
-
# Name for the variable to which each character is set when computing their salience for an action
|
76
|
-
SALIENCES_VARIABLE_NAME = "c"
|
77
|
-
|
78
|
-
# Name for the variable to which each character is set when computing their associations for an action
|
79
|
-
ASSOCIATIONS_VARIABLE_NAME = "c"
|
80
|
-
|
81
74
|
# A default salience value, to be used when the API caller does not provide one
|
82
75
|
DEFAULT_SALIENCE_VALUE = 1.0
|
83
76
|
|
@@ -86,3 +79,9 @@ DEFAULT_ASSOCIATIONS_VALUE = []
|
|
86
79
|
|
87
80
|
# A default reaction priority value, to be used when the API caller does not provide one
|
88
81
|
DEFAULT_REACTION_PRIORITY_VALUE = 1.0
|
82
|
+
|
83
|
+
# A list containing the names of action fields in which assignments are permitted
|
84
|
+
ACTION_FIELDS_PERMITTING_ASSIGNMENTS = ("scratch", "effects",)
|
85
|
+
|
86
|
+
# A list containing the names of action fields in which reactions are permitted
|
87
|
+
ACTION_FIELDS_PERMITTING_REACTIONS = ("reactions",)
|
viv_compiler/core/core.py
CHANGED
@@ -106,7 +106,7 @@ def _honor_user_supplied_config_parameters(
|
|
106
106
|
"""
|
107
107
|
viv_compiler.config.ACTION_DEFINITION_OPTIONAL_FIELD_DEFAULT_VALUES["saliences"] = {
|
108
108
|
"default": {'type': 'float', 'value': default_salience},
|
109
|
-
"variable":
|
109
|
+
"variable": None,
|
110
110
|
"body": [],
|
111
111
|
}
|
112
112
|
default_associations_expression = {
|
@@ -115,7 +115,7 @@ def _honor_user_supplied_config_parameters(
|
|
115
115
|
}
|
116
116
|
viv_compiler.config.ACTION_DEFINITION_OPTIONAL_FIELD_DEFAULT_VALUES["associations"] = {
|
117
117
|
"default": default_associations_expression,
|
118
|
-
"variable":
|
118
|
+
"variable": None,
|
119
119
|
"body": [],
|
120
120
|
}
|
121
121
|
viv_compiler.config.REACTION_FIELD_DEFAULT_OPTIONS["priority"] = {
|
viv_compiler/core/metadata.py
CHANGED
@@ -33,15 +33,17 @@ def create_metadata(
|
|
33
33
|
"buildRoles": [],
|
34
34
|
"timeOfDayConstrainedReactions": []
|
35
35
|
}
|
36
|
+
all_referenced_enum_names = []
|
37
|
+
all_referenced_function_names = []
|
36
38
|
for ast_chunk in action_definitions + trope_definitions:
|
37
39
|
# Compile all referenced enums
|
38
|
-
all_referenced_enum_names
|
39
|
-
metadata["referencedEnums"].extend(all_referenced_enum_names)
|
40
|
+
all_referenced_enum_names.extend(viv_compiler.utils.get_all_referenced_enum_names(ast_chunk=ast_chunk))
|
40
41
|
# Compile all referenced adapter functions
|
41
|
-
all_referenced_function_names
|
42
|
-
|
43
|
-
)
|
44
|
-
|
42
|
+
all_referenced_function_names.extend(viv_compiler.utils.get_all_referenced_adapter_function_names(
|
43
|
+
ast_chunk=ast_chunk
|
44
|
+
))
|
45
|
+
metadata["referencedEnums"].extend(sorted(set(all_referenced_enum_names)))
|
46
|
+
metadata["referencedFunctionNames"].extend(sorted(set(all_referenced_function_names)))
|
45
47
|
for action_definition in action_definitions:
|
46
48
|
# Compile all roles carrying 'item' and 'build' labels
|
47
49
|
for role_definition in action_definition["roles"].values():
|
viv_compiler/core/validation.py
CHANGED
@@ -20,7 +20,7 @@ from pydantic import TypeAdapter
|
|
20
20
|
|
21
21
|
|
22
22
|
def validate_join_directives(raw_action_definitions: list[viv_compiler.types.RawActionDefinition]) -> None:
|
23
|
-
"""
|
23
|
+
"""Make sure that the given action definition makes proper use of `join` directives.
|
24
24
|
|
25
25
|
If this validation check passes, the given action definitions are ready for inheritance to be handled.
|
26
26
|
|
@@ -65,10 +65,10 @@ def validate_preliminary_action_definitions(
|
|
65
65
|
Exception: At least one action definition did not pass validation.
|
66
66
|
"""
|
67
67
|
for action_definition in intermediate_action_definitions:
|
68
|
-
#
|
68
|
+
# Make sure that a 'roles' field is present
|
69
69
|
if "roles" not in action_definition:
|
70
70
|
raise KeyError(f"Action '{action_definition['name']}' is missing a 'roles' field, which is required")
|
71
|
-
#
|
71
|
+
# Make sure that there is a single initiator role. Note that during initial validation,
|
72
72
|
# the 'roles' field is still a list.
|
73
73
|
_detect_wrong_number_of_initiators(action_definition=action_definition)
|
74
74
|
# Detect duplicated role names
|
@@ -137,7 +137,9 @@ def _detect_reference_to_undefined_role(action_definition: viv_compiler.types.In
|
|
137
137
|
Exception: The action definition did not pass validation.
|
138
138
|
"""
|
139
139
|
# Retrieve the names of all defined roles ('roles' is still a list at this point in postprocessing)
|
140
|
-
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
|
+
)
|
141
143
|
# Validate report references
|
142
144
|
if action_definition["report"]:
|
143
145
|
for reference in viv_compiler.utils.get_all_referenced_roles(action_definition["report"]):
|
@@ -243,11 +245,25 @@ def _validate_trope_definitions(trope_definitions: dict[str, viv_compiler.types.
|
|
243
245
|
# Detect any reference to an undefined param
|
244
246
|
all_referenced_params = viv_compiler.utils.get_all_referenced_roles(ast_chunk=trope_definition)
|
245
247
|
for referenced_param in all_referenced_params:
|
246
|
-
if referenced_param not in trope_definition["params"]:
|
248
|
+
if referenced_param not in [trope_param["name"] for trope_param in trope_definition["params"]]:
|
247
249
|
raise KeyError(
|
248
250
|
f"Trope '{trope_definition['name']}' references undefined parameter: "
|
249
251
|
f"'{referenced_param}'"
|
250
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)")
|
251
267
|
|
252
268
|
|
253
269
|
def _validate_action_definitions(
|
@@ -279,7 +295,10 @@ def _validate_action_definitions(
|
|
279
295
|
_validate_action_effects(action_definition=action_definition)
|
280
296
|
# Validate reactions
|
281
297
|
_validate_action_reactions(action_definition=action_definition, all_action_definitions=action_definitions)
|
298
|
+
# Validate saliences
|
299
|
+
_validate_action_saliences(action_definition=action_definition)
|
282
300
|
# Validate associations
|
301
|
+
_validate_action_associations(action_definition=action_definition)
|
283
302
|
# Validate trope-fit expressions
|
284
303
|
_validate_action_trope_fit_expressions(
|
285
304
|
action_definition=action_definition,
|
@@ -291,6 +310,8 @@ def _validate_action_definitions(
|
|
291
310
|
_validate_action_loops(action_definition=action_definition)
|
292
311
|
# Validate assignments
|
293
312
|
_validate_action_assignments(action_definition=action_definition)
|
313
|
+
# Validate references to scratch variables
|
314
|
+
_validate_scratch_variable_references(action_definition=action_definition)
|
294
315
|
# Validate negated expressions
|
295
316
|
_validate_negated_expressions(action_definition=action_definition)
|
296
317
|
# Validate chance expressions
|
@@ -330,13 +351,13 @@ def _validate_action_roles(action_definition: viv_compiler.types.ActionDefinitio
|
|
330
351
|
"""
|
331
352
|
# Validate the initiator role
|
332
353
|
_validate_action_initiator_role(action_definition=action_definition)
|
333
|
-
#
|
354
|
+
# Make sure all roles have proper 'min' and 'max' values
|
334
355
|
_validate_action_role_min_and_max_values(action_definition=action_definition)
|
335
|
-
#
|
356
|
+
# Make sure all roles have proper 'chance' and 'mean' values
|
336
357
|
_validate_action_role_chance_and_mean_values(action_definition=action_definition)
|
337
|
-
#
|
358
|
+
# Make sure all roles with binding pools have proper ones
|
338
359
|
_validate_action_role_pool_directives(action_definition=action_definition)
|
339
|
-
#
|
360
|
+
# Make sure that the 'precast' label is only used if this is a special action
|
340
361
|
_validate_action_role_precast_label_usages(action_definition=action_definition)
|
341
362
|
|
342
363
|
|
@@ -354,13 +375,13 @@ def _validate_action_initiator_role(action_definition: viv_compiler.types.Action
|
|
354
375
|
"""
|
355
376
|
# Retrieve the definition for the action's initiator role
|
356
377
|
initiator_role_definition = action_definition['initiator']
|
357
|
-
#
|
378
|
+
# Make sure that the initiator role has no pool directive
|
358
379
|
if initiator_role_definition["pool"]:
|
359
380
|
raise ValueError(
|
360
381
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
361
382
|
f"with a pool directive, which is not allowed for initiator roles"
|
362
383
|
)
|
363
|
-
#
|
384
|
+
# Make sure that the initiator role casts exactly one entity
|
364
385
|
if initiator_role_definition['min'] != 1:
|
365
386
|
raise ValueError(
|
366
387
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
@@ -371,13 +392,13 @@ def _validate_action_initiator_role(action_definition: viv_compiler.types.Action
|
|
371
392
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
372
393
|
f"with max other than 1 (there must be a single initiator)"
|
373
394
|
)
|
374
|
-
#
|
395
|
+
# Make sure that the initiator role has no specified binding mean
|
375
396
|
if initiator_role_definition['mean'] is not None:
|
376
397
|
raise ValueError(
|
377
398
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
378
399
|
f"with declared casting mean (there must be a single initiator)"
|
379
400
|
)
|
380
|
-
#
|
401
|
+
# Make sure that the initiator role has no specified binding chance
|
381
402
|
if initiator_role_definition['chance'] is not None:
|
382
403
|
raise ValueError(
|
383
404
|
f"Action '{action_definition['name']}' has initiator role '{initiator_role_definition['name']}' "
|
@@ -479,7 +500,7 @@ def _validate_action_role_pool_directives(action_definition: viv_compiler.types.
|
|
479
500
|
f"Action '{action_definition['name']}' has role '{role_definition['name']}' "
|
480
501
|
f"that requires a pool declaration but does not have one"
|
481
502
|
)
|
482
|
-
#
|
503
|
+
# Make sure that all pool directives reference at most one other (valid) role
|
483
504
|
if role_definition["pool"]:
|
484
505
|
all_pool_references = viv_compiler.utils.get_all_referenced_roles(role_definition["pool"])
|
485
506
|
if len(all_pool_references) > 1:
|
@@ -604,25 +625,21 @@ def _validate_action_reactions(
|
|
604
625
|
Raises:
|
605
626
|
Exception: The action definition did not pass validation.
|
606
627
|
"""
|
607
|
-
#
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
for field_name, field_value in fields_to_check:
|
614
|
-
reactions_in_this_field = viv_compiler.utils.get_all_expressions_of_type(
|
615
|
-
expression_type="reaction",
|
616
|
-
ast_chunk=field_value
|
617
|
-
)
|
618
|
-
if reactions_in_this_field:
|
619
|
-
raise ValueError(
|
620
|
-
f"Action '{action_definition['name']}' has reaction in '{field_name}' field "
|
621
|
-
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
|
622
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
|
+
)
|
623
640
|
# Collect all reactions
|
624
641
|
all_reactions = viv_compiler.utils.get_all_expressions_of_type(
|
625
|
-
expression_type=
|
642
|
+
expression_type=ExpressionDiscriminator.REACTION,
|
626
643
|
ast_chunk=action_definition['reactions']
|
627
644
|
)
|
628
645
|
# Validate each reaction in turn
|
@@ -640,7 +657,7 @@ def _validate_action_reactions(
|
|
640
657
|
queued_action_initiator_role_name = queued_action_definition['initiator']['name']
|
641
658
|
initiator_is_bound = False
|
642
659
|
for binding in reaction['bindings']:
|
643
|
-
if binding['
|
660
|
+
if binding['role'] == queued_action_initiator_role_name:
|
644
661
|
initiator_is_bound = True
|
645
662
|
break
|
646
663
|
if not initiator_is_bound:
|
@@ -651,12 +668,21 @@ def _validate_action_reactions(
|
|
651
668
|
# Make sure the reaction references only roles defined in the queued actions
|
652
669
|
all_queued_action_role_names = queued_action_definition['roles'].keys()
|
653
670
|
for binding in reaction['bindings']:
|
654
|
-
bound_role_name = binding['
|
671
|
+
bound_role_name = binding['role']
|
655
672
|
if bound_role_name not in all_queued_action_role_names:
|
656
673
|
raise KeyError(
|
657
674
|
f"Action '{action_definition['name']}' has '{queued_action_name}' reaction that "
|
658
675
|
f"references a role that is undefined for '{queued_action_name}': '{bound_role_name}'"
|
659
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'])
|
660
686
|
# Make sure the reaction precasts all 'precast' roles
|
661
687
|
for role_object in queued_action_definition['roles'].values():
|
662
688
|
if not role_object['precast']:
|
@@ -664,7 +690,7 @@ def _validate_action_reactions(
|
|
664
690
|
precast_role_name = role_object['name']
|
665
691
|
role_is_precast = False
|
666
692
|
for binding in reaction['bindings']:
|
667
|
-
bound_role_name = binding['
|
693
|
+
bound_role_name = binding['role']
|
668
694
|
if bound_role_name == precast_role_name:
|
669
695
|
role_is_precast = True
|
670
696
|
break
|
@@ -675,6 +701,50 @@ def _validate_action_reactions(
|
|
675
701
|
)
|
676
702
|
|
677
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
|
+
|
678
748
|
def _validate_action_trope_fit_expressions(
|
679
749
|
action_definition: viv_compiler.types.ActionDefinition,
|
680
750
|
trope_definitions: dict[str, TropeDefinition]
|
@@ -692,7 +762,7 @@ def _validate_action_trope_fit_expressions(
|
|
692
762
|
Exception: The action definition did not pass validation.
|
693
763
|
"""
|
694
764
|
all_trope_fit_expressions = viv_compiler.utils.get_all_expressions_of_type(
|
695
|
-
expression_type=
|
765
|
+
expression_type=ExpressionDiscriminator.TROPE_FIT_EXPRESSION,
|
696
766
|
ast_chunk=action_definition
|
697
767
|
)
|
698
768
|
for trope_fit_expression in all_trope_fit_expressions:
|
@@ -725,21 +795,32 @@ def _validate_action_role_unpackings(action_definition: viv_compiler.types.Actio
|
|
725
795
|
Raises:
|
726
796
|
Exception: The action definition did not pass validation.
|
727
797
|
"""
|
728
|
-
#
|
798
|
+
# Retrieve all unpacked role names
|
729
799
|
all_unpacked_role_names = viv_compiler.utils.get_all_expressions_of_type(
|
730
|
-
expression_type=
|
800
|
+
expression_type=ExpressionDiscriminator.ROLE_UNPACKING,
|
731
801
|
ast_chunk=action_definition
|
732
802
|
)
|
733
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)
|
734
818
|
for role_definition in action_definition['roles'].values():
|
735
819
|
if role_definition['name'] == unpacked_role_name:
|
736
820
|
if role_definition['min'] == role_definition['max'] == 1:
|
737
|
-
raise ValueError(
|
738
|
-
f"Action '{action_definition['name']}' unpacks a singleton role (one that is "
|
739
|
-
f"always bound to a single entity): '{unpacked_role_name}'"
|
740
|
-
)
|
821
|
+
raise ValueError(error_message)
|
741
822
|
break
|
742
|
-
#
|
823
|
+
# Make sure that all other references to roles that can cast multiple entities use role unpackings
|
743
824
|
single_entity_role_references = viv_compiler.utils.get_all_referenced_roles( # I.e., using the '@role' notation
|
744
825
|
ast_chunk=action_definition,
|
745
826
|
ignore_role_unpackings=True
|
@@ -769,7 +850,10 @@ def _validate_action_loops(action_definition: viv_compiler.types.ActionDefinitio
|
|
769
850
|
Raises:
|
770
851
|
Exception: The action definition did not pass validation.
|
771
852
|
"""
|
772
|
-
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
|
+
)
|
773
857
|
for loop in all_loops:
|
774
858
|
# Detect attempts to loop over single-entity role references (i.e., ones using '@role' notation)
|
775
859
|
if loop['iterable']['type'] == ExpressionDiscriminator.ENTITY_REFERENCE:
|
@@ -780,10 +864,10 @@ def _validate_action_loops(action_definition: viv_compiler.types.ActionDefinitio
|
|
780
864
|
f"role: '{role_name}' (perhaps use * instead of @)"
|
781
865
|
)
|
782
866
|
# Detect cases of a loop variable shadowing the name of a role from the same action
|
783
|
-
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):
|
784
868
|
raise ValueError(
|
785
869
|
f"Action '{action_definition['name']}' has loop with variable name that "
|
786
|
-
f"shadows role name '{loop['variable']}' (this is not allowed)"
|
870
|
+
f"shadows role name '{loop['variable']['name']}' (this is not allowed)"
|
787
871
|
)
|
788
872
|
|
789
873
|
|
@@ -799,27 +883,48 @@ def _validate_action_assignments(action_definition: viv_compiler.types.ActionDef
|
|
799
883
|
Raises:
|
800
884
|
Exception: The action definition did not pass validation.
|
801
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
|
802
899
|
all_assignments = viv_compiler.utils.get_all_expressions_of_type(
|
803
|
-
expression_type=
|
900
|
+
expression_type=ExpressionDiscriminator.ASSIGNMENT,
|
804
901
|
ast_chunk=action_definition
|
805
902
|
)
|
903
|
+
# Validate each assignment in turn
|
806
904
|
for assignment in all_assignments:
|
807
905
|
anchor = assignment['left']['value']['anchor']
|
808
906
|
path = assignment['left']['value']['path']
|
809
|
-
|
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']:
|
810
916
|
# Detect attempts to recast a role via assignment (not allowed)
|
811
917
|
if not path:
|
812
918
|
raise ValueError(
|
813
919
|
f"Assignment expression in action '{action_definition['name']}' recasts "
|
814
920
|
f"role '{anchor}' (this is prohibited)"
|
815
921
|
)
|
816
|
-
# Detect attempts to set data on
|
817
|
-
if anchor
|
818
|
-
|
819
|
-
|
820
|
-
|
821
|
-
|
822
|
-
)
|
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
|
+
)
|
823
928
|
# Detect a trailing eval fail-safe marker, which is bizarre and probably an authoring error
|
824
929
|
if path and path[-1].get('failSafe'):
|
825
930
|
raise ValueError(
|
@@ -828,8 +933,11 @@ def _validate_action_assignments(action_definition: viv_compiler.types.ActionDef
|
|
828
933
|
)
|
829
934
|
|
830
935
|
|
831
|
-
def
|
832
|
-
"""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.
|
833
941
|
|
834
942
|
Args:
|
835
943
|
action_definition: An action definition from a compiled content bundle.
|
@@ -840,16 +948,22 @@ def _validate_chance_expressions(action_definition: viv_compiler.types.ActionDef
|
|
840
948
|
Raises:
|
841
949
|
Exception: The action definition did not pass validation.
|
842
950
|
"""
|
843
|
-
|
844
|
-
|
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(
|
845
953
|
ast_chunk=action_definition
|
846
|
-
)
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
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
|
+
)
|
853
967
|
|
854
968
|
|
855
969
|
def _validate_negated_expressions(action_definition: viv_compiler.types.ActionDefinition) -> None:
|
@@ -873,6 +987,30 @@ def _validate_negated_expressions(action_definition: viv_compiler.types.ActionDe
|
|
873
987
|
)
|
874
988
|
|
875
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
|
+
|
876
1014
|
def _validate_compiled_content_bundle_against_schema(content_bundle: viv_compiler.types.CompiledContentBundle) -> None:
|
877
1015
|
"""Validate a compiled content bundle against its public schema.
|
878
1016
|
|