Skip to content

Add C++ parsergen implementation with tests and CNI layer - #8

Open
mikecovlee wants to merge 46 commits into
mainfrom
cxx_impl
Open

mikecovlee wants to merge 46 commits into
mainfrom
cxx_impl

Conversation

@mikecovlee

Copy link
Copy Markdown
Owner

No description provided.

- 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
…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
…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)
… semantically correct for non-negative positions

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 via PARSERGEN_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 assigns wpos = pos and passes pos into error(...), but pos is [col, line, cursor]. This makes token/error positions contain an extra cursor element instead of [col, line].
    parsergen_debug.csp:288
  • unicode_lexer_type.run() later does wpos = pos / error(..., pos) even though pos is [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.

Comment thread cpp/test/test_v1_old.csc Outdated
Comment thread parsergen.csp
Comment thread parsergen_debug.csp

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to add_language(lang, "ascii", gram), but in the CovScript implementation add_language recompiles gram.lex via regex.build_optimize(...) while add_grammar simply stores the grammar as-is. This matters because existing grammars (e.g. tests/run_parser.ecs) commonly store compiled regex objects in gram.lex, which would not be valid input for add_language as currently implemented.
| `add_grammar(lang, gram)` | `add_language(lang, "ascii", gram)` | 保留 |

parsergen.csp:1141

  • generator.add_language assumes gram.lex contains string patterns and unconditionally calls regex.build_optimize(it.second). However, other in-repo grammars store compiled regex objects in gram.lex (see tests/run_parser.ecs), and API.md currently implies add_grammar can be replaced by add_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_language always recompiles gram.lex via regex.build_optimize(...), which will fail if callers pass a grammar whose lex entries 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.

…-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)
- 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)
…+ 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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants