From a60ee97f90c6a67a1ffff62d0ff01feee9cfa41f Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Fri, 24 Jul 2026 14:11:19 +0800 Subject: [PATCH 01/11] feat: add grammar static analysis and error recovery tests --- .github/workflows/ci.yml | 19 +++++++ ecs | 2 +- imports/ecs.csp | 4 +- imports/ecs_bootstrap.csp | 108 +++++++++++++++++++++---------------- imports/ecs_parser.csp | 2 +- misc/check_grammar.csc | 22 ++++++++ misc/visitorgen.csp | 2 +- tests/run_recovery.sh | 34 ++++++++++++ tests/test_multi_error.ecs | 7 +++ 9 files changed, 150 insertions(+), 50 deletions(-) create mode 100644 misc/check_grammar.csc create mode 100644 tests/run_recovery.sh create mode 100644 tests/test_multi_error.ecs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af67862..58030ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,6 +142,16 @@ jobs: echo "=== Installed packages ===" cspkg list + # ------------------------------------------------------------------ # + # 6.5. Grammar static analysis # + # ------------------------------------------------------------------ # + - name: Grammar analysis + shell: bash + run: | + echo "=== Grammar Static Analysis ===" + cspkg install parsergen_analysis --yes + cs -i "$COVSCRIPT_HOME/packages" -i imports misc/check_grammar.csc + # ------------------------------------------------------------------ # # 7. Run unit tests # # ------------------------------------------------------------------ # @@ -187,3 +197,12 @@ jobs: run: | echo "=== Formatter Correctness Tests ===" bash format_tests/run.sh + + # ------------------------------------------------------------------ # + # 10. Error recovery tests # + # ------------------------------------------------------------------ # + - name: Error recovery tests + shell: bash + run: | + echo "=== Error Recovery Tests ===" + bash tests/run_recovery.sh diff --git a/ecs b/ecs index 6bb998a..6052ac4 100644 --- a/ecs +++ b/ecs @@ -14,7 +14,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Copyright (C) 2017-2025 Michael Lee(李登淳) +# Copyright (C) 2017-2026 Michael Lee(李登淳) # # Email: mikecovlee@163.com # Github: https://github.com/mikecovlee diff --git a/imports/ecs.csp b/imports/ecs.csp index dd2d8b4..f80bc65 100644 --- a/imports/ecs.csp +++ b/imports/ecs.csp @@ -2,7 +2,7 @@ # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. -# You may obtain a clone of the License at +# You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# cloneright (C) 2017-2026 Michael Lee(李登淳) +# Copyright (C) 2017-2026 Michael Lee(李登淳) # # Email: mikecovlee@163.com # Github: https://github.com/mikecovlee diff --git a/imports/ecs_bootstrap.csp b/imports/ecs_bootstrap.csp index ba44f4b..1a6b012 100644 --- a/imports/ecs_bootstrap.csp +++ b/imports/ecs_bootstrap.csp @@ -525,61 +525,54 @@ function run_ecs(cmd_args) return 0 end parser.add_grammar("ecs-lang", ecs_parser.grammar) + parser.show_prompt = false parser.from_file(file_name) - if parser.ast != null - if match(lcs_reg, file_name) - if csx_path != null - compiler_args += " -i " + csx_path - end - return no_run ? 0 : system.run(process_path(executor + compiler_args + " " + file_name + arguments)) + if parser.ast == null + var all_errors = new array + if parser.lexer != null + foreach it in parser.lexer.error_log do all_errors.push_back(it) + end + if all_errors.empty() && parser.token_buff != null + var recovery = new parsergen.recovering_parser_type + recovery.init(ecs_parser.grammar.stx) + recovery.parse_with_recovery(parser.token_buff) + foreach it in recovery.get_all_errors() do all_errors.push_back(it) + end + if !all_errors.empty() + all_errors.sort([](lhs, rhs)->lhs.pos[1] < rhs.pos[1]) + parsergen.print_error(file_name, parser.code_buff, all_errors) + else + system.out.println("Error: Failed to parse '" + file_name + "'.") end - var codegen = new ecs_generator.generator + return 1 + end + if match(lcs_reg, file_name) if csx_path != null compiler_args += " -i " + csx_path - codegen.ecsx_path = csx_path.split({system.path.delimiter}) end - codegen.gen_dbg_info = csym - codegen.code_buff = parser.code_buff - codegen.file_name = file_name - codegen.minimal = minimal - if unicode != null - codegen.custom_header.push_back("@charset: " + unicode.tolower()) - end - if output != null - var result = ecs_reg.match(file_name) - if !result.empty() - var name = output + system.path.separator + result.str(1).split({'\\', '/'})[-1] - var target_name = codegen.run(name, parser.ast) - if target_name == null - return 1 - end - if csym - var csym_ofs = iostream.ofstream(name + ".csym") - var dbg_info = "#$cSYM/1.0(" + file_name + "):" - foreach it in codegen.dbg_line_map do dbg_info += it + "," - dbg_info.cut(1) - csym_ofs.println(dbg_info) - foreach it in parser.code_buff do csym_ofs.println(it) - end - if !no_hash - var ofs = iostream.ofstream("./.ecs_output/" + file_hash) - ofs.println(target_name) - ofs.println(system.file.mtime(file_name)) - end - if splash != null - system.out.println(splash) - end - return no_run ? 0 : system.run(process_path(executor + compiler_args + " " + target_name + arguments)) - end - else - system.path.mkdir_p("./.ecs_output/") - var name = "./.ecs_output/" + codec.sha256.hash_str(file_name) + return no_run ? 0 : system.run(process_path(executor + compiler_args + " " + file_name + arguments)) + end + var codegen = new ecs_generator.generator + if csx_path != null + compiler_args += " -i " + csx_path + codegen.ecsx_path = csx_path.split({system.path.delimiter}) + end + codegen.gen_dbg_info = csym + codegen.code_buff = parser.code_buff + codegen.file_name = file_name + codegen.minimal = minimal + if unicode != null + codegen.custom_header.push_back("@charset: " + unicode.tolower()) + end + if output != null + var result = ecs_reg.match(file_name) + if !result.empty() + var name = output + system.path.separator + result.str(1).split({'\\', '/'})[-1] var target_name = codegen.run(name, parser.ast) if target_name == null return 1 end if csym - compiler_args += " -g " + name + ".csym" var csym_ofs = iostream.ofstream(name + ".csym") var dbg_info = "#$cSYM/1.0(" + file_name + "):" foreach it in codegen.dbg_line_map do dbg_info += it + "," @@ -597,6 +590,31 @@ function run_ecs(cmd_args) end return no_run ? 0 : system.run(process_path(executor + compiler_args + " " + target_name + arguments)) end + else + system.path.mkdir_p("./.ecs_output/") + var name = "./.ecs_output/" + codec.sha256.hash_str(file_name) + var target_name = codegen.run(name, parser.ast) + if target_name == null + return 1 + end + if csym + compiler_args += " -g " + name + ".csym" + var csym_ofs = iostream.ofstream(name + ".csym") + var dbg_info = "#$cSYM/1.0(" + file_name + "):" + foreach it in codegen.dbg_line_map do dbg_info += it + "," + dbg_info.cut(1) + csym_ofs.println(dbg_info) + foreach it in parser.code_buff do csym_ofs.println(it) + end + if !no_hash + var ofs = iostream.ofstream("./.ecs_output/" + file_hash) + ofs.println(target_name) + ofs.println(system.file.mtime(file_name)) + end + if splash != null + system.out.println(splash) + end + return no_run ? 0 : system.run(process_path(executor + compiler_args + " " + target_name + arguments)) end return 1 end diff --git a/imports/ecs_parser.csp b/imports/ecs_parser.csp index 9cffb1a..fa718c2 100644 --- a/imports/ecs_parser.csp +++ b/imports/ecs_parser.csp @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Copyright (C) 2017-2025 Michael Lee(李登淳) +# Copyright (C) 2017-2026 Michael Lee(李登淳) # # Email: mikecovlee@163.com # Github: https://github.com/mikecovlee diff --git a/misc/check_grammar.csc b/misc/check_grammar.csc new file mode 100644 index 0000000..4f07b8f --- /dev/null +++ b/misc/check_grammar.csc @@ -0,0 +1,22 @@ +import ecs_parser, parsergen_analysis +var report = parsergen_analysis.analyze(ecs_parser.grammar.stx) +var errors = 0 +var warnings = 0 +foreach it in report + if it.find("[OVERLAP]", 0) == 0 + ++warnings + else + ++errors + end +end +if report.empty() + system.out.println("Grammar analysis: No issues found.") +else + system.out.println("Grammar analysis: " + errors + " error(s), " + warnings + " warning(s):") + foreach it in report + system.out.println(" " + it) + end +end +if errors > 0 + system.exit(1) +end diff --git a/misc/visitorgen.csp b/misc/visitorgen.csp index ee79ad3..0b7c79d 100644 --- a/misc/visitorgen.csp +++ b/misc/visitorgen.csp @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -# Copyright (C) 2017-2025 Michael Lee(李登淳) +# Copyright (C) 2017-2026 Michael Lee(李登淳) # # Email: mikecovlee@163.com # Github: https://github.com/mikecovlee diff --git a/tests/run_recovery.sh b/tests/run_recovery.sh new file mode 100644 index 0000000..48a05d0 --- /dev/null +++ b/tests/run_recovery.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Error recovery tests for ECS compiler +# +# Verifies that the recovering parser reports multiple syntax errors +# in a single pass instead of stopping at the first one. +# +# Usage: +# ./tests/run_recovery.sh + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +PASS=0 +FAIL=0 + +echo "=== Error Recovery Tests ===" +echo "" + +echo -n " multiple errors reported in one pass ... " +output=$(cs -i "$ROOT_DIR/imports" "$ROOT_DIR/ecs" -i "$ROOT_DIR/imports" -c "$SCRIPT_DIR/test_multi_error.ecs" 2>&1) || true + +error_count=$(echo "$output" | grep -c "line [0-9]*:") +if [ "$error_count" -ge 2 ]; then + echo "OK ($error_count errors reported)" + ((PASS++)) +else + echo "FAIL (expected >= 2 errors, got $error_count)" + echo "$output" | sed 's/^/ /' + ((FAIL++)) +fi + +echo "" +echo "=== Recovery results: ${PASS} passed, ${FAIL} failed ===" +[ "$FAIL" -gt 0 ] && exit 1 +exit 0 diff --git a/tests/test_multi_error.ecs b/tests/test_multi_error.ecs new file mode 100644 index 0000000..b7a346f --- /dev/null +++ b/tests/test_multi_error.ecs @@ -0,0 +1,7 @@ +var a = 1 + +var b = (3 +var c = 3 +if a > +var d = 4 +foreach x in +var e = 5 From 3380d21ecd3089a02bbc16e0be14e16707b4f44c Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Thu, 30 Jul 2026 09:51:19 +0800 Subject: [PATCH 02/11] chore: update version numbers across multiple package files --- csbuild/ecs.json | 2 +- csbuild/ecs_bootstrap.json | 2 +- csbuild/ecs_generator.json | 2 +- csbuild/ecs_parser.json | 2 +- ecs | 2 +- imports/ecs.csp | 2 +- imports/ecs_bootstrap.csp | 4 ++-- imports/ecs_generator.csp | 2 +- imports/ecs_parser.csp | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/csbuild/ecs.json b/csbuild/ecs.json index 3e88aad..d2e9cff 100644 --- a/csbuild/ecs.json +++ b/csbuild/ecs.json @@ -3,7 +3,7 @@ "Name": "ecs", "Info": "Extended CovScript(ECS Lang) Header", "Author": "Michael Lee", - "Version": "1.4.2", + "Version": "1.4.3", "Target": "imports/ecs.csp", "Dependencies": [ "sdk_extension" diff --git a/csbuild/ecs_bootstrap.json b/csbuild/ecs_bootstrap.json index 25730d1..9737c3c 100644 --- a/csbuild/ecs_bootstrap.json +++ b/csbuild/ecs_bootstrap.json @@ -3,7 +3,7 @@ "Name": "ecs_bootstrap", "Info": "Extended CovScript(ECS Lang) Bootstrap", "Author": "Michael Lee", - "Version": "1.7.1", + "Version": "1.8.0", "Target": "imports/ecs_bootstrap.csp", "Dependencies": [ "parsergen", diff --git a/csbuild/ecs_generator.json b/csbuild/ecs_generator.json index 14be71d..a16eaa9 100644 --- a/csbuild/ecs_generator.json +++ b/csbuild/ecs_generator.json @@ -3,7 +3,7 @@ "Name": "ecs_generator", "Info": "Extended CovScript(ECS Lang) Generator", "Author": "Michael Lee", - "Version": "4.1.0.3", + "Version": "4.1.0.4", "Target": "imports/ecs_generator.csp", "Dependencies": [ "parsergen", diff --git a/csbuild/ecs_parser.json b/csbuild/ecs_parser.json index 49069f3..55c3125 100644 --- a/csbuild/ecs_parser.json +++ b/csbuild/ecs_parser.json @@ -3,7 +3,7 @@ "Name": "ecs_parser", "Info": "Extended CovScript(ECS Lang) Parser", "Author": "Michael Lee", - "Version": "1.4.0", + "Version": "1.4.1", "Target": "imports/ecs_parser.csp", "Dependencies": [ "parsergen", diff --git a/ecs b/ecs index 6052ac4..14ef71d 100644 --- a/ecs +++ b/ecs @@ -1,6 +1,6 @@ #!/usr/bin/env cs # -# Extended Covariant Script Launcher v1.0.0 +# Extended Covariant Script Launcher v1.0.1 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/imports/ecs.csp b/imports/ecs.csp index f80bc65..05d4052 100644 --- a/imports/ecs.csp +++ b/imports/ecs.csp @@ -1,4 +1,4 @@ -# Extended Covariant Script Header: v1.4.2 +# Extended Covariant Script Header: v1.4.3 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/imports/ecs_bootstrap.csp b/imports/ecs_bootstrap.csp index 1a6b012..f41e5a6 100644 --- a/imports/ecs_bootstrap.csp +++ b/imports/ecs_bootstrap.csp @@ -1,4 +1,4 @@ -# Bootstrap of Extended Covariant Script Generator v1.7.1 +# Bootstrap of Extended Covariant Script Generator v1.8.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ package ecs_bootstrap import parsergen, ecs_parser, ecs_generator, ecs_lint, ecs_format, codec, regex import sdk_extension as sdk -var wrapper_ver = "1.7.1" +var wrapper_ver = "1.8.0" var exit_code = -1 # -1: continue, 0: success, >0: error function show_version_simple() diff --git a/imports/ecs_generator.csp b/imports/ecs_generator.csp index 27d4d95..87bab93 100644 --- a/imports/ecs_generator.csp +++ b/imports/ecs_generator.csp @@ -40,7 +40,7 @@ package ecs_generator # Master namespace ecs_info - constant version = "4.1.0 Cuon alpinus(Stable) Build 3" + constant version = "4.1.0 Cuon alpinus(Stable) Build 4" constant std_version = "260702" constant min_runtime = "260702" end diff --git a/imports/ecs_parser.csp b/imports/ecs_parser.csp index fa718c2..4480407 100644 --- a/imports/ecs_parser.csp +++ b/imports/ecs_parser.csp @@ -1,4 +1,4 @@ -# Covariant Script Parser Generator: Grammar of Extended CovScript(ECS Lang) v1.4.0 +# Covariant Script Parser Generator: Grammar of Extended CovScript(ECS Lang) v1.4.1 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. From b11cb34ad55c451ced98b794ca1d867d4399af97 Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Sun, 9 Aug 2026 18:08:53 +0800 Subject: [PATCH 03/11] Drop-in parsergen/parsergen_cxx support via PARSERGEN_IMPL env var - All .csp files use context.import with PARSERGEN_IMPL selection - Migrate test_parser_modes.ecs to unified API (make_grammar_from, add_language) - CI builds parsergen_cxx from parsergen repo source - Add import regex before context.import to avoid circular re-load - .gitignore: exclude parsergen_cxx.cse build artifact --- .github/workflows/ci.yml | 44 ++++++++- .gitignore | 3 +- imports/ecs_bootstrap.csp | 153 +++++++++++-------------------- imports/ecs_format.csp | 28 +++--- imports/ecs_generator.csp | 18 ++-- imports/ecs_lint.csp | 20 ++-- imports/ecs_parser.csp | 55 ++++++----- test_cxx_migrate.csc | 35 +++++++ unit_tests/test_parser_modes.ecs | 29 +++--- 9 files changed, 223 insertions(+), 162 deletions(-) create mode 100644 test_cxx_migrate.csc diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58030ae..6f2ca8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,10 @@ # ecs is a CovScript-to-CovScript transpiler. This CI: # 1. Downloads a pre-built CovScript SDK from csbuild releases # 2. Configures cspkg to use the bundled package repository -# 3. Installs remote dependencies (parsergen, regex, codec, sdk_extension) -# 4. Builds and installs ecs/ecs_parser/ecs_generator/ecs_bootstrap from local source -# 5. Runs non-interactive unit tests in unit_tests/ +# 3. Builds parsergen_cxx (C++ parser extension) from source +# 4. Installs remote dependencies (parsergen, regex, codec, sdk_extension) +# 5. Builds and installs ecs/ecs_parser/ecs_generator/ecs_bootstrap from local source +# 6. Runs non-interactive unit tests in unit_tests/ name: CI @@ -122,6 +123,43 @@ jobs: CS_HOME="${{ steps.sdk.outputs.cs_home }}" echo "COVSCRIPT_HOME=${CS_HOME}" >> "$GITHUB_ENV" echo "${CS_HOME}/bin" >> "$GITHUB_PATH" + echo "PARSERGEN_IMPL=parsergen_cxx" >> "$GITHUB_ENV" + + # ------------------------------------------------------------------ # + # 5.5. Build parsergen_cxx from source # + # ------------------------------------------------------------------ # + - name: Checkout parsergen + uses: actions/checkout@v5 + with: + repository: mikecovlee/parsergen + ref: cxx_impl + path: parsergen-src + + - name: Build parsergen_cxx (Unix) + if: runner.os != 'Windows' + shell: bash + run: | + mkdir -p parsergen-src/cpp/build && cd parsergen-src/cpp/build + cmake .. -DCMAKE_BUILD_TYPE=Release + cmake --build . --target parsergen_cni -- -j$(nproc) + cp parsergen_cxx.cse "${{ github.workspace }}/imports/" + + - name: Build parsergen_cxx (Windows) + if: runner.os == 'Windows' + uses: msys2/setup-msys2@v2 + with: + msystem: UCRT64 + install: mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake make + path-type: inherit + + - name: Build parsergen_cxx (Windows build) + if: runner.os == 'Windows' + shell: msys2 {0} + run: | + mkdir -p parsergen-src/cpp/build && cd parsergen-src/cpp/build + cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release + cmake --build . --target parsergen_cni -- -j4 + cp parsergen_cxx.cse "${{ github.workspace }}/imports/" # ------------------------------------------------------------------ # # 6. Install remote dependencies + build local packages # diff --git a/.gitignore b/.gitignore index dcc638d..154d8ba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store .ecs_output/ -cspkg-repo/ \ No newline at end of file +cspkg-repo/ +imports/parsergen_cxx.cse \ No newline at end of file diff --git a/imports/ecs_bootstrap.csp b/imports/ecs_bootstrap.csp index f41e5a6..b4f3225 100644 --- a/imports/ecs_bootstrap.csp +++ b/imports/ecs_bootstrap.csp @@ -20,7 +20,15 @@ package ecs_bootstrap -import parsergen, ecs_parser, ecs_generator, ecs_lint, ecs_format, codec, regex +import regex + +var _pg_impl = "parsergen" +try + _pg_impl = system.getenv("PARSERGEN_IMPL") +catch e +end +var parsergen = context.import(runtime.get_import_path(), _pg_impl) +import ecs_parser, ecs_generator, ecs_lint, ecs_format, codec, regex import sdk_extension as sdk var wrapper_ver = "1.8.0" @@ -38,21 +46,22 @@ end class repl_instance var codegen = new ecs_generator.generator + var gen = new parsergen.generator var parser = new parsergen.partial_parser_type - var unicode_cvt = null + var coding = "ascii" var code_buff = new array var repl_impl = null var silent = false - function on_eof_hook(parser) + function on_eof_hook(p) var tokens = null loop tokens = this.readline(".. ") until tokens != null - foreach it in tokens do parser.lex.push_back(it) - return true + p.push_tokens(tokens) end function initialize() - parser.on_eof_hook = on_eof_hook + gen.set_show_prompt(false) + parser.set_eof_hook(on_eof_hook) codegen.code_buff := code_buff codegen.file_name = "" codegen.minimal = true @@ -71,23 +80,17 @@ class repl_instance return new array end line += "\n" - var lexer = null - if unicode_cvt != null - lexer = new parsergen.unicode_lexer_type - lexer.cvt = unicode_cvt - else - lexer = new parsergen.lexer_type - end - lexer.pos[1] = code_buff.size - 1 - var tokens = lexer.run(ecs_parser.grammar.lex, line) - if !lexer.error_log.empty() - parsergen.print_error("", code_buff, lexer.error_log) + var tokens = gen.lex_string("ecs-lang", line, code_buff.size - 1) + var lex_errors = gen.get_lex_errors() + if !lex_errors.empty() + parsergen.print_error("", code_buff, lex_errors) return new array else return tokens end end function run(...args) + gen.add_language("ecs-lang", coding, ecs_parser.grammar) if !silent system.out.println("Extended Covariant Script Interpreter REPL") show_version_simple() @@ -102,7 +105,7 @@ class repl_instance loop tokens = this.readline(">> ") until tokens != null - if parser.run(ecs_parser.grammar.stx, tokens) + if parser.run(ecs_parser.get_syntax(false), tokens) var ast = parser.production() if ast != null var code = codegen.repl_run(ast) @@ -309,34 +312,6 @@ function process_args(cmd_args) end end -@begin -var codecvt_map = { - "UTF8": ([](unicode)->new unicode.codecvt.utf8), - "GBK": ([](unicode)->new unicode.codecvt.gbk) -}.to_hash_map() -@end - -struct wrapper_result - var result = true - function empty() - return result - end -end - -struct gbk_wrapper - var codecvt = null - function match(str) - var r = new wrapper_result - foreach i in range(str.size) - if !codecvt.is_identifier(str.at(i)) - return r - end - end - # empty() == false -> match succeed - r.result = false - return r - end -end var ecs_reg = regex.build_optimize("^(.*)\\.ecs$") var lcs_reg = regex.build_optimize("^(.*)\\.(csc|csp)$") @@ -361,31 +336,23 @@ function process_path(str) end end -function setup_unicode(charset) - var unicode_ext = context.import(runtime.get_import_path(), "unicode") - if unicode_ext == null - system.out.println("Error: unicode extension not installed yet.") - system.out.println("Run 'cspkg install extension --yes' to enable unicode support.") - exit_code = 2 - return null +function resolve_coding(charset) + if charset == null + return "ascii" end var cvt_name = charset.toupper() if cvt_name == "AUTO" cvt_name = is_windows ? "GBK" : "UTF8" end - if !codecvt_map.exist(cvt_name) - system.out.println("Error: unknown unicode charset \"" + cvt_name + "\".") - exit_code = 1 - return null + if cvt_name == "UTF8" + return "utf8" end - var cvt = codecvt_map.at(cvt_name)(unicode_ext) - ecs_parser.grammar.lex = ecs_parser.get_lexical([](str)->unicode_ext.build_optimize_wregex(cvt.local2wide(str)), false) if cvt_name == "GBK" - var wrapper = new gbk_wrapper - wrapper.codecvt = cvt - ecs_parser.grammar.lex["id"] = wrapper + return "gbk" end - return cvt + system.out.println("Error: unknown unicode charset \"" + charset + "\".") + exit_code = 1 + return null end function run_ecs(cmd_args) @@ -444,11 +411,9 @@ function run_ecs(cmd_args) if repl var instance = new repl_instance instance.silent = silent - if unicode != null - instance.unicode_cvt = setup_unicode(unicode) - if exit_code != -1 - return exit_code - end + instance.coding = resolve_coding(unicode) + if exit_code != -1 + return exit_code end if csx_path != null sdk.set_import_path(runtime.get_import_path() + system.path.delimiter + csx_path) @@ -498,20 +463,13 @@ function run_ecs(cmd_args) end return 0 end - var parser = new parsergen.generator - if unicode != null - parser.unicode_cvt = setup_unicode(unicode) - if exit_code != -1 - return exit_code - end + var coding = resolve_coding(unicode) + if exit_code != -1 + return exit_code end # Format mode (uses format-specific grammar with com tokens) if format_only - var cvt = null - if unicode != null - cvt = parser.unicode_cvt - end - var formatted = ecs_format.format_file(file_name, cvt) + var formatted = ecs_format.format_file(file_name, coding) if formatted == null return 1 end @@ -524,23 +482,24 @@ function run_ecs(cmd_args) end return 0 end - parser.add_grammar("ecs-lang", ecs_parser.grammar) - parser.show_prompt = false + var parser = new parsergen.generator + parser.add_language("ecs-lang", coding, ecs_parser.grammar) + parser.set_show_prompt(false) parser.from_file(file_name) - if parser.ast == null - var all_errors = new array - if parser.lexer != null - foreach it in parser.lexer.error_log do all_errors.push_back(it) - end - if all_errors.empty() && parser.token_buff != null - var recovery = new parsergen.recovering_parser_type - recovery.init(ecs_parser.grammar.stx) - recovery.parse_with_recovery(parser.token_buff) - foreach it in recovery.get_all_errors() do all_errors.push_back(it) + if parser.ast() == null + var all_errors = parser.get_lex_errors() + if all_errors.empty() + var tokens = parser.get_tokens() + if !tokens.empty() + var recovery = new parsergen.recovering_parser_type + recovery.init(ecs_parser.get_syntax(false)) + recovery.parse_with_recovery(tokens) + foreach it in recovery.get_all_errors() do all_errors.push_back(it) + end end if !all_errors.empty() all_errors.sort([](lhs, rhs)->lhs.pos[1] < rhs.pos[1]) - parsergen.print_error(file_name, parser.code_buff, all_errors) + parsergen.print_error(file_name, parser.code_buff(), all_errors) else system.out.println("Error: Failed to parse '" + file_name + "'.") end @@ -558,7 +517,7 @@ function run_ecs(cmd_args) codegen.ecsx_path = csx_path.split({system.path.delimiter}) end codegen.gen_dbg_info = csym - codegen.code_buff = parser.code_buff + codegen.code_buff = parser.code_buff() codegen.file_name = file_name codegen.minimal = minimal if unicode != null @@ -568,7 +527,7 @@ function run_ecs(cmd_args) var result = ecs_reg.match(file_name) if !result.empty() var name = output + system.path.separator + result.str(1).split({'\\', '/'})[-1] - var target_name = codegen.run(name, parser.ast) + var target_name = codegen.run(name, parser.ast()) if target_name == null return 1 end @@ -578,7 +537,7 @@ function run_ecs(cmd_args) foreach it in codegen.dbg_line_map do dbg_info += it + "," dbg_info.cut(1) csym_ofs.println(dbg_info) - foreach it in parser.code_buff do csym_ofs.println(it) + foreach it in parser.code_buff() do csym_ofs.println(it) end if !no_hash var ofs = iostream.ofstream("./.ecs_output/" + file_hash) @@ -593,7 +552,7 @@ function run_ecs(cmd_args) else system.path.mkdir_p("./.ecs_output/") var name = "./.ecs_output/" + codec.sha256.hash_str(file_name) - var target_name = codegen.run(name, parser.ast) + var target_name = codegen.run(name, parser.ast()) if target_name == null return 1 end @@ -604,7 +563,7 @@ function run_ecs(cmd_args) foreach it in codegen.dbg_line_map do dbg_info += it + "," dbg_info.cut(1) csym_ofs.println(dbg_info) - foreach it in parser.code_buff do csym_ofs.println(it) + foreach it in parser.code_buff() do csym_ofs.println(it) end if !no_hash var ofs = iostream.ofstream("./.ecs_output/" + file_hash) diff --git a/imports/ecs_format.csp b/imports/ecs_format.csp index b0dbf9b..d8d9ed0 100644 --- a/imports/ecs_format.csp +++ b/imports/ecs_format.csp @@ -20,14 +20,18 @@ package ecs_format -import parsergen, ecs_parser, regex +import regex + +var _pg_impl = "parsergen" +try + _pg_impl = system.getenv("PARSERGEN_IMPL") +catch e +end +var parsergen = context.import(runtime.get_import_path(), _pg_impl) +import ecs_parser function get_fmt_grammar() - var g = new parsergen.grammar - g.ext = ".*\\.(csp|csc|ecs|ecsx)" - g.lex = ecs_parser.get_lexical(regex.build_optimize, true) - g.stx := ecs_parser.get_syntax(true) - return g + return parsergen.make_grammar_from(".*\\.(csp|csc|ecs|ecsx)", ecs_parser.get_lexical_patterns(true), ecs_parser.get_syntax(true)) end # ============================================================ @@ -1406,19 +1410,17 @@ function format_ast(file_name, code_buff, ast) return vis.run(file_name, code_buff, ast) end -function format_file(file_name, unicode_cvt) +function format_file(file_name, coding) # Use format-specific grammar so comments appear as "com" tokens in AST var parser = new parsergen.generator - if unicode_cvt != null - parser.unicode_cvt = unicode_cvt - end - parser.add_grammar("ecs-lang-fmt", get_fmt_grammar()) + parser.set_show_prompt(false) + parser.add_language("ecs-lang-fmt", coding, get_fmt_grammar()) parser.from_file(file_name) - if parser.ast == null + if parser.ast() == null system.out.println("Error: Failed to parse file '" + file_name + "'.") return null end - return format_ast(file_name, parser.code_buff, parser.ast) + return format_ast(file_name, parser.code_buff(), parser.ast()) end var version = "1.0.0" diff --git a/imports/ecs_generator.csp b/imports/ecs_generator.csp index 87bab93..deb51e3 100644 --- a/imports/ecs_generator.csp +++ b/imports/ecs_generator.csp @@ -45,7 +45,15 @@ namespace ecs_info constant min_runtime = "260702" end -import parsergen, ecs +import regex + +var _pg_impl = "parsergen" +try + _pg_impl = system.getenv("PARSERGEN_IMPL") +catch e +end +var parsergen = context.import(runtime.get_import_path(), _pg_impl) +import ecs struct module_type var list = new array @@ -106,16 +114,12 @@ class generator end end function error(pos, text) - var err = new parsergen.lex_error - err.text = text - err.pos = pos + var err = parsergen.make_lex_error(text, pos) parsergen.print_error(file_name, code_buff, {err}) throw runtime.exception("ECS_ERROR") end function warning(pos, text) - var err = new parsergen.lex_error - err.text = "warning: " + text - err.pos = pos + var err = parsergen.make_lex_error("warning: " + text, pos) parsergen.print_error(file_name, code_buff, {err}) end function print_indent() diff --git a/imports/ecs_lint.csp b/imports/ecs_lint.csp index 3f6283b..11edeb0 100644 --- a/imports/ecs_lint.csp +++ b/imports/ecs_lint.csp @@ -20,7 +20,15 @@ package ecs_lint -import parsergen, ecs_parser +import regex + +var _pg_impl = "parsergen" +try + _pg_impl = system.getenv("PARSERGEN_IMPL") +catch e +end +var parsergen = context.import(runtime.get_import_path(), _pg_impl) +import ecs_parser # ============================================================ # Severity Levels @@ -456,9 +464,7 @@ class linter end var errors = new array foreach iss in this.ctx.issues - var err = new parsergen.lex_error - err.text = iss.severity + ": [" + iss.rule_name + "] " + iss.message - err.pos = iss.pos + var err = parsergen.make_lex_error(iss.severity + ": [" + iss.rule_name + "] " + iss.message, iss.pos) errors.push_back(err) end parsergen.print_error(this.ctx.file_name, this.ctx.code_buff, errors) @@ -792,14 +798,14 @@ end function lint_file(file_name) var parser = new parsergen.generator - parser.add_grammar("ecs-lang", ecs_parser.grammar) + parser.add_language("ecs-lang", "ascii", ecs_parser.grammar) parser.from_file(file_name) - if parser.ast == null + if parser.ast() == null system.out.println("Error: Failed to parse file '" + file_name + "'.") return null end var lint = new linter - var issues = lint.run(file_name, parser.code_buff, parser.ast) + var issues = lint.run(file_name, parser.code_buff(), parser.ast()) return {lint, issues} end diff --git a/imports/ecs_parser.csp b/imports/ecs_parser.csp index 4480407..9058fd2 100644 --- a/imports/ecs_parser.csp +++ b/imports/ecs_parser.csp @@ -20,31 +20,47 @@ package ecs_parser -import parsergen, regex +import regex -constant syntax = parsergen.syntax +var _pg_impl = "parsergen" +try + _pg_impl = system.getenv("PARSERGEN_IMPL") +catch e +end +var parsergen = context.import(runtime.get_import_path(), _pg_impl) -function get_lexical(reg_builder, strict) +var syntax = parsergen.syntax + +function get_lexical_patterns(strict) @begin var lex = { - "endl" : reg_builder("^\\n+$"), - "id" : reg_builder("^[A-Za-z_\\p{Han}](\\w|\\p{Han})*$"), - "num" : reg_builder("^[0-9]+\\.?([0-9]+)?$"), - "str" : reg_builder("^(\"|\"([^\"]|\\\\\")*\"?)$"), - "char" : reg_builder("^(\'|\'([^\']|\\\\(0|\\\\|\'|\"|\\w))\'?)$"), - "bsig" : reg_builder("^(;|:=?|::|\\?|\\.\\.?|\\.\\.\\.)$"), - "msig" : reg_builder("^(\\+(\\+|=)?|-(-|=|>)?|\\*=?|/=?|%=?|\\^=?)$"), - "lsig" : reg_builder("^(>|<|&|(\\|)|&&|(\\|\\|)|!|=(=|>)?|!=?|>=?|<=?)$"), - "brac" : reg_builder("^(\\(|\\)|\\[|\\]|\\{|\\}|,)$"), - "prep" : reg_builder("^@.*$"), - "err" : reg_builder("^(\"|\'|(\\|)|\\.\\.)$") + "endl" : "^\\n+$", + "id" : "^[A-Za-z_\\p{Han}](\\w|\\p{Han})*$", + "num" : "^[0-9]+\\.?([0-9]+)?$", + "str" : "^(\"|\"([^\"]|\\\\\")*\"?)$", + "char" : "^(\'|\'([^\']|\\\\(0|\\\\|\'|\"|\\w))\'?)$", + "bsig" : "^(;|:=?|::|\\?|\\.\\.?|\\.\\.\\.)$", + "msig" : "^(\\+(\\+|=)?|-(-|=|>)?|\\*=?|/=?|%=?|\\^=?)$", + "lsig" : "^(>|<|&|(\\|)|&&|(\\|\\|)|!|=(=|>)?|!=?|>=?|<=?)$", + "brac" : "^(\\(|\\)|\\[|\\]|\\{|\\}|,)$", + "prep" : "^@.*$", + "err" : "^(\"|\'|(\\|)|\\.\\.)$" }.to_hash_map() @end if strict - lex["com"] = reg_builder("^#.*$") - lex["ign"] = reg_builder("^[ \\f\\r\\t]+$") + lex["com"] = "^#.*$" + lex["ign"] = "^[ \\f\\r\\t]+$" else - lex["ign"] = reg_builder("^([ \\f\\r\\t]+|#.*)$") + lex["ign"] = "^([ \\f\\r\\t]+|#.*)$" + end + return lex +end + +function get_lexical(reg_builder, strict) + var patterns = get_lexical_patterns(strict) + var lex = new hash_map + foreach it in patterns + lex[it.first] = reg_builder(it.second) end return lex end @@ -407,7 +423,4 @@ function get_syntax(strict) return stx end -var grammar = new parsergen.grammar -grammar.ext = ".*\\.(csp|csc|ecs|ecsx)" -grammar.lex = get_lexical(regex.build_optimize, false) -grammar.stx := get_syntax(false) +var grammar = parsergen.make_grammar_from(".*\\.(csp|csc|ecs|ecsx)", get_lexical_patterns(false), get_syntax(false)) diff --git a/test_cxx_migrate.csc b/test_cxx_migrate.csc new file mode 100644 index 0000000..8b6ee1f --- /dev/null +++ b/test_cxx_migrate.csc @@ -0,0 +1,35 @@ +var _pg_impl = "parsergen" +try + _pg_impl = system.getenv("PARSERGEN_IMPL") +catch e +end +var parsergen = context.import(runtime.get_import_path(), _pg_impl) +import ecs_parser, ecs_generator + +var lex = ecs_parser.get_lexical_patterns(false) +var stx = ecs_parser.get_syntax(false) +var cxx_gram = parsergen.make_grammar_from(".*\\.(csp|csc|ecs|ecsx)", lex, stx) + +var gen = new parsergen.generator +gen.set_show_prompt(false) +gen.add_language("ecs-lang", "ascii", cxx_gram) + +var file = context.cmd_args.at(1) +if gen.from_file(file) + system.out.println("PARSE OK") + var codegen = new ecs_generator.generator + codegen.code_buff = gen.get_code_buff() + codegen.file_name = file + codegen.minimal = true + var result = codegen.run("./.ecs_output/test_migrated", gen.get_ast()) + if result != null + system.out.println("CODEGEN OK: " + result) + else + system.out.println("CODEGEN FAIL") + end +else + system.out.println("PARSE FAIL") + foreach it in gen.get_errors() + system.out.println(" " + it.text()) + end +end diff --git a/unit_tests/test_parser_modes.ecs b/unit_tests/test_parser_modes.ecs index 714501c..cd65301 100644 --- a/unit_tests/test_parser_modes.ecs +++ b/unit_tests/test_parser_modes.ecs @@ -1,6 +1,13 @@ # Unit tests: Parser strict/non-strict mode coverage import ecs -import ecs_parser, parsergen, regex +import ecs_parser, regex + +var _pg_impl = "parsergen" +try + _pg_impl = system.getenv("PARSERGEN_IMPL") +catch e +end +var parsergen = context.import(runtime.get_import_path(), _pg_impl) var passed = 0 var failed = 0 @@ -59,26 +66,22 @@ assert(key_exists(stx_s, "begin"), "strict stx has 'begin'") assert(key_exists(stx_s, "stmts"), "strict stx has 'stmts'") # === Parse with non-strict grammar (default) === -var grammar_ns = new parsergen.grammar -grammar_ns.ext = ".*\\.(csp|csc|ecs|ecsx)" -grammar_ns.lex = ecs_parser.get_lexical(regex.build_optimize, false) -grammar_ns.stx := ecs_parser.get_syntax(false) +var grammar_ns = parsergen.make_grammar_from(".*\\.(csp|csc|ecs|ecsx)", ecs_parser.get_lexical_patterns(false), ecs_parser.get_syntax(false)) var parser_ns = new parsergen.generator -parser_ns.add_grammar("ecs-lang-ns", grammar_ns) +parser_ns.set_show_prompt(false) +parser_ns.add_language("ecs-lang-ns", "ascii", grammar_ns) parser_ns.from_file("unit_tests/test_functions.ecs") -assert(parser_ns.ast != null, "non-strict mode parses test_functions.ecs") +assert(parser_ns.get_ast() != null, "non-strict mode parses test_functions.ecs") # === Parse with strict grammar === -var grammar_s = new parsergen.grammar -grammar_s.ext = ".*\\.(csp|csc|ecs|ecsx)" -grammar_s.lex = ecs_parser.get_lexical(regex.build_optimize, true) -grammar_s.stx := ecs_parser.get_syntax(true) +var grammar_s = parsergen.make_grammar_from(".*\\.(csp|csc|ecs|ecsx)", ecs_parser.get_lexical_patterns(true), ecs_parser.get_syntax(true)) var parser_s = new parsergen.generator -parser_s.add_grammar("ecs-lang-s", grammar_s) +parser_s.set_show_prompt(false) +parser_s.add_language("ecs-lang-s", "ascii", grammar_s) parser_s.from_file("unit_tests/test_functions.ecs") -assert(parser_s.ast != null, "strict mode parses test_functions.ecs") +assert(parser_s.get_ast() != null, "strict mode parses test_functions.ecs") # === results === system.out.println("") From 330ad6d6fd5f3a341721f81f5b6df6c546b55d2d Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Sun, 9 Aug 2026 18:15:55 +0800 Subject: [PATCH 04/11] Fix unified API usage: .ast() -> .get_ast(), .code_buff() -> .get_code_buff(), remove duplicate import regex --- imports/ecs_bootstrap.csp | 18 +++++++++--------- imports/ecs_format.csp | 4 ++-- imports/ecs_lint.csp | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/imports/ecs_bootstrap.csp b/imports/ecs_bootstrap.csp index b4f3225..97224fc 100644 --- a/imports/ecs_bootstrap.csp +++ b/imports/ecs_bootstrap.csp @@ -28,7 +28,7 @@ try catch e end var parsergen = context.import(runtime.get_import_path(), _pg_impl) -import ecs_parser, ecs_generator, ecs_lint, ecs_format, codec, regex +import ecs_parser, ecs_generator, ecs_lint, ecs_format, codec import sdk_extension as sdk var wrapper_ver = "1.8.0" @@ -486,7 +486,7 @@ function run_ecs(cmd_args) parser.add_language("ecs-lang", coding, ecs_parser.grammar) parser.set_show_prompt(false) parser.from_file(file_name) - if parser.ast() == null + if parser.get_ast() == null var all_errors = parser.get_lex_errors() if all_errors.empty() var tokens = parser.get_tokens() @@ -499,7 +499,7 @@ function run_ecs(cmd_args) end if !all_errors.empty() all_errors.sort([](lhs, rhs)->lhs.pos[1] < rhs.pos[1]) - parsergen.print_error(file_name, parser.code_buff(), all_errors) + parsergen.print_error(file_name, parser.get_code_buff(), all_errors) else system.out.println("Error: Failed to parse '" + file_name + "'.") end @@ -517,7 +517,7 @@ function run_ecs(cmd_args) codegen.ecsx_path = csx_path.split({system.path.delimiter}) end codegen.gen_dbg_info = csym - codegen.code_buff = parser.code_buff() + codegen.code_buff = parser.get_code_buff() codegen.file_name = file_name codegen.minimal = minimal if unicode != null @@ -527,7 +527,7 @@ function run_ecs(cmd_args) var result = ecs_reg.match(file_name) if !result.empty() var name = output + system.path.separator + result.str(1).split({'\\', '/'})[-1] - var target_name = codegen.run(name, parser.ast()) + var target_name = codegen.run(name, parser.get_ast()) if target_name == null return 1 end @@ -537,7 +537,7 @@ function run_ecs(cmd_args) foreach it in codegen.dbg_line_map do dbg_info += it + "," dbg_info.cut(1) csym_ofs.println(dbg_info) - foreach it in parser.code_buff() do csym_ofs.println(it) + foreach it in parser.get_code_buff() do csym_ofs.println(it) end if !no_hash var ofs = iostream.ofstream("./.ecs_output/" + file_hash) @@ -552,7 +552,7 @@ function run_ecs(cmd_args) else system.path.mkdir_p("./.ecs_output/") var name = "./.ecs_output/" + codec.sha256.hash_str(file_name) - var target_name = codegen.run(name, parser.ast()) + var target_name = codegen.run(name, parser.get_ast()) if target_name == null return 1 end @@ -563,7 +563,7 @@ function run_ecs(cmd_args) foreach it in codegen.dbg_line_map do dbg_info += it + "," dbg_info.cut(1) csym_ofs.println(dbg_info) - foreach it in parser.code_buff() do csym_ofs.println(it) + foreach it in parser.get_code_buff() do csym_ofs.println(it) end if !no_hash var ofs = iostream.ofstream("./.ecs_output/" + file_hash) @@ -580,4 +580,4 @@ end function main(cmd_args) system.exit(run_ecs(cmd_args)) -end \ No newline at end of file +end diff --git a/imports/ecs_format.csp b/imports/ecs_format.csp index d8d9ed0..37a684c 100644 --- a/imports/ecs_format.csp +++ b/imports/ecs_format.csp @@ -1416,11 +1416,11 @@ function format_file(file_name, coding) parser.set_show_prompt(false) parser.add_language("ecs-lang-fmt", coding, get_fmt_grammar()) parser.from_file(file_name) - if parser.ast() == null + if parser.get_ast() == null system.out.println("Error: Failed to parse file '" + file_name + "'.") return null end - return format_ast(file_name, parser.code_buff(), parser.ast()) + return format_ast(file_name, parser.get_code_buff(), parser.get_ast()) end var version = "1.0.0" diff --git a/imports/ecs_lint.csp b/imports/ecs_lint.csp index 11edeb0..8ce8e72 100644 --- a/imports/ecs_lint.csp +++ b/imports/ecs_lint.csp @@ -800,12 +800,12 @@ function lint_file(file_name) var parser = new parsergen.generator parser.add_language("ecs-lang", "ascii", ecs_parser.grammar) parser.from_file(file_name) - if parser.ast() == null + if parser.get_ast() == null system.out.println("Error: Failed to parse file '" + file_name + "'.") return null end var lint = new linter - var issues = lint.run(file_name, parser.code_buff(), parser.ast()) + var issues = lint.run(file_name, parser.get_code_buff(), parser.get_ast()) return {lint, issues} end From 6198753a7f6be94e6554b1dc02d82e3dbf4fe1a1 Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Sun, 9 Aug 2026 18:20:28 +0800 Subject: [PATCH 05/11] CI: install parsergen_cxx via cspkg build (proper extension installation) --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f2ca8e..240bbab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -142,7 +142,8 @@ jobs: mkdir -p parsergen-src/cpp/build && cd parsergen-src/cpp/build cmake .. -DCMAKE_BUILD_TYPE=Release cmake --build . --target parsergen_cni -- -j$(nproc) - cp parsergen_cxx.cse "${{ github.workspace }}/imports/" + mkdir -p ../../build/imports + cp parsergen_cxx.cse ../../build/imports/ - name: Build parsergen_cxx (Windows) if: runner.os == 'Windows' @@ -159,7 +160,8 @@ jobs: mkdir -p parsergen-src/cpp/build && cd parsergen-src/cpp/build cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release cmake --build . --target parsergen_cni -- -j4 - cp parsergen_cxx.cse "${{ github.workspace }}/imports/" + mkdir -p ../../build/imports + cp parsergen_cxx.cse ../../build/imports/ # ------------------------------------------------------------------ # # 6. Install remote dependencies + build local packages # @@ -172,6 +174,8 @@ jobs: fi # Initialize cspkg cspkg install --import --yes + # Install parsergen_cxx extension (built from source in step 5.5) + cspkg build parsergen-src/csbuild/parsergen_cxx.json --install --yes # Build and install local packages from csbuild/ cspkg build . --install --yes # Fix dependencies From cca9d997ab79ee8e28319307c3de0abc96fe1fef Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Mon, 10 Aug 2026 10:06:02 +0800 Subject: [PATCH 06/11] CI: checkout parsergen with submodules (covscript-regex/pcre2 needed for cmake) --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 240bbab..9406ad3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,6 +134,7 @@ jobs: repository: mikecovlee/parsergen ref: cxx_impl path: parsergen-src + submodules: recursive - name: Build parsergen_cxx (Unix) if: runner.os != 'Windows' From acd65746b179d1748a37a67a3a5f86b4d0631806 Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Mon, 10 Aug 2026 10:11:16 +0800 Subject: [PATCH 07/11] CI: set CS_DEV_PATH when building parsergen_cxx (parsergen_cni target requires CovScript SDK) --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9406ad3..261206e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -139,6 +139,8 @@ jobs: - name: Build parsergen_cxx (Unix) if: runner.os != 'Windows' shell: bash + env: + CS_DEV_PATH: ${{ steps.sdk.outputs.cs_home }}/ run: | mkdir -p parsergen-src/cpp/build && cd parsergen-src/cpp/build cmake .. -DCMAKE_BUILD_TYPE=Release @@ -158,6 +160,7 @@ jobs: if: runner.os == 'Windows' shell: msys2 {0} run: | + export CS_DEV_PATH="$(cygpath -u "${{ steps.sdk.outputs.cs_home }}")/" mkdir -p parsergen-src/cpp/build && cd parsergen-src/cpp/build cmake .. -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Release cmake --build . --target parsergen_cni -- -j4 From e964791b46296802f972da0cd10839d21f4f760c Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Mon, 10 Aug 2026 10:19:59 +0800 Subject: [PATCH 08/11] CI: fix cspkg install syntax for parsergen_cxx (build --install , verified locally) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 261206e..cbe8042 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -179,7 +179,7 @@ jobs: # Initialize cspkg cspkg install --import --yes # Install parsergen_cxx extension (built from source in step 5.5) - cspkg build parsergen-src/csbuild/parsergen_cxx.json --install --yes + cspkg build parsergen-src --install parsergen_cxx --yes # Build and install local packages from csbuild/ cspkg build . --install --yes # Fix dependencies From b6aa8672e2fbe78cac3af3d6640a2ad34c58ce30 Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Mon, 10 Aug 2026 10:22:39 +0800 Subject: [PATCH 09/11] CI: pin Unix Makefiles generator for parsergen build (avoid Xcode default on macOS) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbe8042..f8050b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,8 +143,8 @@ jobs: CS_DEV_PATH: ${{ steps.sdk.outputs.cs_home }}/ run: | mkdir -p parsergen-src/cpp/build && cd parsergen-src/cpp/build - cmake .. -DCMAKE_BUILD_TYPE=Release - cmake --build . --target parsergen_cni -- -j$(nproc) + cmake -G "Unix Makefiles" .. -DCMAKE_BUILD_TYPE=Release + cmake --build . --target parsergen_cni --parallel 4 mkdir -p ../../build/imports cp parsergen_cxx.cse ../../build/imports/ From ca34348dc0910f4050d252ace329095acbd5a2a8 Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Mon, 10 Aug 2026 11:01:09 +0800 Subject: [PATCH 10/11] CI: fix grammar analysis cs invocation - drop multiple -i, use cspkg default import path (covscript-network pattern) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8050b0..3b2884f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -196,7 +196,7 @@ jobs: run: | echo "=== Grammar Static Analysis ===" cspkg install parsergen_analysis --yes - cs -i "$COVSCRIPT_HOME/packages" -i imports misc/check_grammar.csc + cs misc/check_grammar.csc # ------------------------------------------------------------------ # # 7. Run unit tests # From fb7e95c000e185a093240ebd1ca38cbd6a784094 Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Mon, 10 Aug 2026 13:57:47 +0800 Subject: [PATCH 11/11] CI: install parsergen_cxx after install --fix to avoid revert, use parsergen for grammar analysis --- .github/workflows/ci.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b2884f..9647dbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -178,21 +178,28 @@ jobs: fi # Initialize cspkg cspkg install --import --yes - # Install parsergen_cxx extension (built from source in step 5.5) - cspkg build parsergen-src --install parsergen_cxx --yes # Build and install local packages from csbuild/ cspkg build . --install --yes # Fix dependencies cspkg install --fix --yes + # Install parsergen_cxx extension + updated parsergen (built from source + # in step 5.5). Done after 'install --fix' so the newer parsergen is + # not reverted to the older cspkg-repo version. + cspkg build parsergen-src --install parsergen_cxx parsergen --yes echo "" echo "=== Installed packages ===" cspkg list # ------------------------------------------------------------------ # # 6.5. Grammar static analysis # + # Uses the CovScript parsergen (PARSERGEN_IMPL=parsergen): the # + # analyzer is a CovScript package designed for the script-built # + # grammar structure. # # ------------------------------------------------------------------ # - name: Grammar analysis shell: bash + env: + PARSERGEN_IMPL: parsergen run: | echo "=== Grammar Static Analysis ===" cspkg install parsergen_analysis --yes