futurescript
Version:
A functional style, but highly readable language that compiles to JavaScript.
590 lines (564 loc) • 23.5 kB
JavaScript
import * as $lex from "./lex.mjs";
import * as $tools from "./tools.mjs";
// `ruler` can be a token type, an array of token types, or a validating function.
// Array means to return true if any element in the array matches.
let checkToken = (index, ruler, lexPart) => {
let token = lexPart.lex.at(index);
if (token instanceof $lex.Token) {
if (Array.isArray(ruler)) {
return $tools.instanceof(token, ruler);
}
else if ($tools.classIsClass(ruler, $lex.Token)) {
return token instanceof ruler;
}
else {
return ruler(token, index, lexPart);
}
}
else {
return false;
}
};
export class Pattern {
static searchOne(ruler, lexPart, leftToRight) {
let lex = lexPart.lex;
let startIndex = lexPart.startIndex;
let endIndex = lexPart.endIndex;
let level = 0;
for (
let i = leftToRight ? startIndex : endIndex;
leftToRight ? i <= endIndex : i >= startIndex;
leftToRight ? i++ : i--
) {
if (
lex.at(i) instanceof $lex.RightParenthesis ||
lex.at(i) instanceof $lex.RightBracket ||
lex.at(i) instanceof $lex.RightBrace ||
lex.at(i) instanceof $lex.RightChevron
) {
level--;
}
if (level === 0 && checkToken(i, ruler, lexPart)) {
return i;
}
if (
lex.at(i) instanceof $lex.LeftParenthesis ||
lex.at(i) instanceof $lex.LeftBracket ||
lex.at(i) instanceof $lex.LeftBrace ||
lex.at(i) instanceof $lex.LeftChevron
) {
level++;
}
}
return null;
}
static searchSequence(tokenTypes, lexPart, leftToRight) {
let lex = lexPart.lex;
let startIndex = lexPart.startIndex;
let endIndex = lexPart.endIndex;
let level = 0;
for (
let i = leftToRight ? startIndex : endIndex;
leftToRight ? i <= endIndex : i >= startIndex;
leftToRight ? i++ : i--
) {
if (
lex.at(i) instanceof $lex.LeftParenthesis ||
lex.at(i) instanceof $lex.LeftBracket ||
lex.at(i) instanceof $lex.LeftBrace ||
lex.at(i) instanceof $lex.LeftChevron
) {
level++;
}
else if (
lex.at(i) instanceof $lex.RightParenthesis ||
lex.at(i) instanceof $lex.RightBracket ||
lex.at(i) instanceof $lex.RightBrace ||
lex.at(i) instanceof $lex.RightChevron
) {
level--;
}
else if (lex.at(i) instanceof tokenTypes[0] && level === 0) {
let matched = true;
for (let j = i + 1; j <= endIndex; j++) {
if (j - i > tokenTypes.length - 1) {
break;
}
else if (!(lex.at(j) instanceof tokenTypes[j - i])) {
matched = false;
break;
}
}
if (matched) {
return i;
}
}
}
return null;
}
// We use non-greedy matching. And even when no match is found, it won't go backtracking to
// be greedier, but will directly return null.
// Also, if a tokenType is a function, it currently can't process chevron, parenthesis,
// bracket, or brace very well. So function ruler is limited in this method.
// If function ruler is needed, better use `searchOne` method.
static matchPattern(tokenTypes, lexPart, leftToRight) {
let lex = lexPart.lex;
let startIndex = lexPart.startIndex;
let endIndex = lexPart.endIndex;
let rightToLeftHead = null;
if (!leftToRight) {
if (tokenTypes[0] instanceof AnyExcept || tokenTypes[0] instanceof TokensExcept) {
rightToLeftHead = tokenTypes[0];
tokenTypes = tokenTypes.slice(1);
}
else {
// Simulate left-to-right, because no need to use the slower right-to-left.
leftToRight = true;
}
}
// For each token, label its level.
let levels = [];
let level = 0;
for (let i = startIndex; i <= endIndex; i++) {
if (
lex.at(i) instanceof $lex.LeftParenthesis ||
lex.at(i) instanceof $lex.LeftBracket ||
lex.at(i) instanceof $lex.LeftBrace ||
lex.at(i) instanceof $lex.LeftChevron
) {
levels.push(level);
level++;
}
else if (
lex.at(i) instanceof $lex.RightParenthesis ||
lex.at(i) instanceof $lex.RightBracket ||
lex.at(i) instanceof $lex.RightBrace ||
lex.at(i) instanceof $lex.RightChevron
) {
level--;
levels.push(level);
}
else {
levels.push(level);
}
}
for (
let i = leftToRight ? startIndex : endIndex;
leftToRight ? i <= endIndex : i >= startIndex;
leftToRight ? i++ : i--
) {
let level = levels[i - startIndex];
if (level === 0 && !(
lex.at(i) instanceof $lex.RightChevron ||
lex.at(i) instanceof $lex.RightParenthesis ||
lex.at(i) instanceof $lex.RightBracket ||
lex.at(i) instanceof $lex.RightBrace
)) {
let result = [];
let stack = [];
let matched = true;
let tokenTypeIndex = 0;
let inWildcard = false;
let earlyPassed = false;
if (rightToLeftHead !== null) {
result.push(startIndex);
}
let j = i;
while (j <= endIndex) {
let step = 1;
let token = lex.at(j);
let tokenType = tokenTypes[tokenTypeIndex];
if (
token instanceof $lex.RightChevron ||
token instanceof $lex.RightParenthesis ||
token instanceof $lex.RightBracket ||
token instanceof $lex.RightBrace
) {
stack.pop();
}
if (stack.length === 0) {
if (tokenType === undefined) {
if (tokenTypes.length > 0) {
matched = false;
}
break;
}
else if (tokenType === ChevronPair) {
if (token instanceof $lex.LeftChevron) {
result.push(j);
}
else if (token instanceof $lex.RightChevron) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === ParenthesisPair) {
if (token instanceof $lex.LeftParenthesis) {
result.push(j);
}
else if (token instanceof $lex.RightParenthesis) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === NormalParenthesisPair) {
if (token instanceof $lex.NormalLeftParenthesis) {
result.push(j);
}
else if (token instanceof $lex.NormalRightParenthesis) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === CallParenthesisPair) {
if (token instanceof $lex.CallLeftParenthesis) {
result.push(j);
}
else if (token instanceof $lex.CallRightParenthesis) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === PseudoCallParenthesisPair) {
if (token instanceof $lex.PseudoCallLeftParenthesis) {
result.push(j);
}
else if (token instanceof $lex.PseudoCallRightParenthesis) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === BracketPair) {
if (token instanceof $lex.LeftBracket) {
result.push(j);
}
else if (token instanceof $lex.RightBracket) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === NormalBracketPair) {
if (token instanceof $lex.NormalLeftBracket) {
result.push(j);
}
else if (token instanceof $lex.NormalRightBracket) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === CallBracketPair) {
if (token instanceof $lex.CallLeftBracket) {
result.push(j);
}
else if (token instanceof $lex.CallRightBracket) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === BracePair) {
if (token instanceof $lex.LeftBrace) {
result.push(j);
}
else if (token instanceof $lex.RightBrace) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === NormalBracePair) {
if (token instanceof $lex.NormalLeftBrace) {
result.push(j);
}
else if (token instanceof $lex.NormalRightBrace) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType === CallBracePair) {
if (token instanceof $lex.CallLeftBrace) {
result.push(j);
}
else if (token instanceof $lex.CallRightBrace) {
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
else if (tokenType instanceof AnyExcept || tokenType instanceof TokensExcept) {
let exceptions = tokenType.tokenTypes;
let checkJumpOut = k => (
exceptions.length === 0 ?
(
tokenTypeIndex < tokenTypes.length - 1 &&
checkToken(k, tokenTypes[tokenTypeIndex + 1], lexPart)
) :
(
tokenTypeIndex < tokenTypes.length - 1 &&
checkToken(k, tokenTypes[tokenTypeIndex + 1], lexPart)
) ||
exceptions.some(m => lex.at(k) instanceof m)
);
if (exceptions.length === 0 && tokenTypeIndex === tokenTypes.length - 1) {
result.push(j);
earlyPassed = true;
break; // no need to do further check
}
else if (!inWildcard && checkJumpOut(j)) { // if the part is zero-length
if (tokenType instanceof AnyExcept) {
result.push(j);
tokenTypeIndex++;
step = 0;
}
else {
matched = false;
break;
}
}
else if (checkJumpOut(j + 1)) {
if (!inWildcard) {
result.push(j);
}
inWildcard = false;
tokenTypeIndex++;
}
else {
if (!inWildcard) {
result.push(j);
inWildcard = true;
}
}
}
else if (checkToken(j, tokenType, lexPart)) {
result.push(j);
tokenTypeIndex++;
}
else {
matched = false;
break;
}
}
if (token instanceof $lex.LeftChevron) {
stack.push(ChevronPair);
}
else if (token instanceof $lex.LeftParenthesis) {
stack.push(ParenthesisPair);
}
else if (token instanceof $lex.LeftBracket) {
stack.push(BracketPair);
}
else if (token instanceof $lex.LeftBrace) {
stack.push(BracePair);
}
j += step;
}
if (rightToLeftHead !== null) { // check exceptions in rightToLeftHead
let last = result[1] === undefined ? endIndex : result[1] - 1;
if (rightToLeftHead instanceof TokensExcept && result[0] > last) {
matched = false;
}
else {
for (let k = result[0]; k <= last; k++) {
let exceptions = rightToLeftHead.tokenTypes;
if (exceptions.some(m => lex.at(k) instanceof m)) {
matched = false;
break;
}
}
}
}
if (matched) {
let tailCount = inWildcard ? 1 : 0;
if (tokenTypeIndex + tailCount < tokenTypes.length && !earlyPassed) {
if (
tokenTypeIndex === tokenTypes.length - 1 &&
tokenTypes[tokenTypeIndex] instanceof AnyExcept
) {
result.push(j);
}
else {
matched = false;
}
}
}
if (matched) {
return result;
}
}
if (leftToRight || rightToLeftHead === null) {
break;
}
}
return null;
}
// `capture` elements must be in ascendant order.
// A `capture` element can be a number, or an array of 2 numbers meaning "combine
// all matches from x to y".
static matchPatternCapture(tokenTypes, lexPart, leftToRight, capture) {
let match = this.matchPattern(tokenTypes, lexPart, leftToRight);
if (match === null) {
return null;
}
else {
return this.captureAfterMatch(match, capture, lexPart);
}
}
// If `capture` is undefined, then it will capture all.
static captureAfterMatch(match, capture, lexPart) {
if (capture === undefined) {
capture = [];
for (let i = 0; i < match.length; i++) {
capture.push(i);
}
}
return capture.map(x => {
if (x === null) {
return null;
}
else if (Array.isArray(x)) {
return {
startIndex: match[x[0]],
endIndex: x[1] === match.length - 1 ? lexPart.endIndex : match[x[1] + 1] - 1
};
}
else {
return {
startIndex: match[x],
endIndex: x === match.length - 1 ? lexPart.endIndex : match[x + 1] - 1
};
}
});
}
// `wraps` is optional, defaulting to false. If true, it also returns the "capture all"
// array (these 2 arrays will be wrapped in an object).
static matchPatternsAndCaptures(pcs, lexPart, leftToRight, wraps = false) {
let best = null;
let extremeValue = null;
for (let i = 0; i < pcs.length; i++) {
let pc = pcs[i];
let match = this.matchPattern(pc[0], lexPart, leftToRight);
if (match !== null) {
let checker = this.captureAfterMatch(match, undefined, lexPart);
let candidate = this.captureAfterMatch(match, pc[1], lexPart);
let value =
leftToRight ?
checker[0].endIndex :
checker[checker.length - 1].startIndex;
if (extremeValue === null || (
leftToRight ? value < extremeValue : value > extremeValue
)) {
extremeValue = value;
best = wraps ? {selected: candidate, all: checker} : candidate;
}
}
}
return best;
}
// `token` can be a token type or an array of token types.
// If lex doesn't contain the token, the returned range will be the whole lex.
// If the first or last token is the splitter, or if one splitter is immediately after another,
// then the corresponding range will be -1 (endIndex = startIndex - 1).
// If the input range is negative (endIndex < startIndex), then the output range will be
// the same.
static split(token, lexPart) {
let lex = lexPart.lex;
let startIndex = lexPart.startIndex;
let endIndex = lexPart.endIndex;
let level = 0;
let oldPos = startIndex;
let r = [];
let match = (lexToken) => {
if (Array.isArray(token)) {
return token.some(m => lexToken instanceof m);
}
else {
return lexToken instanceof token;
}
};
for (let i = startIndex; i <= endIndex; i++) {
if (
lex.at(i) instanceof $lex.LeftParenthesis ||
lex.at(i) instanceof $lex.LeftBracket ||
lex.at(i) instanceof $lex.LeftBrace ||
lex.at(i) instanceof $lex.LeftChevron
) {
level++;
}
else if (
lex.at(i) instanceof $lex.RightParenthesis ||
lex.at(i) instanceof $lex.RightBracket ||
lex.at(i) instanceof $lex.RightBrace ||
lex.at(i) instanceof $lex.RightChevron
) {
level--;
}
else if (match(lex.at(i)) && level === 0) {
r.push({startIndex: oldPos, endIndex: i - 1});
oldPos = i + 1;
}
}
r.push({startIndex: oldPos, endIndex: endIndex});
return r;
}
}
// Including zero tokens. `anyExcept` and `any` are just shorthands.
export class AnyExcept {
constructor(tokenTypes) {
this.tokenTypes = tokenTypes;
}
}
export let anyExcept = function(tokenTypes) {
return new AnyExcept(tokenTypes);
};
export let any = anyExcept([]);
// At least 1 token. `tokensExcept` and `tokens` are just shorthands.
export class TokensExcept {
constructor(tokenTypes) {
this.tokenTypes = tokenTypes;
}
}
export let tokensExcept = function(tokenTypes) {
return new TokensExcept(tokenTypes);
};
export let tokens = tokensExcept([]);
export let ChevronPair = $lex.LeftChevron;
export let ParenthesisPair = $lex.LeftParenthesis;
export let BracketPair = $lex.LeftBracket;
export let BracePair = $lex.LeftBrace;
export let NormalParenthesisPair = $lex.NormalLeftParenthesis;
export let NormalBracketPair = $lex.NormalLeftBracket;
export let NormalBracePair = $lex.NormalLeftBrace;
export let CallParenthesisPair = $lex.CallLeftParenthesis;
export let CallBracketPair = $lex.CallLeftBracket;
export let CallBracePair = $lex.CallLeftBrace;
export let PseudoCallParenthesisPair = $lex.PseudoCallLeftParenthesis;