plcc-ng 0.1.2__tar.gz
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_ng-0.1.2/PKG-INFO +63 -0
- plcc_ng-0.1.2/README.md +40 -0
- plcc_ng-0.1.2/pyproject.toml +102 -0
- plcc_ng-0.1.2/src/plcc/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/cmd/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/cmd/make.py +140 -0
- plcc_ng-0.1.2/src/plcc/cmd/make_test.py +74 -0
- plcc_ng-0.1.2/src/plcc/cmd/parse.py +146 -0
- plcc_ng-0.1.2/src/plcc/cmd/rep.py +190 -0
- plcc_ng-0.1.2/src/plcc/cmd/scan.py +112 -0
- plcc_ng-0.1.2/src/plcc/cmd/skeleton_test.py +0 -0
- plcc_ng-0.1.2/src/plcc/diagram/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/diagram/dispatch.py +48 -0
- plcc_ng-0.1.2/src/plcc/diagram/dispatch_test.py +86 -0
- plcc_ng-0.1.2/src/plcc/diagram/list.py +68 -0
- plcc_ng-0.1.2/src/plcc/diagram/list_test.py +32 -0
- plcc_ng-0.1.2/src/plcc/diagram/plantuml/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/diagram/plantuml/emit.py +61 -0
- plcc_ng-0.1.2/src/plcc/diagram/plantuml/emit_test.py +79 -0
- plcc_ng-0.1.2/src/plcc/lang/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/lang/build.py +43 -0
- plcc_ng-0.1.2/src/plcc/lang/build_test.py +22 -0
- plcc_ng-0.1.2/src/plcc/lang/emit.py +51 -0
- plcc_ng-0.1.2/src/plcc/lang/emit_test.py +29 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/build.py +53 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/emit.py +106 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/emit_test.py +180 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/run.py +50 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/Deserializer.java +46 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/Node.java +4 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/Registry.java +28 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/Token.java +16 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/org.json-20250107.jar +0 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/templates/Main.java.jinja +37 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/java/templates/class_file.java.jinja +39 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/emit.py +100 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/emit_test.py +132 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/run.py +44 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/base.py +8 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/base_test.py +17 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/deserialize.py +22 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/deserialize_test.py +115 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/registry.py +27 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/registry_test.py +67 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/templates/class_file.py.jinja +21 -0
- plcc_ng-0.1.2/src/plcc/lang/ext/python/templates/main.py.jinja +22 -0
- plcc_ng-0.1.2/src/plcc/lang/list.py +69 -0
- plcc_ng-0.1.2/src/plcc/lang/list_test.py +27 -0
- plcc_ng-0.1.2/src/plcc/lang/run.py +45 -0
- plcc_ng-0.1.2/src/plcc/lines/Line.py +8 -0
- plcc_ng-0.1.2/src/plcc/lines/__init__.py +2 -0
- plcc_ng-0.1.2/src/plcc/lines/parseLines.py +16 -0
- plcc_ng-0.1.2/src/plcc/lines/parse_from_file.py +10 -0
- plcc_ng-0.1.2/src/plcc/lines/parse_from_string.py +5 -0
- plcc_ng-0.1.2/src/plcc/lines/parse_from_string_test.py +54 -0
- plcc_ng-0.1.2/src/plcc/lines/parse_from_strings.py +6 -0
- plcc_ng-0.1.2/src/plcc/ll1/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/ll1/ll1_cli.py +64 -0
- plcc_ng-0.1.2/src/plcc/ll1/ll1_cli_test.py +93 -0
- plcc_ng-0.1.2/src/plcc/ll1/ll1_result_builder.py +122 -0
- plcc_ng-0.1.2/src/plcc/ll1/ll1_result_builder_test.py +225 -0
- plcc_ng-0.1.2/src/plcc/ll1/spec_json_decoder.py +70 -0
- plcc_ng-0.1.2/src/plcc/ll1/spec_json_decoder_test.py +184 -0
- plcc_ng-0.1.2/src/plcc/model/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/model/build_model.py +155 -0
- plcc_ng-0.1.2/src/plcc/model/build_model_test.py +468 -0
- plcc_ng-0.1.2/src/plcc/model/model_cli.py +44 -0
- plcc_ng-0.1.2/src/plcc/model/model_cli_test.py +62 -0
- plcc_ng-0.1.2/src/plcc/parser/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/parser/list_cli.py +67 -0
- plcc_ng-0.1.2/src/plcc/parser/predictive_parser.py +152 -0
- plcc_ng-0.1.2/src/plcc/parser/predictive_parser_test.py +263 -0
- plcc_ng-0.1.2/src/plcc/parser/table_cli.py +89 -0
- plcc_ng-0.1.2/src/plcc/parser/table_cli_test.py +161 -0
- plcc_ng-0.1.2/src/plcc/scan/LexError.py +12 -0
- plcc_ng-0.1.2/src/plcc/scan/Skip.py +11 -0
- plcc_ng-0.1.2/src/plcc/scan/Token.py +9 -0
- plcc_ng-0.1.2/src/plcc/scan/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/scan/matcher.py +61 -0
- plcc_ng-0.1.2/src/plcc/scan/matcher_test.py +126 -0
- plcc_ng-0.1.2/src/plcc/scan/scanner.py +23 -0
- plcc_ng-0.1.2/src/plcc/scan/scanner_test.py +101 -0
- plcc_ng-0.1.2/src/plcc/scan/sink.py +12 -0
- plcc_ng-0.1.2/src/plcc/scan/sink_test.py +118 -0
- plcc_ng-0.1.2/src/plcc/scan/source.py +25 -0
- plcc_ng-0.1.2/src/plcc/scan/source_test.py +119 -0
- plcc_ng-0.1.2/src/plcc/schemas/ll1.schema.json +65 -0
- plcc_ng-0.1.2/src/plcc/schemas/model.schema.json +61 -0
- plcc_ng-0.1.2/src/plcc/schemas/spec.schema.json +46 -0
- plcc_ng-0.1.2/src/plcc/schemas/token.schema.json +21 -0
- plcc_ng-0.1.2/src/plcc/schemas/tree.schema.json +34 -0
- plcc_ng-0.1.2/src/plcc/spec/Spec.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/SpecError.py +11 -0
- plcc_ng-0.1.2/src/plcc/spec/SpecError_test.py +34 -0
- plcc_ng-0.1.2/src/plcc/spec/ValidationError.py +4 -0
- plcc_ng-0.1.2/src/plcc/spec/__init__.py +48 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/DuplicateName.py +7 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/LexicalRule.py +11 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/LexicalSpec.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/LexicalSpecError.py +5 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/NameExpected.py +8 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/Parser.py +83 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/PatternCompilationError.py +6 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/PatternDelimiterExpected.py +6 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/PatternExpected.py +5 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/UnexpectedContent.py +5 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/__init__.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/check_for_duplicate_names.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/parseLexicalSpec.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/parse_from_lines.py +4 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/parse_from_string.py +7 -0
- plcc_ng-0.1.2/src/plcc/spec/lexical/parse_lexical_test.py +247 -0
- plcc_ng-0.1.2/src/plcc/spec/parseSpec.py +13 -0
- plcc_ng-0.1.2/src/plcc/spec/parseSpec_test.py +50 -0
- plcc_ng-0.1.2/src/plcc/spec/plcc_spec_cli.py +50 -0
- plcc_ng-0.1.2/src/plcc/spec/plcc_spec_cli_test.py +49 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/Block.py +8 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/CircularIncludeError.py +5 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/Divider.py +11 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/Include.py +9 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/UnclosedBlockError.py +5 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/__init__.py +6 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/iterate_rough.py +16 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parseRough.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parseRough_test.py +75 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_blocks.py +66 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_blocks_test.py +95 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_dividers.py +77 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_dividers_test.py +88 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_from_lines.py +6 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_from_lines_test.py +9 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_from_string.py +6 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_includes.py +18 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/parse_includes_test.py +53 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/raise_handler.py +2 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/resolve_includes.py +69 -0
- plcc_ng-0.1.2/src/plcc/spec/rough/resolve_includes_test.py +52 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/CodeFragment.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/InvalidClassNameError.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/SemanticSpec.py +11 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/TargetLocator.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/UndefinedBlockError.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/UndefinedTargetLocatorError.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/__init__.py +2 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/parse_code_fragments.py +57 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/parse_code_fragments_test.py +83 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/parse_semantic_spec.py +15 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/parse_semantic_spec_test.py +44 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/parse_target_locator.py +16 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/parse_target_locator_test.py +31 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/validation.py +48 -0
- plcc_ng-0.1.2/src/plcc/spec/semantics/validation_test.py +105 -0
- plcc_ng-0.1.2/src/plcc/spec/split_rough.py +27 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/CapturingSymbol.py +14 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/CapturingTerminal.py +9 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/DuplicateAttribute.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/DuplicateLhsError.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidAttribute.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidLhsAltNameError.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidLhsNameError.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidNonterminal.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidSeparator.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidSymbolException.py +8 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidSyntacticSpecException.py +8 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidTerminal.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/LL1Error.py +9 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/LhsNonTerminal.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/MalformedBNFError.py +3 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/NonTerminal.py +9 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/RepeatingSyntacticRule.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/RhsNonTerminal.py +9 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/StandardSyntacticRule.py +8 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/Symbol.py +6 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/SyntacticRule.py +13 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/SyntacticSpec.py +26 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/Terminal.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/UndefinedNonterminal.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/UndefinedTerminalError.py +12 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/__init__.py +1 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/parse_syntactic_spec.py +142 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/parse_syntactic_spec_test.py +411 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/Grammar.py +68 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/Grammar_test.py +115 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/LL1Wrapper.py +17 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/LL1Wrapper_test.py +119 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/__init__.py +1 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_first_sets.py +60 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_first_sets_test.py +94 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_follow_sets.py +79 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_follow_sets_test.py +106 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_parsing_table.py +69 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_parsing_table_test.py +37 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_spec_grammar.py +104 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_spec_grammar_test.py +112 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_left_recursion.py +96 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_left_recursion_test.py +134 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_ll1.py +31 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_ll1_test.py +30 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_parsing_table_for_ll1.py +10 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_parsing_table_for_ll1_test.py +38 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/replace_repeating_with_standard_rules.py +107 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/replace_repeating_with_standard_rules_test.py +138 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_lhs.py +63 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_lhs_test.py +136 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_rhs.py +99 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_rhs_test.py +122 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_syntactic_spec.py +38 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_syntactic_spec_test.py +62 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_terminals_defined.py +46 -0
- plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_terminals_defined_test.py +250 -0
- plcc_ng-0.1.2/src/plcc/tokens/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/tokens/jsonl_formatter.py +20 -0
- plcc_ng-0.1.2/src/plcc/tokens/jsonl_formatter_test.py +33 -0
- plcc_ng-0.1.2/src/plcc/tokens/spec_loader.py +22 -0
- plcc_ng-0.1.2/src/plcc/tokens/spec_loader_test.py +31 -0
- plcc_ng-0.1.2/src/plcc/tokens/tokens_cli.py +61 -0
- plcc_ng-0.1.2/src/plcc/tokens/tokens_cli_test.py +73 -0
- plcc_ng-0.1.2/src/plcc/tree/__init__.py +0 -0
- plcc_ng-0.1.2/src/plcc/tree/tree_cli.py +47 -0
- plcc_ng-0.1.2/src/plcc/tree/tree_cli_test.py +16 -0
- plcc_ng-0.1.2/src/plcc/verbose.py +181 -0
- plcc_ng-0.1.2/src/plcc/verbose_test.py +267 -0
- plcc_ng-0.1.2/tests/bats/commands/.gitkeep +0 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-diagram-list.bats +10 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-diagram.bats +34 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-java-build.bats +30 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-java-emit.bats +40 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-java-run.bats +28 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-lang-build.bats +18 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-lang-emit.bats +30 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-lang-list.bats +11 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-lang-run.bats +34 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-ll1.bats +59 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-make.bats +43 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-model.bats +44 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-parse.bats +37 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-parser-list.bats +21 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-parser-table.bats +73 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-plantuml-diagram.bats +33 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-python-emit.bats +26 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-python-run.bats +31 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-rep.bats +41 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-scan.bats +44 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-spec.bats +47 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-tokens.bats +48 -0
- plcc_ng-0.1.2/tests/bats/commands/plcc-tree.bats +50 -0
- plcc_ng-0.1.2/tests/bats/e2e/.gitkeep +0 -0
- plcc_ng-0.1.2/tests/bats/e2e/error-propagation.bats +34 -0
- plcc_ng-0.1.2/tests/bats/e2e/happy-path.bats +73 -0
- plcc_ng-0.1.2/tests/bats/e2e/languages-java.bats +58 -0
- plcc_ng-0.1.2/tests/bats/e2e/plcc-rep.bats +106 -0
- plcc_ng-0.1.2/tests/bats/integration/.gitkeep +0 -0
- plcc_ng-0.1.2/tests/bats/integration/java-emit.bats +65 -0
- plcc_ng-0.1.2/tests/bats/integration/ll1-tree.bats +36 -0
- plcc_ng-0.1.2/tests/bats/integration/model-lang-emit.bats +18 -0
- plcc_ng-0.1.2/tests/bats/integration/plcc-parse-errors.bats +22 -0
- plcc_ng-0.1.2/tests/bats/integration/python-emit.bats +65 -0
- plcc_ng-0.1.2/tests/bats/integration/spec-ll1.bats +14 -0
- plcc_ng-0.1.2/tests/bats/integration/spec-model.bats +21 -0
- plcc_ng-0.1.2/tests/bats/integration/spec-tokens.bats +20 -0
- plcc_ng-0.1.2/tests/bats/integration/tokens-tree.bats +20 -0
- plcc_ng-0.1.2/tests/fixtures/arith.plcc +36 -0
- plcc_ng-0.1.2/tests/fixtures/languages-corpus.txt +32 -0
- plcc_ng-0.1.2/tests/fixtures/languages-pin.txt +1 -0
- plcc_ng-0.1.2/tests/fixtures/trivial-arbno.plcc +26 -0
- plcc_ng-0.1.2/tests/fixtures/trivial-full.plcc +5 -0
- plcc_ng-0.1.2/tests/fixtures/trivial-java.plcc +11 -0
- plcc_ng-0.1.2/tests/fixtures/trivial-python.plcc +9 -0
- plcc_ng-0.1.2/tests/fixtures/trivial.plcc +3 -0
- plcc_ng-0.1.2/tests/fixtures/trivial_input.txt +1 -0
plcc_ng-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: plcc-ng
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: Programming Languages Compiler Compiler — experimental rewrite of PLCC
|
|
5
|
+
Keywords: compiler,parser,education,plcc,teaching,programming-languages
|
|
6
|
+
Author-Email: PLCC Community <https://discord.gg/EVtNSxS9E2>
|
|
7
|
+
License: AGPL-3.0-or-later
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Education
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Software Development :: Compilers
|
|
15
|
+
Classifier: Topic :: Education
|
|
16
|
+
Project-URL: Homepage, https://github.com/ourPLCC/plcc-ng
|
|
17
|
+
Project-URL: Repository, https://github.com/ourPLCC/plcc-ng
|
|
18
|
+
Project-URL: Issues, https://github.com/ourPLCC/plcc-ng/issues
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Requires-Dist: docopt-ng>=0.9.0
|
|
21
|
+
Requires-Dist: jinja2>=3.1.0
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# plcc-ng
|
|
25
|
+
|
|
26
|
+
PLCC is designed for teaching and learning programming language concepts.
|
|
27
|
+
plcc-ng is a reimagining and reimplementation of PLCC.
|
|
28
|
+
|
|
29
|
+
This is currently under development.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install plcc-ng
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> This package has a separate identity from the original `plcc` package.
|
|
38
|
+
> `plcc-ng` is experimental — no compatibility guarantees with `plcc` until a stable 1.0 release.
|
|
39
|
+
|
|
40
|
+
## Licensing
|
|
41
|
+
|
|
42
|
+
Developers license contributions under [AGPL-3.0-or-later](LICENSES/AGPL-3.0-or-later.txt) and sign off on the
|
|
43
|
+
[DCO-1.1](DCO-1.1.txt)
|
|
44
|
+
|
|
45
|
+
## Community
|
|
46
|
+
|
|
47
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
48
|
+
- [Discord server]((https://discord.gg/EVtNSxS9E2))
|
|
49
|
+
|
|
50
|
+
## Development
|
|
51
|
+
|
|
52
|
+
- Python
|
|
53
|
+
- PDM
|
|
54
|
+
- Codespaces
|
|
55
|
+
- Dev Containers
|
|
56
|
+
- Clean code
|
|
57
|
+
- TDD
|
|
58
|
+
- Open Source Values
|
|
59
|
+
- Continuously run unit tests (^C to stop)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pdm ctest
|
|
63
|
+
```
|
plcc_ng-0.1.2/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# plcc-ng
|
|
2
|
+
|
|
3
|
+
PLCC is designed for teaching and learning programming language concepts.
|
|
4
|
+
plcc-ng is a reimagining and reimplementation of PLCC.
|
|
5
|
+
|
|
6
|
+
This is currently under development.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install plcc-ng
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
> This package has a separate identity from the original `plcc` package.
|
|
15
|
+
> `plcc-ng` is experimental — no compatibility guarantees with `plcc` until a stable 1.0 release.
|
|
16
|
+
|
|
17
|
+
## Licensing
|
|
18
|
+
|
|
19
|
+
Developers license contributions under [AGPL-3.0-or-later](LICENSES/AGPL-3.0-or-later.txt) and sign off on the
|
|
20
|
+
[DCO-1.1](DCO-1.1.txt)
|
|
21
|
+
|
|
22
|
+
## Community
|
|
23
|
+
|
|
24
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
25
|
+
- [Discord server]((https://discord.gg/EVtNSxS9E2))
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
- Python
|
|
30
|
+
- PDM
|
|
31
|
+
- Codespaces
|
|
32
|
+
- Dev Containers
|
|
33
|
+
- Clean code
|
|
34
|
+
- TDD
|
|
35
|
+
- Open Source Values
|
|
36
|
+
- Continuously run unit tests (^C to stop)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pdm ctest
|
|
40
|
+
```
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "plcc-ng"
|
|
3
|
+
dynamic = []
|
|
4
|
+
description = "Programming Languages Compiler Compiler — experimental rewrite of PLCC"
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "PLCC Community", email = "https://discord.gg/EVtNSxS9E2" },
|
|
7
|
+
]
|
|
8
|
+
dependencies = [
|
|
9
|
+
"docopt-ng>=0.9.0",
|
|
10
|
+
"jinja2>=3.1.0",
|
|
11
|
+
]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
keywords = [
|
|
15
|
+
"compiler",
|
|
16
|
+
"parser",
|
|
17
|
+
"education",
|
|
18
|
+
"plcc",
|
|
19
|
+
"teaching",
|
|
20
|
+
"programming-languages",
|
|
21
|
+
]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 3 - Alpha",
|
|
24
|
+
"Intended Audience :: Education",
|
|
25
|
+
"Intended Audience :: Developers",
|
|
26
|
+
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Topic :: Software Development :: Compilers",
|
|
30
|
+
"Topic :: Education",
|
|
31
|
+
]
|
|
32
|
+
version = "0.1.2"
|
|
33
|
+
|
|
34
|
+
[project.license]
|
|
35
|
+
text = "AGPL-3.0-or-later"
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/ourPLCC/plcc-ng"
|
|
39
|
+
Repository = "https://github.com/ourPLCC/plcc-ng"
|
|
40
|
+
Issues = "https://github.com/ourPLCC/plcc-ng/issues"
|
|
41
|
+
|
|
42
|
+
[project.scripts]
|
|
43
|
+
plcc-spec = "plcc.spec.plcc_spec_cli:main"
|
|
44
|
+
plcc-tokens = "plcc.tokens.tokens_cli:main"
|
|
45
|
+
plcc-tree = "plcc.tree.tree_cli:main"
|
|
46
|
+
plcc-model = "plcc.model.model_cli:main"
|
|
47
|
+
plcc-plantuml-diagram = "plcc.diagram.plantuml.emit:main"
|
|
48
|
+
plcc-lang-emit = "plcc.lang.emit:main"
|
|
49
|
+
plcc-diagram = "plcc.diagram.dispatch:main"
|
|
50
|
+
plcc-lang-build = "plcc.lang.build:main"
|
|
51
|
+
plcc-lang-list = "plcc.lang.list:main"
|
|
52
|
+
plcc-diagram-list = "plcc.diagram.list:main"
|
|
53
|
+
plcc-make = "plcc.cmd.make:main"
|
|
54
|
+
plcc-scan = "plcc.cmd.scan:main"
|
|
55
|
+
plcc-parse = "plcc.cmd.parse:main"
|
|
56
|
+
plcc-rep = "plcc.cmd.rep:main"
|
|
57
|
+
plcc-ll1 = "plcc.ll1.ll1_cli:main"
|
|
58
|
+
plcc-parser-table = "plcc.parser.table_cli:main"
|
|
59
|
+
plcc-parser-list = "plcc.parser.list_cli:main"
|
|
60
|
+
plcc-python-emit = "plcc.lang.ext.python.emit:main"
|
|
61
|
+
plcc-lang-run = "plcc.lang.run:main"
|
|
62
|
+
plcc-python-run = "plcc.lang.ext.python.run:main"
|
|
63
|
+
plcc-java-emit = "plcc.lang.ext.java.emit:main"
|
|
64
|
+
plcc-java-build = "plcc.lang.ext.java.build:main"
|
|
65
|
+
plcc-java-run = "plcc.lang.ext.java.run:main"
|
|
66
|
+
|
|
67
|
+
[build-system]
|
|
68
|
+
requires = [
|
|
69
|
+
"pdm-backend",
|
|
70
|
+
]
|
|
71
|
+
build-backend = "pdm.backend"
|
|
72
|
+
|
|
73
|
+
[tool.pdm]
|
|
74
|
+
distribution = true
|
|
75
|
+
|
|
76
|
+
[tool.pdm.build]
|
|
77
|
+
package-dir = "src"
|
|
78
|
+
|
|
79
|
+
[tool.pdm.version]
|
|
80
|
+
source = "scm"
|
|
81
|
+
fallback_version = "0.0.0"
|
|
82
|
+
|
|
83
|
+
[tool.pdm.scripts]
|
|
84
|
+
test = "pytest -qq -rfEsxXP --cov=plcc --cov-branch --cov-report term-missing:skip-covered"
|
|
85
|
+
ctest = "pytest-watch --runner \"bin/test/units.bash\""
|
|
86
|
+
|
|
87
|
+
[tool.semantic_release]
|
|
88
|
+
tag_format = "v{version}"
|
|
89
|
+
version_variables = []
|
|
90
|
+
version_toml = []
|
|
91
|
+
major_on_zero = false
|
|
92
|
+
|
|
93
|
+
[dependency-groups]
|
|
94
|
+
dev = [
|
|
95
|
+
"pytest>=8.2.0",
|
|
96
|
+
"pyfakefs>=5.4.1",
|
|
97
|
+
"pytest-cov>=5.0.0",
|
|
98
|
+
"pytest-watch>=4.2.0",
|
|
99
|
+
"check-jsonschema>=0.29.0",
|
|
100
|
+
"python-semantic-release>=9.0.0",
|
|
101
|
+
"twine>=5.0.0",
|
|
102
|
+
]
|
|
File without changes
|
|
File without changes
|
|
@@ -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)
|
|
@@ -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
|
|
@@ -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}")
|