Add C++ parsergen implementation with tests and CNI layer - #8
Open
mikecovlee wants to merge 46 commits into
Open
mikecovlee wants to merge 46 commits into
mikecovlee wants to merge 46 commits into
Conversation
- Pure C++17 library, zero CovScript dependency - PCRE2 via covscript-regex submodule, utfcpp for Unicode - Algorithm identical to CovScript parsergen v1.4.0: lexer (greedy longest-match), parser (backtracking recursive descent with PEG semantics, bootset prediction, packrat memoization), partial parser (EOF hook + retry), recovering parser (sync points) - CNI layer pending
- CNI layer: full type extensions for grammar/lexer/parser/generator - pcre2_inline.hpp: inline-patched pcre2.hpp to avoid ODR conflicts - parsergen.csp wrapper: converts CovScript variadic API to CNI array API - C++ tests: 14/14 pass; CNI tests: 6/6 pass (JSON parse via CovScript)
…4 pass - Variadic functions via cs::callable(vector&) for cond_or/nlook/repeat/optional - Factory functions (non-protected var) + setter methods for property access - parsergen.csp wrapper: native CovScript classes with property assignment - make_grammar_from() for one-step grammar conversion - ecs_parser.csp unmodified, all 4 ECS test files parse correctly
…dentical (9844 lines, 0 diff)
…t (9844 lines, 0 diff)
…parsergen_cxx verified CovScript parsergen/parsergen_debug: - generator.add_language(lang, coding, gram): compiles string patterns internally, per-language cvt (ascii/utf8/gbk), priv_run selects lexer accordingly - generator.lex_string(lang, text, start_line): standalone lexing for REPL - generator.get_lex_errors()/get_tokens()/get_code(): proper accessors - make_grammar_from(ext, lex, stx): one-shot grammar construction C++ libparsergen: - generator: add_language, lex_string, get_lex_errors, unicode_lexer_ support - from_file: proper PCRE2 regex ext matching (was naive char matcher) - from_string: append trailing newline if missing (match CovScript behavior) - print_ast: S-expression format (identical to CovScript dump) - lexer pos made public for lex_string start_line CNI layer (parsergen_cxx.cse): - Expose add_language, lex_string, get_lex_errors, get_tokens - Remove all pcre2_regex_t dependency (lex only accepts strings) - Remove pcre2_inline.hpp include ecs_parser.csp migration: - get_lexical_patterns(strict): returns string patterns (new) - get_lexical(reg_builder, strict): backward compat wrapper - grammar built via make_grammar_from (no regex import needed) - All in-repo consumers updated to add_language Verified: - CovScript add_language: 4/4 ECS AST identical to old path (9844 lines) - C++ test_ecs_cpp: 4/4 PARSE OK, 14/14 unit tests - CNI add_language: 7/7 pass - CNI drop-in (import parsergen_cxx as parsergen): 4/4 AST identical - 35KB parsergen.csp: CovScript 4518ms vs C++ drop-in 74ms (61x)
…emory leak and type dispatch - Remove --pos[0] compensation from make_token/error in both C++ and CovScript - Fix lexer cursor tracking: check consumed char instead of look-ahead - CNI: remove legacy aliases (ast, stop_on_error, show_prompt, etc.), expose unified API only - Fix pparser_run reference cycle: capture weak_ptr instead of shared_ptr - Fix print_error type dispatch: use typeid comparison instead of string match - Add from_stream to generator; clear file_path in from_string/from_stream - Add override to partial_parser_type::match_syntax - Add API.md (unified) and CXX_API.md (C++ native) references - Add drop-in replace test (13 cases), partial parser test, typeid test
…stency - parser_type: copy grammar into syn_storage to prevent dangling pointer - lexer run(): preserve pos[1] (start_line) instead of resetting all - generator priv_run: reset lexer_/unicode_lexer_/parser_/ast_ at top - generator from_file: inline logic instead of delegating to from_string - parse_with_recovery: add stack.size()==1 check (both C++ and CovScript) - expand_pending_ref: throw on undefined grammar reference (both sides) - CovScript lexer run(): add state resets matching C++ behavior - CovScript generator priv_run: reset ast/token_buff/parser/lexer at top
…job, package descriptor
…ewline, tab-to-space, lex lifetime, remove dead header - CI: remove cp that overwrites modified ecs_parser (use tracked version) - CMakeLists: guard --export-all-symbols with if(WIN32) - generator: from_string no longer appends trailing newline (matches CovScript) - generator: from_stream/from_file replace tabs with spaces in code_buff - parser: clear lex pointer after parse/parse_with_recovery completes - Remove dead pcre2_inline.hpp (unused vendored copy)
…MPL controls implementation
…<FILE> default in from_string/from_stream
… semantically correct for non-negative positions
There was a problem hiding this comment.
Pull request overview
This PR introduces a C++ implementation of ParserGen (native libparsergen + CovScript CNI extension parsergen_cxx.cse) and updates the CovScript side to a unified API surface so tests can run against either implementation.
Changes:
- Add a full C++ ParserGen library (lexer/parser/generator + unicode support) and a CNI layer exporting the unified API to CovScript.
- Update CovScript ParserGen and ECS grammar packaging to support unified constructors/helpers (
make_grammar_from,make_lex_error,add_language, etc.) and implementation selection viaPARSERGEN_IMPL. - Add C++ tests/benchmarks and extend GitHub Actions CI to build/test the C++ implementation and run drop-in replacement tests.
Reviewed changes
Copilot reviewed 45 out of 46 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/run_parser.ecs | Switch ECS language registration to unified add_language. |
| tests/ecs_repl.ecs | Precompile lexical rules once and reuse in the REPL lexer. |
| tests/ecs_parser.csp | Make ParserGen implementation selectable and split lexical patterns vs compiled lexical rules. |
| parsergen.csp | Add unified API helpers/methods and state resets; add per-language encoding support. |
| parsergen_debug.csp | Mirror unified API helpers/methods and state resets in debug implementation. |
| CXX_API.md | Document the C++ native API of libparsergen. |
| csbuild/parsergen_cxx.json | Add csbuild extension manifest for parsergen_cxx. |
| csbuild/make.sh | Add Unix build script to produce parsergen_cxx.cse. |
| csbuild/make.bat | Add Windows build script to produce parsergen_cxx.cse. |
| csbuild/format.sh | Add Unix formatting script for C++ sources. |
| csbuild/format.bat | Add Windows formatting script for C++ sources. |
| cpp/test/test_v1_old.csc | Add legacy-style test (currently needs lex compilation fix). |
| cpp/test/test_typeid.csc | Add CNI typeid interoperability test. |
| cpp/test/test_partial_parser.csc | Add partial-parser EOF-hook test via CNI. |
| cpp/test/test_ecs_cpp.cpp | Add native C++ ECS parsing demo/test. |
| cpp/test/test_dropin_all.csc | Add “drop-in replace” compatibility test suite for CNI implementation. |
| cpp/test/test_cni_add_language.csc | Add CNI-focused unified add_language + lex_string tests. |
| cpp/test/test_add_language.csc | Add unified add_language usage example/test for CovScript implementation. |
| cpp/test/main.cpp | Add native C++ unit tests for core behaviors (JSON/TINY/empty/recovery). |
| cpp/test/ecs_parser.hpp | Add C++ ECS grammar definition used by native C++ tests/benchmarks. |
| cpp/test/bench_native.csc | Add benchmark script using unified add_language. |
| cpp/test/bench_lex_parse.csc | Add benchmark separating lex vs parse on CovScript implementation. |
| cpp/test/bench_lex_parse_cpp.cpp | Add native C++ lex/parse benchmark. |
| cpp/test/ast_dump_native.csc | Add AST dumper script using unified generator API. |
| cpp/src/unicode_lexer.cpp | Implement C++ unicode lexer using PCRE2-32. |
| cpp/src/parser.cpp | Implement C++ parser, partial parser, and recovering parser. |
| cpp/src/lexer.cpp | Implement C++ lexer and regex helpers. |
| cpp/src/generator.cpp | Implement C++ generator (file/string/stream parsing and error formatting). |
| cpp/include/parsergen/unicode.hpp | Add C++ codecvt charset implementations (ascii/utf8/gbk). |
| cpp/include/parsergen/token.hpp | Add C++ token and lex_error types. |
| cpp/include/parsergen/syntax.hpp | Add C++ syntax factory API. |
| cpp/include/parsergen/parsergen.hpp | Add umbrella header for the C++ API. |
| cpp/include/parsergen/parser.hpp | Add C++ parser class declarations. |
| cpp/include/parsergen/lexer.hpp | Add C++ lexer/unicode lexer declarations and grammar type. |
| cpp/include/parsergen/generator.hpp | Add C++ generator declarations. |
| cpp/include/parsergen/bootset.hpp | Add C++ bootset/prediction set type. |
| cpp/include/parsergen/ast.hpp | Add C++ AST structures and printer. |
| cpp/cni/parsergen_cni.cpp | Add CovScript CNI bindings for the unified API (parsergen_cxx). |
| cpp/CMakeLists.txt | Add CMake build for libparsergen, tests, and optional CNI build. |
| API.md | Add unified API reference across CovScript and C++ CNI implementations. |
| .gitmodules | Add submodules for covscript-regex and utfcpp. |
| .gitignore | Ignore C++ build artifacts and local test data. |
| .github/workflows/ci.yml | Extend CI to build/test C++ implementation and run drop-in tests across OSes. |
| .editorconfig | Add EditorConfig to standardize formatting across file types. |
Suppressed comments (2)
parsergen.csp:249
unicode_lexer_type.run()later assignswpos = posand passesposintoerror(...), butposis[col, line, cursor]. This makes token/error positions contain an extra cursor element instead of[col, line].
parsergen_debug.csp:288unicode_lexer_type.run()later doeswpos = pos/error(..., pos)even thoughposis[col, line, cursor]. This causes token/error positions to contain an extra element instead of[col, line].
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 44 out of 45 changed files in this pull request and generated no new comments.
Suppressed comments (3)
API.md:79
add_grammar(lang, gram)is documented as equivalent toadd_language(lang, "ascii", gram), but in the CovScript implementationadd_languagerecompilesgram.lexviaregex.build_optimize(...)whileadd_grammarsimply stores the grammar as-is. This matters because existing grammars (e.g. tests/run_parser.ecs) commonly store compiled regex objects ingram.lex, which would not be valid input foradd_languageas currently implemented.
| `add_grammar(lang, gram)` | `add_language(lang, "ascii", gram)` | 保留 |
parsergen.csp:1141
generator.add_languageassumesgram.lexcontains string patterns and unconditionally callsregex.build_optimize(it.second). However, other in-repo grammars store compiled regex objects ingram.lex(see tests/run_parser.ecs), and API.md currently impliesadd_grammarcan be replaced byadd_language. Consider accepting either precompiled regex objects or pattern strings here to avoid runtime type errors and make the unified API less surprising.
parsergen_debug.csp:1374- Same as parsergen.csp:
generator.add_languagealways recompilesgram.lexviaregex.build_optimize(...), which will fail if callers pass a grammar whoselexentries are already compiled regex objects (common in existing scripts). Supporting both compiled-regex and string-pattern inputs would reduce breakage and better match the unified API docs.
…, fix CI cspkg and paths
…-network pattern) - Switch from var::make_protect to var::make_constant for type_t registration - Use type_id(typeid(...)) instead of std::type_index(typeid(...)) - type_t is constructed via 'new' keyword, not () call syntax
…, fix unicode.hpp indent - Rename private/protected members to m_ prefix (match CovScript SDK style) - Add final to leaf classes (lexer_type, unicode_lexer_type, generator, partial_parser_type, recovering_parser_type) - Change parse_state from namespace+constexpr to enum class - Initialize syntax_impl::type default value - Mark production() const - Fix unicode.hpp is_identifier indentation (restructure to early-return, astyle-stable)
… const production)
- API.md: correct parser.log (read-only in C++, use generator.set_enable_log), clarify gram.ext/lex/stx read-only in C++, document property-assignment limits - CXX_API.md: sync m_ prefix, final, enum class parse_state, const production, syntax_impl::type default - README.md / README-zh.md: rewrite Quick Start to unified API (make_grammar_from + add_language + get_ast), add C++ implementation section, update project structure; fix '*' input that grammar didn't support - docs/SYNTAX.md / SYNTAX-zh.md: lexical rules as pattern strings (not regex.build), fix Complete Example grammar to support '*' (was unparseable) - Verified all examples run identically on parsergen and parsergen_cxx
…ignment - API.md: add prominent section explaining unified API is method-first; property assignment does not穿透 C++ CNI objects (silently no-op), so get_*/set_*/make_*/add_language should be used; read-only AST/token/error field access remains supported on both impls - Mark AST node access section as read-only - README.md / README-zh.md: ebnf_parser example now uses make_grammar_from instead of 'new parsergen.grammar' + field assignment
…failed-parse codegen path get_ast()/production() returned a typed object var holding a null pointer, which never compares equal to CovScript null (any::compare requires both operands non-null). ecs bootstrap's 'if parser.get_ast() == null' therefore fell through to codegen with an empty AST; walking it via .nodes segfaulted. - gen_get_ast/parser_production/pparser_production now return cs::var: null_pointer when empty (matches CovScript parsergen semantics) - tree_root/tree_nodes throw catchable lang_error on null instead of UB - drop-in tests: add null-AST regression cases (17 checks total)
…ile; add CNI script tests + docs
…+ pass-by-value); add clear_eof_hook to partial parser (CSC/C++ parity); MinGW-only --export-all-symbols; add CSC partial parser unit tests
Guard against recurring CRLF churn in the worktree. Working tree was normalized back to LF.
- from_file selects a language by anchored full-match of the whole path (new pg::regex_match_full, PCRE2_ANCHORED|ENDANCHORED) to match the CovScript reference; the previous unanchored search over-selected for ext patterns without a leading '.*'. - generator::lex_string now returns nullopt (CovScript null via the CNI) for an unregistered language instead of an empty list, restoring the 'if tokens == null' REPL idiom. Tests: C++ from_file full-match accept/reject + lex_string unknown->nullopt; CNI script lex_string unknown->null.
The unified API documents lex patterns as strings, which the C++/CNI side accepts; the CovScript reference's lexer_type.run only accepted pre-compiled regex objects. run() now normalizes the lex map at entry - string patterns are compiled (regex.build_optimize), regex objects pass through - leaving the generator path (pre-compiled objects) unchanged while making string- based run() portable. unicode_lexer_type (internal) is untouched. Test: CSC unit test lexing via string patterns.
…erns API.md / CXX_API.md now state: from_file ext is a full-path (anchored) match; lex_string returns null for an unregistered language; and lexer_type.run accepts string patterns (portable), with the CovScript side also accepting compiled regex objects.
…nts) - generator::lex_string: reset m_lexer/m_unicode_lexer so get_lex_errors reflects the active lexer (parity with the single-lexer CSC generator) - generator::from_string: drop the \r strip to match CSC behaviour - parser: add explicit 'case parse_state::accept' to the 4 predict switches to silence -Wswitch (behavior unchanged) - parser::init: throw a clear error when a grammar lacks a 'begin' rule - replace magic numbers with named constants (kMaxRecoveryRetries; static constexpr m_max_prediction_pass) - fix two misleading-indentation spots (production, gbk::is_identifier) Verified: clean build with no -Wswitch, parsergen_test 29/0, test_ecs_cpp 4/4 PARSE OK, all CNI scripts pass, and C++/CSC AST dumps remain byte-identical (9844 lines, 0 diff).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.