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.
Files changed (275) hide show
  1. plcc_ng-0.1.2/PKG-INFO +63 -0
  2. plcc_ng-0.1.2/README.md +40 -0
  3. plcc_ng-0.1.2/pyproject.toml +102 -0
  4. plcc_ng-0.1.2/src/plcc/__init__.py +0 -0
  5. plcc_ng-0.1.2/src/plcc/cmd/__init__.py +0 -0
  6. plcc_ng-0.1.2/src/plcc/cmd/make.py +140 -0
  7. plcc_ng-0.1.2/src/plcc/cmd/make_test.py +74 -0
  8. plcc_ng-0.1.2/src/plcc/cmd/parse.py +146 -0
  9. plcc_ng-0.1.2/src/plcc/cmd/rep.py +190 -0
  10. plcc_ng-0.1.2/src/plcc/cmd/scan.py +112 -0
  11. plcc_ng-0.1.2/src/plcc/cmd/skeleton_test.py +0 -0
  12. plcc_ng-0.1.2/src/plcc/diagram/__init__.py +0 -0
  13. plcc_ng-0.1.2/src/plcc/diagram/dispatch.py +48 -0
  14. plcc_ng-0.1.2/src/plcc/diagram/dispatch_test.py +86 -0
  15. plcc_ng-0.1.2/src/plcc/diagram/list.py +68 -0
  16. plcc_ng-0.1.2/src/plcc/diagram/list_test.py +32 -0
  17. plcc_ng-0.1.2/src/plcc/diagram/plantuml/__init__.py +0 -0
  18. plcc_ng-0.1.2/src/plcc/diagram/plantuml/emit.py +61 -0
  19. plcc_ng-0.1.2/src/plcc/diagram/plantuml/emit_test.py +79 -0
  20. plcc_ng-0.1.2/src/plcc/lang/__init__.py +0 -0
  21. plcc_ng-0.1.2/src/plcc/lang/build.py +43 -0
  22. plcc_ng-0.1.2/src/plcc/lang/build_test.py +22 -0
  23. plcc_ng-0.1.2/src/plcc/lang/emit.py +51 -0
  24. plcc_ng-0.1.2/src/plcc/lang/emit_test.py +29 -0
  25. plcc_ng-0.1.2/src/plcc/lang/ext/__init__.py +0 -0
  26. plcc_ng-0.1.2/src/plcc/lang/ext/java/__init__.py +0 -0
  27. plcc_ng-0.1.2/src/plcc/lang/ext/java/build.py +53 -0
  28. plcc_ng-0.1.2/src/plcc/lang/ext/java/emit.py +106 -0
  29. plcc_ng-0.1.2/src/plcc/lang/ext/java/emit_test.py +180 -0
  30. plcc_ng-0.1.2/src/plcc/lang/ext/java/run.py +50 -0
  31. plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/Deserializer.java +46 -0
  32. plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/Node.java +4 -0
  33. plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/Registry.java +28 -0
  34. plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/Token.java +16 -0
  35. plcc_ng-0.1.2/src/plcc/lang/ext/java/runtime/org.json-20250107.jar +0 -0
  36. plcc_ng-0.1.2/src/plcc/lang/ext/java/templates/Main.java.jinja +37 -0
  37. plcc_ng-0.1.2/src/plcc/lang/ext/java/templates/class_file.java.jinja +39 -0
  38. plcc_ng-0.1.2/src/plcc/lang/ext/python/__init__.py +0 -0
  39. plcc_ng-0.1.2/src/plcc/lang/ext/python/emit.py +100 -0
  40. plcc_ng-0.1.2/src/plcc/lang/ext/python/emit_test.py +132 -0
  41. plcc_ng-0.1.2/src/plcc/lang/ext/python/run.py +44 -0
  42. plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/__init__.py +0 -0
  43. plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/base.py +8 -0
  44. plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/base_test.py +17 -0
  45. plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/deserialize.py +22 -0
  46. plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/deserialize_test.py +115 -0
  47. plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/registry.py +27 -0
  48. plcc_ng-0.1.2/src/plcc/lang/ext/python/runtime/registry_test.py +67 -0
  49. plcc_ng-0.1.2/src/plcc/lang/ext/python/templates/class_file.py.jinja +21 -0
  50. plcc_ng-0.1.2/src/plcc/lang/ext/python/templates/main.py.jinja +22 -0
  51. plcc_ng-0.1.2/src/plcc/lang/list.py +69 -0
  52. plcc_ng-0.1.2/src/plcc/lang/list_test.py +27 -0
  53. plcc_ng-0.1.2/src/plcc/lang/run.py +45 -0
  54. plcc_ng-0.1.2/src/plcc/lines/Line.py +8 -0
  55. plcc_ng-0.1.2/src/plcc/lines/__init__.py +2 -0
  56. plcc_ng-0.1.2/src/plcc/lines/parseLines.py +16 -0
  57. plcc_ng-0.1.2/src/plcc/lines/parse_from_file.py +10 -0
  58. plcc_ng-0.1.2/src/plcc/lines/parse_from_string.py +5 -0
  59. plcc_ng-0.1.2/src/plcc/lines/parse_from_string_test.py +54 -0
  60. plcc_ng-0.1.2/src/plcc/lines/parse_from_strings.py +6 -0
  61. plcc_ng-0.1.2/src/plcc/ll1/__init__.py +0 -0
  62. plcc_ng-0.1.2/src/plcc/ll1/ll1_cli.py +64 -0
  63. plcc_ng-0.1.2/src/plcc/ll1/ll1_cli_test.py +93 -0
  64. plcc_ng-0.1.2/src/plcc/ll1/ll1_result_builder.py +122 -0
  65. plcc_ng-0.1.2/src/plcc/ll1/ll1_result_builder_test.py +225 -0
  66. plcc_ng-0.1.2/src/plcc/ll1/spec_json_decoder.py +70 -0
  67. plcc_ng-0.1.2/src/plcc/ll1/spec_json_decoder_test.py +184 -0
  68. plcc_ng-0.1.2/src/plcc/model/__init__.py +0 -0
  69. plcc_ng-0.1.2/src/plcc/model/build_model.py +155 -0
  70. plcc_ng-0.1.2/src/plcc/model/build_model_test.py +468 -0
  71. plcc_ng-0.1.2/src/plcc/model/model_cli.py +44 -0
  72. plcc_ng-0.1.2/src/plcc/model/model_cli_test.py +62 -0
  73. plcc_ng-0.1.2/src/plcc/parser/__init__.py +0 -0
  74. plcc_ng-0.1.2/src/plcc/parser/list_cli.py +67 -0
  75. plcc_ng-0.1.2/src/plcc/parser/predictive_parser.py +152 -0
  76. plcc_ng-0.1.2/src/plcc/parser/predictive_parser_test.py +263 -0
  77. plcc_ng-0.1.2/src/plcc/parser/table_cli.py +89 -0
  78. plcc_ng-0.1.2/src/plcc/parser/table_cli_test.py +161 -0
  79. plcc_ng-0.1.2/src/plcc/scan/LexError.py +12 -0
  80. plcc_ng-0.1.2/src/plcc/scan/Skip.py +11 -0
  81. plcc_ng-0.1.2/src/plcc/scan/Token.py +9 -0
  82. plcc_ng-0.1.2/src/plcc/scan/__init__.py +0 -0
  83. plcc_ng-0.1.2/src/plcc/scan/matcher.py +61 -0
  84. plcc_ng-0.1.2/src/plcc/scan/matcher_test.py +126 -0
  85. plcc_ng-0.1.2/src/plcc/scan/scanner.py +23 -0
  86. plcc_ng-0.1.2/src/plcc/scan/scanner_test.py +101 -0
  87. plcc_ng-0.1.2/src/plcc/scan/sink.py +12 -0
  88. plcc_ng-0.1.2/src/plcc/scan/sink_test.py +118 -0
  89. plcc_ng-0.1.2/src/plcc/scan/source.py +25 -0
  90. plcc_ng-0.1.2/src/plcc/scan/source_test.py +119 -0
  91. plcc_ng-0.1.2/src/plcc/schemas/ll1.schema.json +65 -0
  92. plcc_ng-0.1.2/src/plcc/schemas/model.schema.json +61 -0
  93. plcc_ng-0.1.2/src/plcc/schemas/spec.schema.json +46 -0
  94. plcc_ng-0.1.2/src/plcc/schemas/token.schema.json +21 -0
  95. plcc_ng-0.1.2/src/plcc/schemas/tree.schema.json +34 -0
  96. plcc_ng-0.1.2/src/plcc/spec/Spec.py +10 -0
  97. plcc_ng-0.1.2/src/plcc/spec/SpecError.py +11 -0
  98. plcc_ng-0.1.2/src/plcc/spec/SpecError_test.py +34 -0
  99. plcc_ng-0.1.2/src/plcc/spec/ValidationError.py +4 -0
  100. plcc_ng-0.1.2/src/plcc/spec/__init__.py +48 -0
  101. plcc_ng-0.1.2/src/plcc/spec/lexical/DuplicateName.py +7 -0
  102. plcc_ng-0.1.2/src/plcc/spec/lexical/LexicalRule.py +11 -0
  103. plcc_ng-0.1.2/src/plcc/spec/lexical/LexicalSpec.py +12 -0
  104. plcc_ng-0.1.2/src/plcc/spec/lexical/LexicalSpecError.py +5 -0
  105. plcc_ng-0.1.2/src/plcc/spec/lexical/NameExpected.py +8 -0
  106. plcc_ng-0.1.2/src/plcc/spec/lexical/Parser.py +83 -0
  107. plcc_ng-0.1.2/src/plcc/spec/lexical/PatternCompilationError.py +6 -0
  108. plcc_ng-0.1.2/src/plcc/spec/lexical/PatternDelimiterExpected.py +6 -0
  109. plcc_ng-0.1.2/src/plcc/spec/lexical/PatternExpected.py +5 -0
  110. plcc_ng-0.1.2/src/plcc/spec/lexical/UnexpectedContent.py +5 -0
  111. plcc_ng-0.1.2/src/plcc/spec/lexical/__init__.py +10 -0
  112. plcc_ng-0.1.2/src/plcc/spec/lexical/check_for_duplicate_names.py +12 -0
  113. plcc_ng-0.1.2/src/plcc/spec/lexical/parseLexicalSpec.py +10 -0
  114. plcc_ng-0.1.2/src/plcc/spec/lexical/parse_from_lines.py +4 -0
  115. plcc_ng-0.1.2/src/plcc/spec/lexical/parse_from_string.py +7 -0
  116. plcc_ng-0.1.2/src/plcc/spec/lexical/parse_lexical_test.py +247 -0
  117. plcc_ng-0.1.2/src/plcc/spec/parseSpec.py +13 -0
  118. plcc_ng-0.1.2/src/plcc/spec/parseSpec_test.py +50 -0
  119. plcc_ng-0.1.2/src/plcc/spec/plcc_spec_cli.py +50 -0
  120. plcc_ng-0.1.2/src/plcc/spec/plcc_spec_cli_test.py +49 -0
  121. plcc_ng-0.1.2/src/plcc/spec/rough/Block.py +8 -0
  122. plcc_ng-0.1.2/src/plcc/spec/rough/CircularIncludeError.py +5 -0
  123. plcc_ng-0.1.2/src/plcc/spec/rough/Divider.py +11 -0
  124. plcc_ng-0.1.2/src/plcc/spec/rough/Include.py +9 -0
  125. plcc_ng-0.1.2/src/plcc/spec/rough/UnclosedBlockError.py +5 -0
  126. plcc_ng-0.1.2/src/plcc/spec/rough/__init__.py +6 -0
  127. plcc_ng-0.1.2/src/plcc/spec/rough/iterate_rough.py +16 -0
  128. plcc_ng-0.1.2/src/plcc/spec/rough/parseRough.py +10 -0
  129. plcc_ng-0.1.2/src/plcc/spec/rough/parseRough_test.py +75 -0
  130. plcc_ng-0.1.2/src/plcc/spec/rough/parse_blocks.py +66 -0
  131. plcc_ng-0.1.2/src/plcc/spec/rough/parse_blocks_test.py +95 -0
  132. plcc_ng-0.1.2/src/plcc/spec/rough/parse_dividers.py +77 -0
  133. plcc_ng-0.1.2/src/plcc/spec/rough/parse_dividers_test.py +88 -0
  134. plcc_ng-0.1.2/src/plcc/spec/rough/parse_from_lines.py +6 -0
  135. plcc_ng-0.1.2/src/plcc/spec/rough/parse_from_lines_test.py +9 -0
  136. plcc_ng-0.1.2/src/plcc/spec/rough/parse_from_string.py +6 -0
  137. plcc_ng-0.1.2/src/plcc/spec/rough/parse_includes.py +18 -0
  138. plcc_ng-0.1.2/src/plcc/spec/rough/parse_includes_test.py +53 -0
  139. plcc_ng-0.1.2/src/plcc/spec/rough/raise_handler.py +2 -0
  140. plcc_ng-0.1.2/src/plcc/spec/rough/resolve_includes.py +69 -0
  141. plcc_ng-0.1.2/src/plcc/spec/rough/resolve_includes_test.py +52 -0
  142. plcc_ng-0.1.2/src/plcc/spec/semantics/CodeFragment.py +10 -0
  143. plcc_ng-0.1.2/src/plcc/spec/semantics/InvalidClassNameError.py +10 -0
  144. plcc_ng-0.1.2/src/plcc/spec/semantics/SemanticSpec.py +11 -0
  145. plcc_ng-0.1.2/src/plcc/spec/semantics/TargetLocator.py +10 -0
  146. plcc_ng-0.1.2/src/plcc/spec/semantics/UndefinedBlockError.py +10 -0
  147. plcc_ng-0.1.2/src/plcc/spec/semantics/UndefinedTargetLocatorError.py +10 -0
  148. plcc_ng-0.1.2/src/plcc/spec/semantics/__init__.py +2 -0
  149. plcc_ng-0.1.2/src/plcc/spec/semantics/parse_code_fragments.py +57 -0
  150. plcc_ng-0.1.2/src/plcc/spec/semantics/parse_code_fragments_test.py +83 -0
  151. plcc_ng-0.1.2/src/plcc/spec/semantics/parse_semantic_spec.py +15 -0
  152. plcc_ng-0.1.2/src/plcc/spec/semantics/parse_semantic_spec_test.py +44 -0
  153. plcc_ng-0.1.2/src/plcc/spec/semantics/parse_target_locator.py +16 -0
  154. plcc_ng-0.1.2/src/plcc/spec/semantics/parse_target_locator_test.py +31 -0
  155. plcc_ng-0.1.2/src/plcc/spec/semantics/validation.py +48 -0
  156. plcc_ng-0.1.2/src/plcc/spec/semantics/validation_test.py +105 -0
  157. plcc_ng-0.1.2/src/plcc/spec/split_rough.py +27 -0
  158. plcc_ng-0.1.2/src/plcc/spec/syntax/CapturingSymbol.py +14 -0
  159. plcc_ng-0.1.2/src/plcc/spec/syntax/CapturingTerminal.py +9 -0
  160. plcc_ng-0.1.2/src/plcc/spec/syntax/DuplicateAttribute.py +12 -0
  161. plcc_ng-0.1.2/src/plcc/spec/syntax/DuplicateLhsError.py +12 -0
  162. plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidAttribute.py +12 -0
  163. plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidLhsAltNameError.py +12 -0
  164. plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidLhsNameError.py +12 -0
  165. plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidNonterminal.py +12 -0
  166. plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidSeparator.py +12 -0
  167. plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidSymbolException.py +8 -0
  168. plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidSyntacticSpecException.py +8 -0
  169. plcc_ng-0.1.2/src/plcc/spec/syntax/InvalidTerminal.py +12 -0
  170. plcc_ng-0.1.2/src/plcc/spec/syntax/LL1Error.py +9 -0
  171. plcc_ng-0.1.2/src/plcc/spec/syntax/LhsNonTerminal.py +10 -0
  172. plcc_ng-0.1.2/src/plcc/spec/syntax/MalformedBNFError.py +3 -0
  173. plcc_ng-0.1.2/src/plcc/spec/syntax/NonTerminal.py +9 -0
  174. plcc_ng-0.1.2/src/plcc/spec/syntax/RepeatingSyntacticRule.py +10 -0
  175. plcc_ng-0.1.2/src/plcc/spec/syntax/RhsNonTerminal.py +9 -0
  176. plcc_ng-0.1.2/src/plcc/spec/syntax/StandardSyntacticRule.py +8 -0
  177. plcc_ng-0.1.2/src/plcc/spec/syntax/Symbol.py +6 -0
  178. plcc_ng-0.1.2/src/plcc/spec/syntax/SyntacticRule.py +13 -0
  179. plcc_ng-0.1.2/src/plcc/spec/syntax/SyntacticSpec.py +26 -0
  180. plcc_ng-0.1.2/src/plcc/spec/syntax/Terminal.py +10 -0
  181. plcc_ng-0.1.2/src/plcc/spec/syntax/UndefinedNonterminal.py +12 -0
  182. plcc_ng-0.1.2/src/plcc/spec/syntax/UndefinedTerminalError.py +12 -0
  183. plcc_ng-0.1.2/src/plcc/spec/syntax/__init__.py +1 -0
  184. plcc_ng-0.1.2/src/plcc/spec/syntax/parse_syntactic_spec.py +142 -0
  185. plcc_ng-0.1.2/src/plcc/spec/syntax/parse_syntactic_spec_test.py +411 -0
  186. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/__init__.py +0 -0
  187. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/Grammar.py +68 -0
  188. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/Grammar_test.py +115 -0
  189. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/LL1Wrapper.py +17 -0
  190. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/LL1Wrapper_test.py +119 -0
  191. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/__init__.py +1 -0
  192. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_first_sets.py +60 -0
  193. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_first_sets_test.py +94 -0
  194. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_follow_sets.py +79 -0
  195. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_follow_sets_test.py +106 -0
  196. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_parsing_table.py +69 -0
  197. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_parsing_table_test.py +37 -0
  198. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_spec_grammar.py +104 -0
  199. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/build_spec_grammar_test.py +112 -0
  200. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_left_recursion.py +96 -0
  201. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_left_recursion_test.py +134 -0
  202. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_ll1.py +31 -0
  203. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_ll1_test.py +30 -0
  204. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_parsing_table_for_ll1.py +10 -0
  205. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/ll1/check_parsing_table_for_ll1_test.py +38 -0
  206. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/replace_repeating_with_standard_rules.py +107 -0
  207. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/replace_repeating_with_standard_rules_test.py +138 -0
  208. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_lhs.py +63 -0
  209. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_lhs_test.py +136 -0
  210. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_rhs.py +99 -0
  211. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_rhs_test.py +122 -0
  212. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_syntactic_spec.py +38 -0
  213. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_syntactic_spec_test.py +62 -0
  214. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_terminals_defined.py +46 -0
  215. plcc_ng-0.1.2/src/plcc/spec/syntax/validations/validate_terminals_defined_test.py +250 -0
  216. plcc_ng-0.1.2/src/plcc/tokens/__init__.py +0 -0
  217. plcc_ng-0.1.2/src/plcc/tokens/jsonl_formatter.py +20 -0
  218. plcc_ng-0.1.2/src/plcc/tokens/jsonl_formatter_test.py +33 -0
  219. plcc_ng-0.1.2/src/plcc/tokens/spec_loader.py +22 -0
  220. plcc_ng-0.1.2/src/plcc/tokens/spec_loader_test.py +31 -0
  221. plcc_ng-0.1.2/src/plcc/tokens/tokens_cli.py +61 -0
  222. plcc_ng-0.1.2/src/plcc/tokens/tokens_cli_test.py +73 -0
  223. plcc_ng-0.1.2/src/plcc/tree/__init__.py +0 -0
  224. plcc_ng-0.1.2/src/plcc/tree/tree_cli.py +47 -0
  225. plcc_ng-0.1.2/src/plcc/tree/tree_cli_test.py +16 -0
  226. plcc_ng-0.1.2/src/plcc/verbose.py +181 -0
  227. plcc_ng-0.1.2/src/plcc/verbose_test.py +267 -0
  228. plcc_ng-0.1.2/tests/bats/commands/.gitkeep +0 -0
  229. plcc_ng-0.1.2/tests/bats/commands/plcc-diagram-list.bats +10 -0
  230. plcc_ng-0.1.2/tests/bats/commands/plcc-diagram.bats +34 -0
  231. plcc_ng-0.1.2/tests/bats/commands/plcc-java-build.bats +30 -0
  232. plcc_ng-0.1.2/tests/bats/commands/plcc-java-emit.bats +40 -0
  233. plcc_ng-0.1.2/tests/bats/commands/plcc-java-run.bats +28 -0
  234. plcc_ng-0.1.2/tests/bats/commands/plcc-lang-build.bats +18 -0
  235. plcc_ng-0.1.2/tests/bats/commands/plcc-lang-emit.bats +30 -0
  236. plcc_ng-0.1.2/tests/bats/commands/plcc-lang-list.bats +11 -0
  237. plcc_ng-0.1.2/tests/bats/commands/plcc-lang-run.bats +34 -0
  238. plcc_ng-0.1.2/tests/bats/commands/plcc-ll1.bats +59 -0
  239. plcc_ng-0.1.2/tests/bats/commands/plcc-make.bats +43 -0
  240. plcc_ng-0.1.2/tests/bats/commands/plcc-model.bats +44 -0
  241. plcc_ng-0.1.2/tests/bats/commands/plcc-parse.bats +37 -0
  242. plcc_ng-0.1.2/tests/bats/commands/plcc-parser-list.bats +21 -0
  243. plcc_ng-0.1.2/tests/bats/commands/plcc-parser-table.bats +73 -0
  244. plcc_ng-0.1.2/tests/bats/commands/plcc-plantuml-diagram.bats +33 -0
  245. plcc_ng-0.1.2/tests/bats/commands/plcc-python-emit.bats +26 -0
  246. plcc_ng-0.1.2/tests/bats/commands/plcc-python-run.bats +31 -0
  247. plcc_ng-0.1.2/tests/bats/commands/plcc-rep.bats +41 -0
  248. plcc_ng-0.1.2/tests/bats/commands/plcc-scan.bats +44 -0
  249. plcc_ng-0.1.2/tests/bats/commands/plcc-spec.bats +47 -0
  250. plcc_ng-0.1.2/tests/bats/commands/plcc-tokens.bats +48 -0
  251. plcc_ng-0.1.2/tests/bats/commands/plcc-tree.bats +50 -0
  252. plcc_ng-0.1.2/tests/bats/e2e/.gitkeep +0 -0
  253. plcc_ng-0.1.2/tests/bats/e2e/error-propagation.bats +34 -0
  254. plcc_ng-0.1.2/tests/bats/e2e/happy-path.bats +73 -0
  255. plcc_ng-0.1.2/tests/bats/e2e/languages-java.bats +58 -0
  256. plcc_ng-0.1.2/tests/bats/e2e/plcc-rep.bats +106 -0
  257. plcc_ng-0.1.2/tests/bats/integration/.gitkeep +0 -0
  258. plcc_ng-0.1.2/tests/bats/integration/java-emit.bats +65 -0
  259. plcc_ng-0.1.2/tests/bats/integration/ll1-tree.bats +36 -0
  260. plcc_ng-0.1.2/tests/bats/integration/model-lang-emit.bats +18 -0
  261. plcc_ng-0.1.2/tests/bats/integration/plcc-parse-errors.bats +22 -0
  262. plcc_ng-0.1.2/tests/bats/integration/python-emit.bats +65 -0
  263. plcc_ng-0.1.2/tests/bats/integration/spec-ll1.bats +14 -0
  264. plcc_ng-0.1.2/tests/bats/integration/spec-model.bats +21 -0
  265. plcc_ng-0.1.2/tests/bats/integration/spec-tokens.bats +20 -0
  266. plcc_ng-0.1.2/tests/bats/integration/tokens-tree.bats +20 -0
  267. plcc_ng-0.1.2/tests/fixtures/arith.plcc +36 -0
  268. plcc_ng-0.1.2/tests/fixtures/languages-corpus.txt +32 -0
  269. plcc_ng-0.1.2/tests/fixtures/languages-pin.txt +1 -0
  270. plcc_ng-0.1.2/tests/fixtures/trivial-arbno.plcc +26 -0
  271. plcc_ng-0.1.2/tests/fixtures/trivial-full.plcc +5 -0
  272. plcc_ng-0.1.2/tests/fixtures/trivial-java.plcc +11 -0
  273. plcc_ng-0.1.2/tests/fixtures/trivial-python.plcc +9 -0
  274. plcc_ng-0.1.2/tests/fixtures/trivial.plcc +3 -0
  275. 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
+ ```
@@ -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}")