refactor: added more sequence patterns to the lexer
This commit is contained in:
parent
c6251810ae
commit
723379321d
@ -161,6 +161,51 @@ describe('6.4 quantities', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('6.4d double dash and plus as a quantity (owner, 5 Nov 2026)', () => {
|
||||
it('reads -- and + before 1-3 digits as the quantity', () => {
|
||||
expect(read('1921--50 5149--40 5328+12').lines).toEqual(['1921x50', '5149x40', '5328x12']);
|
||||
expect(read('1921--5 1921--500 5328+1 5328+999').lines).toEqual(['1921x5', '1921x500', '5328x1', '5328x999']);
|
||||
});
|
||||
it('allows spaces around them, like a single dash', () => {
|
||||
for (const text of ['1921 -- 50', '1921 --50', '1921-- 50']) expect(read(text).lines).toEqual(['1921x50']);
|
||||
for (const text of ['5328 + 12', '5328 +12', '5328+ 12']) expect(read(text).lines).toEqual(['5328x12']);
|
||||
});
|
||||
it('never reads 4 digits after them as an ending or a range', () => {
|
||||
expect(read('1921--1925').unreadable).toEqual(['1921--1925:bad_range']);
|
||||
expect(read('1921 -- 1925').unreadable).toEqual(['1921 -- 1925:bad_range']);
|
||||
expect(read('5328+5329').unreadable).toEqual(['5328+5329:quantity_too_large']);
|
||||
expect(read('5328 + 5329').unreadable).toEqual(['5328 + 5329:quantity_too_large']);
|
||||
});
|
||||
it('keeps refusing what it cannot be sure of', () => {
|
||||
expect(read('1921--0 5328+0').unreadable).toEqual(['1921--0:zero_quantity', '5328+0:zero_quantity']);
|
||||
expect(read('1921---50').lines).toEqual([]);
|
||||
expect(read('1921--').lines).toEqual([]);
|
||||
expect(read('5328+12+3 1921--50--3').unreadable).toEqual(['5328+12+3:double_quantity', '1921--50--3:double_quantity']);
|
||||
expect(read('0888, +12').unreadable).toEqual(['+12:orphan_quantity']);
|
||||
});
|
||||
it('a single dash still makes a range', () => {
|
||||
expect(read('1921-1923').lines).toEqual(['1921x1', '1922x1', '1923x1']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('6.4e colon as a quantity (owner, 5 Nov 2026)', () => {
|
||||
it('reads : before 1-3 digits after an ending, in every spacing', () => {
|
||||
for (const text of ['1123:5', '1123 : 5', '1123 :5', '1123: 5']) expect(read(text).lines).toEqual(['1123x5']);
|
||||
expect(read('1123:50 1123:999').lines).toEqual(['1123x50', '1123x999']);
|
||||
});
|
||||
it('never reads 4 digits after it as an ending, and refuses the rest', () => {
|
||||
expect(read('1123:1234').unreadable).toEqual(['1123:1234:quantity_too_large']);
|
||||
expect(read('1123:0').unreadable).toEqual(['1123:0:zero_quantity']);
|
||||
expect(read('1123:5:30').unreadable).toEqual(['1123:5:30:date_or_time']);
|
||||
expect(read('1123:').lines).toEqual([]);
|
||||
});
|
||||
it('a time or date without a 4-digit ending before it is still date_or_time', () => {
|
||||
expect(read('14:35').unreadable).toEqual(['14:35:date_or_time']);
|
||||
expect(read('0888 14:35')).toMatchObject({ lines: ['0888x1'], unreadable: ['14:35:date_or_time'] });
|
||||
expect(read('[12/10, 14:35] Ravi: 1123:5').lines).toEqual(['1123x5']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('6.4b a dot as a quantity (decided 4 Nov 2026)', () => {
|
||||
it('reads a glued dot and 1-3 digits as the quantity', () => {
|
||||
expect(read('9001.10').lines).toEqual(['9001x10']);
|
||||
|
||||
@ -23,7 +23,7 @@ function generate(seed: number): { text: string; expected: Line[] } {
|
||||
}
|
||||
const ending = pad4(r.int(10000));
|
||||
const qty = r.int(3) === 0 ? 1 : 1 + r.int(150);
|
||||
const form = r.pick(['bare', 'x', ' x ', '*', '=', ' = ', '_', '-', '()', ' ()', 'space'] as const);
|
||||
const form = r.pick(['bare', 'x', ' x ', '*', '=', ' = ', '_', '-', '--', ' -- ', '+', ' + ', ':', ' : ', '()', ' ()', 'space'] as const);
|
||||
const q = String(qty);
|
||||
const rendered =
|
||||
form === 'bare' ? (qty === 1 ? ending : `${ending}x${q}`)
|
||||
|
||||
@ -2,7 +2,7 @@ export type AtomKind =
|
||||
| 'num' // run of ASCII digits
|
||||
| 'baddigit' // run of non-ASCII digits (Malayalam, Arabic-Indic, Devanagari)
|
||||
| 'word' // run of anything else that is not an operator, separator or space (letters, emoji, ?, ...)
|
||||
| 'op' // one of x * = _ - ( ) / : . (x only when it stands alone as a word)
|
||||
| 'op' // one of x * = _ + - ( ) / : . (x only when it stands alone as a word)
|
||||
| 'sep'; // , ; |
|
||||
|
||||
export interface Atom {
|
||||
@ -13,7 +13,7 @@ export interface Atom {
|
||||
glued: boolean;
|
||||
}
|
||||
|
||||
const OPS = new Set(['*', '=', '_', '-', '(', ')', '/', ':', '.']);
|
||||
const OPS = new Set(['*', '=', '_', '+', '-', '(', ')', '/', ':', '.']);
|
||||
const SEPS = new Set([',', ';', '|']);
|
||||
|
||||
function isAsciiDigit(c: number): boolean {
|
||||
|
||||
@ -17,8 +17,8 @@ interface Loose {
|
||||
|
||||
type Item = ScanItem | Loose;
|
||||
|
||||
const QTY_OPS = new Set(['x', '*', '=', '_']);
|
||||
const CONTINUATION_OPS = new Set(['x', '*', '=', '_', '(', '-']);
|
||||
const QTY_OPS = new Set(['x', '*', '=', '_', '+', ':']);
|
||||
const CONTINUATION_OPS = new Set(['x', '*', '=', '_', '+', '(', '-']);
|
||||
|
||||
type Step = { ok: true; item: ScanItem; next: number } | { ok: false; reason: UnreadableReason; next: number };
|
||||
|
||||
@ -88,6 +88,13 @@ export function scanLine(body: string, atoms: Atom[], line: number): ScanItem[]
|
||||
const a = at(k);
|
||||
const b = at(k + 1);
|
||||
|
||||
if (isOp(a, '-') && isOp(b, '-') && (b as Atom).glued) {
|
||||
const c = at(k + 2);
|
||||
if (c === undefined || c.kind !== 'num') return { ok: false, reason: 'mixed_characters', next: k + 2 };
|
||||
if (c.text.length > 3) return { ok: false, reason: 'bad_range', next: k + 3 };
|
||||
return withQty(i, c.text, k + 3);
|
||||
}
|
||||
|
||||
if (isOp(a, '-')) {
|
||||
const gluedBoth = (a as Atom).glued && b !== undefined && b.glued;
|
||||
if (b !== undefined && b.kind === 'num') {
|
||||
|
||||
9
test/fixtures/orders/colon-2026-11-05.expected.json
vendored
Normal file
9
test/fixtures/orders/colon-2026-11-05.expected.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"totalTickets": 5,
|
||||
"lines": [
|
||||
"1123 x5 @1"
|
||||
],
|
||||
"repeated": [],
|
||||
"unreadable": [],
|
||||
"ignored": []
|
||||
}
|
||||
1
test/fixtures/orders/colon-2026-11-05.txt
vendored
Normal file
1
test/fixtures/orders/colon-2026-11-05.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
1123:5
|
||||
11
test/fixtures/orders/double-dash-and-plus-2026-11-05.expected.json
vendored
Normal file
11
test/fixtures/orders/double-dash-and-plus-2026-11-05.expected.json
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"totalTickets": 102,
|
||||
"lines": [
|
||||
"1921 x50 @1",
|
||||
"5149 x40 @2",
|
||||
"5328 x12 @3"
|
||||
],
|
||||
"repeated": [],
|
||||
"unreadable": [],
|
||||
"ignored": []
|
||||
}
|
||||
3
test/fixtures/orders/double-dash-and-plus-2026-11-05.txt
vendored
Normal file
3
test/fixtures/orders/double-dash-and-plus-2026-11-05.txt
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
1921--50
|
||||
5149--40
|
||||
5328+12
|
||||
Loading…
Reference in New Issue
Block a user