| // The MIT License (MIT) |
| |
| // Copyright (c) 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Simon Lydell |
| |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| // THE SOFTWARE. |
| |
| "use strict"; |
| |
| function jsTokensWrapperFunction() { |
| var Identifier, JSXIdentifier, JSXPunctuator, JSXString, JSXText, KeywordsWithExpressionAfter, KeywordsWithNoLineTerminatorAfter, LineTerminatorSequence, MultiLineComment, Newline, NumericLiteral, Punctuator, RegularExpressionLiteral, SingleLineComment, StringLiteral, Template, TokensNotPrecedingObjectLiteral, TokensPrecedingExpression, WhiteSpace, jsTokens; |
| RegularExpressionLiteral = /\/(?![*\/])(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\\]).|\\.)*(\/[$_\u200C\u200D\p{ID_Continue}]*|\\)?/yu; |
| Punctuator = /--|\+\+|=>|\.{3}|\??\.(?!\d)|(?:&&|\|\||\?\?|[+\-%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2}|\/(?![\/*]))=?|[?~,:;[\](){}]/y; |
| Identifier = /(\x23?)(?=[$_\p{ID_Start}\\])(?:[$_\u200C\u200D\p{ID_Continue}]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+/yu; |
| StringLiteral = /(['"])(?:(?!\1)[^\\\n\r]|\\(?:\r\n|[^]))*(\1)?/y; |
| NumericLiteral = /(?:0[xX][\da-fA-F](?:_?[\da-fA-F])*|0[oO][0-7](?:_?[0-7])*|0[bB][01](?:_?[01])*)n?|0n|[1-9](?:_?\d)*n|(?:(?:0(?!\d)|0\d*[89]\d*|[1-9](?:_?\d)*)(?:\.(?:\d(?:_?\d)*)?)?|\.\d(?:_?\d)*)(?:[eE][+-]?\d(?:_?\d)*)?|0[0-7]+/y; |
| Template = /[`}](?:[^`\\$]|\\[^]|\$(?!\{))*(`|\$\{)?/y; |
| WhiteSpace = /[\t\v\f\ufeff\p{Zs}]+/yu; |
| LineTerminatorSequence = /\r?\n|[\r\u2028\u2029]/y; |
| MultiLineComment = /\/\*(?:[^*]|\*(?!\/))*(\*\/)?/y; |
| SingleLineComment = /\/\/.*/y; |
| JSXPunctuator = /[<>.:={}]|\/(?![\/*])/y; |
| JSXIdentifier = /[$_\p{ID_Start}][$_\u200C\u200D\p{ID_Continue}-]*/yu; |
| JSXString = /(['"])(?:(?!\1)[^])*(\1)?/y; |
| JSXText = /[^<>{}]+/y; |
| TokensPrecedingExpression = /^(?:[\/+-]|\.{3}|\?(?:InterpolationIn(?:JSX|Template)|NoLineTerminatorHere|NonExpressionParenEnd|UnaryIncDec))?$|[{}([,;<>=*%&|^!~?:]$/; |
| TokensNotPrecedingObjectLiteral = /^(?:=>|[;\]){}]|else|\?(?:NoLineTerminatorHere|NonExpressionParenEnd))?$/; |
| KeywordsWithExpressionAfter = /^(?:await|case|default|delete|do|else|instanceof|new|return|throw|typeof|void|yield)$/; |
| KeywordsWithNoLineTerminatorAfter = /^(?:return|throw|yield)$/; |
| Newline = RegExp(LineTerminatorSequence.source); |
| return function*(input, {jsx = false} = {}) { |
| var braces, firstCodePoint, isExpression, lastIndex, lastSignificantToken, length, match, mode, nextLastIndex, nextLastSignificantToken, parenNesting, postfixIncDec, punctuator, stack; |
| ({length} = input); |
| lastIndex = 0; |
| lastSignificantToken = ""; |
| stack = [ |
| {tag: "JS"} |
| ]; |
| braces = []; |
| parenNesting = 0; |
| postfixIncDec = false; |
| while (lastIndex < length) { |
| mode = stack[stack.length - 1]; |
| switch (mode.tag) { |
| case "JS": |
| case "JSNonExpressionParen": |
| case "InterpolationInTemplate": |
| case "InterpolationInJSX": |
| if (input[lastIndex] === "/" && (TokensPrecedingExpression.test(lastSignificantToken) || KeywordsWithExpressionAfter.test(lastSignificantToken))) { |
| RegularExpressionLiteral.lastIndex = lastIndex; |
| if (match = RegularExpressionLiteral.exec(input)) { |
| lastIndex = RegularExpressionLiteral.lastIndex; |
| lastSignificantToken = match[0]; |
| postfixIncDec = true; |
| yield ({ |
| type: "RegularExpressionLiteral", |
| value: match[0], |
| closed: match[1] !== void 0 && match[1] !== "\\" |
| }); |
| continue; |
| } |
| } |
| Punctuator.lastIndex = lastIndex; |
| if (match = Punctuator.exec(input)) { |
| punctuator = match[0]; |
| nextLastIndex = Punctuator.lastIndex; |
| nextLastSignificantToken = punctuator; |
| switch (punctuator) { |
| case "(": |
| if (lastSignificantToken === "?NonExpressionParenKeyword") { |
| stack.push({ |
| tag: "JSNonExpressionParen", |
| nesting: parenNesting |
| }); |
| } |
| parenNesting++; |
| postfixIncDec = false; |
| break; |
| case ")": |
| parenNesting--; |
| postfixIncDec = true; |
| if (mode.tag === "JSNonExpressionParen" && parenNesting === mode.nesting) { |
| stack.pop(); |
| nextLastSignificantToken = "?NonExpressionParenEnd"; |
| postfixIncDec = false; |
| } |
| break; |
| case "{": |
| Punctuator.lastIndex = 0; |
| isExpression = !TokensNotPrecedingObjectLiteral.test(lastSignificantToken) && (TokensPrecedingExpression.test(lastSignificantToken) || KeywordsWithExpressionAfter.test(lastSignificantToken)); |
| braces.push(isExpression); |
| postfixIncDec = false; |
| break; |
| case "}": |
| switch (mode.tag) { |
| case "InterpolationInTemplate": |
| if (braces.length === mode.nesting) { |
| Template.lastIndex = lastIndex; |
| match = Template.exec(input); |
| lastIndex = Template.lastIndex; |
| lastSignificantToken = match[0]; |
| if (match[1] === "${") { |
| lastSignificantToken = "?InterpolationInTemplate"; |
| postfixIncDec = false; |
| yield ({ |
| type: "TemplateMiddle", |
| value: match[0] |
| }); |
| } else { |
| stack.pop(); |
| postfixIncDec = true; |
| yield ({ |
| type: "TemplateTail", |
| value: match[0], |
| closed: match[1] === "`" |
| }); |
| } |
| continue; |
| } |
| break; |
| case "InterpolationInJSX": |
| if (braces.length === mode.nesting) { |
| stack.pop(); |
| lastIndex += 1; |
| lastSignificantToken = "}"; |
| yield ({ |
| type: "JSXPunctuator", |
| value: "}" |
| }); |
| continue; |
| } |
| } |
| postfixIncDec = braces.pop(); |
| nextLastSignificantToken = postfixIncDec ? "?ExpressionBraceEnd" : "}"; |
| break; |
| case "]": |
| postfixIncDec = true; |
| break; |
| case "++": |
| case "--": |
| nextLastSignificantToken = postfixIncDec ? "?PostfixIncDec" : "?UnaryIncDec"; |
| break; |
| case "<": |
| if (jsx && (TokensPrecedingExpression.test(lastSignificantToken) || KeywordsWithExpressionAfter.test(lastSignificantToken))) { |
| stack.push({tag: "JSXTag"}); |
| lastIndex += 1; |
| lastSignificantToken = "<"; |
| yield ({ |
| type: "JSXPunctuator", |
| value: punctuator |
| }); |
| continue; |
| } |
| postfixIncDec = false; |
| break; |
| default: |
| postfixIncDec = false; |
| } |
| lastIndex = nextLastIndex; |
| lastSignificantToken = nextLastSignificantToken; |
| yield ({ |
| type: "Punctuator", |
| value: punctuator |
| }); |
| continue; |
| } |
| Identifier.lastIndex = lastIndex; |
| if (match = Identifier.exec(input)) { |
| lastIndex = Identifier.lastIndex; |
| nextLastSignificantToken = match[0]; |
| switch (match[0]) { |
| case "for": |
| case "if": |
| case "while": |
| case "with": |
| if (lastSignificantToken !== "." && lastSignificantToken !== "?.") { |
| nextLastSignificantToken = "?NonExpressionParenKeyword"; |
| } |
| } |
| lastSignificantToken = nextLastSignificantToken; |
| postfixIncDec = !KeywordsWithExpressionAfter.test(match[0]); |
| yield ({ |
| type: match[1] === "#" ? "PrivateIdentifier" : "IdentifierName", |
| value: match[0] |
| }); |
| continue; |
| } |
| StringLiteral.lastIndex = lastIndex; |
| if (match = StringLiteral.exec(input)) { |
| lastIndex = StringLiteral.lastIndex; |
| lastSignificantToken = match[0]; |
| postfixIncDec = true; |
| yield ({ |
| type: "StringLiteral", |
| value: match[0], |
| closed: match[2] !== void 0 |
| }); |
| continue; |
| } |
| NumericLiteral.lastIndex = lastIndex; |
| if (match = NumericLiteral.exec(input)) { |
| lastIndex = NumericLiteral.lastIndex; |
| lastSignificantToken = match[0]; |
| postfixIncDec = true; |
| yield ({ |
| type: "NumericLiteral", |
| value: match[0] |
| }); |
| continue; |
| } |
| Template.lastIndex = lastIndex; |
| if (match = Template.exec(input)) { |
| lastIndex = Template.lastIndex; |
| lastSignificantToken = match[0]; |
| if (match[1] === "${") { |
| lastSignificantToken = "?InterpolationInTemplate"; |
| stack.push({ |
| tag: "InterpolationInTemplate", |
| nesting: braces.length |
| }); |
| postfixIncDec = false; |
| yield ({ |
| type: "TemplateHead", |
| value: match[0] |
| }); |
| } else { |
| postfixIncDec = true; |
| yield ({ |
| type: "NoSubstitutionTemplate", |
| value: match[0], |
| closed: match[1] === "`" |
| }); |
| } |
| continue; |
| } |
| break; |
| case "JSXTag": |
| case "JSXTagEnd": |
| JSXPunctuator.lastIndex = lastIndex; |
| if (match = JSXPunctuator.exec(input)) { |
| lastIndex = JSXPunctuator.lastIndex; |
| nextLastSignificantToken = match[0]; |
| switch (match[0]) { |
| case "<": |
| stack.push({tag: "JSXTag"}); |
| break; |
| case ">": |
| stack.pop(); |
| if (lastSignificantToken === "/" || mode.tag === "JSXTagEnd") { |
| nextLastSignificantToken = "?JSX"; |
| postfixIncDec = true; |
| } else { |
| stack.push({tag: "JSXChildren"}); |
| } |
| break; |
| case "{": |
| stack.push({ |
| tag: "InterpolationInJSX", |
| nesting: braces.length |
| }); |
| nextLastSignificantToken = "?InterpolationInJSX"; |
| postfixIncDec = false; |
| break; |
| case "/": |
| if (lastSignificantToken === "<") { |
| stack.pop(); |
| if (stack[stack.length - 1].tag === "JSXChildren") { |
| stack.pop(); |
| } |
| stack.push({tag: "JSXTagEnd"}); |
| } |
| } |
| lastSignificantToken = nextLastSignificantToken; |
| yield ({ |
| type: "JSXPunctuator", |
| value: match[0] |
| }); |
| continue; |
| } |
| JSXIdentifier.lastIndex = lastIndex; |
| if (match = JSXIdentifier.exec(input)) { |
| lastIndex = JSXIdentifier.lastIndex; |
| lastSignificantToken = match[0]; |
| yield ({ |
| type: "JSXIdentifier", |
| value: match[0] |
| }); |
| continue; |
| } |
| JSXString.lastIndex = lastIndex; |
| if (match = JSXString.exec(input)) { |
| lastIndex = JSXString.lastIndex; |
| lastSignificantToken = match[0]; |
| yield ({ |
| type: "JSXString", |
| value: match[0], |
| closed: match[2] !== void 0 |
| }); |
| continue; |
| } |
| break; |
| case "JSXChildren": |
| JSXText.lastIndex = lastIndex; |
| if (match = JSXText.exec(input)) { |
| lastIndex = JSXText.lastIndex; |
| lastSignificantToken = match[0]; |
| yield ({ |
| type: "JSXText", |
| value: match[0] |
| }); |
| continue; |
| } |
| switch (input[lastIndex]) { |
| case "<": |
| stack.push({tag: "JSXTag"}); |
| lastIndex++; |
| lastSignificantToken = "<"; |
| yield ({ |
| type: "JSXPunctuator", |
| value: "<" |
| }); |
| continue; |
| case "{": |
| stack.push({ |
| tag: "InterpolationInJSX", |
| nesting: braces.length |
| }); |
| lastIndex++; |
| lastSignificantToken = "?InterpolationInJSX"; |
| postfixIncDec = false; |
| yield ({ |
| type: "JSXPunctuator", |
| value: "{" |
| }); |
| continue; |
| } |
| } |
| WhiteSpace.lastIndex = lastIndex; |
| if (match = WhiteSpace.exec(input)) { |
| lastIndex = WhiteSpace.lastIndex; |
| yield ({ |
| type: "WhiteSpace", |
| value: match[0] |
| }); |
| continue; |
| } |
| LineTerminatorSequence.lastIndex = lastIndex; |
| if (match = LineTerminatorSequence.exec(input)) { |
| lastIndex = LineTerminatorSequence.lastIndex; |
| postfixIncDec = false; |
| if (KeywordsWithNoLineTerminatorAfter.test(lastSignificantToken)) { |
| lastSignificantToken = "?NoLineTerminatorHere"; |
| } |
| yield ({ |
| type: "LineTerminatorSequence", |
| value: match[0] |
| }); |
| continue; |
| } |
| MultiLineComment.lastIndex = lastIndex; |
| if (match = MultiLineComment.exec(input)) { |
| lastIndex = MultiLineComment.lastIndex; |
| if (Newline.test(match[0])) { |
| postfixIncDec = false; |
| if (KeywordsWithNoLineTerminatorAfter.test(lastSignificantToken)) { |
| lastSignificantToken = "?NoLineTerminatorHere"; |
| } |
| } |
| yield ({ |
| type: "MultiLineComment", |
| value: match[0], |
| closed: match[1] !== void 0 |
| }); |
| continue; |
| } |
| SingleLineComment.lastIndex = lastIndex; |
| if (match = SingleLineComment.exec(input)) { |
| lastIndex = SingleLineComment.lastIndex; |
| postfixIncDec = false; |
| yield ({ |
| type: "SingleLineComment", |
| value: match[0] |
| }); |
| continue; |
| } |
| firstCodePoint = String.fromCodePoint(input.codePointAt(lastIndex)); |
| lastIndex += firstCodePoint.length; |
| lastSignificantToken = firstCodePoint; |
| postfixIncDec = false; |
| yield ({ |
| type: mode.tag.startsWith("JSX") ? "JSXInvalid" : "Invalid", |
| value: firstCodePoint |
| }); |
| } |
| }; |
| }; |
| |
| const jsTokensSourceCode = jsTokensWrapperFunction.toString(); |
| const jsxSourceCode = ` |
| |
| function Comment(props) { |
| return ( |
| <div className="Comment"> |
| <div className="UserInfo"> |
| <Avatar user={props.author} /> |
| <div className="UserInfo-name"> |
| {props.author.name} |
| </div> |
| </div> |
| <div className="Comment-text"> |
| {props.text} |
| </div> |
| <div className="Comment-date"> |
| {formatDate(props.date)} |
| </div> |
| </div> |
| ); |
| } |
| |
| `; |
| |
| const jsTokens = jsTokensWrapperFunction(); |
| |
| class Benchmark { |
| runIteration() { |
| this.tokenCount = 0; |
| |
| for (var i = 0; i < 25; i++) { |
| for (const token of jsTokens(jsTokensSourceCode)) |
| this.tokenCount++; |
| } |
| |
| for (var i = 0; i < 250; i++) { |
| for (const token of jsTokens(jsxSourceCode, { jsx: true })) |
| this.tokenCount++; |
| } |
| } |
| |
| validate() { |
| if (this.tokenCount !== 113975) |
| throw new Error(`this.tokenCount of ${this.tokenCount} is invalid!`); |
| } |
| } |