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.
- viv_compiler/__init__.py +14 -0
- viv_compiler/__main__.py +3 -0
- viv_compiler/_samples/__init__.py +0 -0
- viv_compiler/_samples/smoke-test.viv +5 -0
- viv_compiler/api.py +58 -0
- viv_compiler/backports/__init__.py +1 -0
- viv_compiler/backports/backports.py +12 -0
- viv_compiler/cli.py +237 -0
- viv_compiler/config/__init__.py +1 -0
- viv_compiler/config/config.py +88 -0
- viv_compiler/core/__init__.py +5 -0
- viv_compiler/core/core.py +185 -0
- viv_compiler/core/importer.py +111 -0
- viv_compiler/core/postprocessor.py +749 -0
- viv_compiler/core/validator.py +915 -0
- viv_compiler/core/visitor.py +1188 -0
- viv_compiler/grammar/__init__.py +0 -0
- viv_compiler/grammar/viv.peg +228 -0
- viv_compiler/py.typed +1 -0
- viv_compiler/types/__init__.py +3 -0
- viv_compiler/types/content_public_schemas.py +420 -0
- viv_compiler/types/dsl_public_schemas.py +566 -0
- viv_compiler/types/internal_types.py +167 -0
- viv_compiler/utils/__init__.py +1 -0
- viv_compiler/utils/_version.py +2 -0
- viv_compiler/utils/utils.py +171 -0
- viv_compiler-0.1.0.dist-info/METADATA +284 -0
- viv_compiler-0.1.0.dist-info/RECORD +32 -0
- viv_compiler-0.1.0.dist-info/WHEEL +5 -0
- viv_compiler-0.1.0.dist-info/entry_points.txt +3 -0
- viv_compiler-0.1.0.dist-info/licenses/LICENSE +21 -0
- viv_compiler-0.1.0.dist-info/top_level.txt +1 -0
viv_compiler/__init__.py
ADDED
@@ -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__"]
|
viv_compiler/__main__.py
ADDED
File without changes
|
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 *
|
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,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
|