mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
The flip: FUNCTION/XFUNCTION/FUN::fun/delim_check and the module interfaces take `const UTF8 * const fargs[]`. Double-const is load-bearing: C++ qualification conversion needs const at both pointer levels, so builder-side `UTF8 *[]` arrays convert implicitly — the evaluator, the JIT marshaller, and every owner site need zero casts, and slot reassignment inside bodies becomes a compile error for free. The conversions: the flip landed first so the compiler enumerated every violation; this commit is that inventory worked to zero — ~250 sites across funceval, funceval2, functions, funmath, help, mail, session, powers, levels, predicates, conf, walkdb, stringutil, timeutil/ date_scan (regenerated, one-line diff), exp3, and mux_main, each classified per docs/campaign-2136-const-fargs.md's four recipes. New idioms (functions.h): trim_space_sep_n() — non-destructive trim for (pointer, length) consumers, so trim-then-scan sites need no copy at all; FargVec — the argv counterpart of FargCopy for CS_ARGV handlers. countwords() and DecodeListOfIntegers() rewritten non-destructive. The flip deleted more than it added: #2157's fun_munge list1 copy, the engine_com help-topic copy, fun_index's in-place NUL write, and five const_casts (process_sex x4, sha1_helper). const_cast budget: zero added. Trap recorded in the brief: an old-signature definition doesn't fail the build — it becomes a C++ overload, and the new-signature symbol stays undefined until dlopen(RTLD_NOW). delim_check, the conn_bridge bridges, the dbt_spike stub, and exp3::Call were all silently shadowed; muxscript was the only host that noticed, because netmux's own net.cpp resolved the flat-namespace lookup. After any signature flip, grep the old spelling. Verified: make test EXPECT_CONFIG="jit=yes" (35 passed / 0 failed) and make test-scenario, including the new tests/scenario/sidefx_fargs.py that live-probes the class-3 wrappers smoke never touches (pemit/ trigger/link/tel/wipe/destroy). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2430 lines
52 KiB
C++
2430 lines
52 KiB
C++
|
|
/*! \file date_scan.rl
|
|
* \brief Unified date parser — Ragel -G2 scanner + recursive descent.
|
|
*
|
|
* Replaces both do_convtime() and ParseDate() with a single code path.
|
|
* Ragel tokenizes the input; a small top-down parser assembles tokens
|
|
* into a FIELDEDTIME plus timezone offset.
|
|
*
|
|
* Build: ragel -G2 -o date_scan.cpp date_scan.rl
|
|
*/
|
|
|
|
#include "copyright.h"
|
|
#include "autoconf.h"
|
|
#include "config.h"
|
|
#include "core.h"
|
|
#include "timeutil.h"
|
|
|
|
#include <climits>
|
|
#include <cstring>
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Token types
|
|
// -----------------------------------------------------------------------
|
|
|
|
enum DateTokType : unsigned char
|
|
{
|
|
DTT_EOF = 0,
|
|
DTT_NUM, // digit sequence; iVal = integer value, nDigits = count
|
|
DTT_MONTH, // month name; iVal = 1..12
|
|
DTT_DOW, // day-of-week name; iVal = 0..6 (Sun=0)
|
|
DTT_TZ_NAME, // named timezone; iVal = offset in minutes
|
|
DTT_MERIDIAN, // AM/PM; iVal = 0 or 12
|
|
DTT_SUFFIX, // ordinal suffix (st/nd/rd/th)
|
|
DTT_T, // letter T
|
|
DTT_W, // letter W
|
|
DTT_Z, // letter Z
|
|
DTT_DASH, // -
|
|
DTT_PLUS, // +
|
|
DTT_COLON, // :
|
|
DTT_DOT, // .
|
|
DTT_COMMA, // ,
|
|
DTT_SPACE, // whitespace (collapsed)
|
|
};
|
|
|
|
struct DateTok
|
|
{
|
|
DateTokType type;
|
|
unsigned char nDigits; // for DTT_NUM: digit count
|
|
int iVal; // numeric value or encoded meaning
|
|
};
|
|
|
|
// Maximum tokens a date string can produce.
|
|
// "Wed, 24 Jun 1992 10:22:54.1234567 -0700" is ~20 tokens.
|
|
//
|
|
#define DATE_MAX_TOKENS 32
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Token classifiers — called from committed => actions only.
|
|
// These examine the matched text (ts..te) to determine the value.
|
|
// -----------------------------------------------------------------------
|
|
|
|
static inline int toupper_ascii(int c) { return (c >= 'a' && c <= 'z') ? c - 32 : c; }
|
|
|
|
static int classify_month(const UTF8 *ts, const UTF8 *te)
|
|
{
|
|
(void)te;
|
|
// First 3 chars determine the month unambiguously.
|
|
int c0 = toupper_ascii(ts[0]);
|
|
int c1 = toupper_ascii(ts[1]);
|
|
int c2 = toupper_ascii(ts[2]);
|
|
switch (c0)
|
|
{
|
|
case 'J':
|
|
if (c1 == 'A') return 1; // Jan
|
|
if (c2 == 'N') return 6; // Jun
|
|
return 7; // Jul
|
|
case 'F': return 2; // Feb
|
|
case 'M':
|
|
return (c2 == 'R') ? 3 : 5; // Mar / May
|
|
case 'A':
|
|
return (c1 == 'P') ? 4 : 8; // Apr / Aug
|
|
case 'S': return 9; // Sep
|
|
case 'O': return 10; // Oct
|
|
case 'N': return 11; // Nov
|
|
case 'D': return 12; // Dec
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int classify_dow(const UTF8 *ts, const UTF8 *te)
|
|
{
|
|
(void)te;
|
|
int c0 = toupper_ascii(ts[0]);
|
|
int c1 = toupper_ascii(ts[1]);
|
|
switch (c0)
|
|
{
|
|
case 'S':
|
|
return (c1 == 'U') ? 0 : 6; // Sun=0, Sat=6
|
|
case 'M': return 1; // Mon
|
|
case 'T':
|
|
return (c1 == 'U') ? 2 : 4; // Tue=2, Thu=4
|
|
case 'W': return 3; // Wed
|
|
case 'F': return 5; // Fri
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int classify_tz(const UTF8 *ts, const UTF8 *te)
|
|
{
|
|
int len = (int)(te - ts);
|
|
// Build a simple key from first chars for fast dispatch.
|
|
int c0 = toupper_ascii(ts[0]);
|
|
int c1 = (len > 1) ? toupper_ascii(ts[1]) : 0;
|
|
int c2 = (len > 2) ? toupper_ascii(ts[2]) : 0;
|
|
|
|
if (len == 2)
|
|
{
|
|
// UT
|
|
return 0;
|
|
}
|
|
if (len == 3)
|
|
{
|
|
switch (c0)
|
|
{
|
|
case 'U': return 0; // UTC
|
|
case 'G': return 0; // GMT
|
|
case 'E':
|
|
return (c2 == 'T') ? -300 : // EST
|
|
(c2 == 'T') ? -300 : // EET handled below
|
|
-240; // EDT
|
|
case 'C':
|
|
if (c1 == 'S') return -360; // CST
|
|
if (c1 == 'D') return -300; // CDT
|
|
if (c1 == 'E') return 60; // CET
|
|
break;
|
|
case 'M':
|
|
return (c2 == 'T') ? -420 : -360; // MST / MDT
|
|
case 'P':
|
|
return (c2 == 'T') ? -480 : -420; // PST / PDT
|
|
case 'H': return -600; // HST
|
|
case 'A':
|
|
if (c1 == 'S') return -240; // AST
|
|
if (c1 == 'D') return -180; // ADT
|
|
break;
|
|
case 'B': return 60; // BST
|
|
case 'I': return 60; // IST
|
|
}
|
|
|
|
// 3-letter: EST/EDT/EET
|
|
if (c0 == 'E')
|
|
{
|
|
if (c1 == 'S') return -300; // EST
|
|
if (c1 == 'D') return -240; // EDT
|
|
if (c1 == 'E') return 120; // EET
|
|
}
|
|
}
|
|
if (len == 4)
|
|
{
|
|
// AKST, AKDT, CEST, EEST, AEST
|
|
if (c0 == 'A' && c1 == 'K')
|
|
return (c2 == 'S') ? -540 : -480; // AKST / AKDT
|
|
if (c0 == 'C' && c1 == 'E')
|
|
return 120; // CEST
|
|
if (c0 == 'E' && c1 == 'E')
|
|
return 180; // EEST
|
|
if (c0 == 'A' && c1 == 'E')
|
|
return 600; // AEST
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Ragel scanner
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
static const int date_scanner_start = 40;
|
|
static const int date_scanner_error = 0;
|
|
|
|
static const int date_scanner_en_main = 40;
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Run the Ragel scanner. Returns number of tokens, or -1 on error.
|
|
// -----------------------------------------------------------------------
|
|
|
|
static int date_scan(const UTF8 *input, size_t len, DateTok *toks)
|
|
{
|
|
const UTF8 *p = input;
|
|
const UTF8 *pe = input + len;
|
|
const UTF8 *eof = pe;
|
|
const UTF8 *ts = nullptr;
|
|
const UTF8 *te = nullptr;
|
|
int cs = 0;
|
|
int act = 0;
|
|
int ntok = 0;
|
|
|
|
|
|
(void)ts; (void)te; (void)act; // suppress unused warnings
|
|
|
|
|
|
{
|
|
cs = date_scanner_start;
|
|
ts = 0;
|
|
te = 0;
|
|
act = 0;
|
|
}
|
|
|
|
|
|
{
|
|
if ( p == pe )
|
|
goto _test_eof;
|
|
switch ( cs )
|
|
{
|
|
tr0:
|
|
{ switch( act ) {
|
|
case 10:
|
|
{{p = ((te))-1;}
|
|
if (ntok < DATE_MAX_TOKENS) {
|
|
int c = toupper_ascii(ts[0]);
|
|
// A=+1, B=+2, ..., I=+9
|
|
int off = (c - 'A' + 1) * 60;
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, off };
|
|
}
|
|
}
|
|
break;
|
|
case 12:
|
|
{{p = ((te))-1;}
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, 720 }; // M=+12
|
|
}
|
|
break;
|
|
case 13:
|
|
{{p = ((te))-1;}
|
|
if (ntok < DATE_MAX_TOKENS) {
|
|
int c = toupper_ascii(ts[0]);
|
|
// N=-1, O=-2, ..., Y=-12
|
|
int off = -(c - 'N' + 1) * 60;
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, off };
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
goto st40;
|
|
tr1:
|
|
{te = p+1;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, classify_tz(ts, te) };
|
|
}}
|
|
goto st40;
|
|
tr2:
|
|
{{p = ((te))-1;}{
|
|
if (ntok < DATE_MAX_TOKENS) {
|
|
int c = toupper_ascii(ts[0]);
|
|
// A=+1, B=+2, ..., I=+9
|
|
int off = (c - 'A' + 1) * 60;
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, off };
|
|
}
|
|
}}
|
|
goto st40;
|
|
tr5:
|
|
{{p = ((te))-1;}{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_MONTH, 0, classify_month(ts, te) };
|
|
}}
|
|
goto st40;
|
|
tr6:
|
|
{te = p+1;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_MONTH, 0, classify_month(ts, te) };
|
|
}}
|
|
goto st40;
|
|
tr18:
|
|
{{p = ((te))-1;}{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_DOW, 0, classify_dow(ts, te) };
|
|
}}
|
|
goto st40;
|
|
tr20:
|
|
{te = p+1;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_DOW, 0, classify_dow(ts, te) };
|
|
}}
|
|
goto st40;
|
|
tr27:
|
|
{{p = ((te))-1;}{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, 720 }; // M=+12
|
|
}}
|
|
goto st40;
|
|
tr29:
|
|
{{p = ((te))-1;}{
|
|
if (ntok < DATE_MAX_TOKENS) {
|
|
int c = toupper_ascii(ts[0]);
|
|
// N=-1, O=-2, ..., Y=-12
|
|
int off = -(c - 'N' + 1) * 60;
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, off };
|
|
}
|
|
}}
|
|
goto st40;
|
|
tr36:
|
|
{{p = ((te))-1;}{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_T, 0, 0 }; }}
|
|
goto st40;
|
|
tr38:
|
|
{{p = ((te))-1;}{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_W, 0, 0 }; }}
|
|
goto st40;
|
|
tr42:
|
|
{te = p+1;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_PLUS, 0, 0 }; }}
|
|
goto st40;
|
|
tr43:
|
|
{te = p+1;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_COMMA, 0, 0 }; }}
|
|
goto st40;
|
|
tr44:
|
|
{te = p+1;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_DASH, 0, 0 }; }}
|
|
goto st40;
|
|
tr45:
|
|
{te = p+1;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_DOT, 0, 0 }; }}
|
|
goto st40;
|
|
tr47:
|
|
{te = p+1;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_COLON, 0, 0 }; }}
|
|
goto st40;
|
|
tr55:
|
|
{te = p+1;{
|
|
if (ntok < DATE_MAX_TOKENS) {
|
|
int c = toupper_ascii(ts[0]);
|
|
// K=+10, L=+11
|
|
int off = (c - 'A') * 60; // K=10, L=11
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, off };
|
|
}
|
|
}}
|
|
goto st40;
|
|
tr60:
|
|
{te = p+1;{
|
|
if (ntok < DATE_MAX_TOKENS) {
|
|
int c = toupper_ascii(ts[0]);
|
|
// N=-1, O=-2, ..., Y=-12
|
|
int off = -(c - 'N' + 1) * 60;
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, off };
|
|
}
|
|
}}
|
|
goto st40;
|
|
tr66:
|
|
{te = p+1;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_Z, 0, 0 }; }}
|
|
goto st40;
|
|
tr67:
|
|
{te = p;p--;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_SPACE, 0, 0 }; }}
|
|
goto st40;
|
|
tr68:
|
|
{te = p;p--;{
|
|
int ndig = (int)(te - ts);
|
|
// Accumulate in 64 bits and saturate to INT_MAX so an over-long
|
|
// digit run cannot wrap a 32-bit int into a small or negative
|
|
// value that then slips past downstream range checks (e.g. a
|
|
// 10-digit "year" wrapping into a plausible one).
|
|
int64_t acc = 0;
|
|
for (const UTF8 *d = ts; d < te; d++)
|
|
{
|
|
acc = acc * 10 + (*d - '0');
|
|
if (acc > INT_MAX)
|
|
{
|
|
acc = INT_MAX;
|
|
break;
|
|
}
|
|
}
|
|
if (ndig > 255) ndig = 255; // nDigits is stored in a byte
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_NUM, (unsigned char)ndig, (int)acc };
|
|
}}
|
|
goto st40;
|
|
tr69:
|
|
{te = p;p--;{
|
|
if (ntok < DATE_MAX_TOKENS) {
|
|
int c = toupper_ascii(ts[0]);
|
|
// A=+1, B=+2, ..., I=+9
|
|
int off = (c - 'A' + 1) * 60;
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, off };
|
|
}
|
|
}}
|
|
goto st40;
|
|
tr72:
|
|
{te = p+1;{
|
|
int val = (ts[0] == 'p' || ts[0] == 'P') ? 12 : 0;
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_MERIDIAN, 0, val };
|
|
}}
|
|
goto st40;
|
|
tr75:
|
|
{te = p;p--;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_MONTH, 0, classify_month(ts, te) };
|
|
}}
|
|
goto st40;
|
|
tr83:
|
|
{te = p;p--;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_DOW, 0, classify_dow(ts, te) };
|
|
}}
|
|
goto st40;
|
|
tr84:
|
|
{te = p;p--;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, 720 }; // M=+12
|
|
}}
|
|
goto st40;
|
|
tr88:
|
|
{te = p;p--;{
|
|
if (ntok < DATE_MAX_TOKENS) {
|
|
int c = toupper_ascii(ts[0]);
|
|
// N=-1, O=-2, ..., Y=-12
|
|
int off = -(c - 'N' + 1) * 60;
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, off };
|
|
}
|
|
}}
|
|
goto st40;
|
|
tr89:
|
|
{te = p+1;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_SUFFIX, 0, 0 };
|
|
}}
|
|
goto st40;
|
|
tr96:
|
|
{te = p;p--;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_T, 0, 0 }; }}
|
|
goto st40;
|
|
tr99:
|
|
{te = p;p--;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_SUFFIX, 0, 0 };
|
|
}}
|
|
goto st40;
|
|
tr102:
|
|
{te = p;p--;{
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
toks[ntok++] = { DTT_TZ_NAME, 0, classify_tz(ts, te) };
|
|
}}
|
|
goto st40;
|
|
tr103:
|
|
{te = p;p--;{ if (ntok < DATE_MAX_TOKENS) toks[ntok++] = { DTT_W, 0, 0 }; }}
|
|
goto st40;
|
|
st40:
|
|
{ts = 0;}
|
|
if ( ++p == pe )
|
|
goto _test_eof40;
|
|
case 40:
|
|
{ts = p;}
|
|
switch( (*p) ) {
|
|
case 32u: goto st41;
|
|
case 43u: goto tr42;
|
|
case 44u: goto tr43;
|
|
case 45u: goto tr44;
|
|
case 46u: goto tr45;
|
|
case 58u: goto tr47;
|
|
case 65u: goto tr48;
|
|
case 67u: goto tr50;
|
|
case 68u: goto tr51;
|
|
case 69u: goto tr50;
|
|
case 70u: goto tr52;
|
|
case 71u: goto tr53;
|
|
case 74u: goto st23;
|
|
case 77u: goto tr56;
|
|
case 78u: goto tr57;
|
|
case 79u: goto tr58;
|
|
case 80u: goto tr59;
|
|
case 82u: goto st63;
|
|
case 83u: goto tr62;
|
|
case 84u: goto tr63;
|
|
case 85u: goto st71;
|
|
case 87u: goto tr65;
|
|
case 90u: goto tr66;
|
|
case 97u: goto tr48;
|
|
case 99u: goto tr50;
|
|
case 100u: goto tr51;
|
|
case 101u: goto tr50;
|
|
case 102u: goto tr52;
|
|
case 103u: goto tr53;
|
|
case 106u: goto st23;
|
|
case 109u: goto tr56;
|
|
case 110u: goto tr57;
|
|
case 111u: goto tr58;
|
|
case 112u: goto tr59;
|
|
case 114u: goto st63;
|
|
case 115u: goto tr62;
|
|
case 116u: goto tr63;
|
|
case 117u: goto st71;
|
|
case 119u: goto tr65;
|
|
case 122u: goto tr66;
|
|
}
|
|
if ( (*p) < 75u ) {
|
|
if ( (*p) < 48u ) {
|
|
if ( 9u <= (*p) && (*p) <= 13u )
|
|
goto st41;
|
|
} else if ( (*p) > 57u ) {
|
|
if ( 66u <= (*p) && (*p) <= 73u )
|
|
goto tr49;
|
|
} else
|
|
goto st42;
|
|
} else if ( (*p) > 76u ) {
|
|
if ( (*p) < 98u ) {
|
|
if ( 81u <= (*p) && (*p) <= 89u )
|
|
goto tr60;
|
|
} else if ( (*p) > 105u ) {
|
|
if ( (*p) > 108u ) {
|
|
if ( 113u <= (*p) && (*p) <= 121u )
|
|
goto tr60;
|
|
} else if ( (*p) >= 107u )
|
|
goto tr55;
|
|
} else
|
|
goto tr49;
|
|
} else
|
|
goto tr55;
|
|
goto st0;
|
|
st0:
|
|
cs = 0;
|
|
goto _out;
|
|
st41:
|
|
if ( ++p == pe )
|
|
goto _test_eof41;
|
|
case 41:
|
|
if ( (*p) == 32u )
|
|
goto st41;
|
|
if ( 9u <= (*p) && (*p) <= 13u )
|
|
goto st41;
|
|
goto tr67;
|
|
st42:
|
|
if ( ++p == pe )
|
|
goto _test_eof42;
|
|
case 42:
|
|
if ( 48u <= (*p) && (*p) <= 57u )
|
|
goto st42;
|
|
goto tr68;
|
|
tr48:
|
|
{te = p+1;}
|
|
{act = 10;}
|
|
goto st43;
|
|
st43:
|
|
if ( ++p == pe )
|
|
goto _test_eof43;
|
|
case 43:
|
|
switch( (*p) ) {
|
|
case 68u: goto st1;
|
|
case 69u: goto st2;
|
|
case 75u: goto st3;
|
|
case 77u: goto tr72;
|
|
case 80u: goto st4;
|
|
case 83u: goto st1;
|
|
case 85u: goto st6;
|
|
case 100u: goto st1;
|
|
case 101u: goto st2;
|
|
case 107u: goto st3;
|
|
case 109u: goto tr72;
|
|
case 112u: goto st4;
|
|
case 115u: goto st1;
|
|
case 117u: goto st6;
|
|
}
|
|
goto tr69;
|
|
st1:
|
|
if ( ++p == pe )
|
|
goto _test_eof1;
|
|
case 1:
|
|
switch( (*p) ) {
|
|
case 84u: goto tr1;
|
|
case 116u: goto tr1;
|
|
}
|
|
goto tr0;
|
|
st2:
|
|
if ( ++p == pe )
|
|
goto _test_eof2;
|
|
case 2:
|
|
switch( (*p) ) {
|
|
case 83u: goto st1;
|
|
case 115u: goto st1;
|
|
}
|
|
goto tr2;
|
|
st3:
|
|
if ( ++p == pe )
|
|
goto _test_eof3;
|
|
case 3:
|
|
switch( (*p) ) {
|
|
case 68u: goto st1;
|
|
case 83u: goto st1;
|
|
case 100u: goto st1;
|
|
case 115u: goto st1;
|
|
}
|
|
goto tr2;
|
|
st4:
|
|
if ( ++p == pe )
|
|
goto _test_eof4;
|
|
case 4:
|
|
switch( (*p) ) {
|
|
case 82u: goto tr4;
|
|
case 114u: goto tr4;
|
|
}
|
|
goto tr2;
|
|
tr4:
|
|
{te = p+1;}
|
|
goto st44;
|
|
st44:
|
|
if ( ++p == pe )
|
|
goto _test_eof44;
|
|
case 44:
|
|
switch( (*p) ) {
|
|
case 73u: goto st5;
|
|
case 105u: goto st5;
|
|
}
|
|
goto tr75;
|
|
st5:
|
|
if ( ++p == pe )
|
|
goto _test_eof5;
|
|
case 5:
|
|
switch( (*p) ) {
|
|
case 76u: goto tr6;
|
|
case 108u: goto tr6;
|
|
}
|
|
goto tr5;
|
|
st6:
|
|
if ( ++p == pe )
|
|
goto _test_eof6;
|
|
case 6:
|
|
switch( (*p) ) {
|
|
case 71u: goto tr7;
|
|
case 103u: goto tr7;
|
|
}
|
|
goto tr2;
|
|
tr7:
|
|
{te = p+1;}
|
|
goto st45;
|
|
st45:
|
|
if ( ++p == pe )
|
|
goto _test_eof45;
|
|
case 45:
|
|
switch( (*p) ) {
|
|
case 85u: goto st7;
|
|
case 117u: goto st7;
|
|
}
|
|
goto tr75;
|
|
st7:
|
|
if ( ++p == pe )
|
|
goto _test_eof7;
|
|
case 7:
|
|
switch( (*p) ) {
|
|
case 83u: goto st8;
|
|
case 115u: goto st8;
|
|
}
|
|
goto tr5;
|
|
st8:
|
|
if ( ++p == pe )
|
|
goto _test_eof8;
|
|
case 8:
|
|
switch( (*p) ) {
|
|
case 84u: goto tr6;
|
|
case 116u: goto tr6;
|
|
}
|
|
goto tr5;
|
|
tr49:
|
|
{te = p+1;}
|
|
{act = 10;}
|
|
goto st46;
|
|
st46:
|
|
if ( ++p == pe )
|
|
goto _test_eof46;
|
|
case 46:
|
|
switch( (*p) ) {
|
|
case 83u: goto st1;
|
|
case 115u: goto st1;
|
|
}
|
|
goto tr69;
|
|
tr50:
|
|
{te = p+1;}
|
|
{act = 10;}
|
|
goto st47;
|
|
st47:
|
|
if ( ++p == pe )
|
|
goto _test_eof47;
|
|
case 47:
|
|
switch( (*p) ) {
|
|
case 68u: goto st1;
|
|
case 69u: goto st9;
|
|
case 83u: goto st1;
|
|
case 100u: goto st1;
|
|
case 101u: goto st9;
|
|
case 115u: goto st1;
|
|
}
|
|
goto tr69;
|
|
st9:
|
|
if ( ++p == pe )
|
|
goto _test_eof9;
|
|
case 9:
|
|
switch( (*p) ) {
|
|
case 83u: goto st1;
|
|
case 84u: goto tr1;
|
|
case 115u: goto st1;
|
|
case 116u: goto tr1;
|
|
}
|
|
goto tr2;
|
|
tr51:
|
|
{te = p+1;}
|
|
goto st48;
|
|
st48:
|
|
if ( ++p == pe )
|
|
goto _test_eof48;
|
|
case 48:
|
|
switch( (*p) ) {
|
|
case 69u: goto st10;
|
|
case 101u: goto st10;
|
|
}
|
|
goto tr69;
|
|
st10:
|
|
if ( ++p == pe )
|
|
goto _test_eof10;
|
|
case 10:
|
|
switch( (*p) ) {
|
|
case 67u: goto tr9;
|
|
case 99u: goto tr9;
|
|
}
|
|
goto tr2;
|
|
tr9:
|
|
{te = p+1;}
|
|
goto st49;
|
|
st49:
|
|
if ( ++p == pe )
|
|
goto _test_eof49;
|
|
case 49:
|
|
switch( (*p) ) {
|
|
case 69u: goto st11;
|
|
case 101u: goto st11;
|
|
}
|
|
goto tr75;
|
|
st11:
|
|
if ( ++p == pe )
|
|
goto _test_eof11;
|
|
case 11:
|
|
switch( (*p) ) {
|
|
case 77u: goto st12;
|
|
case 109u: goto st12;
|
|
}
|
|
goto tr5;
|
|
st12:
|
|
if ( ++p == pe )
|
|
goto _test_eof12;
|
|
case 12:
|
|
switch( (*p) ) {
|
|
case 66u: goto st13;
|
|
case 98u: goto st13;
|
|
}
|
|
goto tr5;
|
|
st13:
|
|
if ( ++p == pe )
|
|
goto _test_eof13;
|
|
case 13:
|
|
switch( (*p) ) {
|
|
case 69u: goto st14;
|
|
case 101u: goto st14;
|
|
}
|
|
goto tr5;
|
|
st14:
|
|
if ( ++p == pe )
|
|
goto _test_eof14;
|
|
case 14:
|
|
switch( (*p) ) {
|
|
case 82u: goto tr6;
|
|
case 114u: goto tr6;
|
|
}
|
|
goto tr5;
|
|
tr52:
|
|
{te = p+1;}
|
|
goto st50;
|
|
st50:
|
|
if ( ++p == pe )
|
|
goto _test_eof50;
|
|
case 50:
|
|
switch( (*p) ) {
|
|
case 69u: goto st15;
|
|
case 82u: goto st20;
|
|
case 101u: goto st15;
|
|
case 114u: goto st20;
|
|
}
|
|
goto tr69;
|
|
st15:
|
|
if ( ++p == pe )
|
|
goto _test_eof15;
|
|
case 15:
|
|
switch( (*p) ) {
|
|
case 66u: goto tr13;
|
|
case 98u: goto tr13;
|
|
}
|
|
goto tr2;
|
|
tr13:
|
|
{te = p+1;}
|
|
goto st51;
|
|
st51:
|
|
if ( ++p == pe )
|
|
goto _test_eof51;
|
|
case 51:
|
|
switch( (*p) ) {
|
|
case 82u: goto st16;
|
|
case 114u: goto st16;
|
|
}
|
|
goto tr75;
|
|
st16:
|
|
if ( ++p == pe )
|
|
goto _test_eof16;
|
|
case 16:
|
|
switch( (*p) ) {
|
|
case 85u: goto st17;
|
|
case 117u: goto st17;
|
|
}
|
|
goto tr5;
|
|
st17:
|
|
if ( ++p == pe )
|
|
goto _test_eof17;
|
|
case 17:
|
|
switch( (*p) ) {
|
|
case 65u: goto st18;
|
|
case 97u: goto st18;
|
|
}
|
|
goto tr5;
|
|
st18:
|
|
if ( ++p == pe )
|
|
goto _test_eof18;
|
|
case 18:
|
|
switch( (*p) ) {
|
|
case 82u: goto st19;
|
|
case 114u: goto st19;
|
|
}
|
|
goto tr5;
|
|
st19:
|
|
if ( ++p == pe )
|
|
goto _test_eof19;
|
|
case 19:
|
|
switch( (*p) ) {
|
|
case 89u: goto tr6;
|
|
case 121u: goto tr6;
|
|
}
|
|
goto tr5;
|
|
st20:
|
|
if ( ++p == pe )
|
|
goto _test_eof20;
|
|
case 20:
|
|
switch( (*p) ) {
|
|
case 73u: goto tr17;
|
|
case 105u: goto tr17;
|
|
}
|
|
goto tr2;
|
|
tr17:
|
|
{te = p+1;}
|
|
goto st52;
|
|
st52:
|
|
if ( ++p == pe )
|
|
goto _test_eof52;
|
|
case 52:
|
|
switch( (*p) ) {
|
|
case 68u: goto st21;
|
|
case 100u: goto st21;
|
|
}
|
|
goto tr83;
|
|
st21:
|
|
if ( ++p == pe )
|
|
goto _test_eof21;
|
|
case 21:
|
|
switch( (*p) ) {
|
|
case 65u: goto st22;
|
|
case 97u: goto st22;
|
|
}
|
|
goto tr18;
|
|
st22:
|
|
if ( ++p == pe )
|
|
goto _test_eof22;
|
|
case 22:
|
|
switch( (*p) ) {
|
|
case 89u: goto tr20;
|
|
case 121u: goto tr20;
|
|
}
|
|
goto tr18;
|
|
tr53:
|
|
{te = p+1;}
|
|
{act = 10;}
|
|
goto st53;
|
|
st53:
|
|
if ( ++p == pe )
|
|
goto _test_eof53;
|
|
case 53:
|
|
switch( (*p) ) {
|
|
case 77u: goto st1;
|
|
case 109u: goto st1;
|
|
}
|
|
goto tr69;
|
|
st23:
|
|
if ( ++p == pe )
|
|
goto _test_eof23;
|
|
case 23:
|
|
switch( (*p) ) {
|
|
case 65u: goto st24;
|
|
case 85u: goto st25;
|
|
case 97u: goto st24;
|
|
case 117u: goto st25;
|
|
}
|
|
goto st0;
|
|
st24:
|
|
if ( ++p == pe )
|
|
goto _test_eof24;
|
|
case 24:
|
|
switch( (*p) ) {
|
|
case 78u: goto tr24;
|
|
case 110u: goto tr24;
|
|
}
|
|
goto st0;
|
|
tr24:
|
|
{te = p+1;}
|
|
goto st54;
|
|
st54:
|
|
if ( ++p == pe )
|
|
goto _test_eof54;
|
|
case 54:
|
|
switch( (*p) ) {
|
|
case 85u: goto st17;
|
|
case 117u: goto st17;
|
|
}
|
|
goto tr75;
|
|
st25:
|
|
if ( ++p == pe )
|
|
goto _test_eof25;
|
|
case 25:
|
|
switch( (*p) ) {
|
|
case 76u: goto st55;
|
|
case 78u: goto st56;
|
|
case 108u: goto st55;
|
|
case 110u: goto st56;
|
|
}
|
|
goto st0;
|
|
st55:
|
|
if ( ++p == pe )
|
|
goto _test_eof55;
|
|
case 55:
|
|
switch( (*p) ) {
|
|
case 89u: goto tr6;
|
|
case 121u: goto tr6;
|
|
}
|
|
goto tr75;
|
|
st56:
|
|
if ( ++p == pe )
|
|
goto _test_eof56;
|
|
case 56:
|
|
switch( (*p) ) {
|
|
case 69u: goto tr6;
|
|
case 101u: goto tr6;
|
|
}
|
|
goto tr75;
|
|
tr56:
|
|
{te = p+1;}
|
|
{act = 12;}
|
|
goto st57;
|
|
st57:
|
|
if ( ++p == pe )
|
|
goto _test_eof57;
|
|
case 57:
|
|
switch( (*p) ) {
|
|
case 65u: goto st26;
|
|
case 68u: goto st1;
|
|
case 79u: goto st28;
|
|
case 83u: goto st1;
|
|
case 97u: goto st26;
|
|
case 100u: goto st1;
|
|
case 111u: goto st28;
|
|
case 115u: goto st1;
|
|
}
|
|
goto tr84;
|
|
st26:
|
|
if ( ++p == pe )
|
|
goto _test_eof26;
|
|
case 26:
|
|
switch( (*p) ) {
|
|
case 82u: goto tr28;
|
|
case 89u: goto tr6;
|
|
case 114u: goto tr28;
|
|
case 121u: goto tr6;
|
|
}
|
|
goto tr27;
|
|
tr28:
|
|
{te = p+1;}
|
|
goto st58;
|
|
st58:
|
|
if ( ++p == pe )
|
|
goto _test_eof58;
|
|
case 58:
|
|
switch( (*p) ) {
|
|
case 67u: goto st27;
|
|
case 99u: goto st27;
|
|
}
|
|
goto tr75;
|
|
st27:
|
|
if ( ++p == pe )
|
|
goto _test_eof27;
|
|
case 27:
|
|
switch( (*p) ) {
|
|
case 72u: goto tr6;
|
|
case 104u: goto tr6;
|
|
}
|
|
goto tr5;
|
|
st28:
|
|
if ( ++p == pe )
|
|
goto _test_eof28;
|
|
case 28:
|
|
switch( (*p) ) {
|
|
case 78u: goto tr17;
|
|
case 110u: goto tr17;
|
|
}
|
|
goto tr0;
|
|
tr57:
|
|
{te = p+1;}
|
|
goto st59;
|
|
st59:
|
|
if ( ++p == pe )
|
|
goto _test_eof59;
|
|
case 59:
|
|
switch( (*p) ) {
|
|
case 68u: goto tr89;
|
|
case 79u: goto st29;
|
|
case 100u: goto tr89;
|
|
case 111u: goto st29;
|
|
}
|
|
goto tr88;
|
|
st29:
|
|
if ( ++p == pe )
|
|
goto _test_eof29;
|
|
case 29:
|
|
switch( (*p) ) {
|
|
case 86u: goto tr9;
|
|
case 118u: goto tr9;
|
|
}
|
|
goto tr29;
|
|
tr58:
|
|
{te = p+1;}
|
|
goto st60;
|
|
st60:
|
|
if ( ++p == pe )
|
|
goto _test_eof60;
|
|
case 60:
|
|
switch( (*p) ) {
|
|
case 67u: goto st30;
|
|
case 99u: goto st30;
|
|
}
|
|
goto tr88;
|
|
st30:
|
|
if ( ++p == pe )
|
|
goto _test_eof30;
|
|
case 30:
|
|
switch( (*p) ) {
|
|
case 84u: goto tr30;
|
|
case 116u: goto tr30;
|
|
}
|
|
goto tr29;
|
|
tr30:
|
|
{te = p+1;}
|
|
goto st61;
|
|
st61:
|
|
if ( ++p == pe )
|
|
goto _test_eof61;
|
|
case 61:
|
|
switch( (*p) ) {
|
|
case 79u: goto st12;
|
|
case 111u: goto st12;
|
|
}
|
|
goto tr75;
|
|
tr59:
|
|
{te = p+1;}
|
|
{act = 13;}
|
|
goto st62;
|
|
st62:
|
|
if ( ++p == pe )
|
|
goto _test_eof62;
|
|
case 62:
|
|
switch( (*p) ) {
|
|
case 68u: goto st1;
|
|
case 77u: goto tr72;
|
|
case 83u: goto st1;
|
|
case 100u: goto st1;
|
|
case 109u: goto tr72;
|
|
case 115u: goto st1;
|
|
}
|
|
goto tr88;
|
|
st63:
|
|
if ( ++p == pe )
|
|
goto _test_eof63;
|
|
case 63:
|
|
switch( (*p) ) {
|
|
case 68u: goto tr89;
|
|
case 100u: goto tr89;
|
|
}
|
|
goto tr88;
|
|
tr62:
|
|
{te = p+1;}
|
|
{act = 13;}
|
|
goto st64;
|
|
st64:
|
|
if ( ++p == pe )
|
|
goto _test_eof64;
|
|
case 64:
|
|
switch( (*p) ) {
|
|
case 65u: goto st31;
|
|
case 69u: goto st34;
|
|
case 84u: goto tr89;
|
|
case 85u: goto st28;
|
|
case 97u: goto st31;
|
|
case 101u: goto st34;
|
|
case 116u: goto tr89;
|
|
case 117u: goto st28;
|
|
}
|
|
goto tr88;
|
|
st31:
|
|
if ( ++p == pe )
|
|
goto _test_eof31;
|
|
case 31:
|
|
switch( (*p) ) {
|
|
case 84u: goto tr31;
|
|
case 116u: goto tr31;
|
|
}
|
|
goto tr29;
|
|
tr31:
|
|
{te = p+1;}
|
|
goto st65;
|
|
st65:
|
|
if ( ++p == pe )
|
|
goto _test_eof65;
|
|
case 65:
|
|
switch( (*p) ) {
|
|
case 85u: goto st32;
|
|
case 117u: goto st32;
|
|
}
|
|
goto tr83;
|
|
st32:
|
|
if ( ++p == pe )
|
|
goto _test_eof32;
|
|
case 32:
|
|
switch( (*p) ) {
|
|
case 82u: goto st33;
|
|
case 114u: goto st33;
|
|
}
|
|
goto tr18;
|
|
st33:
|
|
if ( ++p == pe )
|
|
goto _test_eof33;
|
|
case 33:
|
|
switch( (*p) ) {
|
|
case 68u: goto st21;
|
|
case 100u: goto st21;
|
|
}
|
|
goto tr18;
|
|
st34:
|
|
if ( ++p == pe )
|
|
goto _test_eof34;
|
|
case 34:
|
|
switch( (*p) ) {
|
|
case 80u: goto tr34;
|
|
case 112u: goto tr34;
|
|
}
|
|
goto tr29;
|
|
tr34:
|
|
{te = p+1;}
|
|
goto st66;
|
|
st66:
|
|
if ( ++p == pe )
|
|
goto _test_eof66;
|
|
case 66:
|
|
switch( (*p) ) {
|
|
case 84u: goto st35;
|
|
case 116u: goto st35;
|
|
}
|
|
goto tr75;
|
|
st35:
|
|
if ( ++p == pe )
|
|
goto _test_eof35;
|
|
case 35:
|
|
switch( (*p) ) {
|
|
case 69u: goto st11;
|
|
case 101u: goto st11;
|
|
}
|
|
goto tr5;
|
|
tr63:
|
|
{te = p+1;}
|
|
goto st67;
|
|
st67:
|
|
if ( ++p == pe )
|
|
goto _test_eof67;
|
|
case 67:
|
|
switch( (*p) ) {
|
|
case 72u: goto st68;
|
|
case 85u: goto st37;
|
|
case 104u: goto st68;
|
|
case 117u: goto st37;
|
|
}
|
|
goto tr96;
|
|
st68:
|
|
if ( ++p == pe )
|
|
goto _test_eof68;
|
|
case 68:
|
|
switch( (*p) ) {
|
|
case 85u: goto tr100;
|
|
case 117u: goto tr100;
|
|
}
|
|
goto tr99;
|
|
tr100:
|
|
{te = p+1;}
|
|
goto st69;
|
|
st69:
|
|
if ( ++p == pe )
|
|
goto _test_eof69;
|
|
case 69:
|
|
switch( (*p) ) {
|
|
case 82u: goto st36;
|
|
case 114u: goto st36;
|
|
}
|
|
goto tr83;
|
|
st36:
|
|
if ( ++p == pe )
|
|
goto _test_eof36;
|
|
case 36:
|
|
switch( (*p) ) {
|
|
case 83u: goto st33;
|
|
case 115u: goto st33;
|
|
}
|
|
goto tr18;
|
|
st37:
|
|
if ( ++p == pe )
|
|
goto _test_eof37;
|
|
case 37:
|
|
switch( (*p) ) {
|
|
case 69u: goto tr37;
|
|
case 101u: goto tr37;
|
|
}
|
|
goto tr36;
|
|
tr37:
|
|
{te = p+1;}
|
|
goto st70;
|
|
st70:
|
|
if ( ++p == pe )
|
|
goto _test_eof70;
|
|
case 70:
|
|
switch( (*p) ) {
|
|
case 83u: goto st33;
|
|
case 115u: goto st33;
|
|
}
|
|
goto tr83;
|
|
st71:
|
|
if ( ++p == pe )
|
|
goto _test_eof71;
|
|
case 71:
|
|
switch( (*p) ) {
|
|
case 84u: goto st72;
|
|
case 116u: goto st72;
|
|
}
|
|
goto tr88;
|
|
st72:
|
|
if ( ++p == pe )
|
|
goto _test_eof72;
|
|
case 72:
|
|
switch( (*p) ) {
|
|
case 67u: goto tr1;
|
|
case 99u: goto tr1;
|
|
}
|
|
goto tr102;
|
|
tr65:
|
|
{te = p+1;}
|
|
goto st73;
|
|
st73:
|
|
if ( ++p == pe )
|
|
goto _test_eof73;
|
|
case 73:
|
|
switch( (*p) ) {
|
|
case 69u: goto st38;
|
|
case 101u: goto st38;
|
|
}
|
|
goto tr103;
|
|
st38:
|
|
if ( ++p == pe )
|
|
goto _test_eof38;
|
|
case 38:
|
|
switch( (*p) ) {
|
|
case 68u: goto tr39;
|
|
case 100u: goto tr39;
|
|
}
|
|
goto tr38;
|
|
tr39:
|
|
{te = p+1;}
|
|
goto st74;
|
|
st74:
|
|
if ( ++p == pe )
|
|
goto _test_eof74;
|
|
case 74:
|
|
switch( (*p) ) {
|
|
case 78u: goto st39;
|
|
case 110u: goto st39;
|
|
}
|
|
goto tr83;
|
|
st39:
|
|
if ( ++p == pe )
|
|
goto _test_eof39;
|
|
case 39:
|
|
switch( (*p) ) {
|
|
case 69u: goto st36;
|
|
case 101u: goto st36;
|
|
}
|
|
goto tr18;
|
|
}
|
|
_test_eof40: cs = 40; goto _test_eof;
|
|
_test_eof41: cs = 41; goto _test_eof;
|
|
_test_eof42: cs = 42; goto _test_eof;
|
|
_test_eof43: cs = 43; goto _test_eof;
|
|
_test_eof1: cs = 1; goto _test_eof;
|
|
_test_eof2: cs = 2; goto _test_eof;
|
|
_test_eof3: cs = 3; goto _test_eof;
|
|
_test_eof4: cs = 4; goto _test_eof;
|
|
_test_eof44: cs = 44; goto _test_eof;
|
|
_test_eof5: cs = 5; goto _test_eof;
|
|
_test_eof6: cs = 6; goto _test_eof;
|
|
_test_eof45: cs = 45; goto _test_eof;
|
|
_test_eof7: cs = 7; goto _test_eof;
|
|
_test_eof8: cs = 8; goto _test_eof;
|
|
_test_eof46: cs = 46; goto _test_eof;
|
|
_test_eof47: cs = 47; goto _test_eof;
|
|
_test_eof9: cs = 9; goto _test_eof;
|
|
_test_eof48: cs = 48; goto _test_eof;
|
|
_test_eof10: cs = 10; goto _test_eof;
|
|
_test_eof49: cs = 49; goto _test_eof;
|
|
_test_eof11: cs = 11; goto _test_eof;
|
|
_test_eof12: cs = 12; goto _test_eof;
|
|
_test_eof13: cs = 13; goto _test_eof;
|
|
_test_eof14: cs = 14; goto _test_eof;
|
|
_test_eof50: cs = 50; goto _test_eof;
|
|
_test_eof15: cs = 15; goto _test_eof;
|
|
_test_eof51: cs = 51; goto _test_eof;
|
|
_test_eof16: cs = 16; goto _test_eof;
|
|
_test_eof17: cs = 17; goto _test_eof;
|
|
_test_eof18: cs = 18; goto _test_eof;
|
|
_test_eof19: cs = 19; goto _test_eof;
|
|
_test_eof20: cs = 20; goto _test_eof;
|
|
_test_eof52: cs = 52; goto _test_eof;
|
|
_test_eof21: cs = 21; goto _test_eof;
|
|
_test_eof22: cs = 22; goto _test_eof;
|
|
_test_eof53: cs = 53; goto _test_eof;
|
|
_test_eof23: cs = 23; goto _test_eof;
|
|
_test_eof24: cs = 24; goto _test_eof;
|
|
_test_eof54: cs = 54; goto _test_eof;
|
|
_test_eof25: cs = 25; goto _test_eof;
|
|
_test_eof55: cs = 55; goto _test_eof;
|
|
_test_eof56: cs = 56; goto _test_eof;
|
|
_test_eof57: cs = 57; goto _test_eof;
|
|
_test_eof26: cs = 26; goto _test_eof;
|
|
_test_eof58: cs = 58; goto _test_eof;
|
|
_test_eof27: cs = 27; goto _test_eof;
|
|
_test_eof28: cs = 28; goto _test_eof;
|
|
_test_eof59: cs = 59; goto _test_eof;
|
|
_test_eof29: cs = 29; goto _test_eof;
|
|
_test_eof60: cs = 60; goto _test_eof;
|
|
_test_eof30: cs = 30; goto _test_eof;
|
|
_test_eof61: cs = 61; goto _test_eof;
|
|
_test_eof62: cs = 62; goto _test_eof;
|
|
_test_eof63: cs = 63; goto _test_eof;
|
|
_test_eof64: cs = 64; goto _test_eof;
|
|
_test_eof31: cs = 31; goto _test_eof;
|
|
_test_eof65: cs = 65; goto _test_eof;
|
|
_test_eof32: cs = 32; goto _test_eof;
|
|
_test_eof33: cs = 33; goto _test_eof;
|
|
_test_eof34: cs = 34; goto _test_eof;
|
|
_test_eof66: cs = 66; goto _test_eof;
|
|
_test_eof35: cs = 35; goto _test_eof;
|
|
_test_eof67: cs = 67; goto _test_eof;
|
|
_test_eof68: cs = 68; goto _test_eof;
|
|
_test_eof69: cs = 69; goto _test_eof;
|
|
_test_eof36: cs = 36; goto _test_eof;
|
|
_test_eof37: cs = 37; goto _test_eof;
|
|
_test_eof70: cs = 70; goto _test_eof;
|
|
_test_eof71: cs = 71; goto _test_eof;
|
|
_test_eof72: cs = 72; goto _test_eof;
|
|
_test_eof73: cs = 73; goto _test_eof;
|
|
_test_eof38: cs = 38; goto _test_eof;
|
|
_test_eof74: cs = 74; goto _test_eof;
|
|
_test_eof39: cs = 39; goto _test_eof;
|
|
|
|
_test_eof: {}
|
|
if ( p == eof )
|
|
{
|
|
switch ( cs ) {
|
|
case 41: goto tr67;
|
|
case 42: goto tr68;
|
|
case 43: goto tr69;
|
|
case 1: goto tr0;
|
|
case 2: goto tr2;
|
|
case 3: goto tr2;
|
|
case 4: goto tr2;
|
|
case 44: goto tr75;
|
|
case 5: goto tr5;
|
|
case 6: goto tr2;
|
|
case 45: goto tr75;
|
|
case 7: goto tr5;
|
|
case 8: goto tr5;
|
|
case 46: goto tr69;
|
|
case 47: goto tr69;
|
|
case 9: goto tr2;
|
|
case 48: goto tr69;
|
|
case 10: goto tr2;
|
|
case 49: goto tr75;
|
|
case 11: goto tr5;
|
|
case 12: goto tr5;
|
|
case 13: goto tr5;
|
|
case 14: goto tr5;
|
|
case 50: goto tr69;
|
|
case 15: goto tr2;
|
|
case 51: goto tr75;
|
|
case 16: goto tr5;
|
|
case 17: goto tr5;
|
|
case 18: goto tr5;
|
|
case 19: goto tr5;
|
|
case 20: goto tr2;
|
|
case 52: goto tr83;
|
|
case 21: goto tr18;
|
|
case 22: goto tr18;
|
|
case 53: goto tr69;
|
|
case 54: goto tr75;
|
|
case 55: goto tr75;
|
|
case 56: goto tr75;
|
|
case 57: goto tr84;
|
|
case 26: goto tr27;
|
|
case 58: goto tr75;
|
|
case 27: goto tr5;
|
|
case 28: goto tr0;
|
|
case 59: goto tr88;
|
|
case 29: goto tr29;
|
|
case 60: goto tr88;
|
|
case 30: goto tr29;
|
|
case 61: goto tr75;
|
|
case 62: goto tr88;
|
|
case 63: goto tr88;
|
|
case 64: goto tr88;
|
|
case 31: goto tr29;
|
|
case 65: goto tr83;
|
|
case 32: goto tr18;
|
|
case 33: goto tr18;
|
|
case 34: goto tr29;
|
|
case 66: goto tr75;
|
|
case 35: goto tr5;
|
|
case 67: goto tr96;
|
|
case 68: goto tr99;
|
|
case 69: goto tr83;
|
|
case 36: goto tr18;
|
|
case 37: goto tr36;
|
|
case 70: goto tr83;
|
|
case 71: goto tr88;
|
|
case 72: goto tr102;
|
|
case 73: goto tr103;
|
|
case 38: goto tr38;
|
|
case 74: goto tr83;
|
|
case 39: goto tr18;
|
|
}
|
|
}
|
|
|
|
_out: {}
|
|
}
|
|
|
|
|
|
if (cs == date_scanner_error)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Append EOF sentinel.
|
|
//
|
|
if (ntok < DATE_MAX_TOKENS)
|
|
{
|
|
toks[ntok++] = { DTT_EOF, 0, 0 };
|
|
}
|
|
return ntok;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Recursive descent parser
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Parsed date result.
|
|
//
|
|
struct DateResult
|
|
{
|
|
int iYear;
|
|
int iMonth; // 1-12
|
|
int iDayOfMonth; // 1-31
|
|
int iDayOfYear; // 1-366 (0 = not specified)
|
|
int iWeekOfYear; // 1-53 (0 = not specified)
|
|
int iDayOfWeek; // 0-6 (-1 = not specified)
|
|
int iHour;
|
|
int iMinute;
|
|
int iSecond;
|
|
int iFracHectoNano; // 0..9999999 (100ns units)
|
|
int iTzMinutes; // timezone offset in minutes (INT_MIN = not specified)
|
|
bool bHasTime;
|
|
};
|
|
|
|
#define TZ_NOT_SPECIFIED INT_MIN
|
|
|
|
static void date_result_init(DateResult *r)
|
|
{
|
|
memset(r, 0, sizeof(*r));
|
|
r->iDayOfWeek = -1;
|
|
r->iTzMinutes = TZ_NOT_SPECIFIED;
|
|
}
|
|
|
|
// Token stream cursor.
|
|
//
|
|
struct TokCursor
|
|
{
|
|
const DateTok *toks;
|
|
int pos;
|
|
int ntok;
|
|
};
|
|
|
|
static inline const DateTok *peek(const TokCursor *c)
|
|
{
|
|
return (c->pos < c->ntok) ? &c->toks[c->pos] : nullptr;
|
|
}
|
|
|
|
static inline const DateTok *peek2(const TokCursor *c)
|
|
{
|
|
return (c->pos + 1 < c->ntok) ? &c->toks[c->pos + 1] : nullptr;
|
|
}
|
|
|
|
static inline const DateTok *consume(TokCursor *c, DateTokType expected)
|
|
{
|
|
const DateTok *t = peek(c);
|
|
if (t && t->type == expected)
|
|
{
|
|
c->pos++;
|
|
return t;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static inline void skip_spaces(TokCursor *c)
|
|
{
|
|
while (peek(c) && peek(c)->type == DTT_SPACE)
|
|
{
|
|
c->pos++;
|
|
}
|
|
}
|
|
|
|
static inline bool at_end(const TokCursor *c)
|
|
{
|
|
const DateTok *t = peek(c);
|
|
return !t || t->type == DTT_EOF;
|
|
}
|
|
|
|
// Parse fractional seconds: DOT + NUM → hectonanoseconds.
|
|
//
|
|
static bool parse_frac(TokCursor *c, DateResult *r)
|
|
{
|
|
if (!consume(c, DTT_DOT))
|
|
{
|
|
return false;
|
|
}
|
|
const DateTok *t = consume(c, DTT_NUM);
|
|
if (!t)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Right-pad to 7 digits (100ns resolution).
|
|
//
|
|
int val = t->iVal;
|
|
int ndig = t->nDigits;
|
|
if (ndig > 7)
|
|
{
|
|
// Truncate excess digits.
|
|
while (ndig > 7)
|
|
{
|
|
val /= 10;
|
|
ndig--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (ndig < 7)
|
|
{
|
|
val *= 10;
|
|
ndig++;
|
|
}
|
|
}
|
|
r->iFracHectoNano = val;
|
|
return true;
|
|
}
|
|
|
|
// Parse timezone: Z | named | +/-HHMM | +/-HH:MM
|
|
//
|
|
static bool parse_timezone(TokCursor *c, DateResult *r)
|
|
{
|
|
skip_spaces(c);
|
|
const DateTok *t = peek(c);
|
|
if (!t || t->type == DTT_EOF)
|
|
{
|
|
return true; // no timezone is OK
|
|
}
|
|
|
|
if (t->type == DTT_Z)
|
|
{
|
|
c->pos++;
|
|
r->iTzMinutes = 0;
|
|
return true;
|
|
}
|
|
|
|
if (t->type == DTT_TZ_NAME)
|
|
{
|
|
c->pos++;
|
|
r->iTzMinutes = t->iVal;
|
|
return true;
|
|
}
|
|
|
|
// Military timezone letters T and W — these are also DTT_T/DTT_W
|
|
// but in timezone position (after time), they're timezone indicators.
|
|
//
|
|
if (t->type == DTT_T)
|
|
{
|
|
c->pos++;
|
|
r->iTzMinutes = -420; // T = UTC-7
|
|
return true;
|
|
}
|
|
|
|
if (t->type == DTT_W)
|
|
{
|
|
c->pos++;
|
|
r->iTzMinutes = -600; // W = UTC-10
|
|
return true;
|
|
}
|
|
|
|
if (t->type == DTT_PLUS || t->type == DTT_DASH)
|
|
{
|
|
int sign = (t->type == DTT_PLUS) ? 1 : -1;
|
|
c->pos++;
|
|
|
|
const DateTok *n = consume(c, DTT_NUM);
|
|
if (!n)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int hh, mm;
|
|
if (n->nDigits == 4)
|
|
{
|
|
// +HHMM
|
|
hh = n->iVal / 100;
|
|
mm = n->iVal % 100;
|
|
}
|
|
else if (n->nDigits == 2)
|
|
{
|
|
// +HH:MM or +HH
|
|
hh = n->iVal;
|
|
mm = 0;
|
|
if (consume(c, DTT_COLON))
|
|
{
|
|
const DateTok *m = consume(c, DTT_NUM);
|
|
if (!m || m->nDigits != 2)
|
|
{
|
|
return false;
|
|
}
|
|
mm = m->iVal;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (hh > 23 || mm > 59)
|
|
{
|
|
return false;
|
|
}
|
|
r->iTzMinutes = sign * (hh * 60 + mm);
|
|
return true;
|
|
}
|
|
|
|
// Unknown token — might be trailing garbage.
|
|
return true;
|
|
}
|
|
|
|
// Parse time part: [T|space] HH:MM[:SS[.frac]] [meridian] [TZ]
|
|
//
|
|
static bool parse_time(TokCursor *c, DateResult *r)
|
|
{
|
|
// Optional T or space separator.
|
|
if (!consume(c, DTT_T))
|
|
{
|
|
skip_spaces(c);
|
|
}
|
|
|
|
const DateTok *t = peek(c);
|
|
if (!t || t->type != DTT_NUM)
|
|
{
|
|
// No time component. That's OK for date-only.
|
|
r->bHasTime = false;
|
|
return parse_timezone(c, r);
|
|
}
|
|
|
|
// Hour — or compact HHMM / HHMMSS
|
|
const DateTok *hh = consume(c, DTT_NUM);
|
|
if (!hh)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (hh->nDigits == 6)
|
|
{
|
|
// Compact HHMMSS
|
|
r->iHour = hh->iVal / 10000;
|
|
r->iMinute = (hh->iVal / 100) % 100;
|
|
r->iSecond = hh->iVal % 100;
|
|
}
|
|
else if (hh->nDigits == 4)
|
|
{
|
|
// Compact HHMM
|
|
r->iHour = hh->iVal / 100;
|
|
r->iMinute = hh->iVal % 100;
|
|
r->iSecond = 0;
|
|
}
|
|
else if (hh->nDigits <= 2)
|
|
{
|
|
// Extended: HH:MM[:SS]
|
|
r->iHour = hh->iVal;
|
|
|
|
if (consume(c, DTT_COLON))
|
|
{
|
|
const DateTok *mm = consume(c, DTT_NUM);
|
|
if (!mm || mm->nDigits != 2)
|
|
{
|
|
return false;
|
|
}
|
|
r->iMinute = mm->iVal;
|
|
|
|
if (consume(c, DTT_COLON))
|
|
{
|
|
const DateTok *ss = consume(c, DTT_NUM);
|
|
if (!ss || ss->nDigits != 2)
|
|
{
|
|
return false;
|
|
}
|
|
r->iSecond = ss->iVal;
|
|
|
|
if (peek(c) && peek(c)->type == DTT_DOT)
|
|
{
|
|
parse_frac(c, r);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Optional .frac (for compact forms)
|
|
if (peek(c) && peek(c)->type == DTT_DOT)
|
|
{
|
|
parse_frac(c, r);
|
|
}
|
|
|
|
r->bHasTime = true;
|
|
|
|
// Validate basic time ranges.
|
|
if (r->iHour > 23 || r->iMinute > 59 || r->iSecond > 59)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Optional meridian.
|
|
const DateTok *mer = peek(c);
|
|
if (mer && mer->type == DTT_MERIDIAN)
|
|
{
|
|
c->pos++;
|
|
if (r->iHour == 0 || r->iHour > 12)
|
|
{
|
|
return false;
|
|
}
|
|
if (r->iHour == 12)
|
|
{
|
|
r->iHour = 0;
|
|
}
|
|
r->iHour += mer->iVal;
|
|
}
|
|
|
|
return parse_timezone(c, r);
|
|
}
|
|
|
|
// Parse compact time: HHMMSS[.frac][TZ] (no colons, for ISO basic).
|
|
//
|
|
static bool parse_compact_time(TokCursor *c, DateResult *r)
|
|
{
|
|
if (!consume(c, DTT_T))
|
|
{
|
|
skip_spaces(c);
|
|
}
|
|
|
|
const DateTok *t = peek(c);
|
|
if (!t || t->type != DTT_NUM)
|
|
{
|
|
r->bHasTime = false;
|
|
return parse_timezone(c, r);
|
|
}
|
|
|
|
const DateTok *hms = consume(c, DTT_NUM);
|
|
if (!hms)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (hms->nDigits == 6)
|
|
{
|
|
r->iHour = hms->iVal / 10000;
|
|
r->iMinute = (hms->iVal / 100) % 100;
|
|
r->iSecond = hms->iVal % 100;
|
|
}
|
|
else if (hms->nDigits == 4)
|
|
{
|
|
r->iHour = hms->iVal / 100;
|
|
r->iMinute = hms->iVal % 100;
|
|
r->iSecond = 0;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
r->bHasTime = true;
|
|
|
|
if (r->iHour > 23 || r->iMinute > 59 || r->iSecond > 59)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Optional .frac
|
|
if (peek(c) && peek(c)->type == DTT_DOT)
|
|
{
|
|
parse_frac(c, r);
|
|
}
|
|
|
|
return parse_timezone(c, r);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Format dispatchers
|
|
// -----------------------------------------------------------------------
|
|
|
|
// ISO 8601 family: leading 4+ digit year (or signed year).
|
|
//
|
|
static bool parse_iso(TokCursor *c, DateResult *r)
|
|
{
|
|
// Year (already validated as 4+ digits by caller).
|
|
const DateTok *yr = consume(c, DTT_NUM);
|
|
if (!yr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Handle compact forms where digits run together:
|
|
// 8 digits = YYYYMMDD, 7 digits = YYYYDDD
|
|
//
|
|
if (yr->nDigits == 8)
|
|
{
|
|
r->iYear = yr->iVal / 10000;
|
|
r->iMonth = (yr->iVal / 100) % 100;
|
|
r->iDayOfMonth = yr->iVal % 100;
|
|
if (r->iMonth < 1 || r->iMonth > 12 || r->iDayOfMonth < 1 || r->iDayOfMonth > 31)
|
|
{
|
|
return false;
|
|
}
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
if (yr->nDigits == 7)
|
|
{
|
|
r->iYear = yr->iVal / 1000;
|
|
r->iDayOfYear = yr->iVal % 1000;
|
|
if (r->iDayOfYear < 1 || r->iDayOfYear > 366)
|
|
{
|
|
return false;
|
|
}
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
r->iYear = yr->iVal;
|
|
|
|
const DateTok *next = peek(c);
|
|
if (!next)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (next->type == DTT_W)
|
|
{
|
|
// ISO week date: YYYYWww-D or YYYYWwwD
|
|
c->pos++;
|
|
const DateTok *wk = consume(c, DTT_NUM);
|
|
if (!wk || wk->iVal < 1 || wk->iVal > 53)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (wk->nDigits == 2)
|
|
{
|
|
r->iWeekOfYear = wk->iVal;
|
|
|
|
if (consume(c, DTT_DASH))
|
|
{
|
|
const DateTok *dw = consume(c, DTT_NUM);
|
|
if (!dw || dw->nDigits != 1 || dw->iVal < 1 || dw->iVal > 7)
|
|
{
|
|
return false;
|
|
}
|
|
r->iDayOfWeek = dw->iVal % 7;
|
|
}
|
|
else
|
|
{
|
|
// Maybe compact: WwwD as 3 digits
|
|
r->iDayOfWeek = 1; // default Monday
|
|
}
|
|
}
|
|
else if (wk->nDigits == 3)
|
|
{
|
|
// Compact WwwD: e.g., W151 → week 15, day 1
|
|
r->iWeekOfYear = wk->iVal / 10;
|
|
int dow = wk->iVal % 10;
|
|
if (r->iWeekOfYear < 1 || r->iWeekOfYear > 53 || dow < 1 || dow > 7)
|
|
{
|
|
return false;
|
|
}
|
|
r->iDayOfWeek = dow % 7;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
if (next->type == DTT_DASH)
|
|
{
|
|
// Extended ISO: YYYY-MM-DD or YYYY-DDD
|
|
c->pos++;
|
|
const DateTok *n2 = consume(c, DTT_NUM);
|
|
if (!n2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (n2->nDigits == 3)
|
|
{
|
|
// Ordinal date: YYYY-DDD
|
|
if (n2->iVal < 1 || n2->iVal > 366)
|
|
{
|
|
return false;
|
|
}
|
|
r->iDayOfYear = n2->iVal;
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
if (n2->nDigits > 2)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// YYYY-MM-DD
|
|
r->iMonth = n2->iVal;
|
|
if (r->iMonth < 1 || r->iMonth > 12)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!consume(c, DTT_DASH))
|
|
{
|
|
// YYYY-MM only (no day) — date only, default day 1.
|
|
r->iDayOfMonth = 1;
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
const DateTok *dd = consume(c, DTT_NUM);
|
|
if (!dd || dd->nDigits > 2 || dd->iVal < 1 || dd->iVal > 31)
|
|
{
|
|
return false;
|
|
}
|
|
r->iDayOfMonth = dd->iVal;
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
if (next->type == DTT_NUM && yr->nDigits == 4)
|
|
{
|
|
// Basic ISO: YYYYMMDD or YYYYDDD
|
|
const DateTok *rest = consume(c, DTT_NUM);
|
|
if (!rest)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (rest->nDigits == 4)
|
|
{
|
|
// YYYYMMDD — rest = MMDD
|
|
r->iMonth = rest->iVal / 100;
|
|
r->iDayOfMonth = rest->iVal % 100;
|
|
if (r->iMonth < 1 || r->iMonth > 12 || r->iDayOfMonth < 1 || r->iDayOfMonth > 31)
|
|
{
|
|
return false;
|
|
}
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
if (rest->nDigits == 3)
|
|
{
|
|
// YYYYDDD
|
|
if (rest->iVal < 1 || rest->iVal > 366)
|
|
{
|
|
return false;
|
|
}
|
|
r->iDayOfYear = rest->iVal;
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// YYYY followed by T or space — date-only with just year?
|
|
// Or YYYY alone. Both are under-specified.
|
|
return false;
|
|
}
|
|
|
|
// Month-name leading: Mmm DD[suffix][,] [YYYY] HH:MM:SS YYYY | Mmm DD HH:MM:SS YYYY
|
|
//
|
|
static bool parse_month_leading(TokCursor *c, DateResult *r)
|
|
{
|
|
const DateTok *mon = consume(c, DTT_MONTH);
|
|
if (!mon)
|
|
{
|
|
return false;
|
|
}
|
|
r->iMonth = mon->iVal;
|
|
|
|
skip_spaces(c);
|
|
|
|
const DateTok *day = consume(c, DTT_NUM);
|
|
if (!day || day->iVal < 1 || day->iVal > 31)
|
|
{
|
|
return false;
|
|
}
|
|
r->iDayOfMonth = day->iVal;
|
|
|
|
// Optional ordinal suffix and comma.
|
|
if (peek(c) && peek(c)->type == DTT_SUFFIX)
|
|
{
|
|
c->pos++;
|
|
}
|
|
if (peek(c) && peek(c)->type == DTT_COMMA)
|
|
{
|
|
c->pos++;
|
|
}
|
|
|
|
skip_spaces(c);
|
|
|
|
// Now: either YYYY then time, or time then YYYY (legacy order).
|
|
const DateTok *n = peek(c);
|
|
const DateTok *n2 = peek2(c);
|
|
|
|
if (n && n->type == DTT_NUM && n2 && n2->type == DTT_COLON)
|
|
{
|
|
// Time first (legacy: Mmm DD HH:MM:SS YYYY)
|
|
if (!parse_time(c, r))
|
|
{
|
|
return false;
|
|
}
|
|
skip_spaces(c);
|
|
const DateTok *yr = consume(c, DTT_NUM);
|
|
if (!yr)
|
|
{
|
|
return false;
|
|
}
|
|
r->iYear = yr->iVal;
|
|
// Timezone may have been consumed by parse_time, or follows the year.
|
|
if (r->iTzMinutes == TZ_NOT_SPECIFIED)
|
|
{
|
|
return parse_timezone(c, r);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Year first: Mmm DD YYYY time
|
|
const DateTok *yr = consume(c, DTT_NUM);
|
|
if (!yr)
|
|
{
|
|
return false;
|
|
}
|
|
r->iYear = yr->iVal;
|
|
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
// Day-leading (European): DD[suffix] Mmm YYYY time
|
|
//
|
|
static bool parse_day_leading(TokCursor *c, DateResult *r)
|
|
{
|
|
const DateTok *day = consume(c, DTT_NUM);
|
|
if (!day || day->iVal < 1 || day->iVal > 31)
|
|
{
|
|
return false;
|
|
}
|
|
r->iDayOfMonth = day->iVal;
|
|
|
|
// Optional ordinal suffix.
|
|
if (peek(c) && peek(c)->type == DTT_SUFFIX)
|
|
{
|
|
c->pos++;
|
|
}
|
|
|
|
skip_spaces(c);
|
|
|
|
const DateTok *mon = consume(c, DTT_MONTH);
|
|
if (!mon)
|
|
{
|
|
return false;
|
|
}
|
|
r->iMonth = mon->iVal;
|
|
|
|
// Optional comma.
|
|
if (peek(c) && peek(c)->type == DTT_COMMA)
|
|
{
|
|
c->pos++;
|
|
}
|
|
|
|
skip_spaces(c);
|
|
|
|
const DateTok *yr = consume(c, DTT_NUM);
|
|
if (!yr)
|
|
{
|
|
return false;
|
|
}
|
|
r->iYear = yr->iVal;
|
|
|
|
return parse_time(c, r);
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Top-level parse dispatch
|
|
// -----------------------------------------------------------------------
|
|
|
|
static bool date_parse_tokens(const DateTok *toks, int ntok, DateResult *r)
|
|
{
|
|
TokCursor cur = { toks, 0, ntok };
|
|
TokCursor *c = &cur;
|
|
|
|
date_result_init(r);
|
|
skip_spaces(c);
|
|
|
|
// Optional day-of-week prefix.
|
|
if (peek(c) && peek(c)->type == DTT_DOW)
|
|
{
|
|
c->pos++;
|
|
if (peek(c) && peek(c)->type == DTT_COMMA)
|
|
{
|
|
c->pos++;
|
|
}
|
|
skip_spaces(c);
|
|
}
|
|
|
|
const DateTok *first = peek(c);
|
|
if (!first || first->type == DTT_EOF)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool ok = false;
|
|
|
|
if (first->type == DTT_NUM && first->nDigits >= 4)
|
|
{
|
|
// ISO family (4+ digit year leading).
|
|
ok = parse_iso(c, r);
|
|
}
|
|
else if (first->type == DTT_DASH || first->type == DTT_PLUS)
|
|
{
|
|
// Signed year: -YYYY... or +YYYYY...
|
|
int sign = (first->type == DTT_DASH) ? -1 : 1;
|
|
c->pos++;
|
|
ok = parse_iso(c, r);
|
|
if (ok)
|
|
{
|
|
r->iYear *= sign;
|
|
}
|
|
}
|
|
else if (first->type == DTT_MONTH)
|
|
{
|
|
// Month-name leading (US order / legacy).
|
|
ok = parse_month_leading(c, r);
|
|
}
|
|
else if (first->type == DTT_NUM)
|
|
{
|
|
const DateTok *second = peek2(c);
|
|
if (second && (second->type == DTT_MONTH ||
|
|
(second->type == DTT_SPACE)))
|
|
{
|
|
// Could be DD Mmm or DD SPACE MONTH.
|
|
// Save position and try day-leading.
|
|
int save = c->pos;
|
|
|
|
// If there's a space, skip past the number and space to check
|
|
// if a month follows.
|
|
if (second->type == DTT_SPACE && c->pos + 2 < c->ntok &&
|
|
c->toks[c->pos + 2].type == DTT_MONTH)
|
|
{
|
|
ok = parse_day_leading(c, r);
|
|
}
|
|
else if (second->type == DTT_MONTH)
|
|
{
|
|
ok = parse_day_leading(c, r);
|
|
}
|
|
|
|
if (!ok)
|
|
{
|
|
c->pos = save;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!ok)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Must have consumed everything (except trailing whitespace and EOF).
|
|
skip_spaces(c);
|
|
if (!at_end(c))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Public API: drop-in replacement for ParseDate()
|
|
// -----------------------------------------------------------------------
|
|
|
|
// Forward declaration for week-date conversion helper.
|
|
static bool ConvertWeekDateToLinearTime(CLinearTimeAbsolute <a, int iYear,
|
|
int iWeek, int iDayOfWeek);
|
|
|
|
bool ParseDate
|
|
(
|
|
CLinearTimeAbsolute <,
|
|
const UTF8 *pDateString,
|
|
bool *pbZoneSpecified
|
|
)
|
|
{
|
|
if (!pDateString || !*pDateString)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
size_t len = strlen(reinterpret_cast<const char *>(pDateString));
|
|
|
|
DateTok toks[DATE_MAX_TOKENS];
|
|
int ntok = date_scan(pDateString, len, toks);
|
|
if (ntok < 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
DateResult dr;
|
|
if (!date_parse_tokens(toks, ntok, &dr))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Convert to FIELDEDTIME and then to linear time.
|
|
//
|
|
FIELDEDTIME ft;
|
|
memset(&ft, 0, sizeof(ft));
|
|
|
|
// Reject years outside the range timeutil accepts (see isValidDate)
|
|
// *before* narrowing to short. Otherwise an out-of-range year wraps
|
|
// silently into a plausible one — e.g. 67536 -> 2000 — and is accepted
|
|
// as the wrong date. This guards every conversion branch below,
|
|
// including the week-date and ordinal paths that bypass
|
|
// FieldedTimeToLinearTime's own year check.
|
|
if (dr.iYear < -27256 || 30826 < dr.iYear)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ft.iYear = static_cast<short>(dr.iYear);
|
|
|
|
if (dr.iWeekOfYear > 0)
|
|
{
|
|
// ISO week date — needs special conversion.
|
|
if (!ConvertWeekDateToLinearTime(lt, dr.iYear, dr.iWeekOfYear, dr.iDayOfWeek))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Apply time-of-day if present.
|
|
if (dr.bHasTime)
|
|
{
|
|
FIELDEDTIME ftTime;
|
|
lt.ReturnFields(&ftTime);
|
|
ftTime.iHour = static_cast<unsigned short>(dr.iHour);
|
|
ftTime.iMinute = static_cast<unsigned short>(dr.iMinute);
|
|
ftTime.iSecond = static_cast<unsigned short>(dr.iSecond);
|
|
|
|
// Sub-seconds.
|
|
int frac = dr.iFracHectoNano;
|
|
ftTime.iMillisecond = static_cast<unsigned short>(frac / 10000);
|
|
ftTime.iMicrosecond = static_cast<unsigned short>((frac / 10) % 1000);
|
|
ftTime.iNanosecond = static_cast<unsigned short>((frac % 10) * 100);
|
|
|
|
if (!lt.SetFields(&ftTime))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (dr.iDayOfYear > 0)
|
|
{
|
|
// Ordinal date: set Jan 1, then add (DayOfYear - 1) days.
|
|
ft.iMonth = 1;
|
|
ft.iDayOfMonth = 1;
|
|
ft.iHour = static_cast<unsigned short>(dr.iHour);
|
|
ft.iMinute = static_cast<unsigned short>(dr.iMinute);
|
|
ft.iSecond = static_cast<unsigned short>(dr.iSecond);
|
|
|
|
int frac = dr.iFracHectoNano;
|
|
ft.iMillisecond = static_cast<unsigned short>(frac / 10000);
|
|
ft.iMicrosecond = static_cast<unsigned short>((frac / 10) % 1000);
|
|
ft.iNanosecond = static_cast<unsigned short>((frac % 10) * 100);
|
|
|
|
if (!lt.SetFields(&ft))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (dr.iDayOfYear > 1)
|
|
{
|
|
CLinearTimeDelta ltd;
|
|
ltd.Set100ns(FACTOR_100NS_PER_DAY);
|
|
lt += ltd * (dr.iDayOfYear - 1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Standard date.
|
|
ft.iMonth = static_cast<unsigned short>(dr.iMonth);
|
|
ft.iDayOfMonth = static_cast<unsigned short>(dr.iDayOfMonth);
|
|
ft.iHour = static_cast<unsigned short>(dr.iHour);
|
|
ft.iMinute = static_cast<unsigned short>(dr.iMinute);
|
|
ft.iSecond = static_cast<unsigned short>(dr.iSecond);
|
|
|
|
int frac = dr.iFracHectoNano;
|
|
ft.iMillisecond = static_cast<unsigned short>(frac / 10000);
|
|
ft.iMicrosecond = static_cast<unsigned short>((frac / 10) % 1000);
|
|
ft.iNanosecond = static_cast<unsigned short>((frac % 10) * 100);
|
|
|
|
int64_t i64;
|
|
if (!FieldedTimeToLinearTime(&ft, &i64))
|
|
{
|
|
return false;
|
|
}
|
|
lt.Set100ns(i64);
|
|
}
|
|
|
|
// Apply timezone offset.
|
|
if (dr.iTzMinutes != TZ_NOT_SPECIFIED)
|
|
{
|
|
CLinearTimeDelta ltd;
|
|
ltd.SetSeconds(60 * dr.iTzMinutes);
|
|
lt -= ltd;
|
|
*pbZoneSpecified = true;
|
|
}
|
|
else
|
|
{
|
|
*pbZoneSpecified = false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Week-date conversion helper
|
|
// -----------------------------------------------------------------------
|
|
|
|
static bool ConvertWeekDateToLinearTime(CLinearTimeAbsolute <a, int iYear,
|
|
int iWeek, int iDayOfWeek)
|
|
{
|
|
// Find the Monday of ISO week 1 of iYear.
|
|
// ISO week 1 contains January 4th.
|
|
//
|
|
FIELDEDTIME ftJan4;
|
|
memset(&ftJan4, 0, sizeof(ftJan4));
|
|
ftJan4.iYear = static_cast<short>(iYear);
|
|
ftJan4.iMonth = 1;
|
|
ftJan4.iDayOfMonth = 4;
|
|
|
|
if (!lta.SetFields(&ftJan4))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Find what day of week Jan 4 falls on.
|
|
lta.ReturnFields(&ftJan4);
|
|
// iDayOfWeek: 0=Sun, 1=Mon, ..., 6=Sat
|
|
// ISO day-of-week: 1=Mon, ..., 7=Sun
|
|
// Convert to ISO: Mon=1..Sun=7
|
|
int jan4_iso_dow = (ftJan4.iDayOfWeek == 0) ? 7 : ftJan4.iDayOfWeek;
|
|
|
|
// Monday of week 1 = Jan 4 minus (jan4_iso_dow - 1) days.
|
|
CLinearTimeDelta oneDay;
|
|
oneDay.Set100ns(FACTOR_100NS_PER_DAY);
|
|
lta -= oneDay * (jan4_iso_dow - 1);
|
|
|
|
// Now advance to the target week and day.
|
|
// iDayOfWeek is 0=Sun..6=Sat from our parser, but for ISO week dates
|
|
// the input was 1=Mon..7=Sun. The parser stored it as iDayOfWeek % 7
|
|
// (so 1=Mon..6=Sat, 0=Sun). Convert back to ISO for offset calculation.
|
|
int iso_dow = (iDayOfWeek == 0) ? 7 : iDayOfWeek;
|
|
int totalDays = (iWeek - 1) * 7 + (iso_dow - 1);
|
|
|
|
lta += oneDay * totalDays;
|
|
|
|
return true;
|
|
}
|