diff --git a/CMakeLists.txt b/CMakeLists.txt index 8548984d..dadf7a22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,9 @@ if(${COVERAGE}) ) endif() endif() -set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +if(CMAKE_BUILD_TYPE STREQUAL "Release") + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) +endif () # compile if (MSVC) add_compile_options(/Zc:preprocessor) diff --git a/src/babel/babel-arena/memory.hpp b/src/babel/babel-arena/memory.hpp index d5cccb71..91724a1f 100644 --- a/src/babel/babel-arena/memory.hpp +++ b/src/babel/babel-arena/memory.hpp @@ -31,7 +31,7 @@ namespace BabelArena { approximate_size = codeSize * 10.0; break; case static_cast(MemoryType::String): - approximate_size = codeSize / 50.0; + approximate_size = codeSize / 10.0; break; case static_cast(MemoryType::Identifier): approximate_size = codeSize * 10.0; diff --git a/src/babel/babel-generator/generators/base.hpp b/src/babel/babel-generator/generators/base.hpp index 38721020..3467c308 100644 --- a/src/babel/babel-generator/generators/base.hpp +++ b/src/babel/babel-generator/generators/base.hpp @@ -77,17 +77,25 @@ namespace BabelGenerator::Generators { // NOTE: In directives we can't change escapings, // because they change the behavior. // e.g. "us\x65 strict" (\x65 is e) is not a "use strict" directive. - - // if (!unescapedDoubleQuoteRE.test(value)) { - // _this.token(`"${value}"`); - // } else if (!unescapedSingleQuoteRE.test(value)) { - // _this.token(`'${value}'`); - // } else { - // throw new Error( - // "Malformed AST: it is not possible to print a directive containing" + - // " both unescaped single and double quotes.", - // ); - // } + size_t slash_count = 0; + bool is_double_escaped = false; + bool is_single_escaped = false; + for (auto ch: value) { + if (ch == u'\\') { slash_count++; } + else if (ch == u'\'') { is_single_escaped |= slash_count % 2 == 1; } + else if (ch == u'\"') { is_double_escaped |= slash_count % 2 == 1; } + else { slash_count = 0; } + } + if (!is_double_escaped) { + _this.token(BASE::UStringViewArray<3>{u"\"", value, u"\""}); + } else if (!is_single_escaped) { + _this.token(BASE::UStringViewArray<3>{u"\'", value, u"\'"}); + } else { + throw new BASE::JavaScript::Error( + u"Malformed AST: it is not possible to print a directive containing" + u" both unescaped single and double quotes." + ); + } } DEF_GENERATOR(InterpreterDirective, node, _) { diff --git a/src/babel/babel-generator/generators/jsx.hpp b/src/babel/babel-generator/generators/jsx.hpp index 457076f5..bfc39875 100644 --- a/src/babel/babel-generator/generators/jsx.hpp +++ b/src/babel/babel-generator/generators/jsx.hpp @@ -52,9 +52,9 @@ namespace BabelGenerator::Generators { auto raw = _this.getPossibleRaw(node); if (raw != std::nullopt) { - _this.token(*raw, true); + _this.template token(*raw); } else { - _this.token(node->value, true); + _this.template token(node->value); } } diff --git a/src/babel/babel-generator/generators/methods.hpp b/src/babel/babel-generator/generators/methods.hpp index 76ce6ff0..174e949f 100644 --- a/src/babel/babel-generator/generators/methods.hpp +++ b/src/babel/babel-generator/generators/methods.hpp @@ -116,13 +116,13 @@ namespace BabelGenerator::Generators { _this.print(key); } - // if ( - // // @ts-expect-error optional is not in ObjectMethod - // node->optional - // ) { - // // TS - // _this.token(u"?"); - // } + if ( + // @ts-expect-error optional is not in ObjectMethod + node->isOptional() + ) { + // TS + _this.token(u"?"); + } _params(_this, node, node->computed && node->key()->type() != Node::Type::StringLiteral ? nullptr : node->key(), nullptr); } diff --git a/src/babel/babel-generator/generators/template-literals.hpp b/src/babel/babel-generator/generators/template-literals.hpp index 684c52be..bdc37112 100644 --- a/src/babel/babel-generator/generators/template-literals.hpp +++ b/src/babel/babel-generator/generators/template-literals.hpp @@ -25,7 +25,7 @@ namespace BabelGenerator::Generators { bool not_last = i + 1 < quasis->size(); auto value = BASE::UStringViewArray<3>{i == 0 ? u"`" : u"}", quasis->at(i)->value.raw, not_last ? u"${" : u"`"}; if (not_last) { - _this.token(value, true); + _this.template token(value); _this.print(node->expressions->at(i)); // In Babel 7 we have indivirual tokens for ${ and }, so the automatic @@ -35,7 +35,7 @@ namespace BabelGenerator::Generators { // if (token) _this._catchUpTo(token.loc.start); // } } else { - _this.token(value, true); + _this.template token(value); } } } diff --git a/src/babel/babel-generator/generators/types.hpp b/src/babel/babel-generator/generators/types.hpp index c72adfa3..7e426da5 100644 --- a/src/babel/babel-generator/generators/types.hpp +++ b/src/babel/babel-generator/generators/types.hpp @@ -5,6 +5,7 @@ #include "../printer.hpp" #include "./index.h" #include "./methods.hpp" +#include "../../../npm_modules/jsesc/jsesc.hpp" namespace BabelGenerator::Generators { using namespace BabelParser; @@ -245,17 +246,20 @@ namespace BabelGenerator::Generators { DEF_GENERATOR(NumericLiteral, node, _) { auto raw = _this.getPossibleRaw(node); - // const opts = this.format.jsescOption; + auto const& opts = _this.format.jsescOption; auto value = node->value; - // const str = value + ""; - // if (opts.numbers) { - // this.number(jsesc(value, opts), value); - // } else - if (raw == std::nullopt) { + // const str = value + ""; + if (opts.numbers) { + Jsesc::jsesc(_this.allocator, *value, opts, [&](auto&& str){ + _this.number(str, value); + }); + } else if (raw == std::nullopt) { unreachable(); - // _this.number(str, value); // normalize - // } else if (this.format.minified) { - // _this.number(raw->size() < str.length ? raw : str, value); + // _this.number(str, value); // normalize + } else if (_this.format.minified) { + value->withString([&](BASE::UStringView str){ + _this.number(raw->size() < str.size() ? *raw : str, value); + }); } else { _this.number(*raw, value); } @@ -267,10 +271,9 @@ namespace BabelGenerator::Generators { _this.token(*raw); return; } - // - // const val = jsesc(node.value, this.format.jsescOption); - // - // this.token(val); + + auto val = Jsesc::jsesc(_this.allocator, node->value, _this.format.jsescOption); + _this.token(val); } DEF_GENERATOR(BigIntLiteral, node, _) { diff --git a/src/babel/babel-generator/generators/typescript.hpp b/src/babel/babel-generator/generators/typescript.hpp index a6836bbd..04003faf 100644 --- a/src/babel/babel-generator/generators/typescript.hpp +++ b/src/babel/babel-generator/generators/typescript.hpp @@ -399,7 +399,7 @@ namespace BabelGenerator::Generators { _this.print(node->indexType); _this.token(u"]"); } - + inline void tokenIfPlusMinus(Printer::Printer& self, Node::TypeScriptReadonlyOptionalFlag::Value tok); DEF_GENERATOR(TSMappedType, node, _) { auto nameType = node->nameType; auto optional = node->optional; @@ -411,7 +411,7 @@ namespace BabelGenerator::Generators { { _this.space(); if (readonly) { - // tokenIfPlusMinus(this, readonly); + tokenIfPlusMinus(_this, readonly); _this.word(u"readonly"); _this.space(); } @@ -421,7 +421,7 @@ namespace BabelGenerator::Generators { _this.word(node->key->name->string()); } else { // @ts-ignore(Babel 7 vs Babel 8) Babel 7 AST shape - // _this.word(node->typeParameter->name); + _this.word(node->typeParameter->name_->name->string()); } _this.space(); _this.word(u"in"); @@ -431,7 +431,7 @@ namespace BabelGenerator::Generators { _this.print(node->constraint); } else { // @ts-ignore(Babel 7 vs Babel 8) Babel 7 AST shape - // _this.print(node->typeParameter.constraint); + _this.print(node->typeParameter->constraint); } if (nameType) { _this.space(); @@ -441,7 +441,7 @@ namespace BabelGenerator::Generators { } _this.token(u"]"); if (optional) { - // tokenIfPlusMinus(this, optional); + tokenIfPlusMinus(_this, optional); _this.token(u"?"); } if (typeAnnotation) { @@ -455,12 +455,12 @@ namespace BabelGenerator::Generators { _this.token(u"}"); } - // function tokenIfPlusMinus(self: Printer, tok: true | "+" | "-") { - // if (tok !== true) { - // self.token(tok); - // } - // } - // + inline void tokenIfPlusMinus(Printer::Printer& self, Node::TypeScriptReadonlyOptionalFlag::Value tok) { + if (tok != Node::TypeScriptReadonlyOptionalFlag::True) { + self.token(toString(tok)); + } + } + DEF_GENERATOR(TSLiteralType, node, _) { _this.print(node->literal); } template @@ -763,7 +763,7 @@ namespace BabelGenerator::Generators { printModifiersList( _this, node, Node::Modifier::Value{ - (isField && node->modifier.isDeclare() ? Node::Modifier::Declare : Node::Modifier::Undefined) | node->modifier.getAccessibility() + (isField && node->isDeclare() ? Node::Modifier::Declare : Node::Modifier::Undefined) | node->modifier.getAccessibility() } ); if (node->isStatic()) { diff --git a/src/babel/babel-generator/index.h b/src/babel/babel-generator/index.h index e50473f6..22521f8a 100644 --- a/src/babel/babel-generator/index.h +++ b/src/babel/babel-generator/index.h @@ -1,9 +1,14 @@ #pragma once #include "../../base/unicode.hpp" +#include "../../base/allocator.hpp" +#include "../../npm_modules/jsesc/jsesc.hpp" #include "../babel-types/ast-types/generated/enum.hpp" #include "../babel-types/ast-types/generated/index.hpp" +namespace BabelArena { + class Memory; +} namespace BabelGenerator { struct GeneratorResult { @@ -107,7 +112,7 @@ namespace BabelGenerator { /** * Options for outputting jsesc representation. */ - // jsescOption?: jsescOptions; + std::optional jsescOption; /** * For use with the recordAndTuple token. @@ -130,5 +135,5 @@ namespace BabelGenerator { std::optional importAttributesKeyword; }; GeneratorResult - generate(PTR(BabelParser::Node::File) ast, GeneratorOptions opts = GeneratorOptions{}, std::optional code = std::nullopt); + generate(BabelArena::Memory& allocator, PTR(BabelParser::Node::File) ast, GeneratorOptions opts = GeneratorOptions{}, std::optional code = std::nullopt); } // namespace BabelGenerator diff --git a/src/babel/babel-generator/index.hpp b/src/babel/babel-generator/index.hpp index 1e58812d..ba0344c3 100644 --- a/src/babel/babel-generator/index.hpp +++ b/src/babel/babel-generator/index.hpp @@ -57,19 +57,18 @@ namespace BabelGenerator { .adjustMultilineComment = true, .style = u" ", }, - // jsescOption: { - // quotes: "double", - // wrap: true, - // minimal: process.env.BABEL_8_BREAKING ? true : false, - // ...opts.jsescOption, - // }, + .jsescOption = Jsesc::Option{ + .quotes = u'"', + .wrap = true, + .minimal = BASE::process::env.BABEL_8_BREAKING ? true : false, + }.assign(opts.jsescOption.value_or(Jsesc::PartialOption{})), .topicToken = opts.topicToken, .importAttributesKeyword = opts.importAttributesKeyword, }; if (!BASE::process::env.BABEL_8_BREAKING) { format.decoratorsBeforeExport = opts.decoratorsBeforeExport; - // format.jsescOption.json = opts.jsonCompatibleStrings; + format.jsescOption.json = opts.jsonCompatibleStrings.value_or(false); format.recordAndTupleSyntaxType = opts.recordAndTupleSyntaxType.value_or(RecordAndTupleSyntaxType::hash); } @@ -148,6 +147,7 @@ namespace BabelGenerator { * @returns - an object containing the output code and source map. */ GeneratorResult generate( + BabelArena::Memory& allocator, PTR(Node::File) ast, GeneratorOptions opts, std::optional code // code?: string | { [filename: string]: string }, ) { @@ -160,7 +160,7 @@ namespace BabelGenerator { // (ast as any).tokens, // typeof code === "string" ? code : null, // ); - Printer::Printer printer(format, code); + Printer::Printer printer(*allocator.string(), format, code); return printer.generate(ast); } diff --git a/src/babel/babel-generator/node/index.hpp b/src/babel/babel-generator/node/index.hpp index 2ddf39ec..3e00d02d 100644 --- a/src/babel/babel-generator/node/index.hpp +++ b/src/babel/babel-generator/node/index.hpp @@ -22,13 +22,13 @@ namespace BabelGenerator::Nodes { // const expandedWhitespaceNodes = expandAliases(whitespace.nodes); - // function isOrHasCallExpression(node: t.Node): boolean { - // if (isCallExpression(node)) { - // return true; - // } + inline bool isOrHasCallExpression(PTR(Node::Node) node) { + if (BabelTypes::isCallExpression(node)) { + return true; + } - // return isMemberExpression(node) && isOrHasCallExpression(node.object); - // } + return BabelTypes::isMemberExpression(node) && isOrHasCallExpression(NODEAS(node, MemberExpression)->object); + } // export function needsWhitespace( // node: t.Node, @@ -57,6 +57,7 @@ namespace BabelGenerator::Nodes { // export function needsWhitespaceAfter(node: t.Node, parent: t.Node) { // return needsWhitespace(node, parent, 2); // } + inline bool isDecoratorMemberExpression(PTR(Node::Node) node); inline bool needsParens( PTR(Node::Node) node, PTR(Node::Node) parent, Node::Type nodeType, TokenContext::Type tokenContext = 0, bool inForInit = false @@ -65,17 +66,17 @@ namespace BabelGenerator::Nodes { if (!parent) return false; - if (parent->type() == Node::Type::NewExpression && NODEAS(parent, NewExpression)->callee == node) { - // if (isOrHasCallExpression(node)) return true; + if (parent->type() == Node::Type::NewExpression && NODEAS(parent, NewExpression)->callee == node) [[unlikely]] { + if (isOrHasCallExpression(node)) return true; } - // if (isDecorator(parent)) { - // return ( - // !isDecoratorMemberExpression(node) && - // !(isCallExpression(node) && isDecoratorMemberExpression(node.callee)) && - // !isParenthesizedExpression(node) - // ); - // } + if (BabelTypes::isDecorator(parent)) [[unlikely]] { + return ( + !isDecoratorMemberExpression(node) && + !(BabelTypes::isCallExpression(node) && isDecoratorMemberExpression(NODEAS(node, CallExpression)->callee)) && + !BabelTypes::isParenthesizedExpression(node) + ); + } auto method = parenthesesMethods[nodeType]; if (method) { @@ -85,20 +86,20 @@ namespace BabelGenerator::Nodes { } } - // function isDecoratorMemberExpression(node: t.Node): boolean { - // switch (node.type) { - // case "Identifier": - // return true; - // case "MemberExpression": - // return ( - // !node.computed && - // node.property.type === "Identifier" && - // isDecoratorMemberExpression(node.object) - // ); - // default: - // return false; - // } - // } + inline bool isDecoratorMemberExpression(PTR(Node::Node) node) { + switch (node->type()) { + case Node::Type::Identifier: + return true; + case Node::Type::MemberExpression: + return ( + !NODEAS(node, MemberExpression)->computed && + NODEAS(node, MemberExpression)->property->type() == Node::Type::Identifier && + isDecoratorMemberExpression(NODEAS(node, MemberExpression)->object) + ); + default: + return false; + } + } // export function isLastChild(parent: t.Node, child: t.Node) { // const visitorKeys = VISITOR_KEYS[parent.type]; diff --git a/src/babel/babel-generator/node/parentheses.hpp b/src/babel/babel-generator/node/parentheses.hpp index 820128de..06a52545 100644 --- a/src/babel/babel-generator/node/parentheses.hpp +++ b/src/babel/babel-generator/node/parentheses.hpp @@ -66,11 +66,15 @@ namespace BabelGenerator::Nodes::Parentheses { return getBinaryPrecedence(node->_operator); } else if constexpr (BabelTypes::IsLogicalExpression) { return getBinaryPrecedence(node->_operator); + } else if constexpr (BabelTypes::IsTSAsExpression || BabelTypes::IsTSSatisfiesExpression) { + return getBinaryPrecedence(BinaryOperator::In); } else { if (nodeType == Node::Type::BinaryExpression) { return getBinaryPrecedence(NODEAS(node, BinaryExpression)->_operator); } else if (nodeType == Node::Type::LogicalExpression) { return getBinaryPrecedence(NODEAS(node, LogicalExpression)->_operator); + } else if (nodeType == Node::Type::TSAsExpression || nodeType == Node::Type::TSSatisfiesExpression) { + return getBinaryPrecedence(BinaryOperator::In); } else { return 0; } diff --git a/src/babel/babel-generator/printer.hpp b/src/babel/babel-generator/printer.hpp index 04ba4085..b66733ec 100644 --- a/src/babel/babel-generator/printer.hpp +++ b/src/babel/babel-generator/printer.hpp @@ -34,6 +34,7 @@ #include "./generators/index.h" #include "./node/index.hpp" #include "buffer.hpp" +#include "../../npm_modules/jsesc/jsesc.hpp" namespace BabelGenerator::Printer { @@ -104,7 +105,7 @@ namespace BabelGenerator::Printer { * @deprecated Removed in Babel 8, syntax type is always 'hash' */ std::optional recordAndTupleSyntaxType; - // jsescOption: jsescOptions; + Jsesc::Option jsescOption; /** * @deprecated Removed in Babel 8, use `jsescOption` instead */ @@ -169,7 +170,10 @@ namespace BabelGenerator::Printer { // } // declare _inputMap: TraceMap; - Printer(Format const &format, std::optional originalCode = std::nullopt) : + BASE::Memory::Allocator& allocator; + + Printer(BASE::Memory::Allocator& allocator, Format const &format, std::optional originalCode = std::nullopt) : + allocator(allocator), format(format), _buf(format.indent.style[0], _originalCode ? _originalCode->size() : 0), _indentRepeat(format.indent.style.size()), @@ -395,7 +399,8 @@ namespace BabelGenerator::Printer { } return false; } - void number(BASE::UStringView str, std::optional number) { + template + void number(STR && str, std::optional number) { // const NON_DECIMAL_LITERAL = /^0[box]/; this->word(str); @@ -1432,9 +1437,9 @@ namespace BabelGenerator::Printer { if (commentsHead->next() == nullptr) { bool singleLine = comment->locStart().line() == comment->locEnd().line(); - bool shouldSkipNewline = singleLine && !BabelTypes::isStatement(node) && !BabelTypes::isClassBody(parent) && true; - // !BabelTypes::isTSInterfaceBody(parent) && - // !BabelTypes::isTSEnumMember(node); + bool shouldSkipNewline = singleLine && !BabelTypes::isStatement(node) && !BabelTypes::isClassBody(parent) && + !BabelTypes::isTSInterfaceBody(parent) && + !BabelTypes::isTSEnumMember(node); if constexpr (commentPos == CommentPosition::Leading) { if ((shouldSkipNewline && node->type() != Node::Type::ObjectExpression) || @@ -1450,8 +1455,8 @@ namespace BabelGenerator::Printer { } } else if (commentPos == CommentPosition::Inner && !(node->type() == Node::Type::ObjectExpression && NODEAS(node, ObjectExpression)->properties->size() > 1) && - node->type() != Node::Type::ClassBody && true - // node->type() != Node::Type::TSInterfaceBody + node->type() != Node::Type::ClassBody && + node->type() != Node::Type::TSInterfaceBody ) { // class X { // /*:: a: number*/ diff --git a/src/babel/babel-generator/test/index.hpp b/src/babel/babel-generator/test/index.hpp index 0469bc3e..9690f031 100644 --- a/src/babel/babel-generator/test/index.hpp +++ b/src/babel/babel-generator/test/index.hpp @@ -10,34 +10,37 @@ namespace BabelGenerator { inline void runAutogeneratedTests(BASE::UStringView prefix, const BabelHelperFixtures::Test &task) { const auto &expected = task.expect; - const auto &actual = task.actual; - const auto &actualCode = actual.code; + const auto &_actual = task.actual; + const auto &code = _actual.code; - if (actualCode.size()) { + if (code.size()) { BabelParser::Options::PartialOptions parserOpts{task.options.toParserOptions()}; parserOpts.strictMode = task.options.strictMode.value_or(false) == false ? false : true; parserOpts.sourceType = BabelParser::SourceType::Value::module; auto const &throwMsg = task.options.throws; if (!throwMsg.has_value()) { - auto resultCode = BabelParser::withParser( - actualCode, parserOpts, - [&](PTR(BabelParser::Node::File) file, BabelParser::Options::Options const &options) + // std::cout << BASE::String::convertUTF16ToBasic(code) << std::endl; + auto actualCode = BabelParser::withParser( + code, parserOpts, + [&](BabelArena::Memory& allocator, PTR(BabelParser::Node::File) file, BabelParser::Options::Options const &options) { auto generatorOptions = task.options.toGeneratorOptions(); - auto result = BabelGenerator::generate(file, generatorOptions); + auto result = BabelGenerator::generate(allocator, file, generatorOptions); return result.code; } ); - if (expected.code.size() == 0 && resultCode.size()) { + if (expected.code.size() == 0 && actualCode.size()) { // write } else { auto const &expectedCode = expected.code; - resultCode = BASE::String::removeTrailSpace(resultCode); - if (resultCode == expectedCode) { + actualCode = BASE::String::removeTrailSpace(actualCode); + if (actualCode == expectedCode) { std::cout << "PASS" << std::endl; } else { - throw BASE::JavaScript::Error(BASE::String::Concat(u"\nexpect: \n`", expectedCode, u"`\n, actual:\n`", resultCode, u"`")); + std::cout << "actual: " << BASE::String::convertUTF16ToBasic(actualCode) << std::endl; + std::cout << "expect: " << BASE::String::convertUTF16ToBasic(expectedCode) << std::endl; + throw BASE::JavaScript::Error(BASE::String::Concat(u"\nexpect: \n`", expectedCode, u"`\n, actual:\n`", actualCode, u"`")); } } } diff --git a/src/babel/babel-helper-fixtures/index.hpp b/src/babel/babel-helper-fixtures/index.hpp index e2d56598..b5bf6785 100644 --- a/src/babel/babel-helper-fixtures/index.hpp +++ b/src/babel/babel-helper-fixtures/index.hpp @@ -84,6 +84,8 @@ namespace BabelHelperFixtures { std::optional decoratorsBeforeExport; std::optional importAttributesKeyword; std::optional topicToken; + std::optional jsescOption; + std::optional jsonCompatibleStrings; void assign(JSON const &json) { if (auto const &parserOpts = json.find("parserOpts"); parserOpts != json.end()) { @@ -235,6 +237,45 @@ namespace BabelHelperFixtures { if (auto const &decoratorsBeforeExport = json.find("decoratorsBeforeExport"); decoratorsBeforeExport != json.end()) { this->decoratorsBeforeExport = decoratorsBeforeExport->get(); } + if (auto const &jsonCompatibleStrings = json.find("jsonCompatibleStrings"); jsonCompatibleStrings != json.end()) { + this->jsonCompatibleStrings = jsonCompatibleStrings->get(); + } + if (auto const &jsescOption = json.find("jsescOption"); jsescOption != json.end()) { + if (jsescOption->is_object()) { + this->jsescOption = Jsesc::PartialOption{}; + if (auto const& wrap = jsescOption->find("wrap"); wrap != jsescOption->end()) { + this->jsescOption->wrap = wrap->get(); + } + if (auto const& minimal = jsescOption->find("minimal"); minimal != jsescOption->end()) { + this->jsescOption->minimal = minimal->get(); + } + if (auto const& json = jsescOption->find("json"); json != jsescOption->end()) { + this->jsescOption->json = json->get(); + } + if (auto const& lowercaseHex = jsescOption->find("lowercaseHex"); lowercaseHex != jsescOption->end()) { + this->jsescOption->lowercaseHex = lowercaseHex->get(); + } + if (auto const& numbers = jsescOption->find("numbers"); numbers != jsescOption->end()) { + auto const& numbers_str = numbers->get(); + this->jsescOption->numbers = ( + numbers_str == "binary" ? std::optional(2) : + numbers_str == "octal" ? std::optional(8) : + numbers_str == "decimal" ? std::optional(10) : + numbers_str == "hexadecimal" ? std::optional(16) : + std::nullopt + ); + } + if (auto const& quotes = jsescOption->find("quotes"); quotes != jsescOption->end()) { + auto const& quotes_str = quotes->get(); + this->jsescOption->quotes = ( + quotes_str == "double" ? std::optional(u'"') : + quotes_str == "single" ? std::optional(u'\'') : + quotes_str == "backtick" ? std::optional(u'`') : + std::nullopt + ); + } + } + } } BabelGenerator::GeneratorOptions toGeneratorOptions() const { auto const &taskOptions = *this; @@ -285,6 +326,8 @@ namespace BabelHelperFixtures { if (*topicToken == u"^^") generatorOptions.topicToken = BabelTypes::Enum::TopicToken::_uparrow2; } + generatorOptions.jsescOption = taskOptions.jsescOption; + generatorOptions.jsonCompatibleStrings = taskOptions.jsonCompatibleStrings; return generatorOptions; } BabelParser::Options::PartialOptions toParserOptions() const { diff --git a/src/babel/babel-parser/index.h b/src/babel/babel-parser/index.h index acec8eb9..93aeb860 100644 --- a/src/babel/babel-parser/index.h +++ b/src/babel/babel-parser/index.h @@ -14,15 +14,15 @@ namespace BabelParser { using ParseFunction = JSON(BASE::UStringView const &source, Options::PartialOptions); struct ParseArgumentedCallback { - using Callback = void (*)(PTR(BabelParser::Node::File), BabelParser::Options::Options const &, void *arguments); + using Callback = void (*)(BabelArena::Memory&, PTR(BabelParser::Node::File), BabelParser::Options::Options const &, void *arguments); void *arguments; Callback callback; - void invoke(PTR(BabelParser::Node::File) file, BabelParser::Options::Options const &options) { this->callback(file, options, this->arguments); } + void invoke(BabelArena::Memory& allocator, PTR(BabelParser::Node::File) file, BabelParser::Options::Options const &options) { this->callback(allocator, file, options, this->arguments); } }; extern void withParserImpl(const BASE::UStringView &source, Options::PartialOptions partial, ParseArgumentedCallback callback); template inline auto withParser(const BASE::UStringView &source, Options::PartialOptions partial, CB &&callback) { - using ReturnType = typename std::invoke_result_t; + using ReturnType = typename std::invoke_result_t; struct Arguments { CB &&callback; std::optional returnValue = std::nullopt; @@ -33,10 +33,10 @@ namespace BabelParser { ParseArgumentedCallback{ .arguments = &args, .callback = - [](PTR(BabelParser::Node::File) file, BabelParser::Options::Options const &options, void *_arguments) + [](BabelArena::Memory& allocator, PTR(BabelParser::Node::File) file, BabelParser::Options::Options const &options, void *_arguments) { auto const &arguments = static_cast(_arguments); - arguments->returnValue = arguments->callback(file, options); + arguments->returnValue = arguments->callback(allocator, file, options); } } ); diff --git a/src/babel/babel-parser/index.hpp b/src/babel/babel-parser/index.hpp index a0c14622..809edb37 100644 --- a/src/babel/babel-parser/index.hpp +++ b/src/babel/babel-parser/index.hpp @@ -56,7 +56,7 @@ namespace BabelParser { [&](PARSER &&parser) { auto file = parser.parse(); - callback.invoke(file, options); + callback.invoke(allocator, file, options); }, allocator, options, source ); @@ -92,7 +92,7 @@ namespace BabelParser { BASE::UString code; { STAT_TIMING(Generator, Generate) - code = std::move(BabelGenerator::generate(file).code); + code = std::move(BabelGenerator::generate(*allocator, file).code); } { STAT_TIMING(Runtime, Deallocation) diff --git a/src/babel/babel-parser/plugins/typescript/index.hpp b/src/babel/babel-parser/plugins/typescript/index.hpp index 3b2a589d..e8e5c701 100644 --- a/src/babel/babel-parser/plugins/typescript/index.hpp +++ b/src/babel/babel-parser/plugins/typescript/index.hpp @@ -1052,7 +1052,7 @@ namespace BabelParser::Plugins::TypeScript { this->expect(TokenType::$_bracketL); PTR(Node::Identifier) key = nullptr; PTR(Node::TSType) constraint = nullptr; - PTR(Node::TSType) typeParameter = nullptr; + PTR(Node::TSTypeParameter) typeParameter = nullptr; if (BASE::process::env.BABEL_8_BREAKING) { key = this->tsParseTypeParameterName(); constraint = this->tsExpectThenParseType(TokenType::$_in); diff --git a/src/babel/babel-parser/tokenizer/state.hpp b/src/babel/babel-parser/tokenizer/state.hpp index 2e951e90..ac58d62b 100644 --- a/src/babel/babel-parser/tokenizer/state.hpp +++ b/src/babel/babel-parser/tokenizer/state.hpp @@ -25,7 +25,8 @@ namespace BabelParser::Tokenizer::State { }; union UnionValue { BASE::UStringView _string; - std::optional _val; + std::optional _val; + bool _bool; RegExpValue _regexp; UnionValue() {} @@ -40,11 +41,11 @@ namespace BabelParser::Tokenizer::State { }; Type type = Type::Null; UnionValue value; - Value(double value) : type(Type::Number) { this->value._val = value; } - Value(std::optional value) : type(Type::Number) { this->value._val = value; } + Value(BASE::Number::FixedNumber value) : type(Type::Number) { this->value._val = value; } + Value(std::optional value) : type(Type::Number) { this->value._val = value; } Value(BASE::UStringView value) : type(Type::String) { this->value._string = value; } Value(RegExpValue value) : type(Type::RegExp) { this->value._regexp = value; } - Value(bool value) : type(Type::Boolean) { this->value._val = value ? std::optional(1) : std::nullopt; } + Value(bool value) : type(Type::Boolean) { this->value._bool = value; } explicit Value() : type(Type::Null) {} bool has_value() const { return this->type != Type::Null; } const RegExpValue ®exp() const { @@ -58,13 +59,13 @@ namespace BabelParser::Tokenizer::State { } bool operator==(BASE::UStringView str) const { return this->type == Type::String && this->value._string == str; } - const std::optional &number() const { + const std::optional &number() const { BASE_ASSERT(this->type == Type::Number, "state.value should be Number"); return this->value._val; } bool boolean() const { BASE_ASSERT(this->type == Type::Boolean, "state.value should be Boolean"); - return this->value._val.has_value(); + return this->value._bool; } }; struct PartialLookaheadState { diff --git a/src/babel/babel-parser/tokenizer/tokenizer.hpp b/src/babel/babel-parser/tokenizer/tokenizer.hpp index ff1ef0ad..01a6a1c4 100644 --- a/src/babel/babel-parser/tokenizer/tokenizer.hpp +++ b/src/babel/babel-parser/tokenizer/tokenizer.hpp @@ -1214,7 +1214,7 @@ namespace BabelParser::Tokenizer { return; } - this->finishToken(TokenType::$_num, val ? std::optional(static_cast(*val)) : std::nullopt); + this->finishToken(TokenType::$_num, val ? std::optional(BASE::Number::FixedNumber{*val, 0}) : std::nullopt); } // Read an integer, octal integer, or floating-point number. diff --git a/src/babel/babel-parser/types.hpp b/src/babel/babel-parser/types.hpp index fc7399a0..cce572e4 100644 --- a/src/babel/babel-parser/types.hpp +++ b/src/babel/babel-parser/types.hpp @@ -294,18 +294,24 @@ public: }; class NumericLiteral : public Expression { DEFINE_NODE_IMPL_BODY(NumericLiteral) - std::optional value; - std::optional rawValue; + std::optional value; + std::optional rawValue; std::optional raw; explicit NumericLiteral( - PositionInfo const &pos, std::optional const &value, std::optional const &rawValue, std::optional const &raw + PositionInfo const &pos, std::optional const &value, std::optional const &rawValue, std::optional const &raw ) : Expression(TYPE, pos), value(value), rawValue(rawValue), raw(raw) {} - void format(Writer::NodeWriter &writer) const { writer.field("value", this->value); } + void format(Writer::NodeWriter &writer) const { + if (auto value = this->value) { + writer.field("value", value->to()); + } else { + writer.field("value", std::nullopt); + } + } }; class BigIntLiteral : public Expression { DEFINE_NODE_IMPL_BODY(BigIntLiteral) @@ -1070,6 +1076,7 @@ public: bool isStatic() const { return _static; } void setStatic(bool v) { _static = v; } bool isAbstract() const { return modifier.isAbstract(); } + bool isOptional() const { return modifier.isOptional(); } VirtualClassMember(const Value &inter) : computed(inter.computed), _static(inter._static), @@ -1295,6 +1302,7 @@ public: bool computed; bool method_; // TODO: Not in spec, bool isAbstract() const { return false; } + bool isOptional() const { return false; } void format(Writer::NodeWriter &writer) const { if (auto kind = this->kind) { writer.field("kind", MethodKind::toString(kind)); @@ -2479,7 +2487,11 @@ public: if (this->value.type == Value::Type::String) { writer.field("value", this->value.string()); } else if (this->value.type == Value::Type::Number) { - writer.field("value", this->value.number()); + if (auto value = this->value.number()) { + writer.field("value", value->to()); + } else { + writer.field("value", std::nullopt); + } } else if (this->value.type == Value::Type::Boolean) { writer.field("value", this->value.boolean()); } else if (this->value.type == Value::Type::RegExp) { @@ -3148,7 +3160,7 @@ public: TypeScriptReadonlyOptionalFlag::Value readonly; TypeScriptReadonlyOptionalFlag::Value optional; PTR(TSType) typeAnnotation; - PTR(TSType) typeParameter; + PTR(TSTypeParameter) typeParameter; PTR(TSType) nameType; explicit TSMappedType( PositionInfo const &pos, @@ -3157,7 +3169,7 @@ public: TypeScriptReadonlyOptionalFlag::Value readonly, TypeScriptReadonlyOptionalFlag::Value optional, PTR(TSType) typeAnnotation, - PTR(TSType) typeParameter, + PTR(TSTypeParameter) typeParameter, PTR(TSType) nameType ) : TSType(TYPE, pos), diff --git a/src/base/javascript.hpp b/src/base/javascript.hpp index 55563b8f..d7e173ac 100644 --- a/src/base/javascript.hpp +++ b/src/base/javascript.hpp @@ -4,6 +4,7 @@ #include #include #include "./unicode.hpp" +#include "number.hpp" #include "string.hpp" namespace BASE { @@ -76,7 +77,7 @@ namespace BASE::JavaScript { return digit; } - static inline double SignedZero(bool sign) { return sign ? -0.0 : 0.0; } + static inline Number::FixedNumber SignedZero(bool sign) { return sign ? Number::FixedNumber{-0, 0} : Number::FixedNumber{0, 0}; } template static inline bool Advance(Iterator *it, Iterator &end) { @@ -101,7 +102,7 @@ namespace BASE::JavaScript { return result; } - static inline double stringToIeee(const BASE::UStringView &input, size_t length, size_t *processed_characters_count) { + static inline Number::FixedNumber stringToIeee(const BASE::UStringView &input, size_t length, size_t *processed_characters_count) { auto begin = input.begin(); auto current = begin; auto end = current + length; @@ -117,7 +118,7 @@ namespace BASE::JavaScript { // 5. Code before 'parsing_done' may rely on 'current != end'. if (current == end) - return 0.0; + return Number::FixedNumber{0, 0}; // The longest form of simplified number is: "-.1eXXX\0". const int kBufferSize = kMaxSignificantDigits + 10; @@ -137,7 +138,7 @@ namespace BASE::JavaScript { sign = (*current == u'-'); ++current; if (current == end) - return 0.0; + return Number::FixedNumber{0, 0}; } bool leading_zero = false; @@ -175,7 +176,7 @@ namespace BASE::JavaScript { if (*current == '.') { if (Advance(¤t, end)) { if (significant_digits == 0 && !leading_zero) { - return 0.0; + return Number::FixedNumber{0, 0}; } else { goto parsing_done; } @@ -215,7 +216,7 @@ namespace BASE::JavaScript { // If exponent < 0 then string was [+-]\.0*... // If significant_digits != 0 the string is not equal to 0. // Otherwise there are no digits in the string. - return 0.0; + return Number::FixedNumber{0, 0}; } // Parse exponential part. @@ -271,18 +272,14 @@ namespace BASE::JavaScript { // ASSERT(buffer_pos < kBufferSize); buffer[buffer_pos] = '\0'; - double converted = static_cast(ReadUint64(buffer, buffer_pos)); - if (exponent > 0) { - while (exponent--) { - converted *= 10.0; - } - } else if (exponent < 0) { - while (exponent++) { - converted /= 10.0; - } - } *processed_characters_count = static_cast(current - begin); - return sign ? -converted : converted; + + int64_t converted = ReadUint64(buffer, buffer_pos); + while (converted % 10 == 0 && converted) { + converted /= 10; + exponent++; + } + return sign ? Number::FixedNumber{-converted, exponent} : Number::FixedNumber{converted, exponent}; } static inline double parseIntOverflow(const BASE::UStringView &s, int radix) { @@ -322,7 +319,7 @@ namespace BASE::JavaScript { } // ES5.1 15.1.2.2 - inline std::optional parseInt(const BASE::UStringView &s, int radix) { + inline std::optional parseInt(const BASE::UStringView &s, int radix) { // 1. Let inputString be ToString(string). // 2. Let S be a newly created substring of inputString consisting of the first character that is not a // StrWhiteSpaceChar and all characters following that character. (In other words, remove leading white @@ -398,7 +395,7 @@ namespace BASE::JavaScript { } // 15. Return sign x number. - return sign * number; + return Number::FixedNumber(sign * number, 0); } inline int parseDouble(const BASE::UStringView &string, double &value) { @@ -436,13 +433,13 @@ namespace BASE::JavaScript { return length; } - inline std::optional parseFloat(const BASE::UStringView &s) { + inline std::optional parseFloat(const BASE::UStringView &s) { unsigned size = s.length(); if (size == 1) { BASE::UChar c = s[0]; if (isASCIIDigit(c)) - return c - '0'; + return Number::FixedNumber(c - '0', 0); return std::nullopt; } @@ -462,7 +459,7 @@ namespace BASE::JavaScript { size_t len = data.size() - i; size_t parsedLength = 0; - double number = stringToIeee(data.substr(i, len), len, &parsedLength); + Number::FixedNumber number = stringToIeee(data.substr(i, len), len, &parsedLength); // size_t parsedLength = parseDouble(data.substr(i, data.size() - i), number); if (parsedLength) { return number; diff --git a/src/base/number.hpp b/src/base/number.hpp new file mode 100644 index 00000000..4b51368e --- /dev/null +++ b/src/base/number.hpp @@ -0,0 +1,120 @@ +#pragma once + +#include +#include "./define.hpp" +#include "./platform.h" +#include "./unicode.hpp" + +namespace BASE::Number { + class FixedNumber { + private: + int64_t value_; + int64_t scale_; + public: + FixedNumber(int64_t v, int64_t s) : value_(v), scale_(s) {} + template + T to() const { + int64_t abs_value = std::abs(value_); + int64_t int_part = abs_value; + int64_t fractional_part = 0; + T return_value; + if (scale_ < 0) { + int64_t divisor = static_cast(std::pow(10, -scale_)); + int_part = abs_value / divisor; + fractional_part = abs_value % divisor; + } + if (scale_ < 0) { + double frac_part_double = fractional_part / std::pow(10.0, std::abs(scale_)); + double int_part_double = static_cast(int_part); + return_value = int_part_double + frac_part_double; + } else { + return_value = int_part * std::pow(10, scale_); + } + return return_value; + } + template + static BASE::UChar* writePositiveToBuffer(int64_t val, BASE::UChar* begin) { + BASE::UChar* current = begin; + do { + int64_t mod = val % HEX; + + if constexpr (HEX == 16) { + if (mod >= 10) { + *current++ = ABASE + mod - 10; + } else { + *current++ = '0' + mod; + } + } else { + *current++ = '0' + mod; + } + val /= HEX; + } while (val > 0); + size_t len = current - begin; + for (int j = 0; j < len / 2; ++j) { + BASE::UChar temp = begin[j]; + begin[j] = begin[len - j - 1]; + begin[len - j - 1] = temp; + } + return current; + } + template + void withHexString(CB&& callback) const { + constexpr int bufferSize = 1024; + BASE::UChar buffer[bufferSize]; + int64_t int_part = value_ * std::pow(10, scale_); + BASE::UChar* current = buffer; + current = writePositiveToBuffer(int_part, current); + *current++ = 0; + if (current - buffer > bufferSize) { + unreachable(); + } + callback(BASE::UStringView{buffer, static_cast(current - buffer - 1)}); + } + template + void withString(CB&& callback) const { + constexpr int bufferSize = 1024; + BASE::UChar buffer[bufferSize]; + int64_t abs_value = std::abs(value_); + int64_t int_part = abs_value; + int64_t frac_part = 0; + + BASE::UChar* current = buffer; + if (value_ < 0) { + *current++ = u'-'; + } + if (scale_ < 0) { + int64_t divisor = static_cast(std::pow(10, -scale_)); + int_part = abs_value / divisor; + frac_part = abs_value % divisor; + } + current = writePositiveToBuffer(int_part, current); + if (scale_ < 0) { + *current++ = u'.'; + int64_t current_scale = -scale_; + while (--current_scale >= 0) { + *current++ = u'0'; + } + current = writePositiveToBuffer(frac_part, current); + } else { + int64_t current_scale = scale_; + while (--current_scale >= 0) { + *current++ = u'0'; + } + } + *current++ = 0; + if (current - buffer > bufferSize) { + unreachable(); + } + callback(BASE::UStringView{buffer, static_cast(current - buffer - 1)}); + } + bool operator>(double v) const { + return this->template to() > v; + } + bool operator>(int v) const { + return this->template to() > v; + } + explicit operator int32_t() const { + return this->template to(); + } + }; +} \ No newline at end of file diff --git a/src/benchmark/generate.hpp b/src/benchmark/generate.hpp index 7886b470..c3621cbb 100644 --- a/src/benchmark/generate.hpp +++ b/src/benchmark/generate.hpp @@ -30,10 +30,10 @@ static void BM_BabelGenerate(benchmark::State &state, std::string const &filenam auto ret = BabelParser::withParser( code, BabelParser::Options::PartialOptions{}, - [&](PTR(BabelParser::Node::File) file, BabelParser::Options::Options const &options) + [&](BabelArena::Memory& allocator, PTR(BabelParser::Node::File) file, BabelParser::Options::Options const &options) { for (auto _ : state) { - auto code = BabelGenerator::generate(file).code; + auto code = BabelGenerator::generate(allocator, file).code; benchmark::DoNotOptimize(code); } return true; diff --git a/src/npm_modules/jsesc/jsesc.hpp b/src/npm_modules/jsesc/jsesc.hpp new file mode 100644 index 00000000..ac7dad54 --- /dev/null +++ b/src/npm_modules/jsesc/jsesc.hpp @@ -0,0 +1,493 @@ +#pragma once + +#include "../../babel/babel-arena/index.hpp" +#include "../../base/unicode.hpp" +#include "../../base/string.hpp" +#include "../../base/number.hpp" +#include "../../base/allocator.hpp" + +namespace Jsesc { +// +//const object = {}; +//const hasOwnProperty = object.hasOwnProperty; +//const forOwn = (object, callback) => { +// for (const key in object) { +// if (hasOwnProperty.call(object, key)) { +// callback(key, object[key]); +// } +// } +//}; +// +//const extend = (destination, source) => { +// if (!source) { +// return destination; +// } +// forOwn(source, (key, value) => { +// destination[key] = value; +// }); +// return destination; +//}; +// +//const forEach = (array, callback) => { +// const length = array.length; +// let index = -1; +// while (++index < length) { +// callback(array[index]); +// } +//}; +// +//const fourHexEscape = (hex) => { +// return '\\u' + ('0000' + hex).slice(-4); +//} +// + struct PartialOption { + std::optional numbers; + std::optional quotes; + std::optional wrap; + std::optional minimal; + std::optional json; + std::optional lowercaseHex; + }; + struct Option { + // 'numbers': 'decimal', + // 'indent': '\t', + // 'indentLevel': 0, + // '__inline1__': false, + // '__inline2__': false + uint8_t numbers = 0; + BASE::UChar quotes = u'\''; + bool wrap = false; + bool minimal = false; + bool json = false; + static constexpr bool escapeEverything = false; + static constexpr bool es6 = false; + static constexpr bool compact = true; + bool lowercaseHex = false; + Option& assign(PartialOption const&opt) { + if (auto numbers = opt.numbers) { + this->numbers = *numbers; + } + if (auto quotes = opt.quotes) { + this->quotes = *quotes; + } + if (auto wrap = opt.wrap) { + this->wrap = *wrap; + } + if (auto minimal = opt.minimal) { + this->minimal = *minimal; + } + if (auto json = opt.json) { + this->json = *json; + } + if (auto lowercaseHex = opt.lowercaseHex) { + this->lowercaseHex = *lowercaseHex; + } + return *this; + } + }; + inline std::array hexadecimal(BASE::UChar code, bool lowercase, int* size = nullptr) { + std::array escaped{u'0',u'0',u'0',u'0'}; + int i; + for(i = 3; i >= 0; i--) { + uint8_t ch = code % 16; + code = (code - ch) / 16; + if (ch < 10) { + escaped[i] = u'0' + ch; + } else if (!lowercase) { + escaped[i] = u'A' + ch - 10; + } else { + escaped[i] = u'a' + ch - 10; + } + if (!code) { + break; + } + } + if (size != nullptr) { + *size = 4 - i; + } + return escaped; + } + inline void fourHexEscape(BASE::String::Slices& str, BASE::UChar code, bool lowercase) { + auto const& hex = hexadecimal(code, lowercase); + str.add<2>(u"\\u"); + str.add(hex[0]); + str.add(hex[1]); + str.add(hex[2]); + str.add(hex[3]); + } +//const toString = object.toString; +//const isArray = Array.isArray; +//const isBuffer = (value) => { +// return typeof Buffer === 'function' && Buffer.isBuffer(value); +//}; +//const isObject = (value) => { +// // This is a very simple check, but it’s good enough for what we need. +// return toString.call(value) == '[object Object]'; +//}; +//const isString = (value) => { +// return typeof value == 'string' || +// toString.call(value) == '[object String]'; +//}; +//const isNumber = (value) => { +// return typeof value == 'number' || +// toString.call(value) == '[object Number]'; +//}; +//const isFunction = (value) => { +// return typeof value == 'function'; +//}; +//const isMap = (value) => { +// return toString.call(value) == '[object Map]'; +//}; +//const isSet = (value) => { +// return toString.call(value) == '[object Set]'; +//}; +// +///*--------------------------------------------------------------------------*/ +// +//// https://mathiasbynens.be/notes/javascript-escapes#single +//const singleEscapes = { +// '\\': '\\\\', +// '\b': '\\b', +// '\f': '\\f', +// '\n': '\\n', +// '\r': '\\r', +// '\t': '\\t' +// // `\v` is omitted intentionally, because in IE < 9, '\v' == 'v'. +// // '\v': '\\x0B' +//}; +//const regexSingleEscape = /[\\\b\f\n\r\t]/; +// +//const regexDigit = /[0-9]/; +//const regexWhitespace = /[\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/; +// +//const escapeEverythingRegex = /([\uD800-\uDBFF][\uDC00-\uDFFF])|([\uD800-\uDFFF])|(['"`])|[^]/g; +//const escapeNonAsciiRegex = /([\uD800-\uDBFF][\uDC00-\uDFFF])|([\uD800-\uDFFF])|(['"`])|[^ !#-&\(-\[\]-_a-~]/g; +// +template +inline void jsesc(BASE::Memory::Allocator& allocator, BASE::Number::FixedNumber const& input, Option const& opts, CB&& callback) { + constexpr bool escapeEverything = Option::escapeEverything; + bool minimal = opts.minimal; + constexpr bool isScriptContext = false; + BASE::UChar quote = opts.quotes; + bool wrap = opts.wrap; + constexpr bool es6 = Option::es6; + bool json = opts.json; + constexpr bool compact = Option::compact; + bool lowercaseHex = opts.lowercaseHex; + bool useBinNumbers = opts.numbers == 2; + bool useOctNumbers = opts.numbers == 8; + bool useDecNumbers = opts.numbers == 10; + bool useHexNumbers = opts.numbers == 16; + + if (json) { + // Some number values (e.g. `Infinity`) cannot be represented in JSON. + unreachable(); + // return JSON.stringify(argument); + } + if (useDecNumbers) { + // return String(argument); + input.template withHexString<10, 0>([&](auto&& str) { + callback(str); + }); + } else if (useHexNumbers) { + if (!lowercaseHex) { + input.template withHexString<16, u'A'>([&](auto&& str) { + callback(BASE::UStringViewArray<2>{u"0x", str}); + }); + } else { + input.template withHexString<16, u'a'>([&](auto&& str) { + callback(BASE::UStringViewArray<2>{u"0x", str}); + }); + } + } else if (useBinNumbers) { + input.template withHexString<2, 0>([&](auto&& str) { + callback(BASE::UStringViewArray<2>{u"0b", str}); + }); + } else if (useOctNumbers) { + input.template withHexString<8, 0>([&](auto&& str) { + callback(BASE::UStringViewArray<2>{u"0o", str}); + }); + } else { + unreachable(); + } +} +inline BASE::UStringViewArray<3> jsesc(BASE::Memory::Allocator& allocator, BASE::UStringView const& input, Option const& opts) { +//const jsesc = (argument, options) => { +// const increaseIndentation = () => { +// oldIndent = indent; +// ++options.indentLevel; +// indent = options.indent.repeat(options.indentLevel) +// }; + // Handle options + // quotes: "double", + // wrap: true, + // minimal: process.env.BABEL_8_BREAKING ? true : false, + // ...opts.jsescOption, + + constexpr bool escapeEverything = Option::escapeEverything; + bool minimal = opts.minimal; + constexpr bool isScriptContext = false; + BASE::UChar quote = opts.quotes; + bool wrap = opts.wrap; + constexpr bool es6 = Option::es6; + bool json = opts.json; + constexpr bool compact = Option::compact; + bool lowercaseHex = opts.lowercaseHex; + if (json) { + quote = u'"'; + wrap = true; + } +// let indent = options.indent.repeat(options.indentLevel); +// let oldIndent = ''; +// const inline1 = options.__inline1__; +// const inline2 = options.__inline2__; +// const newLine = compact ? '' : '\n'; +// let result; +// let isEmpty = true; +// const useBinNumbers = options.numbers == 'binary'; +// const useOctNumbers = options.numbers == 'octal'; +// const useDecNumbers = options.numbers == 'decimal'; +// const useHexNumbers = options.numbers == 'hexadecimal'; +// +// if (json && argument && isFunction(argument.toJSON)) { +// argument = argument.toJSON(); +// } +// +// if (!isString(argument)) { +// if (isMap(argument)) { +// if (argument.size == 0) { +// return 'new Map()'; +// } +// if (!compact) { +// options.__inline1__ = true; +// options.__inline2__ = false; +// } +// return 'new Map(' + jsesc(Array.from(argument), options) + ')'; +// } +// if (isSet(argument)) { +// if (argument.size == 0) { +// return 'new Set()'; +// } +// return 'new Set(' + jsesc(Array.from(argument), options) + ')'; +// } +// if (isBuffer(argument)) { +// if (argument.length == 0) { +// return 'Buffer.from([])'; +// } +// return 'Buffer.from(' + jsesc(Array.from(argument), options) + ')'; +// } +// if (isArray(argument)) { +// result = []; +// options.wrap = true; +// if (inline1) { +// options.__inline1__ = false; +// options.__inline2__ = true; +// } +// if (!inline2) { +// increaseIndentation(); +// } +// forEach(argument, (value) => { +// isEmpty = false; +// if (inline2) { +// options.__inline2__ = false; +// } +// result.push( +// (compact || inline2 ? '' : indent) + +// jsesc(value, options) +// ); +// }); +// if (isEmpty) { +// return '[]'; +// } +// if (inline2) { +// return '[' + result.join(', ') + ']'; +// } +// return '[' + newLine + result.join(',' + newLine) + newLine + +// (compact ? '' : oldIndent) + ']'; +// } else if (isNumber(argument)) { +// if (json) { +// // Some number values (e.g. `Infinity`) cannot be represented in JSON. +// return JSON.stringify(argument); +// } +// if (useDecNumbers) { +// return String(argument); +// } +// if (useHexNumbers) { +// let hexadecimal = argument.toString(16); +// if (!lowercaseHex) { +// hexadecimal = hexadecimal.toUpperCase(); +// } +// return '0x' + hexadecimal; +// } +// if (useBinNumbers) { +// return '0b' + argument.toString(2); +// } +// if (useOctNumbers) { +// return '0o' + argument.toString(8); +// } +// } else if (!isObject(argument)) { +// if (json) { +// // For some values (e.g. `undefined`, `function` objects), +// // `JSON.stringify(value)` returns `undefined` (which isn’t valid +// // JSON) instead of `'null'`. +// return JSON.stringify(argument) || 'null'; +// } +// return String(argument); +// } else { // it’s an object +// result = []; +// options.wrap = true; +// increaseIndentation(); +// forOwn(argument, (key, value) => { +// isEmpty = false; +// result.push( +// (compact ? '' : indent) + +// jsesc(key, options) + ':' + +// (compact ? '' : ' ') + +// jsesc(value, options) +// ); +// }); +// if (isEmpty) { +// return '{}'; +// } +// return '{' + newLine + result.join(',' + newLine) + newLine + +// (compact ? '' : oldIndent) + '}'; +// } +// } +// +// const regex = options.escapeEverything ? escapeEverythingRegex : escapeNonAsciiRegex; + BASE::String::Slices str{input, &allocator}; + auto data = input.data(); + size_t size = input.size(); + size_t index = 0; + size_t next = 1; + for(;index < size;index = next, ++next) { + BASE::UChar current_char = data[index]; + // if constexpr (quote == u'`') { + // if (current_char == u'$' && next_char == u'{') { + // str.add(u'\\'); + // str.add(index, ++next); + // continue; + // } + // } + if constexpr (!escapeEverything) { + if ( + current_char == ' ' || current_char == '!' || (current_char >= '#' && current_char <= '&') || + (current_char >= '(' && current_char <= '[') || (current_char >= ']' && current_char <= '_') || (current_char >= 'a' && current_char <= '~') + ) [[likely]] { + str.add(index, next); + continue; + } + } + if ( + current_char >= 0xD800 && current_char <= 0xDBFF + ) [[unlikely]] { + BASE::UChar next_char = next < size ? data[next] : 0; + if (next_char >= 0xDC00 && next_char <= 0xDFFF) { + if (minimal) { + str.add(index, ++next); + continue; + } else { + if constexpr (es6) { + BASE::UCodePoint codePoint = (current_char - 0xD800) * 0x400 + next_char - 0xDC00 + 0x10000; + int hex_size = 0; + str.add<3>(u"\\u{"); + auto const& hexFirst = hexadecimal(codePoint >> 32, lowercaseHex, &hex_size); + hex_size >= 4 ? str.add(hexFirst[0]) : void(0); + hex_size >= 3 ? str.add(hexFirst[1]) : void(0); + hex_size >= 2 ? str.add(hexFirst[2]) : void(0); + hex_size >= 1 ? str.add(hexFirst[3]) : void(0); + auto const& hexSecond = hexadecimal(codePoint % 0xFFFF, lowercaseHex, &hex_size); + str.add(hexSecond[0]); + str.add(hexSecond[1]); + str.add(hexSecond[2]); + str.add(hexSecond[3]); + str.add(u'}'); + } else { + fourHexEscape(str, current_char, lowercaseHex); + fourHexEscape(str, next_char, lowercaseHex); + } + ++next; + continue; + } + } + } + if ( + current_char >= 0xD800 && current_char <= 0xDFFF + ) [[unlikely]] { + fourHexEscape(str, current_char, lowercaseHex); + continue; + } + if ( + current_char == 0 && + !json + ) [[unlikely]] { + BASE::UChar next_char = next < size ? data[next] : 0; + if (!(next_char >= u'0' && next_char <= u'9')) { + str.add<3>(u"\\0"); + continue; + } + } + if ( current_char == u'\'' || current_char == u'"' || current_char == u'`') [[unlikely]] { + if (current_char == quote || escapeEverything) { + str.add(u'\\'); + } + str.add(index, next); + continue; + } + switch (current_char) { + [[unlikely]] case u'\\': str.add<2>(u"\\\\"); continue; + [[unlikely]] case u'\b': str.add<2>(u"\\b"); continue; + [[unlikely]] case u'\f': str.add<2>(u"\\f"); continue; + [[unlikely]] case u'\n': str.add<2>(u"\\n"); continue; + [[unlikely]] case u'\r': str.add<2>(u"\\r"); continue; + [[unlikely]] case u'\t': str.add<2>(u"\\t"); continue; + [[likely]] default:break; + } + if (minimal && !( + current_char == 0xa0 || current_char == 0x1680 || + (current_char >= 0x2000 && current_char <= 0x200a) || + current_char == 0x2028 || current_char == 0x2029 || current_char == 0x202f || current_char == 0x205f || current_char == 0x3000 + )) [[likely]] { + str.add(index, next); + continue; + } + + auto const& hex = hexadecimal(current_char, lowercaseHex); + if (json || current_char >= 256) { + str.add<2>(u"\\u"); + str.add(hex[0]); + str.add(hex[1]); + str.add(hex[2]); + str.add(hex[3]); + } else { + str.add<2>(u"\\x"); + str.add(hex[2]); + str.add(hex[3]); + } + continue; + } +// if (quote == '`') { +// result = result.replace(/\$\{/g, '\\${'); +// } +// if (options.isScriptContext) { +// // https://mathiasbynens.be/notes/etago +// result = result +// .replace(/<\/(script|style)/gi, '<\\/$1') +// .replace(/