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.
@@ -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."""
@@ -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 'min' in child:
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.ReactionBinding | viv_compiler.types.Loop:
407
+ def visit_binding(_, children: Any) -> viv_compiler.types.ReactionRoleBindings:
392
408
  """Visit a <binding> node."""
393
- # Support both (name ":" reference) and the 'loop' alternative per the grammar
394
- if len(children) == 1:
395
- if isinstance(children[0], dict) and children[0].get("type") == ExpressionDiscriminator.LOOP:
396
- return children[0]
397
- role_name, entity_to_bind = children
398
- 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
+ }
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
- hour = (raw_hour % 12) + (12 if period.upper() == 'PM' else 0)
530
- if len(children) == 2: # Only the hour was provided
531
- component = {
532
- "type": "time",
533
- "hour": hour,
534
- "minute": 0
535
- }
536
- else:
537
- component = {
538
- "type": "time",
539
- "hour": hour,
540
- "minute": int(children[1])
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
- default_value_expression = children[0]
563
- 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
564
582
  component = {
565
583
  "saliences": {
566
584
  "default": default_value_expression,
567
- "variable": viv_compiler.config.SALIENCES_VARIABLE_NAME,
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
- default_value_expression = children[0]
593
- 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
594
615
  component = {
595
616
  "associations": {
596
617
  "default": default_value_expression,
597
- "variable": viv_compiler.config.ASSOCIATIONS_VARIABLE_NAME,
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, variable_name, loop_body = children
781
+ iterable_reference, loop_variable, loop_body = children
761
782
  component = {
762
783
  "type": ExpressionDiscriminator.LOOP,
763
- "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"]
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
- _, children: Any
942
- ) -> viv_compiler.types.EntityReference | viv_compiler.types.LocalVariableReference:
943
- return children[0]
944
-
945
- @staticmethod
946
- def visit_role_anchored_reference(_, children: Any) -> viv_compiler.types.EntityReference:
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
- anchor = viv_compiler.config.GLOBAL_VARIABLE_REFERENCE_ANCHOR
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.GLOBAL_VARIABLE_REFERENCE_PATH_PREFIX,
987
+ *viv_compiler.config.SCRATCH_VARIABLE_REFERENCE_PATH_PREFIX,
956
988
  {
957
989
  "type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_PROPERTY_NAME,
958
- "name": children[0]["name"],
990
+ "name": children[0],
959
991
  }
960
992
  ]
961
993
  path += children[1] if len(children) > 1 else []
962
- else:
963
- 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]
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": ExpressionDiscriminator.ENTITY_REFERENCE,
1013
+ "type": component_type,
967
1014
  "value": {
968
- "anchor": anchor, # The name of the role anchoring this entity reference
969
- "path": path, # Sequence of components constituting a property path
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 visit_local_variable_anchored_reference(_, children: Any) -> viv_compiler.types.LocalVariableReference:
976
- """Visit a <local_variable_anchored_reference> node."""
977
- anchor = children[0]["name"]
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 visit_role_reference(_, children: Any) -> dict[str, str]:
990
- """Visit a <role_reference> node."""
991
- name = children[0]
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 visit_local_variable_reference(_, children: Any):
996
- """Visit a <local_variable_reference> node."""
997
- return {"name": children[0]}
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 visit_global_variable_reference(_, children: Any):
1001
- """Visit a <global_variable_reference> node."""
1002
- 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
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 an scaled enum
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."""