viv-compiler 0.1.0__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.
@@ -0,0 +1,14 @@
1
+ try:
2
+ from importlib.metadata import version, PackageNotFoundError
3
+ try:
4
+ __version__ = version("viv-compiler")
5
+ except PackageNotFoundError:
6
+ # If we're running from a repo checkout, not the published package,
7
+ # we can pull the version from file.
8
+ from ._version import __version__
9
+ except ImportError as e:
10
+ raise RuntimeError("viv_compiler appears to be corrupted: missing file `_version.py`") from e
11
+
12
+
13
+ from .api import compile_from_path, get_version, VivCompileError
14
+ __all__ = ["compile_from_path", "get_version", "VivCompileError", "__version__"]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+ if __name__ == "__main__":
3
+ main()
File without changes
@@ -0,0 +1,5 @@
1
+ // A test action serving a quick smoke test that the
2
+ // Viv compiler is functioning on the target machine.
3
+ action test:
4
+ roles:
5
+ x: initiator
viv_compiler/api.py ADDED
@@ -0,0 +1,58 @@
1
+ """API for the Viv compiler.
2
+
3
+ This API is for machines. For human use, see the CLI exposed in `cli.py`.
4
+ """
5
+
6
+ from pathlib import Path
7
+ from typing import Sequence
8
+ from viv_compiler import __version__
9
+ from viv_compiler.types import CompiledContentBundle
10
+ from .core import compile_viv_source_code
11
+
12
+
13
+ def compile_from_path(
14
+ *, # Require keyword arguments only
15
+ source_file_path: Path,
16
+ default_salience: float = 1.0,
17
+ default_associations: Sequence[str] = (),
18
+ default_reaction_priority: float = 1.0,
19
+ use_memoization: bool = True,
20
+ debug: bool = False,
21
+ ) -> CompiledContentBundle:
22
+ """Compile the given Viv source file to produce a JSON-serializable compiled content bundle.
23
+
24
+ Args:
25
+ source_file_path: The absolute path to the Viv source file to be parsed.
26
+ default_salience: A user-provided default salience value to use when one is not specified
27
+ in an action definition.
28
+ default_associations: A user-provided default associations value to use when one is not
29
+ specified in an action definition.
30
+ default_reaction_priority: A user-provided default reaction priority to use when one is not
31
+ specified in a reaction declaration.
32
+ use_memoization: Whether to use memoization during PEG parsing (faster, but uses more memory).
33
+ debug: Whether to invoke verbose debugging for the PEG parser itself.
34
+
35
+ Returns:
36
+ The compiled content bundle.
37
+ """
38
+ try:
39
+ return compile_viv_source_code(
40
+ source_file_path=source_file_path,
41
+ default_salience=float(default_salience),
42
+ default_associations=list(default_associations),
43
+ default_reaction_priority=float(default_reaction_priority),
44
+ use_memoization=use_memoization,
45
+ debug=debug,
46
+ )
47
+ except Exception as e:
48
+ raise VivCompileError(str(e)) from e
49
+
50
+
51
+ def get_version() -> str:
52
+ """Return the Viv version number associated with this compiler instance."""
53
+ return __version__
54
+
55
+
56
+ class VivCompileError(Exception):
57
+ """Raised when Viv compilation fails."""
58
+ pass
@@ -0,0 +1 @@
1
+ from .backports import *
@@ -0,0 +1,12 @@
1
+ """Backports for <3.11 Python functionality."""
2
+
3
+ import sys
4
+ from enum import Enum
5
+
6
+
7
+ if sys.version_info >= (3, 11):
8
+ from enum import StrEnum
9
+ else:
10
+ class StrEnum(str, Enum):
11
+ """Backport of Python 3.11's StrEnum."""
12
+ pass
viv_compiler/cli.py ADDED
@@ -0,0 +1,237 @@
1
+ """Command‑line interface (CLI) for the Viv DSL compiler.
2
+
3
+ This CLI is for humans. For programmatic use, call the compiler functions exposed in `api.py`.
4
+ """
5
+
6
+ import sys
7
+ import json
8
+ import argparse
9
+ import traceback
10
+ import viv_compiler.config
11
+ from pathlib import Path
12
+ from importlib import resources
13
+ from .api import compile_from_path, get_version
14
+ from viv_compiler.types import CompiledContentBundle
15
+
16
+
17
+ def main() -> None:
18
+ """Command-line interface (CLI) for the Viv DSL compiler."""
19
+ # Build the parser for command-line arguments
20
+ parser = _build_parser()
21
+ # Parse the command-line arguments
22
+ args = parser.parse_args()
23
+ # If the user has requested the compiler version, print it and exit
24
+ if args.version:
25
+ print(f"\nviv_compiler {get_version()}\n")
26
+ sys.exit(0)
27
+ # If test mode is engaged, invoke the compiler on a test file and exit
28
+ if args.test:
29
+ _run_smoke_test(args=args)
30
+ sys.exit(0)
31
+ elif not args.input:
32
+ parser.error("Unless the 'test' flag is engaged, an action file must be provided")
33
+ # Otherwise, it's showtime, so let's invoke the compiler
34
+ print("\nCompiling...\n", file=sys.stderr)
35
+ if args.output:
36
+ path_to_output_file = Path(args.output).expanduser().resolve()
37
+ if not path_to_output_file.parent.exists():
38
+ raise FileNotFoundError(f"Output-file directory does not exist: {path_to_output_file.parent}")
39
+ else:
40
+ path_to_output_file = None
41
+ compiled_content_bundle = _invoke_compiler(args=args)
42
+ # If we get to here, compilation succeeded
43
+ print("Success!\n", file=sys.stderr)
44
+ _emit_results(compiled_content_bundle=compiled_content_bundle, args=args, path_to_output_file=path_to_output_file)
45
+
46
+
47
+ def _build_parser() -> argparse.ArgumentParser:
48
+ """Build the parser for our command-line arguments.
49
+
50
+ Returns:
51
+ A prepared parser for our command-line arguments.
52
+ """
53
+ parser = argparse.ArgumentParser(
54
+ description="Compile a Viv source file (.viv) to produce a content bundle ready for use in a Viv runtime",
55
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter
56
+ )
57
+ parser.add_argument(
58
+ '-i',
59
+ '--input',
60
+ metavar='source_file',
61
+ type=str,
62
+ help='relative or absolute path to the Viv source file (.viv) to be compiled'
63
+ )
64
+ parser.add_argument(
65
+ '-o',
66
+ '--output',
67
+ metavar='output_file',
68
+ type=str,
69
+ default=None,
70
+ help='path to which output file (.json) will be written'
71
+ )
72
+ parser.add_argument(
73
+ '-s',
74
+ '--default_salience',
75
+ metavar='default_salience',
76
+ type=float,
77
+ default=viv_compiler.config.DEFAULT_SALIENCE_VALUE,
78
+ help='default salience value to use when one is not specified'
79
+ )
80
+ parser.add_argument(
81
+ '-a',
82
+ '--default_associations',
83
+ metavar='default_associations',
84
+ type=str,
85
+ nargs='*',
86
+ default=viv_compiler.config.DEFAULT_ASSOCIATIONS_VALUE,
87
+ help='default associations (sequence of strings) to use when not specified'
88
+ )
89
+ parser.add_argument(
90
+ '-r',
91
+ '--default_reaction_priority',
92
+ metavar='default_reaction_priority',
93
+ type=float,
94
+ default=viv_compiler.config.DEFAULT_REACTION_PRIORITY_VALUE,
95
+ help='default reaction priority to use when not specified'
96
+ )
97
+ parser.add_argument(
98
+ '-p',
99
+ '--print',
100
+ action='store_true',
101
+ default=False,
102
+ help='after compilation, print compiled content bundle in console'
103
+ )
104
+ parser.add_argument(
105
+ '-l',
106
+ '--list',
107
+ action='store_true',
108
+ default=False,
109
+ help='after compilation, list all compiled actions in console'
110
+ )
111
+ parser.add_argument(
112
+ '-m',
113
+ '--memoization',
114
+ action=argparse.BooleanOptionalAction,
115
+ default=True,
116
+ help='enable/disable memoization in the underlying PEG parser',
117
+ )
118
+ parser.add_argument(
119
+ '-d',
120
+ '--debug',
121
+ action='store_true',
122
+ default=False,
123
+ help='engage debug mode in the underlying PEG parser'
124
+ )
125
+ parser.add_argument(
126
+ '-t',
127
+ '--test',
128
+ action='store_true',
129
+ default=False,
130
+ help='run a simple smoke test to confirm the compiler is installed correctly'
131
+ )
132
+ parser.add_argument(
133
+ '-v',
134
+ '--version',
135
+ action='store_true',
136
+ help='print compiler version and exit'
137
+ )
138
+ return parser
139
+
140
+
141
+ def _run_smoke_test(args: argparse.Namespace) -> None:
142
+ """Runs a smoke test to confirm that the Viv compiler installation appears to be functioning.
143
+
144
+ Args:
145
+ args: Parsed command-line arguments.
146
+
147
+ Side Effects:
148
+ The results are printed out to `stderr`.
149
+ """
150
+ with resources.as_file(resources.files("viv_compiler._samples") / "smoke-test.viv") as sample_path:
151
+ compile_from_path(
152
+ source_file_path=sample_path,
153
+ default_salience=float(args.default_salience),
154
+ default_associations=args.default_associations,
155
+ default_reaction_priority=float(args.default_priority),
156
+ debug=False,
157
+ use_memoization=True,
158
+ )
159
+ print("Smoke test passed: Viv compiler installation looks good.\n", file=sys.stderr)
160
+
161
+
162
+ def _invoke_compiler(args: argparse.Namespace) -> CompiledContentBundle:
163
+ """Invokes the compiler on the user's specified source file, with their specified configuration settings.
164
+
165
+ Args:
166
+ args: Parsed command-line arguments.
167
+
168
+ Returns:
169
+ The compiled content bundle, if compilation succeeds.
170
+ """
171
+ source_file_path = Path(args.input).expanduser().resolve()
172
+ try:
173
+ compiled_content_bundle = compile_from_path(
174
+ source_file_path=source_file_path,
175
+ default_salience=float(args.default_salience),
176
+ default_associations=args.default_associations,
177
+ default_reaction_priority=float(args.default_priority),
178
+ debug=args.debug,
179
+ use_memoization=args.memoization,
180
+ )
181
+ return compiled_content_bundle
182
+ except KeyboardInterrupt:
183
+ sys.exit(130)
184
+ except BrokenPipeError:
185
+ # noinspection PyBroadException
186
+ try:
187
+ sys.stderr.close()
188
+ except Exception:
189
+ pass
190
+ sys.exit(1)
191
+ except Exception as e:
192
+ print(f"Error encountered during compilation: {e}", file=sys.stderr)
193
+ if args.debug:
194
+ traceback.print_exc()
195
+ sys.exit(1)
196
+
197
+
198
+ def _emit_results(
199
+ compiled_content_bundle: CompiledContentBundle,
200
+ args: argparse.Namespace,
201
+ path_to_output_file: Path | None
202
+ ) -> None:
203
+ """Emits the compiled content bundle according to the user's specified output parameters.
204
+
205
+ Args:
206
+ compiled_content_bundle: A compiled content bundle.
207
+ args: Parsed command-line arguments.
208
+
209
+ Side Effects:
210
+ The results are written to file and/or printed to `stderr` and `stdout`, depending on user parameters.
211
+ """
212
+ # If we're to print out the result, let's do so now, via `stdout` (with headers piped to `stderr`)
213
+ if args.print:
214
+ print("\t== Result ==\n", file=sys.stderr)
215
+ sys.stdout.write(json.dumps(compiled_content_bundle, indent=2, sort_keys=True))
216
+ sys.stdout.write("\n\n")
217
+ # If we're to list out the compiled actions, let's do so now (again via
218
+ # `stdout`, with headers piped to `stderr`).
219
+ if args.list:
220
+ lines = []
221
+ action_names = [action_definition['name'] for action_definition in compiled_content_bundle['actions'].values()]
222
+ for action_name in sorted(action_names):
223
+ lines.append(action_name)
224
+ if not action_names:
225
+ lines.append("N/A")
226
+ print(f"\t== Actions ({len(action_names)}) ==\n", file=sys.stderr)
227
+ print("\n".join(f"- {line}" for line in lines), file=sys.stderr)
228
+ print("", file=sys.stderr)
229
+ # If an output file path has been provided, write the output file to the specified path
230
+ if path_to_output_file:
231
+ with open(path_to_output_file, "w", encoding="utf-8") as outfile:
232
+ outfile.write(json.dumps(compiled_content_bundle, ensure_ascii=False))
233
+ print(f"Wrote output to file: {path_to_output_file}\n", file=sys.stderr)
234
+
235
+
236
+ if __name__ == "__main__":
237
+ main()
@@ -0,0 +1 @@
1
+ from .config import *
@@ -0,0 +1,88 @@
1
+ """System configuration for the Viv DSL compiler.
2
+
3
+ Defines a set of constants constituting the configuration parameters necessary for the function
4
+ of the compiler. These are only meant to be modified in the course of system development, i.e.,
5
+ not by users. That said, user-supplied default values for the `saliences` and `associations`
6
+ fields will be used programmatically to update the dictionary below containing default values
7
+ for optional fields in action definitions.
8
+ """
9
+
10
+ from viv_compiler.types import ExpressionDiscriminator, ReferencePathComponentDiscriminator
11
+
12
+
13
+ # Name of the root symbol in the Viv DSL grammar
14
+ GRAMMAR_ROOT_SYMBOL = "file"
15
+
16
+ # Name of the symbol associated with comments in the Viv DSL grammar
17
+ GRAMMAR_COMMENT_SYMBOL = "comment"
18
+
19
+ # Viv expression types that support negation. While the grammar (and therefore the parser)
20
+ # will allow for negation in other expression types, the validator will use this config
21
+ # parameter to enforce this policy.
22
+ NEGATABLE_EXPRESSION_TYPES = {
23
+ ExpressionDiscriminator.ADAPTER_FUNCTION_CALL,
24
+ ExpressionDiscriminator.COMPARISON,
25
+ ExpressionDiscriminator.CONJUNCTION,
26
+ ExpressionDiscriminator.DISJUNCTION,
27
+ ExpressionDiscriminator.ENTITY_REFERENCE,
28
+ ExpressionDiscriminator.LOCAL_VARIABLE_REFERENCE,
29
+ ExpressionDiscriminator.LOOP,
30
+ ExpressionDiscriminator.MEMBERSHIP_TEST,
31
+ ExpressionDiscriminator.TROPE_FIT_EXPRESSION,
32
+ }
33
+
34
+ # Default values for various optional fields in an action definition
35
+ ACTION_DEFINITION_OPTIONAL_FIELD_DEFAULT_VALUES = {
36
+ "gloss": None,
37
+ "report": None,
38
+ "control": {},
39
+ "tags": {"type": "list", "value": []}, # This is an expression that produces a list, not a list itself
40
+ "preconditions": [],
41
+ "scratch": [],
42
+ "effects": [],
43
+ "reactions": [],
44
+ "saliences": None, # Set by user via CLI, which uses a default value if none is provided
45
+ "associations": None, # Set by user via CLI, which uses a default value if none is provided
46
+ "embargoes": [],
47
+ }
48
+
49
+ # Default values for the options in an action definition's reaction field
50
+ REACTION_FIELD_DEFAULT_OPTIONS = {
51
+ "urgent": {"type": "bool", "value": False},
52
+ "priority": None, # Set by user via CLI, which uses a default value if none is provided
53
+ "killCode": None,
54
+ "where": None,
55
+ "when": None,
56
+ "abandonmentConditions": [],
57
+ }
58
+
59
+ # The path to which the global-variable sigil `$` expands. This sigil is really just syntactic sugar for
60
+ # the path `@this.scratch`, which stores a blackboard local to a performed action. For instance, the scratch
61
+ # operation `$foo.bar = 99` is syntactic sugar for the expression `@this.scratch.foo.bar = 99`.
62
+ GLOBAL_VARIABLE_REFERENCE_ANCHOR = "this"
63
+ GLOBAL_VARIABLE_REFERENCE_PATH_PREFIX = [{
64
+ "type": ReferencePathComponentDiscriminator.REFERENCE_PATH_COMPONENT_PROPERTY_NAME,
65
+ "name": "scratch",
66
+ }]
67
+
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
+ # A default salience value, to be used when the API caller does not provide one
82
+ DEFAULT_SALIENCE_VALUE = 1.0
83
+
84
+ # A default associations value, to be used when the API caller does not provide one
85
+ DEFAULT_ASSOCIATIONS_VALUE = []
86
+
87
+ # A default reaction priority value, to be used when the API caller does not provide one
88
+ DEFAULT_REACTION_PRIORITY_VALUE = 1.0
@@ -0,0 +1,5 @@
1
+ from .core import *
2
+ from .importer import *
3
+ from .postprocessor import *
4
+ from .validator import *
5
+ from .visitor import *
@@ -0,0 +1,185 @@
1
+ """Compiler for the Viv DSL.
2
+
3
+ This high-level module invokes other components of the compiler out the full compilation pipeline:
4
+
5
+ * Loading and parsing of the Viv DSL grammar.
6
+ * Loading and parsing of a Viv source file.
7
+ * Walking the parse tree with a visitor, to produce an AST.
8
+ * Combining of ASTs, to honor 'include' statements (imports between Viv files).
9
+ * Postprocessing of a combined AST, to produce full-fledged trope and action definitions,
10
+ together constituting a Viv compiled content bundle.
11
+ * Validation of the Viv compiled content bundle.
12
+ * Emitting JSON output for the validated Viv compiled content bundle.
13
+
14
+ The entrypoint function is `compile_viv_source_code()`, and everything else
15
+ is only meant to be invoked internally, i.e., within this module.
16
+ """
17
+
18
+ __all__ = ["compile_viv_source_code"]
19
+
20
+ import arpeggio
21
+ import viv_compiler.config
22
+ import viv_compiler.types
23
+ from typing import Any
24
+ from pathlib import Path
25
+ from importlib.resources import files
26
+ from .importer import integrate_imported_files
27
+ from .visitor import Visitor
28
+ from .postprocessor import postprocess_combined_ast
29
+ from .validator import validate_content_bundle
30
+ # noinspection PyUnresolvedReferences
31
+ from arpeggio.cleanpeg import ParserPEG
32
+
33
+
34
+ def compile_viv_source_code(
35
+ source_file_path: Path,
36
+ default_salience: float,
37
+ default_associations: list[str],
38
+ default_reaction_priority: float,
39
+ use_memoization: bool,
40
+ debug = False,
41
+ ) -> viv_compiler.types.CompiledContentBundle:
42
+ """Compile the given Viv source file to produce a JSON-serializable compiled content bundle.
43
+
44
+ Args:
45
+ source_file_path: The absolute path to the Viv source file to be parsed.
46
+ default_salience: A user-provided default salience value to use when one is not specified
47
+ in an action definition.
48
+ default_associations: A user-provided default associations value to use when one is not
49
+ specified in an action definition.
50
+ default_reaction_priority: A user-provided default reaction priority to use when one is not
51
+ specified in a reaction declaration.
52
+ use_memoization: Whether to use memoization during PEG parsing (faster, but uses more memory).
53
+ debug: Whether to invoke verbose debugging for the PEG parser itself (default: `False`).
54
+
55
+ Returns:
56
+ The compiled content bundle.
57
+ """
58
+ # Honor user-supplied config parameters, or the associated default values if none were supplied
59
+ _honor_user_supplied_config_parameters(
60
+ default_salience=default_salience,
61
+ default_associations=default_associations,
62
+ default_reaction_priority=default_reaction_priority,
63
+ )
64
+ # Create a Viv parser
65
+ viv_parser = _create_viv_parser(use_memoization=use_memoization, debug=debug)
66
+ # Load the source file to be compiled
67
+ source_file_contents = _load_source_file(source_file_path=source_file_path)
68
+ # Parse the source file to produce a parse tree
69
+ tree = viv_parser.parse(_input=source_file_contents)
70
+ # Following the visitor pattern in parsing, traverse the parse tree to gradually
71
+ # construct an abstract syntax tree (AST).
72
+ ast: viv_compiler.types.AST = _sanitize_ast(ast=arpeggio.visit_parse_tree(tree, Visitor()))
73
+ # If there are any include declarations (i.e., import statements), honor those now
74
+ combined_ast: viv_compiler.types.CombinedAST = integrate_imported_files(
75
+ viv_parser=viv_parser,
76
+ ast=ast,
77
+ entry_point_file_path=source_file_path
78
+ )
79
+ # Conduct postprocessing to produce a final compiled content bundle
80
+ compiled_content_bundle: viv_compiler.types.CompiledContentBundle = postprocess_combined_ast(
81
+ combined_ast=combined_ast
82
+ )
83
+ # Conduct final validation. This will throw an error if any serious issues are detected.
84
+ validate_content_bundle(content_bundle=compiled_content_bundle)
85
+ # Finally, return the compiled content bundle
86
+ return compiled_content_bundle
87
+
88
+
89
+ def _honor_user_supplied_config_parameters(
90
+ default_salience: float,
91
+ default_associations: list[str],
92
+ default_reaction_priority: float,
93
+ ) -> None:
94
+ """Updates the global Viv compiler config to honor any user-supplied parameters.
95
+
96
+ Args:
97
+ default_salience: A user-provided default salience value to use when one is not specified in
98
+ an action definition. The CLI provides a default value if the user does not supply one.
99
+ default_associations: A user-provided default associations value to use when one is not specified
100
+ in an action definition. The CLI provides a default value if the user does not supply one.
101
+ default_reaction_priority: A user-provided default reaction priority to use when one is not
102
+ specified in a reaction declaration.
103
+
104
+ Returns:
105
+ The compiled content bundle JSON string, if no output file path was provided, else None.
106
+ """
107
+ viv_compiler.config.ACTION_DEFINITION_OPTIONAL_FIELD_DEFAULT_VALUES["saliences"] = {
108
+ "default": {'type': 'float', 'value': default_salience},
109
+ "variable": viv_compiler.config.SALIENCES_VARIABLE_NAME,
110
+ "body": [],
111
+ }
112
+ default_associations_expression = {
113
+ "type": "list",
114
+ "value": [{"type": "string", "value": association} for association in default_associations]
115
+ }
116
+ viv_compiler.config.ACTION_DEFINITION_OPTIONAL_FIELD_DEFAULT_VALUES["associations"] = {
117
+ "default": default_associations_expression,
118
+ "variable": viv_compiler.config.ASSOCIATIONS_VARIABLE_NAME,
119
+ "body": [],
120
+ }
121
+ viv_compiler.config.REACTION_FIELD_DEFAULT_OPTIONS["priority"] = {
122
+ "type": "float",
123
+ "value": default_reaction_priority
124
+ }
125
+
126
+
127
+ def _create_viv_parser(use_memoization: bool, debug: bool) -> ParserPEG:
128
+ """Return a PEG parser initialized for the Viv DSL, with user-defined settings.
129
+
130
+ Args:
131
+ use_memoization: Whether to use memoization during PEG parsing (faster, but uses more memory).
132
+ debug: Whether to invoke verbose debugging for the PEG parser itself.
133
+
134
+ Returns:
135
+ A PEG parser initialized for the Viv DSL.
136
+ """
137
+ # Load the Viv DSL grammar
138
+ viv_grammar = files("viv_compiler.grammar").joinpath("viv.peg").read_text(encoding="utf-8")
139
+ # Prepare and return the parser
140
+ viv_parser = ParserPEG(
141
+ language_def=viv_grammar,
142
+ root_rule_name=viv_compiler.config.GRAMMAR_ROOT_SYMBOL,
143
+ comment_rule_name=viv_compiler.config.GRAMMAR_COMMENT_SYMBOL,
144
+ reduce_tree=False,
145
+ ws="\t\n\r ",
146
+ memoization=use_memoization,
147
+ debug=debug
148
+ )
149
+ return viv_parser
150
+
151
+
152
+ def _load_source_file(source_file_path: Path) -> str:
153
+ """Return the contents of the Viv source file at the given path.
154
+
155
+ Args:
156
+ source_file_path: Path to the Viv source file to be compiled.
157
+
158
+ Returns:
159
+ Contents of the Viv source file at the given path.
160
+ """
161
+ try:
162
+ source_file_contents = open(source_file_path, encoding="utf8").read()
163
+ except FileNotFoundError:
164
+ raise FileNotFoundError(f"Bad input file path (file not found): {source_file_path}")
165
+ return source_file_contents
166
+
167
+
168
+ def _sanitize_ast(ast: Any) -> Any:
169
+ """Returns a deep copy of the given AST with all (nested) Arpeggio containers replaced with plain lists.
170
+
171
+ Args:
172
+ ast: An AST produced by our visitor.
173
+
174
+ Returns:
175
+ A deep copy of the given AST with all (nested) Arpeggio containers replaced with plain lists.
176
+ """
177
+ if isinstance(ast, arpeggio.SemanticActionResults):
178
+ return [_sanitize_ast(v) for v in ast]
179
+ if isinstance(ast, dict):
180
+ return {k: _sanitize_ast(v) for k, v in ast.items()}
181
+ if isinstance(ast, list):
182
+ return [_sanitize_ast(v) for v in ast]
183
+ if isinstance(ast, tuple):
184
+ return [_sanitize_ast(v) for v in ast]
185
+ return ast