sclass SimpleLeftToRightParser > Meta { S text; LS tok; gettable TokPtr ptr; TokPtr mainLoopPtr; S currentToken; bool caseInsensitive; new L warnings; *() {} *(S *text) {} *(LS *tok) {} swappable LS tokenize(S text) { ret javaTok(text); } S t aka token() { ret currentToken; } S token(int i) { ret get(tok, ptr.idx()+i*2); } S next aka consume aka tpp() { var t = t(); next(1); ret t; } S lastSpace aka prevSpace() { ret get(tok, ptr.idx()-1); } S nextSpace() { ret get(tok, ptr.idx()+1); } S space(int i) { ret get(tok, ptr.idx()+i*2+1); } void unconsume() { next(-1); } bool tokEq aka eqTok(S a, S b) { ret eqOrEqic(caseInsensitive, a, b); } bool tokEqOneOf(S a, S... l) { ret any(l, b -> tokEq(a, b)); } bool is(int i, S t) { ret tokEq(token(i), t); } bool is(S t) { ret tokEq(currentToken, t); } bool was(S t) { ret tokEq(token(-1), t); } bool isOneOf(S... tokens) { ret tokEqOneOf(currentToken, tokens); } S[] consumeArray(int n) { S[] array = new[n]; for i to n: array[i] = consume(); ret array; } meta-for isInteger also as isIdentifier { bool isInteger() { ret isInteger(t()); } bool isInteger(S s) { ret main isInteger(s); } } S consumeIdentifier() { ret assertIdentifier(consume()); } S consumeIdentifierOpt() { ret isIdentifier(t()) ? consume() : null; } int consumeInteger() { ret parseInt(assertInteger(consume())); } bool consumeOpt(S token) { if (!is(token)) false; consume(); true; } void consume(S token) { if (!is(token)) fail("Expected " + quote(token) + ", got " + describeToken(token()); consume(); } S describeToken(S token) { ret token == null ? "EOF" : quote(token); } S consumeOneOf(S... tokens) { if (!isOneOf(tokens)) fail("Expected one of " + asList(tokens)); ret consume(); } void ptr(TokPtr ptr) { this.ptr = ptr; fetch(); } int idx aka tokIdx() { ret ptr.idx(); } int lTok() { ret l(tok); } int nRemainingTokens() { ret (lTok()-idx())/2; } bool atEnd aka endOfText() { ret ptr.atEnd(); } void fetch { currentToken = ptr!; } bool lineBreak() { ret containsLineBreak(get(tok, ptr.idx()-1)); } bool atEndOrLineBreak() { ret atEnd() || lineBreak(); } void init { tok if null = tokenize(text); if (ptr == null) ptr(ListAndIndex(tok, 1)); } bool mainLoop() { init(); if (atEnd()) false; if (eq(mainLoopPtr, ptr)) fail("main loop didn't advance (current token: " + quote(token()) + ")"); mainLoopPtr = ptr; true; } class AssureAdvance { TokPtr cur; { init(); } bool get() { if (atEnd()) false; if (eq(cur, ptr)) fail("Parse loop didn't advance (current token: " + quote(token()) + ")"); cur = ptr; true; } } void unknownToken { warn("Unknown token: " + t()); } void warn(S msg) { warnings.add(print(msg)); } // can also move backwards for negative n void next aka skip aka consume(int n) { ptr(min(lTok(), ptr.idx()+n*2)); } // if i points to an N token, it is incremented void ptr(int i) { ptr(ListAndIndex(tok, min(i | 1, l(tok)))); } LineAndColumn lineAndColumn() { ret tokenToLineAndColumn(ptr); } LineAndColumn lineAndColumn(int idx) { ret tokenToLineAndColumn(ptr.plus(idx*2)); } S consumeUntilSpaceOr(IF0 pred) { int i = idx(); do next(); while (!atEnd() && empty(lastSpace()) && !pred!); ret joinSubList(tok, i, idx()-1); } void setText(S text) { this.text = text; tok = null; ptr = null; } int relativeIndexOf(S token) { int n = nRemainingTokens(); for i to n: if (eqTok(token(i), token)) ret i; ret -1; } }