From cfa027d196712d16da5d0393aa858c8ad84f3816 Mon Sep 17 00:00:00 2001 From: David de Hilster Date: Tue, 27 Oct 2026 20:27:10 -0400 Subject: [PATCH 1/2] -ZONES 40-45 runs only the zones it names (4.2.50) Zone mode had no way to run part of a document: chasing a problem in zone 4,102 of War and Peace meant an 18-minute run, and -DEV wrote trees only for the first three zones. Now nlp -ANA parse-en-us -ZONES 4100-4104 -DEV wp.txt runs only those zones, in 27 s without -DEV (117 s with it), and -DEV writes every pass tree for each zone that runs. A spec is a zone number, a range or a list: 7, 40-45, 3,40-45; numbers count from 1, as in the z040-ana###.tree names. - Zones outside the range get only the passes before the zone line; the passes after endzone still run. $isfirstzone and $islastzone mark the first and last zone that runs. - The KB and G() start without what skipped zones would have added, so a zone that depends on earlier ones can differ from a full run. - stderr says how many zones -ZONES ran; a bad spec ("3-1") stops the run with exit 1. - NLP_ENGINE::setZoneRange() for the Python and Node bindings; the spec parser is in lite/zonerange.h, shared by main, NLP_ENGINE and Parse. - Zone dump names no longer wrap past 999 (z1234-, was z234-). - Interpreted and compiled analyzers share Parse::zoneLoop, so both honor it. - CI: the zone-mode fixture with -ZONES 2-3, 1,3 and a bad range, on Linux, macOS and Windows. README: -ZONES, and memory goes by the skim tree too, not only the largest zone. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build-linux.yml | 29 +++++++++++++ .github/workflows/build-macos.yml | 28 +++++++++++++ .github/workflows/build-windows.yml | 28 +++++++++++++ README.md | 11 ++++- include/Api/lite/nlp.h | 8 ++++ include/Api/lite/nlp_engine.h | 8 ++++ include/Api/lite/parse.h | 9 ++++ include/Api/lite/zonerange.h | 58 +++++++++++++++++++++++++ lite/nlp.cpp | 2 + lite/nlp_engine.cpp | 21 +++++++++- lite/parse.cpp | 65 ++++++++++++++++++++++++++--- nlp/main.cpp | 35 +++++++++++++--- 12 files changed, 289 insertions(+), 13 deletions(-) create mode 100644 include/Api/lite/zonerange.h diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index c6cf7a0f..9c80fb06 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -379,6 +379,35 @@ jobs: done echo "[zone-mode] one run of the zone passes per zone, zones kept as stubs" + - name: -ZONES runs only the zones it names (Non-20.04) + if: matrix.id != 'docker-ubuntu-20.04' + shell: bash + run: | + set -eo pipefail + # -ZONES 2-3: zone 1 gets only the passes before the zone line, so + # it keeps its tokens and is no stub. $isfirstzone and $islastzone + # mark the first and last zone that runs, and -DEV writes trees for + # every zone that runs. + FIX=.github/workflows/tests/zone-mode + rm -rf "$FIX/logs" "$FIX"/input/*_log "$FIX/output" + out=$(./bin/nlp -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d ' ') + want=$(printf '%s +' "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" "zone 2: first=0 last=1 lines under root=1 words=1 text=six" "summary: zones=2 stubs=2 first=0 last=0") + if [ "$out" != "$want" ]; then + echo "FAIL(-ZONES): stdout was:"; echo "$out"; echo "wanted:"; echo "$want"; exit 1 + fi + L="$FIX/input/text.txt_log" + if [ -f "$L/z001-ana003.tree" ] || [ ! -f "$L/z002-ana003.tree" ] || [ ! -f "$L/z003-ana003.tree" ]; then + echo "FAIL(-ZONES): wrong zone trees:"; ls "$L"; exit 1 + fi + # A list, and a spec that is not zone numbers. + out=$(./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d ' ' | grep -c '^zone ' || true) + if [ "$out" != "2" ]; then echo "FAIL(-ZONES 1,3): ran $out zones"; exit 1; fi + if ./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 3-1 "$FIX/input/text.txt" > /dev/null 2>&1; then + echo "FAIL(-ZONES 3-1): a bad range was accepted"; exit 1 + fi + echo "[-ZONES] only the named zones run" + - name: A rec pass that never settles is stopped (Non-20.04) if: matrix.id != 'docker-ubuntu-20.04' shell: bash diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 28bf6829..9dda6cc9 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -226,6 +226,34 @@ jobs: done echo "[zone-mode] one run of the zone passes per zone, zones kept as stubs" + - name: -ZONES runs only the zones it names + shell: bash + run: | + set -eo pipefail + # -ZONES 2-3: zone 1 gets only the passes before the zone line, so + # it keeps its tokens and is no stub. $isfirstzone and $islastzone + # mark the first and last zone that runs, and -DEV writes trees for + # every zone that runs. + FIX=.github/workflows/tests/zone-mode + rm -rf "$FIX/logs" "$FIX"/input/*_log "$FIX/output" + out=$(./bin/nlp -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d ' ') + want=$(printf '%s +' "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" "zone 2: first=0 last=1 lines under root=1 words=1 text=six" "summary: zones=2 stubs=2 first=0 last=0") + if [ "$out" != "$want" ]; then + echo "FAIL(-ZONES): stdout was:"; echo "$out"; echo "wanted:"; echo "$want"; exit 1 + fi + L="$FIX/input/text.txt_log" + if [ -f "$L/z001-ana003.tree" ] || [ ! -f "$L/z002-ana003.tree" ] || [ ! -f "$L/z003-ana003.tree" ]; then + echo "FAIL(-ZONES): wrong zone trees:"; ls "$L"; exit 1 + fi + # A list, and a spec that is not zone numbers. + out=$(./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d ' ' | grep -c '^zone ' || true) + if [ "$out" != "2" ]; then echo "FAIL(-ZONES 1,3): ran $out zones"; exit 1; fi + if ./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 3-1 "$FIX/input/text.txt" > /dev/null 2>&1; then + echo "FAIL(-ZONES 3-1): a bad range was accepted"; exit 1 + fi + echo "[-ZONES] only the named zones run" + - name: A rec pass that never settles is stopped shell: bash run: | diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index b0ddcdc7..d19711f3 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -314,6 +314,34 @@ jobs: done echo "[zone-mode] one run of the zone passes per zone, zones kept as stubs" + - name: -ZONES runs only the zones it names + shell: bash + run: | + set -eo pipefail + # -ZONES 2-3: zone 1 gets only the passes before the zone line, so + # it keeps its tokens and is no stub. $isfirstzone and $islastzone + # mark the first and last zone that runs, and -DEV writes trees for + # every zone that runs. + FIX=.github/workflows/tests/zone-mode + rm -rf "$FIX/logs" "$FIX"/input/*_log "$FIX/output" + out=$(./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d ' ') + want=$(printf '%s +' "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" "zone 2: first=0 last=1 lines under root=1 words=1 text=six" "summary: zones=2 stubs=2 first=0 last=0") + if [ "$out" != "$want" ]; then + echo "FAIL(-ZONES): stdout was:"; echo "$out"; echo "wanted:"; echo "$want"; exit 1 + fi + L="$FIX/input/text.txt_log" + if [ -f "$L/z001-ana003.tree" ] || [ ! -f "$L/z002-ana003.tree" ] || [ ! -f "$L/z003-ana003.tree" ]; then + echo "FAIL(-ZONES): wrong zone trees:"; ls "$L"; exit 1 + fi + # A list, and a spec that is not zone numbers. + out=$(./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d ' ' | grep -c '^zone ' || true) + if [ "$out" != "2" ]; then echo "FAIL(-ZONES 1,3): ran $out zones"; exit 1; fi + if ./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ -ZONES 3-1 "$FIX/input/text.txt" > /dev/null 2>&1; then + echo "FAIL(-ZONES 3-1): a bad range was accepted"; exit 1 + fi + echo "[-ZONES] only the named zones run" + - name: A rec pass that never settles is stopped shell: bash run: | diff --git a/README.md b/README.md index b0ce1535..1881de1d 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,8 @@ runs once per node of that type: - The KB and `G()` carry from zone to zone. Node offsets count from the start of the document, and there is one set of output files. - After its passes a zone keeps its node, attributes and span, and its - subtree is freed. Memory goes by the largest zone, not the document. + subtree is freed. Memory goes by the largest zone and by the tree the + passes before the `zone` line build, which is of the whole document. - The passes after an `endzone` line run once, after the last zone, on the zone stubs and the whole KB: the place for work that belongs to the whole document, such as dumping the KB. @@ -176,6 +177,14 @@ runs once per node of that type: `_TEXTZONE` nodes into sections); that stops the run with an error. - With `-DEV`, the first three zones write their pass trees as `z001-ana040.tree` and so on. +- `-ZONES 40-45` runs only those zones (also `-ZONES 7` or `-ZONES 3,40-45`; + numbers from 1, as in the tree names). The other zones get only the passes + before the `zone` line, so a problem in zone 4,000 of a book can be rerun in + seconds. `$isfirstzone` and `$islastzone` mark the first and last zone that + runs, and with `-DEV` every zone that runs writes its pass trees. The KB and + `G()` start without what the skipped zones would have added, so a zone that + depends on earlier ones can come out differently than in a full run. + (Since 4.2.50.) - Compiled analyzers run zones the same way (since 4.2.49; before that they ignored the `zone` and `endzone` lines). diff --git a/include/Api/lite/nlp.h b/include/Api/lite/nlp.h index 5d3cc216..5984931c 100644 --- a/include/Api/lite/nlp.h +++ b/include/Api/lite/nlp.h @@ -467,6 +467,14 @@ class LITE_API NLP long long runLimitMs_ = 0; bool runStopped_ = false; + // ZONE RANGE (-ZONES): each new Parse runs only these zones; see + // Parse::setZoneRange. Empty is every zone. +public: + void setZoneRange(const _TCHAR *spec) { zoneRange_ = spec ? spec : _T(""); } + const _TCHAR *getZoneRange() { return zoneRange_.c_str(); } +private: + std::basic_string<_TCHAR> zoneRange_; + // When code is well debugged and stable, can remove the stuff below. #ifndef STABLE_ public: diff --git a/include/Api/lite/nlp_engine.h b/include/Api/lite/nlp_engine.h index 09a778ee..ac5ed81e 100644 --- a/include/Api/lite/nlp_engine.h +++ b/include/Api/lite/nlp_engine.h @@ -179,6 +179,13 @@ class LITE_API NLP_ENGINE long long getRunLimit(); bool lastRunStopped(); + // Zone mode: run only these zones of each input, eg "40-45" or + // "3,40-45" (zone numbers from 1, as in z040-ana###.tree). The others + // get only the passes before the zone line. Empty or 0 is every zone. + // False, and no change, if the spec is not that form. + bool setZoneRange(const _TCHAR *spec); + const _TCHAR *getZoneRange(); + private: void analyzeFile(const std::string &file, _TCHAR *outdir, std::_t_ofstream *fp); int createDir(_TCHAR *dirPath); @@ -194,4 +201,5 @@ class LITE_API NLP_ENGINE // At the end of the class, so its layout is unchanged before it. std::basic_string<_TCHAR> m_datum; // G("$datum") for analyses. long long m_runLimitMs = 0; // setRunLimit; 0 = none. + std::basic_string<_TCHAR> m_zoneRange; // setZoneRange; empty = every zone. }; \ No newline at end of file diff --git a/include/Api/lite/parse.h b/include/Api/lite/parse.h index f9d7c144..8a4710b1 100644 --- a/include/Api/lite/parse.h +++ b/include/Api/lite/parse.h @@ -23,6 +23,7 @@ All rights reserved. #include #include #include +#include typedef void TREE; // 07/07/03 AM. typedef void LTREE; // 07/07/03 AM. @@ -696,6 +697,14 @@ class LITE_API Parse _TCHAR zprefix_[32] = {0}; // zonePrefix()'s "z001-ana". + // ZONE RANGE (-ZONES 40-45): run only these zones; the others get only + // the passes before the zone line. Empty, the default, is every zone. +public: + void setZoneRange(const _TCHAR *spec); +private: + bool zoneSelected(long num); + std::vector> zoneRange_; // [first, last], from 1. + #ifndef STABLE_ public: static int getCount(); diff --git a/include/Api/lite/zonerange.h b/include/Api/lite/zonerange.h new file mode 100644 index 00000000..c28123fe --- /dev/null +++ b/include/Api/lite/zonerange.h @@ -0,0 +1,58 @@ +/******************************************************************************* +Copyright (c) 2026 by Text Analysis International, Inc. +All rights reserved. +******************************************************************************** +* +* NAME: ZONERANGE.H +* FILE: include/Api/lite/zonerange.h +* SUBJ: Read a -ZONES spec: which zones of a zone-mode run to run. +* NOTE: Used by Parse, which applies it, and NLP_ENGINE, which checks it. +* +*******************************************************************************/ + +#ifndef ZONERANGE_H_ +#define ZONERANGE_H_ + +#include +#include + +// "7", "40-45" or a list such as "3,40-45" into [first, last] pairs. Zone +// numbers count from 1, as in the z040-ana###.tree names. False, with +// ranges cleared, if the spec is not that form. +template +inline bool parseZoneRange(const CH *spec, std::vector> &ranges) +{ +ranges.clear(); +if (!spec || !*spec) + return false; +const CH *p = spec; +auto digit = [](CH c) { return c >= '0' && c <= '9'; }; +while (digit(*p)) + { + long lo = 0; + while (digit(*p)) + lo = lo * 10 + (*p++ - '0'); + long hi = lo; + if (*p == '-') + { + ++p; + if (!digit(*p)) + break; + hi = 0; + while (digit(*p)) + hi = hi * 10 + (*p++ - '0'); + } + if (lo < 1 || hi < lo) + break; + ranges.push_back(std::make_pair(lo, hi)); + if (!*p) + return true; + if (*p != ',') + break; + ++p; + } +ranges.clear(); +return false; +} + +#endif diff --git a/lite/nlp.cpp b/lite/nlp.cpp index 655b4eeb..c55e9244 100644 --- a/lite/nlp.cpp +++ b/lite/nlp.cpp @@ -1463,6 +1463,7 @@ Parse *parse; // The current text analysis instance. parse = new Parse(); // 05/13/99 AM. parse->nlp_ = this; // 06/25/03 AM. parse->setRunLimit(runLimitMs_); // 0 = none; see Parse::overBudget. // 10/25/26 DD. +parse->setZoneRange(zoneRange_.c_str()); // -ZONES; empty = every zone. parse->htab_ = this->htab_; // 06/25/03 AM. parse->setEana(eana); // 10/13/99 AM. @@ -1610,6 +1611,7 @@ Parse *parse; // The current text analysis instance. parse = new Parse(); // 05/13/99 AM. parse->nlp_ = this; // 06/25/03 AM. parse->setRunLimit(runLimitMs_); // 0 = none; see Parse::overBudget. // 10/25/26 DD. +parse->setZoneRange(zoneRange_.c_str()); // -ZONES; empty = every zone. parse->htab_ = this->htab_; // 06/25/03 AM. parse->setEana(eana); // 10/13/99 AM. diff --git a/lite/nlp_engine.cpp b/lite/nlp_engine.cpp index 29f066b3..a62aa43b 100644 --- a/lite/nlp_engine.cpp +++ b/lite/nlp_engine.cpp @@ -11,6 +11,7 @@ MIT License *******************************************************************************/ #include "nlp_engine.h" +#include "lite/zonerange.h" #include #include @@ -853,10 +854,28 @@ bool NLP_ENGINE::lastRunStopped() { return m_nlp && m_nlp->getRunStopped(); } -// init() may have made a new NLP; give it the limit, and clear the flag. +// init() may have made a new NLP; give it the limit and the zone range, and +// clear the flag. void NLP_ENGINE::armRunLimit() { m_nlp->setRunLimit(m_runLimitMs); m_nlp->setRunStopped(false); + m_nlp->setZoneRange(m_zoneRange.c_str()); +} + +bool NLP_ENGINE::setZoneRange(const _TCHAR *spec) { + if (!spec || !*spec || !_tcscmp(spec, _T("0"))) { + m_zoneRange.clear(); + return true; + } + std::vector> ranges; + if (!parseZoneRange(spec, ranges)) + return false; + m_zoneRange = spec; + return true; +} + +const _TCHAR *NLP_ENGINE::getZoneRange() { + return m_zoneRange.c_str(); } // The datum to pass to an analysis: 0 when unset, as before. diff --git a/lite/parse.cpp b/lite/parse.cpp index b3cae3d7..9f6fe598 100644 --- a/lite/parse.cpp +++ b/lite/parse.cpp @@ -50,6 +50,7 @@ All rights reserved. #include "consh/cg.h" //#include "iarg.h" // 05/23/01 AM. #include "parse.h" +#include "lite/zonerange.h" #include "lite/nlpdebug.h" #include "lite/nlp.h" // FOR DEBUGGING!!! // 10/10/99 AM. #include "var.h" // 09/26/00 AM. @@ -721,19 +722,49 @@ if (root) if (zones.empty()) { std::_t_cerr << _T("[zone: no ") << ztype - << _T(" nodes, so the passes after the zone line ran on the whole document.]") - << std::endl; + << _T(" nodes, so the passes after the zone line ran on the whole document") + << (zoneRange_.empty() ? _T("") : _T(" and -ZONES had no zones to pick")) + << _T(".]") << std::endl; return whole(); } int result = RUN_OK; std::vector links; long nzones = (long) zones.size(); + +// -ZONES: the zones outside the range are skipped, so they get only the +// passes before the zone line. $isfirstzone and $islastzone mark the first +// and last zone that runs. +long firstRun = -1, lastRun = -1; +for (long z = 0; z < nzones; ++z) + if (zoneSelected(z + 1)) + { + if (firstRun < 0) + firstRun = z; + lastRun = z; + } +if (!zoneRange_.empty()) + { + long nrun = 0; + for (long z = firstRun; firstRun >= 0 && z <= lastRun; ++z) + if (zoneSelected(z + 1)) + ++nrun; + std::_t_cerr << _T("[zone: -ZONES runs "); + if (nrun) + std::_t_cerr << nrun; + else + std::_t_cerr << _T("none"); + std::_t_cerr << _T(" of the ") << nzones << _T(" ") << ztype + << _T(" zones.]") << std::endl; + } + for (long z = 0; z < nzones && result == RUN_OK; ++z) { + if (!zoneSelected(z + 1)) + continue; zoneNum_ = z + 1; - zoneFirst_ = (z == 0); - zoneLast_ = (z == nzones - 1); + zoneFirst_ = (z == firstRun); + zoneLast_ = (z == lastRun); hidePath(root, zones[z], links); result = zone(); @@ -772,21 +803,43 @@ return result; * NOTE: With -DEV, the first ZONE_DUMPS zones write z001-ana075.tree and * so on; 2,500 zones of a 130-pass analyzer would otherwise write * 325,000 trees. Later zones write no pass trees or pass times. +* With -ZONES, every zone that runs writes them instead. * Called before iniPass() by interpreted and compiled passes. ********************************************/ _TCHAR *Parse::zonePrefix(_TCHAR *prefix, bool &flogfiles, bool &ftimepass) { -if (zoneNum_ > ZONE_DUMPS) +if (zoneNum_ > ZONE_DUMPS && zoneRange_.empty()) flogfiles = ftimepass = false; else if (zoneNum_ > 0) { - _stprintf(zprefix_, _T("z%03ld-%s"), zoneNum_ % 1000, prefix); + _stprintf(zprefix_, _T("z%03ld-%s"), zoneNum_, prefix); prefix = zprefix_; } return prefix; } +/******************************************** +* FN: SETZONERANGE +* SUBJ: Run only the zones a -ZONES spec names (see zonerange.h). +********************************************/ + +void Parse::setZoneRange(const _TCHAR *spec) +{ +if (!parseZoneRange(spec, zoneRange_)) + zoneRange_.clear(); // None or bad: every zone. +} + +bool Parse::zoneSelected(long num) +{ +if (zoneRange_.empty()) + return true; +for (size_t i = 0; i < zoneRange_.size(); ++i) + if (num >= zoneRange_[i].first && num <= zoneRange_[i].second) + return true; +return false; +} + void Parse::execute() { Delt *seq; diff --git a/nlp/main.cpp b/nlp/main.cpp index 49f71a28..d6f81569 100644 --- a/nlp/main.cpp +++ b/nlp/main.cpp @@ -13,11 +13,14 @@ All rights reserved. *******************************************************************************/ #include "lite/nlp_engine.h" +#include "lite/zonerange.h" #include "version.h" -#define NLP_ENGINE_VERSION "4.2.49" +#include -bool cmdReadArgs(int, _TCHAR *argv[], _TCHAR *&, _TCHAR *&, _TCHAR *&, _TCHAR *&, bool &, bool &, bool &, bool &, bool &, bool &, int &, bool &, bool &, _TCHAR *&, double &); +#define NLP_ENGINE_VERSION "4.2.50" + +bool cmdReadArgs(int, _TCHAR *argv[], _TCHAR *&, _TCHAR *&, _TCHAR *&, _TCHAR *&, bool &, bool &, bool &, bool &, bool &, bool &, int &, bool &, bool &, _TCHAR *&, double &, _TCHAR *&); void cmdHelpargs(_TCHAR *); #include "lite/nlpdebug.h" @@ -49,13 +52,14 @@ int _tmain( bool dictSource = false; // -DICTSOURCE: nodes record their .dict file and line. _TCHAR *datum = 0; // -DATUM value: G("$datum") in the analyzer. double timeLimit = 0; // -TIMELIMIT seconds: stop a run past it. 0 = none. + _TCHAR *zones = 0; // -ZONES 40-45: run only these zones in zone mode. ///////////////////////////////////////////////// // GET APP INFORMATION ///////////////////////////////////////////////// // Get analyzer name, input and output filenames from command line. - if (!cmdReadArgs(argc, argv, analyzerpath, input, output, workdir, develop, compile, compiled, silent, compileKB, compileAna, debugPort, stream, dictSource, datum, timeLimit)) + if (!cmdReadArgs(argc, argv, analyzerpath, input, output, workdir, develop, compile, compiled, silent, compileKB, compileAna, debugPort, stream, dictSource, datum, timeLimit, zones)) exit(1); if (dictSource) NLP_ENGINE::setDictSource(true); // #432. @@ -76,6 +80,8 @@ int _tmain( nlpEngine->setDatum(datum); if (timeLimit > 0) nlpEngine->setRunLimit((long long)(timeLimit * 1000.0)); + if (zones) + nlpEngine->setZoneRange(zones); // Checked in cmdReadArgs. if (compile) { // Compile mode: generate C++ code for the analyzer and KB. @@ -144,7 +150,8 @@ bool cmdReadArgs( bool &stream, // -STDIN: read input file paths from stdin. bool &dictSource, // -DICTSOURCE: nodes record their .dict file and line. _TCHAR *&datum, // -DATUM value: G("$datum") in the analyzer. - double &timeLimit // -TIMELIMIT seconds: stop a run past it. + double &timeLimit, // -TIMELIMIT seconds: stop a run past it. + _TCHAR *&zones // -ZONES spec: run only these zones in zone mode. ) { _TCHAR *ptr; @@ -157,6 +164,7 @@ bool cmdReadArgs( bool f_debug = false; bool f_datum = false; bool f_timelimit = false; + bool f_zones = false; bool flag = false; bool compiledck = false; // If compiled/interp arg seen. bool doubledash = false; @@ -243,6 +251,8 @@ bool cmdReadArgs( f_datum = flag = true; // Expecting G("$datum"). else if (!strcmp_i(ptr, _T("timelimit"))) f_timelimit = flag = true; // Expecting seconds. + else if (!strcmp_i(ptr, _T("zones"))) + f_zones = flag = true; // Expecting zone numbers. else if (!strcmp_i(ptr, _T("dev"))) // 12/25/98 AM. { if (silent) @@ -379,6 +389,19 @@ bool cmdReadArgs( } f_timelimit = flag = false; } + else if (f_zones) + { + std::vector> ranges; + if (!parseZoneRange(ptr, ranges)) + { + std::_t_cerr << _T("[") << argv[0] + << _T(": -ZONES needs zone numbers from 1, eg 7, 40-45 or 3,40-45.]") << std::endl; + cmdHelpargs(argv[0]); + return false; + } + zones = ptr; + f_zones = flag = false; + } else if (f_debug) { debugPort = _ttoi(ptr); @@ -411,7 +434,7 @@ bool cmdReadArgs( { // The last argument was an option that takes a value, eg "-ANA". std::_t_cerr << _T("[") << argv[0] << _T(": ") - << (f_ana ? _T("-ANA") : f_in ? _T("-IN") : f_out ? _T("-OUT") : f_work ? _T("-WORK") : f_datum ? _T("-DATUM") : f_timelimit ? _T("-TIMELIMIT") : _T("-DEBUG")) + << (f_ana ? _T("-ANA") : f_in ? _T("-IN") : f_out ? _T("-OUT") : f_work ? _T("-WORK") : f_datum ? _T("-DATUM") : f_timelimit ? _T("-TIMELIMIT") : f_zones ? _T("-ZONES") : _T("-DEBUG")) << _T(" needs a value.]") << std::endl; cmdHelpargs(argv[0]); return false; @@ -462,6 +485,8 @@ void cmdHelpargs(_TCHAR *name) << _T(" [-DATUM value] pass a string the analyzer reads as G(\"$datum\")") << std::endl << _T(" [-TIMELIMIT seconds] stop an input's analysis that runs longer, at its") << std::endl << _T(" next while turn or pass; the run then exits 2") << std::endl + << _T(" [-ZONES 40-45] zone mode: run only these zones (7, 40-45, 3,40-45);") << std::endl + << _T(" the others get only the passes before the zone line") << std::endl << _T(" [-DICTSOURCE] nodes built from .dict entries get dictfile and dictline") << std::endl << _T(" attributes naming the entry's file and line") << std::endl << std::endl From a4427e83cea594357e382412feeea9d7fd429501 Mon Sep 17 00:00:00 2001 From: David de Hilster Date: Tue, 27 Oct 2026 20:35:28 -0400 Subject: [PATCH 2/2] CI: fix the -ZONES step's escapes A heredoc edit had turned the step's '\r' and '\n' into real characters, so the three workflow files did not parse and no build ran. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build-linux.yml | 10 ++++++---- .github/workflows/build-macos.yml | 10 ++++++---- .github/workflows/build-windows.yml | 10 ++++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 9c80fb06..bf54371d 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -390,9 +390,11 @@ jobs: # every zone that runs. FIX=.github/workflows/tests/zone-mode rm -rf "$FIX/logs" "$FIX"/input/*_log "$FIX/output" - out=$(./bin/nlp -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d ' ') - want=$(printf '%s -' "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" "zone 2: first=0 last=1 lines under root=1 words=1 text=six" "summary: zones=2 stubs=2 first=0 last=0") + out=$(./bin/nlp -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d '\r') + want=$(printf '%s\n' \ + "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" \ + "zone 2: first=0 last=1 lines under root=1 words=1 text=six" \ + "summary: zones=2 stubs=2 first=0 last=0") if [ "$out" != "$want" ]; then echo "FAIL(-ZONES): stdout was:"; echo "$out"; echo "wanted:"; echo "$want"; exit 1 fi @@ -401,7 +403,7 @@ jobs: echo "FAIL(-ZONES): wrong zone trees:"; ls "$L"; exit 1 fi # A list, and a spec that is not zone numbers. - out=$(./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d ' ' | grep -c '^zone ' || true) + out=$(./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d '\r' | grep -c '^zone ' || true) if [ "$out" != "2" ]; then echo "FAIL(-ZONES 1,3): ran $out zones"; exit 1; fi if ./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 3-1 "$FIX/input/text.txt" > /dev/null 2>&1; then echo "FAIL(-ZONES 3-1): a bad range was accepted"; exit 1 diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 9dda6cc9..ffd54cdf 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -236,9 +236,11 @@ jobs: # every zone that runs. FIX=.github/workflows/tests/zone-mode rm -rf "$FIX/logs" "$FIX"/input/*_log "$FIX/output" - out=$(./bin/nlp -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d ' ') - want=$(printf '%s -' "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" "zone 2: first=0 last=1 lines under root=1 words=1 text=six" "summary: zones=2 stubs=2 first=0 last=0") + out=$(./bin/nlp -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d '\r') + want=$(printf '%s\n' \ + "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" \ + "zone 2: first=0 last=1 lines under root=1 words=1 text=six" \ + "summary: zones=2 stubs=2 first=0 last=0") if [ "$out" != "$want" ]; then echo "FAIL(-ZONES): stdout was:"; echo "$out"; echo "wanted:"; echo "$want"; exit 1 fi @@ -247,7 +249,7 @@ jobs: echo "FAIL(-ZONES): wrong zone trees:"; ls "$L"; exit 1 fi # A list, and a spec that is not zone numbers. - out=$(./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d ' ' | grep -c '^zone ' || true) + out=$(./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d '\r' | grep -c '^zone ' || true) if [ "$out" != "2" ]; then echo "FAIL(-ZONES 1,3): ran $out zones"; exit 1; fi if ./bin/nlp -ANA "$FIX" -WORK ./ -ZONES 3-1 "$FIX/input/text.txt" > /dev/null 2>&1; then echo "FAIL(-ZONES 3-1): a bad range was accepted"; exit 1 diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index d19711f3..3e065f96 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -324,9 +324,11 @@ jobs: # every zone that runs. FIX=.github/workflows/tests/zone-mode rm -rf "$FIX/logs" "$FIX"/input/*_log "$FIX/output" - out=$(./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d ' ') - want=$(printf '%s -' "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" "zone 2: first=0 last=1 lines under root=1 words=1 text=six" "summary: zones=2 stubs=2 first=0 last=0") + out=$(./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ -DEV -ZONES 2-3 "$FIX/input/text.txt" | tr -d '\r') + want=$(printf '%s\n' \ + "zone 1: first=1 last=0 lines under root=1 words=3 text=three four five" \ + "zone 2: first=0 last=1 lines under root=1 words=1 text=six" \ + "summary: zones=2 stubs=2 first=0 last=0") if [ "$out" != "$want" ]; then echo "FAIL(-ZONES): stdout was:"; echo "$out"; echo "wanted:"; echo "$want"; exit 1 fi @@ -335,7 +337,7 @@ jobs: echo "FAIL(-ZONES): wrong zone trees:"; ls "$L"; exit 1 fi # A list, and a spec that is not zone numbers. - out=$(./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d ' ' | grep -c '^zone ' || true) + out=$(./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ -ZONES 1,3 "$FIX/input/text.txt" | tr -d '\r' | grep -c '^zone ' || true) if [ "$out" != "2" ]; then echo "FAIL(-ZONES 1,3): ran $out zones"; exit 1; fi if ./bin/Release/nlp.exe -ANA "$FIX" -WORK ./ -ZONES 3-1 "$FIX/input/text.txt" > /dev/null 2>&1; then echo "FAIL(-ZONES 3-1): a bad range was accepted"; exit 1