diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 5c9f221d..88a7bdf9 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -387,6 +387,33 @@ jobs: fi echo "[str positions] every string position is a character" + - name: A run past -TIMELIMIT is stopped (Non-20.04) + if: matrix.id != 'docker-ubuntu-20.04' + timeout-minutes: 3 + shell: bash + run: | + set -o pipefail + # A loop that never ends used to run until something killed the + # process; one that makes a string a turn, until it ran out of + # memory. With -TIMELIMIT the run stops at the next while turn past + # the limit: the code after the loop and the passes after it do not + # run, the stop is reported on stderr, and nlp exits 2. + FIX=.github/workflows/tests/run-limit + rm -rf "$FIX/logs" "$FIX/input/text.txt_log" + mkdir -p "$FIX/logs" + rc=0 + ./bin/nlp -TIMELIMIT 2 -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" 2> /tmp/runlimit.err || rc=$? + if [ "$rc" != 2 ]; then + echo "FAIL(run limit): exit $rc, wanted 2"; cat /tmp/runlimit.err; exit 1 + fi + got=$(tr -d '\r' < "$FIX/input/text.txt_log/out.txt") + if [ "$got" != "before" ]; then + echo "FAIL(run limit): out.txt was:"; echo "$got"; echo "wanted: before"; exit 1 + fi + grep -qF '[Run stopped: past its time limit of 2000 ms in pass 3 (loop)' /tmp/runlimit.err \ + || { echo "FAIL(run limit): no stop message on stderr"; cat /tmp/runlimit.err; exit 1; } + echo "[run limit] a loop that never ends is stopped at -TIMELIMIT" + - name: Run-mode globals ($dev / $silent, 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 23966386..e54b3681 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -235,6 +235,32 @@ jobs: fi echo "[str positions] every string position is a character" + - name: A run past -TIMELIMIT is stopped + timeout-minutes: 3 + shell: bash + run: | + set -o pipefail + # A loop that never ends used to run until something killed the + # process; one that makes a string a turn, until it ran out of + # memory. With -TIMELIMIT the run stops at the next while turn past + # the limit: the code after the loop and the passes after it do not + # run, the stop is reported on stderr, and nlp exits 2. + FIX=.github/workflows/tests/run-limit + rm -rf "$FIX/logs" "$FIX/input/text.txt_log" + mkdir -p "$FIX/logs" + rc=0 + ./bin/nlp -TIMELIMIT 2 -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" 2> /tmp/runlimit.err || rc=$? + if [ "$rc" != 2 ]; then + echo "FAIL(run limit): exit $rc, wanted 2"; cat /tmp/runlimit.err; exit 1 + fi + got=$(tr -d '\r' < "$FIX/input/text.txt_log/out.txt") + if [ "$got" != "before" ]; then + echo "FAIL(run limit): out.txt was:"; echo "$got"; echo "wanted: before"; exit 1 + fi + grep -qF '[Run stopped: past its time limit of 2000 ms in pass 3 (loop)' /tmp/runlimit.err \ + || { echo "FAIL(run limit): no stop message on stderr"; cat /tmp/runlimit.err; exit 1; } + echo "[run limit] a loop that never ends is stopped at -TIMELIMIT" + # INTEL MACS (#231). The steps above built and tested arm64 (Apple # Silicon). Build the same tree for x86_64 too and merge the two with # lipo, so nlpm.exe and the compile libraries are universal binaries: diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 3c0204a9..6c9bf3ff 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -322,6 +322,32 @@ jobs: fi echo "[str positions] every string position is a character" + - name: A run past -TIMELIMIT is stopped + timeout-minutes: 3 + shell: bash + run: | + set -o pipefail + # A loop that never ends used to run until something killed the + # process; one that makes a string a turn, until it ran out of + # memory. With -TIMELIMIT the run stops at the next while turn past + # the limit: the code after the loop and the passes after it do not + # run, the stop is reported on stderr, and nlp exits 2. + FIX=.github/workflows/tests/run-limit + rm -rf "$FIX/logs" "$FIX/input/text.txt_log" + mkdir -p "$FIX/logs" + rc=0 + ./bin/Release/nlp.exe -TIMELIMIT 2 -ANA "$FIX" -WORK ./ "$FIX/input/text.txt" 2> /tmp/runlimit.err || rc=$? + if [ "$rc" != 2 ]; then + echo "FAIL(run limit): exit $rc, wanted 2"; cat /tmp/runlimit.err; exit 1 + fi + got=$(tr -d '\r' < "$FIX/input/text.txt_log/out.txt") + if [ "$got" != "before" ]; then + echo "FAIL(run limit): out.txt was:"; echo "$got"; echo "wanted: before"; exit 1 + fi + grep -qF '[Run stopped: past its time limit of 2000 ms in pass 3 (loop)' /tmp/runlimit.err \ + || { echo "FAIL(run limit): no stop message on stderr"; cat /tmp/runlimit.err; exit 1; } + echo "[run limit] a loop that never ends is stopped at -TIMELIMIT" + - name: Run-mode globals ($dev / $silent) shell: bash run: | diff --git a/.github/workflows/tests/run-limit/input/text.txt b/.github/workflows/tests/run-limit/input/text.txt new file mode 100644 index 00000000..480c16cd --- /dev/null +++ b/.github/workflows/tests/run-limit/input/text.txt @@ -0,0 +1 @@ +The fixture loops; the text does not matter. diff --git a/.github/workflows/tests/run-limit/kb/user/.gitkeep b/.github/workflows/tests/run-limit/kb/user/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/.github/workflows/tests/run-limit/spec/after.nlp b/.github/workflows/tests/run-limit/spec/after.nlp new file mode 100644 index 00000000..0805f5fc --- /dev/null +++ b/.github/workflows/tests/run-limit/spec/after.nlp @@ -0,0 +1,3 @@ +@CODE +"out.txt" << "after-pass\n"; +@@CODE diff --git a/.github/workflows/tests/run-limit/spec/analyzer.seq b/.github/workflows/tests/run-limit/spec/analyzer.seq new file mode 100644 index 00000000..70d889eb --- /dev/null +++ b/.github/workflows/tests/run-limit/spec/analyzer.seq @@ -0,0 +1,4 @@ +tokenize nil # tokenize only -- fixture needs no dictionary +nlp before # runs, and says so +nlp loop # never ends on its own +nlp after # must not run once the limit stops the loop diff --git a/.github/workflows/tests/run-limit/spec/before.nlp b/.github/workflows/tests/run-limit/spec/before.nlp new file mode 100644 index 00000000..5b4fbc22 --- /dev/null +++ b/.github/workflows/tests/run-limit/spec/before.nlp @@ -0,0 +1,3 @@ +@CODE +"out.txt" << "before\n"; +@@CODE diff --git a/.github/workflows/tests/run-limit/spec/loop.nlp b/.github/workflows/tests/run-limit/spec/loop.nlp new file mode 100644 index 00000000..466e779c --- /dev/null +++ b/.github/workflows/tests/run-limit/spec/loop.nlp @@ -0,0 +1,23 @@ +############################################### +# FILE: loop.nlp +# SUBJ: A loop that never ends is stopped by -TIMELIMIT. +# NOTE: Like the loop that killed a document extractor: every turn +# makes a new string, so without a limit it runs until the +# process is killed for its memory. With -TIMELIMIT 2 the run +# stops at the next while turn after two seconds; the line after +# the loop and the pass after this one do not run, and nlp exits 2. +# Expected out.txt: +# before +############################################### + +@CODE + +L("s") = "x"; +L("n") = 0; +while (L("n") >= 0) { + L("n")++; + L("s") = "x" + str(L("n")); +} +"out.txt" << "after-loop\n"; + +@@CODE diff --git a/include/Api/lite/Arun.h b/include/Api/lite/Arun.h index d435e8d2..2a2be584 100644 --- a/include/Api/lite/Arun.h +++ b/include/Api/lite/Arun.h @@ -553,6 +553,7 @@ class LITE_API Arun static bool iassign(int,_TCHAR*,int,long long,Nlppp*,bool); // 12/10/02 AM. static long long iassign(int,_TCHAR*,int,long long,Nlppp*,int); + static bool overbudget(Nlppp*); // Run past its time limit. // 10/25/26 DD. static bool truth(long long); static bool truth(float); static bool truth(_TCHAR *); diff --git a/include/Api/lite/nlp.h b/include/Api/lite/nlp.h index e3fd30d0..5d3cc216 100644 --- a/include/Api/lite/nlp.h +++ b/include/Api/lite/nlp.h @@ -455,6 +455,18 @@ class LITE_API NLP // Intended for notable errors. // VTLOG // 05/06/13 AM. _TCHAR *logfile_; // VTLOG // 05/06/13 AM. + // RUN LIMIT for each text's analysis, in milliseconds (0 = none), and + // whether a run since the flag was cleared was stopped at it. Each new + // Parse takes the limit; Parse::overBudget sets the flag. // 10/25/26 DD. +public: + void setRunLimit(long long ms) { runLimitMs_ = ms > 0 ? ms : 0; } + long long getRunLimit() { return runLimitMs_; } + void setRunStopped(bool stopped) { runStopped_ = stopped; } + bool getRunStopped() { return runStopped_; } +private: + long long runLimitMs_ = 0; + bool runStopped_ = false; + // 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 6a705649..09a778ee 100644 --- a/include/Api/lite/nlp_engine.h +++ b/include/Api/lite/nlp_engine.h @@ -171,6 +171,14 @@ class LITE_API NLP_ENGINE void setDatum(const _TCHAR *datum); const _TCHAR *getDatum(); + // A time limit on each text's analysis, in milliseconds; 0 (the + // default) is none. A run past it is stopped -- at its next while turn + // or pass -- instead of looping until something kills the process, and + // lastRunStopped() is true after the analyze() call that stopped one. + void setRunLimit(long long ms); + long long getRunLimit(); + bool lastRunStopped(); + private: void analyzeFile(const std::string &file, _TCHAR *outdir, std::_t_ofstream *fp); int createDir(_TCHAR *dirPath); @@ -181,7 +189,9 @@ class LITE_API NLP_ENGINE // passed as different path-forms maps to one entry -- see #658 / #380. void anaRegName(const _TCHAR *path, _TCHAR *out); _TCHAR *datumArg(); + void armRunLimit(); // 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. }; \ No newline at end of file diff --git a/include/Api/lite/parse.h b/include/Api/lite/parse.h index 6748d41b..fb7a651d 100644 --- a/include/Api/lite/parse.h +++ b/include/Api/lite/parse.h @@ -21,6 +21,7 @@ All rights reserved. #endif #include #include +#include typedef void TREE; // 07/07/03 AM. typedef void LTREE; // 07/07/03 AM. @@ -658,6 +659,20 @@ class LITE_API Parse // Pass info to analyzer, to G("$datum") variable. // 03/13/03 AM. _TCHAR *datum_; // 03/13/03 AM. + // RUN LIMIT. A time limit on this text's analysis, so an analyzer whose + // loop never ends is stopped instead of taking its process with it: every + // turn of a while loop, and every pass, asks overBudget(). 0, the + // default, is no limit. // 10/25/26 DD. +public: + void setRunLimit(long long ms); + bool overBudget(bool now=false); // True once past the limit; stays true. + bool runStopped() { return runStopped_; } +private: + long long runLimitMs_ = 0; + std::chrono::steady_clock::time_point runDeadline_; + unsigned long runTicks_ = 0; // Calls since the clock was last read. + bool runStopped_ = false; + #ifndef STABLE_ public: static int getCount(); diff --git a/include/Api/my_tchar.h b/include/Api/my_tchar.h index 3131ba47..48ca3e41 100644 --- a/include/Api/my_tchar.h +++ b/include/Api/my_tchar.h @@ -93,6 +93,7 @@ All rights reserved. #define _tcsnccpy strncpy #define _tgetenv getenv #define _ttoi atoi +#define _tstof atof // nlp -TIMELIMIT seconds. // 10/25/26 DD. #define _tsystem system #define _stprintf sprintf #define _ftprintf fprintf diff --git a/lite/Arun.cpp b/lite/Arun.cpp index da2e9e25..07fbcef6 100644 --- a/lite/Arun.cpp +++ b/lite/Arun.cpp @@ -8425,6 +8425,20 @@ return iassign(typ, varname, nelt, index, nlppp, (long long)val); } +/******************************************** +* FN: OVERBUDGET +* CR: 10/25/26 DD. +* SUBJ: Has this run passed its time limit? Compiled runtime. +* NOTE: Generated into every compiled while condition, as the interpreter +* asks Parse::overBudget on every turn (Iwhilestmt::eval). +********************************************/ + +bool Arun::overbudget(Nlppp *nlppp) +{ +return nlppp && nlppp->parse_ && nlppp->parse_->overBudget(); +} + + /******************************************** * FN: TRUTH * CR: 05/13/00 AM. diff --git a/lite/iwhilestmt.cpp b/lite/iwhilestmt.cpp index 6ab4257b..b4ceb56a 100644 --- a/lite/iwhilestmt.cpp +++ b/lite/iwhilestmt.cpp @@ -260,6 +260,15 @@ _TCHAR *str; for (;;) // EXECUTE THE WHILE-LOOP. { + // A run past its time limit (Parse::setRunLimit) stops here, and fails + // the region: the code after a loop that did not finish would run on + // whatever it left half done. Parse::overBudget has said why. // 10/25/26 DD. + if (nlppp->parse_->overBudget()) + { + ok = false; + goto done; + } + ok = cond_->eval(nlppp, /*UP*/ c_val); if (!ok || !c_val) { @@ -478,7 +487,8 @@ enum RFASemtype typ = RSNULL; // function with all possible return types! Elegant! Can be an inline. // This can be a general way....(but not totally optimized.) // If var, evals it. (see Iwhilestmt::eval). -*fcode << indent << _T("while (Arun::truth("); // FIX! // 04/29/01 AM. +// The run limit applies to compiled analyzers too (see eval). // 10/25/26 DD. +*fcode << indent << _T("while (!Arun::overbudget(nlppp) && Arun::truth("); if (!cond_->genEval(gen)) { *fcode << _T("\n// ERROR in whilestmt condition.") << std::endl; diff --git a/lite/nlp.cpp b/lite/nlp.cpp index c530ee73..655b4eeb 100644 --- a/lite/nlp.cpp +++ b/lite/nlp.cpp @@ -1462,6 +1462,7 @@ if (!htab_) // 06/25/03 AM. 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->htab_ = this->htab_; // 06/25/03 AM. parse->setEana(eana); // 10/13/99 AM. @@ -1608,6 +1609,7 @@ if (!htab_) // 06/25/03 AM. 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->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 6272d224..59c6d582 100644 --- a/lite/nlp_engine.cpp +++ b/lite/nlp_engine.cpp @@ -452,6 +452,7 @@ int NLP_ENGINE::analyze( if (!m_nlp) // defensive: never analyze without a live analyzer object. return -1; + armRunLimit(); // #639: a missing input-file argument (easy to hit with -COMPILED, whose // syntax differs from -COMPILE only by the trailing input path) used to @@ -514,6 +515,7 @@ int NLP_ENGINE::analyzeStream( if (!m_nlp) // defensive: never analyze without a live analyzer object. return -1; + armRunLimit(); std::filesystem::path fname(analyzer); fname /= _T("logs"); @@ -670,6 +672,7 @@ int NLP_ENGINE::analyze( if (!m_nlp) // defensive: never analyze without a live analyzer object. return -1; + armRunLimit(); // The analyzer's cout() writes to standard output. @@ -722,6 +725,7 @@ int NLP_ENGINE::analyze( if (!m_nlp) // defensive: never analyze without a live analyzer object. return -1; + armRunLimit(); // The analyzer's cout() writes to standard output. @@ -826,6 +830,29 @@ const _TCHAR *NLP_ENGINE::getDatum() { return m_datum.c_str(); } +// A time limit on each text's analysis, in milliseconds; 0 is none. A run +// past it stops at its next while turn or pass, and lastRunStopped() says +// so. See Parse::overBudget for why. // 10/25/26 DD. +void NLP_ENGINE::setRunLimit(long long ms) { + m_runLimitMs = ms > 0 ? ms : 0; +} + +long long NLP_ENGINE::getRunLimit() { + return m_runLimitMs; +} + +// Whether the last analyze() or analyzeStream() call stopped a run (any of +// its inputs) at the time limit. +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. +void NLP_ENGINE::armRunLimit() { + m_nlp->setRunLimit(m_runLimitMs); + m_nlp->setRunStopped(false); +} + // The datum to pass to an analysis: 0 when unset, as before. _TCHAR *NLP_ENGINE::datumArg() { return m_datum.empty() ? 0 : (_TCHAR *) m_datum.c_str(); diff --git a/lite/parse.cpp b/lite/parse.cpp index 630f0d96..9036e722 100644 --- a/lite/parse.cpp +++ b/lite/parse.cpp @@ -521,6 +521,11 @@ CG *cg = ana->getCG(); // Cosmetic rewrite as for loop. // 11/10/99 AM. for (seq = ana->getSeq(); seq; seq = seq->Right()) { + // A run past its time limit runs no more passes. The pass that ran + // out stopped at its next while turn; the rest are skipped whole. // 10/25/26 DD. + if (overBudget(true)) + break; + ++rulepass_; // 02/03/05 AM. if (cg && !cg->getUserkbLoaded() // 07/11/26 DD. @@ -567,6 +572,82 @@ if (gui_.IsMessage(GUI_MESSAGE_ABORT)) // 11/10/99 DD. #endif } +/******************************************** +* FN: SETRUNLIMIT +* CR: 10/25/26 DD. +* SUBJ: Give this text's analysis a time limit, from now. +* NOTE: 0 or less is no limit, as before. +********************************************/ + +void Parse::setRunLimit(long long ms) +{ +runLimitMs_ = ms > 0 ? ms : 0; +runStopped_ = false; +runTicks_ = 0; +if (runLimitMs_) + runDeadline_ = std::chrono::steady_clock::now() + + std::chrono::milliseconds(runLimitMs_); +} + + +/******************************************** +* FN: OVERBUDGET +* CR: 10/25/26 DD. +* SUBJ: Has this run passed its time limit? +* RET: True once it has, and from then on. +* NOTE: Asked on every turn of every while loop, so it reads the clock only +* every 256th call -- a loop turn is a few microseconds, and the +* limit is in seconds. `now` reads it regardless, for the check +* between passes, which come too seldom to count. +* The first time it is over, the run is marked stopped (here and on +* the NLP object, for NLP_ENGINE::lastRunStopped) and says so once, +* in err.log and on stderr. +* +* WHY: NLP++ is a C++ extension. A grammar whose loop never ends +* cannot be interrupted from outside -- a Python caller has no +* thread to stop, only a process to kill -- and a loop that interns +* a string a turn grows until the process is killed for memory. +* One such loop took a document extractor down five times in half +* an hour on one document. With a limit it stops that run; the +* string table goes with the run, so the memory comes back. +********************************************/ + +bool Parse::overBudget(bool now) +{ +if (runStopped_) + return true; +if (!runLimitMs_) + return false; +if (!now && (++runTicks_ & 0xFF)) + return false; +if (std::chrono::steady_clock::now() < runDeadline_) + return false; + +runStopped_ = true; +if (nlp_) + nlp_->setRunStopped(true); + +_TCHAR *file = 0; +Dlist *seqs = ana ? ana->getSeqlist() : 0; +for (Delt *d = seqs ? seqs->getFirst() : 0; d; d = d->Right()) + if (d->getData()->getPassnum() == currpass_) + { + file = d->getData()->getRulesfilename(); + break; + } + +std::_t_strstream gerrStr; +gerrStr << _T("[Run stopped: past its time limit of ") << runLimitMs_ + << _T(" ms in pass ") << currpass_; +if (file) + gerrStr << _T(" (") << file << _T(")"); +gerrStr << _T("; the rest of the analysis was not run.]") << std::ends; +std::_t_cerr << gerrStr.str() << std::endl; +errOut(&gerrStr,false); +return true; +} + + /******************************************** * FN: FINEXECUTE * CR: 06/13/99 AM. diff --git a/nlp/main.cpp b/nlp/main.cpp index 801da19d..e9ce58df 100644 --- a/nlp/main.cpp +++ b/nlp/main.cpp @@ -15,9 +15,9 @@ All rights reserved. #include "lite/nlp_engine.h" #include "version.h" -#define NLP_ENGINE_VERSION "4.2.40" +#define NLP_ENGINE_VERSION "4.2.41" -bool cmdReadArgs(int, _TCHAR *argv[], _TCHAR *&, _TCHAR *&, _TCHAR *&, _TCHAR *&, bool &, bool &, bool &, bool &, bool &, bool &, int &, bool &, bool &, _TCHAR *&); +bool cmdReadArgs(int, _TCHAR *argv[], _TCHAR *&, _TCHAR *&, _TCHAR *&, _TCHAR *&, bool &, bool &, bool &, bool &, bool &, bool &, int &, bool &, bool &, _TCHAR *&, double &); void cmdHelpargs(_TCHAR *); #include "lite/nlpdebug.h" @@ -48,13 +48,14 @@ int _tmain( bool stream = false; // -STDIN: read input file paths from stdin. 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. ///////////////////////////////////////////////// // 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)) + if (!cmdReadArgs(argc, argv, analyzerpath, input, output, workdir, develop, compile, compiled, silent, compileKB, compileAna, debugPort, stream, dictSource, datum, timeLimit)) exit(1); if (dictSource) NLP_ENGINE::setDictSource(true); // #432. @@ -73,6 +74,8 @@ int _tmain( NLP_ENGINE *nlpEngine = new NLP_ENGINE(workdir ? workdir : _T("")); if (datum) nlpEngine->setDatum(datum); + if (timeLimit > 0) + nlpEngine->setRunLimit((long long)(timeLimit * 1000.0)); if (compile) { // Compile mode: generate C++ code for the analyzer and KB. @@ -109,8 +112,11 @@ int _tmain( } NlpDebug::runEnd(); NlpDebug::shutdown(); + // A run stopped at -TIMELIMIT exits 2: it is neither a success nor the + // missing analyzer or input that exits 1. // 10/25/26 DD. + bool stopped = nlpEngine->lastRunStopped(); delete nlpEngine; - return rc < 0 ? 1 : 0; + return rc < 0 ? 1 : (stopped ? 2 : 0); } /******************************************** @@ -137,7 +143,8 @@ bool cmdReadArgs( int &debugPort, // -debug : rule-level debug server. 0 = off. 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. + _TCHAR *&datum, // -DATUM value: G("$datum") in the analyzer. + double &timeLimit // -TIMELIMIT seconds: stop a run past it. ) { _TCHAR *ptr; @@ -149,6 +156,7 @@ bool cmdReadArgs( bool f_work = false; bool f_debug = false; bool f_datum = false; + bool f_timelimit = false; bool flag = false; bool compiledck = false; // If compiled/interp arg seen. bool doubledash = false; @@ -233,6 +241,8 @@ bool cmdReadArgs( f_debug = flag = true; // Expecting a TCP port. else if (!strcmp_i(ptr, _T("datum"))) 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("dev"))) // 12/25/98 AM. { if (silent) @@ -357,6 +367,18 @@ bool cmdReadArgs( datum = ptr; f_datum = flag = false; } + else if (f_timelimit) + { + timeLimit = _tstof(ptr); + if (timeLimit <= 0) + { + std::_t_cerr << _T("[") << argv[0] + << _T(": -TIMELIMIT needs a number of seconds above 0.]") << std::endl; + cmdHelpargs(argv[0]); + return false; + } + f_timelimit = flag = false; + } else if (f_debug) { debugPort = _ttoi(ptr); @@ -389,7 +411,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") : _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") : _T("-DEBUG")) << _T(" needs a value.]") << std::endl; cmdHelpargs(argv[0]); return false; @@ -438,6 +460,8 @@ void cmdHelpargs(_TCHAR *name) << _T(" a blank line or EOF ends the run. Each input is analyzed") << std::endl << _T(" when the next line arrives, so $islastfile can be set.") << std::endl << _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(" [-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