The ability to move back one term can be implemented as a utility function. Unfortunately the time complexity is O(n) in the number of syntax items.
To my mind a big reason to have this ability is to expand a term based on the type of syntax read:
import { prev } from './utils' for syntax;
syntax m = ctx => {
ctx.next() // eatThis
let stx = ctx.next().value;
let ret;
if(stx.isIdentifier()) {
ret = #`${stx}`;
} else if(stx.isDelimiter() && stx.isBracket()) {
prev(ctx); // <- call to prev
ret = #`${ctx.expand('ArrayExpression')}`;
}
return ret;
}
m eatThis foo; // foo
m eatThis [1,2,3] // [1,2,3]
But the same thing can be accomplished with lookahead:
import { lookahead } from './utils' for syntax;
syntax m = ctx => {
ctx.next(); // eatThis
let stx = lookahead(ctx); // <- call to lookahead
let ret;
if(stx.isIdentifier()) {
ret = #`${stx}`;
} else if(stx.isDelimiter() && stx.isBracket()) {
ret = #`${ctx.expand('ArrayExpression')}`;
}
return ret;
}
m eatThis foo; // foo
m eatThis [1,2,3] // [1,2,3]
With this PR lookahead(1)
can be implemented in constant time and lookahead(m)
is O(m).
The feature works as follows:
syntax m = ctx => {
ctx.next(); // eatThis
ctx.mark(); // <- marks a reset point
let stx = ctx.next().value
let ret;
if(stx.isIdentifier()) {
ret = #`${stx}`;
} else if(stx.isDelimiter() && stx.isBracket()) {
ctx.reset(); // <- back to reset point
ret = #`${ctx.expand('ArrayExpression')}`;
}
return ret;
}
m foo; // foo
m [1,2,3] // [1,2,3]