futurescript
Version:
A functional style, but highly readable language that compiles to JavaScript.
1,199 lines (1,139 loc) • 40.5 kB
JavaScript
import * as $tools from "./tools.mjs";
import * as $lex from "./lex-core.mjs";
let Lex = $lex.Lex;
Lex.prototype._massiveRounds = function() {
this._round2();
this._round3();
this._round4();
this._round5();
this._round6();
this._round7();
this._round8();
this._round9();
this._round10();
this._round11();
this._round12();
this._round13();
this._round14();
this._round15();
this._round16();
this._round17();
this._round18();
this._round19();
this._round20();
this._round21();
this._round22();
this._round23();
this._round24();
this._round25();
this._round26();
this._round27();
this._round28();
this._round29();
this._round30();
this._round31();
this._round32();
};
// Check syntax error and let right-hand pair parts be more accurate.
// For example "CallLeftParenthesis, ... , RightParenthesis" will be normalized to
// "CallLeftParenthesis, ... , CallRightParenthesis".
Lex.prototype._round2 = function() {
let newValue = [];
let stack = [];
let opposite = left => {
if (left === $lex.LeftParenthesis) {
return $lex.RightParenthesis;
}
else if (left === $lex.NormalLeftParenthesis) {
return $lex.NormalRightParenthesis;
}
else if (left === $lex.CallLeftParenthesis) {
return $lex.CallRightParenthesis;
}
else if (left === $lex.PseudoCallLeftParenthesis) {
return $lex.PseudoCallRightParenthesis;
}
else if (left === $lex.LeftBracket) {
return $lex.RightBracket;
}
else if (left === $lex.NormalLeftBracket) {
return $lex.NormalRightBracket;
}
else if (left === $lex.CallLeftBracket) {
return $lex.CallRightBracket;
}
else if (left === $lex.LeftBrace) {
return $lex.RightBrace;
}
else if (left === $lex.NormalLeftBrace) {
return $lex.NormalRightBrace;
}
else if (left === $lex.CallLeftBrace) {
return $lex.CallRightBrace;
}
else if (left === $lex.LeftChevron) {
return $lex.RightChevron;
}
else {
throw new Error("No opposite.");
}
};
this.value.forEach((token, index) => {
if (
token instanceof $lex.LeftParenthesis ||
token instanceof $lex.LeftBracket ||
token instanceof $lex.LeftBrace ||
token instanceof $lex.LeftChevron
) {
stack.push(opposite(token.constructor));
newValue.push(token);
}
else if (
token instanceof $lex.RightParenthesis ||
token instanceof $lex.RightBracket ||
token instanceof $lex.RightBrace ||
token instanceof $lex.RightChevron
) {
if (token.constructor === stack[stack.length - 1]) {
newValue.push(token);
stack.pop();
}
else if ($tools.classIsClass(stack[stack.length - 1], token.constructor)) {
newValue.push(new stack[stack.length - 1]().copyPositionFrom(token));
stack.pop();
}
else {
throw new $lex.PunctuationPairError(this.part(index, index));
}
}
else {
newValue.push(token);
}
});
if (stack.length > 0) {
throw new $lex.PunctuationPairError(this.part(this.count() - 1, this.count() - 1));
}
this.value = newValue;
};
Lex.prototype._round3 = function() {
let min = null;
let arr = null;
this.value.forEach((token, index) => {
if (token instanceof $lex.Str && !token.type.isInline) {
if (token.isFirst) {
min = 9999;
arr = [];
}
let start = token.isFirst ? 1 : 0;
let end = token.isLast ? token.value.length - 1 : token.value.length;
for (let i = start; i < end; i++) {
if (token.value[i].content.trim() !== "" && token.value[i].indentOffset < min) {
min = token.value[i].indentOffset;
}
}
arr.push(token);
if (token.isLast) {
for (let i = 0; i < arr.length; i++) {
arr[i].minIndentOffset = min;
}
}
}
});
};
Lex.prototype._round4 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.Str) {
if (token.type.isInline) {
let s = "";
for (let i = 0; i < token.value.length; i++) {
if (i === 0 && this.value[index - 1] instanceof $lex.Plus) {
s += token.value[i].content;
}
else {
s += " ".repeat(token.value[i].indentOffset) + token.value[i].content;
}
}
newValue.push(token.replaceWith(s));
}
else {
let arr = [];
let start = token.isFirst ? 1 : 0;
let end = token.isLast ? token.value.length - 1 : token.value.length;
for (let i = start; i < end; i++) {
let spaceCount = token.value[i].indentOffset - token.minIndentOffset;
if (spaceCount < 0) { // empty line may make it negative
spaceCount = 0;
}
let s = null;
if (i === 0 && this.value[index - 1] instanceof $lex.Plus) {
s = token.value[i].content;
}
else {
s = " ".repeat(spaceCount) + token.value[i].content;
}
if (i > 0 && token.value[i - 1].isJoin && arr.length > 0) {
arr[arr.length - 1] += s;
}
else {
arr.push(s);
}
}
newValue.push(token.replaceWith(arr.join("\n")));
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
// This will add a parenthesis wrapper to make it easier to parse later.
Lex.prototype._round5 = function() {
let newValue = [];
let level = null;
this.value.forEach((token, index) => {
if (this.value[index - 1] instanceof $lex.Dot && token instanceof $lex.InlineNormalString) {
newValue.push(new $lex.NormalLeftParenthesis());
newValue.push(token);
level = 0;
}
else if (level !== null && token instanceof $lex.LeftParenthesis) {
newValue.push(token);
level++;
}
else if (level !== null && token instanceof $lex.RightParenthesis) {
newValue.push(token);
level--;
if (level === 0) {
newValue.push(new $lex.NormalRightParenthesis());
level = null;
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
// This will add a parenthesis wrapper to make things like `a.0` easier to parse later.
Lex.prototype._round6 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (this.value[index - 1] instanceof $lex.Dot && token instanceof $lex.Num) {
newValue.push(new $lex.NormalLeftParenthesis());
newValue.push(token);
newValue.push(new $lex.NormalRightParenthesis());
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
Lex.prototype._detectKeyword = function(normalToken) {
let t = null;
switch (normalToken.value) {
case "above":
t = $lex.Above; break;
case "and":
t = $lex.And; break;
case "as":
t = $lex.As; break;
case "catch":
t = $lex.Catch; break;
case "class":
t = $lex.Class; break;
case "delete":
t = $lex.Delete; break;
case "do":
t = $lex.Do; break;
case "else":
t = $lex.Else; break;
case "export":
t = $lex.Export; break;
case "false":
t = $lex.False; break;
case "finally":
t = $lex.Finally; break;
case "fun":
t = $lex.Fun; break;
case "if":
t = $lex.If; break;
case "ifnull":
t = $lex.Ifnull; break;
case "ifvoid":
t = $lex.Ifvoid; break;
case "import":
t = $lex.Import; break;
case "in":
t = $lex.In; break;
case "is":
t = $lex.Is; break;
case "isnt":
t = $lex.Isnt; break;
case "match":
t = $lex.Match; break;
case "me":
t = $lex.Me; break;
case "Me":
t = $lex.ClassMe; break;
case "mod":
t = $lex.Mod; break;
case "new":
t = $lex.New; break;
case "nonew":
t = $lex.Nonew; break;
case "not":
t = $lex.Not; break;
case "null":
t = $lex.Null; break;
case "or":
t = $lex.Or; break;
case "pause":
t = $lex.Pause; break;
case "rem":
t = $lex.Rem; break;
case "self":
t = $lex.Self; break;
case "super":
t = $lex.Super; break;
case "then":
t = $lex.Then; break;
case "throw":
t = $lex.Throw; break;
case "true":
t = $lex.True; break;
case "try":
t = $lex.Try; break;
case "void":
t = $lex.Void; break;
}
return t;
};
Lex.prototype._round7 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.NormalToken) {
let t = this._detectKeyword(token);
if (
t !== null &&
!(this.value[index - 1] instanceof $lex.Dot) &&
!(this.value[index - 1] instanceof $lex.NormalVariant) &&
!(
this.value[index + 1] instanceof $lex.Colon || (
this.value[index + 1] instanceof $lex.NormalVariant &&
this.value[index + 3] instanceof $lex.Colon
) || (
this.value[index + 1] instanceof $lex.NormalVariant &&
this.value[index + 3] instanceof $lex.NormalVariant &&
this.value[index + 5] instanceof $lex.Colon
)
) &&
!(newValue[newValue.length - 1] instanceof $lex.As)
) {
newValue.push(new t().copyPositionFrom(token).setOriginalTokens([token]));
}
else {
newValue.push(token);
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
Lex.prototype._round8 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (
(
this.value[index - 1] instanceof $lex.NormalToken ||
this.value[index - 1] instanceof $lex.RightBracket
) &&
token instanceof $lex.NormalToken &&
this.value[index + 1] instanceof $lex.Colon
) {
if (token.value === "ifvoid") {
newValue.push(new $lex.Ifvoid().copyPositionFrom(token));
}
else if (token.value === "ifnull") {
newValue.push(new $lex.Ifnull().copyPositionFrom(token));
}
else {
newValue.push(token);
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
Lex.prototype._round9 = function() {
let newValue = [];
let i = 0;
while (i < this.value.length) {
let token = this.value[i];
let next = this.value[i + 1];
if (token instanceof $lex.Export && next instanceof $lex.As) {
newValue.push(new $lex.ExportAs().start(i).end(i + 1).setOriginalTokens([token, next]));
i += 2;
}
else if (token instanceof $lex.Not && next instanceof $lex.Equal) {
newValue.push(new $lex.NotEqual().start(i).end(i + 1).setOriginalTokens([token, next]));
i += 2;
}
else if (token instanceof $lex.Not && next instanceof $lex.In) {
newValue.push(new $lex.NotIn().start(i).end(i + 1).setOriginalTokens([token, next]));
i += 2;
}
else if (token instanceof $lex.Not && next instanceof $lex.Is) {
newValue.push(new $lex.Isnt().start(i).end(i + 1).setOriginalTokens([token, next]));
i += 2;
}
else if (token instanceof $lex.Is && next instanceof $lex.Not) {
newValue.push(new $lex.Isnt().start(i).end(i + 1).setOriginalTokens([token, next]));
i += 2;
}
else if (token instanceof $lex.Question) {
newValue.push(new $lex.Then().copyPositionFrom(token).setOriginalTokens([token]));
i++;
}
else if (token instanceof $lex.Stick) {
newValue.push(new $lex.Else().copyPositionFrom(token).setOriginalTokens([token]));
i++;
}
else {
newValue.push(token);
i++;
}
}
this.value = newValue;
};
Lex.prototype._removeEmptyLines = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.Indent && (
index === this.value.length - 1 || this.value[index + 1] instanceof $lex.Indent
)) {
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
Lex.prototype._round10 = function() {
this._removeEmptyLines();
};
Lex.prototype._round11 = function() {
let newValue = [];
let ignoredTokens = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.InlineComment) {
ignoredTokens.push(token);
}
// Removing version directive is temporary, and just for simplification.
// In the final round we will recover version directive.
else if (token instanceof $lex.VersionDirective) {
this._versionDirective = token;
}
else if (
token instanceof $lex.FormattedComment &&
!(
this.value[index - 2] instanceof $lex.VersionDirective &&
this.value[index - 1] instanceof $lex.Indent &&
(index === this.value.length - 1 || this.value[index + 1] instanceof $lex.Indent)
)
) {
ignoredTokens.push(token);
}
else if (token instanceof $lex.Above) {
ignoredTokens.push(token);
}
else {
newValue.push(token);
}
});
this.value = newValue;
this.ignoredTokens = ignoredTokens;
};
Lex.prototype._round12 = function() {
this._removeEmptyLines();
};
// Each indent uses only 1 whitespace.
Lex.prototype._round13 = function() {
let newValue = [];
let stack = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.Indent) {
if (stack.length === 0 || token.value > stack[stack.length - 1]) {
let newIndentValue = stack.length;
stack.push(token.value);
newValue.push(token.replaceWith(newIndentValue));
}
else if (token.value === stack[stack.length - 1]) {
newValue.push(token.replaceWith(stack.length - 1));
}
else {
let found = stack.indexOf(token.value);
if (found !== -1) {
newValue.push(token.replaceWith(found));
stack.splice(found + 1, stack.length - found - 1);
}
else {
throw new $lex.IndentError(this.part(index, index));
}
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
// Normalize indents to all use inline "<<" and ">>" and ";".
Lex.prototype._round14 = function() {
let newValue = [];
let oldIndentValue = 0;
this.value.forEach((token, index) => {
if (token instanceof $lex.Indent) {
if (token.value > oldIndentValue) {
newValue.push(new $lex.LeftChevron().copyPositionFrom(token));
oldIndentValue = token.value;
}
else if (token.value < oldIndentValue) {
for (let i = 0; i < oldIndentValue - token.value; i++) {
newValue.push(new $lex.RightChevron().copyPositionFrom(token));
}
if (!(
this.value[index + 1] instanceof $lex.RightParenthesis ||
this.value[index + 1] instanceof $lex.RightBracket ||
this.value[index + 1] instanceof $lex.RightBrace
)) {
newValue.push(new $lex.Semicolon());
}
oldIndentValue = token.value;
}
else if (newValue.length !== 0) {
newValue.push(new $lex.Semicolon());
}
}
else {
newValue.push(token);
}
});
for (let i = 0; i < oldIndentValue; i++) {
newValue.push(new $lex.RightChevron());
}
this.value = newValue;
};
// Add necessary ";".
Lex.prototype._round15 = function() {
let newValue = [];
this.value.forEach((token, index) => {
newValue.push(token);
if (
token instanceof $lex.RightChevron &&
!(this.value[index + 1] instanceof $lex.RightChevron) &&
!(this.value[index + 1] instanceof $lex.Semicolon) &&
!(this.value[index + 1] instanceof $lex.RightParenthesis) &&
!(this.value[index + 1] instanceof $lex.RightBracket) &&
!(this.value[index + 1] instanceof $lex.RightBrace) &&
index !== this.value.length - 1
) {
newValue.push(new $lex.Semicolon());
}
});
this.value = newValue;
};
// Remove redundant ";".
Lex.prototype._round16 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.Semicolon) {
if (this.value[index - 1] instanceof $lex.Semicolon) {
}
else {
newValue.push(token);
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
// Remove redundant ";".
Lex.prototype._round17 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.Semicolon) {
if (index === 0) {
}
else if (index === this.value.length - 1) {
}
else if (this.value[index - 1] instanceof $lex.LeftChevron) {
}
else if (this.value[index + 1] instanceof $lex.RightChevron) {
}
else if (this.value[index - 1] instanceof $lex.LeftBrace) {
}
else if (this.value[index + 1] instanceof $lex.RightBrace) {
}
else if (this.value[index - 1] instanceof $lex.LeftBracket) {
}
else if (this.value[index + 1] instanceof $lex.RightBracket) {
}
else if (this.value[index - 1] instanceof $lex.LeftParenthesis) {
}
else if (this.value[index + 1] instanceof $lex.RightParenthesis) {
}
else {
newValue.push(token);
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
/*
Remove useless or incorrect indents and semicolons to combine multiple lines. Example:
a:
if true
1
else
2
Or:
a:
if true
1
else
2
Both normalized to:
a: if true
1
else
2
3 +
5
Normalized to:
3 + 5
Objects and arrays like:
{
a: 2
b: 3
}
Normalized to:
{a: 2, b: 3} (This is just JS equivalent. It's actually normalized to {a: 2; b: 3}.)
Function calls like:
a(
1
2
)
Normalized to:
a(1, 2) (This is just JS equivalent. It's actually normalized to a(1; 2).)
Note that we don't remove empty chevron pair, because empty chevron pair can be nested,
like "<< << >> << >> >>", removing it will be too complicated. Also, "(())"
isn't stripped, so there's no reason to strip empty chevrons.
*/
Lex.prototype._round18 = function() {
let newValue = [];
let stack = [];
this.value.forEach((token, index) => {
let prev = this.value[index - 1];
let next = this.value[index + 1];
if (token instanceof $lex.LeftChevron) {
if (newValue[newValue.length - 1] instanceof $lex.Join) {
newValue.pop();
stack.push(true);
}
else if (
prev instanceof $lex.LeftParenthesis ||
prev instanceof $lex.LeftBracket ||
prev instanceof $lex.LeftBrace ||
(prev !== undefined && prev.constructor.isJoint) ||
(next !== undefined && next.constructor.isJoint)
) {
stack.push(true);
}
else {
stack.push(false);
newValue.push(token);
}
}
else if (token instanceof $lex.RightChevron) {
if (stack.pop()) {
}
else {
newValue.push(token);
}
}
else if (token instanceof $lex.Semicolon) {
if (newValue[newValue.length - 1] instanceof $lex.Join) {
newValue.pop();
}
else if (
(prev !== undefined && prev.constructor.isJoint) ||
(next !== undefined && next.constructor.isJoint)
) {
}
else {
newValue.push(token);
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
// Remove ";" if "," is before ";".
Lex.prototype._round19 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (
this.value[index - 1] instanceof $lex.Comma &&
token instanceof $lex.Semicolon
) {
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
// Remove incorrect ";" before token that can't be statement start. But this doesn't
// include "else" because "else" may appear in "match", where "else" can be after ";".
Lex.prototype._round20 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (
token instanceof $lex.Semicolon &&
this.value[index + 1].constructor.expressionStartForbidden &&
!this.value[index + 1].constructor.isCommand &&
!(this.value[index + 1] instanceof $lex.Else)
) {
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
// Remove incorrect ";" before "else" that's not in "match".
Lex.prototype._round21 = function() {
let newValue = [];
let stack = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.Match) {
stack.push({position: index, kind: $lex.Match});
newValue.push(token);
}
else if (token instanceof $lex.LeftChevron) {
stack.push({position: index, kind: $lex.LeftChevron});
newValue.push(token);
}
else if (token instanceof $lex.LeftParenthesis) {
stack.push({position: index, kind: $lex.LeftParenthesis});
newValue.push(token);
}
else if (token instanceof $lex.LeftBracket) {
stack.push({position: index, kind: $lex.LeftBracket});
newValue.push(token);
}
else if (token instanceof $lex.LeftBrace) {
stack.push({position: index, kind: $lex.LeftBrace});
newValue.push(token);
}
else if (
token instanceof $lex.RightChevron ||
token instanceof $lex.RightParenthesis ||
token instanceof $lex.RightBracket ||
token instanceof $lex.RightBrace
) {
if (stack[stack.length - 1].kind === $lex.Match) {
throw new $lex.MatchNoBodyError(this.part(stack[stack.length - 1].position, index));
}
stack.pop();
if (
token instanceof $lex.RightChevron &&
stack.length > 0 &&
stack[stack.length - 1].kind === $lex.Match
) {
stack.pop();
}
newValue.push(token);
}
else if (token instanceof $lex.Semicolon && this.value[index + 1] instanceof $lex.Else) {
if (
stack.length >= 2 &&
stack[stack.length - 2].kind === $lex.Match &&
stack[stack.length - 1].kind === $lex.LeftChevron
) {
newValue.push(token);
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
// Two features: Mark levels and containers, and correct the default export token.
// This is also the beginning period of processing flexible objects.
// Note: We set many properties of tokens in this method, but these properties are
// just for temporary use. Other modules should not use them.
Lex.prototype._round22 = function() {
let newValue = [];
let level = 0;
let stack = [null];
// Brace container doesn't mean brace's container, but the container is brace type.
this._braceContainers = [];
this.value.forEach((token, index) => {
if (
token instanceof $lex.LeftChevron ||
token instanceof $lex.LeftParenthesis ||
token instanceof $lex.LeftBracket ||
token instanceof $lex.LeftBrace
) {
token.level = level;
token.container = stack[stack.length - 1];
level++;
if (token instanceof $lex.LeftChevron) {
stack.push({type: $lex.Chevron});
}
else if (token instanceof $lex.LeftParenthesis) {
stack.push({type: $lex.Parenthesis});
}
else if (token instanceof $lex.LeftBracket) {
stack.push({type: $lex.Bracket});
}
else if (token instanceof $lex.LeftBrace) {
let container = {type: $lex.Brace};
stack.push(container);
this._braceContainers.push(container);
}
stack[stack.length - 1].leftIndex = index; // the left wrapper, like `{`
stack[stack.length - 1].leftToken = token;
token.selfContainer = stack[stack.length - 1]; // only wrapper tokens have `selfContainer`
newValue.push(token);
}
else if (
token instanceof $lex.RightChevron ||
token instanceof $lex.RightParenthesis ||
token instanceof $lex.RightBracket ||
token instanceof $lex.RightBrace
) {
stack[stack.length - 1].rightIndex = index; // the right wrapper, like `}`
stack[stack.length - 1].rightToken = token;
token.selfContainer = stack[stack.length - 1];
level--;
stack.pop();
token.level = level;
token.container = stack[stack.length - 1];
newValue.push(token);
}
else if (
level === 0 &&
(index === 0 || this.value[index - 1] instanceof $lex.Semicolon) &&
token instanceof $lex.NormalToken && token.value === "export" &&
this.value[index + 1] instanceof $lex.Colon
) {
let newToken = new $lex.Export().copyPositionFrom(token);
newToken.level = level;
newToken.container = stack[stack.length - 1];
newValue.push(newToken);
}
else {
token.level = level;
token.container = stack[stack.length - 1];
newValue.push(token);
}
});
this.value = newValue;
};
// We will call this method not only once. Because some operations will change the tokens,
// we need to call it to update the container.
Lex.prototype._fillBraceContainers = function() {
this._braceContainers.forEach(container => {
container.tokens = [];
});
this.value.forEach((token, index) => {
token.index = index;
if (token.container !== null && token.container.type === $lex.Brace) {
token.container.tokens.push(token);
}
if (token.selfContainer !== undefined) {
if (token instanceof $lex.LeftBrace) {
token.selfContainer.leftIndex = index;
token.selfContainer.leftToken = token;
}
else if (token instanceof $lex.RightBrace) {
token.selfContainer.rightIndex = index;
token.selfContainer.rightToken = token;
}
}
});
this._braceContainers.forEach(container => {
if (container.exotic) {
let functionPos = container.tokens.findIndex(m =>
m instanceof $lex.ArrowFunction ||
m instanceof $lex.DashFunction ||
m instanceof $lex.DiamondFunction
);
container.exoticEndInternalIndex =
functionPos === -1 ? container.tokens.length - 1 : functionPos;
}
});
};
Lex.prototype._round23 = function() {
this._fillBraceContainers();
};
// Check every brace container to find if it's exotic or not.
Lex.prototype._round24 = function() {
this._braceContainers.forEach(container => {
if (
container !== null && container.type === $lex.Brace &&
!(this.value[container.leftIndex - 1] instanceof $lex.As) &&
!(this.value[container.rightIndex + 1] instanceof $lex.Colon)
) {
container.isExpression = true;
if (container.tokens.some(m => m instanceof $lex.Colon)) {
container.exotic = false;
}
else {
let splitterPos = -1;
for (let i = 0; i < container.tokens.length; i++) {
let token = container.tokens[i];
if (
i === container.tokens.length - 1 ||
token instanceof $lex.Comma ||
token instanceof $lex.Semicolon
) {
// If it's at the end, there will be no comma or semicolon so the limit
// should be subtracted by 1.
let limit = i === container.tokens.length - 1 ? 1 : 2;
if (i - splitterPos > limit) {
container.exotic = true;
let functionPos = container.tokens.findIndex(m =>
m instanceof $lex.ArrowFunction ||
m instanceof $lex.DashFunction ||
m instanceof $lex.DiamondFunction
);
container.exoticEndInternalIndex =
functionPos === -1 ? container.tokens.length - 1 : functionPos;
break;
}
splitterPos = i;
}
}
if (container.exotic === undefined) {
container.exotic = false;
}
}
}
else {
container.isExpression = false;
}
});
};
// For exotic brace, remove commas and semicolons and convert keyword to its original `NormalToken`
// form.
Lex.prototype._round25 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (
token.container !== null && token.container.type === $lex.Brace &&
token.container.isExpression && token.container.exotic
) {
if (token instanceof $lex.Comma || token instanceof $lex.Semicolon) {
}
else if (
index <= token.container.tokens[token.container.exoticEndInternalIndex].index &&
token.getOriginalTokens() !== null &&
token.getOriginalTokens().some(m => m instanceof $lex.NormalToken)
) {
token.getOriginalTokens().forEach(originalToken => {
originalToken.container = token.container;
originalToken.isProbablyKeyword = true;
newValue.push(originalToken);
});
}
else {
newValue.push(token);
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
Lex.prototype._round26 = function() {
this._fillBraceContainers();
};
// This will enable all tokens to act like a linked list. Useful in next round,
// because in next round tokens will be traversed, modified or inserted not in order.
Lex.prototype._round27 = function() {
this.value.forEach((token, index) => {
if (index === this.value.length - 1) {
token.next = null;
}
else {
token.next = this.value[index + 1];
}
});
};
// This is the core round of processing flexible objects.
Lex.prototype._round28 = function() {
this._braceContainers.forEach(container => {
if (container.isExpression) {
if (container.exotic) {
let isKey = false;
for (let i = container.exoticEndInternalIndex; i >= 1; i--) {
if (
(isKey && container.tokens[i] instanceof $lex.NormalToken) ||
(
!isKey &&
container.tokens[i - 1] instanceof $lex.NormalToken &&
!container.tokens[i].constructor.isJoint &&
!(
container.tokens[i] instanceof $lex.CallLeftParenthesis ||
container.tokens[i] instanceof $lex.CallLeftBracket ||
container.tokens[i] instanceof $lex.CallLeftBrace ||
container.tokens[i] instanceof $lex.LeftChevron
) &&
!container.tokens[i].constructor.expressionStartForbidden
)
) {
if (isKey) {
let newToken = new $lex.Comma();
newToken.next = container.tokens[i];
container.tokens[i - 1].next = newToken;
}
else {
let newToken = new $lex.Colon();
newToken.next = container.tokens[i];
container.tokens[i - 1].next = newToken;
container.tokens[i - 1].isProbablyKeyword = false;
}
isKey = !isKey; // switch between key and value
}
}
if (!isKey) { // if the total number of keys and values is odd
let inlineNormalString = new $lex.InlineNormalString();
let pseudoCallLeftParenthesis = new $lex.PseudoCallLeftParenthesis();
let str = new $lex.Str("");
let pseudoCallRightParenthesis = new $lex.PseudoCallRightParenthesis();
let colon = new $lex.Colon();
inlineNormalString.next = pseudoCallLeftParenthesis;
pseudoCallLeftParenthesis.next = str;
str.next = pseudoCallRightParenthesis;
pseudoCallRightParenthesis.next = colon;
colon.next = container.tokens[0];
this.value[container.leftIndex].next = inlineNormalString;
}
}
else if (container.tokens.length >= 1) {
let hasColon = false;
container.tokens.concat([this.value[container.rightIndex]]).forEach((token, index) => {
if (
token instanceof $lex.Comma ||
token instanceof $lex.Semicolon ||
token === this.value[container.rightIndex]
) {
if (!hasColon) {
let newColon = new $lex.Colon();
let newTrue = new $lex.True();
newColon.next = newTrue;
newTrue.next = token;
if (container.tokens[index - 1] instanceof $lex.Keyword) {
let normalToken = container.tokens[index - 1].getOriginalTokens()[0];
normalToken.next = newColon;
if (index >= 2) {
container.tokens[index - 2].next = normalToken;
}
else {
this.value[container.leftIndex].next = normalToken;
}
}
else {
container.tokens[index - 1].next = newColon;
}
}
hasColon = false;
}
else if (token instanceof $lex.Colon) {
hasColon = true;
}
});
}
}
});
};
Lex.prototype._round29 = function() {
if (this.value.length === 0) return;
let newValue = [];
let token = this.value[0];
while (token !== null) {
newValue.push(token);
token = token.next;
}
this.value = newValue;
};
// Recover all keyword-like tokens that's not keys to be keywords again in exotic brace.
// This is the end of processing flexible objects.
Lex.prototype._round30 = function() {
let newValue = [];
this.value.forEach((token, index) => {
if (token instanceof $lex.NormalToken && token.isProbablyKeyword) {
let t = this._detectKeyword(token);
if (t !== null) {
newValue.push(new t().copyPositionFrom(token).setOriginalTokens([token]));
}
else {
newValue.push(token);
}
}
else {
newValue.push(token);
}
});
this.value = newValue;
};
Lex.prototype._round31 = function() {
let newValue = [this._versionDirective];
this.value.forEach(token => {
newValue.push(token);
});
this.value = newValue;
};
// `oppositeIndex` property will be used by parser to scan fast.
Lex.prototype._round32 = function() {
let stack = [];
this.value.forEach((token, index) => {
if (
token instanceof $lex.LeftChevron ||
token instanceof $lex.LeftParenthesis ||
token instanceof $lex.LeftBracket ||
token instanceof $lex.LeftBrace
) {
stack.push([token, index]);
}
else if (
token instanceof $lex.RightChevron ||
token instanceof $lex.RightParenthesis ||
token instanceof $lex.RightBracket ||
token instanceof $lex.RightBrace
) {
stack[stack.length - 1][0].oppositeIndex = index;
token.oppositeIndex = stack[stack.length - 1][1];
stack.pop();
}
});
};