UNPKG

futurescript

Version:

A functional style, but highly readable language that compiles to JavaScript.

404 lines (369 loc) 15.4 kB
// Generally, a ruler can be a token type, an array of token types, a validating function, // a pair, or a quantifier. But many checkers have stricter restrictions. 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; } }; let sameLevelNextIndex = (index, lex, leftToRight) => { let token = lex.at(index); if (token instanceof $lex.Enclosing && token.constructor.leftFlag === (leftToRight ? true : undefined)) { return token.oppositeIndex; } else { return index + (leftToRight ? 1 : -1); } }; let sameLevelNextIndexForPattern = (index, lex, leftToRight) => { let token = lex.at(index); if (token instanceof $lex.Enclosing && token.constructor.leftFlag === (leftToRight ? true : undefined)) { return token.oppositeIndex + (leftToRight ? 1 : -1); } else { return index + (leftToRight ? 1 : -1); } }; let checkNonQuantifier = (index, ruler, lexPart, leftToRight) => { let lex = lexPart.lex; let token = lex.at(index); if ($tools.classIsClass(ruler, Pair)) { // for jumping quickly if it isn't pair let matched = null; if (ruler === ChevronPair) { matched = token instanceof (leftToRight ? $lex.LeftChevron : $lex.RightChevron); } else if (ruler === ParenthesisPair) { matched = token instanceof (leftToRight ? $lex.LeftParenthesis : $lex.RightParenthesis); } else if (ruler === NormalParenthesisPair) { matched = token instanceof ( leftToRight ? $lex.NormalLeftParenthesis : $lex.NormalRightParenthesis ); } else if (ruler === CallParenthesisPair) { matched = token instanceof (leftToRight ? $lex.CallLeftParenthesis : $lex.CallRightParenthesis); } else if (ruler === PseudoCallParenthesisPair) { matched = token instanceof ( leftToRight ? $lex.PseudoCallLeftParenthesis : $lex.PseudoCallRightParenthesis ); } else if (ruler === BracketPair) { matched = token instanceof (leftToRight ? $lex.LeftBracket : $lex.RightBracket); } else if (ruler === NormalBracketPair) { matched = token instanceof (leftToRight ? $lex.NormalLeftBracket : $lex.NormalRightBracket); } else if (ruler === CallBracketPair) { matched = token instanceof (leftToRight ? $lex.CallLeftBracket : $lex.CallRightBracket); } else if (ruler === BracePair) { matched = token instanceof (leftToRight ? $lex.LeftBrace : $lex.RightBrace); } else if (ruler === NormalBracePair) { matched = token instanceof (leftToRight ? $lex.NormalLeftBrace : $lex.NormalRightBrace); } else if (ruler === CallBracePair) { matched = token instanceof (leftToRight ? $lex.CallLeftBrace : $lex.CallRightBrace); } else { throw new Error(); } return matched && ( leftToRight ? token.oppositeIndex <= lexPart.endIndex : token.oppositeIndex >= lexPart.startIndex ); } else { return checkToken(index, ruler, lexPart); } }; export class Pattern { // `ruler` can't be a pair or quantifier. Note: For performance, it won't check that. static searchOne(ruler, lexPart, leftToRight) { let lex = lexPart.lex; let startIndex = lexPart.startIndex; let endIndex = lexPart.endIndex; let i = leftToRight ? startIndex : endIndex; while (leftToRight ? i <= endIndex : i >= startIndex) { if (checkToken(i, ruler, lexPart)) { return i; } i = sameLevelNextIndex(i, lex, leftToRight); } 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. `lexPart` can be empty (`endIndex` smaller than `startIndex` by 1). `rulers` can also be empty. If both `lexPart` and `rulers` are empty then it will return an empty array, not `null`. Quantifiers can't be consecutive. Note: For performance, it won't check that. A ruler won't match the ending token of a "pair" or tokens in it. For example: lexPart: [LeftChevron, NormalToken "a", RightChevron], leftToRight: true If `rulers` is [LeftChevron], then the pattern will be matched. If `rulers` is [LeftChevron, NormalToken, RightChevron], then the pattern won't be matched. If `rulers` is [LeftChevron, RightChevron], then the pattern won't be matched. So, if `leftToRight` is true then the token representing the right side of the pair will be jumped. If false then the left will be jumped. But it's not a good practice to use `LeftChevron` or `RightChevron` for a ruler. Better use `ChevronPair`. `LeftChevron` or `RightChevron` is only useful inside a function ruler. */ static matchPattern(rulers, lexPart, leftToRight) { let lex = lexPart.lex; let startIndex = lexPart.startIndex; let endIndex = lexPart.endIndex; let nextLexCursor = leftToRight ? startIndex : endIndex; let nextPatternCursor = leftToRight ? 0 : rulers.length - 1; let result = []; let quantifierAfter = null; let quantifierTokenCount = 0; let quantifierStartTokenIndex = null; let k = 0; let previousRuler = null; while ( leftToRight ? nextLexCursor <= endIndex && nextPatternCursor < rulers.length : nextLexCursor >= startIndex && nextPatternCursor >= 0 ) { let lexCursor = nextLexCursor; let patternCursor = nextPatternCursor; let token = lex.at(lexCursor); let ruler = rulers[patternCursor]; if (quantifierAfter !== null) { nextPatternCursor += leftToRight ? 1 : -1; nextLexCursor = sameLevelNextIndexForPattern(quantifierAfter, lex, leftToRight); quantifierAfter = null; } else if (ruler instanceof AnyExcept || ruler instanceof TokensExcept) { if (ruler !== previousRuler) { quantifierStartTokenIndex = lexCursor; } if ( leftToRight ? ( patternCursor + 1 <= rulers.length - 1 && checkNonQuantifier(lexCursor, rulers[patternCursor + 1], lexPart, leftToRight) ) : ( patternCursor - 1 >= 0 && checkNonQuantifier(lexCursor, rulers[patternCursor - 1], lexPart, leftToRight) ) ) { quantifierAfter = lexCursor; } let isLastRuler = patternCursor === (leftToRight ? rulers.length - 1 : 0); let candidateNextLexCursor = sameLevelNextIndexForPattern(lexCursor, lex, leftToRight); let hasNoFurtherToken = leftToRight ? candidateNextLexCursor > endIndex : candidateNextLexCursor < startIndex; let exceptionFound = $tools.instanceof(token, ruler.tokenTypes); if (isLastRuler && hasNoFurtherToken) { if (exceptionFound) { return null; } nextLexCursor = candidateNextLexCursor; nextPatternCursor += leftToRight ? 1 : -1; } else if (quantifierAfter !== null || exceptionFound) { if (ruler instanceof TokensExcept && quantifierTokenCount === 0) { if (quantifierAfter !== null && !exceptionFound) { quantifierTokenCount++; nextLexCursor = candidateNextLexCursor; } else { return null; } } else { quantifierTokenCount = 0; nextPatternCursor += leftToRight ? 1 : -1; } } else { quantifierTokenCount++; nextLexCursor = candidateNextLexCursor; } } else if (checkNonQuantifier(lexCursor, ruler, lexPart, leftToRight)) { nextPatternCursor += leftToRight ? 1 : -1; nextLexCursor = sameLevelNextIndexForPattern(lexCursor, lex, leftToRight); } else { return null; } if ( k === 0 || ( patternCursor !== nextPatternCursor && patternCursor !== (leftToRight ? 0 : rulers.length - 1) ) ) { result.push(ruler instanceof Quantifier ? quantifierStartTokenIndex : lexCursor); } k++; previousRuler = ruler; } if ( ( leftToRight ? nextPatternCursor === rulers.length - 1 : nextPatternCursor === 0 ) && rulers[nextPatternCursor] instanceof AnyExcept ) { result.push(leftToRight ? endIndex + 1 : startIndex - 1); } else if ( leftToRight ? nextPatternCursor < rulers.length || nextLexCursor <= endIndex : nextPatternCursor >= 0 || nextLexCursor >= startIndex ) { return null; } if (!leftToRight && result.length > 0) { result.shift(); result.reverse(); result.forEach((m, index) => { result[index]++; }); result.unshift(startIndex); } return result; } // `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(rulers, lexPart, leftToRight, capture) { let match = this.matchPattern(rulers, 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; } // `ruler` can only be a token type or an array of token types. Note: For performance, // it won't check that. // If lex part doesn't contain the splitter, the returned range will be the whole lex part. // 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(ruler, lexPart) { let lex = lexPart.lex; let startIndex = lexPart.startIndex; let endIndex = lexPart.endIndex; let oldPos = startIndex; let r = []; let i = startIndex; while (i <= endIndex) { if (checkToken(i, ruler, lexPart)) { r.push({startIndex: oldPos, endIndex: i - 1}); oldPos = i + 1; } i = sameLevelNextIndex(i, lex, true); } r.push({startIndex: oldPos, endIndex: endIndex}); return r; } } class Quantifier {} // Including zero tokens. `anyExcept` and `any` are just shorthands. export class AnyExcept extends Quantifier { constructor(tokenTypes) { super(); 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 extends Quantifier { constructor(tokenTypes) { super(); this.tokenTypes = tokenTypes; } } export let tokensExcept = function(tokenTypes) { return new TokensExcept(tokenTypes); }; export let tokens = tokensExcept([]); class Pair {} export class ChevronPair extends Pair {} export class ParenthesisPair extends Pair {} export class BracketPair extends Pair {} export class BracePair extends Pair {} export class NormalParenthesisPair extends ParenthesisPair {} export class NormalBracketPair extends BracketPair {} export class NormalBracePair extends BracePair {} export class CallParenthesisPair extends ParenthesisPair {} export class CallBracketPair extends BracketPair {} export class CallBracePair extends BracePair {} export class PseudoCallParenthesisPair extends ParenthesisPair {}