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.
@@ -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, TypeGuard
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["type"] == 'include':
24
+ if child["_type"] == 'include':
25
25
  ast['_includes'].append(child['value'])
26
- elif child["type"] == 'action':
26
+ elif child["_type"] == 'action':
27
27
  ast['actions'].append(child['value'])
28
- elif child["type"] == 'trope':
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 {"type": "include", "value": children[0]}
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 {"type": "action", "value": action_definition}
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."""
@@ -166,17 +195,23 @@ class Visitor(arpeggio.PTNodeVisitor):
166
195
  "partner": False,
167
196
  "recipient": False,
168
197
  "bystander": False,
169
- "subject": False,
170
198
  "absent": False,
171
199
  "precast": False,
172
200
  "build": False,
173
201
  }
202
+ marked_entity_role = False
174
203
  for child in children:
175
- if 'min' in child:
204
+ if "_is_entity" in child:
205
+ marked_entity_role = child["_is_entity"]
206
+ elif "min" in child:
176
207
  component['min'] = child['min']['value']
177
208
  component['max'] = child['max']['value']
178
209
  else:
179
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")
180
215
  if not (component['item'] or component['action'] or component['location'] or component['symbol']):
181
216
  component['character'] = True
182
217
  if component['mean'] is not None:
@@ -206,6 +241,11 @@ class Visitor(arpeggio.PTNodeVisitor):
206
241
  return {"min": children[0], "max": children[0]}
207
242
  return {"min": children[0], "max": children[1]}
208
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
+
209
249
  @staticmethod
210
250
  def visit_role_name(_, children: Any) -> dict[str, str]:
211
251
  """Visit a <role_name> node."""
@@ -348,31 +388,6 @@ class Visitor(arpeggio.PTNodeVisitor):
348
388
  # Move the 'bindings' field from the options component to the top level of the reaction value
349
389
  reaction_object['bindings'] = reaction_object['options']['bindings']
350
390
  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
391
  # Package up the component and return it
377
392
  reaction_value: viv_compiler.types.ReactionValue = reaction_object # type: ignore[assignment]
378
393
  component: viv_compiler.types.Reaction = {"type": ExpressionDiscriminator.REACTION, "value": reaction_value}
@@ -389,14 +404,14 @@ class Visitor(arpeggio.PTNodeVisitor):
389
404
  return {"bindings": children}
390
405
 
391
406
  @staticmethod
392
- def visit_binding(_, children: Any) -> viv_compiler.types.ReactionBinding | viv_compiler.types.Loop:
407
+ def visit_binding(_, children: Any) -> viv_compiler.types.ReactionRoleBindings:
393
408
  """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}}
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
+ }
400
415
  return component
401
416
 
402
417
  @staticmethod
@@ -525,21 +540,20 @@ class Visitor(arpeggio.PTNodeVisitor):
525
540
  @staticmethod
526
541
  def visit_time(_, children: Any) -> dict[str, Any]:
527
542
  """Visit a <time> node."""
528
- period = children[-1] # AM or PM
529
543
  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
- }
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}
543
557
  return component
544
558
 
545
559
  @staticmethod
@@ -560,12 +574,15 @@ class Visitor(arpeggio.PTNodeVisitor):
560
574
  if children[0] == "join":
561
575
  join_saliences = True
562
576
  children = children[1:]
563
- default_value_expression = children[0]
564
- body = children[1] if len(children) > 1 else []
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
565
582
  component = {
566
583
  "saliences": {
567
584
  "default": default_value_expression,
568
- "variable": viv_compiler.config.SALIENCES_VARIABLE_NAME,
585
+ "variable": local_variable,
569
586
  "body": body,
570
587
  }
571
588
  }
@@ -590,12 +607,15 @@ class Visitor(arpeggio.PTNodeVisitor):
590
607
  if children[0] == "join":
591
608
  join_associations = True
592
609
  children = children[1:]
593
- default_value_expression = children[0]
594
- body = children[1] if len(children) > 1 else []
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
595
615
  component = {
596
616
  "associations": {
597
617
  "default": default_value_expression,
598
- "variable": viv_compiler.config.ASSOCIATIONS_VARIABLE_NAME,
618
+ "variable": local_variable,
599
619
  "body": body,
600
620
  }
601
621
  }
@@ -641,6 +661,19 @@ class Visitor(arpeggio.PTNodeVisitor):
641
661
  conditional_object['value'].update(child)
642
662
  return conditional_object
643
663
 
664
+ @staticmethod
665
+ def visit_associations_conditional_branches(_, children: Any) -> dict[str, viv_compiler.types.Expression]:
666
+ """Visit a <associations_conditional_branches> node."""
667
+ return {"branches": children}
668
+
669
+ @staticmethod
670
+ def visit_associations_conditional_branch(_, children: Any) -> dict[str, viv_compiler.types.Expression]:
671
+ """Visit a <associations_conditional_branch> node."""
672
+ component = {}
673
+ for child in children:
674
+ component.update(child)
675
+ return component
676
+
644
677
  @staticmethod
645
678
  def visit_associations_conditional_consequent(_, children: Any) -> dict[str, list[viv_compiler.types.Expression]]:
646
679
  """Visit a <associations_conditional_consequent> node."""
@@ -709,10 +742,23 @@ class Visitor(arpeggio.PTNodeVisitor):
709
742
  @staticmethod
710
743
  def visit_conditional(_, children: Any) -> viv_compiler.types.Conditional:
711
744
  """Visit a <conditional> node."""
712
- conditional_object = {"type": ExpressionDiscriminator.CONDITIONAL, "value": {}}
745
+ component = {"type": ExpressionDiscriminator.CONDITIONAL, "value": {}}
713
746
  for child in children:
714
- conditional_object['value'].update(child)
715
- return conditional_object
747
+ component['value'].update(child)
748
+ return component
749
+
750
+ @staticmethod
751
+ def visit_conditional_branches(_, children: Any) -> dict[str, viv_compiler.types.Expression]:
752
+ """Visit a <conditional_branches> node."""
753
+ return {"branches": children}
754
+
755
+ @staticmethod
756
+ def visit_conditional_branch(_, children: Any) -> dict[str, viv_compiler.types.Expression]:
757
+ """Visit a <conditional_branch> node."""
758
+ component = {}
759
+ for child in children:
760
+ component.update(child)
761
+ return component
716
762
 
717
763
  @staticmethod
718
764
  def visit_condition(_, children: Any) -> dict[str, viv_compiler.types.Expression]:
@@ -732,10 +778,20 @@ class Visitor(arpeggio.PTNodeVisitor):
732
778
  @staticmethod
733
779
  def visit_loop(_, children: Any) -> viv_compiler.types.Loop:
734
780
  """Visit a <loop> node."""
735
- iterable_reference, variable_name, loop_body = children
781
+ iterable_reference, loop_variable, loop_body = children
736
782
  component = {
737
783
  "type": ExpressionDiscriminator.LOOP,
738
- "value": {"iterable": iterable_reference, "variable": variable_name['name'], "body": loop_body}
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"]
739
795
  }
740
796
  return component
741
797
 
@@ -912,68 +968,76 @@ class Visitor(arpeggio.PTNodeVisitor):
912
968
  return children
913
969
 
914
970
  @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
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
927
978
  # 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
- })
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
986
+ path = [
987
+ *viv_compiler.config.SCRATCH_VARIABLE_REFERENCE_PATH_PREFIX,
988
+ {
989
+ "type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_PROPERTY_NAME,
990
+ "name": children[0],
991
+ }
992
+ ]
935
993
  path += children[1] if len(children) > 1 else []
936
- else:
937
- anchor = children[0]["name"]
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]
938
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
939
1012
  component = {
940
- "type": ExpressionDiscriminator.ENTITY_REFERENCE,
1013
+ "type": component_type,
941
1014
  "value": {
942
- "anchor": anchor, # The name of the role anchoring this entity reference
943
- "path": path, # Sequence of components constituting a property path
1015
+ "local": anchor_is_local_variable,
1016
+ "anchor": anchor,
1017
+ "path": path,
944
1018
  }
945
1019
  }
946
1020
  return component
947
1021
 
948
1022
  @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
1023
+ def visit_entity_sigil(node: Any, _) -> Any:
1024
+ """Visit an <entity_sigil> node."""
1025
+ return node
961
1026
 
962
1027
  @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}
1028
+ def visit_symbol_sigil(node: Any, _) -> Any:
1029
+ """Visit a <symbol_sigil> node."""
1030
+ return node
967
1031
 
968
1032
  @staticmethod
969
- def visit_local_variable_reference(_, children: Any):
970
- """Visit a <local_variable_reference> node."""
971
- return {"name": children[0]}
1033
+ def visit_scratch_variable_sigil(node: Any, _) -> Any:
1034
+ """Visit a <scratch_variable_sigil> node."""
1035
+ return node
972
1036
 
973
1037
  @staticmethod
974
- def visit_global_variable_reference(_, children: Any):
975
- """Visit a <global_variable_reference> node."""
976
- return {"globalVariable": True, "name": children[0]}
1038
+ def visit_local_variable_sigil(node: Any, _) -> Any:
1039
+ """Visit a <local_variable_sigil> node."""
1040
+ return node
977
1041
 
978
1042
  @staticmethod
979
1043
  def visit_reference_path(_, children: Any) -> list[viv_compiler.types.ReferencePathComponent]:
@@ -1063,13 +1127,17 @@ class Visitor(arpeggio.PTNodeVisitor):
1063
1127
  @staticmethod
1064
1128
  def visit_enum(_, children: Any) -> viv_compiler.types.Enum:
1065
1129
  """Visit an <enum> node."""
1066
- scaled = "#" in children # As opposed to ##, which yields an unscaled enum
1067
- additive_inverse_present = "-" in children
1130
+ additive_inverse_present = False
1131
+ if len(children) > 1:
1132
+ additive_inverse_present = children[0] == "-"
1133
+ token = children[-1]
1134
+ unscaled = token[:2] == "##" # As opposed to '#', which yields a scaled enum
1135
+ name = token.lstrip('#')
1068
1136
  component = {
1069
1137
  "type": ExpressionDiscriminator.ENUM,
1070
1138
  "value": {
1071
- "name": children[-1],
1072
- "scaled": scaled,
1139
+ "name": name,
1140
+ "scaled": not unscaled,
1073
1141
  "minus": additive_inverse_present
1074
1142
  }
1075
1143
  }
@@ -1140,35 +1208,12 @@ class Visitor(arpeggio.PTNodeVisitor):
1140
1208
  component = {"type": ExpressionDiscriminator.NULL_TYPE, "value": None}
1141
1209
  return component
1142
1210
 
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
1211
  @staticmethod
1150
1212
  def visit_eval_fail_safe_marker(_, __: Any) -> viv_compiler.types.EvalFailSafeField:
1151
1213
  """Visit an <eval_fail_safe_marker> node."""
1152
1214
  component = {"type": ExpressionDiscriminator.EVAL_FAIL_SAFE}
1153
1215
  return component
1154
1216
 
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
1217
  @staticmethod
1173
1218
  def visit_trope_fit_expression(_, children: Any) -> viv_compiler.types.TropeFitExpression:
1174
1219
  """Visit a <trope_fit_expression> node."""