plcc-ng 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.
- plcc/__init__.py +0 -0
- plcc/cmd/__init__.py +0 -0
- plcc/cmd/make.py +140 -0
- plcc/cmd/make_test.py +74 -0
- plcc/cmd/parse.py +146 -0
- plcc/cmd/rep.py +190 -0
- plcc/cmd/scan.py +112 -0
- plcc/cmd/skeleton_test.py +0 -0
- plcc/diagram/__init__.py +0 -0
- plcc/diagram/dispatch.py +48 -0
- plcc/diagram/dispatch_test.py +86 -0
- plcc/diagram/list.py +68 -0
- plcc/diagram/list_test.py +32 -0
- plcc/diagram/plantuml/__init__.py +0 -0
- plcc/diagram/plantuml/emit.py +61 -0
- plcc/diagram/plantuml/emit_test.py +79 -0
- plcc/lang/__init__.py +0 -0
- plcc/lang/build.py +43 -0
- plcc/lang/build_test.py +22 -0
- plcc/lang/emit.py +51 -0
- plcc/lang/emit_test.py +29 -0
- plcc/lang/ext/__init__.py +0 -0
- plcc/lang/ext/java/__init__.py +0 -0
- plcc/lang/ext/java/build.py +53 -0
- plcc/lang/ext/java/emit.py +106 -0
- plcc/lang/ext/java/emit_test.py +180 -0
- plcc/lang/ext/java/run.py +50 -0
- plcc/lang/ext/java/runtime/Deserializer.java +46 -0
- plcc/lang/ext/java/runtime/Node.java +4 -0
- plcc/lang/ext/java/runtime/Registry.java +28 -0
- plcc/lang/ext/java/runtime/Token.java +16 -0
- plcc/lang/ext/java/runtime/org.json-20250107.jar +0 -0
- plcc/lang/ext/java/templates/Main.java.jinja +37 -0
- plcc/lang/ext/java/templates/class_file.java.jinja +39 -0
- plcc/lang/ext/python/__init__.py +0 -0
- plcc/lang/ext/python/emit.py +100 -0
- plcc/lang/ext/python/emit_test.py +132 -0
- plcc/lang/ext/python/run.py +44 -0
- plcc/lang/ext/python/runtime/__init__.py +0 -0
- plcc/lang/ext/python/runtime/base.py +8 -0
- plcc/lang/ext/python/runtime/base_test.py +17 -0
- plcc/lang/ext/python/runtime/deserialize.py +22 -0
- plcc/lang/ext/python/runtime/deserialize_test.py +115 -0
- plcc/lang/ext/python/runtime/registry.py +27 -0
- plcc/lang/ext/python/runtime/registry_test.py +67 -0
- plcc/lang/ext/python/templates/class_file.py.jinja +21 -0
- plcc/lang/ext/python/templates/main.py.jinja +22 -0
- plcc/lang/list.py +69 -0
- plcc/lang/list_test.py +27 -0
- plcc/lang/run.py +45 -0
- plcc/lines/Line.py +8 -0
- plcc/lines/__init__.py +2 -0
- plcc/lines/parseLines.py +16 -0
- plcc/lines/parse_from_file.py +10 -0
- plcc/lines/parse_from_string.py +5 -0
- plcc/lines/parse_from_string_test.py +54 -0
- plcc/lines/parse_from_strings.py +6 -0
- plcc/ll1/__init__.py +0 -0
- plcc/ll1/ll1_cli.py +64 -0
- plcc/ll1/ll1_cli_test.py +93 -0
- plcc/ll1/ll1_result_builder.py +122 -0
- plcc/ll1/ll1_result_builder_test.py +225 -0
- plcc/ll1/spec_json_decoder.py +70 -0
- plcc/ll1/spec_json_decoder_test.py +184 -0
- plcc/model/__init__.py +0 -0
- plcc/model/build_model.py +155 -0
- plcc/model/build_model_test.py +468 -0
- plcc/model/model_cli.py +44 -0
- plcc/model/model_cli_test.py +62 -0
- plcc/parser/__init__.py +0 -0
- plcc/parser/list_cli.py +67 -0
- plcc/parser/predictive_parser.py +152 -0
- plcc/parser/predictive_parser_test.py +263 -0
- plcc/parser/table_cli.py +89 -0
- plcc/parser/table_cli_test.py +161 -0
- plcc/scan/LexError.py +12 -0
- plcc/scan/Skip.py +11 -0
- plcc/scan/Token.py +9 -0
- plcc/scan/__init__.py +0 -0
- plcc/scan/matcher.py +61 -0
- plcc/scan/matcher_test.py +126 -0
- plcc/scan/scanner.py +23 -0
- plcc/scan/scanner_test.py +101 -0
- plcc/scan/sink.py +12 -0
- plcc/scan/sink_test.py +118 -0
- plcc/scan/source.py +25 -0
- plcc/scan/source_test.py +119 -0
- plcc/schemas/ll1.schema.json +65 -0
- plcc/schemas/model.schema.json +61 -0
- plcc/schemas/spec.schema.json +46 -0
- plcc/schemas/token.schema.json +21 -0
- plcc/schemas/tree.schema.json +34 -0
- plcc/spec/Spec.py +10 -0
- plcc/spec/SpecError.py +11 -0
- plcc/spec/SpecError_test.py +34 -0
- plcc/spec/ValidationError.py +4 -0
- plcc/spec/__init__.py +48 -0
- plcc/spec/lexical/DuplicateName.py +7 -0
- plcc/spec/lexical/LexicalRule.py +11 -0
- plcc/spec/lexical/LexicalSpec.py +12 -0
- plcc/spec/lexical/LexicalSpecError.py +5 -0
- plcc/spec/lexical/NameExpected.py +8 -0
- plcc/spec/lexical/Parser.py +83 -0
- plcc/spec/lexical/PatternCompilationError.py +6 -0
- plcc/spec/lexical/PatternDelimiterExpected.py +6 -0
- plcc/spec/lexical/PatternExpected.py +5 -0
- plcc/spec/lexical/UnexpectedContent.py +5 -0
- plcc/spec/lexical/__init__.py +10 -0
- plcc/spec/lexical/check_for_duplicate_names.py +12 -0
- plcc/spec/lexical/parseLexicalSpec.py +10 -0
- plcc/spec/lexical/parse_from_lines.py +4 -0
- plcc/spec/lexical/parse_from_string.py +7 -0
- plcc/spec/lexical/parse_lexical_test.py +247 -0
- plcc/spec/parseSpec.py +13 -0
- plcc/spec/parseSpec_test.py +50 -0
- plcc/spec/plcc_spec_cli.py +50 -0
- plcc/spec/plcc_spec_cli_test.py +49 -0
- plcc/spec/rough/Block.py +8 -0
- plcc/spec/rough/CircularIncludeError.py +5 -0
- plcc/spec/rough/Divider.py +11 -0
- plcc/spec/rough/Include.py +9 -0
- plcc/spec/rough/UnclosedBlockError.py +5 -0
- plcc/spec/rough/__init__.py +6 -0
- plcc/spec/rough/iterate_rough.py +16 -0
- plcc/spec/rough/parseRough.py +10 -0
- plcc/spec/rough/parseRough_test.py +75 -0
- plcc/spec/rough/parse_blocks.py +66 -0
- plcc/spec/rough/parse_blocks_test.py +95 -0
- plcc/spec/rough/parse_dividers.py +77 -0
- plcc/spec/rough/parse_dividers_test.py +88 -0
- plcc/spec/rough/parse_from_lines.py +6 -0
- plcc/spec/rough/parse_from_lines_test.py +9 -0
- plcc/spec/rough/parse_from_string.py +6 -0
- plcc/spec/rough/parse_includes.py +18 -0
- plcc/spec/rough/parse_includes_test.py +53 -0
- plcc/spec/rough/raise_handler.py +2 -0
- plcc/spec/rough/resolve_includes.py +69 -0
- plcc/spec/rough/resolve_includes_test.py +52 -0
- plcc/spec/semantics/CodeFragment.py +10 -0
- plcc/spec/semantics/InvalidClassNameError.py +10 -0
- plcc/spec/semantics/SemanticSpec.py +11 -0
- plcc/spec/semantics/TargetLocator.py +10 -0
- plcc/spec/semantics/UndefinedBlockError.py +10 -0
- plcc/spec/semantics/UndefinedTargetLocatorError.py +10 -0
- plcc/spec/semantics/__init__.py +2 -0
- plcc/spec/semantics/parse_code_fragments.py +57 -0
- plcc/spec/semantics/parse_code_fragments_test.py +83 -0
- plcc/spec/semantics/parse_semantic_spec.py +15 -0
- plcc/spec/semantics/parse_semantic_spec_test.py +44 -0
- plcc/spec/semantics/parse_target_locator.py +16 -0
- plcc/spec/semantics/parse_target_locator_test.py +31 -0
- plcc/spec/semantics/validation.py +48 -0
- plcc/spec/semantics/validation_test.py +105 -0
- plcc/spec/split_rough.py +27 -0
- plcc/spec/syntax/CapturingSymbol.py +14 -0
- plcc/spec/syntax/CapturingTerminal.py +9 -0
- plcc/spec/syntax/DuplicateAttribute.py +12 -0
- plcc/spec/syntax/DuplicateLhsError.py +12 -0
- plcc/spec/syntax/InvalidAttribute.py +12 -0
- plcc/spec/syntax/InvalidLhsAltNameError.py +12 -0
- plcc/spec/syntax/InvalidLhsNameError.py +12 -0
- plcc/spec/syntax/InvalidNonterminal.py +12 -0
- plcc/spec/syntax/InvalidSeparator.py +12 -0
- plcc/spec/syntax/InvalidSymbolException.py +8 -0
- plcc/spec/syntax/InvalidSyntacticSpecException.py +8 -0
- plcc/spec/syntax/InvalidTerminal.py +12 -0
- plcc/spec/syntax/LL1Error.py +9 -0
- plcc/spec/syntax/LhsNonTerminal.py +10 -0
- plcc/spec/syntax/MalformedBNFError.py +3 -0
- plcc/spec/syntax/NonTerminal.py +9 -0
- plcc/spec/syntax/RepeatingSyntacticRule.py +10 -0
- plcc/spec/syntax/RhsNonTerminal.py +9 -0
- plcc/spec/syntax/StandardSyntacticRule.py +8 -0
- plcc/spec/syntax/Symbol.py +6 -0
- plcc/spec/syntax/SyntacticRule.py +13 -0
- plcc/spec/syntax/SyntacticSpec.py +26 -0
- plcc/spec/syntax/Terminal.py +10 -0
- plcc/spec/syntax/UndefinedNonterminal.py +12 -0
- plcc/spec/syntax/UndefinedTerminalError.py +12 -0
- plcc/spec/syntax/__init__.py +1 -0
- plcc/spec/syntax/parse_syntactic_spec.py +142 -0
- plcc/spec/syntax/parse_syntactic_spec_test.py +411 -0
- plcc/spec/syntax/validations/__init__.py +0 -0
- plcc/spec/syntax/validations/ll1/Grammar.py +68 -0
- plcc/spec/syntax/validations/ll1/Grammar_test.py +115 -0
- plcc/spec/syntax/validations/ll1/LL1Wrapper.py +17 -0
- plcc/spec/syntax/validations/ll1/LL1Wrapper_test.py +119 -0
- plcc/spec/syntax/validations/ll1/__init__.py +1 -0
- plcc/spec/syntax/validations/ll1/build_first_sets.py +60 -0
- plcc/spec/syntax/validations/ll1/build_first_sets_test.py +94 -0
- plcc/spec/syntax/validations/ll1/build_follow_sets.py +79 -0
- plcc/spec/syntax/validations/ll1/build_follow_sets_test.py +106 -0
- plcc/spec/syntax/validations/ll1/build_parsing_table.py +69 -0
- plcc/spec/syntax/validations/ll1/build_parsing_table_test.py +37 -0
- plcc/spec/syntax/validations/ll1/build_spec_grammar.py +104 -0
- plcc/spec/syntax/validations/ll1/build_spec_grammar_test.py +112 -0
- plcc/spec/syntax/validations/ll1/check_left_recursion.py +96 -0
- plcc/spec/syntax/validations/ll1/check_left_recursion_test.py +134 -0
- plcc/spec/syntax/validations/ll1/check_ll1.py +31 -0
- plcc/spec/syntax/validations/ll1/check_ll1_test.py +30 -0
- plcc/spec/syntax/validations/ll1/check_parsing_table_for_ll1.py +10 -0
- plcc/spec/syntax/validations/ll1/check_parsing_table_for_ll1_test.py +38 -0
- plcc/spec/syntax/validations/replace_repeating_with_standard_rules.py +107 -0
- plcc/spec/syntax/validations/replace_repeating_with_standard_rules_test.py +138 -0
- plcc/spec/syntax/validations/validate_lhs.py +63 -0
- plcc/spec/syntax/validations/validate_lhs_test.py +136 -0
- plcc/spec/syntax/validations/validate_rhs.py +99 -0
- plcc/spec/syntax/validations/validate_rhs_test.py +122 -0
- plcc/spec/syntax/validations/validate_syntactic_spec.py +38 -0
- plcc/spec/syntax/validations/validate_syntactic_spec_test.py +62 -0
- plcc/spec/syntax/validations/validate_terminals_defined.py +46 -0
- plcc/spec/syntax/validations/validate_terminals_defined_test.py +250 -0
- plcc/tokens/__init__.py +0 -0
- plcc/tokens/jsonl_formatter.py +20 -0
- plcc/tokens/jsonl_formatter_test.py +33 -0
- plcc/tokens/spec_loader.py +22 -0
- plcc/tokens/spec_loader_test.py +31 -0
- plcc/tokens/tokens_cli.py +61 -0
- plcc/tokens/tokens_cli_test.py +73 -0
- plcc/tree/__init__.py +0 -0
- plcc/tree/tree_cli.py +47 -0
- plcc/tree/tree_cli_test.py +16 -0
- plcc/verbose.py +181 -0
- plcc/verbose_test.py +267 -0
- plcc_ng-0.1.2.dist-info/METADATA +63 -0
- plcc_ng-0.1.2.dist-info/RECORD +228 -0
- plcc_ng-0.1.2.dist-info/WHEEL +4 -0
- plcc_ng-0.1.2.dist-info/entry_points.txt +27 -0
plcc/__init__.py
ADDED
|
File without changes
|
plcc/cmd/__init__.py
ADDED
|
File without changes
|
plcc/cmd/make.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import enum
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from docopt import docopt
|
|
11
|
+
|
|
12
|
+
from plcc.verbose import VerboseContext, VERBOSE_OPTIONS
|
|
13
|
+
|
|
14
|
+
__doc__ = """plcc-make
|
|
15
|
+
Build a PLCC project from a grammar file.
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
plcc-make [options] GRAMMAR
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
GRAMMAR Path to the PLCC grammar file.
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
-h --help Show this message.
|
|
25
|
+
""" + VERBOSE_OPTIONS
|
|
26
|
+
|
|
27
|
+
_TOOL_NAME_RE = re.compile(r'^[a-zA-Z0-9_-]+$')
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Events(enum.Enum):
|
|
31
|
+
STARTED = "started"
|
|
32
|
+
PHASE = "phase"
|
|
33
|
+
FINISHED = "finished"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def main(argv=None):
|
|
37
|
+
if argv is None:
|
|
38
|
+
argv = sys.argv[1:]
|
|
39
|
+
args = docopt(__doc__, argv)
|
|
40
|
+
verbose = VerboseContext.from_args("plcc-make", Events, args)
|
|
41
|
+
grammar = args['GRAMMAR']
|
|
42
|
+
build_dir = 'build'
|
|
43
|
+
|
|
44
|
+
verbose.emit(Events.STARTED, message=f"building {grammar}")
|
|
45
|
+
|
|
46
|
+
# 1. Clean
|
|
47
|
+
if os.path.exists(build_dir):
|
|
48
|
+
shutil.rmtree(build_dir)
|
|
49
|
+
os.makedirs(build_dir)
|
|
50
|
+
|
|
51
|
+
child_flags = verbose.child_flags_for_orchestrator(min_level=0)
|
|
52
|
+
|
|
53
|
+
# 2. Spec
|
|
54
|
+
verbose.emit(Events.PHASE, message="spec")
|
|
55
|
+
spec_json = os.path.join(build_dir, 'spec.json')
|
|
56
|
+
_run_or_die(['plcc-spec', grammar] + child_flags, stdout_file=spec_json, verbose=verbose)
|
|
57
|
+
|
|
58
|
+
# 3. LL(1)
|
|
59
|
+
verbose.emit(Events.PHASE, message="ll1")
|
|
60
|
+
ll1_json = os.path.join(build_dir, 'll1.json')
|
|
61
|
+
_run_or_die(['plcc-ll1'] + child_flags, stdin_file=spec_json, stdout_file=ll1_json, verbose=verbose)
|
|
62
|
+
with open(ll1_json) as f:
|
|
63
|
+
ll1 = json.load(f)
|
|
64
|
+
if not ll1.get("is_ll1", True):
|
|
65
|
+
_report_ll1_failure(ll1, ll1_json, verbose)
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
|
|
68
|
+
# 4. Model
|
|
69
|
+
verbose.emit(Events.PHASE, message="model")
|
|
70
|
+
model_json = os.path.join(build_dir, 'model.json')
|
|
71
|
+
_run_or_die(['plcc-model', spec_json] + child_flags, stdout_file=model_json, verbose=verbose)
|
|
72
|
+
|
|
73
|
+
# 5 & 6. Emit and build per semantic section
|
|
74
|
+
with open(spec_json) as f:
|
|
75
|
+
spec = json.load(f)
|
|
76
|
+
for section in spec.get('semantics', []):
|
|
77
|
+
tool = section['tool']
|
|
78
|
+
lang = section['language']
|
|
79
|
+
try:
|
|
80
|
+
validate_tool_name(tool)
|
|
81
|
+
except ValueError as e:
|
|
82
|
+
print(f"plcc-make: {e}", file=sys.stderr)
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
output_dir = os.path.join(build_dir, tool)
|
|
85
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
86
|
+
verbose.emit(Events.PHASE, message=f"emit {lang} -> {tool}")
|
|
87
|
+
_run_or_die(
|
|
88
|
+
['plcc-lang-emit', f'--target={lang}', f'--output={output_dir}'] + child_flags,
|
|
89
|
+
stdin_file=model_json,
|
|
90
|
+
verbose=verbose,
|
|
91
|
+
)
|
|
92
|
+
verbose.emit(Events.PHASE, message=f"build {lang} -> {tool}")
|
|
93
|
+
_run_or_die(
|
|
94
|
+
['plcc-lang-build', f'--target={lang}', f'--output={output_dir}'] + child_flags,
|
|
95
|
+
verbose=verbose,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
verbose.emit(Events.FINISHED, message="done")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def validate_tool_name(name):
|
|
102
|
+
if not name or not _TOOL_NAME_RE.match(name):
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"Invalid tool name '{name}'. "
|
|
105
|
+
"Tool names must match [a-zA-Z0-9_-]+ to prevent path traversal."
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _report_ll1_failure(ll1, path, verbose):
|
|
110
|
+
print(
|
|
111
|
+
f"plcc-make: error: grammar is not LL(1); see {path}",
|
|
112
|
+
file=sys.stderr,
|
|
113
|
+
)
|
|
114
|
+
for conflict in ll1.get("conflicts", []):
|
|
115
|
+
print(
|
|
116
|
+
f"plcc-make: error: conflict at "
|
|
117
|
+
f"{conflict.get('nonterminal', '?')} on "
|
|
118
|
+
f"{conflict.get('lookahead', '?')}: "
|
|
119
|
+
f"{conflict.get('productions', [])}",
|
|
120
|
+
file=sys.stderr,
|
|
121
|
+
)
|
|
122
|
+
for entry in ll1.get("left_recursion", []):
|
|
123
|
+
cycle = entry.get("cycle", [])
|
|
124
|
+
print(
|
|
125
|
+
f"plcc-make: error: left-recursion cycle: {' -> '.join(cycle)}",
|
|
126
|
+
file=sys.stderr,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _run_or_die(cmd, stdout_file=None, stdin_file=None, verbose=None):
|
|
131
|
+
with contextlib.ExitStack() as stack:
|
|
132
|
+
stdin = stack.enter_context(open(stdin_file)) if stdin_file else None
|
|
133
|
+
stdout = stack.enter_context(open(stdout_file, 'w')) if stdout_file else None
|
|
134
|
+
result = subprocess.run(cmd, stdin=stdin, stdout=stdout, stderr=subprocess.PIPE)
|
|
135
|
+
if verbose and result.stderr:
|
|
136
|
+
events = verbose.parse_child_events(result.stderr.decode("utf-8", errors="replace"))
|
|
137
|
+
verbose.reformat_child_events(events)
|
|
138
|
+
if result.returncode != 0:
|
|
139
|
+
print(f"plcc-make: {cmd[0]} failed (exit {result.returncode})", file=sys.stderr)
|
|
140
|
+
sys.exit(result.returncode)
|
plcc/cmd/make_test.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import docopt
|
|
3
|
+
|
|
4
|
+
from .make import main as run_main, validate_tool_name, _report_ll1_failure
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_no_args_prints_usage():
|
|
8
|
+
with pytest.raises((docopt.DocoptExit, SystemExit)):
|
|
9
|
+
run_main([])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_help(capsys):
|
|
13
|
+
with pytest.raises(SystemExit):
|
|
14
|
+
run_main(['--help'])
|
|
15
|
+
out, err = capsys.readouterr()
|
|
16
|
+
assert 'Usage' in out
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_validate_tool_name_accepts_valid():
|
|
20
|
+
validate_tool_name('diagram')
|
|
21
|
+
validate_tool_name('Java')
|
|
22
|
+
validate_tool_name('my-tool')
|
|
23
|
+
validate_tool_name('tool_123')
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_validate_tool_name_rejects_path_traversal():
|
|
27
|
+
with pytest.raises(ValueError):
|
|
28
|
+
validate_tool_name('../etc')
|
|
29
|
+
with pytest.raises(ValueError):
|
|
30
|
+
validate_tool_name('foo/bar')
|
|
31
|
+
with pytest.raises(ValueError):
|
|
32
|
+
validate_tool_name('/absolute')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_validate_tool_name_rejects_empty():
|
|
36
|
+
with pytest.raises(ValueError):
|
|
37
|
+
validate_tool_name('')
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_report_ll1_failure_prints_error_and_conflicts(capsys):
|
|
41
|
+
ll1 = {
|
|
42
|
+
"is_ll1": False,
|
|
43
|
+
"conflicts": [
|
|
44
|
+
{"nonterminal": "E", "lookahead": "+", "competing": ["E + T", "E"]}
|
|
45
|
+
],
|
|
46
|
+
"left_recursion": [],
|
|
47
|
+
}
|
|
48
|
+
_report_ll1_failure(ll1, "build/ll1.json", verbose=None)
|
|
49
|
+
_, err = capsys.readouterr()
|
|
50
|
+
assert "plcc-make: error:" in err
|
|
51
|
+
assert "build/ll1.json" in err
|
|
52
|
+
assert "E" in err
|
|
53
|
+
assert "+" in err
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_report_left_recursion_cycle(capsys):
|
|
57
|
+
ll1 = {
|
|
58
|
+
"conflicts": [],
|
|
59
|
+
"left_recursion": [{"cycle": ["A", "B", "A"]}],
|
|
60
|
+
}
|
|
61
|
+
_report_ll1_failure(ll1, "build/ll1.json", None)
|
|
62
|
+
_, err = capsys.readouterr()
|
|
63
|
+
assert "A -> B -> A" in err
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_report_conflict(capsys):
|
|
67
|
+
ll1 = {
|
|
68
|
+
"conflicts": [{"nonterminal": "E", "lookahead": "PLUS", "productions": []}],
|
|
69
|
+
"left_recursion": [],
|
|
70
|
+
}
|
|
71
|
+
_report_ll1_failure(ll1, "build/ll1.json", None)
|
|
72
|
+
_, err = capsys.readouterr()
|
|
73
|
+
assert "E" in err
|
|
74
|
+
assert "PLUS" in err
|
plcc/cmd/parse.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
from docopt import docopt
|
|
9
|
+
|
|
10
|
+
from plcc.verbose import VerboseContext, VERBOSE_OPTIONS, reap_pipeline
|
|
11
|
+
|
|
12
|
+
__doc__ = """plcc-parse
|
|
13
|
+
Parse source input and print parse tree in human-readable format.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
plcc-parse [options] GRAMMAR [SOURCE ...]
|
|
17
|
+
|
|
18
|
+
Arguments:
|
|
19
|
+
GRAMMAR Path to the PLCC grammar file.
|
|
20
|
+
SOURCE Source files to parse. Reads stdin if none given.
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
-h --help Show this message.
|
|
24
|
+
""" + VERBOSE_OPTIONS
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Events(enum.Enum):
|
|
28
|
+
STARTED = "started"
|
|
29
|
+
FINISHED = "finished"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _location_str(source):
|
|
33
|
+
file = source.get("file")
|
|
34
|
+
line = source.get("line", "?")
|
|
35
|
+
col = source.get("column", "?")
|
|
36
|
+
if file and file != "<stdin>":
|
|
37
|
+
return f"{file}:{line}:{col}"
|
|
38
|
+
return f"{line}:{col}"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def main(argv=None):
|
|
42
|
+
if argv is None:
|
|
43
|
+
argv = sys.argv[1:]
|
|
44
|
+
args = docopt(__doc__, argv)
|
|
45
|
+
verbose = VerboseContext.from_args("plcc-parse", Events, args)
|
|
46
|
+
grammar = args["GRAMMAR"]
|
|
47
|
+
sources = args["SOURCE"]
|
|
48
|
+
|
|
49
|
+
verbose.emit(Events.STARTED, message=f"parsing with {grammar}")
|
|
50
|
+
child_flags = verbose.child_flags_for_orchestrator(min_level=0)
|
|
51
|
+
|
|
52
|
+
spec_path = tempfile.mktemp(suffix=".json")
|
|
53
|
+
ll1_path = tempfile.mktemp(suffix=".json")
|
|
54
|
+
try:
|
|
55
|
+
# plcc-spec
|
|
56
|
+
_run_child(["plcc-spec", grammar] + child_flags, stdout_file=spec_path, verbose=verbose, label="plcc-spec")
|
|
57
|
+
# plcc-ll1
|
|
58
|
+
_run_child(["plcc-ll1"] + child_flags, stdin_file=spec_path, stdout_file=ll1_path, verbose=verbose, label="plcc-ll1")
|
|
59
|
+
|
|
60
|
+
# Build input
|
|
61
|
+
input_data = b""
|
|
62
|
+
for src in sources:
|
|
63
|
+
with open(src, "rb") as sf:
|
|
64
|
+
input_data += sf.read()
|
|
65
|
+
if not sources:
|
|
66
|
+
input_data = sys.stdin.buffer.read()
|
|
67
|
+
|
|
68
|
+
# plcc-tokens | plcc-tree
|
|
69
|
+
tokens_proc = subprocess.Popen(
|
|
70
|
+
["plcc-tokens", spec_path] + child_flags,
|
|
71
|
+
stdin=subprocess.PIPE,
|
|
72
|
+
stdout=subprocess.PIPE,
|
|
73
|
+
stderr=subprocess.PIPE,
|
|
74
|
+
)
|
|
75
|
+
tree_proc = subprocess.Popen(
|
|
76
|
+
["plcc-tree", f"--ll1={ll1_path}"] + child_flags,
|
|
77
|
+
stdin=tokens_proc.stdout,
|
|
78
|
+
stdout=subprocess.PIPE,
|
|
79
|
+
stderr=subprocess.PIPE,
|
|
80
|
+
)
|
|
81
|
+
tokens_proc.stdout.close()
|
|
82
|
+
tokens_proc.stdin.write(input_data)
|
|
83
|
+
tokens_proc.stdin.close()
|
|
84
|
+
|
|
85
|
+
tree_out, tree_err = tree_proc.communicate()
|
|
86
|
+
tokens_err = tokens_proc.stderr.read()
|
|
87
|
+
tokens_proc.wait()
|
|
88
|
+
tokens_proc.stderr_captured = tokens_err
|
|
89
|
+
tree_proc.stderr_captured = tree_err
|
|
90
|
+
|
|
91
|
+
result = reap_pipeline([
|
|
92
|
+
(tokens_proc, "plcc-tokens"),
|
|
93
|
+
(tree_proc, "plcc-tree"),
|
|
94
|
+
])
|
|
95
|
+
verbose.reformat_child_events(result.events_to_render)
|
|
96
|
+
if result.failed_stage:
|
|
97
|
+
sys.exit(result.exit_code)
|
|
98
|
+
|
|
99
|
+
# Print tree in human-readable format
|
|
100
|
+
for line in tree_out.decode("utf-8").splitlines():
|
|
101
|
+
if not line.strip():
|
|
102
|
+
continue
|
|
103
|
+
tree = json.loads(line)
|
|
104
|
+
_print_tree(tree, indent=0)
|
|
105
|
+
finally:
|
|
106
|
+
for p in (spec_path, ll1_path):
|
|
107
|
+
if os.path.exists(p):
|
|
108
|
+
os.unlink(p)
|
|
109
|
+
|
|
110
|
+
verbose.emit(Events.FINISHED, message="done")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _run_child(cmd, stdout_file, verbose, label, stdin_file=None):
|
|
114
|
+
with open(stdout_file, "w") as out:
|
|
115
|
+
stdin = open(stdin_file) if stdin_file else None
|
|
116
|
+
result = subprocess.run(cmd, stdin=stdin, stdout=out, stderr=subprocess.PIPE)
|
|
117
|
+
if stdin:
|
|
118
|
+
stdin.close()
|
|
119
|
+
if result.stderr:
|
|
120
|
+
events = verbose.parse_child_events(result.stderr.decode("utf-8", errors="replace"))
|
|
121
|
+
verbose.reformat_child_events(events)
|
|
122
|
+
if result.returncode != 0:
|
|
123
|
+
print(f"plcc-parse: {label} failed (exit {result.returncode})", file=sys.stderr)
|
|
124
|
+
sys.exit(result.returncode)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _print_tree(node, indent):
|
|
128
|
+
prefix = " " * indent
|
|
129
|
+
kind = node.get("kind", "?")
|
|
130
|
+
if kind == "tree":
|
|
131
|
+
rule = node.get("rule", "?")
|
|
132
|
+
print(f"{prefix}{rule}")
|
|
133
|
+
for _field, child in node.get("children", []):
|
|
134
|
+
_print_tree(child, indent + 1)
|
|
135
|
+
elif kind == "token":
|
|
136
|
+
name = node.get("name", "?")
|
|
137
|
+
lexeme = node.get("lexeme", "?")
|
|
138
|
+
source = node.get("source", {})
|
|
139
|
+
loc = _location_str(source)
|
|
140
|
+
print(f"{prefix}{name} '{lexeme}' [{loc}]")
|
|
141
|
+
# forward-looking: plcc-tree may emit error records inline in a future protocol
|
|
142
|
+
elif kind == "error":
|
|
143
|
+
source = node.get("source", {})
|
|
144
|
+
loc = _location_str(source)
|
|
145
|
+
message = node.get("message", "unknown error")
|
|
146
|
+
print(f"{prefix}{loc}: error: {message}")
|
plcc/cmd/rep.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from docopt import docopt
|
|
8
|
+
|
|
9
|
+
from plcc.verbose import VerboseContext, VERBOSE_OPTIONS
|
|
10
|
+
|
|
11
|
+
__doc__ = """plcc-rep
|
|
12
|
+
REPL — read, eval, print loop for a PLCC grammar.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
plcc-rep [options] GRAMMAR [SOURCE ...]
|
|
16
|
+
|
|
17
|
+
Arguments:
|
|
18
|
+
GRAMMAR Path to the PLCC grammar file (build/ is resolved from the current directory).
|
|
19
|
+
SOURCE Source files to evaluate before entering interactive mode.
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--tool=NAME Semantic section to run (inferred if only one exists).
|
|
23
|
+
-h --help Show this message.
|
|
24
|
+
""" + VERBOSE_OPTIONS
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Events(enum.Enum):
|
|
28
|
+
STARTED = "started"
|
|
29
|
+
FINISHED = "finished"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main(argv=None):
|
|
33
|
+
if argv is None:
|
|
34
|
+
argv = sys.argv[1:]
|
|
35
|
+
args = docopt(__doc__, argv)
|
|
36
|
+
verbose = VerboseContext.from_args("plcc-rep", Events, args)
|
|
37
|
+
sources = args['SOURCE']
|
|
38
|
+
tool_name = args['--tool']
|
|
39
|
+
verbose_format = args['--verbose-format'] or 'text'
|
|
40
|
+
|
|
41
|
+
verbose.emit(Events.STARTED, message='starting rep')
|
|
42
|
+
|
|
43
|
+
spec_path = os.path.join('build', 'spec.json')
|
|
44
|
+
ll1_path = os.path.join('build', 'll1.json')
|
|
45
|
+
|
|
46
|
+
if not os.path.exists(spec_path) or not os.path.exists(ll1_path):
|
|
47
|
+
print('plcc-rep: build/ not found. Run plcc-make first.', file=sys.stderr)
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
|
|
50
|
+
with open(spec_path) as f:
|
|
51
|
+
spec = json.load(f)
|
|
52
|
+
|
|
53
|
+
tool_name, language = _resolve_tool(spec, tool_name)
|
|
54
|
+
tool_dir = os.path.join('build', tool_name)
|
|
55
|
+
|
|
56
|
+
if not os.path.exists(tool_dir):
|
|
57
|
+
print(f'plcc-rep: build/{tool_name}/ not found. Run plcc-make first.', file=sys.stderr)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
interpreter = subprocess.Popen(
|
|
61
|
+
['plcc-lang-run', f'--target={language}', f'--output={tool_dir}'],
|
|
62
|
+
stdin=subprocess.PIPE,
|
|
63
|
+
stdout=subprocess.PIPE,
|
|
64
|
+
stderr=None,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
for src in sources:
|
|
69
|
+
with open(src, 'rb') as f:
|
|
70
|
+
chunk = f.read()
|
|
71
|
+
_eval_chunk(chunk, interpreter, spec_path, ll1_path, verbose_format)
|
|
72
|
+
|
|
73
|
+
if not sources:
|
|
74
|
+
interactive = sys.stdin.isatty()
|
|
75
|
+
if interactive:
|
|
76
|
+
while True:
|
|
77
|
+
try:
|
|
78
|
+
print('>>> ', end='', flush=True, file=sys.stderr)
|
|
79
|
+
line = sys.stdin.readline()
|
|
80
|
+
if not line:
|
|
81
|
+
break
|
|
82
|
+
chunk = line.encode()
|
|
83
|
+
_eval_chunk(chunk, interpreter, spec_path, ll1_path, verbose_format)
|
|
84
|
+
except KeyboardInterrupt:
|
|
85
|
+
print(file=sys.stderr)
|
|
86
|
+
break
|
|
87
|
+
else:
|
|
88
|
+
chunk = sys.stdin.buffer.read()
|
|
89
|
+
_eval_chunk(chunk, interpreter, spec_path, ll1_path, verbose_format)
|
|
90
|
+
finally:
|
|
91
|
+
try:
|
|
92
|
+
interpreter.stdin.close()
|
|
93
|
+
except BrokenPipeError:
|
|
94
|
+
pass
|
|
95
|
+
interpreter.wait()
|
|
96
|
+
|
|
97
|
+
verbose.emit(Events.FINISHED, message='done')
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _resolve_tool(spec, tool_name):
|
|
101
|
+
sections = spec.get('semantics', [])
|
|
102
|
+
if tool_name:
|
|
103
|
+
for s in sections:
|
|
104
|
+
if s['tool'] == tool_name:
|
|
105
|
+
return s['tool'], s['language']
|
|
106
|
+
print(f"plcc-rep: no semantic section with tool '{tool_name}'", file=sys.stderr)
|
|
107
|
+
sys.exit(1)
|
|
108
|
+
|
|
109
|
+
if len(sections) == 0:
|
|
110
|
+
print("plcc-rep: no semantic sections found. Run plcc-make first.", file=sys.stderr)
|
|
111
|
+
sys.exit(1)
|
|
112
|
+
|
|
113
|
+
if len(sections) == 1:
|
|
114
|
+
return sections[0]['tool'], sections[0]['language']
|
|
115
|
+
|
|
116
|
+
names = [s['tool'] for s in sections]
|
|
117
|
+
print(f"plcc-rep: multiple semantic sections: {names}. Use --tool=NAME.", file=sys.stderr)
|
|
118
|
+
sys.exit(1)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _eval_chunk(chunk, interpreter, spec_path, ll1_path, verbose_format):
|
|
122
|
+
tokens_proc = subprocess.Popen(
|
|
123
|
+
['plcc-tokens', spec_path],
|
|
124
|
+
stdin=subprocess.PIPE,
|
|
125
|
+
stdout=subprocess.PIPE,
|
|
126
|
+
stderr=subprocess.PIPE,
|
|
127
|
+
)
|
|
128
|
+
tree_proc = subprocess.Popen(
|
|
129
|
+
['plcc-tree', f'--ll1={ll1_path}'],
|
|
130
|
+
stdin=tokens_proc.stdout,
|
|
131
|
+
stdout=subprocess.PIPE,
|
|
132
|
+
stderr=subprocess.PIPE,
|
|
133
|
+
)
|
|
134
|
+
tokens_proc.stdout.close()
|
|
135
|
+
tokens_proc.stdin.write(chunk)
|
|
136
|
+
tokens_proc.stdin.close()
|
|
137
|
+
|
|
138
|
+
tree_out, tree_err = tree_proc.communicate()
|
|
139
|
+
tokens_err = tokens_proc.stderr.read()
|
|
140
|
+
tokens_proc.wait()
|
|
141
|
+
|
|
142
|
+
if tokens_proc.returncode != 0 or tree_proc.returncode != 0:
|
|
143
|
+
for msg in [tokens_err, tree_err]:
|
|
144
|
+
if msg:
|
|
145
|
+
sys.stderr.buffer.write(msg)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
tree_line = tree_out.strip()
|
|
149
|
+
if not tree_line:
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
interpreter.stdin.write(tree_line + b'\n')
|
|
154
|
+
interpreter.stdin.flush()
|
|
155
|
+
except BrokenPipeError:
|
|
156
|
+
print('plcc-rep: interpreter exited unexpectedly', file=sys.stderr)
|
|
157
|
+
sys.exit(1)
|
|
158
|
+
|
|
159
|
+
_read_response(interpreter.stdout, verbose_format)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _read_response(stdout, verbose_format):
|
|
163
|
+
while True:
|
|
164
|
+
raw = stdout.readline()
|
|
165
|
+
if not raw:
|
|
166
|
+
print('plcc-rep: interpreter exited unexpectedly', file=sys.stderr)
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
line = raw.decode('utf-8', errors='replace').rstrip('\n')
|
|
169
|
+
try:
|
|
170
|
+
record = json.loads(line)
|
|
171
|
+
except json.JSONDecodeError:
|
|
172
|
+
print(line)
|
|
173
|
+
continue
|
|
174
|
+
if 'kind' not in record:
|
|
175
|
+
print(line)
|
|
176
|
+
continue
|
|
177
|
+
_render_record(record, verbose_format)
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _render_record(record, verbose_format):
|
|
182
|
+
if verbose_format == 'json':
|
|
183
|
+
print(json.dumps(record))
|
|
184
|
+
return
|
|
185
|
+
if record['kind'] == 'result':
|
|
186
|
+
value = record.get('value')
|
|
187
|
+
if value is not None:
|
|
188
|
+
print(value)
|
|
189
|
+
elif record['kind'] == 'error':
|
|
190
|
+
print(f"error: {record.get('type')}: {record.get('message')}", file=sys.stderr)
|
plcc/cmd/scan.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
|
|
8
|
+
from docopt import docopt
|
|
9
|
+
|
|
10
|
+
from plcc.verbose import VerboseContext, VERBOSE_OPTIONS
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _location_str(source):
|
|
14
|
+
file = source.get("file")
|
|
15
|
+
line = source.get("line", "?")
|
|
16
|
+
col = source.get("column", "?")
|
|
17
|
+
if file and file != "<stdin>":
|
|
18
|
+
return f"{file}:{line}:{col}"
|
|
19
|
+
return f"{line}:{col}"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
__doc__ = """plcc-scan
|
|
23
|
+
Tokenize source input and print tokens in human-readable format.
|
|
24
|
+
|
|
25
|
+
Usage:
|
|
26
|
+
plcc-scan [options] GRAMMAR [SOURCE ...]
|
|
27
|
+
|
|
28
|
+
Arguments:
|
|
29
|
+
GRAMMAR Path to the PLCC grammar file.
|
|
30
|
+
SOURCE Source files to tokenize. Reads stdin if none given.
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
-h --help Show this message.
|
|
34
|
+
""" + VERBOSE_OPTIONS
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Events(enum.Enum):
|
|
38
|
+
STARTED = "started"
|
|
39
|
+
FINISHED = "finished"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def main(argv=None):
|
|
43
|
+
if argv is None:
|
|
44
|
+
argv = sys.argv[1:]
|
|
45
|
+
args = docopt(__doc__, argv)
|
|
46
|
+
verbose = VerboseContext.from_args("plcc-scan", Events, args)
|
|
47
|
+
grammar = args["GRAMMAR"]
|
|
48
|
+
sources = args["SOURCE"]
|
|
49
|
+
|
|
50
|
+
verbose.emit(Events.STARTED, message=f"scanning with {grammar}")
|
|
51
|
+
child_flags = verbose.child_flags_for_orchestrator(min_level=0)
|
|
52
|
+
|
|
53
|
+
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f:
|
|
54
|
+
spec_path = f.name
|
|
55
|
+
try:
|
|
56
|
+
# plcc-spec grammar > spec.json
|
|
57
|
+
with open(spec_path, "w") as spec_out:
|
|
58
|
+
result = subprocess.run(
|
|
59
|
+
["plcc-spec", grammar] + child_flags,
|
|
60
|
+
stdout=spec_out,
|
|
61
|
+
stderr=subprocess.PIPE,
|
|
62
|
+
)
|
|
63
|
+
if result.stderr:
|
|
64
|
+
events = verbose.parse_child_events(result.stderr.decode("utf-8", errors="replace"))
|
|
65
|
+
verbose.reformat_child_events(events)
|
|
66
|
+
if result.returncode != 0:
|
|
67
|
+
print(f"plcc-scan: plcc-spec failed (exit {result.returncode})", file=sys.stderr)
|
|
68
|
+
sys.exit(result.returncode)
|
|
69
|
+
|
|
70
|
+
# Build input: concatenate source files, then stdin
|
|
71
|
+
input_data = b""
|
|
72
|
+
for src in sources:
|
|
73
|
+
with open(src, "rb") as sf:
|
|
74
|
+
input_data += sf.read()
|
|
75
|
+
if not sources:
|
|
76
|
+
input_data = sys.stdin.buffer.read()
|
|
77
|
+
|
|
78
|
+
# plcc-tokens spec.json < input
|
|
79
|
+
result = subprocess.run(
|
|
80
|
+
["plcc-tokens", spec_path] + child_flags,
|
|
81
|
+
input=input_data,
|
|
82
|
+
stdout=subprocess.PIPE,
|
|
83
|
+
stderr=subprocess.PIPE,
|
|
84
|
+
)
|
|
85
|
+
if result.stderr:
|
|
86
|
+
events = verbose.parse_child_events(result.stderr.decode("utf-8", errors="replace"))
|
|
87
|
+
verbose.reformat_child_events(events)
|
|
88
|
+
if result.returncode != 0:
|
|
89
|
+
# lex error: plcc-tokens already emitted the error to stderr via verbose;
|
|
90
|
+
# treat as non-fatal — pipeline completed with an error in-band
|
|
91
|
+
pass
|
|
92
|
+
else:
|
|
93
|
+
for line in result.stdout.decode("utf-8").splitlines():
|
|
94
|
+
if not line.strip():
|
|
95
|
+
continue
|
|
96
|
+
record = json.loads(line)
|
|
97
|
+
if record.get("kind") == "token":
|
|
98
|
+
name = record.get("name", "?")
|
|
99
|
+
lexeme = record.get("lexeme", "?")
|
|
100
|
+
source = record.get("source", {})
|
|
101
|
+
loc = _location_str(source)
|
|
102
|
+
print(f"{loc} {name} '{lexeme}'")
|
|
103
|
+
# forward-looking: plcc-tokens may emit error records inline in a future protocol
|
|
104
|
+
elif record.get("kind") == "error":
|
|
105
|
+
source = record.get("source", {})
|
|
106
|
+
loc = _location_str(source)
|
|
107
|
+
message = record.get("message", "unknown error")
|
|
108
|
+
print(f"{loc}: error: {message}")
|
|
109
|
+
finally:
|
|
110
|
+
os.unlink(spec_path)
|
|
111
|
+
|
|
112
|
+
verbose.emit(Events.FINISHED, message="done")
|
|
File without changes
|
plcc/diagram/__init__.py
ADDED
|
File without changes
|