From 9cd5c0ec79b532621605072994d98f47a2867f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Sun, 24 Jun 2018 16:56:00 +0200 Subject: [PATCH 001/116] test: add test for missing dynamic instantiate hook PR-URL: https://github.com/nodejs/node/pull/21506 Reviewed-By: Benjamin Gruenbaum Reviewed-By: James M Snell Reviewed-By: Gus Caplan --- ...esm-loader-missing-dynamic-instantiate-hook.mjs | 14 ++++++++++++++ .../missing-dynamic-instantiate-hook.mjs | 6 ++++++ 2 files changed, 20 insertions(+) create mode 100644 test/es-module/test-esm-loader-missing-dynamic-instantiate-hook.mjs create mode 100644 test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs diff --git a/test/es-module/test-esm-loader-missing-dynamic-instantiate-hook.mjs b/test/es-module/test-esm-loader-missing-dynamic-instantiate-hook.mjs new file mode 100644 index 00000000000000..27447b3e436afe --- /dev/null +++ b/test/es-module/test-esm-loader-missing-dynamic-instantiate-hook.mjs @@ -0,0 +1,14 @@ +// Flags: --experimental-modules --loader ./test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs + +import { + crashOnUnhandledRejection, + expectsError +} from '../common'; + +crashOnUnhandledRejection(); + +import('test').catch(expectsError({ + code: 'ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK', + message: 'The ES Module loader may not return a format of \'dynamic\' ' + + 'when no dynamicInstantiate function was provided' +})); diff --git a/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs b/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs new file mode 100644 index 00000000000000..6993747fcc0142 --- /dev/null +++ b/test/fixtures/es-module-loaders/missing-dynamic-instantiate-hook.mjs @@ -0,0 +1,6 @@ +export function resolve(specifier, parentModule, defaultResolver) { + if (specifier !== 'test') { + return defaultResolver(specifier, parentModule); + } + return { url: 'file://', format: 'dynamic' }; +} From 96dae837137fb5352a274f4e6e833d20dd90c115 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sat, 30 Jun 2018 14:58:19 +0200 Subject: [PATCH 002/116] zlib: fix memory leak for unused zlib instances MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An oversight in an earlier commit led to a memory leak in the untypical situation that zlib instances are created but never used, because zlib handles no longer started out their life as weak handles. The bug was introduced in bd201102862a194f3f5ce669e0a3c8143eafc900. Refs: https://github.com/nodejs/node/pull/20455 PR-URL: https://github.com/nodejs/node/pull/21607 Reviewed-By: Tobias Nießen --- src/node_zlib.cc | 1 + test/parallel/test-zlib-unused-weak.js | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 test/parallel/test-zlib-unused-weak.js diff --git a/src/node_zlib.cc b/src/node_zlib.cc index de8b0335892f65..8e30241f4e6d45 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -90,6 +90,7 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork { refs_(0), gzip_id_bytes_read_(0), write_result_(nullptr) { + MakeWeak(); } diff --git a/test/parallel/test-zlib-unused-weak.js b/test/parallel/test-zlib-unused-weak.js new file mode 100644 index 00000000000000..7a5a6728533e18 --- /dev/null +++ b/test/parallel/test-zlib-unused-weak.js @@ -0,0 +1,18 @@ +'use strict'; +// Flags: --expose-gc +require('../common'); +const assert = require('assert'); +const zlib = require('zlib'); + +// Tests that native zlib handles start out their life as weak handles. + +const before = process.memoryUsage().external; +for (let i = 0; i < 100; ++i) + zlib.createGzip(); +const afterCreation = process.memoryUsage().external; +global.gc(); +const afterGC = process.memoryUsage().external; + +assert((afterGC - before) / (afterCreation - before) <= 0.05, + `Expected after-GC delta ${afterGC - before} to be less than 5 %` + + ` of before-GC delta ${afterCreation - before}`); From 3a627c830b15fd1c5ce9d8926991abf6772b8fb8 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Wed, 13 Jun 2018 14:01:33 -0400 Subject: [PATCH 003/116] src: add context-aware init macro and doc Introduces macros `NODE_MODULE_INITIALIZER` which expands to the name of the special symbol that process.dlopen() will look for to initialize an addon, and `NODE_MODULE_INIT()` which creates the boilerplate for a context-aware module which can be loaded multiple times via the special symbol mechanism. Additionally, provides an example of using the new macro to construct an addon which stores per-addon-instance data in a heap-allocated structure that gets passed to each binding, rather than in a collection of global static variables. Re: https://github.com/nodejs/node/issues/21291#issuecomment-396729727 PR-URL: https://github.com/nodejs/node/pull/21318 Reviewed-By: Matteo Collina Reviewed-By: James M Snell Reviewed-By: Ujjwal Sharma --- doc/api/addons.md | 135 +++++++++++++++++++++++++++++ src/node.h | 22 +++++ test/addons/hello-world/binding.cc | 13 ++- 3 files changed, 163 insertions(+), 7 deletions(-) diff --git a/doc/api/addons.md b/doc/api/addons.md index 578b1a0f90b4ce..9ea0a6b6446ce4 100644 --- a/doc/api/addons.md +++ b/doc/api/addons.md @@ -98,6 +98,140 @@ the `.node` suffix). In the `hello.cc` example, then, the initialization function is `Initialize` and the addon module name is `addon`. +When building addons with `node-gyp`, using the macro `NODE_GYP_MODULE_NAME` as +the first parameter of `NODE_MODULE()` will ensure that the name of the final +binary will be passed to `NODE_MODULE()`. + +### Context-aware addons + +There are environments in which Node.js addons may need to be loaded multiple +times in multiple contexts. For example, the [Electron][] runtime runs multiple +instances of Node.js in a single process. Each instance will have its own +`require()` cache, and thus each instance will need a native addon to behave +correctly when loaded via `require()`. From the addon's perspective, this means +that it must support multiple initializations. + +A context-aware addon can be constructed by using the macro +`NODE_MODULE_INITIALIZER`, which expands to the name of a function which Node.js +will expect to find when it loads an addon. An addon can thus be initialized as +in the following example: + +```cpp +using namespace v8; + +extern "C" NODE_MODULE_EXPORT void +NODE_MODULE_INITIALIZER(Local exports, + Local module, + Local context) { + /* Perform addon initialization steps here. */ +} +``` + +Another option is to use the macro `NODE_MODULE_INIT()`, which will also +construct a context-aware addon. Unlike `NODE_MODULE()`, which is used to +construct an addon around a given addon initializer function, +`NODE_MODULE_INIT()` serves as the declaration of such an initializer to be +followed by a function body. + +The following three variables may be used inside the function body following an +invocation of `NODE_MODULE_INIT()`: +* `Local exports`, +* `Local module`, and +* `Local context` + +The choice to build a context-aware addon carries with it the responsibility of +carefully managing global static data. Since the addon may be loaded multiple +times, potentially even from different threads, any global static data stored +in the addon must be properly protected, and must not contain any persistent +references to JavaScript objects. The reason for this is that JavaScript +objects are only valid in one context, and will likely cause a crash when +accessed from the wrong context or from a different thread than the one on which +they were created. + +The context-aware addon can be structured to avoid global static data by +performing the following steps: +* defining a class which will hold per-addon-instance data. Such +a class should include a `v8::Persistent` which will hold a weak +reference to the addon's `exports` object. The callback associated with the weak +reference will then destroy the instance of the class. +* constructing an instance of this class in the addon initializer such that the +`v8::Persistent` is set to the `exports` object. +* storing the instance of the class in a `v8::External`, and +* passing the `v8::External` to all methods exposed to JavaScript by passing it +to the `v8::FunctionTemplate` constructor which creates the native-backed +JavaScript functions. The `v8::FunctionTemplate` constructor's third parameter +accepts the `v8::External`. + +This will ensure that the per-addon-instance data reaches each binding that can +be called from JavaScript. The per-addon-instance data must also be passed into +any asynchronous callbacks the addon may create. + +The following example illustrates the implementation of a context-aware addon: + +```cpp +#include + +using namespace v8; + +class AddonData { + public: + AddonData(Isolate* isolate, Local exports): + call_count(0) { + // Link the existence of this object instance to the existence of exports. + exports_.Reset(isolate, exports); + exports_.SetWeak(this, DeleteMe, WeakCallbackType::kParameter); + } + + ~AddonData() { + if (!exports_.IsEmpty()) { + // Reset the reference to avoid leaking data. + exports_.ClearWeak(); + exports_.Reset(); + } + } + + // Per-addon data. + int call_count; + + private: + // Method to call when "exports" is about to be garbage-collected. + static void DeleteMe(const WeakCallbackInfo& info) { + delete info.GetParameter(); + } + + // Weak handle to the "exports" object. An instance of this class will be + // destroyed along with the exports object to which it is weakly bound. + v8::Persistent exports_; +}; + +static void Method(const v8::FunctionCallbackInfo& info) { + // Retrieve the per-addon-instance data. + AddonData* data = + reinterpret_cast(info.Data().As()->Value()); + data->call_count++; + info.GetReturnValue().Set((double)data->call_count); +} + +// Initialize this addon to be context-aware. +NODE_MODULE_INIT(/* exports, module, context */) { + Isolate* isolate = context->GetIsolate(); + + // Create a new instance of AddonData for this instance of the addon. + AddonData* data = new AddonData(isolate, exports); + // Wrap the data in a v8::External so we can pass it to the method we expose. + Local external = External::New(isolate, data); + + // Expose the method "Method" to JavaScript, and make sure it receives the + // per-addon-instance data we created above by passing `external` as the + // third parameter to the FunctionTemplate constructor. + exports->Set(context, + String::NewFromUtf8(isolate, "method", NewStringType::kNormal) + .ToLocalChecked(), + FunctionTemplate::New(isolate, Method, external) + ->GetFunction(context).ToLocalChecked()).FromJust(); +} +``` + ### Building Once the source code has been written, it must be compiled into the binary @@ -1162,6 +1296,7 @@ Test in JavaScript by running: require('./build/Release/addon'); ``` +[Electron]: https://electronjs.org/ [Embedder's Guide]: https://github.com/v8/v8/wiki/Embedder's%20Guide [Linking to Node.js' own dependencies]: #addons_linking_to_node_js_own_dependencies [Native Abstractions for Node.js]: https://github.com/nodejs/nan diff --git a/src/node.h b/src/node.h index b551c8bb09baf9..ed2c074b922687 100644 --- a/src/node.h +++ b/src/node.h @@ -579,6 +579,28 @@ extern "C" NODE_EXTERN void node_module_register(void* mod); */ #define NODE_MODULE_DECL /* nothing */ +#define NODE_MODULE_INITIALIZER_BASE node_register_module_v + +#define NODE_MODULE_INITIALIZER_X(base, version) \ + NODE_MODULE_INITIALIZER_X_HELPER(base, version) + +#define NODE_MODULE_INITIALIZER_X_HELPER(base, version) base##version + +#define NODE_MODULE_INITIALIZER \ + NODE_MODULE_INITIALIZER_X(NODE_MODULE_INITIALIZER_BASE, \ + NODE_MODULE_VERSION) + +#define NODE_MODULE_INIT() \ + extern "C" NODE_MODULE_EXPORT void \ + NODE_MODULE_INITIALIZER(v8::Local exports, \ + v8::Local module, \ + v8::Local context); \ + NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, \ + NODE_MODULE_INITIALIZER) \ + void NODE_MODULE_INITIALIZER(v8::Local exports, \ + v8::Local module, \ + v8::Local context) + /* Called after the event loop exits but before the VM is disposed. * Callbacks are run in reverse order of registration, i.e. newest first. */ diff --git a/test/addons/hello-world/binding.cc b/test/addons/hello-world/binding.cc index e267a3b2a7629a..341b58f9a640d8 100644 --- a/test/addons/hello-world/binding.cc +++ b/test/addons/hello-world/binding.cc @@ -6,13 +6,12 @@ void Method(const v8::FunctionCallbackInfo& args) { args.GetReturnValue().Set(v8::String::NewFromUtf8(isolate, "world")); } -#define CONCAT(a, b) CONCAT_HELPER(a, b) -#define CONCAT_HELPER(a, b) a##b -#define INITIALIZER CONCAT(node_register_module_v, NODE_MODULE_VERSION) - -extern "C" NODE_MODULE_EXPORT void INITIALIZER(v8::Local exports, - v8::Local module, - v8::Local context) { +// Not using the full NODE_MODULE_INIT() macro here because we want to test the +// addon loader's reaction to the FakeInit() entry point below. +extern "C" NODE_MODULE_EXPORT void +NODE_MODULE_INITIALIZER(v8::Local exports, + v8::Local module, + v8::Local context) { NODE_SET_METHOD(exports, "hello", Method); } From 221c8bd58fa7b3277749bf39e43bd055dff1de67 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Mon, 25 Jun 2018 00:48:48 -0400 Subject: [PATCH 004/116] messaging: use actual DOMException for DataCloneError PR-URL: https://github.com/nodejs/node/pull/21540 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- lib/internal/bootstrap/node.js | 9 ++++ lib/internal/domexception.js | 83 ++++++++++++++++++++++++++++++++++ node.gyp | 1 + src/env.h | 1 + src/node_messaging.cc | 26 ++++++++++- 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 lib/internal/domexception.js diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index d120d8ccae14e6..df5d667aeb7b39 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -101,6 +101,10 @@ setupGlobalURL(); } + if (process.binding('config').experimentalWorker) { + setupDOMException(); + } + // On OpenBSD process.execPath will be relative unless we // get the full path before process.execPath is used. if (process.platform === 'openbsd') { @@ -381,6 +385,11 @@ }); } + function setupDOMException() { + // Registers the constructor with C++. + NativeModule.require('internal/domexception'); + } + function setupInspector(originalConsole, wrappedConsole, CJSModule) { if (!process.config.variables.v8_enable_inspector) { return; diff --git a/lib/internal/domexception.js b/lib/internal/domexception.js new file mode 100644 index 00000000000000..fe371e099eb17f --- /dev/null +++ b/lib/internal/domexception.js @@ -0,0 +1,83 @@ +'use strict'; + +const { internalBinding } = require('internal/bootstrap/loaders'); +const { registerDOMException } = internalBinding('messaging'); +const { ERR_INVALID_THIS } = require('internal/errors').codes; + +const internalsMap = new WeakMap(); + +const nameToCodeMap = new Map(); + +class DOMException extends Error { + constructor(message = '', name = 'Error') { + super(); + internalsMap.set(this, { + message: `${message}`, + name: `${name}` + }); + } + + get name() { + const internals = internalsMap.get(this); + if (internals === undefined) { + throw new ERR_INVALID_THIS('DOMException'); + } + return internals.name; + } + + get message() { + const internals = internalsMap.get(this); + if (internals === undefined) { + throw new ERR_INVALID_THIS('DOMException'); + } + return internals.message; + } + + get code() { + const internals = internalsMap.get(this); + if (internals === undefined) { + throw new ERR_INVALID_THIS('DOMException'); + } + const code = nameToCodeMap.get(internals.name); + return code === undefined ? 0 : code; + } +} + +for (const [name, codeName, value] of [ + ['IndexSizeError', 'INDEX_SIZE_ERR', 1], + ['DOMStringSizeError', 'DOMSTRING_SIZE_ERR', 2], + ['HierarchyRequestError', 'HIERARCHY_REQUEST_ERR', 3], + ['WrongDocumentError', 'WRONG_DOCUMENT_ERR', 4], + ['InvalidCharacterError', 'INVALID_CHARACTER_ERR', 5], + ['NoDataAllowedError', 'NO_DATA_ALLOWED_ERR', 6], + ['NoModificationAllowedError', 'NO_MODIFICATION_ALLOWED_ERR', 7], + ['NotFoundError', 'NOT_FOUND_ERR', 8], + ['NotSupportedError', 'NOT_SUPPORTED_ERR', 9], + ['InUseAttributeError', 'INUSE_ATTRIBUTE_ERR', 10], + ['InvalidStateError', 'INVALID_STATE_ERR', 11], + ['SyntaxError', 'SYNTAX_ERR', 12], + ['InvalidModificationError', 'INVALID_MODIFICATION_ERR', 13], + ['NamespaceError', 'NAMESPACE_ERR', 14], + ['InvalidAccessError', 'INVALID_ACCESS_ERR', 15], + ['ValidationError', 'VALIDATION_ERR', 16], + ['TypeMismatchError', 'TYPE_MISMATCH_ERR', 17], + ['SecurityError', 'SECURITY_ERR', 18], + ['NetworkError', 'NETWORK_ERR', 19], + ['AbortError', 'ABORT_ERR', 20], + ['URLMismatchError', 'URL_MISMATCH_ERR', 21], + ['QuotaExceededError', 'QUOTA_EXCEEDED_ERR', 22], + ['TimeoutError', 'TIMEOUT_ERR', 23], + ['InvalidNodeTypeError', 'INVALID_NODE_TYPE_ERR', 24], + ['DataCloneError', 'DATA_CLONE_ERR', 25] + // There are some more error names, but since they don't have codes assigned, + // we don't need to care about them. +]) { + const desc = { enumerable: true, value }; + Object.defineProperty(DOMException, codeName, desc); + Object.defineProperty(DOMException.prototype, codeName, desc); + nameToCodeMap.set(name, value); +} + +module.exports = DOMException; + +registerDOMException(DOMException); diff --git a/node.gyp b/node.gyp index 300a20b6e306c4..207c72cdb56a29 100644 --- a/node.gyp +++ b/node.gyp @@ -106,6 +106,7 @@ 'lib/internal/constants.js', 'lib/internal/dns/promises.js', 'lib/internal/dns/utils.js', + 'lib/internal/domexception.js', 'lib/internal/encoding.js', 'lib/internal/errors.js', 'lib/internal/error-serdes.js', diff --git a/src/env.h b/src/env.h index 6c759c84e7685d..c7e75004bf0ceb 100644 --- a/src/env.h +++ b/src/env.h @@ -322,6 +322,7 @@ struct PackageConfig { V(buffer_prototype_object, v8::Object) \ V(context, v8::Context) \ V(domain_callback, v8::Function) \ + V(domexception_function, v8::Function) \ V(fdclose_constructor_template, v8::ObjectTemplate) \ V(fd_constructor_template, v8::ObjectTemplate) \ V(filehandlereadwrap_template, v8::ObjectTemplate) \ diff --git a/src/node_messaging.cc b/src/node_messaging.cc index be37cd39c366d0..7ddd3c4165c511 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -144,6 +144,21 @@ void Message::AddMessagePort(std::unique_ptr&& data) { namespace { +void ThrowDataCloneError(Environment* env, Local message) { + Local argv[] = { + message, + FIXED_ONE_BYTE_STRING(env->isolate(), "DataCloneError") + }; + Local exception; + Local domexception_ctor = env->domexception_function(); + CHECK(!domexception_ctor.IsEmpty()); + if (!domexception_ctor->NewInstance(env->context(), arraysize(argv), argv) + .ToLocal(&exception)) { + return; + } + env->isolate()->ThrowException(exception); +} + // This tells V8 how to serialize objects that it does not understand // (e.g. C++ objects) into the output buffer, in a way that our own // DeserializerDelegate understands how to unpack. @@ -153,7 +168,7 @@ class SerializerDelegate : public ValueSerializer::Delegate { : env_(env), context_(context), msg_(m) {} void ThrowDataCloneError(Local message) override { - env_->isolate()->ThrowException(Exception::Error(message)); + ThrowDataCloneError(env_, message); } Maybe WriteHostObject(Isolate* isolate, Local object) override { @@ -688,6 +703,13 @@ static void MessageChannel(const FunctionCallbackInfo& args) { .FromJust(); } +static void RegisterDOMException(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK_EQ(args.Length(), 1); + CHECK(args[0]->IsFunction()); + env->set_domexception_function(args[0].As()); +} + static void InitMessaging(Local target, Local unused, Local context, @@ -708,6 +730,8 @@ static void InitMessaging(Local target, env->message_port_constructor_string(), GetMessagePortConstructor(env, context).ToLocalChecked()) .FromJust(); + + env->SetMethod(target, "registerDOMException", RegisterDOMException); } } // anonymous namespace From ed774b7930f699119f36d6afa23b4e9e6c35aa6a Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sun, 24 Jun 2018 23:10:37 -0400 Subject: [PATCH 005/116] messaging: fix edge cases with transferring ports Currently, transferring the port on which postMessage is called causes a segmentation fault, and transferring the target port causes a subsequent port.onmessage setting to throw, or a deadlock if onmessage is set before the postMessage. Fix both of these behaviors and align the methods more closely with the normative definitions in the HTML Standard. Also, per spec postMessage must not throw just because the ports are disentangled. Implement that behavior. PR-URL: https://github.com/nodejs/node/pull/21540 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell --- src/node_messaging.cc | 99 ++++++++++++++----- src/node_messaging.h | 31 +++++- ...est-worker-message-port-transfer-closed.js | 54 ++++++++++ .../test-worker-message-port-transfer-self.js | 33 +++++++ ...est-worker-message-port-transfer-target.js | 24 +++++ 5 files changed, 213 insertions(+), 28 deletions(-) create mode 100644 test/parallel/test-worker-message-port-transfer-closed.js create mode 100644 test/parallel/test-worker-message-port-transfer-self.js create mode 100644 test/parallel/test-worker-message-port-transfer-target.js diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 7ddd3c4165c511..712add06d3e2bc 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -144,7 +144,7 @@ void Message::AddMessagePort(std::unique_ptr&& data) { namespace { -void ThrowDataCloneError(Environment* env, Local message) { +void ThrowDataCloneException(Environment* env, Local message) { Local argv[] = { message, FIXED_ONE_BYTE_STRING(env->isolate(), "DataCloneError") @@ -168,7 +168,7 @@ class SerializerDelegate : public ValueSerializer::Delegate { : env_(env), context_(context), msg_(m) {} void ThrowDataCloneError(Local message) override { - ThrowDataCloneError(env_, message); + ThrowDataCloneException(env_, message); } Maybe WriteHostObject(Isolate* isolate, Local object) override { @@ -239,7 +239,8 @@ class SerializerDelegate : public ValueSerializer::Delegate { Maybe Message::Serialize(Environment* env, Local context, Local input, - Local transfer_list_v) { + Local transfer_list_v, + Local source_port) { HandleScope handle_scope(env->isolate()); Context::Scope context_scope(context); @@ -273,8 +274,23 @@ Maybe Message::Serialize(Environment* env, continue; } else if (env->message_port_constructor_template() ->HasInstance(entry)) { + // Check if the source MessagePort is being transferred. + if (!source_port.IsEmpty() && entry == source_port) { + ThrowDataCloneException( + env, + FIXED_ONE_BYTE_STRING(env->isolate(), + "Transfer list contains source port")); + return Nothing(); + } MessagePort* port = Unwrap(entry.As()); - CHECK_NE(port, nullptr); + if (port == nullptr || port->IsDetached()) { + ThrowDataCloneException( + env, + FIXED_ONE_BYTE_STRING( + env->isolate(), + "MessagePort in transfer list is already detached")); + return Nothing(); + } delegate.ports_.push_back(port); continue; } @@ -410,6 +426,10 @@ uv_async_t* MessagePort::async() { return reinterpret_cast(GetHandle()); } +bool MessagePort::IsDetached() const { + return data_ == nullptr || IsHandleClosing(); +} + void MessagePort::TriggerAsync() { if (IsHandleClosing()) return; CHECK_EQ(uv_async_send(async()), 0); @@ -552,36 +572,69 @@ std::unique_ptr MessagePort::Detach() { } -void MessagePort::Send(Message&& message) { - Mutex::ScopedLock lock(*data_->sibling_mutex_); - if (data_->sibling_ == nullptr) - return; - data_->sibling_->AddToIncomingQueue(std::move(message)); -} +Maybe MessagePort::PostMessage(Environment* env, + Local message_v, + Local transfer_v) { + Isolate* isolate = env->isolate(); + Local obj = object(isolate); + Local context = obj->CreationContext(); -void MessagePort::Send(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - Local context = object(env->isolate())->CreationContext(); Message msg; - if (msg.Serialize(env, context, args[0], args[1]) - .IsNothing()) { - return; + + // Per spec, we need to both check if transfer list has the source port, and + // serialize the input message, even if the MessagePort is closed or detached. + + Maybe serialization_maybe = + msg.Serialize(env, context, message_v, transfer_v, obj); + if (data_ == nullptr) { + return serialization_maybe; + } + if (serialization_maybe.IsNothing()) { + return Nothing(); + } + + Mutex::ScopedLock lock(*data_->sibling_mutex_); + bool doomed = false; + + // Check if the target port is posted to itself. + if (data_->sibling_ != nullptr) { + for (const auto& port_data : msg.message_ports()) { + if (data_->sibling_ == port_data.get()) { + doomed = true; + ProcessEmitWarning(env, "The target port was posted to itself, and " + "the communication channel was lost"); + break; + } + } } - Send(std::move(msg)); + + if (data_->sibling_ == nullptr || doomed) + return Just(true); + + data_->sibling_->AddToIncomingQueue(std::move(msg)); + return Just(true); } void MessagePort::PostMessage(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - MessagePort* port; - ASSIGN_OR_RETURN_UNWRAP(&port, args.This()); - if (!port->data_) { - return THROW_ERR_CLOSED_MESSAGE_PORT(env); - } if (args.Length() == 0) { return THROW_ERR_MISSING_ARGS(env, "Not enough arguments to " "MessagePort.postMessage"); } - port->Send(args); + + MessagePort* port = Unwrap(args.This()); + // Even if the backing MessagePort object has already been deleted, we still + // want to serialize the message to ensure spec-compliant behavior w.r.t. + // transfers. + if (port == nullptr) { + Message msg; + Local obj = args.This(); + Local context = obj->CreationContext(); + USE(msg.Serialize(env, context, args[0], args[1], obj)); + return; + } + + port->PostMessage(env, args[0], args[1]); } void MessagePort::Start() { diff --git a/src/node_messaging.h b/src/node_messaging.h index 28122c526cccea..62ae633b9e0b8d 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -32,10 +32,14 @@ class Message { // Serialize a JS value, and optionally transfer objects, into this message. // The Message object retains ownership of all transferred objects until // deserialization. + // The source_port parameter, if provided, will make Serialize() throw a + // "DataCloneError" DOMException if source_port is found in transfer_list. v8::Maybe Serialize(Environment* env, v8::Local context, v8::Local input, - v8::Local transfer_list); + v8::Local transfer_list, + v8::Local source_port = + v8::Local()); // Internal method of Message that is called when a new SharedArrayBuffer // object is encountered in the incoming value's structure. @@ -44,6 +48,13 @@ class Message { // and that transfers ownership of `data` to this message. void AddMessagePort(std::unique_ptr&& data); + // The MessagePorts that will be transferred, as recorded by Serialize(). + // Used for warning user about posting the target MessagePort to itself, + // which will as a side effect destroy the communication channel. + const std::vector>& message_ports() const { + return message_ports_; + } + private: MallocedBuffer main_message_buf_; std::vector> array_buffer_contents_; @@ -122,10 +133,11 @@ class MessagePort : public HandleWrap { std::unique_ptr data = nullptr); // Send a message, i.e. deliver it into the sibling's incoming queue. - // If there is no sibling, i.e. this port is closed, - // this message is silently discarded. - void Send(Message&& message); - void Send(const v8::FunctionCallbackInfo& args); + // If this port is closed, or if there is no sibling, this message is + // serialized with transfers, then silently discarded. + v8::Maybe PostMessage(Environment* env, + v8::Local message, + v8::Local transfer); // Deliver a single message into this port's incoming queue. void AddToIncomingQueue(Message&& message); @@ -157,6 +169,15 @@ class MessagePort : public HandleWrap { void Close( v8::Local close_callback = v8::Local()) override; + // Returns true if either data_ has been freed, or if the handle is being + // closed. Equivalent to the [[Detached]] internal slot in the HTML Standard. + // + // If checking if a JavaScript MessagePort object is detached, this method + // alone is often not enough, since the backing C++ MessagePort object may + // have been deleted already. For all intents and purposes, an object with a + // NULL pointer to the C++ MessagePort object is also detached. + inline bool IsDetached() const; + size_t self_size() const override; private: diff --git a/test/parallel/test-worker-message-port-transfer-closed.js b/test/parallel/test-worker-message-port-transfer-closed.js new file mode 100644 index 00000000000000..435a3789fca724 --- /dev/null +++ b/test/parallel/test-worker-message-port-transfer-closed.js @@ -0,0 +1,54 @@ +// Flags: --experimental-worker +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { MessageChannel } = require('worker_threads'); + +// This tests various behaviors around transferring MessagePorts with closing +// or closed handles. + +const { port1, port2 } = new MessageChannel(); + +const arrayBuf = new ArrayBuffer(10); +port1.onmessage = common.mustNotCall(); +port2.onmessage = common.mustNotCall(); + +function testSingle(closedPort, potentiallyOpenPort) { + assert.throws(common.mustCall(() => { + potentiallyOpenPort.postMessage(null, [arrayBuf, closedPort]); + }), common.mustCall((err) => { + assert.strictEqual(err.name, 'DataCloneError'); + assert.strictEqual(err.message, + 'MessagePort in transfer list is already detached'); + assert.strictEqual(err.code, 25); + assert.ok(err instanceof Error); + + const DOMException = err.constructor; + assert.ok(err instanceof DOMException); + assert.strictEqual(DOMException.name, 'DOMException'); + + return true; + })); + + // arrayBuf must not be transferred, even though it is present earlier in the + // transfer list than the closedPort. + assert.strictEqual(arrayBuf.byteLength, 10); +} + +function testBothClosed() { + testSingle(port1, port2); + testSingle(port2, port1); +} + +// Even though the port handles may not be completely closed in C++ land, the +// observable behavior must be that the closing/detachment is synchronous and +// instant. + +port1.close(common.mustCall(testBothClosed)); +testSingle(port1, port2); +port2.close(common.mustCall(testBothClosed)); +testBothClosed(); + +setTimeout(common.mustNotCall('The communication channel is still open'), + common.platformTimeout(1000)).unref(); diff --git a/test/parallel/test-worker-message-port-transfer-self.js b/test/parallel/test-worker-message-port-transfer-self.js new file mode 100644 index 00000000000000..1855023cdfae04 --- /dev/null +++ b/test/parallel/test-worker-message-port-transfer-self.js @@ -0,0 +1,33 @@ +// Flags: --experimental-worker +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { MessageChannel } = require('worker_threads'); + +const { port1, port2 } = new MessageChannel(); + +assert.throws(common.mustCall(() => { + port1.postMessage(null, [port1]); +}), common.mustCall((err) => { + assert.strictEqual(err.name, 'DataCloneError'); + assert.strictEqual(err.message, 'Transfer list contains source port'); + assert.strictEqual(err.code, 25); + assert.ok(err instanceof Error); + + const DOMException = err.constructor; + assert.ok(err instanceof DOMException); + assert.strictEqual(DOMException.name, 'DOMException'); + + return true; +})); + +// The failed transfer should not affect the ports in anyway. +port2.onmessage = common.mustCall((message) => { + assert.strictEqual(message, 2); + port1.close(); + + setTimeout(common.mustNotCall('The communication channel is still open'), + common.platformTimeout(1000)).unref(); +}); +port1.postMessage(2); diff --git a/test/parallel/test-worker-message-port-transfer-target.js b/test/parallel/test-worker-message-port-transfer-target.js new file mode 100644 index 00000000000000..8e6354d8269771 --- /dev/null +++ b/test/parallel/test-worker-message-port-transfer-target.js @@ -0,0 +1,24 @@ +// Flags: --experimental-worker +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const { MessageChannel } = require('worker_threads'); + +const { port1, port2 } = new MessageChannel(); + +const arrayBuf = new ArrayBuffer(10); + +common.expectWarning('Warning', + 'The target port was posted to itself, and the ' + + 'communication channel was lost', + common.noWarnCode); +port2.onmessage = common.mustNotCall(); +port2.postMessage(null, [port1, arrayBuf]); + +// arrayBuf must be transferred, despite the fact that port2 never received the +// message. +assert.strictEqual(arrayBuf.byteLength, 0); + +setTimeout(common.mustNotCall('The communication channel is still open'), + common.platformTimeout(1000)).unref(); From fe9888a34a4367e90fe17d695d5ae1f555ce64da Mon Sep 17 00:00:00 2001 From: Masashi Hirano Date: Mon, 2 Jul 2018 03:52:30 +0900 Subject: [PATCH 006/116] test: check type for Worker filename argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add test to check type for first argument of Worker to increase coverage. PR-URL: https://github.com/nodejs/node/pull/21620 Reviewed-By: Luigi Pinca Reviewed-By: Trivikram Kamat Reviewed-By: Tobias Nießen Reviewed-By: Colin Ihrig Reviewed-By: Benjamin Gruenbaum Reviewed-By: Minwoo Jung Reviewed-By: Tiancheng "Timothy" Gu --- test/parallel/test-worker-type-check.js | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 test/parallel/test-worker-type-check.js diff --git a/test/parallel/test-worker-type-check.js b/test/parallel/test-worker-type-check.js new file mode 100644 index 00000000000000..43013bc538712a --- /dev/null +++ b/test/parallel/test-worker-type-check.js @@ -0,0 +1,28 @@ +// Flags: --experimental-worker +'use strict'; + +const common = require('../common'); +const { Worker } = require('worker_threads'); + +{ + [ + undefined, + null, + false, + 0, + Symbol('test'), + {}, + [], + () => {} + ].forEach((val) => { + common.expectsError( + () => new Worker(val), + { + code: 'ERR_INVALID_ARG_TYPE', + type: TypeError, + message: 'The "filename" argument must be of type string. ' + + `Received type ${typeof val}` + } + ); + }); +} From 25dac9516453cba3ba492abd301394ec4024f300 Mon Sep 17 00:00:00 2001 From: Haroon Khan Date: Wed, 27 Jun 2018 18:51:53 +0500 Subject: [PATCH 007/116] test: fix args passed to strictEqual The third argument passed to asert.strictEqual() displays the message passed as third argument and does not report the difference between actual and expected if the test is failing. Hence, the third argument has been removed. PR-URL: https://github.com/nodejs/node/pull/21584 Reviewed-By: Rich Trott Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Luigi Pinca Reviewed-By: James M Snell Reviewed-By: Trivikram Kamat --- test/parallel/test-timers-immediate-queue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-timers-immediate-queue.js b/test/parallel/test-timers-immediate-queue.js index 511a3adf864859..ba9ba87c4070bd 100644 --- a/test/parallel/test-timers-immediate-queue.js +++ b/test/parallel/test-timers-immediate-queue.js @@ -50,5 +50,5 @@ for (let i = 0; i < QUEUE; i++) process.on('exit', function() { console.log('hit', hit); - assert.strictEqual(hit, QUEUE, 'We ticked between the immediate queue'); + assert.strictEqual(hit, QUEUE); }); From 6f8ebc08b9f7537f82f57f07c6911c176627722b Mon Sep 17 00:00:00 2001 From: Thomas Watson Date: Tue, 3 Jul 2018 10:56:29 +0200 Subject: [PATCH 008/116] doc: unify spelling of backpressure PR-URL: https://github.com/nodejs/node/pull/21630 Reviewed-By: Gireesh Punathil Reviewed-By: Colin Ihrig Reviewed-By: Vse Mozhet Byt Reviewed-By: Trivikram Kamat --- doc/api/stream.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/stream.md b/doc/api/stream.md index fe0eae32ed8417..7e7a3fb92a933d 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -637,7 +637,7 @@ state, attaching a listener for the `'data'` event, calling the `readable.readableFlowing` to `true`, causing the `Readable` to begin actively emitting events as data is generated. -Calling `readable.pause()`, `readable.unpipe()`, or receiving "back pressure" +Calling `readable.pause()`, `readable.unpipe()`, or receiving backpressure will cause the `readable.readableFlowing` to be set as `false`, temporarily halting the flowing of events but *not* halting the generation of data. While in this state, attaching a listener for the `'data'` event From 4fa7150962621cc4d68640c37cbbb7967780b7a9 Mon Sep 17 00:00:00 2001 From: Timothy Gu Date: Sun, 24 Jun 2018 01:11:53 -0400 Subject: [PATCH 009/116] fs: support pseudofiles in promises.readFile PR-URL: https://github.com/nodejs/node/pull/21497 Fixes: https://github.com/nodejs/node/issues/21331 Refs: http://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html Refs: https://groups.google.com/forum/#!topic/nodejs-dev/rxZ_RoH1Gn0 Reviewed-By: Gus Caplan Reviewed-By: Anna Henningsen Reviewed-By: Trivikram Kamat Reviewed-By: Colin Ihrig Reviewed-By: Benjamin Gruenbaum --- lib/internal/fs/promises.js | 17 +++---- .../test-fs-promises-file-handle-readFile.js | 17 +++++++ test/parallel/test-fs-promises-readfile.js | 44 +++++++++++++------ 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index befcccb2769589..e567bf0731b5c7 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -125,6 +125,10 @@ async function writeFileHandle(filehandle, data, options) { } while (remaining > 0); } +// Note: This is different from kReadFileBufferLength used for non-promisified +// fs.readFile. +const kReadFileMaxChunkSize = 16384; + async function readFileHandle(filehandle, options) { const statFields = await binding.fstat(filehandle.fd, false, kUsePromises); @@ -135,22 +139,19 @@ async function readFileHandle(filehandle, options) { size = 0; } - if (size === 0) - return options.encoding ? '' : Buffer.alloc(0); - if (size > kMaxLength) throw new ERR_FS_FILE_TOO_LARGE(size); const chunks = []; - const chunkSize = Math.min(size, 16384); - let totalRead = 0; + const chunkSize = size === 0 ? + kReadFileMaxChunkSize : + Math.min(size, kReadFileMaxChunkSize); let endOfFile = false; do { const buf = Buffer.alloc(chunkSize); const { bytesRead, buffer } = - await read(filehandle, buf, 0, chunkSize, totalRead); - totalRead += bytesRead; - endOfFile = bytesRead !== chunkSize; + await read(filehandle, buf, 0, chunkSize, -1); + endOfFile = bytesRead === 0; if (bytesRead > 0) chunks.push(buffer.slice(0, bytesRead)); } while (!endOfFile); diff --git a/test/parallel/test-fs-promises-file-handle-readFile.js b/test/parallel/test-fs-promises-file-handle-readFile.js index 316fd6581fa446..9e6fcc2784c49d 100644 --- a/test/parallel/test-fs-promises-file-handle-readFile.js +++ b/test/parallel/test-fs-promises-file-handle-readFile.js @@ -28,5 +28,22 @@ async function validateReadFile() { assert.deepStrictEqual(buffer, readFileData); } +async function validateReadFileProc() { + // Test to make sure reading a file under the /proc directory works. Adapted + // from test-fs-read-file-sync-hostname.js. + // Refs: + // - https://groups.google.com/forum/#!topic/nodejs-dev/rxZ_RoH1Gn0 + // - https://github.com/nodejs/node/issues/21331 + + // Test is Linux-specific. + if (!common.isLinux) + return; + + const fileHandle = await open('/proc/sys/kernel/hostname', 'r'); + const hostname = await fileHandle.readFile(); + assert.ok(hostname.length > 0); +} + validateReadFile() + .then(() => validateReadFileProc()) .then(common.mustCall()); diff --git a/test/parallel/test-fs-promises-readfile.js b/test/parallel/test-fs-promises-readfile.js index 4334673c30f5fb..b1186ad2172507 100644 --- a/test/parallel/test-fs-promises-readfile.js +++ b/test/parallel/test-fs-promises-readfile.js @@ -12,17 +12,35 @@ const fn = path.join(tmpdir.path, 'large-file'); common.crashOnUnhandledRejection(); -// Creating large buffer with random content -const buffer = Buffer.from( - Array.apply(null, { length: 16834 * 2 }) - .map(Math.random) - .map((number) => (number * (1 << 8))) -); - -// Writing buffer to a file then try to read it -writeFile(fn, buffer) - .then(() => readFile(fn)) - .then((readBuffer) => { - assert.strictEqual(readBuffer.equals(buffer), true); - }) +async function validateReadFile() { + // Creating large buffer with random content + const buffer = Buffer.from( + Array.apply(null, { length: 16834 * 2 }) + .map(Math.random) + .map((number) => (number * (1 << 8))) + ); + + // Writing buffer to a file then try to read it + await writeFile(fn, buffer); + const readBuffer = await readFile(fn); + assert.strictEqual(readBuffer.equals(buffer), true); +} + +async function validateReadFileProc() { + // Test to make sure reading a file under the /proc directory works. Adapted + // from test-fs-read-file-sync-hostname.js. + // Refs: + // - https://groups.google.com/forum/#!topic/nodejs-dev/rxZ_RoH1Gn0 + // - https://github.com/nodejs/node/issues/21331 + + // Test is Linux-specific. + if (!common.isLinux) + return; + + const hostname = await readFile('/proc/sys/kernel/hostname'); + assert.ok(hostname.length > 0); +} + +validateReadFile() + .then(() => validateReadFileProc()) .then(common.mustCall()); From 47b10e30c07833d6642ed7216ebbeab036692acd Mon Sep 17 00:00:00 2001 From: Developer Davo Date: Sat, 30 Jun 2018 08:55:23 +0200 Subject: [PATCH 010/116] test: replace third argument with comment in strict equals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/21603 Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig Reviewed-By: Yuta Hiroto Reviewed-By: Tobias Nießen Reviewed-By: Luigi Pinca Reviewed-By: Rich Trott Reviewed-By: Trivikram Kamat --- test/parallel/test-tls-env-bad-extra-ca.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-tls-env-bad-extra-ca.js b/test/parallel/test-tls-env-bad-extra-ca.js index e141de34a9fe1b..5167f586079187 100644 --- a/test/parallel/test-tls-env-bad-extra-ca.js +++ b/test/parallel/test-tls-env-bad-extra-ca.js @@ -29,7 +29,8 @@ let stderr = ''; fork(__filename, opts) .on('exit', common.mustCall(function(status) { - assert.strictEqual(status, 0, 'client did not succeed in connecting'); + // Check that client succeeded in connecting. + assert.strictEqual(status, 0); })) .on('close', common.mustCall(function() { // TODO(addaleax): Make `SafeGetenv` work like `process.env` From f3c397cd212dfa372e3a23012a62aae62b36011e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= Date: Wed, 13 Jun 2018 15:30:21 +0200 Subject: [PATCH 011/116] console: implement timeLog method Refs: https://github.com/whatwg/console/pull/138 PR-URL: https://github.com/nodejs/node/pull/21312 Reviewed-By: Colin Ihrig Reviewed-By: Gus Caplan Reviewed-By: Anatoli Papirovski Reviewed-By: Weijia Wang Reviewed-By: Ruben Bridgewater Reviewed-By: James M Snell --- doc/api/console.md | 19 ++++++++++++++++ lib/console.js | 28 +++++++++++++++++++----- test/parallel/test-console.js | 41 +++++++++++++++++++++++++++-------- 3 files changed, 73 insertions(+), 15 deletions(-) diff --git a/doc/api/console.md b/doc/api/console.md index 6c63a4ffb56656..95801c31fed63c 100644 --- a/doc/api/console.md +++ b/doc/api/console.md @@ -413,6 +413,25 @@ console.timeEnd('100-elements'); // prints 100-elements: 225.438ms ``` +### console.timeLog([label][, ...data]) + +* `label` {string} **Default:** `'default'` +* `...data` {any} + +For a timer that was previously started by calling [`console.time()`][], prints +the elapsed time and other `data` arguments to `stdout`: + +```js +console.time('process'); +const value = expensiveProcess1(); // Returns 42 +console.timeLog('process', value); +// Prints "process: 365.227ms 42". +doExpensiveProcess2(value); +console.timeEnd('process'); +``` + ### console.trace([message][, ...args]) ```C napi_status @@ -424,6 +462,7 @@ TypeError [ERR_ERROR_1] #### napi_throw ```C NAPI_EXTERN napi_status napi_throw(napi_env env, napi_value error); @@ -438,6 +477,7 @@ This API throws the JavaScript value provided. #### napi_throw_error ```C NAPI_EXTERN napi_status napi_throw_error(napi_env env, @@ -456,6 +496,7 @@ This API throws a JavaScript `Error` with the text provided. #### napi_throw_type_error ```C NAPI_EXTERN napi_status napi_throw_type_error(napi_env env, @@ -474,6 +515,7 @@ This API throws a JavaScript `TypeError` with the text provided. #### napi_throw_range_error ```C NAPI_EXTERN napi_status napi_throw_range_error(napi_env env, @@ -492,6 +534,7 @@ This API throws a JavaScript `RangeError` with the text provided. #### napi_is_error ```C NAPI_EXTERN napi_status napi_is_error(napi_env env, @@ -510,6 +553,7 @@ This API queries a `napi_value` to check if it represents an error object. #### napi_create_error ```C NAPI_EXTERN napi_status napi_create_error(napi_env env, @@ -531,6 +575,7 @@ This API returns a JavaScript `Error` with the text provided. #### napi_create_type_error ```C NAPI_EXTERN napi_status napi_create_type_error(napi_env env, @@ -552,6 +597,7 @@ This API returns a JavaScript `TypeError` with the text provided. #### napi_create_range_error ```C NAPI_EXTERN napi_status napi_create_range_error(napi_env env, @@ -573,6 +619,7 @@ This API returns a JavaScript `RangeError` with the text provided. #### napi_get_and_clear_last_exception ```C napi_status napi_get_and_clear_last_exception(napi_env env, @@ -591,6 +638,7 @@ This API can be called even if there is a pending JavaScript exception. #### napi_is_exception_pending ```C napi_status napi_is_exception_pending(napi_env env, bool* result); @@ -608,7 +656,9 @@ This API can be called even if there is a pending JavaScript exception. #### napi_fatal_exception + ```C napi_status napi_fatal_exception(napi_env env, napi_value err); ``` @@ -627,6 +677,7 @@ thrown to immediately terminate the process. #### napi_fatal_error ```C NAPI_NO_RETURN void napi_fatal_error(const char* location, @@ -740,6 +791,7 @@ can only be called once. #### napi_open_handle_scope ```C NAPI_EXTERN napi_status napi_open_handle_scope(napi_env env, @@ -755,6 +807,7 @@ This API open a new scope. #### napi_close_handle_scope ```C NAPI_EXTERN napi_status napi_close_handle_scope(napi_env env, @@ -773,6 +826,7 @@ This API can be called even if there is a pending JavaScript exception. #### napi_open_escapable_handle_scope ```C NAPI_EXTERN napi_status @@ -790,6 +844,7 @@ to the outer scope. #### napi_close_escapable_handle_scope ```C NAPI_EXTERN napi_status @@ -809,6 +864,7 @@ This API can be called even if there is a pending JavaScript exception. #### napi_escape_handle ```C napi_status napi_escape_handle(napi_env env, @@ -874,6 +930,7 @@ individual count. #### napi_create_reference ```C NAPI_EXTERN napi_status napi_create_reference(napi_env env, @@ -896,6 +953,7 @@ to the `Object` passed in. #### napi_delete_reference ```C NAPI_EXTERN napi_status napi_delete_reference(napi_env env, napi_ref ref); @@ -913,6 +971,7 @@ This API can be called even if there is a pending JavaScript exception. #### napi_reference_ref ```C NAPI_EXTERN napi_status napi_reference_ref(napi_env env, @@ -931,6 +990,7 @@ passed in and returns the resulting reference count. #### napi_reference_unref ```C NAPI_EXTERN napi_status napi_reference_unref(napi_env env, @@ -949,6 +1009,7 @@ passed in and returns the resulting reference count. #### napi_get_reference_value ```C NAPI_EXTERN napi_status napi_get_reference_value(napi_env env, @@ -982,7 +1043,9 @@ should be freed up. #### napi_add_env_cleanup_hook + ```C NODE_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env, void (*fun)(void* arg), @@ -1007,7 +1070,9 @@ is being torn down anyway. #### napi_remove_env_cleanup_hook + ```C NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env, void (*fun)(void* arg), @@ -1196,6 +1261,7 @@ Elements of this enum correspond to #### napi_create_array ```C napi_status napi_create_array(napi_env env, napi_value* result) @@ -1213,6 +1279,7 @@ JavaScript arrays are described in #### napi_create_array_with_length ```C napi_status napi_create_array_with_length(napi_env env, @@ -1241,6 +1308,7 @@ JavaScript arrays are described in #### napi_create_arraybuffer ```C napi_status napi_create_arraybuffer(napi_env env, @@ -1272,6 +1340,7 @@ JavaScript `ArrayBuffer` objects are described in #### napi_create_buffer ```C napi_status napi_create_buffer(napi_env env, @@ -1293,6 +1362,7 @@ fully-supported data structure, in most cases using a `TypedArray` will suffice. #### napi_create_buffer_copy ```C napi_status napi_create_buffer_copy(napi_env env, @@ -1318,6 +1388,7 @@ structure, in most cases using a `TypedArray` will suffice. #### napi_create_external ```C napi_status napi_create_external(napi_env env, @@ -1350,6 +1421,7 @@ an external value yields `napi_external`. #### napi_create_external_arraybuffer ```C napi_status @@ -1384,6 +1456,7 @@ JavaScript `ArrayBuffer`s are described in #### napi_create_external_buffer ```C napi_status napi_create_external_buffer(napi_env env, @@ -1415,6 +1488,7 @@ For Node.js >=4 `Buffers` are `Uint8Array`s. #### napi_create_function ```C napi_status napi_create_function(napi_env env, @@ -1448,6 +1522,7 @@ of the ECMAScript Language Specification. #### napi_create_object ```C napi_status napi_create_object(napi_env env, napi_value* result) @@ -1468,6 +1543,7 @@ ECMAScript Language Specification. #### napi_create_symbol ```C napi_status napi_create_symbol(napi_env env, @@ -1491,6 +1567,7 @@ of the ECMAScript Language Specification. #### napi_create_typedarray ```C napi_status napi_create_typedarray(napi_env env, @@ -1526,6 +1603,7 @@ JavaScript `TypedArray` objects are described in #### napi_create_dataview ```C @@ -1560,6 +1638,7 @@ JavaScript `DataView` objects are described in #### napi_create_int32 ```C napi_status napi_create_int32(napi_env env, int32_t value, napi_value* result) @@ -1580,6 +1659,7 @@ The JavaScript `Number` type is described in #### napi_create_uint32 ```C napi_status napi_create_uint32(napi_env env, uint32_t value, napi_value* result) @@ -1600,6 +1680,7 @@ The JavaScript `Number` type is described in #### napi_create_int64 ```C napi_status napi_create_int64(napi_env env, int64_t value, napi_value* result) @@ -1626,6 +1707,7 @@ outside the range of #### napi_create_double ```C napi_status napi_create_double(napi_env env, double value, napi_value* result) @@ -1646,6 +1728,7 @@ The JavaScript `Number` type is described in #### napi_create_string_latin1 ```C napi_status napi_create_string_latin1(napi_env env, @@ -1671,6 +1754,7 @@ The JavaScript `String` type is described in #### napi_create_string_utf16 ```C napi_status napi_create_string_utf16(napi_env env, @@ -1696,6 +1780,7 @@ The JavaScript `String` type is described in #### napi_create_string_utf8 ```C napi_status napi_create_string_utf8(napi_env env, @@ -1722,6 +1807,7 @@ The JavaScript `String` type is described in #### napi_get_array_length ```C napi_status napi_get_array_length(napi_env env, @@ -1745,6 +1831,7 @@ of the ECMAScript Language Specification. #### napi_get_arraybuffer_info ```C napi_status napi_get_arraybuffer_info(napi_env env, @@ -1774,6 +1861,7 @@ trigger a GC. #### napi_get_buffer_info ```C napi_status napi_get_buffer_info(napi_env env, @@ -1798,6 +1886,7 @@ lifetime is not guaranteed if it's managed by the VM. #### napi_get_prototype ```C napi_status napi_get_prototype(napi_env env, @@ -1816,6 +1905,7 @@ Returns `napi_ok` if the API succeeded. #### napi_get_typedarray_info ```C napi_status napi_get_typedarray_info(napi_env env, @@ -1852,6 +1942,7 @@ is managed by the VM. #### napi_get_dataview_info ```C @@ -1879,6 +1970,7 @@ This API returns various properties of a `DataView`. #### napi_get_value_bool ```C napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) @@ -1898,6 +1990,7 @@ This API returns the C boolean primitive equivalent of the given JavaScript #### napi_get_value_double ```C napi_status napi_get_value_double(napi_env env, @@ -1919,6 +2012,7 @@ This API returns the C double primitive equivalent of the given JavaScript #### napi_get_value_external ```C napi_status napi_get_value_external(napi_env env, @@ -1939,6 +2033,7 @@ This API retrieves the external data pointer that was previously passed to #### napi_get_value_int32 ```C napi_status napi_get_value_int32(napi_env env, @@ -1967,6 +2062,7 @@ result to zero. #### napi_get_value_int64 ```C napi_status napi_get_value_int64(napi_env env, @@ -1997,6 +2093,7 @@ result to zero. #### napi_get_value_string_latin1 ```C napi_status napi_get_value_string_latin1(napi_env env, @@ -2024,6 +2121,7 @@ in. #### napi_get_value_string_utf8 ```C napi_status napi_get_value_string_utf8(napi_env env, @@ -2050,6 +2148,7 @@ This API returns the UTF8-encoded string corresponding the value passed in. #### napi_get_value_string_utf16 ```C napi_status napi_get_value_string_utf16(napi_env env, @@ -2076,6 +2175,7 @@ This API returns the UTF16-encoded string corresponding the value passed in. #### napi_get_value_uint32 ```C napi_status napi_get_value_uint32(napi_env env, @@ -2098,6 +2198,7 @@ This API returns the C primitive equivalent of the given `napi_value` as a #### napi_get_boolean ```C napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) @@ -2116,6 +2217,7 @@ represent the given boolean value. #### napi_get_global ```C napi_status napi_get_global(napi_env env, napi_value* result) @@ -2131,6 +2233,7 @@ This API returns the `global` object. #### napi_get_null ```C napi_status napi_get_null(napi_env env, napi_value* result) @@ -2146,6 +2249,7 @@ This API returns the `null` object. #### napi_get_undefined ```C napi_status napi_get_undefined(napi_env env, napi_value* result) @@ -2174,6 +2278,7 @@ These APIs support doing one of the following: ### napi_coerce_to_bool ```C napi_status napi_coerce_to_bool(napi_env env, @@ -2195,6 +2300,7 @@ This API can be re-entrant if getters are defined on the passed-in `Object`. ### napi_coerce_to_number ```C napi_status napi_coerce_to_number(napi_env env, @@ -2216,6 +2322,7 @@ This API can be re-entrant if getters are defined on the passed-in `Object`. ### napi_coerce_to_object ```C napi_status napi_coerce_to_object(napi_env env, @@ -2237,6 +2344,7 @@ This API can be re-entrant if getters are defined on the passed-in `Object`. ### napi_coerce_to_string ```C napi_status napi_coerce_to_string(napi_env env, @@ -2258,6 +2366,7 @@ This API can be re-entrant if getters are defined on the passed-in `Object`. ### napi_typeof ```C napi_status napi_typeof(napi_env env, napi_value value, napi_valuetype* result) @@ -2279,6 +2388,7 @@ If `value` has a type that is invalid, an error is returned. ### napi_instanceof ```C napi_status napi_instanceof(napi_env env, @@ -2304,6 +2414,7 @@ of the ECMAScript Language Specification. ### napi_is_array ```C napi_status napi_is_array(napi_env env, napi_value value, bool* result) @@ -2322,6 +2433,7 @@ of the ECMAScript Language Specification. ### napi_is_arraybuffer ```C napi_status napi_is_arraybuffer(napi_env env, napi_value value, bool* result) @@ -2338,6 +2450,7 @@ This API checks if the `Object` passed in is an array buffer. ### napi_is_buffer ```C napi_status napi_is_buffer(napi_env env, napi_value value, bool* result) @@ -2355,6 +2468,7 @@ This API checks if the `Object` passed in is a buffer. ### napi_is_error ```C napi_status napi_is_error(napi_env env, napi_value value, bool* result) @@ -2371,6 +2485,7 @@ This API checks if the `Object` passed in is an `Error`. ### napi_is_typedarray ```C napi_status napi_is_typedarray(napi_env env, napi_value value, bool* result) @@ -2387,6 +2502,7 @@ This API checks if the `Object` passed in is a typed array. ### napi_is_dataview ```C @@ -2404,6 +2520,7 @@ This API checks if the `Object` passed in is a `DataView`. ### napi_strict_equals ```C napi_status napi_strict_equals(napi_env env, @@ -2636,6 +2753,7 @@ this function is invoked. #### napi_get_property_names ```C napi_status napi_get_property_names(napi_env env, @@ -2659,6 +2777,7 @@ included. #### napi_set_property ```C napi_status napi_set_property(napi_env env, @@ -2679,6 +2798,7 @@ This API set a property on the `Object` passed in. #### napi_get_property ```C napi_status napi_get_property(napi_env env, @@ -2699,6 +2819,7 @@ This API gets the requested property from the `Object` passed in. #### napi_has_property ```C napi_status napi_has_property(napi_env env, @@ -2719,6 +2840,7 @@ This API checks if the `Object` passed in has the named property. #### napi_delete_property ```C napi_status napi_delete_property(napi_env env, @@ -2740,6 +2862,7 @@ This API attempts to delete the `key` own property from `object`. #### napi_has_own_property ```C napi_status napi_has_own_property(napi_env env, @@ -2762,6 +2885,7 @@ any conversion between data types. #### napi_set_named_property ```C napi_status napi_set_named_property(napi_env env, @@ -2783,6 +2907,7 @@ created from the string passed in as `utf8Name`. #### napi_get_named_property ```C napi_status napi_get_named_property(napi_env env, @@ -2804,6 +2929,7 @@ created from the string passed in as `utf8Name`. #### napi_has_named_property ```C napi_status napi_has_named_property(napi_env env, @@ -2825,6 +2951,7 @@ created from the string passed in as `utf8Name`. #### napi_set_element ```C napi_status napi_set_element(napi_env env, @@ -2845,6 +2972,7 @@ This API sets and element on the `Object` passed in. #### napi_get_element ```C napi_status napi_get_element(napi_env env, @@ -2865,6 +2993,7 @@ This API gets the element at the requested index. #### napi_has_element ```C napi_status napi_has_element(napi_env env, @@ -2886,6 +3015,7 @@ requested index. #### napi_delete_element ```C napi_status napi_delete_element(napi_env env, @@ -2907,6 +3037,7 @@ This API attempts to delete the specified `index` from `object`. #### napi_define_properties ```C napi_status napi_define_properties(napi_env env, @@ -2950,6 +3081,7 @@ function. ### napi_call_function ```C napi_status napi_call_function(napi_env env, @@ -3016,6 +3148,7 @@ if (status != napi_ok) return; ### napi_create_function ```C napi_status napi_create_function(napi_env env, @@ -3083,6 +3216,7 @@ responsible for creating the `.node` file. ### napi_get_cb_info ```C napi_status napi_get_cb_info(napi_env env, @@ -3113,6 +3247,7 @@ call like the arguments and the `this` pointer from a given callback info. ### napi_get_new_target ```C napi_status napi_get_new_target(napi_env env, @@ -3132,6 +3267,7 @@ callback is not a constructor call, the result is `NULL`. ### napi_new_instance ```C napi_status napi_new_instance(napi_env env, @@ -3227,6 +3363,7 @@ The reference must be freed once it is no longer needed. ### napi_define_class ```C napi_status napi_define_class(napi_env env, @@ -3283,6 +3420,7 @@ reference count is kept >= 1. ### napi_wrap ```C napi_status napi_wrap(napi_env env, @@ -3343,6 +3481,7 @@ first. ### napi_unwrap ```C napi_status napi_unwrap(napi_env env, @@ -3368,6 +3507,7 @@ then by calling `napi_unwrap()` on the wrapper object. ### napi_remove_wrap ```C napi_status napi_remove_wrap(napi_env env, @@ -3444,6 +3584,7 @@ callback invocation, even when it was cancelled. ### napi_create_async_work ```C napi_status napi_delete_async_work(napi_env env, @@ -3509,6 +3651,7 @@ This API can be called even if there is a pending JavaScript exception. ### napi_queue_async_work ```C napi_status napi_queue_async_work(napi_env env, @@ -3526,6 +3669,7 @@ for execution. ### napi_cancel_async_work ```C napi_status napi_cancel_async_work(napi_env env, @@ -3555,6 +3699,7 @@ the runtime. ### napi_async_init ```C napi_status napi_async_init(napi_env env, @@ -3576,6 +3721,7 @@ Returns `napi_ok` if the API succeeded. ### napi_async_destroy ```C napi_status napi_async_destroy(napi_env env, @@ -3592,6 +3738,7 @@ This API can be called even if there is a pending JavaScript exception. ### napi_make_callback ```C NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, @@ -3663,6 +3811,7 @@ the required scope. ### napi_close_callback_scope ```C NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, @@ -3678,6 +3827,7 @@ This API can be called even if there is a pending JavaScript exception. ### napi_get_node_version ```C @@ -3706,6 +3856,7 @@ The returned buffer is statically allocated and does not need to be freed. ### napi_get_version ```C napi_status napi_get_version(napi_env env, @@ -3736,6 +3887,7 @@ support it: ### napi_adjust_external_memory ```C NAPI_EXTERN napi_status napi_adjust_external_memory(napi_env env, @@ -3815,6 +3967,7 @@ deferred = NULL; ### napi_create_promise ```C napi_status napi_create_promise(napi_env env, @@ -3835,6 +3988,7 @@ This API creates a deferred object and a JavaScript promise. ### napi_resolve_deferred ```C napi_status napi_resolve_deferred(napi_env env, @@ -3858,6 +4012,7 @@ The deferred object is freed upon successful completion. ### napi_reject_deferred ```C napi_status napi_reject_deferred(napi_env env, @@ -3881,6 +4036,7 @@ The deferred object is freed upon successful completion. ### napi_is_promise ```C napi_status napi_is_promise(napi_env env, @@ -3901,6 +4057,7 @@ underlying JavaScript engine. ### napi_run_script ```C NAPI_EXTERN napi_status napi_run_script(napi_env env, @@ -3919,7 +4076,10 @@ a specific `napi_env`. ### napi_get_uv_event_loop ```C NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, diff --git a/src/node_api.h b/src/node_api.h index 84706ac3ed6769..b22592db15f110 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -7,6 +7,16 @@ struct uv_loop_s; // Forward declaration. +#ifndef NAPI_VERSION +#ifdef NAPI_EXPERIMENTAL +// Use INT_MAX, this should only be consumed by the pre-processor anyway. +#define NAPI_VERSION 2147483647 +#else +// The baseline version for N-API +#define NAPI_VERSION 3 +#endif +#endif + #ifdef _WIN32 #ifdef BUILDING_NODE_EXTENSION #ifdef EXTERNAL_NAPI @@ -118,19 +128,10 @@ EXTERN_C_START NAPI_EXTERN void napi_module_register(napi_module* mod); -NAPI_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env, - void (*fun)(void* arg), - void* arg); -NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env, - void (*fun)(void* arg), - void* arg); - NAPI_EXTERN napi_status napi_get_last_error_info(napi_env env, const napi_extended_error_info** result); -NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); - NAPI_EXTERN NAPI_NO_RETURN void napi_fatal_error(const char* location, size_t location_len, const char* message, @@ -443,14 +444,6 @@ NAPI_EXTERN napi_status napi_escape_handle(napi_env env, napi_value escapee, napi_value* result); -NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, - napi_value resource_object, - napi_async_context context, - napi_callback_scope* result); - -NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, - napi_callback_scope scope); - // Methods to support error handling NAPI_EXTERN napi_status napi_throw(napi_env env, napi_value error); NAPI_EXTERN napi_status napi_throw_error(napi_env env, @@ -610,11 +603,38 @@ NAPI_EXTERN napi_status napi_run_script(napi_env env, napi_value script, napi_value* result); +#if NAPI_VERSION >= 2 + // Return the current libuv event loop for a given environment NAPI_EXTERN napi_status napi_get_uv_event_loop(napi_env env, struct uv_loop_s** loop); +#endif // NAPI_VERSION >= 2 + +#if NAPI_VERSION >= 3 + +NAPI_EXTERN napi_status napi_open_callback_scope(napi_env env, + napi_value resource_object, + napi_async_context context, + napi_callback_scope* result); + +NAPI_EXTERN napi_status napi_close_callback_scope(napi_env env, + napi_callback_scope scope); + +NAPI_EXTERN napi_status napi_fatal_exception(napi_env env, napi_value err); + +NAPI_EXTERN napi_status napi_add_env_cleanup_hook(napi_env env, + void (*fun)(void* arg), + void* arg); + +NAPI_EXTERN napi_status napi_remove_env_cleanup_hook(napi_env env, + void (*fun)(void* arg), + void* arg); + +#endif // NAPI_VERSION >= 3 + #ifdef NAPI_EXPERIMENTAL + // Calling into JS from other threads NAPI_EXTERN napi_status napi_create_threadsafe_function(napi_env env, @@ -652,6 +672,7 @@ NAPI_EXTERN napi_status napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); #endif // NAPI_EXPERIMENTAL + EXTERN_C_END #endif // SRC_NODE_API_H_ diff --git a/tools/doc/common.js b/tools/doc/common.js index 813935f23b84f4..4dfadd353d9ec8 100644 --- a/tools/doc/common.js +++ b/tools/doc/common.js @@ -25,6 +25,10 @@ function extractAndParseYAML(text) { meta.added = arrify(meta.added); } + if (meta.napiVersion) { + meta.napiVersion = arrify(meta.napiVersion); + } + if (meta.deprecated) { // Treat deprecated like added for consistency. meta.deprecated = arrify(meta.deprecated); diff --git a/tools/doc/html.js b/tools/doc/html.js index ae2da58b7a0131..871a55baf4676c 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -261,6 +261,10 @@ function parseYAML(text) { html += `${added.description}${deprecated.description}\n`; } + if (meta.napiVersion) { + html += `N-API version: ${meta.napiVersion.join(', ')}\n`; + } + html += ''; return html; } From a5233c7e1737673dc060e4820cf55e2da96468c6 Mon Sep 17 00:00:00 2001 From: Gus Caplan Date: Tue, 3 Jul 2018 17:42:16 -0500 Subject: [PATCH 016/116] deps: cherry-pick 477df06 from upstream v8 Original commit message: [API] Expand BigInt API Provide a more complete BigInt API. Bug: v8:7712 Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng Change-Id: Ic8562d616f3125deabdf8b52c7019b191bef0e07 Reviewed-on: chromium-review.googlesource.com/1101198 Commit-Queue: Yang Guo Reviewed-by: Jakob Kummerow Reviewed-by: Yang Guo Cr-Commit-Position: refs/heads/master@{#54122} PR-URL: https://github.com/nodejs/node/pull/21644 Reviewed-By: Tiancheng "Timothy" Gu Reviewed-By: Anna Henningsen --- common.gypi | 2 +- deps/v8/include/v8.h | 42 ++++++++++++ deps/v8/src/api.cc | 43 ++++++++++++ deps/v8/src/api.h | 1 + deps/v8/src/counters.h | 1 + deps/v8/src/objects/bigint.cc | 64 ++++++++++++++++++ deps/v8/src/objects/bigint.h | 5 ++ deps/v8/test/cctest/test-api.cc | 114 ++++++++++++++++++++++++++++++++ 8 files changed, 271 insertions(+), 1 deletion(-) diff --git a/common.gypi b/common.gypi index d25a434b03d433..65eb7caf575498 100644 --- a/common.gypi +++ b/common.gypi @@ -28,7 +28,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.13', + 'v8_embedder_string': '-node.14', # Enable disassembler for `--print-code` v8 options 'v8_enable_disassembler': 1, diff --git a/deps/v8/include/v8.h b/deps/v8/include/v8.h index ab77238a77f97f..fe8f5a383952f7 100644 --- a/deps/v8/include/v8.h +++ b/deps/v8/include/v8.h @@ -3048,6 +3048,48 @@ class V8_EXPORT Uint32 : public Integer { class V8_EXPORT BigInt : public Primitive { public: static Local New(Isolate* isolate, int64_t value); + static Local NewFromUnsigned(Isolate* isolate, uint64_t value); + /** + * Creates a new BigInt object using a specified sign bit and a + * specified list of digits/words. + * The resulting number is calculated as: + * + * (-1)^sign_bit * (words[0] * (2^64)^0 + words[1] * (2^64)^1 + ...) + */ + static MaybeLocal NewFromWords(Local context, int sign_bit, + int word_count, const uint64_t* words); + + /** + * Returns the value of this BigInt as an unsigned 64-bit integer. + * If `lossless` is provided, it will reflect whether the return value was + * truncated or wrapped around. In particular, it is set to `false` if this + * BigInt is negative. + */ + uint64_t Uint64Value(bool* lossless = nullptr) const; + + /** + * Returns the value of this BigInt as a signed 64-bit integer. + * If `lossless` is provided, it will reflect whether this BigInt was + * truncated or not. + */ + int64_t Int64Value(bool* lossless = nullptr) const; + + /** + * Returns the number of 64-bit words needed to store the result of + * ToWordsArray(). + */ + int WordCount() const; + + /** + * Writes the contents of this BigInt to a specified memory location. + * `sign_bit` must be provided and will be set to 1 if this BigInt is + * negative. + * `*word_count` has to be initialized to the length of the `words` array. + * Upon return, it will be set to the actual number of words that would + * be needed to store this BigInt (i.e. the return value of `WordCount()`). + */ + void ToWordsArray(int* sign_bit, int* word_count, uint64_t* words) const; + V8_INLINE static BigInt* Cast(v8::Value* obj); private: diff --git a/deps/v8/src/api.cc b/deps/v8/src/api.cc index 8a0dfca02b9dcf..a7f6d00f6fabf7 100644 --- a/deps/v8/src/api.cc +++ b/deps/v8/src/api.cc @@ -8121,6 +8121,49 @@ Local v8::BigInt::New(Isolate* isolate, int64_t value) { return Utils::ToLocal(result); } +Local v8::BigInt::NewFromUnsigned(Isolate* isolate, uint64_t value) { + CHECK(i::FLAG_harmony_bigint); + i::Isolate* internal_isolate = reinterpret_cast(isolate); + ENTER_V8_NO_SCRIPT_NO_EXCEPTION(internal_isolate); + i::Handle result = i::BigInt::FromUint64(internal_isolate, value); + return Utils::ToLocal(result); +} + +MaybeLocal v8::BigInt::NewFromWords(Local context, + int sign_bit, int word_count, + const uint64_t* words) { + CHECK(i::FLAG_harmony_bigint); + i::Isolate* isolate = reinterpret_cast(context->GetIsolate()); + ENTER_V8_NO_SCRIPT(isolate, context, BigInt, NewFromWords, + MaybeLocal(), InternalEscapableScope); + i::MaybeHandle result = + i::BigInt::FromWords64(isolate, sign_bit, word_count, words); + has_pending_exception = result.is_null(); + RETURN_ON_FAILED_EXECUTION(BigInt); + RETURN_ESCAPED(Utils::ToLocal(result.ToHandleChecked())); +} + +uint64_t v8::BigInt::Uint64Value(bool* lossless) const { + i::Handle handle = Utils::OpenHandle(this); + return handle->AsUint64(lossless); +} + +int64_t v8::BigInt::Int64Value(bool* lossless) const { + i::Handle handle = Utils::OpenHandle(this); + return handle->AsInt64(lossless); +} + +int BigInt::WordCount() const { + i::Handle handle = Utils::OpenHandle(this); + return handle->Words64Count(); +} + +void BigInt::ToWordsArray(int* sign_bit, int* word_count, + uint64_t* words) const { + i::Handle handle = Utils::OpenHandle(this); + return handle->ToWordsArray64(sign_bit, word_count, words); +} + void Isolate::ReportExternalAllocationLimitReached() { i::Heap* heap = reinterpret_cast(this)->heap(); if (heap->gc_state() != i::Heap::NOT_IN_GC) return; diff --git a/deps/v8/src/api.h b/deps/v8/src/api.h index d2e25ae4f72332..c67de0482df66c 100644 --- a/deps/v8/src/api.h +++ b/deps/v8/src/api.h @@ -114,6 +114,7 @@ class RegisteredExtension { V(Promise, JSPromise) \ V(Primitive, Object) \ V(PrimitiveArray, FixedArray) \ + V(BigInt, BigInt) \ V(ScriptOrModule, Script) class Utils { diff --git a/deps/v8/src/counters.h b/deps/v8/src/counters.h index 3c674b0ae01539..3318055185521e 100644 --- a/deps/v8/src/counters.h +++ b/deps/v8/src/counters.h @@ -692,6 +692,7 @@ class RuntimeCallTimer final { V(ArrayBuffer_New) \ V(Array_CloneElementAt) \ V(Array_New) \ + V(BigInt_NewFromWords) \ V(BigInt64Array_New) \ V(BigUint64Array_New) \ V(BigIntObject_New) \ diff --git a/deps/v8/src/objects/bigint.cc b/deps/v8/src/objects/bigint.cc index 0ccc5930b728a4..66962033ffe8b0 100644 --- a/deps/v8/src/objects/bigint.cc +++ b/deps/v8/src/objects/bigint.cc @@ -2205,6 +2205,70 @@ Handle BigInt::FromUint64(Isolate* isolate, uint64_t n) { return MutableBigInt::MakeImmutable(result); } +MaybeHandle BigInt::FromWords64(Isolate* isolate, int sign_bit, + int words64_count, + const uint64_t* words) { + if (words64_count < 0 || words64_count > kMaxLength / (64 / kDigitBits)) { + THROW_NEW_ERROR(isolate, NewRangeError(MessageTemplate::kBigIntTooBig), + BigInt); + } + if (words64_count == 0) return MutableBigInt::Zero(isolate); + STATIC_ASSERT(kDigitBits == 64 || kDigitBits == 32); + int length = (64 / kDigitBits) * words64_count; + DCHECK_GT(length, 0); + if (kDigitBits == 32 && words[words64_count - 1] <= (1ULL << 32)) length--; + + Handle result; + if (!MutableBigInt::New(isolate, length).ToHandle(&result)) { + return MaybeHandle(); + } + + result->set_sign(sign_bit); + if (kDigitBits == 64) { + for (int i = 0; i < length; ++i) { + result->set_digit(i, static_cast(words[i])); + } + } else { + for (int i = 0; i < length; i += 2) { + digit_t lo = static_cast(words[i / 2]); + digit_t hi = static_cast(words[i / 2] >> 32); + result->set_digit(i, lo); + if (i + 1 < length) result->set_digit(i + 1, hi); + } + } + + return MutableBigInt::MakeImmutable(result); +} + +int BigInt::Words64Count() { + STATIC_ASSERT(kDigitBits == 64 || kDigitBits == 32); + return length() / (64 / kDigitBits) + + (kDigitBits == 32 && length() % 2 == 1 ? 1 : 0); +} + +void BigInt::ToWordsArray64(int* sign_bit, int* words64_count, + uint64_t* words) { + DCHECK_NE(sign_bit, nullptr); + DCHECK_NE(words64_count, nullptr); + *sign_bit = sign(); + int available_words = *words64_count; + *words64_count = Words64Count(); + if (available_words == 0) return; + DCHECK_NE(words, nullptr); + + int len = length(); + if (kDigitBits == 64) { + for (int i = 0; i < len && i < available_words; ++i) words[i] = digit(i); + } else { + for (int i = 0; i < len && available_words > 0; i += 2) { + uint64_t lo = digit(i); + uint64_t hi = (i + 1) < len ? digit(i + 1) : 0; + words[i / 2] = lo | (hi << 32); + available_words--; + } + } +} + uint64_t MutableBigInt::GetRawBits(BigIntBase* x, bool* lossless) { if (lossless != nullptr) *lossless = true; if (x->is_zero()) return 0; diff --git a/deps/v8/src/objects/bigint.h b/deps/v8/src/objects/bigint.h index 3899853955d91d..65ce808394452d 100644 --- a/deps/v8/src/objects/bigint.h +++ b/deps/v8/src/objects/bigint.h @@ -144,8 +144,13 @@ class V8_EXPORT_PRIVATE BigInt : public BigIntBase { static Handle FromInt64(Isolate* isolate, int64_t n); static Handle FromUint64(Isolate* isolate, uint64_t n); + static MaybeHandle FromWords64(Isolate* isolate, int sign_bit, + int words64_count, + const uint64_t* words); int64_t AsInt64(bool* lossless = nullptr); uint64_t AsUint64(bool* lossless = nullptr); + int Words64Count(); + void ToWordsArray64(int* sign_bit, int* words64_count, uint64_t* words); DECL_CAST(BigInt) DECL_VERIFIER(BigInt) diff --git a/deps/v8/test/cctest/test-api.cc b/deps/v8/test/cctest/test-api.cc index d698c1a9e008a4..637dc7d2062f9a 100644 --- a/deps/v8/test/cctest/test-api.cc +++ b/deps/v8/test/cctest/test-api.cc @@ -27613,3 +27613,117 @@ TEST(WasmStreamingAbortNoReject) { streaming.Abort({}); CHECK_EQ(streaming.GetPromise()->State(), v8::Promise::kPending); } + +TEST(BigIntAPI) { + LocalContext env; + v8::Isolate* isolate = env->GetIsolate(); + v8::HandleScope scope(isolate); + bool lossless; + uint64_t words1[10]; + uint64_t words2[10]; + + { + Local bi = CompileRun("12n"); + CHECK(bi->IsBigInt()); + + CHECK_EQ(bi.As()->Uint64Value(), 12); + CHECK_EQ(bi.As()->Uint64Value(&lossless), 12); + CHECK_EQ(lossless, true); + CHECK_EQ(bi.As()->Int64Value(), 12); + CHECK_EQ(bi.As()->Int64Value(&lossless), 12); + CHECK_EQ(lossless, true); + } + + { + Local bi = CompileRun("-12n"); + CHECK(bi->IsBigInt()); + + CHECK_EQ(bi.As()->Uint64Value(), static_cast(-12)); + CHECK_EQ(bi.As()->Uint64Value(&lossless), + static_cast(-12)); + CHECK_EQ(lossless, false); + CHECK_EQ(bi.As()->Int64Value(), -12); + CHECK_EQ(bi.As()->Int64Value(&lossless), -12); + CHECK_EQ(lossless, true); + } + + { + Local bi = CompileRun("123456789012345678901234567890n"); + CHECK(bi->IsBigInt()); + + CHECK_EQ(bi.As()->Uint64Value(), 14083847773837265618ULL); + CHECK_EQ(bi.As()->Uint64Value(&lossless), + 14083847773837265618ULL); + CHECK_EQ(lossless, false); + CHECK_EQ(bi.As()->Int64Value(), -4362896299872285998LL); + CHECK_EQ(bi.As()->Int64Value(&lossless), + -4362896299872285998LL); + CHECK_EQ(lossless, false); + } + + { + Local bi = CompileRun("-123456789012345678901234567890n"); + CHECK(bi->IsBigInt()); + + CHECK_EQ(bi.As()->Uint64Value(), 4362896299872285998LL); + CHECK_EQ(bi.As()->Uint64Value(&lossless), + 4362896299872285998LL); + CHECK_EQ(lossless, false); + CHECK_EQ(bi.As()->Int64Value(), 4362896299872285998LL); + CHECK_EQ(bi.As()->Int64Value(&lossless), 4362896299872285998LL); + CHECK_EQ(lossless, false); + } + + { + Local bi = + v8::BigInt::NewFromWords(env.local(), 0, 0, words1).ToLocalChecked(); + CHECK_EQ(bi->Uint64Value(), 0); + CHECK_EQ(bi->WordCount(), 0); + } + + { + TryCatch try_catch(isolate); + v8::MaybeLocal bi = v8::BigInt::NewFromWords( + env.local(), 0, std::numeric_limits::max(), words1); + CHECK(bi.IsEmpty()); + CHECK(try_catch.HasCaught()); + } + + { + TryCatch try_catch(isolate); + v8::MaybeLocal bi = + v8::BigInt::NewFromWords(env.local(), 0, -1, words1); + CHECK(bi.IsEmpty()); + CHECK(try_catch.HasCaught()); + } + + { + TryCatch try_catch(isolate); + v8::MaybeLocal bi = + v8::BigInt::NewFromWords(env.local(), 0, 1 << 30, words1); + CHECK(bi.IsEmpty()); + CHECK(try_catch.HasCaught()); + } + + for (int sign_bit = 0; sign_bit <= 1; sign_bit++) { + words1[0] = 0xffffffff00000000ULL; + words1[1] = 0x00000000ffffffffULL; + v8::Local bi = + v8::BigInt::NewFromWords(env.local(), sign_bit, 2, words1) + .ToLocalChecked(); + CHECK_EQ(bi->Uint64Value(&lossless), + sign_bit ? static_cast(-static_cast(words1[0])) + : words1[0]); + CHECK_EQ(lossless, false); + CHECK_EQ(bi->Int64Value(&lossless), sign_bit + ? -static_cast(words1[0]) + : static_cast(words1[0])); + CHECK_EQ(lossless, false); + CHECK_EQ(bi->WordCount(), 2); + int real_sign_bit; + int word_count = arraysize(words2); + bi->ToWordsArray(&real_sign_bit, &word_count, words2); + CHECK_EQ(real_sign_bit, sign_bit); + CHECK_EQ(word_count, 2); + } +} From f7aa22a0ebc089fdafbe2c56d4f8b9b495c02611 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Tue, 3 Jul 2018 15:55:33 -0700 Subject: [PATCH 017/116] doc: improve guide text for CI runs Make the text about CI runs in the COLLABORATOR_GUIDE more concise. Add information about Resume Build which should generally be preferred over Rebuild. PR-URL: https://github.com/nodejs/node/pull/21645 Reviewed-By: Gus Caplan Reviewed-By: Trivikram Kamat Reviewed-By: Luigi Pinca Reviewed-By: Vse Mozhet Byt --- COLLABORATOR_GUIDE.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/COLLABORATOR_GUIDE.md b/COLLABORATOR_GUIDE.md index 238a72547247b6..8d50589afdd11d 100644 --- a/COLLABORATOR_GUIDE.md +++ b/COLLABORATOR_GUIDE.md @@ -199,14 +199,10 @@ status indicator. Do not land any Pull Requests without passing (green or yellow) CI runs. If you believe any failed (red or grey) CI sub-tasks are unrelated to the change in the -Pull Request, you may re-run the sub-task to try to see if it passes (just open -the failed sub-task page and press the "Rebuild" button; be sure you are still -logged in for this action). If re-runs of all failed sub-tasks pass (do not -forget to provide the links for successfully rerun sub-tasks), it is permissible -to land the Pull Request but only if the initial failures are believed in good -faith to be unrelated to the changes in the Pull Request. Otherwise, reasonable -steps must be taken to confirm that the changes are not resulting in an -unreliable test. +Pull Request, use "Resume Build" in the left navigation of the relevant +`node-test-pull-request` job. It will create a new `node-test-pull-request` run +that preserves all the green results from the current job but re-runs everything +else. #### Useful CI Jobs From 9776f1cbef855cbc6bb70c57e7747f931f64062d Mon Sep 17 00:00:00 2001 From: Kenny Yuan Date: Wed, 27 Jun 2018 12:42:18 +0800 Subject: [PATCH 018/116] benchmark: add n-api function args benchmark This benchmark suite is added to measure the performance of n-api function call with various type/number of arguments. The cases in this suite are carefully selected to efficiently show the performance trend. PR-URL: https://github.com/nodejs/node/pull/21555 Reviewed-By: Gabriel Schulhof Reviewed-By: Kyle Farnung --- Makefile | 9 + benchmark/napi/function_args/.gitignore | 1 + benchmark/napi/function_args/binding.cc | 142 ++++++++++++ benchmark/napi/function_args/binding.gyp | 12 + benchmark/napi/function_args/index.js | 99 +++++++++ benchmark/napi/function_args/napi_binding.c | 229 ++++++++++++++++++++ 6 files changed, 492 insertions(+) create mode 100644 benchmark/napi/function_args/.gitignore create mode 100644 benchmark/napi/function_args/binding.cc create mode 100644 benchmark/napi/function_args/binding.gyp create mode 100644 benchmark/napi/function_args/index.js create mode 100644 benchmark/napi/function_args/napi_binding.c diff --git a/Makefile b/Makefile index c75194dbaa1566..6bba4c5052574e 100644 --- a/Makefile +++ b/Makefile @@ -305,6 +305,15 @@ benchmark/napi/function_call/build/Release/binding.node: all \ --directory="$(shell pwd)/benchmark/napi/function_call" \ --nodedir="$(shell pwd)" +benchmark/napi/function_args/build/Release/binding.node: all \ + benchmark/napi/function_args/napi_binding.c \ + benchmark/napi/function_args/binding.cc \ + benchmark/napi/function_args/binding.gyp + $(NODE) deps/npm/node_modules/node-gyp/bin/node-gyp rebuild \ + --python="$(PYTHON)" \ + --directory="$(shell pwd)/benchmark/napi/function_args" \ + --nodedir="$(shell pwd)" + # Implicitly depends on $(NODE_EXE). We don't depend on it explicitly because # it always triggers a rebuild due to it being a .PHONY rule. See the comment # near the build-addons rule for more background. diff --git a/benchmark/napi/function_args/.gitignore b/benchmark/napi/function_args/.gitignore new file mode 100644 index 00000000000000..567609b1234a9b --- /dev/null +++ b/benchmark/napi/function_args/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/benchmark/napi/function_args/binding.cc b/benchmark/napi/function_args/binding.cc new file mode 100644 index 00000000000000..11eb394a6a98f5 --- /dev/null +++ b/benchmark/napi/function_args/binding.cc @@ -0,0 +1,142 @@ +#include +#include +#include + +using v8::Isolate; +using v8::Context; +using v8::Local; +using v8::MaybeLocal; +using v8::Value; +using v8::Number; +using v8::String; +using v8::Object; +using v8::Array; +using v8::ArrayBufferView; +using v8::ArrayBuffer; +using v8::FunctionCallbackInfo; + +void CallWithString(const FunctionCallbackInfo& args) { + assert(args.Length() == 1 && args[0]->IsString()); + if (args.Length() == 1 && args[0]->IsString()) { + Local str = args[0].As(); + const int32_t length = str->Utf8Length() + 1; + char* buf = new char[length]; + str->WriteUtf8(buf, length); + delete [] buf; + } +} + +void CallWithArray(const FunctionCallbackInfo& args) { + assert(args.Length() == 1 && args[0]->IsArray()); + if (args.Length() == 1 && args[0]->IsArray()) { + const Local array = args[0].As(); + uint32_t length = array->Length(); + for (uint32_t i = 0; i < length; ++ i) { + Local v; + v = array->Get(i); + } + } +} + +void CallWithNumber(const FunctionCallbackInfo& args) { + assert(args.Length() == 1 && args[0]->IsNumber()); + if (args.Length() == 1 && args[0]->IsNumber()) { + args[0].As()->Value(); + } +} + +void CallWithObject(const FunctionCallbackInfo& args) { + Isolate* isolate = args.GetIsolate(); + Local context = isolate->GetCurrentContext(); + + assert(args.Length() == 1 && args[0]->IsObject()); + if (args.Length() == 1 && args[0]->IsObject()) { + Local obj = args[0].As(); + + MaybeLocal map_key = String::NewFromUtf8(isolate, + "map", v8::NewStringType::kNormal); + assert(!map_key.IsEmpty()); + MaybeLocal map_maybe = obj->Get(context, + map_key.ToLocalChecked()); + assert(!map_maybe.IsEmpty()); + Local map; + map = map_maybe.ToLocalChecked(); + + MaybeLocal operand_key = String::NewFromUtf8(isolate, + "operand", v8::NewStringType::kNormal); + assert(!operand_key.IsEmpty()); + MaybeLocal operand_maybe = obj->Get(context, + operand_key.ToLocalChecked()); + assert(!operand_maybe.IsEmpty()); + Local operand; + operand = operand_maybe.ToLocalChecked(); + + MaybeLocal data_key = String::NewFromUtf8(isolate, + "data", v8::NewStringType::kNormal); + assert(!data_key.IsEmpty()); + MaybeLocal data_maybe = obj->Get(context, + data_key.ToLocalChecked()); + assert(!data_maybe.IsEmpty()); + Local data; + data = data_maybe.ToLocalChecked(); + + MaybeLocal reduce_key = String::NewFromUtf8(isolate, + "reduce", v8::NewStringType::kNormal); + assert(!reduce_key.IsEmpty()); + MaybeLocal reduce_maybe = obj->Get(context, + reduce_key.ToLocalChecked()); + assert(!reduce_maybe.IsEmpty()); + Local reduce; + reduce = reduce_maybe.ToLocalChecked(); + } +} + +void CallWithTypedarray(const FunctionCallbackInfo& args) { + assert(args.Length() == 1 && args[0]->IsArrayBufferView()); + if (args.Length() == 1 && args[0]->IsArrayBufferView()) { + assert(args[0]->IsArrayBufferView()); + Local view = args[0].As(); + const size_t byte_offset = view->ByteOffset(); + const size_t byte_length = view->ByteLength(); + assert(byte_length > 0); + assert(view->HasBuffer()); + Local buffer; + buffer = view->Buffer(); + ArrayBuffer::Contents contents; + contents = buffer->GetContents(); + const uint32_t* data = reinterpret_cast( + static_cast(contents.Data()) + byte_offset); + assert(data); + } +} + +void CallWithArguments(const FunctionCallbackInfo& args) { + assert(args.Length() > 1 && args[0]->IsNumber()); + if (args.Length() > 1 && args[0]->IsNumber()) { + int32_t loop = args[0].As()->Value(); + for (int32_t i = 1; i < loop; ++i) { + assert(i < args.Length()); + assert(args[i]->IsUint32()); + args[i].As()->Value(); + } + } +} + +void Initialize(Local target) { + NODE_SET_METHOD(target, "callWithString", CallWithString); + NODE_SET_METHOD(target, "callWithLongString", CallWithString); + + NODE_SET_METHOD(target, "callWithArray", CallWithArray); + NODE_SET_METHOD(target, "callWithLargeArray", CallWithArray); + NODE_SET_METHOD(target, "callWithHugeArray", CallWithArray); + + NODE_SET_METHOD(target, "callWithNumber", CallWithNumber); + NODE_SET_METHOD(target, "callWithObject", CallWithObject); + NODE_SET_METHOD(target, "callWithTypedarray", CallWithTypedarray); + + NODE_SET_METHOD(target, "callWith10Numbers", CallWithArguments); + NODE_SET_METHOD(target, "callWith100Numbers", CallWithArguments); + NODE_SET_METHOD(target, "callWith1000Numbers", CallWithArguments); +} + +NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize) diff --git a/benchmark/napi/function_args/binding.gyp b/benchmark/napi/function_args/binding.gyp new file mode 100644 index 00000000000000..ac122ed1a07962 --- /dev/null +++ b/benchmark/napi/function_args/binding.gyp @@ -0,0 +1,12 @@ +{ + 'targets': [ + { + 'target_name': 'napi_binding', + 'sources': [ 'napi_binding.c' ] + }, + { + 'target_name': 'binding', + 'sources': [ 'binding.cc' ] + } + ] +} diff --git a/benchmark/napi/function_args/index.js b/benchmark/napi/function_args/index.js new file mode 100644 index 00000000000000..c8f281a3429fde --- /dev/null +++ b/benchmark/napi/function_args/index.js @@ -0,0 +1,99 @@ +// show the difference between calling a V8 binding C++ function +// relative to a comparable N-API C++ function, +// in various types/numbers of arguments. +// Reports n of calls per second. +'use strict'; + +const common = require('../../common.js'); + +let v8; +let napi; + +try { + v8 = require('./build/Release/binding'); +} catch (err) { + // eslint-disable-next-line no-path-concat + console.error(__filename + ': V8 Binding failed to load'); + process.exit(0); +} + +try { + napi = require('./build/Release/napi_binding'); +} catch (err) { + // eslint-disable-next-line no-path-concat + console.error(__filename + ': NAPI-Binding failed to load'); + process.exit(0); +} + +const argsTypes = ['String', 'Number', 'Object', 'Array', 'Typedarray', + '10Numbers', '100Numbers', '1000Numbers']; + +const generateArgs = (argType) => { + let args = []; + + if (argType === 'String') { + args.push('The quick brown fox jumps over the lazy dog'); + } else if (argType === 'LongString') { + args.push(Buffer.alloc(32768, '42').toString()); + } else if (argType === 'Number') { + args.push(Math.floor(314158964 * Math.random())); + } else if (argType === 'Object') { + args.push({ + map: 'add', + operand: 10, + data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + reduce: 'add', + }); + } else if (argType === 'Array') { + const arr = []; + for (let i = 0; i < 50; ++i) { + arr.push(Math.random() * 10e9); + } + args.push(arr); + } else if (argType === 'Typedarray') { + const arr = new Uint32Array(1000); + for (let i = 0; i < 1000; ++i) { + arr[i] = Math.random() * 4294967296; + } + args.push(arr); + } else if (argType === '10Numbers') { + args.push(10); + for (let i = 0; i < 9; ++i) { + args = [...args, ...generateArgs('Number')]; + } + } else if (argType === '100Numbers') { + args.push(100); + for (let i = 0; i < 99; ++i) { + args = [...args, ...generateArgs('Number')]; + } + } else if (argType === '1000Numbers') { + args.push(1000); + for (let i = 0; i < 999; ++i) { + args = [...args, ...generateArgs('Number')]; + } + } + + return args; +}; + +const bench = common.createBenchmark(main, { + type: argsTypes, + engine: ['v8', 'napi'], + n: [1, 1e1, 1e2, 1e3, 1e4, 1e5], +}); + +function main({ n, engine, type }) { + const bindings = engine === 'v8' ? v8 : napi; + const methodName = 'callWith' + type; + const fn = bindings[methodName]; + + if (fn) { + const args = generateArgs(type); + + bench.start(); + for (var i = 0; i < n; i++) { + fn.apply(null, args); + } + bench.end(n); + } +} diff --git a/benchmark/napi/function_args/napi_binding.c b/benchmark/napi/function_args/napi_binding.c new file mode 100644 index 00000000000000..b697644ca441e9 --- /dev/null +++ b/benchmark/napi/function_args/napi_binding.c @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include + +static napi_value CallWithString(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_valuetype types[1]; + status = napi_typeof(env, args[0], types); + assert(status == napi_ok); + + assert(types[0] == napi_string); + if (types[0] == napi_string) { + size_t len = 0; + // Get the length + status = napi_get_value_string_utf8(env, args[0], NULL, 0, &len); + assert(status == napi_ok); + char* buf = (char*)malloc(len + 1); + status = napi_get_value_string_utf8(env, args[0], buf, len + 1, &len); + assert(status == napi_ok); + free(buf); + } + + return NULL; +} + +static napi_value CallWithArray(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_value array = args[0]; + bool is_array = false; + status = napi_is_array(env, array, &is_array); + assert(status == napi_ok); + + assert(is_array); + if (is_array) { + uint32_t length; + status = napi_get_array_length(env, array, &length); + assert(status == napi_ok); + + for (uint32_t i = 0; i < length; ++i) { + napi_value v; + status = napi_get_element(env, array, i, &v); + assert(status == napi_ok); + } + } + + return NULL; +} + +static napi_value CallWithNumber(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_valuetype types[1]; + status = napi_typeof(env, args[0], types); + assert(status == napi_ok); + + assert(types[0] == napi_number); + if (types[0] == napi_number) { + double value = 0.0; + status = napi_get_value_double(env, args[0], &value); + assert(status == napi_ok); + } + + return NULL; +} + +static napi_value CallWithObject(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + napi_valuetype types[1]; + status = napi_typeof(env, args[0], types); + assert(status == napi_ok); + + assert(argc == 1 && types[0] == napi_object); + if (argc == 1 && types[0] == napi_object) { + napi_value value; + + status = napi_get_named_property(env, args[0], "map", &value); + assert(status == napi_ok); + + status = napi_get_named_property(env, args[0], "operand", &value); + assert(status == napi_ok); + + status = napi_get_named_property(env, args[0], "data", &value); + assert(status == napi_ok); + + status = napi_get_named_property(env, args[0], "reduce", &value); + assert(status == napi_ok); + } + + return NULL; +} + +static napi_value CallWithTypedarray(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1]; + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + + bool is_typedarray = false; + status = napi_is_typedarray(env, args[0], &is_typedarray); + assert(status == napi_ok); + + assert(is_typedarray); + if (is_typedarray) { + napi_typedarray_type type; + napi_value input_buffer; + size_t byte_offset = 0; + size_t length = 0; + status = napi_get_typedarray_info(env, args[0], &type, &length, + NULL, &input_buffer, &byte_offset); + assert(status == napi_ok); + assert(length > 0); + + void* data = NULL; + size_t byte_length = 0; + status = napi_get_arraybuffer_info(env, + input_buffer, &data, &byte_length); + assert(status == napi_ok); + + uint32_t* input_integers = (uint32_t*)((uint8_t*)(data) + byte_offset); + assert(input_integers); + } + + return NULL; +} + +static napi_value CallWithArguments(napi_env env, napi_callback_info info) { + napi_status status; + + size_t argc = 1; + napi_value args[1000]; + // Get the length + status = napi_get_cb_info(env, info, &argc, NULL, NULL, NULL); + assert(status == napi_ok); + + status = napi_get_cb_info(env, info, &argc, args, NULL, NULL); + assert(status == napi_ok); + assert(argc <= 1000); + + napi_valuetype types[1]; + status = napi_typeof(env, args[0], types); + assert(status == napi_ok); + + assert(argc > 1 && types[0] == napi_number); + if (argc > 1 && types[0] == napi_number) { + uint32_t loop = 0; + status = napi_get_value_uint32(env, args[0], &loop); + assert(status == napi_ok); + + for (uint32_t i = 1; i < loop; ++i) { + assert(i < argc); + status = napi_typeof(env, args[i], types); + assert(status == napi_ok); + assert(types[0] == napi_number); + + uint32_t value = 0; + status = napi_get_value_uint32(env, args[i], &value); + assert(status == napi_ok); + } + } + + return NULL; +} + + +#define EXPORT_FUNC(env, exports, name, func) \ + do { \ + napi_status status; \ + napi_value js_func; \ + status = napi_create_function((env), \ + (name), \ + NAPI_AUTO_LENGTH, \ + (func), \ + NULL, \ + &js_func); \ + assert(status == napi_ok); \ + status = napi_set_named_property((env), \ + (exports), \ + (name), \ + js_func); \ + assert(status == napi_ok); \ + } while (0); + + +NAPI_MODULE_INIT() { + EXPORT_FUNC(env, exports, "callWithString", CallWithString); + EXPORT_FUNC(env, exports, "callWithLongString", CallWithString); + + EXPORT_FUNC(env, exports, "callWithArray", CallWithArray); + EXPORT_FUNC(env, exports, "callWithLargeArray", CallWithArray); + EXPORT_FUNC(env, exports, "callWithHugeArray", CallWithArray); + + EXPORT_FUNC(env, exports, "callWithNumber", CallWithNumber); + + EXPORT_FUNC(env, exports, "callWithObject", CallWithObject); + EXPORT_FUNC(env, exports, "callWithTypedarray", CallWithTypedarray); + + EXPORT_FUNC(env, exports, "callWith10Numbers", CallWithArguments); + EXPORT_FUNC(env, exports, "callWith100Numbers", CallWithArguments); + EXPORT_FUNC(env, exports, "callWith1000Numbers", CallWithArguments); + + return exports; +} From c8d5bab022193625cf801b62b2e68fa066acc7d5 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Mon, 2 Jul 2018 18:20:06 -0400 Subject: [PATCH 019/116] doc: fix doc for napi_create_function Merge the two duplicate entries for `napi_create_function()`. Fixes: https://github.com/nodejs/node/issues/21606 PR-URL: https://github.com/nodejs/node/pull/21627/ Reviewed-By: Colin Ihrig Reviewed-By: Michael Dawson Reviewed-By: Vse Mozhet Byt Reviewed-By: Trivikram Kamat --- doc/api/n-api.md | 44 ++++++++------------------------------------ 1 file changed, 8 insertions(+), 36 deletions(-) diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 982b15f496893b..cdb3fcaf8276bf 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -1485,40 +1485,6 @@ structure, in most cases using a `TypedArray` will suffice. For Node.js >=4 `Buffers` are `Uint8Array`s. -#### napi_create_function - -```C -napi_status napi_create_function(napi_env env, - const char* utf8name, - size_t length, - napi_callback cb, - void* data, - napi_value* result) -``` - -- `[in] env`: The environment that the API is invoked under. -- `[in] utf8name`: A string representing the name of the function encoded as -UTF8. -- `[in] length`: The length of the `utf8name` in bytes, or -`NAPI_AUTO_LENGTH` if it is null-terminated. -- `[in] cb`: A function pointer to the native function to be invoked when the -created function is invoked from JavaScript. -- `[in] data`: Optional arbitrary context data to be passed into the native -function when it is invoked. -- `[out] result`: A `napi_value` representing a JavaScript function. - -Returns `napi_ok` if the API succeeded. - -This API returns an N-API value corresponding to a JavaScript `Function` object. -It's used to wrap native functions so that they can be invoked from JavaScript. - -JavaScript `Function`s are described in -[Section 19.2](https://tc39.github.io/ecma262/#sec-function-objects) -of the ECMAScript Language Specification. - #### napi_create_object @@ -2421,20 +2421,20 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL. + https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html + for detail. + https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html. + https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html. @@ -2568,6 +2568,7 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL.
Constant
SSL_OP_ALL Applies multiple bug workarounds within OpenSSL. See - https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html for - detail.
SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION Allows legacy insecure renegotiation between OpenSSL and unpatched clients or servers. See - https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html.
SSL_OP_CIPHER_SERVER_PREFERENCE Attempts to use the server's preferences instead of the client's when selecting a cipher. Behavior depends on protocol version. See - https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html.
SSL_OP_CISCO_ANYCONNECT
### OpenSSL Engine Constants + From 24f649c8cf69a86f6e982d55b016497ef56d6273 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Wed, 4 Jul 2018 09:55:55 -0700 Subject: [PATCH 023/116] test: fix pummel/test-net-connect-memleak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A loop that generates a long array is resulting in a RangeError. Moving to Array.prototype.fill() along with the ** operator instead of using a loop fixes the issue. PR-URL: https://github.com/nodejs/node/pull/21658 Reviewed-By: Anna Henningsen Reviewed-By: Luigi Pinca Reviewed-By: Сковорода Никита Андреевич Reviewed-By: Michael Dawson --- test/pummel/test-net-connect-memleak.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/pummel/test-net-connect-memleak.js b/test/pummel/test-net-connect-memleak.js index b6a31daed230d5..7546a6caeb62ea 100644 --- a/test/pummel/test-net-connect-memleak.js +++ b/test/pummel/test-net-connect-memleak.js @@ -37,8 +37,7 @@ let before = 0; { // 2**26 == 64M entries global.gc(); - let junk = [0]; - for (let i = 0; i < 26; ++i) junk = junk.concat(junk); + const junk = new Array(2 ** 26).fill(0); before = process.memoryUsage().rss; net.createConnection(common.PORT, '127.0.0.1', function() { From 971679328ec89554b314e88308c6e7523653c4da Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Sat, 7 Jul 2018 13:13:49 -0700 Subject: [PATCH 024/116] doc: add codebytere as collaborator PR-URL: https://github.com/nodejs/node/pull/21700 Reviewed-By: Rich Trott Reviewed-By: Anatoli Papirovski Reviewed-By: Colin Ihrig Reviewed-By: Bryan English --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cd30238a94f293..e81f6629ed342a 100644 --- a/README.md +++ b/README.md @@ -345,6 +345,8 @@ For more information about the governance of the Node.js project, see **Colin Ihrig** <cjihrig@gmail.com> * [claudiorodriguez](https://github.com/claudiorodriguez) - **Claudio Rodriguez** <cjrodr@yahoo.com> +* [codebytere](https://github.com/codebytere) - +**Shelley Vohr** <codebytere@gmail.com> (she/her) * [danbev](https://github.com/danbev) - **Daniel Bevenius** <daniel.bevenius@gmail.com> * [DavidCai1993](https://github.com/DavidCai1993) - From 1044bafec4e08a2725eb98048a8b8e0af9588dd5 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Sat, 7 Jul 2018 13:53:24 -0700 Subject: [PATCH 025/116] doc: remove _Node.js style callback_ We refer to them only as _error-first callbacks_ in our docs. We don't call them _Node.js style callbacks_ so let's take this opporutnity to keep things a bit more concise PR-URL: https://github.com/nodejs/node/pull/21701 Reviewed-By: Anatoli Papirovski Reviewed-By: Vse Mozhet Byt --- doc/api/errors.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/doc/api/errors.md b/doc/api/errors.md index 41a61c48c3166a..a122bc51e39ca6 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -127,12 +127,11 @@ exactly how errors raised by those methods are propagated. Most asynchronous methods exposed by the Node.js core API follow an idiomatic -pattern referred to as an _error-first callback_ (sometimes referred to as -a _Node.js style callback_). With this pattern, a callback function is passed -to the method as an argument. When the operation either completes or an error -is raised, the callback function is called with -the `Error` object (if any) passed as the first argument. If no error was -raised, the first argument will be passed as `null`. +pattern referred to as an _error-first callback_. With this pattern, a callback +function is passed to the method as an argument. When the operation either +completes or an error is raised, the callback function is called with the +`Error` object (if any) passed as the first argument. If no error was raised, +the first argument will be passed as `null`. ```js const fs = require('fs'); From dc848587871fe3f8c8e1ed8c2c89ade62c062c7c Mon Sep 17 00:00:00 2001 From: MaleDong Date: Fri, 22 Jun 2018 19:08:58 +0800 Subject: [PATCH 026/116] test,util: add missing tests and conditions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) Add missing unit tests by `ucs-2` in different kinds of cases. 2) Add missing unit tests by `usc-2` in different kinds of cases. 3) Fix a bug:We cannot find `ucs-2` in `case 5`'s `if` condition after `toLowerCase()` PR-URL: https://github.com/nodejs/node/pull/21455 Reviewed-By: James M Snell Reviewed-By: Trivikram Kamat --- benchmark/util/normalize-encoding.js | 25 +++++++++++++------ lib/internal/util.js | 3 ++- .../test-internal-util-normalizeencoding.js | 7 ++++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/benchmark/util/normalize-encoding.js b/benchmark/util/normalize-encoding.js index 96eab1912d0761..65cf5b1b0ce18c 100644 --- a/benchmark/util/normalize-encoding.js +++ b/benchmark/util/normalize-encoding.js @@ -4,18 +4,25 @@ const common = require('../common.js'); const assert = require('assert'); const groupedInputs = { - group_common: ['undefined', 'utf8', 'utf-8', 'base64', 'binary', 'latin1'], - group_upper: ['UTF-8', 'UTF8', 'UCS2', 'UTF-16LE', 'UTF16LE', 'BASE64'], - group_uncommon: [ 'foo', '1', 'false', 'undefined', '[]'], + group_common: ['undefined', 'utf8', 'utf-8', 'base64', + 'binary', 'latin1', 'ucs-2', 'usc-2'], + group_upper: ['UTF-8', 'UTF8', 'UCS2', 'UTF-16LE', + 'UTF16LE', 'BASE64', 'UCS-2', 'USC-2'], + group_uncommon: ['foo', '1', 'false', 'undefined', '[]', '{}'], group_misc: ['', 'utf16le', 'usc2', 'hex', 'HEX', 'BINARY'] }; const inputs = [ - '', 'utf8', 'utf-8', 'UTF-8', - 'UTF8', 'Utf8', 'uTf-8', 'utF-8', 'ucs2', - 'UCS2', 'utf16le', 'utf-16le', 'UTF-16LE', 'UTF16LE', + '', + 'utf8', 'utf-8', 'UTF-8', + 'UTF8', 'Utf8', 'uTf-8', 'utF-8', + 'ucs2', 'UCS2', 'UcS2', + 'USC2', 'usc2', 'uSc2', + 'ucs-2', 'UCS-2', 'UcS-2', + 'usc-2', 'USC-2', 'uSc-2', + 'utf16le', 'utf-16le', 'UTF-16LE', 'UTF16LE', 'binary', 'BINARY', 'latin1', 'base64', 'BASE64', - 'hex', 'HEX', 'foo', '1', 'false', 'undefined', '[]']; + 'hex', 'HEX', 'foo', '1', 'false', 'undefined', '[]', '{}']; const bench = common.createBenchmark(main, { input: inputs.concat(Object.keys(groupedInputs)), @@ -42,6 +49,8 @@ function getInput(input) { return [undefined]; case '[]': return [[]]; + case '{}': + return [{}]; default: return [input]; } @@ -53,7 +62,7 @@ function main({ input, n }) { var noDead = ''; bench.start(); - for (var i = 0; i < n; i += 1) { + for (var i = 0; i < n; ++i) { for (var j = 0; j < inputs.length; ++j) { noDead = normalizeEncoding(inputs[j]); } diff --git a/lib/internal/util.js b/lib/internal/util.js index 07515e2e090daa..bc513e7fedf79e 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -115,7 +115,7 @@ function slowCases(enc) { if (enc === 'ucs2' || enc === 'UCS2') return 'utf16le'; enc = `${enc}`.toLowerCase(); if (enc === 'utf8') return 'utf8'; - if (enc === 'ucs2' || enc === 'UCS2') return 'utf16le'; + if (enc === 'ucs2') return 'utf16le'; break; case 3: if (enc === 'hex' || enc === 'HEX' || `${enc}`.toLowerCase() === 'hex') @@ -131,6 +131,7 @@ function slowCases(enc) { if (enc === 'utf-8') return 'utf8'; if (enc === 'ascii') return 'ascii'; if (enc === 'usc-2') return 'utf16le'; + if (enc === 'ucs-2') return 'utf16le'; break; case 6: if (enc === 'base64') return 'base64'; diff --git a/test/parallel/test-internal-util-normalizeencoding.js b/test/parallel/test-internal-util-normalizeencoding.js index 081255cef02f2a..2f16d55fad247c 100644 --- a/test/parallel/test-internal-util-normalizeencoding.js +++ b/test/parallel/test-internal-util-normalizeencoding.js @@ -18,10 +18,16 @@ const tests = [ ['utF-8', 'utf8'], ['ucs2', 'utf16le'], ['UCS2', 'utf16le'], + ['ucs-2', 'utf16le'], + ['UCS-2', 'utf16le'], + ['UcS-2', 'utf16le'], ['utf16le', 'utf16le'], ['utf-16le', 'utf16le'], ['UTF-16LE', 'utf16le'], ['UTF16LE', 'utf16le'], + ['usc-2', 'utf16le'], + ['USC-2', 'utf16le'], + ['uSc-2', 'utf16le'], ['binary', 'latin1'], ['BINARY', 'latin1'], ['latin1', 'latin1'], @@ -36,6 +42,7 @@ const tests = [ [NaN, undefined], [0, undefined], [[], undefined], + [{}, undefined] ]; tests.forEach((e, i) => { From 109c59971a6be7bd4dbde15e443e743a78e771d9 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Fri, 6 Jul 2018 10:47:48 -0400 Subject: [PATCH 027/116] n-api: create functions directly Avoid using `v8::FunctionTemplate::New()` when using `v8::Function::New()` suffices. This ensures that individual functions can be gc-ed and that functions can be created dynamically without running out of memory. PR-URL: https://github.com/nodejs/node/pull/21688 Reviewed-By: Anna Henningsen Reviewed-By: Kyle Farnung Reviewed-By: Tiancheng "Timothy" Gu Reviewed-By: Yang Guo Reviewed-By: Colin Ihrig Reviewed-By: Gus Caplan --- src/node_api.cc | 20 +++-- test/addons-napi/test_function/test.js | 8 +- .../addons-napi/test_function/test_function.c | 84 +++++++++++++++++++ 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/src/node_api.cc b/src/node_api.cc index 2b14617175c167..1d42435264cfec 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -1025,11 +1025,11 @@ napi_status napi_create_function(napi_env env, RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); - v8::Local tpl = v8::FunctionTemplate::New( - isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); - v8::Local context = isolate->GetCurrentContext(); - v8::MaybeLocal maybe_function = tpl->GetFunction(context); + v8::MaybeLocal maybe_function = + v8::Function::New(context, + v8impl::FunctionCallbackWrapper::Invoke, + cbdata); CHECK_MAYBE_EMPTY(env, maybe_function, napi_generic_failure); return_value = scope.Escape(maybe_function.ToLocalChecked()); @@ -1491,13 +1491,17 @@ napi_status napi_define_properties(napi_env env, v8::Local cbdata = v8impl::CreateFunctionCallbackData(env, p->method, p->data); - RETURN_STATUS_IF_FALSE(env, !cbdata.IsEmpty(), napi_generic_failure); + CHECK_MAYBE_EMPTY(env, cbdata, napi_generic_failure); + + v8::MaybeLocal maybe_fn = + v8::Function::New(context, + v8impl::FunctionCallbackWrapper::Invoke, + cbdata); - v8::Local t = v8::FunctionTemplate::New( - isolate, v8impl::FunctionCallbackWrapper::Invoke, cbdata); + CHECK_MAYBE_EMPTY(env, maybe_fn, napi_generic_failure); auto define_maybe = obj->DefineOwnProperty( - context, property_name, t->GetFunction(), attributes); + context, property_name, maybe_fn.ToLocalChecked(), attributes); if (!define_maybe.FromMaybe(false)) { return napi_set_last_error(env, napi_generic_failure); diff --git a/test/addons-napi/test_function/test.js b/test/addons-napi/test_function/test.js index 752e9965b23039..6f0f1681752a2c 100644 --- a/test/addons-napi/test_function/test.js +++ b/test/addons-napi/test_function/test.js @@ -1,11 +1,12 @@ 'use strict'; +// Flags: --expose-gc + const common = require('../../common'); const assert = require('assert'); // testing api calls for function const test_function = require(`./build/${common.buildType}/test_function`); - function func1() { return 1; } @@ -29,3 +30,8 @@ assert.strictEqual(test_function.TestCall(func4, 1), 2); assert.strictEqual(test_function.TestName.name, 'Name'); assert.strictEqual(test_function.TestNameShort.name, 'Name_'); + +let tracked_function = test_function.MakeTrackedFunction(common.mustCall()); +assert(!!tracked_function); +tracked_function = null; +global.gc(); diff --git a/test/addons-napi/test_function/test_function.c b/test/addons-napi/test_function/test_function.c index 2c361933cfa071..068999a6e5bc5c 100644 --- a/test/addons-napi/test_function/test_function.c +++ b/test/addons-napi/test_function/test_function.c @@ -30,6 +30,78 @@ static napi_value TestFunctionName(napi_env env, napi_callback_info info) { return NULL; } +static void finalize_function(napi_env env, void* data, void* hint) { + napi_ref ref = data; + + // Retrieve the JavaScript undefined value. + napi_value undefined; + NAPI_CALL_RETURN_VOID(env, napi_get_undefined(env, &undefined)); + + // Retrieve the JavaScript function we must call. + napi_value js_function; + NAPI_CALL_RETURN_VOID(env, napi_get_reference_value(env, ref, &js_function)); + + // Call the JavaScript function to indicate that the generated JavaScript + // function is about to be gc-ed. + NAPI_CALL_RETURN_VOID(env, napi_call_function(env, + undefined, + js_function, + 0, + NULL, + NULL)); + + // Destroy the persistent reference to the function we just called so as to + // properly clean up. + NAPI_CALL_RETURN_VOID(env, napi_delete_reference(env, ref)); +} + +static napi_value MakeTrackedFunction(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value js_finalize_cb; + napi_valuetype arg_type; + + // Retrieve and validate from the arguments the function we will use to + // indicate to JavaScript that the function we are about to create is about to + // be gc-ed. + NAPI_CALL(env, napi_get_cb_info(env, + info, + &argc, + &js_finalize_cb, + NULL, + NULL)); + NAPI_ASSERT(env, argc == 1, "Wrong number of arguments"); + NAPI_CALL(env, napi_typeof(env, js_finalize_cb, &arg_type)); + NAPI_ASSERT(env, arg_type == napi_function, "Argument must be a function"); + + // Dynamically create a function. + napi_value result; + NAPI_CALL(env, napi_create_function(env, + "TrackedFunction", + NAPI_AUTO_LENGTH, + TestFunctionName, + NULL, + &result)); + + // Create a strong reference to the function we will call when the tracked + // function is about to be gc-ed. + napi_ref js_finalize_cb_ref; + NAPI_CALL(env, napi_create_reference(env, + js_finalize_cb, + 1, + &js_finalize_cb_ref)); + + // Attach a finalizer to the dynamically created function and pass it the + // strong reference we created in the previous step. + NAPI_CALL(env, napi_wrap(env, + result, + js_finalize_cb_ref, + finalize_function, + NULL, + NULL)); + + return result; +} + static napi_value Init(napi_env env, napi_value exports) { napi_value fn1; NAPI_CALL(env, napi_create_function( @@ -43,9 +115,21 @@ static napi_value Init(napi_env env, napi_value exports) { NAPI_CALL(env, napi_create_function( env, "Name_extra", 5, TestFunctionName, NULL, &fn3)); + napi_value fn4; + NAPI_CALL(env, napi_create_function(env, + "MakeTrackedFunction", + NAPI_AUTO_LENGTH, + MakeTrackedFunction, + NULL, + &fn4)); + NAPI_CALL(env, napi_set_named_property(env, exports, "TestCall", fn1)); NAPI_CALL(env, napi_set_named_property(env, exports, "TestName", fn2)); NAPI_CALL(env, napi_set_named_property(env, exports, "TestNameShort", fn3)); + NAPI_CALL(env, napi_set_named_property(env, + exports, + "MakeTrackedFunction", + fn4)); return exports; } From b8ba003fbfd4395d2c815d30504d80d60bf71fd2 Mon Sep 17 00:00:00 2001 From: Gabriel Schulhof Date: Thu, 5 Jul 2018 22:24:17 -0400 Subject: [PATCH 028/116] n-api: remove experimental gate from status codes Re: https://github.com/nodejs/node/pull/21226#discussion_r200534431 PR-URL: https://github.com/nodejs/node/pull/21680 Reviewed-By: Anna Henningsen Reviewed-By: Luigi Pinca Reviewed-By: Colin Ihrig Reviewed-By: Gus Caplan --- src/node_api_types.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/node_api_types.h b/src/node_api_types.h index af7d7c7f95331d..3854a32f011048 100644 --- a/src/node_api_types.h +++ b/src/node_api_types.h @@ -76,10 +76,8 @@ typedef enum { napi_escape_called_twice, napi_handle_scope_mismatch, napi_callback_scope_mismatch, -#ifdef NAPI_EXPERIMENTAL napi_queue_full, napi_closing, -#endif // NAPI_EXPERIMENTAL } napi_status; #ifdef NAPI_EXPERIMENTAL From cd77d8782a241f5b4e4d65c1f8852be57f8edbef Mon Sep 17 00:00:00 2001 From: iwko Date: Sun, 10 Jun 2018 15:28:00 +0200 Subject: [PATCH 029/116] doc: improve documentation of fs sync methods Add links to async methods and make wording consistent. PR-URL: https://github.com/nodejs/node/pull/21243 Refs: https://github.com/nodejs/node/issues/21197 Reviewed-By: Tiancheng "Timothy" Gu Reviewed-By: Joyee Cheung Reviewed-By: James M Snell Reviewed-By: Vse Mozhet Byt Reviewed-By: Jamie Davis Reviewed-By: Matteo Collina --- doc/api/fs.md | 63 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index e159ec573c6664..872820a991f170 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1137,8 +1137,8 @@ changes: * `path` {string|Buffer|URL} * `mode` {integer} -Synchronously changes the permissions of a file. Returns `undefined`. -This is the synchronous version of [`fs.chmod()`][]. +For detailed information, see the documentation of the asynchronous version of +this API: [`fs.chmod()`][]. See also: chmod(2). @@ -1562,13 +1562,16 @@ changes: * `path` {string|Buffer|URL} * Returns: {boolean} -Synchronous version of [`fs.exists()`][]. Returns `true` if the path exists, `false` otherwise. +For detailed information, see the documentation of the asynchronous version of +this API: [`fs.exists()`][]. + `fs.exists()` is deprecated, but `fs.existsSync()` is not. The `callback` parameter to `fs.exists()` accepts parameters that are inconsistent with other Node.js callbacks. `fs.existsSync()` does not use a callback. + ## fs.fchmod(fd, mode, callback) _od0~%3!k7}D%}1@6C2TTeJJEMUm#;i| zQv7wF35Wh$h>r|gVP5~AYapXT5}C>@jO2678hW{bbFQ8>Lxc%oT2tVI}8HYn$UTv3t(d%9Kl z)*TWj8NX?P(CvSx7(IS2tXe8UlENj+gv0IT{gX~^jxsIsco)H=V^C-h&I z)$^f{b0PQ|cusHB|7FN=AExo^kaLvW8*=&=;Ik-ChnyIA*)t($1^7?U`hjKr&%The z3Vh(VA?J7C=LZolw5;)e1~0IzA%{ZF{oorfgq%0P`+pyDdKOvMUyp{IMsW8_A?L5) zZLfr!xfkMt*(XA-vj)bJS3_+BF2e6E?wZ~<0X%Q{bZ1`~KJsyCPiNm~%lhqGp*Fmq zuu^9=J1aKf(>y0bZO6dAcQv<_KZ=2e8{2LniyPaPkmZeSPmq^2w*8qL-q^NbGwP3O zYzu9%tg4N%wgq77CO6iW_6r!J8`?_AD;wGtl0|dctgT?doVHeSdVSlDSFyx9o7#R4 zUiW2FTTiQ-HTkD4ZLQ#(x)x_%w{F&scSFuG@c#EgZ5!-vR_X`MZKdac|7dP|7z}S| zX&c)eepO4`YovP;9R*-aZfPqer?#|NJ-{1V+R{?GSrxNeoL5r2S&!UtZrgX#x>++n zh_$@{+MA&ZLf6>R76^my#oEfjY469J>%jCI&uu%#`1LJq=bQ^pZfF}zZi%(s3HI6= zYx{OaH_L6?7Hiu9BXv6h-|lAB?uxZpnGo!bwY7rbJ+Zb$=b^)Wh-X1}IM%iltUVHI z>zUKdiX58VmX_Pis{AOnydF&L-mrWn81CNC7W^*ae?q+|;#*tVewx?K`uORVwi96R z*%oI~emC6z?>x7yHNTs6>u)imQV3rFhk=*OZ?k4}vo0Ug*mmqD_){9&teN27o7=|T z(#t=NyF@J%zq?t@X@?CH#Xy_@xJzn*Q@`=~gz(Xl@5Wr_%}s^LjerFS}WL@_RZp;F5x# z&I)kj4-o&qZr1wDp3c~wFh*qcbdG_)>D7MC<-u#dU*h1Ku$}BcYbZM~4wQ0!U=re! z=(oRgdGJE_uGI7J(9$b#cWSSI*zhwt`JMAwo$}e8{G3jHZYMvI@NwtNM2FEtK>R$v zpM{rdpYQKHtJ7fp1Mv^SKY(QC=-2f%oaofw;kN_U1)gT3&qn-0Up!$WevvPpuo1u5 z7x!#86^SxmB4H!m*B4LNh+pE1Cv3zo^~Do5;+Og22^;Yr_6oSN@0b}Jv5~mkvk_0& zh!66`6E@;QeDQ>h_!Yi*!bW^}B98q}I7s{`k?`4>0qe(Je2dS9{z@;7fhKIkNBiOl z8}X}rabsiu@8T9*?MozV#K-&M2^$0Yi5JI!5;pYLcySCUVIzL6FP^Xwcdzp$5;p(t zt{11xzebz%)6THZ%!PcpP^WrfUoNaU-GBa>+++v*lezv5Gkv+tPPuGfF1u4M*O%j; z+x53ezp*Lon!mxzzpeXJCoz)d@9^H?i$~6|&ulOPy)AH~8`a9200ncUK7}!yf*OlAz1=} z1yW~b-YPNbh0(z3Q0LNKGc1Tdw{_K8QO;AgDwiv_D$5=a-5})%a+7ayBx2q-$Mr|861!|P>su(P^?gegUv$W z%*}Izw!;`#gyh6bDV5op7~0C49dNb|m|N@F+*;3OyBtKu_gpbJVg|f|kr|rRpaaX^iNT=r z9PKYDhqFfGxojLW&DEvM#B6f4BOX-GW?eSS zZHcfBY_^|)x)Bs}&X9|0dv4U%U{o7qBODAH$wvb`f!YbZ*FNKH-{q+B zq1i+SYJVH?=xTg)TxlABAHX|aGjg>}9H0J6#ML(OgNS2wj9%?T90Oq74KM)*C-?$x z1vrG{07?RFY2ZaZdo%~cI5-+xaRL4wT!?k3VPT$yIULLj12GdX+W;b7RsvoKYTPa3 z%)KOSuL5V3h{M(nhHD4I)xTQ(tF^q4#q z%9R{>IUZJ#yC{kuI$+r@3XpQp5;pU?N*s`9Uyb^B05B`Vd&VUwyhpI@L9|)$?0L}^ z?El3YxR{kN3nPGVh*lzb7h?modA-vf&TXQNI>WWF?#0M)D=tR4kUlgQqL4q-yr&!D zYQXD+l}#twz;#$AHe?9!}C2Yj8|4hhb zl&}*OmqFi^5ZV4QAT6Q@PCNbGiF+Wf{X4R}9+;qKzweWB=J$84{O7 zx0)q2)Q-%7f%p;>?%^fSUW(jxYX7JV1E7uAwYaPO|DaoCIs;OA9LFPQ+eBnGL zY{m}+cAs8{{Wk_?G6;n$ks$0JYJ3pmENGa8gCnm{4#yz5r;H6p8LfrB*;e}67Poj1 z%2hkDRW1?~aVgpHb+Cu%or{emY+VJe4i6<);zHO%c7ix~Lr^YKdiM|xhyPd>gV{pUu*}%}~cv;FJ8*VO9-q1J@-ZkZNP1uPz>bN+Sl3)X#?KQ;8UPH-; zjNyUgL&k9PR?6#uZ*yUrb~Z{~)4tj!&h|Y0CVZGEQGp3>Y1B3|-J%UX>#L}?X^^&8 zPwhn9TT#Y28BUNkR?!UXSw8>4NhA(>3B+KVIEmK)Q$cM!RPgnlPc?iX;Zx0UoPOTQ z`BHp22hM_=DL5O9^Njq+|CCjR>;Fec@I)F?fGgl4a;F?h#?7r*HRftin^yqyynXWu z;k8ZwDimJ#v`wcxn3?iG^CJxGO1&zsbZKCgSNiT!2^$TtVLb1dhl+&lyFZ*26>xRQW_edIIzrQA=oajA@R5F77%lVP3#u&m~p0E70P3C!~W;@$&C zA`ZoQRXf9bzEfUR1uj0VQ>$@mIb%bbj#(P3igHUAp+KT*+Z5VJQit^#L7dNWenA5V1P<$UdM;EA@p2~npLSnqlr z_-90cHzTzZ9qJ4cjrwOqt+z^Qn|j{hy{*t~V9rg9&>Nk9AZ(li^mpQQU}hQB11iM# znD`E8(oV$D_GV9G`ir_!0Uaa~rc+eL3`~qOVI>kHPsGe@H10J_jWjHJuU*EycD*)C zeHRUQmA#7Qx<%Yvx1d)Muac&7&F;FjV z_TMD1L?+0kVkHu@OT^4fHSRS)jhQv#p4QWO8j~L&0}6nsvq6b(htd5Hj7p3cL9tCm zwas$EM&$>XXU87VVIpQa*F5Sc;$B;9hSEGUxBmpY!5o+fd(Zo6@X!751M&Z$_T&HRAKl z!03B3QrjO-tb~{I4M6r?JNG8EnES6YFe|}|{u$Bt55{XyMsLoI82~5X<-7rMMY()`cN(~Pfz75I{FV8hx$?Zyf zjZ_Ug->x*iX%BI8fD3C}Hedv%!D8)Di0_{=4R2TCJE+tb;+vkd1EKLvd+<$rAP)6* z>dfIDY2wkq$Qr&I+8!9`s&ImiT!=5AG4b`vKwz2iHz=clbv4nz#+pE2n+{lLeA6&| zGXQW6I})0_84!FkAos8lZVw0pfp^sx1_GaG$HM*ie`JBZ+@|!OMgx^7#VEAN9A!jV z3Jyjo{VBGt_CO%>9brG^Tkk|&B%*=bTI?kRZy`Z;KZ&moQ$M6kSBAlf(2-~WOqOi4 zlcBmSWj46&ZG7dJ>a(Fw7FR}OgT=OA)T9N$rO(&vjY{zF9pS~RKHUFk*JeZ z7YW>~jA{IE-T8<|1BVfJv+4?Au;Ee@pnp__CscUCG*A$D)9A`TY51h^ivuT(9}T=~ z{1T7@3I{&cdLL^&@rwhW7{4!wPtZ{Kg$ln=A^n`X1Cf1y=WGuY@B2G`je-&PKt2Zp z-GdUTE5^*kKM0@Y1Xd+tR)%@5jQEvCUmB>yrHp$Me{fg%)+So~~kMx@+2rQvnN1LJk%twNEm0?Aci$6SRX zT?Gz`tH4IRPDrnF+LeiVTy2>M46a~X_RM-CQaUOT$X zrcJL6-Y3{bCE9R#8So5nMm!uI*$Dl^hEidq>9jadg-de5N4)$zmIfB1(Kn$zh_)7+ z_M%|aw6_>>o(GBZJQTt<2iwI+BvA=5E3ji$;Gtm!(yKgDH5?c!H&Uru= z>-kv%eFd~jP`KSoT>Qf&UxH!+#%HB$#5fQUbIF#VaCNAyOyoIuFDC;NU9t_X8aw1_ zF~`+o8}!{Jx=c%Snb;|pi50wt4{L?P+9~NZMh(x8)JqPAaZk?`VtZVnCAvaOWQFka z7hR$yxr4qLIvL!3gK&2M3WtP-%Kr3}JE6rr24ZbdHM&Tsb=0QFEV&nIJ zA7f-z_vG~{FC=p9vf@EAFuu5mj;EZ5hq`2=C4sMWuwIO_=fFs=1_vywLYtR12IT5N z;fQ%qR_ZFOLM5JyRVY0D^tmeZxdN+Dyc>_$vJ%vBf2rfPh%W=6j;q8$tkRWoSLwb} z$dR+62xNq-bb!=xfU9(X>vcfu5#u(|=eb#r!gd(v%q1?{w_ZC>>hX==>`W5ufVOnJ zUOQf|9k17pH)uy2w4)8$(PqTCV%##WnD{dD&1hHdt`g_Km^ary2S5RtwB^<-aT(ZV z9oS|a*k&ErW*yiT9l#bH0DZ0meYU&B*N52XgV(<}(#aO>WQ%sPMLXG|oov@mwreN! zD`C^;veM`N)93z+FRQQ}eaZeyoP%>I1EH+jS7zbr8F>(_N^`&dGyy zas@EQh8g2Bi!b}X%k&|au-dZ!;>Zf@($05j=exA?U8eIwJp1w4kk5d;WV1opcRmwx z-|2Hu^tmGRxgzwrBJ}yvya(kVU;p=DRJ@ic;Hwt}JjoPrWtiuTnCFZnFH0&pIX7a< zBsfT3-y9@g2lnVB_UI%yfE4!UV%bv60HT332H>ZupP_z+@i}XbTxQDwbGG6$#!1L2 z_s2N_FP^kHNU7^`sn{5oij9$;uo9J%wpDBQL8{=G2&f3dy zB@yTT%i&s9jgUk2cb zyauFUt`4t?30m2kIqR@-uVZi4>_DEJrM}mxTv?3Clhk3ol8*BEk9V!3ItxDi$}IR` zD*MS9d2u%A#gn$IjKpQtsOR9>u2-M*>UggmE-QJnVkPb;D{=3RnimNnAE%k;KJtm# zYlzE8ebP@3MrJJoW?dG(in2bp^k`iHpa0l^TqZ|#Z;tBnoTxhxUhoks$*Pd-@Pz45 z;@k?ZnB=^?Eb|G|P9X4l-GRWa_xFe2Fbv~CmG33Pc;o8Q;5*n7kRv}3S@01n^T{@h z%Y^xexm>(QZaF~SL6V%Tptq6|lc!~_B)5d`C516iMHq%IewSxFsz>PYHH>LMu3x4L3b$N+^Tg(QVh z6gEgwM^Z<&Dyhpup^l`Eq>iMHT!4~~LLE7u`%i;Hl0uTg0u%~KHb_!OQb$r(h(aAn z9Z4NY9Z6jg3UwrPq)QiMHq^=Z&I+8k)I?y#j z1}N-GwN$Sc`s3WN(sUxW)sq2SA9Z4NY9Z6k3?mvb7Q79xSBq<~*B&n-F zp^l`Eq>iMHq;3F8vywXc)REMY)D7hRQ#cTXLXtv~LXyJ4DAbYEk<^i`O6rE9P)AZn zQb$rpE=dG5(;%BbtH8pbtHAwD9uXh z=u<~hN4gYFL7|YOkfe~Lkfg2#g*uWtk~*?gNnMY6G>$?}QPwD@DQm%$dh9=iFpQ9- zkfe~LFb#z|k`0p7k<^jYg;1y?sUxW)sUxXNN1-mg9{W!r0~C@Jk`#tfC?u&PsUxW) zsmnm2j--yHj--yHE)#`1lDbS6|4_&Pg(QVpC=`+ulGKsZkLMuBO6urSM^Z;p7e%3tq>iMHgbttofI(p%3WX$v zB!%QOC3X2I)REMY)REMYwI~HB)REMY)REMYwcP(g7!;Bek`$5@l5DUDg*uWtk~)$) zlDc9P>PYHH>PYHH>Pk?mx&JgMBq<~*Bq=OKVS^-fBy}WpBz1jIs3WN(sUxW)sVhgJ zj&v!cK_N*YNnt+}3Q6in>PYHH>iVNlM^Z;pM^Z=D*5fDMD0vC84u1bDL00qxtemu` z`E0L|QjKxuXvDNZ+iQci*9L8`4cZ)V1qugT;a&d>aKt1>Oj0-ig+h`uBdH^)BdHsR zQmv$pK6NB@WNrO^{0W`?x8ERKWa!rKU#o#F%E!U53P}n{>PYHH>Ux1}kfe^Jj--yH zE&@_VQb$r3abZx%0EN9l3P}n{3Q6in>WV??Na{%HNa{%H`hwJv)REMY)VVY$tNI0#Zj(M^Z;pM^aY_Qb$rpQb$rpLWlqV4}(IILXtv~LXr(ufz*-Ik<^jY zkPYH9*9aM)aH|SQ3P}n{Hb_#p6J&!VbtH8pbtH9r zLF!2ANa{%H_D;wCQ}~<;NeW2{NeW5o4uNctq>iMHq>iNS47sNF7NXNgYWYN!`~VbtH7IF(_n!LXtuUg+h`IlGKsZk<_K5P)AZnQb$rpQg!YLWP>Di{ZXhRsUxW)sUxWyghCxj9Z4NY9qCdy0);}7LXtv~LXx`CDAbYEk<^jY zkiMHgbr^|X5;y9$!xsQKq12@D^N0&rOH0a!OEe^vC47E8s#))Gq@Rr z+^XCKwxTfJ>f#?J7}zx%fBykx-|Pd?>HmrbLJiSCCQ2A33uPS2B*XowfjRq^r_Lb* zfuP}jyQi=h7}h*!^7NS}neV0fUakrlpn#+xq5{w~K%aS%`QDlbO`bmUB=g0Z2Th(n z^Ca_qHSfYO0tP4`DX35ZX!7)#Cz&6jdC=tPGfy&Ksd>=k=`&9x%pwB$X{AA68CQqMvlKEQAgCTCOQIdjG6@aEw`plEe zhcyqHJbmU#=6h)#G=k=`&9Y6XE7RuUSNkYYl=8WOSRYgHd&Ne4*v-X1 zX*5vMl^Nhjlq~gg)Q>3hG+v^-RQW^YAdvO1Fn%P^qbY(v^%e=FDMQLI=rWO^flOt# zGS|mDU`Ny^=`(*9N*)T^5k>S1Y-)-F)8P*`I{JMBDf7w#eKejmuVg#^DlTHwl?4jtEefQ~ z;~zL%uj~u<4TMb|uRrNJfDA+WrGY348qz^XgJIQGCMr|6D3DT-G4C_A^snah@C zp=}N%&kTgCREnSfn$9^08CgHfI$IkQmcp0i;&SKciU>>LOKzw+5A<0IQHsry(`Top zsGF;Gg=O%u{}=^EE}~pBQXU#WQP^#^skrH&0uR`G>?3Gj$`u9!_U7_ZMi?9{cuU9S#6l-2s24BvLkb_@@e$C(i zYc~cjnGz%Nma8n#AIsL?x6c)((KWb4+1Vlzm#XnW|E25AR8GDBk}kr^!AE_w!fs%a zS$YQLvMIvI253h@3p;Jz75$+|Nz#7n%nIK>~ z5PCNo|FtWXc@s)8XG1z2Xd2~}Am@Si4!K#eF86v7ZdB5l(D&5+bJUumKfnjD9%{KA+PX;F9(i(;m)tDR>=4DyA z0>ez_T<-F~IK7-kAkRx!?xJOZ5$K=_THzT7&fE(|LMwMreEv7-aO5)5F^N6qi2DY_ z=OE+wO4gIZ*9NcZ-o9piaI>53 z;7|TLPOlt%i9oK?itiR^ji3};P)87pqU3do=R5dbbcx|>C0g;FHm&@E4XYH|K4`Dp zah8|E?g!ln_?3>8ieim~KThgpqfQj1z2}Bt>SYs!q0DMO{ZMd3w@CZY4Z$A{I(~VoWerNTt!q;)`?^#s6~&qizYhKlsZQS;;NO_a zel2Sz;*Dz0gMC+Odu&fI$G%(W-eYpDCDh*Qv+qkibB)?N`S+u(htS`{=pV&;1pbrS z&r{0ZAa;40jAM{;i1G@dYYjJs4B$sT`^PH0QaM_AmGbJeGY2@nlm8RdU8B4<&9+*M zEq&c)Y*!o?$u?i|=RW(tefC{x_y%8}e~-_;7j697_?GpA@=3(L6}0SggHG$YLHx0p zpl$sC{sloR6$O7Q5rNBtxkaU4ggrCpT=}#1$2JBZ zuCc7@kY!H^IrmKoSyoL*8kwf7RZbV;Zznsi-UQt&^=}S2;hW*d)UOXYee2;jXuY|i z)f<9Y8BHO4g|`XGLbaN3c!4jp5O(_~j|B5%E$!uKc{y5Ip)IWx+N(m&@>Phh*7zEY zuMIiT#}NO8`uJjvF^|LFsXo46A2Z^%d*<@9u0o)dwXugw~@>|7hku1u4UhGu5-hk@E5_q541AcV~+;^t6QcZ zOOP$d5#$OYf~X)*kl(&UAg#!@($x+l zo?&d6aAwh&mrWM5Ilg=@^wx##Z|w;7y{s5b7HgBmwiQ-81My6?vy|C}(uH-AseXA0 zs+VYWWeBl!wZn!|B|~GGO6%hGx3&e#+IwydI_=B01iy7rDHzk`}pGJgm&{B_J&!%*~@8q0z{TkRZW zt}>#G4z{gAwTqxHMzMyphTGrX7QCeQP@VQroKR!ybcbSJ5z7RvE6jMW9_AbGFf6ps z4;veoxH5AX7Fz9WXd}bUyxgP6Tf>vLjk*z7$`R0wNcIthUu1lYSy`eig}#qZTdwhb zi1$~!!j~U_xb-75jUkojs#0d1rVLf$N>DpPnW@ZzK3nY^*w&BRzuXzT`1>Pu;go43 zv2$vt!?vz8vvZ?X43B}M(C4V+)Y9OGe11Cou<@~kurt-p8f9C#YDW-{qU5Qck6b^s z`@3EX1PFTUT)s zS6n_8RmbX*DMN^*s~uKmC^N_6N>$ss+FV{Yj??8DheNJ*XdHSrww$>v__>;k_~KE- z^L>5+{36vA`{E^N+Zr#I<^+s*f{qw=$k=+c;fGN&G@t3qSwAtGdu|n~S7~+F)-|Tp z?@UB^B96*LNoBy#M6s?lbG%>@(v!57GNepLF08h7osqlMkXNg`8rPHBVP%G)j5$+d zS;}l>PW71^my0$dnv0^%0<{a3Map6jf4U1l#Lz$~#QoInudL|Q_yA}IYi_7=m~w=& zQaMsN3IiIW_E^~C)SggnO|`1jnW&tktcGxk+BN8Oy4o|8Hz9U=H6A`DTXy%UxV)yC zRXF3NGi7Rf?498G-KMs$*d6TULY3ZyD%?dZLt=>++{~%6M_J13PWc>-=XQ!m#P5X* zG1A_=Cm4=IC340%^R#yUR6git6?AH(5Cboo+TQv^u-EmSQYD@I(oTLK@z3b39L@F9 z9{VdRlmpBN(gs2@7-c9H5bw|i4%74qWuVT2GUUsp`|NPSZpC*%wx*hw;tT}a-{dX99EY5Q22a|?#tl>%Rpu%4l?BQ| zLm5+%&n`~dc8O?Hl~$?vR-YO-IiYe5_7mFuwUW}R(EI@9K;>ZNP~|Y?2tyfIrN&13 zVxwqZhKpg0CdVqr`9u?Zc9rHQ8nz$Z6U>!o`qUcDO!KL0v0u|AZqE>=D#PtPo(z`c z$c0ylJ>1n2J>w2$df_5bu>RZTCbW7%8jbU zm2Yp7SZe#%PX-G{ihjH5b_nfVNqwr=_+!P%n6)Qq+k2%>YWtYI!CqI}&#J2o3r#n@ zG!{|zRu(I*eZFQ7c+Jk;8;qoz`t}howt8=HkZZl3lv{5|%6c;yv)@wrTQyb~#d_Pf zu)GJJ)H?4b>sar1(tIcy`{Sf0RczO?KG6m~O(yNnB$g_a<^5b@_7|EHnq1gt_tJdC z7wfIDVr5@t#TRm-O(R1zSgE{9Xma>VU*?88S!sPKzV(%|qy445!SBinTWQzJ<=Fn& z-e3WLR-rzZE7RC`L*t8Od-{P-1+xaqY$LvW)Mw}U?0lbHpmuxnQ^6lxZk6~_r9Qil z&o1}b{d{)&n;3@v5OJU{KG9a=~+p=&y+iF8*ITJ5)=lcqy50%Tf`7C6vFn{k41T@(4ahgR z2+Y+$)AdeU)Ae{poQ4+=l$2?$=BA$pJEV3x?6BGyurt-pf}O2)4(wdDBe0`t=fTcb zy8w0}>>{3+A~?mGD1lw7b|2W~YWJJgzV5kTHm;_Dhz{21P}sxN9s#>j?UArYsXYev zShdH&o}hLW?1^emf?cil6xcOtPlH{n_H@`YTy<`OGfVB8VaL?2huxs|T-Z%&H^ZK< z_U*7+)m{L5q1uaKFHw6b>}6^%hrL4WAuHjmQfD>nHEOSgy-w}*(`;*_+M8f+Q+qo! zJKA5}7rcnC1NQ!4pDei)bAKv39Wg|1=>A}Z=1MeI>gAU14;JNE)&PwUR1Ws?FYcGu znNRiy3tXJ0TAZd@wl9697#oix%8)OYuCcH(qZaRs)z0zha(#BhXGc|=hd%RZ=3l+K0mDX zI3DkeWWvs@bHbSrXVsnc9?Z^0E*rU=vvXFiX#)>ib?t)>;q|i%NnRJR{Ig?rL7h`s zfNlzt!?KF%%zLJ8b?y7m`tY-BS*2aX`qbfFa2N6ZXuYD2Z(=45=psF^i`d{UVnfl) zurA^w5U(`N+anF!Q5eYBI?Enc=ky+j>;$Qmcu!i@$)D((`Xtn?@%htyb}ibP?(=8B zzX|=!!cNVCeY3QCMWUlvr;h4)0?~Z?B=Yek$wiFcos|b?AiHV&(>KH z6l>qvIr{+W95_4P>j3I>5fAo8g-Rr?gF3!LShz#*4|9B(?K_SHom8}W1fioi;zxqL zE|KQFjkAx{S*gd)?$UY%Q#g(lJC3R+WVWw$A$$GonEeKNe8acjZ=S8O-a_+lCpD=) z+d3(FZ-~~rq8ntM+}}@DvOmPwKTP`J59^#_#H^3I=7&xH;y;9s{?;oUgZlTVu{!zXZ1- z{wr{M%(?e*a7WB>c7nT*|24Qf=KQ}Wz&&W^NpNq>3GYRRPseyvKinQy~Css6h$XWhG~ z^j^$)?|txtm{as2_)*N+@-f)`RxB$@%B?smx5BT7kCIYvTY2@et@*G^)$RkkLhS*t zYwIl=|4GaF^9?Y%&qigGl-c;9?rgcjX271Sc9XJMIUl)|v!#<&u=lBb0BszcZP|xr zJ8vI?eOTj14CS%xsK#ELEkF6QkIi=KkD=1bvz@A!QR$B=_!I0`#kT)E+e$^T{-XY0 zXFEgw3jc5F|L<%k{J-$u(Yk-v_B^Pd<6d=#>f8u6VAVkBcuDb&;EbN zo$~qr2mf@^x8oQTiru}zIT&cL>>dryS_e#NaAK)oT7y%O)_{9cgIOs%y}?RFvBK(S zxDC$E3aUUMq;p6HaK5j365@X+z&>>7}Ma~GZq!csc=Gr^V|gZRq9V{aQ04wKS}-S2IrA# z)SKMkOq&8uMeh0r+xiH0_c_j~?sF_VFvr3FrLZh#jx2rZ90&h5!m@(u_nhMl={X1c z|1AxiJICpJE-Gfs!OI1NnHtZUps-HKUw3&Vzly@;T1ery$ zpmMNssB)NcgtAgOQaMUFM(K`KW1Mn=vPwBoIZ0WqoT98zPE*z@rz>YDZ&J=u-Yj&j zmnZuiE^oOnR2-y_J4&2Rw`F1S1Z>j*DBX3*DE(D zH!3$NH!HU&w<@oC|^^)u6#rJrt&T2+d})~94qyt*kNTaWklIq=@zTeS6QJPqO4S2rL0m; zR@N$KD(jW=l(<#OeHLf2ZSfk%{El{=MtmCq>;DgU56uKbJgZRH2bPnD;XUn}i* z=Qy|hpNs4Kt2xfzufXne@e({&UV2ju<;f*&F7IFx4|(wh=1}HacKTeWS32s3yU1nC zwNg>6Ol>#I)67BLW_(FWwim!tbdHL0Q6Zvs)Yo|);`tgcfL-X-;_;FZXrQ&m=6n|6HfGKZF&&$^+mJOlYwxvGx^z3=R&XTRt=K zL97XEYCrbJU}1_EZU6F*!Ld=#!#n9ST1eCxdpww(tTW?yuzz;4&cZY5v~M{cyuh_r zU^pvfxYjDzt99sWFh>+?t>)G#*DE(DH!3$NH>1uLwYMs_DYq+kD0j_uIXXQ1V)u8W z;vP-xRX(kJM)@o>`^2^n%*C5t_y;w2NO@R!M0r$bzX<({&>z$IDFS3Y@i}Ms8R};?IzuwyXQ`jv=tQ#N z=ZJ6R!geE?j3OCDAW!}LMyDbleu4UhjZU}_ev$gcjm~|=@JqzEN@4fWTzR8Yb_x7T z)xQkd%i#Y|{maqUZ9wBMyK}>1g=oQ@J8pl5%4R;w?=AwRHO6G0P1 zyOeh;?@``M@}=uO4YVuoS3aP8Q2CJZVdW#rN0pB$e<8HH#y_v+n2bjQ)720bM`$=&g6TreD|6dE?s4N zlWjd9w*6p})AvDW9@5;y%101;R5gz^ITeq=|An{zzeEF%YjP*{YbTPwM(jxwlUY8c z4gJO!`yZeE9Q42S`Oo|8-}&qpe75_0pYsQw{SpT7vhneg1T_3!6MlArE8_#iKhpR= zg!VsiApeQ@zcl`D*#A#+r<$C|DY1LQu26f(sV1uu_8`8pU4=xgCT1d0ul78|KUMpb z@@vF?^?w*l_j$Ox&1+xw4t{x+qD(XVuK-f%8VV~j=Cyac6a3zJ^UnG;j+Hg9z4Ro0 zHg6OWJ7=Exq*`tl%7{v$!f|>^eqgUu@LB3Clcd|qvOX&1RZUBt?} zi1q6t*1wBbg;@f7fMB3tuwbZQ*u3_2?*?plcOYIfYc zAHn#z**Wzw_>X4ixqpD&TX3jax; z-~9q-w>XO$z&S0>ALf8_!MR{#i!-PRTh-iRu77)ei&H!w@!MLQ$+scy>Ok&jar)kY z_yWWh>L3>jU3*E3m5O35Rq?VGr`Iz0%T>5Sxl*}GxmswiX>oe5fqt#}>(J4IEzW%p zg6mtHb?d>0z=yyMEl%48@ZlDx^hLq{burk9Cb?}0osj)0qjci{s$7kpI?1*Zk$~0(R==EKv?N4EI5TC&gKOymqCr;bl_efpldWdbM>^W^meDNs!&*4+^1?+;S#4dcw zJ#8O*TAWv&K5Z|5K^k81!fCtwh}iv<{g0fs8-6dbxxYsTFGnQAZ zVY9RMVNW|Ib!v~DwqJzJ_%Ya(FC)&I_sEw|+qr)fQA8R2<7s<@*j%PozBpGR_BCQ` zz&?K3UVU8bHL!Q6y$klXH>9oYZ=AN*|JB&04b(I3Z20SGd!xo(HqHh&A-L{u39+^Q zZ>R08;#=Ebk9jANGy1XbU`tL(N1vU-Dt;yQD_>#HVSDYq27h^nmOd%JQ6VvGBT zcuAiQdv#y2*YxeMkBV&?eG&ela+5QYI9%Rg-+ZZPW6JtVJM6IoB{ps#Ru;C)YkxWn z?#T=Uqm?4atL(5}SNjdv1y@SE@JcMP*k}?yxth zy&3k<$tG?(8aBDZUOz>APIAMP4*SI$q~5U`TqI|RvtdSu{ppQje|BSsy`?@`X{*@Q zwt6hH#=R4M7_k|%Ma!ApG`qw8Tzu;b*k3fr?7wX2uwQAE_K!E>Ts6Vw^`G4Y5gbD<0{vKYUc|j~~UQ@tD|YLeo(wX~S=_(jS9vtI;uUVrg4D z?6AbF4A^tGNjpv3I_#3i#V%F$dA!55H>81Qx0Jp)UFpMR-pm8=sTjzd#A(BSGxfA$&=dF$qsv+`kOl6iby90Z!_IB85--s^sO@}>BY%|*l@IQgiOYu|K zbI*y(_1|<(+&D5UHxsbr+hnZe~~Hnmzi-p z<2+-V!<>0u+#U*_9S(z?mzB)rXT|L;--*lpf9rSRc5{w2GCwD7ht3xvE2f_xw?B-C z&(r>K1o=YIrWeNTw=WR;&oMHX1}aN&SYpxV`Kq(YedjSaDO_eqHT1V2_w7g36h3d(>VrO z_E&0mz>eG_@#sBq+g@pGGl1?ZaV4yh+^|(~yXZl&i`@s~cJ@Q!Y~!H|i~J^Y7ce`eb3!}`aoco_02crh??zzQme-~-+VKsW&=EYABB4Oi&OaaIolfF z)my9WOK36|O6sO@tj4X*6r*mTj-{v~t*e!~s->!`>O-g!iqfLATJnF+>}vD<|HR|{I^Oqd z-ZQhiGdml_sz?<32kdC}G3*%j3F?HizeA5;A3%>}AEAzmH4!h$df8v$3uW)a7smdE zdM@@hbT@kkx`(}sdRF#ll(Dfle8{k~^~i9rH<96FZy|$WBWykUKh$B^bI`TydFU+r1M0}prV3?M?6>f# z*|UeUj}~g!?@&?Bo?DD-?rQDF&}l z3|^%eyjm-J9c2(a49q76uTl(Nt&{x)8UIi43dP`6iovTDgI6gAuhzs~LHU1!SIe;% zVT-{lWZ3Vai@_@tgNFz;3SOZQyrY$xOd)u+V(==(;MFSGGpHyAuTTtLr5LR3{HwDJn8M(SskwMXf#Y-H`x!VlRfc($|AwMT2KQKy=9B5ytG zLf!|g8+n^p5As&A7Wh}QR`}PjHu%^1SUWNnqqFx>@qN~S0?Sz=3ansFD6ojje`i-H&aP6NUF{~;i#&05h2rcg#o5)aV;%7S zpUD-9ldBXbSG$rmw-XCsc7@{XD#h8=@~j^Ie`i-ai&elDXID9oWuU*q(n!VG6^gT~ zEFH;nY9>S>xy(k?9u4O^shC4_SK|Kl4frbI2$CLs$p9unyWH zOv3Z-X);pR&>%eUel(=`Bz(y2u!Se&OUVcwOGfG5z|hACY2_5;?ncf#kb5C(eQ(f5 z>5*cI(f^hZa)bxyzoH9w0 zREXuaX-MC-MUsVR?maa3o^295i*1vUF0uKLF0@r3U1Xbpw8}OS>ASWuNaxzdBAsU& zhjhMeJkkZWw~(aj8?T6t!gn^)nc@&#b{NF(W(}sRV_xV`u{|$ zT8vh;7%k)=PV`FKOr&D8s+S6JqIn@s^fDn#G@^w#(aUWJEd76@g*efRg*eemgdo!k zg*efRggDVvLM%}paiZr6aiZr5aiZr7aiSLpai#x@7UD$D7UD$D5#oy+#EG6Ego%!t zDFlc7@a{EEh8-W!5_q(Anf?fx?Cusi$%upa#W zKG3)N`$FI5FU6cU`Bh|-zX$Bi{$8-R_2D9mDt`x5TIO#9{e6F1=*#^t zq0(G`9H~OXOHg5nza{)j{jK2V{jJehl^+L+%J=;F&=>m)QFj5#E%4{SUg&QQdyzj6 zb>{lBpwII+gFfG%jXE>X=4_PhINRS8&N=>cIN$YWpyCXF3iO%&CeUa3(@=3L3QqMW zz@Fw$f<4`zj5<^NvC!Y~$3d_3$D_{cBYAfSV-{jsDaN#Rhkqc-h%v3*>hA|#jA>yY zrafVcF)b8hS}De~P>gA%7*j;9QA{hvnAWcMcSAifrnPJQouG>`Eeyo89b!t0F)b8h zS}De~P>gA%7}MJSC#IEROlx_65gHR?TKk^A0J<2{!az)OVT&;>6l01lgP0bIF|8D1 zioi9FX{8v`+IRh#s3^v?cBVfSx){^KKui;1i!m(}V_GT3v`~y`r5Mv%#B_?8G1r_m zGVLe{egvil%eJ4O?GP5QrRIGz(}uD%;2*xfKxB^pQPZ}JrsNk!6UT)Fe!rb@`X(Hv zDMDXrXy`W%Kh~rc>~BmxhJ~6@nI9*U(S|A9KM; zm4iul zyMRxH>KM?_a02;LAWuSCFsQE~4XD4{2W1+Ry^!cf zjdoJGnE?$A8<3|)-3|zO3S>LTwHq56+GAl7pBj9JwTnN{7;FcjlGv!S73 z9^^|OpkWl=ngaPYwrdke_+RbS&_GYZ>h@Z2uQ>zYM_f`8QE$*;x?Qlm(h6WePc_9t3qpS@LDS{vQYWO=rN@2IOFr+1RdFX#j$Ttqt$x=Iskuw1HYdAWfo)qQg zLOLOnrLap6(@kWM0}lJ>GlGH!n-co;VY)r;UBS!<{l#H=3qJgj_B%{pgDi#YJNj^k zCOEW2W93560$m%~Ej}JDyyB2vz{ez5`LK?>8jxSr3_rXh1qURW?8Ksmb!1;a9*jL0 zP|hA7ewf3@!xwg-ap-PfX=F!QYd-WcH)!4;&V0fTiD0t$T@g%5&Fu*0p2W8c4;#ee zoAH=m2N_68h?;2d3ACNq2ut1~D&iteGM|(Yjtn57paaP!3KB-Tl6IsADJO269m0u; zc!-ynNi&j5x|7#POVWz8CPiquf)wH-v{7UP=}2avejCD&OcF_6CRQ?&boP<9D3n27 z5k?{Yn~iin{)UiV(7F)e$(OkOg}hBdAm0~OI2Cb63ZXh{O@N__euSSyzc!uCeGMXfczEX5I zk@(1D5>N15X8apR=8;%3mZXxou-+wcB$%#+?SqvzK^dbH5+__THe_(&ab^sZeIuOs(c#RBl3~~;t95ipxfCoQ|9<$z z;Wvl>GW^bP#2deENk$zTg)gR#mXB7BR*%LWYai_x?HuhI?H=83bcfL$NB0?x?;wtm zjWLXg8WS@nZcNIUreiY3Tpv?E2H)%%n>!X?6&crlT;Fm1#^Hwl@z@3xLo4Q0ET~vl z@pHwEidz-GRNSe!TY*2giRARPw-Fpasru{KCx)xq>0!p&q)KnXoR#M|4%%#T0$touT5aQ zuJ2vMs|@W-sSwIbt^kP?AyQ(w?&B{fFq!_(QDx^m-h!Sf0&zON^$6pwn^!3Nk>01w>Q+=#qG(KDY67wjgcB0W* ziQ*1T>S>1W_YUKtCi9ctVJw+SL}oI%k3mwTM2>I9OQLUKhV*I-axcBqj)w+F-FQN4 ziB$6d-{q&a!yiT^@?W71;}2-IP-;ZqeI@brco8Kv^M4DCAHo|=#+5k``PU?Ekh531 z;BOj7hv^8VgdE2#YN=zG?^8;hiN{3=#8zudpAeZij zGH&`b!k0vqW83>B6Zi&<+#9yKKMQ9Fztg?iV>2=p^n zdX~dFk*r4#_2lpv_^F*p>m_?`W5Bc%{YTN2D8HP3{~VSh{R$>kEFF*a@RTU_(&Qak zDvPxt=_hPT$!Me%l6&aBlFD96lNr?axoAj6rlPYs(&va*J=H4%>w}&gftQ4|6`S)Z zU8z8%q^%G&w=@y^#7Id$L@i3XmRC$?!hK%!W|!^3JjTc#VFueJEwCw)=;w&YYH}9i zjg$WT7gmh)DB?0hx((?G*^gBF?y*>&g0?_C*AUR2PfX|@(B3n@j#h-+jS&YhLqU5Z zV`wnf^6RgNiQqfydpZ_NNl<@%itApYmU*)^R=MDdx_=o16SUZ036`Ru^|;X^*z|{M z59|;4+TF>)oP^v5V?J9O&`v&^hh0$cO}$ZcGoX$8Q;GE{_}br>VZEM^K{fr+kx!h# zMEYL)rFU>j8)7PhHLmUdJvf4jn1MlGWb-k>@r}AUJ*aj2B|BL6oBiS62l`lg{lnl$ zH)Q1Y2>AL>{x&$iMK`(y+gtL>FrhC0?F@W!-vb*>7~tBQ9|W6Te|>+jnYHG%=0NU_ zn|p$}SN`k~oYIzc{emI-@-N&8B(^f;@n1OV2<v9ahj8%v zjX)nh%6dl@jHT<2I;f5C?OzwDa?Ex*CZOH9`F(H*3vzRV8+O5+4#66#TQ`Eax0>JW z8L07X2Gc8`z5dU9tX5$NmR~-=8WuEpo)fwJt(gq_QpT-6g0*$^Rva^g{E?3~1SfFv zx(mCv;9DBgvUx!BK2T#J3qCsht-OHN=C9Fwi&;#w*JJ;T4Mw3^ed}OzhJ!Y70sKfM zKk1M4EmVy>m>Bfc-dPfy$oeUpg4%a?-p8?9D0k}S(cthtkJ%p_K-XI`{>m)I5jy<( z_24G#RzD!vng4HhaK6j#7x0eROtSX*^#Q?2gxtQ)zdD;~>AP|JMlco`4H?0;`}p?b zppT9-wFq=y_u$*$T>tm}!Gi(c8@fS`fcDO<$Y6~>rta7f@TK4WBe*(~E%Soi9(m{E zcLKRv@2BI?av}mV8~l5*phCO)cXn{dqa0zDfX`Pyl5akTalG{PtuetqV(xww43F)m zEx4FUEW3kaX?^?CVD7?O-GVhtV}Hf*O6bCRV+Fr%4iowM#Oo7-A*-oh6AW2GpPb4- zu{YPN4h6K=Zp(3m5~`-&I2xSBxj%M24*1%ve;f?P{a+6S_k?bTEI$Ntr{8YIC%(%# zBvk!Vz8#d;uiu{(+}a$oTOTN8y!Rwn^QZd%1xM1-IV?Yrd;Rg>e+RTBw~K=#x!dP) zaGaf-*Wv=X>faUxTh6@a;!g>^PP*HRzw<5*GE4r#t%2ekI>&Dbwwqu$>kRm2Ke`)S z2L7?2uyTe`_Rqy(OT0+0W!ExTNzb(us z3&G3n_ubS$l|}bTf=9Q(zkLuK@=w2?2zK(U{;yyN*4$0ukI!WyC425o}lHnHNR3>QDy>Iyz^B6~$`44Mw!Vz3Ur+pV(ipkeI2DkO>2knE)c=dy_ zpr(3uEjX{JUycSB%-w!>`vqEA@Y|>Shw~7`t$*ePH^j#eHUz6y|8^=kgWi9<9~@J! zM{|SWeDuH^9Lm0j`+_xEJsKU%o%-v@;Aa2m&WPZOl|JTz2aR`?Ei{1v^t$_!5Lh2( zG&aH$oJYpPwZW#p{OxgY2<@I;3~qt;H#EWBVy7X|6=>%8z2m{$7SHU#9sO8=vow(V z?5~Jt0qx0?CV0Io#INp&C8+I{@0Igs<}(T1-hG2Y4MOT9r1ZPt0sS8Iv2O?TYAGqz zj|k`;;n%+v(8Hi#8z0bT;BjzGItBDyD8H3|WdUA|l?SWMK+zAz1+wQre>2#`eCXSP zWgbKSacrQ>KImTs{qxb>JpQ8vOpeVJY~o8BRa!D6P;?No6L{G|CcKF!IQZ>wtt<~@ zpQK3Xa(O^cg8$Vq0ew02)_f0Ci{yi@C=|UmGLSt4df`nX#^odi(}G{OkXb-w@=X@u znEPq4@n*Zx5kE|D4GLELr#)3Vo!_#E$!XFg7@OOu-#<8V8r{ZB2=s9XdU{1b?+Lvx zA5z7HYkLJ}VaE{on$}fJKiW4tmMrvH$&^Yk7y1HF1(ty6@GXO!1^F(R4_3fl1i2b= z9oPUi0YCTmh#ucfozIAHD~We}F&1-{3i*;>bcNP{jGjLN$~_ zD5!a z&|e0fK{wFdhvXD8dO`LD{lP%+Iv4_mf#F~j7z^G86TunO5$aZg>0lOk7t9BXz+%9I zM!;3;?p@F~YaNj$c{9Eu7sKnDyU1YAKyE2IOsK`6Ke-whA} zJsQM;M34f~KnA#l{AQ53par-C`yMES-WvQ4{SjyjyKLfrlUwzQ>S%fqy^)NP#a6WPoNM7tjg#%~j9> zx)M?YSfB?cU;%dEN+3lGy->nIAquw!Z9zNG5xfGrf+*y_3K<7qyb@YGsN}Q5d@sJs)pMrD5xJLQO zIIp}co;L#d3Y4!EPh$bQb1~&BjqLDWgXhrLvqlYmYobaM2mhDfwFyL&3-8K`>|r!j#S!PDR@bhJ_a%Spp{&6@l^5PjeP@oK%Kl9yIF!31^QP=lhS$_?Re$uEYg^l`ycwP-uy^lCP8N=^g z!MLf;{P!!E9A748E%0JG(_W)WKaBX#K9A;`i|4{X6Z_HRtBtz22zy*3d%u?|6?oC! z21M4EP3TC5R56He? z0C)q;gKrq*NWfoR%gkaH!4sN9Iz<6~^g71NEPWFJLM>v%ox1CFFu%;a>^44*U*%Bji@F6YK%| zz-It=-gNo{`G0|DfN~PA415XyY49CT!N!dMowSgbA+G@`!Fm1@*%9grPCR{V*b2zJ ziHu=NQaZEK1_o$rI&EM}KXDxUiy`6>t=1COf=qrz76r+Vv>Yq&nT?zGQ@ zr|#jp?Ls@i)rf^&5DsvOX<;l#0LdT~;F8nAEP$&^3-dq$Xa(8;T+&+D0np?Lm5SR~ z92Dsk2IT0p7x{=1Mtu|GUX6iu!nLdrR3P_(!>rsh|Uds|vl5yCxCT1x6l>hLyDvW@Ci z*Da`Zt?ODd)y++%6sxc9zIyWNEMDv74pI#Nm6yBkVCow1rgMwr3cFr%_Q2T#A;)M~ z>m}MNH2-}l*UUodX4U1^O{jZxIik*SrM%8smr}R+N^DJJ7&ld_ICA;Syer@{-_?57hPN z(*)Pgbv0K4&3u0)mG2+N^_Nv%3cGxkKOV=$YWvq^26NxKa-Dw`$7MR+zY=yeP@(W@ z*;NVoSmmc?Xb)ZLdzs~5jz{&mml7^t;`hX(@!#u?1*<>2(wYA`o_k%k^isyz$zSQ-ylz%4?y>Gi*9UShLb(8p-L@v6Z^%Y@e zvo8y&;i{0167^A{-sQ@R@$86P5_KmsFwRdd?+7+?zV2P#lf>;&?vUe<5BPIQT#S+; z71S67me-k2%}(VV_5v4!2r~vNfw=t3u?`~!d@k7$%%NZ~{KBSHHQ(N;)Lh<3d6 zB&3zhm28%#@tJ8{qK{URPBNedI$!`Hzz$p>3`Bzj(8onelOdae98d__fMW16C;`1d z8F(F(0~&+s!TSAUlz3+cD58))#2{p3CH*~p-zFgvPJ#$5T!tBajf~Ua3E)%GxTZdZ z3PS@rU;-9k2ObaxqCo;^0)*i;g=_}$K^xE>ybQVm{2F$rQt&z$3Pu4bhVdl^LL`3w z0t{~zhP)e4xC<+Z4CJTYGEd^Isa!s_o$ry#b@6?SN)pV8VS2EisPGO2oB$Mgra1$N z>vz&(92Z8Rf$=~n`%Ke=y?}Z*zyt6iCJoRzwO|M8Oh);sK!!T+YK2;Bw4rLenE}xt zPK^ilC-RADT&6Dt_0m8VXb$p0OVAdy1D!xO&=UxO=>u5?27}>X3>Xh4foWhiSO}JY zmEgq~*Q4K^K$zb_$lzF${GVW;2f&MhCz1J0u;97>3i@kN<`}PU%7r(tKzt^FDPSs? z0cHapaY#h53NRDA3*G~VzzHBMYy&?6P54Y2tO4Kz9uNUyK@w;JGC*@s2wnmm0e(7L z+|F2T;&%QKqq~VIUPIH@gU$EW1oj)84A9+5y02(IqK5|kSchIE8N(aXIlb!`mgip} z3YEVGPk{slWI)9iq;u_jRu#GG0B#TpA^@Isr8=qd`*Gh=rzSvxqmEy-S5^E!QW^<6 z8P#8u$$*^$Xq4HDIzpMOM)`e^S0?5QE%^N<(6Q!U)bHsphhtQu0mQ!oJZc|3~O^ z8s+~E`!RSiw>L1z+pwz|m7%qG6$)OIc{72O{*5v##4^6XmNcS(wNat9SMYP|us1Yn zC<1nDqcRoENU00h26VRflknm zHOe0ddoXx0n?Et*{;*FsDl-E1*hXavvq)(<>>nDHsf0ZfVEYF~IswMjMg`u3y$rm_ zp9}p~qx=o9w=~LM1^u^1`FmjRYm~no`k#&RkHP**i|zlSgU{d~c-8e{$N2&FCGa92 zzr)^1*(m=O>|Yz@{{)?Dl>Zm(=Z*3oK({u^SF<=7vA+L0kig+>RKOwF;Kcw;&|@0q zC%|sfC_fT7`QraCh&Jh2Wsf0bVQJK%s)&$sH8b)2YBI#jV6Z0O1UOxK6F^gt3tEAf!C;JcD0mBu1Y^K>&>eaG!7Au$fsFhm z+rk`XjxlGM^{}^rJ>U>H&Y#NRGGji+(SJ-hDb=9=w{UvF%Mh;<^Bja`J_ct{)Q^9B zOfGkXYQq1T%e8bZloPK4Zw6EnJV*)eRRn~)vGAX$YC1IMx~OYDXv3)~es^1LGv%-8 z^%D28B;2U!Nw>p&ffCY}Hfjdq;!`_RpvenAH2ze3?o~}=M{)=573w_yP6zH3HLvF7 zj@&RQwT?g7nQH;@tTXow^$LHg3m5P6si&!DtE<$@)oaw7)Z5g%)t{<2sSc^XP=BR9 zqyAofQGG>yOZ}_*f%=L1AGJ)Q)tEINO^hZ{ld8$oW$w4$W@O0nK5}G0j(+bMzU__Zmz=_{U#(nOoBCvZzz! zBHVKFqv9t8qfAg{D{lsi%_fUbf+F`|)1*O@BV2)ly$&`-q>PZN#WaNP*NuCPQt?N- zapR~czIh2}q4I0Gm2e8G)Bj|oQysVi?tzD(NKR57L;eN+0oX>k3$u*0qxw_Ai*q*| zVIUgsA#)*HfnGq^y6-XaX)dNMP81l;2qo^FObcLvL)fYdYC3f17D;Qa_2$atH5~?U zsnVLMuXFn)HN|go-_v|dId`+h`WDw9;k~0cM@_3y+yjblF@`%>!;ZzcPomapU7RLz-cZoH+YbRXwcE85`p#&R;O=B*>#QA(N27Nr&Us34_i8V`K%);a zYMF&(F&SoEMplxw-nC={vGap9dROsv&zJVAGi9gT zR2UUS#ZkMxNmMFTmExV$Y%hWPfW{{a>9T>ZezpB;SqfRQC=sU|wWIbhlWo5E0 zjzO{^vU1r-*;rYHY_hCUHbXXy%#rb4R_|qcFsEf_W#{o7i;FV8J*&6+jB<yJYJqGPm^cLbL4sQLV1zASl&_ISzaQ)sOTv#m3MOvl3zBhln#-X%SX!lGh^i+ zS}Wv}<(2Xo@;UPP@+$dK`EvPcd9{3_+%MlQA0XK!|A5^qFXr~i56SN-mOE(dtx2l||*zI8+{0xGGu|uS!;>sWMgbq&cdVs+Ux+s9sg|QuR~y_6|_Jt~w@p zQ}vc=lxm!+LN!@cshXjhqnfX(;wLmUXmwY;+g0iMU3fXTPjyIjoL|7{EjhPTPgH-a zR=FBf6176DQR~!3wMFf4IMnaSJ?e0Ev^rj$tWH&DsI%31>Oysqx>(&&-C13t?!*7w z)KK7C%+#xItMB1x{#gAE_f-8{y*rfF$TeyWs~JNZaBR10+?re7Fin&uPLqUVe1>MI zH(QghX{%|k>7?n3BYkg8DUS7TXohNrYewT}KT#9mn4+1cnWc%<&DB(CqS&7rsrg5PpUd$| z870Fo_?9d_t#vYTyO)W;2h0gf3e%KX?8;(tnNLFVnO00&rakk8r4!SY>CW_KN|}Mo z8_ZBdFw^kS^;~8l^B%)9A8A)GYnb)SCdRizzLnX*e9Y7^`ByFlTLz}J5(-vr7(!Q+iqJ33csvW3(LpxMETsvAjUOQ1cMQis=)6UY) z^=TJs7i*VkS8CU4zYE!*-K^cF-KpKJ{X}~}`?>ar_Jp>F?rZIDrZd`e+6&rC+AG?h zwV9@y+B@3&+K1XF+P}38S_!LQHLQ*`vv#(F)6IslQEVLhwl0ZHWlwlA*lbqm$YTrH z-_1pAG5e;cBiorRVdr{#vVB=!89Rs_!j`im*|BT|JDIIyr?a!!dF&!~3A=}TpIygp zVLxK`usZ5f_8@zhJ<1;BzGP3a-?HDcKeCtEpV;>u3%xhkU)W#S-`PLdEbm|JGnUfH zbV?ng<8&t7V()o}Rp-=sbrHH4-PcTlE=AW=m!-?qoni8O=vwL8>e}l%>ALE=>yFdQ zyuEd0x;J#?y3xAvx{10ex<9?sbhC7Gb>oZ+b&GX6`!d~1-CErS-Dcf3-BE6b?qgkz zZolp`-50v!y03KK=)TjXX@Ag3to+VQgVT44GjbNr!FjlF&Z&;(lDTxQ8P}XUY%bth zb1!jIop@Qqb>n()eYpPIBHe4;U~U*Uf*Z|^=O%JfxM^HR)hw<~IhR|=E#{VSE4j7Y z25yvRGq;V~$^FRf=04#LaG!HWxXauL?rZK0caFQjUE;2C*STBVT_5)w_lWzG`v+gk zlj=K4m3l_c=}mg8z8&k-N9g19Df)G~rur=Xd#+r4D}8%?SAB2&K>bktX#GU}H2qxt zV*N_}2K_euZv6rM5&hTtbNWmA>-xL;NBV#CQUhZ!8Jva)LxLgAkZmY1v^BhJC^7Ug z3^crH7-@Lh;G1HYX;^4@-%xGXYN#=MVfe=IKf_JKLqmf>W3(8881+_e{%8>r9({rX8lerh}$qrf*CaOh1`!n;x3} zG08&oA+C_vko1s(kPaa|LS73Q5i&l+7cwJcUdZB*RUunK_JkY`ITdm~U2~QBecYY9#k|YB z-+aXEJ7qp^zGl8-eq{dJOj}eI&SJ5+Ezy<~OEXKMrJbd#rH^HhWu#@YWsYU3rP{L9 zvfFada?Enda?bKU%Qed{mftK-EYB?pD{Bq0x~$pV}o3#u4IhJABcOG)D_Z zk)wm7tD~2r%rV$8-0`-f(($fiv165EgX2TTUdLyS6OL~k7ac!2ZaMBd9y^{nq)v^~ z;Iuit&S+7G1KYtK<E*Hh(L>RIkt?OEsfz_Z1(&9l?9+w+O%fai11 z7oOvuuRPy)+IsopF+zL2nxq>${8qL9uZ`BdMKAt5bfV?!!K=7%f~*%-1bZ2^FmOM+brNmNZDYy8dQfoPFX(GR98H)Eyx$^#0 zXL%oaJ$50r)ocy3CRy$JY-^FVuXTuZthLfQ-@4qo(Ygz}%t`Bc>ow~=>r?AEN^UdQ z+_pGdhON-n(bm&8$Tre8**3>kNiDTi+oJT_ZToE7xns7owmKW_v(KYgyWJjzoh8p+ zY%j5w*~{$}*hQ-BtL=XKUVE+mwEd#}f&IBXRlkf{w6rbqsNg z#a=PrvD~rIvCA=8e#mjsao%ywv7Nf-$kRV{$gw}ToyGb%XNI%T+0!}5Inp`VImfva z`v8B-u6Ozt%S&8ku5wq2evQ1sHN#cq^2=Ad{I0#OT34CATwkF-jpbeMdfmK4B>pntNy63xB>pz!&LwzMbFTY4FcW-p>avyS^bf0%$ z!}5OWmU|4Imc;Fe^JI7mJsmwgv7SfzJd-_hu!O5U+daoTXFYYE+n&cB+RJ+F-Y9RX zH_uz_E%A0FW!`dcg?EPc236%h%xw@<>CZ zJJLtTMP@`6Ms|$s8969&WTaF*IdV?q(#Yz_?UDN;k45I-1$3nPY-C;J?a0TGbQBw9 zk9vzlMeX2Hql%+SqROJmqbj1F>(z!CQB_f^qs)fAQMFO8s82`v>Z2Y+&7q%1siT+B zYiV11qlW%T^$<4qcZ>0}K&r+X^J|BH8 z`d;+YXnD*`lOe_(6Bm;avzJ~-w&25;!kCURJ!1yNtRN#}CdWKzGbd(gOm)omnD5p5 zVvfa}jj4;d9dk$h*r(n|9>>tJVFotV9vc;#8k-ke99tG!9$OJRBep7bb*w*jZ)|Ps z>DY_0^|9(Wb6i+lQe1XiQC#OZO4B!PNZi=C%D5!M{J7#s5HO#23bQjPDum`-lvR9~nP6eop+-`0Ds; z^!E6D@dwDU__OhK@wek2$I}UHf;}NBAvGZ{p*W!=p)BD&Q+Yx~!iLsi1+gts*Q zguMy138xb-Ce$Z9NO+#0PTZ)e&`j5u6T=dd60;MF5<4gMO&pT=k!EaSW#atA8z%+!JQPBl#x`J)G_I@rf1Tiq>)LJ zllmIwBrQ#lv2(0O&O9h zHl;FUe#*v_T`BvxecYjxlPTv@uBF^dd78qC&c`Np;ihP4_iD*7R)CLx#Ghx0^n0N~g2w z_VoTtRC;Q9UV3qQNqSj&dHO_2Mf!~Ns`SjA#;%M*87DJ*Rg&`=*D^k3?qxj9kY^e)k1!_< z?#%OsxXg^q!pv`&j+s3(2W5`ToSZo)b7^LE=Jw2ena48EW?p3KGH+)-&b-0US!|X) z>o+DUtICv`^*583Rh(6lRhG3%Ql3?jCD&dv%*eWDsLJ|SvKfD?vz{8{Mt|1cEQ9f4 zR=qFlLDusubu)7_w=t|)QnT!4amJ!%ottGC`!+iy8PaTQv&v@kn=Nm)vDvO>g~mh8 z8rnFuC!5_O=bK$?cCVRF`?Q%n+mP+fj?2!-F3j$kJt%u*_T=n2+11%gv$tpO%bue> zmVGw6F8g-&my*ZXbj~>mn`6(3%1O=1!^Nv$@$!Cx%YCP=E|EBsiC>Mc}8=Mw6J-{<~^GaYW|#zY(Ba9oaTd! zOPg2wnwzEDo9}CWtohmIb+yPf|ypDthv>;-0B zRKap-YC&E>alv+DNkLgbdBF$Lih_N{83hTts)E%8{(`*)wFQ~F(*+j`>I)teJTFie znv)B|3X=-63yTVm89NvDEgVvK)>x<;TUc2*zi@fs#=@P_{rKykt1}*x?kYS~$jig^ zxju8~YuRWd1a?`pr)6kTvx9Dntoen zS9_>BZoZ*F*+xFuc$=#rxP5@iBI}v)%1Wx6>`Td1<$Prc^O~YI>lV2|oi7|mJ+RpI zdHT-QEV5PmK$>AJQ+9OL+q-v@zOUS%yer?K9In`>T+dBVTq~TR(9@~f(^*!<5voN0 zDMgzq^kw=geZ8I`aZD#Rol0lA>)wzL(3QmYV)5ERc9B}7D3$HtDw({f+qPmoZ;#M7 zef3`9K}B=zM)D1H5+}~X+8!iI@1sAKC}mFzPblWl!kO)vw58^m&L6d&3nLp1Z;}1F zW4dp3mvlFEpQ)eg&4%9U?-bMV+4Bx=tgJaLoW*MSO%Zxew?gAbIJvqsk=33JuZyqco_p*)T&}ZJw&g@UE&xwa=*6 z4l#^1bT&NDSK=9vcHR-GclnkZI+AF5kfe*YNPWlAS7ACW>uIdbxo21knu;rwGT9IHTH7Y`QKBXB9(K=Omf9>TpHUG5b9K zl`y73#u8IUBdeFwUu%ER{vmltMjB`6KW3A#TP#(rQ*Be3sA*WsqqPg9GWl}p2hw9k z+%ZQTYO&9=)R<%#AU$hL)t5`lO(#P;5);+e@F4bi?0RZ>$}#CVHbfam)frc-y~<0{ zdDLTLt?D_s7}c6}8_#l_{FG{|k2Xug-13&U@uuC-(nGmh#_ zm3}LorW@Jvv2k+C1-dycm$tklt!}x!<-V53TApoL*YbAD$1UkrY^&uud#k8csjc!_ z6}KvBRo1G!RYj{At*TnBZsl*aw^ePc)2%MHs&Dn6)$>;B*5=k9=)zhjwa#u`)H=I! z>%Ogrv>w~Kvi1Ddv}t+kow|*!vB6BcT1Vk?gFMs8*5_MaYh7%**Sf^?w6(m*P~ieRh!jq{B6qlH>35j zrV7*EHnnX|x4GDc=jz)CS7C6KE&l^^s52&Tb!{%L!`Nd2YztmpORV`dT0bew*iw8X zL^-aC2p7@b!u65YY9(FttW03LRfikq8+^f3QQl|H=XOU}2w-?_uWo`#+cRluD` zIjI7w71f3+rew}ZYN@t;_&FwAJy$LC;G|-xRP|ZM8r)+PuXcY{ZJ<`T;GZa_b$GmODpBCwNGjP!3F%uZAw9}BU##yIo#@Gzf9@p8#oAetQL+}E zZHkA^6N(nH)y|2M2a2v5sq0&(!}ALN>0-UB#SZ5+=OCFzHe0$uyHJru$4I&>@RR|v zRMK8HJXFv&OIm5Wiu0T36nbeZj&`)TQ9}5TCHlzLnW2Ml={Z$CQCcnkQWb#KlOY}>8O~f2UATVOuRP1o7 z7;e9qhKb=BCD#5_Z1o41&|9cjewx@@OR=3NO2I!_3}iRavrDY?Gwy^KCJTBH*Bi1=7_K`34`o6fy9*O=1d=p;p!lNu+=Jl2) zTg9BkVyVaChAI>{RI%7ndbrSgU^6}vOMb-jygruthChtj+Dvgav&4EA_#3=FT+v&c z!t15Hk4`BBUCy{M0CzFCH@sqgq--`6+M_i%01ozh9j5bBifAS)p~ z_~hk!3)O>fvs@qje{#ohLaqq4*GL z7Izc=9F1iKq^w)2_m^l7O-dBSzVBog5^AT)T2n=Vu@AwPMVIRyVkOu9t~ZZwg#2oda` zJp9Ua`i;2Vpk=i_n(D>BS*=f&{f#v+lV4MAWw}=V!L6Omv zgsj78wFutdF0B~691_9y!K|`WeQf-U_4){--5aVG8n}gl3j-ca)$#||>tkg(aHR8h z*6ZV`R=j0{z6<@Ahab8@pV4g%*8hIYPeAy~!;%&Lim)gbU}$fG#&Q7i1>c+4szUgl z8St3A&}RmrTfsj`$m5@E(0@g_`7b`ukD}Z)nH%*+iWDz=fb4RdG5z1KmQ-qR&q)0nHXR|(2 z#Y0_!M%wWcHtSpKtahc`EI;bXmp@Vd;L5J~X0v`a<@+ccH?e{mZ~|NgSHVs298eLs zdk83i8fbwVB!Z?O2jqbQ&4&Vl%AOb{# zIFJZZKpOC6AZZ42K?~3dv;oDS1Ly?0fD+IH^alMv8F&r60p0}VU<4Qq#(@ej2}}V~ z!3;1P%moWT6<895?Y|7l3a}ci0~^35;0GUqo#11z7kmm1fX_fJI0{aHufS>WEjR~$ z02cus*4XJP_!-oLU%*{(A3OkmL}B|2H{bjjCEU_W$kU+w2NGXvA`FB`7N)h5q>N+x1zv1C)?u@T>&F{ZDHlH-ODx8`ueUgHM3a+W|-+ z{D&ZG!7*?W2<@DPJPU+@pNG5%>VQ!1T2NjO%6drro}-W4f^rx91|ESwfzZG|kpCZh z?-}02vA=)ss@bw6gC!%`a>Ye*M^?ezvMu+HO$oibV4L0v$Q>I9HL&!SK!WLnAbRgL z^w2v52)#pq=U!1;PvJ0|I~wXhA551Tlbacsw*2q=Rf9{K}dxyeR@-0J^1Z7;VRB2Poa3 zPS8Bijj?xUv?rsz8STqxe?|u~I+)Qfq4Z7+gO(eysRkp!Xh1h$Jd|$f*U-seI+zXU zxNo6!+dCzLLyH?%((%!G&e;&duUpc>E#M?mS!qo88}o#|`nWH24jX=X#|G;^Wz z0ex!FQ&ubiE5ItS7SL%nLbn1s-%dt%GrE^nEYV8rhlj4{AoK`0K}R5bn$fdR`bs|! zy$r5{Ti`Bu2%Z8u{YyrFWAqK9grB7`EGQ4qb=g2|fl-VN-BNm*3OS%7U7;R80q8hC zXb_+)Q8P9zG?cMLG8)Zj45M+3#xt78Xfl+pC)JN=HZ~K1944YUl&9qRT_!zWMi66DL-9Y=gcceK==m~YjM&73WIz{?&S)l- zE+8A)l(BumC_PsO9oGihfw6UAG>_2&M!PXe&!It=-yKTl?FsD*%*THqJaiWhhSC#i zlr!OK=m;>H36F=;6KPBZbRjdL^c)&=!SfldW8xM;mofg8(A9uWx1Q0BmN+A^8J_Ko zaW|uTp>%`xLw^Ft0A1nF(6fM!y9m7segSmBze4FWccBjforaz}<0W_l=rkFGlG(GHAOGqkfFib7j!>AShjy3aVvn zp-{SE;m~LhXJjHUHGnyBGBlm>XG5C;y09-8TPtWg#@30^E{x_eTEJ*GC|y}Gv?u7x z*atE?7#c<=_!6E{P{~BpK>4|# zrgRz=6Q^cW%V=XpLm3TcG?LM1Mq?O_gVG&AeYwW2DfROjJFU5|yEEpV(7sI4fsAc1 zbQohRXKa1+h0+x`K%D`8I6Kx0&=vSF>c?mRlzwojpjr?LB0&rwoe&XE zd!UJoCPV28sk7_s9NN%r#k><^+kg&CiY|=iF&_7bc;75ZaWjdo6)^cI&MD{vuv3C$Dltm{4&^vvf z(T7kv?lJT^WBZNKH;iI#ZgU*VH?vMVxlCF4RSyW&=Rx;Z9#j`33LH@pa66O-9azV2lN92 zz#uRL3;QYg0dN=`1;@cja1LAoSHUgt2s{U`z-#apkRC)S7gzy45C9RV2kZb|5tcRpPQV3t z051>1Sn3OpKL`YkfEs8)2nYiaAPVR~97q7kAQhy8Oppzlg65zlXbswe_MkJ!2Zf*r zlz<+f7wF?*L=VF=5PS(rK^3S0Bfw}d9()ZbFb&KAv%!3@04xNH!BVgs{0K~74cG#< zfgNBM*aP;1pTJRY(umDza28wum%tTp4cq`X!EJB{JOGctQ}6=(2HpbFlPKi@J`eyA zs0ZwT1T+9L-~?QN8}J6cAj%(`AfN&|5CXzMG>8FlARZ)wWRMCnKo)31&D(CL^e%$8 z1oQyCL0`}x3<5*IP++7UY`2pcN5eZFOafEDG%y3q2H${r;5$$U7J(&T1^5xH0qej< zuoY|vyTBf>9~=UQ!BKD=oCK%98E_6<@Wk168J?@)7jO&Q0r$Z}@B};qFTgAC8oUK; zFPz!{W6Y%jfHCIM`alAtKn|P%#+^&ufd^0mU*Hcg7G2s11bg9Z!=Q9&2nYj_AR5Gg zIFJaEK`KZGSs({A2VVdTSeLc|?LY_633LH@pa66O#h^Rr33`LRpg$N0hEQcY?fi}7 z;GF<4)?G@0DFEZ$r8B^6Fc-`N81gQ~kay`KuoSEWCa@l0xVv;a*a`N4eE>t=rH8;_ zfFbYFpTSvh9$W;M!Bvc@m)?NqCcwCN>3#4JVC=gTLLP0o)1koS{#DRE_2$Df6NC%l9 z8(=KHv>9jtS^ON&5v&=d3qeL;UP2n^9~ZWV>^bZO?B(n=>@Dm)?8EF+>`UgfboyV|_u0?c1c$>B za>N`thqife{5V0J#++y)C(&$(=cIF*aK7NQL5OypIYsdG;S4c{D>x&~I)O9Itlw}J zGWHeFwVchI-RAId)**{>&YATJewt`0?}1rga0o6P=HT`Dah8ND=Ndh@eq1#-oEvM7 zNH%Y?%-dY9orq5Ch^?DQY4%$R`*7RZoc2S@U-x(Bb~gT=z3_J*T|h4;)b|h8A?8p8 zwj<5H3Eaut+1&Z?F9JV+)n+?g!ba{+_z!T8bI)uDQq-=55>07T=YL zDPgoXql16XQEv8)uriLfnrb!Is?KVK)mp2qFz&Vb$?Bxl1*;oY_pF{-y@kcfx}LSn z+RfV1FeQF!8Dg!sPO{FjZeiWtI^Vj7^#E(5^>FJk)|B-uCe^>*i4S7`)BXP->mT~% z@7j%jH{*XP-~aaiztoX`^KAIv@&B70_g^|}|2zNxOC9+)599yN|G(LB|E0rbIsd=2 zUTS`DnwZDudPcWc@BZL{ddT`DLg%b6nQd#hH$R9w&bnj$#QM3}^5%E@LQW%*N)(JA zuxmtGkwNt5knvwd{vX5{|3%yW52_a#Ki@U~7uBncpFJ}FC)JCLpF1)BZ>rZAKXYEb zt3JZ~wIfZGBU;631^%A<{Kfje```Z`?th;@KA(@_@oo81zAInB58^lG;|C@DOn!5I zJANL&JHJ2wIBOVYTN%xt#GlEZ&tJ&@fxnKwjlYk7lz#@lQN7K7gjrVDm|w*xv2nt* zD)gi(AvSuOB%3Uo7B=l|@@;z946rfU47V9$Lz(BzT59w8ru?;-FEF#pK+Kf#To8&W zOjZgX3!4gj3New$x1y^eFWWA*-`ZZa^{UsUUQw$pRn~X+vZ_=jqiq?qDuWFF-Gd&?8Xlt#jj(+8$YuDwO#&GD*AD^ z!)DJ1g&qFD|EJ`C>)hOJebnZR%@uRGw=oaSan>61pukJ`aJj$!phC9bIIDtVBd~+M zcaG+ei@^I2RzFOYL#H{)&!icR1t#XpZmD`uF&_{41cKnz5R2( zB&5&yw|V}f+ghrSXV7Mv1X65JFV!^A>YF{#i=Oe*x7fP=||KHZE@S^uN` z|5O2gw$*25aPp151 z`G0%=YlY*4Q-pJb3xvysYcO--9^ql(Dd8pIP2mIK3n3}ue`a{_kJTTK_~0`8DC*zD z{K%rlhDf?l^*f5~sSeA%ZA|J`;sPNRQy`X8?$g9JRP<(VSL)zAu^*jANV%7YIaE-- z*oU$mDRwpW%@=nfP0~WKjhc?PrDl&5YfZPNiJOvCyBXs7w2?#29x4%2;WNb#Db)~3 zeQIZSu@@cTU|Kdy%)`8m7|XV}Ngc&rrmIWEjs;{M^+qOnN%b^{?TsIgK2Pq4f8p2z z_Wqezj84;hkAG;&#^m(wO%eEwAUa@{eb+RC$jc=NXV@0QO#i71dn5FvutqSOd@kHd zy0Mk)M(hxF5<83Cg592-&+cJl4`3VdJIFC?iam?{9eXL;#NNc-#@@$1!amKu%)Z5b z$bQLYacs;3(exm+niGy61*dXyIITDxIfb0=oc^4loGQ*&oQa$%oH?BDI7>Ovm~YR- z*~Hn#*~dA`Ib#l=;#}ff=iK2uK!|qFF|r)ZHd=G*L!#LZTz9kja8+gv<;F1fBxn}5 z8Mm!D9L?@x)^3;wkIpxcTguq0%{rPp(X3N2%p1-Ap1YK5;%?&Z;vT}>cy#)6=B;IT z_c^z%(Keb*@T_^!?9R4D@PEqLBG%%hzx&=<{++t=T$wyxJU{r{C&V+U^Lr@V>^;J0o$pVm9LJsoVSLzg|`!P@13Bh-Q(Tm zJ;fw@JWQ}B#dLZKOr-a2gt?hjTdS^CC06~c23eIEt!k~tSxvE;W3|9)nbnV0(d-RY zJFE_1F1>S@M=zRv2Rs5FyH{3hYk{={bLo8?^Ph#W{~tSdbCF)=t=}h#S6lbQDHLv< zz@c3nlOl=Htlx9AF#A3pZ;ocqvYuzX$a*YYFz@%(gtQ+^wM7k)9nFaP5P|C8`1_Wz$0_aEmq{;B)_<0Af13IE&w zf7E^d_+b6-`2XWB{6`1yf9L-{>b`$`@cwuH|Kl$F3kNWP^CiF1{M;Oc?brMS&J@gl z@g4le#r%~YJW%N&muU7L{sFV?2>H!8ZU3$6ZH<5HsQuTfA7K1DC(VDadRyZ_ zTly;09I|K;<)9wzd3!vx-+9%+td ze>m2>(q_HQcANb+$865pT($Yt<}p@g;0S~Qdx5jSOAsK?3PQ~@c4uQuxdUeE?un7* zQbCPTFjg=b6LWu$Nx4@G{z{AgRhr*~zX_d1Z7`ShS&_4C8{6r&XKjU;-MOaT?t0c3 zo36nO$<}tsnAUhTrZ3iFlH&B~;^)G%Vxc5m@?6+Oa+xo-$+j7Z|F8Slt@?!Rh}mY1 z(T!w5xnQlpJYdawDKKH+dNa!yE)2)q|NqKu`e=`LG2y+x+mB#Ya!Uzku|UPYD(g>i zmP?DC&isAU{SW-_t+bDx?EH_GzZP1_Cxv{Woq0zcu@YxA`-u4o=_53sTK*sOKrs9c z+)xY=epqypIg}>M{e$%j%%4uDA{lFEMtOfoRrH6@`%+zbR>Ei_yU%B$-xvEn%aG5e zeV^t-pAqS+{$-wjd1-xE)I0x3%nbj||DWjB!s)_qg>}LeOzyQ7{q(JMt59scL)gIj zfbh8Roba0Pj_?=Z6QRBJbs-&d6)~@cmxU7RYeKF_Bx)dX5m}4siyTDmB7w*UujWHV zF`~cLxc_+n|5~x}s6HK&CAI!{&G_{Gf4Y*+q9Rcr(GXFEXyhl0ogkVf`bM-sw3vzg zQM5s{tgF|8)&Pujj~O!&G^h%;rr@w z{(lhokNwST|EY`nX9c+8-}^ZMXeBn7(|?nX9|GIDP;M@_i{cl&B-Tt?WYe-Ri391@I4wGDjVv*3 zgKUdzyDT$p59VDzEITf1p4KicFYT16{TlC%q$xR4(vyWM*F;NVS$}x||5&U91Ni+7 zlGPm7@?7h;BXYlYSmLnCVZ8(At;NyP{BYim&o!kdOIR$nYg1QKlT?WnN&BdVbHu*% zn7@>|I1&T>_Y1@hR<{F^{^NDJeF7@3_d3B$hd@bUY+o z?HD6l@0cvx?D(Z@yW?)yOQi=Ke{xKh9drEIF<;xL zOClph)PpQZRH~iaUhXJ&k>7N5mwU^7<$>}W(ks$n`IP56%qf5Pd6Ya>o*=&`O_68F zA4!{Fe)(6@mhvzeTh>-?C5w`Ekav|A%57vNcwh3fcN^2OY)K5s>LoWxb0mvMWB0T( z;{Iuu#KY2Vh^y1?h)1VA5>HBdA)c8=_v`$$WoaAJ4yK(?yPNhV%{Kkxusq!>U7a46 zo|E1wy?1(f`p4nX>66pvr>{!inSLVudivA!kHc?`Vy=Xh(O)jiAPIp)nz8=H`WNmQ z-P8KZzm%7QYWXO+PsZ1BHRA0gkr{~@{nIithNXQipNcv2>*O+ti^N;<1!74eP@9Jh#db#BdxJT16Y2X3KJC*xt;cDI>8PBN zoYI`KoN}F7VC{hRSUn)$smN)!quyz#w5zmG`ssL*XeC{V)3;Q1R2n1OEM(69pB&dp zk5Ns!iDj(rPL!%Z97JC>9O~>KaT{Y_r!SrC<&|jZ2&eH*C$Za8o#@NQ;^v?|>ru>? zGLqQs_^o+=esWyslrEzk-O?paE1lNE-T_^>mC?s&xO^@co#<)uEN+?kYNW5*cNSW2U}1!3PNXy6*$&GhG<5cL4t5T6 zj&)9PZh~bIIy$d+%yTZr(g^n*2RoNK*Eo-NE|pDo#&m=-d-*b4&;L4Jy4~F0sOaVU zO22ca_P3B+nG{8a(-Y$VIIxNJH;F96e%%!bMzDqBcAy|lEjLQ_4Z(J5*8HOz``&eB)mSK3}g4x+z`L1@Zj;;+|eO-fH!>|xTifa?smaZLL3tfBFcOB$f=347I z&UK3G9M=V|%c%veBre@rN$H-aZ)@)Ik8gveQp>T!>3|#CecT93u?j~A`QOQ-H=jv< zEMGVC8O2k(g}cST-$NYENpT|y$~8(7O2tG;@ZPtLgyWLsmWwqY%zaGvG{YswSot|e zJE~(Fi6ig6V{5neSUjwaVhU-(m zmW!QvyWF;U?DC)vwwJ87+3WF>$3?DVgOeWA*bb77tg{{{!NYjj+*=;gb7$w? z#T=VYJ-*HTK6g>>vfP!qt8-s@yumd1JWm0p!WVl=F&)0Erw69Q_wx+GwD{|DH|K88 z-JQEX_ov)rxj*Ng&Apg=HTRd?Uvuy0KF)of`&%y2l+)C@sStOY5YGrty{Ex5$urF} z%QM%rg=cHe_MV-ol=c!q(;=Ql&kE1so+CZScuw%7Jg0fi^8Cj0JI{rlOFe(^G5n&qJO^Jx_X`!Gw3TD{`xFyWn}n^M>bb&wHMaJcqbF^L*v`)|2fu65aKy zw>-3m*Sd@Y85c4hWUw(=)miU%jcZi!6(R-YW~bQYHpYFE`#Sdt?re`6Zo}OhC>#|oiiQerg|8w|5v
L(xRhOwm$d>6^BSj*6~|LPd$9m!h9ykYcE!Oi`s6sTilA6f+dx zC>AJ|Dt=U~Q*2S}QXEhmRh&{>P+U{oRy#p0a_`Md_^!P^z(fK#Vd) znW4;9HdD4#wo!Iac2O26iE9m+k*1Iok727R6b!ou|CN@xjwhEo@Mc}CE0C#l-c<{vDqa)gMFH0x6K}qJ=|xk&lI0g z*>ijr`pnK=<+IUeN%q?89of5l4q#=1(>@2Y&u8Dxev!>@V&BBAiMq*UA48KnK2Lmd znzU&`_!cx7+@z+-*G=X&;rp&^QqNcBYjpGV^;P+X`o{WZ__pvp(Bxv1M@`!L@^bQh zd-x9UE%SBE3CtPpn~*cgccSk!-#NbD`7ZYT!FP@CCf^;t`+N`kp7Ooqd(-!U?+ahj zkMC#a=jhka&(|;5FU&92FWE2Kueo0vzb;topr_veKcio@-&cMU{HFNL@|)+k#BZhF zTBF})zny;j{f_wk?04Spy5D`jr+&ZrvHY$5ZT;>2<^CR6@<8n$?jPfy=%4PN{k|9Sok{a5&}^Izh(-G9ITG5@pvSN$LOzwze;*aXxM zkOnvhcm((a1OaV8ctA`*Vn8~`3HTzw*gl{zpnE`HEQ?STFe+etz+^0p@NGa{Ku`Z= zSR7$JmPgndaK!JYfS&^{2HXg^AMhN@Bya+40_z7l1U3v*1_lOd0>c9JfeC?WflUHi z1hx;%59|>*AkY{%Ja7!w37-}CUEtC{Q{bk+U4e%JPX=BHyb*XW@LAwntd@Y=A(fja z8NjL+MBUHBKeF7_=|5k`pRWN!E%yt3UUk5$vuN6 zt?~}#%yCi%QDaAnqbGEppc-5AnNW@?;O^Z zAS%0?Bw2i=;ebX%8W|f^G+O7e&SQ8Zwg)x!nYTN&wVOmOZ{4twN}~!Uw9+bF9(zwggM!Qt|REJbYRVP(vR2NiNR5w(&RrgenRL@keRBu)6V5?w3 zaJ^tj@a=}OV5eZWV9#J>uzzr)U`=pHa73^^*btl)oEDrF{MIcuxJ7X5;P%0tgY$!n zf_nu2;MOO2K=6=Ys;)?)NUj&WFZfXK(cqK8XM!&TUkSbud`NXW_+Id%;Ag?Fg5L(S z)mCbOx}I91mZ_c8ZfZ}pQthv9q}HfI)Jq#hsP$@tI!SHvpn_iD;~`%$L~T@8sE4aZ zs>i4&s44X{^-0w%^*8G8)C<*1)jz0B>UHW(>TT*>>V4`%>bDJ#s!yuVs4u9msBfrm ztM92Fsh_D|so$#Enuvy08iA&sMxv2voHTA4PmNOJuW6*wXhJj*8okD#`L1D-=7)x9 znk-GOhCY}rjF8r5Z+lH=O}?f`(?iooGe9#$W7M?3tf&=;8*YvtsTpHdl9-^OG}AP* zG~Z~x(=601)%>6_Y1U~rX|`#0Y4&LjX^v`6YMk89Xf9~3Xl`h3Ywl?tX`X3bY2Iqs zS}UzUTTd&|x+r8?C#{>-=&21mNBcngMEk<1eXVV+VXH_TS2xRD65L+R z*NJp&t)0$F+d${2bJ2CyG}L+Pe071kU|qgOrwh|X>0)&Wx)fcAHbd7$H!HZAE=k)` z*H+h2*Hu@jYppHO4cGM2wb$-a_0x6M4$>8Ahw937Rk~W;SGqphA)0Zz0osYWA=)Xr zbsnl2x^Hv~bW3$V=uEnGx-Gh0x&ykyI`f4_Uu(&*3mt9s~2 zj6*{#t}LW1qzbMU?w;(ckgr0=Scy=#o68SddB zVao8p@YZ3PaJu@HVPVE_eK-}C5KdQ2ci1ab#La!zVQJy-x~mBaf8R?V_EC%Q4}0m) zeMI-q`<{8%D{aFqePRsxy+b;OmxT2RD+qtr7d^u1rkFy~u#a^0{la5IhlH1f4-cpJ zN`fxzjHdSp9~J&}`1J63;dSB5!&ilG2;UmMC;VXe@$l2(m%^`y-wA&h{v!NMI5)y3 z!Y)D@;S%8);TusF-Y7yB5fKsFI^u6;{Jo$dXizWaoUs&6pDBqEi4nL1hqh=~z1BIZRbj`$I7L&Ofa18~P9&cR)cxC8eD?sWt=k{c<4YY^!i z=^Yser;Ch=jE~HSYzEghvTI~XWZ%f4kyUVC!A*pl5jih%QRI)28zOf^9xz7IH>cx~ z=OV8~-idq?`5KNJC5&nig|DAcUQq#2x~RyggsAkWW>IaTx<-{m^^5v4sxoR+)Ynnd zqrQz=6tyyHebn}-{ZVaE)v>6vQP-k=jd~LG8eEIwL<^(sqn)F@qF+aOM;?y|h}K5G zzZud#D!M@I(KIqTJ~|`1MRdpLqUe6n#^~DU3DMJ{w?rRhQh$2%N{b0 zqNTpAzN5aYxipk$BntH1^?eZ>3`+IY`ceAv`pNp4`fv4h`epi6`VD&9T-#hMk(rBy z6LS@CL5$n1->l!kxIOwk`U8wRthc!1dW$=yx43h9i@T)1q`wAt6V7-?e@Fj-aZmIX z_d;)Ruk{v3##kKwo7e34F&2ln7-nY|V{r{)EY2~;;#^`Zu3?PDdB<3sZ;Zv^L$fi4 zzB8+0EG|4ZJU1DxIh-~|8$)+FJ?eHRIwB_4+;ItdeGJ{fbf=z+&d}eK8)7~+kVPNv zY;<^2{e$SP`p*wa7C+<_VfvOh+DS27{n5WQs=?Q%#fbD-F}X1GvdYmX`pVQza^TV2)?}{F(Fl z6X%b4&~YEUJ$QGr{l%d&qwLS)Ya~IB$gu&Q%FJ)gE@cv?GY*}m{X;o}(?5NlaPS)}g z`MB7Lv6Exz_dGLVw>r&<{Wp1x$TTkYkD2Dh_C(=BQHZttg44e#;O|uWL7m2TE&b0b zwd~y=^DKy6VD6<)-K{@s3;F2%#Ag$Jl+NT(CHaCh($B}^c-oOfK+GLH1xY+-9Pv2A9vu7 z4Y2G0Ni2^w;w>c81HLiudw_1h9}D<%54>x#r3e1lgMT@HIQD5x?55ZqvHM~V$DWKm z7kef4X6(J#C*~8uQU&t{;*&2R(q6^d%hP3j16XmEOa610^1Hk0$5*O#oKJvl+;pjZ zoHKQ)M&f3^r?Wlce0~qvkU?fYPm2Wvdn)m%TW-vEkBv)+>yTUFmJ*i{_tvvXT(h{A zaUtrqaUJ8j#udhu#Py1M=H4%EP~6bCvbd_a+PESNsecvMR&m?2qheg#$%Yf-ro_#N zn-e!L&QUSOeL>vfxaDy_VjnDSP285aJ#mNQPQ_h{yBYT&?#1sh=6Eha8n_0u#M#I< zhzxdy1_md?LXDfj)1Wl?87#c>se;f8rydW2_zcBEm9;eu*{VUvb3Of$?fd}DUs85SFs8-6sb zF>EqyGwd?#GaND;HJqf@d?gkc?-`bB9~qt*UK!pRc4^r0R`G&(rKVoIBwiNp6z>-A z8Ly1@k8c#Oiw}#Bicg7)jZcVAiO-5}7T+?yZG6Z0uJMKOCGoxD`@s#0H^x`QA5smE z9~nO`eq#KTc!~Rr_&M=T?(^ao#4nEjA>I_fA%08zj`%(C2jUOMACEs}j6WBDDgIjg z&G>uqkK&)jzlwj`=xw~gotwZ-5G2%tlO)IzToODJlnMR`jS@5%cZf*PCm0fv63%GS z5}G75OK6#JN!v1^ZNe2z$Aqp4g$X4IH#8F(^-AcMFeqVYLRmspLT$pBgb4{$!nA~0 z3Ew0vNLZY(JmJTLH3=IMj9U_RBxK;aS3~ zgtrO!sT>BNtP%x@^%5nCj)^Xb4HLZ+pJ{v(8zpKILlT$jA`!uZy-?vp$qd1!K3a+06p?rr7)#OO23prDH_$#l(Lknl-iW9QpTlp z)J;s8k}@M@PRhKL1u2VDjLTCNHvBPVUCO4EZ7I7__N5$3Ii7MRtNB{d_pNoupy zmZ|MiJE!KS7Nzz|?UyhRRBQpcrEOr4TCEA^YyiMolp1*wZue=w$+QrD$! zO5K*aD|KJ$q0}k5y&u+*}V|M0$p25;s_>|tznf{7qajs3jH{aW7i)+)B zu^n>FpQ=emQ`K0>kB(H&B*j6~=7o~wVPu}E_ce(t>0-~dEbmx_-?~aMgEUPh{3LLo z7GIZqMa@g9pGA$nAz4GMOs?O7Raa+P^owK|$JF$W#Dis;eNVEQOh7WFYnY5o|v_##f0#%KM2#Njg&DEjLmlV$mi6S$4L4$b?1p>7uEC`?91pB9NM@0DK;zVjRPHQ^8%X~+PC>J zYTWz;ttfaV$)-x_rtE(vNv8syOJXQ@y7Xe&_BFi`(!uq#FM!T_^*Nfnn)dai%PW2) zu_iOAuU<%ejcbUF#5Q6-af~=cTq3R$cZo;DbK*6@B5lZeq&?|Kx{^vVfD9%ZlMz^N zGM#Kjwj=Y&VzL+6pBzG#l2znrk|L**bIAGRLUI|oirhqQC-;yC$)n`Y&(d4v3w zd`!L|-;ivUHA{rvcfdTHSc=Hq18cjuD?tDV28}@khye*86=Z>?pe1MrI)egG0(yf1 z;7d>js=-KrnakZLf~jB@_!cYxOTZ6cHP`@*Td~;%_JhOV1ULgOf@|OwxCb7C7vK$G za|m~9AOd0_1J0l!Pym0R0y+>5^dKIjfJ~4Jz5s1GL}T|(@Z^JH&)|`8)YRrX z=|Pfx^58yG-~#D)^}Hw!q1w2W=+MoD=)fXo5!bkNbt}-;vnZNG=m}j#ht*Ow<+nzf zNN%8dt&uhgl#uxKI>0x!Z2V(Owj1yOcFjEMg5Yn5?V2h{iku9HKoriq?wk#8euytRD3@tcB2ODIvGYwiR9l*93OcG=FaS7FB zF17KHv=w#VL$*4|9R?l90G+`AFdEDStHBEt@CItnBUC;-D(Ja%mFfN_>3-7wDTmEQ7R-9jXggd3$D(zsxriN_5W)WjOtq4^TpyP|IaJf*yTcvF&zwOes4V)3*8*yJV z@v9JTpyP9?`F^4s-;0pb!*;UZ5`+1creM&=zM!HFP8x52k>bU=ElM7K0UF z4cG{F0Lp!jv;@pIWQ4RM=+gr*) z%DyhXNboBnlAvsqRfy6&__$kvPSo;!(js*x`ld7Z5=U$ZbPO1QGiMG6M3o_^Clz`P z?6klu+UEK0Qj2j~1L=~TWg+H?m|98=k8 zsh$)y77?m!U-P2981 z*H`b`($nsG#FU_AZJ_T5d= zM8$ZzW#j3Vji+074HyZqDo6GNFd57Ma{=A0^PqKLIam!gfX!eB*bfc^+{ClbfvX^x z+Hw~+Li0{(?@CMg4^g#HOsH;C8}3SPJ2ry%Ia-wgeb;(FF)mMP%RT88eovTd0hd~Q zA4g&#ytvbk-Tgu8`AB_I>^jQs*}E&h{xe+pN<`p^GS&cn z*K7}a!o7{X<)V3o2G&ObJ|G(8fI=`7OaMnDg!@ToN95^h3VbfjXY(7Oyv7X(Rk~@$ zD>Q&V6#hvdm^$~H^n%<3Z#0q|g}$qDMFYb9*9MlG%}6w5HdqY~g3I6q5Fx!M=!CTS z&Y6h6-Wl{KyOeBW`G~S zLn-0@4B8EOx|^DCWLMaMU!$z=!6tAV`~uzpu>-mngo9Mj0rW9-w5Ib+hJO**22O!r z0n3q4NkIUJ2AQA>7+~rpr1MONe;L>X&Vl=YC&&E?s6ZUZ0R>=)sjNPoXEyvR!9H*a zJO(ySgvu3YK_X}lih;&1=92eOY`7iLcMLMqQ`vY>t zg>ZLuwVX8TP~K1A7w`s%k=_?Xfm|>iX(_0uD;~72%!B4?L)rH>-B9Xtlqz)lU^g;g z=mZ9XvEW;<4jcm4fV&&v?(IgXB0v^(+*9UnjKF6aBc8?e1%!K$kZ{N9108nYQSus( zgm$=gZ{b=Uh^xdJr$!l0iz)}gz1ER%|4L4{k8>j2zemr6qjjB7G>h2A-^;J7^RqTT z@3XPMHNTF(iXU~5=->(4c%-2}jyEFGi7diE#1iQyqnB(A*&qh72EZcd^mTP%@C&F6 zg{+553cTsyU10%MM2AF#1KbIBN6Jnq^QBlyS)5A_Y|TDMH@h{_Aq(M5q;sN{`^Y@0 z%}SXKHCic4bZn1^PT*aAU%=lR2_5K!O3L0x7VlUL8$MnAUU9@=qC;1NCs4h7WCI)r z!TaS0(VkMGLvMsl6zMB7I#$D53*OaWg#VnmhQq$7VY9C+-enAIbNI)%z!ej{=VQcuo?~M`&0#qQf58GzCP5 z`3>nqu{==bYkC+U+etcJN5oC=ZpYp{NzuY(#u4*V^kd7wZ1gP}Emf`2-6Ay^0hX6R4g68zWP z38U%}0xY!Jsv&MlKmorWG!!Jkp9XCS3g9n>4hFUGe+8We>fm1v-2_gcp?jfc!72D} zLtoKh_^mzg@d04nA$P1fq{8|_s$Z=ISXvq$EFz?GrFsR+qNr}cvWIqi5k3R%0KyYj zE7e3Ji=~#RWpC`{2nPT?$o(j;JHn-40>IifAC_s{ittfz4Lk=t;stjBev!FFxKnsU zSXcKLjeHFxo_IQebwJ=nf2$UXM0|SbDWRx7lm+z^)zvkC-x1&&fXH6tEOJI#SKtol zZ`)FQ9ybtei6O*Dq6@2-TGv?CAm$Lh?qV#eLj>*vH^Gnli4G0$eXpM<(V-tsg%T?w zTaHHLp_lGK#VCx&$D-ZPI{Zx#^1NJjU$%UwABRcaez|Ock2TKlm7Q2%$_SB7%wgvf zJxu3nWp5oxjp^t&vMU;D^GVrL)8?OL3X-~iN>)dGds>!Gy*w?uNZmanE2jpZmH7-` zbXI0sbk^ZZk#W^#F0pE}a!2LbnOj*~xkQ(Bt2V#$zqhlHX3<*9mIJ>f#d{xJ#*RfF zTi?giIqz?>=Ve@rKute zzD+CYUz!I4MfwzlLm%YTluqKZu~-mK*cf3SBBX(H4}d|f)p$YvAe z#EF8zFjNtXN{2yj7R)a#hq77K#vEvV;p)=n&}VtpCF7y@3cSiDLDNWe$qJ}|b+>dh zv@=UpwhDSX?>uKJ$_*lx*vv(EaZ%&4wa}76_mZj54f&&tbx;ktztA`n9!asHWFxds zVQ9%-sDb6d?S!s!ERHWZf$)!owk12ES;R8qY3L6{u4UD@5Vz$=mhpJVPYf)+4Eq>X zrp+xVm6vMs5Gp06l24&d`NN7|LZ1|j%8#advT$R;8-zs!$5_h3S7+aX?FBsO0J3?B zD>RsR!x7W=U0VfmXv6#?g;MB)wY!Z9REV2LNgN9DWL+m+t=Pn#{L^d&;+6RdwjXp; zkq5gR@qzgYK{#z^wJB)~wIQyvgP@~{%j{^Vkwv-Rb>Inh<*~Sl2=6EwNCn?@Xb`cl z=%FA7hSzyHHqD{03Rs+W&~ZfXvT`U{_=epL+L^2@?gY(eO*ZC1%ko)N8455O3p3~nZ}1`$-2Vkg6>FQRaj6m0@`Sc4d*HBS*+eRlMr?*=5eM%+mq2HbD_Zjw6S!jEt_g^&!HjTvGA$j zI*i9D{XGYnaa~^Dl7|RBBS)1yg-#`U7iXf4ZiVe^ULrh(3@F)(!*a4HPw)oeS>)i7 zhxHK8DlK6_TNBkKw$O7$_9f?$&Y1}3U_6_MV2v*k!!BcuD9%B7SBkm|H+N?$Gn*4U0(TJW;H0QK}-pJ47jE7bgcH*>y zhUMpQCP9zpVR}v|o7!|Ao$;hdE~rCrOI|r=33O!PFiv9>dm!(P%_@Y$iE7S9=q7S{ zaSp1vlmF7j4|KSHRZ*%yjf!Rzwi4WheF2eZ z^9EW(<_MlbxdlOj^Uy{GlQ=J-`Na3soCgj@<5puP>WLuNa;l;J&gcI^N3i6_0U0P@Gm^V84YvC;zV|Dm@klb1>+I^rf_e; zRumIlxZXG#w~i>{GN%sqkirw3xlmE@Wx*2YLXx0fJaq7G7*%*euoH$|g>yN3p-HUs zg3&0zNIvBZM!^P3{|JXMrSLB2JfgD-uM4h2R}cotgwz_3tdns7D}P53-^}Fq5byS zavP%=yU3j!S39Wz>yF4&%r=YS3c`H@hL;{b3W5t+<1s8rE=X z!D9!d(yy!1DB=j$x`aiX;EsT0D!VtTJk34NVG-xK{o$W4s$0M!E_2XjtZw-KmPF4zXE%5g5%I zg>5^sH?keX?GAm!8;tEF9&T~OR9-c+>K@3L>sx?MZHD+Mb)Cc+^OvI-0jrR7Y;IVFE#OnLoFKS_R_(Tb+*hD z{?Z|nH0FAb5RVtn6weheEj%y2EROCL-)*p@PO?LBUUFS>OL9eF(;(i^uEFI7a_KVV z2KcR0J?2H(eby3Yy6(~jpIFFUSayZYR9G$>BXZJZ_=q)tP9IH58ak1jp3IY3Jg|Tn`^NKeW9+f9qK9xb0+R9n{@XDCV^vaydFDlzr zcB$-EdBV`M(n{IC^2^FIhVshMmGdhXRh|~Fs9asSvGR%6_R77L)4YDFJW+YJ@^a-b zm3J#2SH7$ys(4kxDsk0yu|w4@v1^rAm0#5bzN)G*ezXu*m0XosbyxgQ+_b8dvFfR~ zL)A-hUR7~b@2Y`SOZmg9-iRx!SQ4JZMq(=wOQez}N`=Hv5+u38A6qr4YI@bGZgZ=? zuTo1IOO{lHOQI!ll0-?Wq`7isRiS@()Rjle#qjj}yb%3{hwY*wU9Z;>V9wrH` zE|*kGqN_(pMoWI+$5*FTH?O|o)26ypbwTx1N%!i$)q|@`t7l5i`BYbrsvci$<2zR} zUoyFRiDZRjmE@(@%<8q0jgqaB>|3W=*S_4mGP5eg zUbgS!Q>3_Ve_nCR{;vJ$+KaUh?SE1Hf1JH{KvUQIKORDgfm*Z(A*=u)ivR(_-aCar z6j`;@qNO$qWbbtqEmbxM0aL4h687E!St2T0YOST#TC~#K$w9^Q`kc=U#GNIj6d!xped=$};8A(W}x`&E=ynG|^frTF;KEYiVe`IBKFq zEPMpvv}lElLfgV~Xk9IWRtC+v@JF(-k7r?oa1W_l8dMlkNYsks<WsKRa#h8DAgL*n$`*= z)fLWZ&1(%8(kTmCH?w(r|t!1s#=x<3+3-Lw$MGuOe6cKPl z+yIq=`x?Dc^t_0ULlxU#sm_+;^!Vv0{GuC=(kxDqGAiE$@!t;!8J39c14SUg<}MPqLYFBUK0 zZsO*P7jd1+Gt|4dCETy*>%}*V?-W1AIm9mGR&YzjtGGwS%f&Bn&x&6ZV@lB4Y9(7s zRJ7H#aU~ks`XxARUG1$g1Z|>rD4$qDD`Aw_mQb|m+6--bZD;LAG;eL^5|%bwd%Jds zc7*mx^bRG@k~>0HNfe|Vt|bPXRw&(Es!wRBAAUCPD#mu@c=;%ijn@JaYMVH!RQpNG%Kzvfeb4=W9ci7YL| zm*V%94x!azVoQ@sd+`~igZRACT>jxwDZa3D9A924D%}w~jd!BV;^*-5_-ptD{7w8K z{w{tA{{VlY^fBI@f--F=UB)+;c9u#@SMZai&}``{{z~Z!{IycFj*5<&j=GM9&W+N= z(pv{`I`>K=X}UTD9ik3JhpzLW^hxPTsYsQf^SqR)W3S_^+) zB6K2kqIA^DG|QrOxH>n;B%L%Jqp}r2mQJ2dzD@!7DsG{Ud0D9rv&<1H zb1(BPtJD$ce1sP3oYZO1k?6GQbm|09*k!w7g3BVxc9%t$MaA&T;>uFWvdaEMA1JFW zOA($dyP$fethKDWY_LpEX{>CzY>V>6vLf>JGIjFJvO8s}be@bI9**`g04mdzJgFes-Ra8b*(BWx{n|#UDv+C zwZet(U6Cd9uh?F}&}Hh{>pJV+B862%R=DeW>+Y>+6|!{Mx{ku_x*@vUiU{3E-6-8? z-3QpDij0cG6@?Y$6?wV`ee!ih6(=eRbPIKLOf`8870nf$6=tzw-ID;INdh36XvY}G zI(2Q2_391+NTzY!dFr$-G^;zO>wGNeSje$`$Arfcj?L?)9m_eEf9%My(qmP}#K-E6 z)lwzLw0*~q%^aIM_KQKdD-&$|pUqLRVv}8G1~;QC0P7&1&6h12UzWS?ySzK#9763G zSNm2w`>?Bnt0Ss+S4USz>+!2osPhv+^+H6`db4_SdIiFHy=!{AL<@RR zqMLe)diz8|(OtbIz02qXQJUz1-ebLGJ%*|Tog-S+djaYFLPhJV=;w>n^l7vsqHW6R z`WpH;eO-NmK2g6^MA4_~GxV!O7Br^5y}r6oEOOR&*Ds;fiCFpz2PC3)QLkuNG%lJE zvGup>&xu0xBlIKnqx7$ew3Qb`(fV9{q5dt=UD18fW6_SOk#kQ)t0Gj5N==-8l77&I z%{5S(ewKco{#Aehldtc^zfCUC7shDRfNuiTl3owB!vTN7K8RFhHjKtHeMaLuxQVNH3> zivFtp3w^YKih-JesOCgXLrrsyv_`{V5~{%&=o%0VKBp25CnY7f-jpl&w^ zF({}lu8lCLtgWp*SsQ5(We{y}rj~0UG>9`uGH9*st{to$tKCNahB{q)vG#f`lx2`- zaF>>EuvGh~cDc5|;92d9+Cl@&@vX;k$Muhw8dMq(kBbb%1}6=s4H(B~4dx7NkIw@r zncm~W$H$M)9G^SBU~to5(ctRwh2wV(mJDtkSE4>Jcx+p z;xi1Hh8lJFIzn9|iBw0gv#PVN(=>Ih^R5e}_}6W(s}!Cih1J;u)T+q3y>;9=F*&v_ zA;#U%+c2q)W%z`|Hq5BYt2}BP40vWZi7tmAY$nH|iGa?$tf0ds4Sj_qE>s$Rd9nr~>RtXZ#H z|B9(mJ*D2fo>~7szrfJ3-o5@XrO>d{(6_$Q@F+!O=t*YR2iHf`@2(dcM%VM}Q|b@Y z8$r~9`r>*&a%FvO{mJ?>^}5t(pVs>B`oVgM;aL52eXHRp`eOYAZNC0`y*>G6{iFJ4 z^)Ko%4QdTMpREnJ2K|O!!$Cu01EYb}5Y!OTAT`|85Y@1+LD=xM>bT*wVM4>K;hf>T zVOqmA!v(`q%1y&X!<>e@hWQOkhAY&Qs?Z^Tb@V6ofgy_aG3BvgX+u>*1I3Nni@uGX zKQpL&{Y(>O*>J^h5WQ;n!Z1Acddtl-ms{?fK^tX3Dn@EXOJ^RPQ8!vX<4x5tdUgh9 z)J}PE2Gd9|A{uG?^ib3qDMlAabR&il)5zYaLDkvlJM`8@Tw@7Uzws3sv2l>%4jFkH zsnMcmSVmGb+lbb<-6+Ipk`iGQY4j^O%80|~8jYhFjkb->jh>A{qd21^qY2fZ#*oHc zjdlDvqj{q?+P+3%WAiliVnX9JqXnb1#+=6d#+ycqMn@V;8><@c8Z8+;Fxq087W3F> z*=WT`+_-Aw;d7k)!lT1>>8>i^g}2my916-;+Exejr&kej>SvUXiRAKbNc;zc5A< z+t4aRbdwqp+oaw!sH#rXAmWIcO}az^kw~Nvb(@TuC{1)CgJ|BwB-#_5iOeR)Cif<( zs&A7!k=+#B6w$Q1$(zU`vWd}6+le8>2x25ej3)A%;+nWbAu*1a(v;PdL`)+dXewwb zZptE7Hq|zrY&z4FPi$@KZW?SFYnpB{*8@lMkc z`XsS|C?U2IJBhu-L86qn)byxnoH$KfHJ3J5HE%Z&H;0(iHAk34nn;?XOjMMkP1>91ROeNDn}?gZCc~s_ zszQ?m@BO;DOM*{(Ei zqS1nHA+#6?msGErkXjZ@=q>8XRxReH_ANI}7ESJ&xV9{rJTQ4|;@xt9?B8;dylk>! zqM^KMf>VBBvb`m&1x->RsgWXE)JbPadt1(srb)U0`j*=g+mh6h(b9z0AmK=PExM!! zsuU8P#84+ONsm?SNy?~2-2*~G&BHA^22j<(+1M+w*J8y7>kcl)-iv(@D>?WiwwJpS z`#iSv;5Fs;gY*1b{2KxjbP=i@m5o*be;N)3x62~^MBv_72FlU*JHDH5HqYDl4d1_& zB7EQW-3uk9>deJ*8xbejByo`-0HxREa^Nsg#0d@?4v!(rN;oo zxgVs9^jm_)+Ortxa&+;TUT$_NLN%a zMJXVnRJT%Bq$_x<;;pW?`rl&oKYYuz|Gh|YU(JRc0@0ISwe06KF z5LXaa9M{QP6|CfA4{08v9IA|~jeFJXWZYJ>GjXkP%jE93!ML%w>9~t=^KsYX%4s*_ zP{KQLOL33lmgAnqy@28{@oMq6Nn7J_@%r(^cv`%QkP&Yi?;P(Le~-k94~h?o-xdEO z2_i?uSJSl2HVgN~3*!^w)8cdD^W%@im&WUwRmF?r>*Ak}O5)q&d*g@W$Kwx^XX5AL zPm`|3FT~%9zZ-98c0c}c{L}c=cvJ$(OeNt!3}$R|f<^*90s5r%dh6ZRA6uWa{@(hc z^_8~GZA*s;38Vyif>nZjf@^|zf`7vHgnpieFf1W5!MKgqra87ZftwJUkd%;-ke6^c zp)jF5L6mSJp&_9;p);XBL4Qn|Fqtr$a3$ed!i|K*gnJ1O5}qWiBs@>R3(<+#MD;|? zMBPNAL`tH0A`?m^2ptpM6YbmF+I#`{mZ=%TY;W7|WE(R_GdHv6WN))K%#*Y=bGo6e+U2` zACoG~ueN>Jw%GQ4o5=hl^E&fJ^8iw-d6&7E)^C24TuRoZjF=lzPSIA|#?5~voijf} zt0G;5%s(-2pCJ_}RIu*F%6H`*6A-P{Bfeh`s34Im`1SbS!&VDYuZ9gFWR9snT8 zrxt%$ys%JaU>V`Enm09S?XCDSwdvX7n}Yf(JfcnEiG*=oh;og&(btHd@OCpe8+;u z{4K-Ac8_r^_l<>E#*V#dnKHK9GH0xTe0Z#hTs$^Ss~WpNYaeST_m916IW~5MHZ%4a z?aCO}GR88&GSxB*z+fJ>JZf2LdCaob@?*<4$qkmf$q^LS4xf&Vrv z?2i17qaEcPCp#KD+B^C>q#a{Nr;lz;`n2QX(fOn8mOYk(mZO%Fmgg5e^?@Y1M z>ddp!?|h$f$m)nyiB%=I1CmBQVO4MSC2bd3V%28VZB@k|urleCT1{BZSY5K>k>{;6 zu%BDqu=>X8u2mfG2dhU`zgVpR?9O+{^iGsD4*QC=8o=+2CTm*jSfj}V2mpA}tSzjW z087xZ)76^Q$?1H%GrCh3OTZGb6f7OPFG-k`kd&5mtW(@s-`U*R)j8OCwsWTQa_6G}>LtfrkqSUlhT^foidm*mNK1kF=amGddkg|4B?%WrIbf0 z%PG%NUZh}B)l#>n;!^cf^Mu4yS}G&eHq|-RGnJJZlp2z{D>W*0U#c)QAvG=aurMbz zKlMmzX=+ufI2Ec(m87<(_NES}j;GF~&ZS;WEfg-K-b%fjdO!7X>eJNKR8*Qu+U7Kk zv~nIkjgUr4qo?VgwMw&3Bb{|k^G@?mGe5gM&Gu}$Ff1)HZEqSkEjBGFEh8;2?QmLQ zT6r3iFG@R+CK5KJHK%o^^`}YG9Ql)JwAk6SD`_W$*Pyf;X^Uz1(jKHeNpt7>@_+06 z(7M>V!dhhgk+qOgXWeMsYOUP$F1gFP-+IJ)-1?mLMe9$juL0QBTh`xNFIhjdeq#Nb zH50p%W{-8ox?^3sR;|6Ue_OM#7^W&y9l*D;vGEi;Sy}JUsg1e~95NsyJ zoN3L3BCv0EMRi4D?U{KL7p5oEkI824V1_Z@Vn$)3n61j2$nP=v%>B%<;v{A|GlzMQ zSpYCz%bC^8LzE*FG4mAj46}vV2@qa~nP-{P0N?crvxM>)^H$e!QYB@9$;FoPYbakc zh1fgH@0nlI5=jr3?~s0GK4spa{lRDS*=PW`SA82}8wvn>wX(6ZaRyMY zzBU0i+imo^iQT5%HM~%pT{aHg@7P2Ggje_OSerzfG=T7Wz~%#+?`bC}^^`(@?OJ7X z+(tqF!b_f?xOY@nL};B|s> z&*n#)#{l4Upyip(Up6xoem5F`yl%F=M2YWy-8QWo2Vh=}Y{|BC+uZKM-SZSn+oEn; z+sf|a-A=Y~*v~0V-8U#nSa;hr>^GFVlpiQrSRY$|+eZKgn`4`Y4YBR)e$#fhEtmYR zZ9bL@FklC|3$QDc1lvLgn`&E%%>q!cpLHL${dnrAZK>@s+gbnw+hE&d+YUfr2W>}f zCjkKLob9K!*8%+NqV0FK_icZ&UAFz*_POn!6eT+qyI1YD+G*M8+8NsY)J*~?uM9gI zJ4b-<>TUOiT@XNbjj(&$Zm%7R`e!$~N3~~b54ZGvJE2{?U5Xu)X_ser$nFRLb*;3k zu{&W`Zzr*9v+K4SYk5i=u#?(N*pYi?>@L~O+kI|#!|oe9^B$X?yLQe!KiGNoJhJoe z+0pY0z;pf64rTv}J@|z$`z_Qh_L}xO_5^zqdz!t4Jre-9y4pwdywl@l&$8dw6VnrD zA8a3PA8Ee_0J-Ww)TADPeVlz}PqO`io(%h3fa&_7eX)Io{f9jw`;Y8Pd#Zct>>KS{ z?N9Wa?&-4cw{Ptk0qCwhJ?HFadp_y8X#a`*HTy3C!t1y8OZE@#pVn$k5IH)>QVvEZ}STXh_)~&Yz3;Fg|mPxQ;ek-;U+lw8< z?(OCEsyj%ry~?S*+Z^x!3-(ZNVQ)n*(Shn<4)9)0s2wDGhv#YN^add|T|HehT{qn* zosw>z&P;bqcTZ=;viZL0!F(5o{@xg}C%}JYJM3@>1DLN-4(~b8sC)qIYC%nMNO#C_ zIOtFSIZXE!Ig~q8J6!GkvUjofQSYz4(^#dxSuD114!fmq9*gU{hBfG0z>@lIV$J#% zvCO`^Sf@U*!zqU|4lNFy4oqsF!>|mQwU>0k;fjL;^)rXqioA;OzTJKA^$GhD`!f0# z0AA}IhwmL8IQ;DJR0i0p?1*)I%~8V<(stB$bfp?QdQmBkW{x*3{*wEL~my({9ejvRdJ&8AVwm7{qy*B-%UHX~y zeqn2RcX}m%Fnuh2I{jk$eERkDo9TDbrNX83N9oJy&(dF{&z!|%sAX);z-8!X5Ho7( zjKG%&CWW&?=h%H2!iPT}B~*nXPh!k>(?*djQV7fEq_V z?s(GiwBt};vkZ84$Z^c^eBYGgmA=pWW*sjB0NO7cZ#v#~yaym?lc|p#e|3E3_?IIZ zz|d}Xdff@%^1$8 z<&S5eRcA8hGOlJUWZcTQn{hv5H~(?Q(+m~W)r@F9DpMs>qhF`r*2$=!3Q%P2`hEJ@ z{X6@2^}9Rm>GyHs_WL`n9^0Im-JjpDk%`YFWRf!Jnf;?ynf95knckWHncFj=u*_R$ zBQy7Aax-Hy)l}R2`}!~Tf7-v$|4skB{)hd`{T!!!>W9=2r#GE;%fNDDoD!T;owA%D zr^8Og)T2(NPMZeQ2ebx`In_Gp4}9#Dl-b~vO=@y#cj|E(bQ*PhDgC)aOpyNjTM3sbceS zRPF8jhI5efPUi^cx1GDFd!75K?>h^f=!k9-)3PP&`oLT|<+=3?Q(ba8NTg$STN!inA)SYO_veoyls=>dqR> z8q30rPiI}sn$NnPbu;TuR#5Cx)}yTDtTX&)Sue6M*-wOO*;})5+4|YUY+5!Wd-J$$ zwsW>;HY+B1wm7>kTaw+L-J9LY zAI=`n)*PR)&ek8F%O;Iq&Ff)ofJGitxEmB?ldYjoF-|k%P}6 z#2q^i2AyKZyEyBfF>U4sXyuI8@Rt`Bma zFxkkB^T=U$l!GnYLt}c+PW{hr(Q4A%TmV|-(rRYilIuC(LKTt8Q~>kij2*SB1wT;Fr$1MuD_RCm=R*L2q$*MqJF zu0^ip04rGR+B=9fJ>`1FwZ*m5wa;}JpaxI7UU0qQ`kCv3>({P#0BrCB*PmUVy8hw% z!gXX&*$u+Fz2>Ijc42US@QXohx7SSd-HhEFbKP^I`?AkeD3w!o4Nd$JGo1_k8+oDpXI*D#pGci+AV=v-U(iS+g-udJX~H}j4Dk( zkC;cxW8`gjv&{>2bI$Y3W90?qrNo5fHSl)j#qoa`{CiMkXqVeNZqaT$w^+ACH_f3m zw`{iqZp0zeA*-Pe+zQ>w0N}AY?YP@Xw{5i3Zq05TZoO_p0QwkDn{u0VyX+P;bk*&i z*jp8PAbuiV5l0Sv;daw4Xgq9u_xQf?*zuI{obkis#p6}uC&tf=w~zOakB!faUm3qX zerx>R_@nWs;AEOgL{*EySqKjh1TOf=-%6-xOJNNql=y=)v zclYN2I$6WCmaO9Os>fCUo2=`xgJ$S)W9S1C$%9Nb^|a(1Z1H{7%d(^A7^^xc^vXM;!y&Ck83Rj8^x zOc*8)n-ALzI}dve`w#CJj#C}*c#9_WnDCeZz{m3*pL^T@@Qrspe(-n%u#8tc{`5cr zq~f^xG}Wy7Ra)lof#DB_ONXnWVKvVa!>5OPh9`z+hd&vW8-Oan7cYyym z?`hs@9tu){HbWW^_^mLA1koWY$R1MRxkBEMKeQd%HljOXJfd&9#S_v5@RI~j6Hl6_ zg(uU~!PC{#%ai39=o#!8?iuO1$8(>jz%vfu0%v&Udggn6=vnMp;i);HKS7!>pRk>9 zo$#Fqnh2ZNJrM@&n~0rAnaENdRL)b)R}}$V-8#=k&sNVa&wkGl&vDPUMn2|U^!x;% z;C|_O%kx{$CC`VRzd@d>o_~8{yi~o^y|#Hp(ePe-M-04pBXJ|CBUAu{Ywcz4<>KY( z<>$rr+TrzkPp3?DXpM8ult2 zIqNm;b-~M$JVdf3U-A0P%h;4+`Z0OI>uaw&USlLD@)XIP4Ed11_mYgT8lxI7jXdzW zI`XsEmm`ZKPrbe$c{K8e*RLZlyp+9HN0g*k@7KIFytTdcz4t;~C>F|!*%zAx8GBQ_ z&AhF=?EqGzhqteHfcJLqQ14yd?|4Uh^SomLEMuB?w)X)5$yn&UpH}9bM5_Woj3>QM zd#BS9$j#oV5V^xUht}&o9(1&)aXd|J?ppJEr5+4xJ984n~Jv2fJfe$Nr9*j;W4|9Sa@bblmHB*zre)b*D#X zSf`*fp)x-`Mx*l}> z-KEm4*=^PB2X#kwzuR5cJ=T4$d#?L-_fq%MZtWgI&v4H~&-I?KdtT|a?6vO=><#IC zt2eGUr?+T!xyVfg^`TDn@EYPK|Vo^pA{=OpjotTckLt zfs`aQlQN}F(l?~x(n1vD(v~X$=OfSSPuow&oWDM~Wm<18=X}ZesY}5ZPM+^NfA(DC zW%}i(bF-H}xU7Ep!R2dHKGWQp$@6J5pPet5xqDtT^XqxcZ16NxeDU72&!t_LB4)0f z`|?uVMaj(a^!`hzbNQDBXKF6JGp&B^>vPK&gwwBF)S7L-)PHey=HkU$7pLd$%~EFd z&q=2`&xxiSW}`10o?e+PnY&x%(#e|dvXzfF?-hWT*`%v zxq=I|7npNq7h5lkU4W+MFBH$ZUu2yNm@~gnG5zuEoeL{dJ1?3}yI)W_ABGZ5oSH!U zG*4ua3utFXuZ)h3W%2GiLFXS6zGt6xp(kIRJUkI}R&%nylrlMARx;sSzWc07h2`Xxv-i$^)?!}mH5peO zB)WcpJ^6f6c`$VHMvLvZ+HsP2uQ)@zd(vSje(33`BM0Lq(P8gDY&zY12-_?|L~+rC=SL}LBri}%U@h%C`@Byy-0hPqFp}M zObH&DXbOME%jQC|7O)ITnRogh<6SUiBs6OOC>dt=@?vvjc?;4m?JY?D-np(?x%Vb(tY|s?3)h`Mafvq6fAjDXR?6ABXOa&LwYmd|b zzyZcfdEoE2;NC7Qpll(;K?ofWdfrrWP-<`$0 z2iAtK8<2xx0E@tVJgG8z-H(1exn($*_cqY$@gO(-rcwDU7fdV(rqkC}<4I{U8>J)q zBSu1uhiJiD8;;llnYvEHLUJ%4%d|-c+E;LGcoX1q87%A`={p;QLdGEDr`E=AwuAK|!1DS~sijI{E}_p zS@Kwe=<>9*L4RbbJY67*AR5%tB`-yMymWo9a4_VxED=kB6y8Rn>M!nTnFugv4hGqN z$SSAhZie+A1ou(T@qSi%3qVDD$M*pM4>B(!SCFk`5iH5d3`Y>F~9MR zc~ss+j0Nuk_k7Idn7_Dx@|6T3;PSoY)N6umf(q_uG4S_*M!AN9FJr!m`7Y+i7_#7N z-n*!D^v^Nh@@#lN@RWJK#Y}OLZ~y$wgFfcAaQ}??Snvwp0|39i%J<_1@;1f3$ukpJ zgCE;^E!IuoBM1=e5ZvY>-}wobecR_9o;Dx(jt?63=BW0l73zZWyMnK{#{gRFFI*aM z-9zqv!8zVGR1H^;_Zq-Ium%_hn*g4H8tP}IJxUJXp1%&NNNE=YJgbd9q%;aVeoo0l z=>vfi5B78n_#V>heAwTCXdk7m;7dgty{-+wF%00hi1v;^r0{Dwkzeo@AAc0}5 z1Ns9&k)T3QBluWg$v-V1$F>Mm0XhdyP{UONcn2SIVczm60@sd_7)=KeesyiTApSz~ zfFe@CICx9Jg$DMfyx73>!Ln0T zf+Wl#Fifu3Mt#?54rWwg!EG`nH!?I$x7?ScfoA5AkZIXk9ZoI6?tR%Yw`HbBI7}h>Ba5~uH27P#ndAqh z_O(ncH!I8**60{WA}}P53#bW58~iEPOzxO)#RhZ$SWaJY;qNxeIl#Nia1?qIgvZ9Z zg@nzeoe8qcRnrsyXGD&F9PY}tt46(Yf>Y5vSR)BZBoR$kK9|LC^i#D@OK99|mH+K0Y=nv?HkL^#-&9E^|5LGZ&D z!W3Q(KFT1$+$ZbZB1>vRGL?|I^6kDR9Gw5<56|+cAIh}*Pr;B7x-CmBav^!9X28J6 zlY-ne>-^HC_hh4yrGMN&BX$Jw56iJ0DlV8$6~0?H8?0^MT2{O*bx{-)`3OWOhqV!V zq@zH04kq*;0SxnyyF@`l8%%@9glw;}|I+@?rOwG%B9^HEj{EYZ^t`d$Y>WyxyH;=~ zz}yYbKk!2n@{ETDFi`I)WR+|APlw1KKoR3!R_t$hJmSht^z}eRHlh3}6Hx<1ko@@v zY&qa{WV;{%^tB?!IhfBkq#Rg}^(FR5-&N%JKNK?%k>LkhL=c|TQsy*E+2gDH><`Bc zv||(*GD$}E{0`YOJ6wigG4$5T53IWjX5t@CkuO7*Z6$fhdVNF5dU+$`Kz)DD@(G&^Sy2A@ZzB=0C!}UFegD&C$lmx%wi95E5C^j! zw?D}Qr~%FE8BlI)X8@EWrx6DU6y{!c5E_&%`Ef2?3I~`xHOt)ww;;(ER*M6@L=W7Xlnt?O8!uZ=)Fe@|E84OJ~x4u@-(&4R){laWdSQY z|F~e__Zo6<>W1m)`~H-P{5 zNMG4-enlkScw9tu?NP9C!GzEOqMh^ysl2AktpH~{4kmsrP5^Y2lt)5lBV3U;ATI?n zw5D&o^iA317I5mx=9%yZ1P6Jef+@l0;8En=*bBhdux()MK~sLs+W+9iAO-!OX9&d1 za${d!6AKacD>9NB*ZdEga}T(m0kcHjWFqkj=Ybj}dBT8uA1VwiXOHBve?AkKtpy@D zs-oXms|-A;{P-z%iR}Cj{|AXl;0gea_`8xmKsk$7QUwT&HQaRYYsFz7D0ODk8;Sd1dS@9C4gYv5T)U_Cb}$_f0=Ya4)_gO@-= zTz^P`A38XguU?7;G|C61{g0g_H@w`+auMO&DYGeM%{TB%Re9$oAX&WB2^dQL=ZNT< zc$SV9IIHae(4zp~6G(-;{zq)RsPG{NvrTb2k^4+um-a{c|BQtl|+F=3>AQs^=S;2Jz1_NgmWSby+6M3VKSRhO0?B6#e%fqskjUy5I(+y*g zu-bS=k!NbTZ7zVnhRBc1V6Sp8(IjwaS+iLJ{)oxt`9m(73$O|-kPpyEt_QqfP zL+Bk~TfrF(9tpqCfN5lYyc8~*aI4%w@c-cVAbX@uii`)&DlZ=aQ6KVO(} zFuy2L#QIqUX1?)6i-hoh`$Xahjw#t$9iF_wC!i?8hTa&}at+rVz*`eAURt(B?OA(+ zjksy!j2fAJ5c|T8k!P?n(+y{J#14;5;qG5$pI^#`qGiU~Ec-N4Y(V)D5ZP|X7TYcB z4Ur`-*bVZt%zv}O5EujWhfgcW*<_<^aP_W{UI3pqN|sZI%!qSpq=+VRwsL=$$pQ~) zg{!^`Mqc|7POr!~O$h>dyz z(UdSK5$PjRBnhU9H&7x)TjLrP0zd+hah=M^pzUZ221O*xDJWYhm((g_ldY7$MkVrt zNnWynv1Ah^gu;;P*4o;5^0t^(*HNVRbM&6I-nSHUcaNTsBE7FF%lV}!=8kLZfH^}z znoU$W!(wU9+LYf&folwV`4VM>&Eh8cxVxK<#vpCxWcj$2O>%0LBp>%PT27r)P;c=2 z(8$~iKxhad)A`wJdY(qh_54=BhJlr{`C7rojxX0ZMnU}wAiofPp?vua{HUB7;>iU( zg_hexA5s|UcLnPng^`97Z6BdOT(ie@g^dCza@K#M<*c_TOs%eP9cffjTRQcS8@LWh1 zW_$Qwt}Wc9(AZodHJdCq*OXLl;2mVSJ#dQX=~oCdrVz$Jp>c|$O_i4qYzgliRUVYP zW>&hYT)WE(5&CKJwr$FCskIeW@Z!sRpDS2zSGdGhA-=tW^EQPrvGQIBMJ3Av2@iH9 zVrrpc;Z2H#$4PSC|5oUESk4CJqF{YbA zyaieAk+g1&%?}fGYXj?A>ehOfCYL7n$=Fo4RJUYNVTPM|n|a70uIhg3f$G8PH)5|I z+&iaxK?P-^gelxZLUrD(y#szG3$7eTTi`ixxGPhP;kLO8sRd5P;i+*s4l^dH43g0 zNE=)m5DKmh2nE*$go0}WLcz5Gq2StpP;hNPD7ZGTNJ#w4NLV|#I3V3{aX=`zHXsyS z8xRVv4G0C-284oZ146;I0iod9fKYI4Kq$C2AQY$#&>9IB2c!`$4hRKT1cZVs0z$zR z0iocEfKYH9Kq$BlAQW5&5DKmX2nA<!PUT21oXXec(&QAJ%8@oWl|u*#=W>LCGdM!Qc^jeNyp2$BvPLL4 zStAsjtPu)M)(8bBYlMQ6HA2BD8lm9Cj8N8)q7lx_NF$t?5eiPs2n8o*gn|<@Lc!@6 zq2MHpP;e4PC^+>Z6r6Ao3MAY$3eLB18>s(q!bQ5_go{vc!bK=J;UW~AW)TWbt_TGu zSA>F-D?-5;6`|maicoOs`doHwg-?Y@8+_PfA@yHh(FmVzkZ$7{T1)<?^Mv6d47l?MPVymrhHkWtkO1y?5@tb8AVqrLzT@tUskc zg|szHxsFyIgc&wWHLSJ4^#U1}bS`OaE@g%?gUo>I1=4%)+`+Y8`x*PSUbrYBz2)c1 zK`#WeX3enHx)&!GCy{Qru)qu>P|F>~F?aZtF|{#|xhG@J#4PdE3ZHTx@mpi0yzZDu z-eAl!pTHyW#$xEa8SZq<#h6v@GyV%cMu00+6I|hK6_j%4V{n3NykV}s;Cjr>m^(2` zF+{-)o<0CUj#=bc@$T{Lc_`j;%sBS}?+I^(_nar@O1RHr(0o_kix@25o9EBN#D?)` z0*1g=pcd;a@D#8FL4sS{c5aAZmmrd-$*<$?<#Bmf)B<;FVHFq8o8`@M)%n}OcUYnX zx_l%4K7mlMH8w$ji%kNj!?}^`Ueye_e`(aqBEOpjq z)HW8LWxyh`s4R1qHOrpm!t!MKvDmCvQU8u8qk&q-l(~_g2mL#$3?5}J3?=LP%unH` zHSiiZ4P6a_Mo{{WkT{s**~)r<=MiZ46a46tNKuJJlKuy4jfcgMQKwCg}z(9aBU?N~9;8MVRz~=!l zYRy*Ef5)x45%5jGIQTI`1$K=Lx<&=H23`Zn&}&{pt>f3+4frA8QNTKejV=m7u~`ZD zGvMEGY}D9W*#AVbc^&n?W7%v*Eo_2rZqnUG*hbu@!zQpz*fh2Uo5_wy)J6Tz=r#^) zTc4NlZCu%2Y<1NCgmJS4^)Dzl&<2BvY)VjW3RIRW@7?X1Jwh!1>yq@0*Qgtz+vz+ zDyy4bY*Im42iga^1bPPg1+oLxQ9hRbmK@6v%Qr1|LzWHXCh{~50OF9}wY)<6jJ6f^ zzX9Q>pppX919Jio2EuSSub>J7ivr67s{_S>YN%6zX98OSI|KUyhXcZ_ zoLy`0?o$<27*yT{Tmbv1|2D}8{itog0qU0Db{|y3$9%2~+gTSbxv_f@54MRzxrlE{b zn^4D4w@~lUH$sC#cZNoUZbtn#3>Hh&+o5}*(Dy@yq4A+9p_!q1p@%|`gqDO>hSr3h z2(1s5gtmothYo~FLnlIKLNA5RhkhP~m>?`JEIBMAEH^Aa?8C6)u!=BI zSinbNbzzNRtzlhZ{b3_v<6-B*E{1&)b}j77uv=l@hAo9X40{sxTi9yY-(i?=)o}Ij zZQ=NEgK%OvHQYR074-wPR|i3j*;nKAzK$Mb9c~})67Cu97tRjf5gr!)R(MqSd*S@> z{ozUB>ESuy;HT_lXfnzu1WyM1E*HR(*^HVFzYu;U{Il?d@UO$~gnu9YARI=Q`8oV` zl!3$3@IS&|geyn87ubR%g>Z%u1(wcx%>U+5;W#a6rpMA>DR+gu0mtM z0{lY#BK_k0lKlGn4fY%1m*V%kUokexZ<^l>zd3#-vi)-Xmiw*tTkp5UZ>K&yR%d4EEB-hA@A*IWf8qaD z^8e)j-QOs{ETC9`WkA^g`+$l8RRd}U)C+J9Xco{apnZTxK(_#&0Kb6X0A0Ypm%=H| zcrl!RtA#U!ZOq)B`A5>8%>79RGmmF}myRZ#%{-lCoMN6*GQ~Q@K1Fg&aZ0(ER43(n z(&fyXnT=BJXFkn*o!KnqZqnnV)+z2OA2Yk8c>Ke9tt_dYKfGVxCnp%Q`DE#Xie1 z%PA{9rA}6(tUf6NQ--Icq%_MKpYlGbb(VY9jJnL=N=d7Pun&}qPEvMT|FE_pN^na`AW5fQfs!vQnLcqV(^|549-++Mu z!vaPJj18C&FeM->Kp!waU{S!*fRzCS0hS=!2^T+Q_#yCX;IBYUP|={0L1lvMg31R~ z4yqnhC#YeNOOR_&+n^3XU4p!VdIkjsg$2b2B?tW$G$d$bP-@Wlp!A@OpqW9fo96`O z1+55L6SN^{s}!^=$gTPDpff=igRTX2YF>=pwSQ>;)c&RYTlufO4*=NtVwW-;5NbT!BXd7&)^=x0l}fck->4nNx}Vt2M3P`c4sNUzXwkWo)$bK zcusJ3aBlGO;MKwFgSQ0l3@!{l7pGDr#;9&*p^ zvD*u`w{D-@q>!Q2M^*psHZEjhh>^RQ`_zzP?pEx-7c??we=lQX!b%n`UDT$iBm2)) ziKPEtmdKL54fz!EJ;W%~EVNjtWoX$@`_PJ^RYPlr)(dqGZ5G-p6c??!y@R1}Q3Z#} z4o(iW97?i(mM`Mfi`s{Jgtls7&RiT?NDi$X+BHOO+#0*-4wb#ba&|f&?BLy>^FzO4#OQrJB-2cpA0=0 zdO7q)=-tpqq0d9#gnkVD7Rtg*!z{u|g;`-8_@~l;sftvL{d--c60A|tqvcMPJ6rBz zxvS-Fmb+W-VYz*-a-Af5CRa-OXQ8Au`i=U(mP(QgwUTVuzg0^*u0N~)w|YrdtU_3o zuo_`?!y1J(4QmIV>}5R@l6-g<(s=^264KZ4BEM_D9&hu)|>| z!cg<5r6v3KqDL(GcFMyPUie5V%~}*)Tz+Z!{PJtcuP?v3{PyyHls_AGDeQXKov?>t z&%$1ZwQbpGlsQxCB7F#xDH6yi z>qh8ObieB+>89yQv;R$Lrm}2?uFD+V=~nG(+^sM&ac_c^)58i+E3_~AvVwiW+X^2m z98UOJp<|6$r=Jy!E1I%wU9N7qZnbW`4vSNxh|{fB=b6*II`bOMYc{X-JokC}q5ZT^ zC9`Dn?2_5m+4k9v*-qJYvWq7-%5Ik3I=ghTdv=#>@9gI(e%WtRKBw3vhh#@)|4fO` z?vt%at&lu0+alF6^=R+m**2*u+2gY(XO~OO%ATE_on1LKFFQZGAiHYv=Iou>d$VgL z*GN5_eKNaV>iO&j$yc*)XFtqtoccWbZT9DE7b*E?wkD@#a{FY99LpS=oEE9&aw_N4 z$f=jpIHyHU+nkO$-Ew?$0&>D~qH_{*+NSo)>6mKH{$629!;~6RMcEeJPF{3AjlA|hfV5+nLW42l>YAw`Ufm>4lNVtT~v zhy@XgBbG(1idYx1Ibuh|o`?exMY=@Hp2(kId{GB{Ef855Zh8Bn!PQB$@_`m;7x3HCo% z$1-Et6+_(y7hPk#xZ>9ucC2sYz{p{dqa(*gPKcZmnH8yzoFBO;a%tqs$b!gCk=rA8 zM_RPqA9*D5WaPQX%aJ!CP1)VZN0HAX-$Z_l^kLs3S(Is%MO3LM>nKSZRUxWMRE?;* zQH`RSMzxG;7u7MUYm|3Xuc)A?@Tlmh-cfy`21E^w8WlArDlKYqRA$tysCiKfqn1SF zN3D(87_}|x5Bw$WaMX#Yvr(6#u1DR8dKmRA>UGqIsIO7KqBPM(qf17YiMER_A6+@R zdUT!WhS4t3uF-9yJ4APh_KHSvtib57=&0!U=;Y|%qK8C}j82UnA01k=6#LIbzRcO* z%Y50e|4X5-3M@T3BYI}EG&edYIxl)f^qS}m(e72ZM%%mZiryPt(fv^L@#r(rRox?N zUW~pLeLMO=^wa29(KX%QM}LVfSJQ!&YWx3FAWXxQGGIlSC#u%;}g5G4I=5jky(bKjuly%b0gD zpJTqX`w?Rtioo;d#v{`?o_{12^QcH>5=5o-(!x)PLG3<$1#sv9#1?RJ9Y2m+sT48@aW<3yT>Gt zY>(9*r#-HEy!2?^$(%WPO!Ii>@!6wDr{bNeb!rVK)MKzmuE!RSA07^!YIXAOq+ugG zE_k?g>Vzo8qtN5NN8?VWtSVNYkk_6tWuAYoJz>T)<1EM7j604sB_#X)#%w5-nLr&Q zzU0I>6J{~)Y(Mk<8unX1V=OUIA;E;{u>3^p{-%sCQL!9LQ#kfFVHsGa!U??&VnK>V z{hPrV-ETVy|cO z@nKmYE0uqa*2}#SPscAFPrY6lz)3AkUGm@63tx7Xx z>(jQT{gE~|X+PXG(ne`v(ss#|9Zfr(b}?;f(sgWmH*Jr!D(P|B%e3`L@3F<#G&aF( zLh%WuCuo@6gbEX?PBiAp zrK?l7&d?Iw+`9#I!xtXAwm|+`*V|oxb}a|LO}Ef)k?_xSO@e>1Yo%^Yy2ZhN-_@j> zc{g+B*C3+%Xsj+UxZ0%}o3LVl37g+wcK66?MVSsO5iG@u0*}Nh0;UbE8=5ko2E)5c z6T9mhu!hELLW2d}HO#_%N`ruEq4-{MOoK$UOPUunIEh6FOjrVz9w^n&g!Qi`b;sfZ zte&T{XDiS4o*teitgNTKXGLf+PfO3LP&3b(p3OXqvu>V#o(Y~=p8Gv5yefEA@k;Yr z;$?6T(#Pcyd2^Q0&9GWF7W z)%CJqeLaVHcJ=c13i1l~>g_epYoXT%FC2mA0I#85W4x|o$bDWXyv}+Zbz5Hblv|IQ z)2kI{de8ZuOFcJvZuflSY3mXHk&=h3J8~_ z3h8_egwg~4y7+-hddqZW`2!s-e887I;ENtC77HHmO9=sk92rYDOFxQbB}Na}GaxqIuS#mK_js|!&a=v&=YGZG>X4J*pyZF{@Rc)WZ>$JXY#LxWg6KvYUtgzk%sZL4*EJ( zZ)D1EHTt-$^WE$_&&8PamR#QYnzF{et$i(Aru*(dT&ns+-%OVSzLpr= z%{SI}nQuNiM)+27F=a=Zm{xz(q^rv;7c+LUNq1j=UsJ3LumpMQ>O+y2eBU&wfx`d$ zFp2F=o;Mli`>{zo7j5;)E;oH&;7?Z}zKOodiUIe0i}fhVq|{#Hd!_f9+bgfvie4Lf z?dnyssVU3oHM3VvuQk24;*T)J{T=;l`J1ppzgvDqpa=b)_`UQqWykzZ`@QqC@ox_A zg5Q0=&wj?t*uTENF}vzl4UZ1~jq&)y&&nUa=MLFC#A4_K)RQ-6Z-=xIlZR;VI2`Mw4K2yGPFI(k-;ZMRa*6q`L)kD3wthM4vpL+;^mcF5xp6SgkoXvitFR)uU1Dav^L zcT4xo>i3$@L%H`Q)$^t5X6#h+7a=!M2tK8%XVnsnmy74c;%8Ma>&^?rJ5>AB{Cjg= z4c>$Wx%Ng4bQ2bin&@e+CXCld9||3S+UVD-8?#ZalU+58*9)&T(t5Tj`(woZ5$1`b zQ6szvxYgkIeS*z&5>qo!id-Uk9z65zU9d6XpTX3@}np?{yiafB-WOTFNjY< zL2)y-9%aa@j{FU!#m~ZDI)WDzzmMpq{;9s_$RG@?lq`Qgf)^n-VRj>RC_#=VCaRvl z9KlPGYgmPmE+aRNJdW}Dp$7V@5g!wZkMtag&vDv}I6a~?s+iA0qru4bBRh>8hic}g zEIm9Ud}jFE@SN}>tW3CFc=_FE?&BL)#CMww=UNA8>4lSDzgT; zF1amp+vj%5^~~*=8mby4MI(2#O zn%s@K38|fuJ(ExO?wK5v?8vs~?#Vrv+b{Ka?%CYSxr0-0=HAbJn)^EUWA67{<2>`c zl6ls7_IbKw$2_OJI(d!qn&q|5bI*(Il-wn+rxYY5C0AtLd472zdA}t`=Edjr$s3wH zFmH77@Vu0~@pg>Giyu7^pyn?*Vc{}sQr0&g|m3&y=d#tv9 zi8F3S?6&p972;9P36E=uI4PCKckv}PWtYQ?iJoH2Eb;i%Oy0eI;y7*jBJ$Xhe)Tx~v1g6Q z#EL<>h+$s|=W9_u$RQEG7mqK+?)KN4SgQ){JW^AG)wERpn`Uq##osw-95AwG@e;V#1?E;;9iIPTs+B;a?S-C5Xoz z;&F`V*-mV$o?@iPh713xh||QWvk=#GvpDYd^6ppgy_D36WBMpM^${D$$BOzgJS{d0 zQ~CKQW^>!Rm)L=~IQoww+ff{GEfHsl9qh*!h;Lg@*1sFCt?#81r)8$N*CvTh#$srB zXYIu)Jt{VkoqD2A7qNpN5w{S7t`H*(*Y{7uU352v?_rHNs1VWhspypB#{G_Bj#I_P z>qVbgVmE8V5Wflkt>~X8oW&wLT09;R=ObTS`>tY~K4JuWQ$8}EVu!MW^+hIVYnE6c zc2-ZMx9eL?!2g8ZB`gzn-q%m;p^-?-Gs+WZ@N93MT`aOe;^f$ixTDyAd*NIZb9^X# z`Lx6e=c{;>owr5#ICI4<^H`h@bN#Ie+VW*Q#Smk}aP6v!Y3oZ()K+OKZzn&t7b9&K zkMhkV`(NC|1~^XT-uKq`o~U(|YKwzi6kVahHi*(5eje4lb0uiQ2%XOk+YSfMDCdp#?PN{@PcsbghGV@zm0(W>}$Ee>`1l*Zo*xncmvogHcd*h|!U_>c)p+OZ1+>cxjOq z%qspHij646S1lMXzq$ZLcKCPa51DLb+Y-APn=rnv@d$i}eoxY6eZ$H4|2}(?l;w`O z-KuH^z8}ONE8>plZ6kaKIs^Ay6Zw&f1*Yb>&g zt89(ZneryywgI-SsMRnG&tp?NQ+sX&0Oo;DO;yeAv zti)Um=RqAQGdFlBw1uJhRQLqtv6P7^BP-{KZzb=Z`b#*y^33Iv%G3O0Dd)qmfg$?m zp!~$k7fjji?|qchujG|;gV#zP%Dd%};Y!Q%{O4{BeUwNZ+|W}#BtEM8K>m5=us?}z zBYD1mpS3beBNTp=Pu$;o7Ba*Y+laEuqr6FrLJRHB_ z+8ZL}E>JoeCS-X2|GkfLiPY0DY|r;2XZQ$o_w#ZL@=oQuUl~%Fsk~eHQ5jyDiZZle zP{aBA`#F*C40&|*ix873i5UzzAX?*#RTyf2@&;ec@e z^WF0y#L=D4~8*#Ux`0il-bMINEt$L`K}Bn z-*ALVKf{?)CSy3C>PuocAmt#j2ML24IvFCvpsJIF`O3#Bdw=g^I4gfWUgfU(=LAqX zQZbdEUdk~jyO2+~`u6nz4A&)_dlOE@5VRd(cjO8;RsCVrA1Lg=C6@FNkcE?JUXylwx@0H*?zPw zVdrG$s^2jKzoS|2<^R?kJA3T#-fKq>@BMP9_~B;sZpv03>2=gJ=bPnn1HkrRb`_E;Y&EPPtH zrtsRqgu}|nA8q+yd{%4zGFv0;#4yvbIC`4{{K)x^AP|HuN%>lzS4U^*6EeMClbMoXDqNT*VAI6k0gbNMgjiWVOGrq&Ib{{EM zaP;4HAy1H>D}K`{r`>Rh)dyfhPZoZ73$>YcOYAn;724@*&BkBI<(t6%*aBSq1%(R= z`3=BhuI+Inv^$V+IN`A6QOl!M{&to491XXsygw6WRoHXiq(Wn6R%m$S4$n+N&RFQ5 zc}dlcZ(`^s55PB4I!`LJLd*xjAK?(IGqNY|K<@F^-O0Tt6`ElWd>k_x72l)h-3^Ak z19_K9SH3%CXtP4Tg_+ofPgb6^oLBlQlT@Z4pAq>{9*6fAdzNU|@|+FB^xUV+kPm8T z8Fv2no&0qKdDrUm#7{kAkGA~!#aaeBq0ALqF`M0PXT&-hubRyqjhPE;$MW;nAU?T= z>3HW8BhXosmK7#7Fh1bG2op-AMCI7M;Q-m%o+eo9q`}M`K^>{QSM> zdPueD5yS(B%exsr4%^I#{mQpFL%TVz>U$CKQJT`GL%)9gnzslakNUIgv`=`_FE*c> zuI!F#r?;@-ebqn@5i2{rQWFD>^-bjiy`-JKLEZ@G;horqv5|adeaGM9eLm67xX1c& z&y{`ub6-+^{#R`DOKd2gNTWH16PXc=jfPI|z&9#7M?R6okylQ7NyN$#Zaad$tAc*; zzSh*&7I{7~t=NXK9#_!k{qCi_PkG+Q4OhldHDG1LcVp$#D>{2WT**7vpaE;E`qrhs z`tS3!HR}CkE80bC+NzyuD>t!~-gmyXRx4$j?|fQYS7NHJtS7eNH$;W2@N^qOxfe)Y z>~(mvf4XxoOmDS7Tc%YMrOG@(ijBt48CMoho-_B{?J96DnY?z~8@c);_kPny@F9j! zDu$;XzxCv)k>m~F-fh1I+)E{IB=_o=yyf0_{W?BGI;HN2(a+4|sSNTy@<}&aCc4ig z?%ZuNn6=u6S*AWoz_4e25K{ z8pu;UYqsa9t>jJOUdF^8+}lOo0`3*mAI`nK`bB(*LzMc=Cp~}EM4mcM-Ui-1y8UDB zogr^0_l8~+Pr-}&HyA>?MyYE&)%IaS-u*Ut54pFqMjrPbkoSUnno~u&_mn)%GrsM? zXzslt&y;%^HJ@|uJ$aVgYk7VI_rAzp7^}j~elON>^Cxv|d;{L>FnsLk%1q|UcUU{# zuW>bT*O`;&%{`q?Ou7VlY22HTyNYjHT7M@8r^%L5b9k!Y*cqNGN8Tdg^-AYnCGwVW zuTINL+;h@5T!xT+q?UjD##0T*tIECoZ^fZDAuoX+!Q`V;dA}C=R6ax-O6hs` zI`WO{PTn5wy&fvAcW3gha<5aZIeZ9D{TV()4@!OJso7}*cq)KA(--h&)H%<+Q1a}! zS7Wu9MWnvcA`B5nsrEdzb&_~yC6PCZPujan0Ux42c|CZ)wqyOcH(1}F4>5vLBYCRe z`vRUyA#XhQ1_tls-tXk)h#|bYac`2okPk79QoDJoQ;q#RHG{lU+}mHj4EN@c_kw$x zx8jvBTW_@(L*!Cw2A}lS_!YeSa`KM7g!lfaxX`P~yUe|U)~>wYdVK^RVhg2;y+UgF zuHSfSCwW@#efJ3DULko6xHqAgcx^bSU&n_yMybv`Ww%URz|-XQ$*(=GMxcUB~I8ZDRkvI1y zAGWI)qC0uN@qRr|wB-GK^>_FX{*-1Fp89u~xN`2y~#`m*%_u1r?`UFqcb_4eokXMC!?o~f< zZ?V48QVg+-Qrmgzc{?kfT1DP5F+`Yn1G0|17u;(+WjybK-zv4c|OZXv~zjCpDg zdDB0`I~^^4rXL`08~0jXZqEB1)fe(1PEo4Q7oRHskGX z-n|=n_xT?8+lU{}KIHx4p8Fb0-p^0}h7S=;sfxFeYVfrIPwB|ZKji@*m;wu29nq64j-btIGw}D>%~3Go{MIl`A=T`lcx{?bURCa0p5BLdUqs%_ zYw*l1@TrU|TdHrEk0Dl4%9D3Ld^w1x3drlty@Iq0+}lLnNbXr47C#ZU>r?pS8#FOq3rMmFc<8~8y>N$B6xOaH}XzslsZ#nm7kMrQ(NAmV_@BVM4 zxc7~`8{AuXT>SK73*?_%U%B^v>{s5;RQ5_T*9S1mof3DGg}xATYE_E*AL55_eBCMD z-Zxr{gcNRBV1-;cO3{izrmwBpXmX;4ugFK6e@CL3FL)0bDiF@Jv|SzZ$|*3&|_?7~W1t@fK(adCjwhPkR2xtK8d0o*(bmFH*dH`9q({huBA{xjglJws^@q zOx_airA%ARhd4pr4&lwp;NDsN9X`Y*N}Uj+FE!(->*QVLUS5;2+`B{GW9}V(Ig@)2 z^$pi*%NRdfu%+W#?M)-ee-3jD0TCb;B!Yfm5Eu?5Fb+%vQ^9mF8!P~e!7{K4tOJ|D z4zLFt07tn3 za1LAsH^5!+2s{UGz(?>6uzAeU6j*>#z#3>l1yBXl0Chnlyn%CU3R;48pd;uCyg@Gz z1j0cy=neXS0bnQ?1;&6hFd1ZmSzsPm2$q0+uoi3t+rS@SA23qE`jUd4tNNj zf!E*z_zHdj&3xuq6qE#IfE_3gDue2v4rmBmfGcPVI)E;~3-knmAPhu-c#u3F*Z(&J zL%>Lo3dVzUkO5|bxgZDRffZm4*Z{VIU0^Rb1df9<;3BvNZi5HlDR>3mgD>DGFj>GH z%|VF;xc;RP*n)DP5^w^wK?BePv;b{@JLn8NK@SiBLO~?J|B`V`0{y{YFao52-@zm> z4a@*@KsLw)%fV_s=z6dP>;#42AUFn2gA3p)xCQQmC*UPeK0JJl_@w^MMs1n8{yFkz zi$OVtbO>VQ1KnPDOZF>w{(1g^CVuVTq%EV3!zRY~^pJl@8-uP1fPd7Or0>2-TfJSu zY&@UA32+%a1s{MZp01X_0aOL`K?~3Uc!M4oCJgk`Z`!2o(Y(oVmeCZn0&bu)=mxw% zPY?*gK{V(Ml0aWDNMC6)-XEkR%mCBD9FPri!E&$?6o5@&JJs4J%&C9ufcop8GHxq1k2C>b5I;uo?u(nZq{aM8gIm#*1R(;V;RT?t3d(S05*ed zU?HcLIhNsZo@F#LW;%S!%H$>7uki1|tABxIxC853 z&_X$Htl>RwnApLq23ms-;1s^Lrrtih1JnHhe=q2Hn`P`nTiIveZTxxq2h0Y~8GeLZ zFlqQg)}7EItkC3vNfAv+O<7GPjiddlMR*+t+dv^W0#1U{fUjj?{gP#1X$_5=CO_Zm zCG)-kUuoN-Z4Zo)FAr6G$CncGuA(xW5Z6?->maTV_=b&CeiOvagzu(tRr#$Ew-bJM zjfZMN?%Z3Ao}t7?x&JWzc>XV}ofp{|;I)r4MQGOHw(yh{6Nx> z*SP*lhj5sYKxxMyj;D@^Lc44FW9CY}54-{55;$uHQokWWT{XkS5xQwcQGPV}sdVIH z$scEngfd_n;z^>Tvqs30_K1<{u8KmMY3S)2s9eGoq$ZmCYn4jkBSI=JVm zab6*Q`x;Moe)&G2!S%DM!#Bh~$v1i<=QU7eB1I5ekY9?lG-+8EXHAK>@&QVgB5>7|MMpkhU35^6$PPIN>R5ra5@}VaGGYD}p}J~Et{9`1$jf(Y zUBnH#F(MKG`mknJtE#4V!-%Ge2tqfb{w?*tm8*~)vrs+)WZbDr(Uf7<`_z_*p9`KdB zmOoAWRO7!upFok9@7fRtG{VI|&YDQl7*hS`quL6Rvt}^0hY5Anj1)V>+ep>GMVH`n zBEYY~81z$4$?u3KsM_g>r>feSh-U!4onH9z-TN6g>mojWFcSQ?oB2pq!VZ)p%R{ny z9Df7rtf?t{x!*GM%NGN>X;uq$))YXM32i{UnSA~0W7-PN@^%N{9ai-_hWI4)JEQV1 zBEF)JI4&+7|6PHf;zx8wo~q7>KPDASXXK@7fH#QW13vLjLc42>-WzU#@9=ulcBbzQ z6E{a(0`ToDxk|X^Wyr9BDo3bATu#-lh`1`?8&+5OwGh`;wHqLI2E1Q0mG6qUwW{3? zu{*$zRry+V!R%|}?U^zG4BDRVF$X@p#IolV+&$(-F@ie-7#VKlAvp znuEq-N-X&^k)OXD@k;X7kgg-$2vsJq1@U(Bcd7h6i1(3yko1Tue;n~CNoAZxd;#!N zb(!> zzV?w^MUCfox-35`g=2Z$k!S9M54 z+(-DXn*OSZ#b9Csss9kt;iRLee+v5Z=gK(McKxy40H91rN<-&KwBZz}lE^?jo&4FP z^GLIyO20*j^T=N&eECe~W2g9UBI5OR8g|GJaE<8arddb2`2)ST-9`iK6a&atayR0= zs`dfIhXEhx80kq>{v6_q03R=k zG+wBiCQ+!Xrq4%wF3CGsEi>F+{gE6<8w`Oe6Bv$olxWL$%Ys@=H}<39d6$A*Mb$Xt z(VhtK_b$y;^y9a6rmBB!^sT#0l5evaq9fi9K$RWNMZ7@OUWhnX)n1Bt1>gs;O69Lb zyk6Dbf_OXNZGNh;$Y;v;}}JTBB(b4ryzkN6V#S4nS>-X^_A`k3?uR5_s6 zh~JU_k$mxr`VEbrlrZ|lH;_-U<`c^Z#^=<^07G!fiXhJ?SX}gzpLeAYmlnRW#+KBc zv^=RJR2ioVVkfyRiFaBx(WnFXsccBvSneS5O%b;cZFyp?FtN6PUsHFeGVlUSa1r3+ zc&N7ThS&>m-eqIm$T*y9mhQCp3S>s{`^N^F5;!a@2**;I{p>#9$`DB z-&(Xcihjb)V;|rr=pgA4(&MD3NY9d9f~qHeRW*?!%kgJX>i2;3 zG3g7joqQm#5x@JS`f%tYj4#yjyHLDRk{W-OCuj;)j>H^sF;%-HVk=eK8nG?l2WC%N zp41U4#*uJJtDsRsG0?7!xSp!r5OHI`2Wm>%LX~fYxUH(~hPb1u-5GH=Roe$~Pil)N zp+6cyfbSqwsH;ZzS^ilQiqpw&r6}@ah2LG%8>*a%DD;T|N;?UC`%?ekXq>*P{2=6p z0$f5#`~`yFd?Qo?4ngP9VgP4ND(QH!fqd;IBA!hCG}0`gZkn0Yo=xp}LR~f4G?7e< z%@1@je0+CS9&ia7`J$teiQt84>VHk=o5C*ggUk$Z3WeryM}L$DO<(i#CFD3{N>n7wRO=I`0G00#~Vmn zhmeMoMv}&nCP0;OlQC8%zQe+A{facs0O~jR3*HDS$2Sb-NRe>WNMa)R#Emu>BL#<$PT8vASTpZb6GWkDwsfZz;}2}^p{V^O~iLp?fZxy z{n^GRCQs3L0r-Zm|4iiPzeD_y{4Xm1JK|rew(&Roat4$$XpXoT;Nz7fwGw&x0BpXo zj0Ak{!1FyMnz21~tVmj!w3^sZK9U-U>#Euf5Id{dE{L0pw!EX3*pUa`p7WqLLN6%p#Ji6u=W z?E_U_zi|&qx&f-0&BWk?MF;taM<5R~Fu1Qn%9|td&COnDyO@S&C%0N7w{8^-P zRQdVe@EV;%iN&hK62!}e@2Xj;+Tm{OY>lelI>Z}AKfHk^-ATF|s!U`*;zOb>Uz(#B zvkm_!qXB6kd^4L)}CCSorwUg35o1_>J(pYu;lV{!0Bu zoEuloSJk*5;QbK&P%84fe9^zb!&K#8woJY=Z*!nrrDEumtooeHWF`K)BJ%vMDNO?u zgQpy8S>)_Q-dW>7S^=u$DyW1eHJ)Pz_WEH9>9A1h|4$pbcmT+!o_MXLum!47vj!&1Jl7wFbB*7IUpA-11rEvuo|ob8y4e3uuTZI zfn8uX*ar@Q6W|Ou2QGnY;1;+C9)L&S8F&HSfDhmk_y&FfBm5=b6chnPK`~GY*a9tZ z0Oj@Cm)c@Qt01fnYU^vh#AhOH5Viwuzyov!-GLA21^huU2nFFF0>pqg&>JL!{$LOo z21Wo0q=Mf;8kh*CfN5Ylmzy`1hYytbgA#eno0H?qi zZ~aHn1Ujp1h54TpggDsYJ%FJ zK4=JegPBwq0ST(0VRMHCh9fx4hRa0X34Q{W0(fp)+Rcz~`;is2+7@BzK_ zH(uj}^hMYo3<5*IFfam0U@Z6@OaxQFG%y{^136$BSPj;J4PX=426lj5U^gfP`@jKk z1RMh=z!`9E3A1;+fZz(a25x~n;2wCOPkn>`6a5b1C-4pY0Kb3<{@P#)ih^RG1Skc{ zfU-af%7ZGPI;aWif`-5uGzG4p9q0f$gYLi!_<$au7YJO+x;Ta+2nP`$4kUtP&=(8> z!@wvYfwACskOn4#DPTI73H18Vw>aO+5Uv0#!CJ5mYyg|U4zLRpf&<_XI0BA=6W|Ou z2QGjs;2O9E?tpvX0eAvlfLGuh_yoRyZ{XKb+<->-=+p!h0mVQGPzsa*Wq}qrfGVIm zs1KY$6VMd6f;PYnbO4<}ci;tj0Dlk&flT7U&=>RvgTM%V<~v;M zi3rod6p#sK0zH@ma=|jN0;~pW!8))3Yyw-r4zLUC28G}NI0BA=Q{W7^04{+W;1;+8 z?tv%EaB^NCcmv*nPv9H)0e%4^d`x5lOhGYF3Rr@uJ%umxJ+0Lp_Zpc<$SYJ&QpA#et+pbcmT+&~A=8FU9;pa%#B;UEIU zfZiYx^aTUJpcS}~Ll6uDqksg)f;5m0rhw@{59Wa!um~&zE5J&y8mtBDzy`1hYyrE$ zK5zWv0tZkLR01_YT~Hr5 zgQmb0v;ysb8}I;Kffw)r{vZ$pgHZkpc{qpwF`zdX0EU1OU=-ke#)99$L@)(R1DRkt zm;)AoMPLb70ak+5U>(>1Hi2zm2Pgywz!4xF!@~)13S0tLz%_6OJOGctGw=eu1E0VT z@Cy{d``Z$r6et7A0$bn!%7aRv8mI|sgSwzTXb7A^la;vs%@MQ$Z9qHF0dxl4ffwij z{6R1X1>qn9#DF-^8zh2c&=>RvgTOE_0*nHwU@Z6@q=AVb9ZXqS2B#81CYTQ9fCc)@ z&v;_2M7Ra)0J}gT*azm}OE>lKM&B7U1Fb-N-~qY;AK(Xqfeyrg1ke`@1jE2+FcwSz zQ$QBbgZW?)So#BhiC&4I0Bi!=!EUf0904c6IdB==0C&M7@Ep7WAHg@kezFWxU;#=2 zYoG-cKow8})CG+|Q_vE$108?jzZG;v;0=0#AP^3sL2u9p3;;vHC@==3fyp2f%mVYk zLa+qngSB8I*arRp`@msv0-Obxz;$p3Je2V847>&(z*q1KXnwJbqM#%w1MEO~P#IJQ zbwESl0$f2`&;fJ-UZ5uk1YsZw#Diq;8yEscf>a=l$3r^E05ic{kOT6-3a|!j09(N> zuooNx$H5tJ5nKbe!2|FVyaMmR7w{99Fry4}Py&<&wxArS#Ehg2Cj_-Y17O6gO7<#X zDaNc`$$*mi`AwLScWd(B72x}OxG6iJ-354oo*)o}fhZ6UlEH6a2p9=c!FZ4kGQdnQ z7vuoE|24{3fnW{T0Jef%U@tfXj)ODcBDe-_g9qR#cm>{rFW@IIF~U)T5}-7&1?4~` z-~?)e2A~OO0onj}2@jotC+GnJKq!a=aUcow2ZO-~kOF=OlfX1E1Iz*0AQvnLtHFA( z1?&Wc;2<~#PJ;{JD!2ut`*?T)UV?YvGx!0Fjg2yjfa1Uk*Z>FM2&#cvpgw2}nuFHB z4RiwCfiLg}As_<8f<({{3`;87KxUL0MoADw;Gl%BYH?Xa!LGKf?ob z13thH@Ei0?f&S$Wt-aOUb^J@82k-@a*3XEoEV)Rp{i&^Jx?~-GaO-`3YRed*t{9gM zvTT)Qmww7mt%KhCr&d!!z4;OSnV(u)qZ2~Pp2H}-j2kM`u**;*mR<6SJitv^?g`O6 z#w_&Zern5gc!5n`{cTPk|7Do3=%XGQ%l4Ga#*>X7$1izo)Ia&9t?#n{Tjzp&unuen zJHQ^W7aRh|z$tJ6TnBf+L+}i|1)sqWV7wl0HD)%nFH<%c=W#vQ0nUOOpwXs4`|2jr zYlNQx(->t`0d+ug&;bO1{$KE0 z!6#r_6i-*s1h@kqkP6m;9pD1E0&alY;Dv;TU%<=)rx4TzjX@jG74!lzpdZ)*_Jfwi z@WKV6!C){SYy>;NAHb|Q4gypL%|T}{6)XfRz-n+3+yc@wJbVQvCGeC4uD}ySg9I=P zqys%z0``DQ;0bsOOiCJMSc96N1?Ud^Kq{C8o`TolCn#D9_ZV;i-GFZ?Tz@|VwJmYf zpgC{@z90tl10%p_Fa^v9i@-Lp8_B-?1=I!&feUB_+5->J6?g$( z;17a97>ELKpdZ(2OxFkh#a|kT#%M4Oq=Cs`8aUU0>2`w|@l3a$E4-D^+0gl5F<1)n z!5Xjw><34{ac~awYRGgqq4&W6#ACn<#9sj`i_2FQ*S{@DpfkaMOTN zpe)b=M^Fva1a&|I&=|OacEBBU0$qVG2nA7~9~c5gfabnTHx8Opva28w!x4>QS7`z4FfT=Bh(t%Q-46p^|K_yTXR0p*{UEmB{Knu`L!b3;U z4fp^*5ClR&42TE)z+f;8j0EXm7MKfiKpt2I^1&Lg4(tGX!D(E=OOz+%CvIZgw%|*C~0xhQlzCx%aYoWI*?W%twdT? zsKi)xGHQ|5C2c_JOzJ|~oU|or8`Acq9Y{Nob|v*B^&#!a75iuYWCW3hlIlpKNMlKR zlO~b&CH;+b5b03T5u~F@Q%T1`@fSzN(#V)ZI)yZYbUNuQ(mAB_NpnaSlP)1$PP&qG z4e2`4japoPK7lP{Y$x4Cx`%Wh=|R#Xq{m55k)9>JKzf<<8tF~aJEZrul6;drBI7CP z3)0u5??^wAej)u%`is=qULMbs)SR>!X-QHmQfr9txwvB zv>PH$#8bTUQ8c7;M8c&)?+K05itP&eY#t_os zq@zevNXL?nC!I(-nRFUy7U@jV*`)JGvq={T#q%$ZjAf+xq^n5_NH>sfCf!E5lXN%f zUeW`khe?l-o+LfPl|TQ_lW~dkD(MZ<+obnMACf*HeNOs{^eyQJ($A#dNPm(VIneW8 zgMdE`i;!B7mLRnxEkkNUs)h1jYsx_@l2@6u8fgvE+NAYJ8_FuN#$+@lZ9&?Kv@NL{ zX-Cq|q}@opNPS6rkp_?klZKH-2*vr2CL@kCfi#)4AL#(n!KA}TN0LgUV@Q7|oj{sS zI+Zk&E5H6T$k3C{C0#(ekTjQcDd`H*RitZ4*OP7{-AcNH^bgWPsCfO~PsSnAqogND zPm`V_y$ID{;#Z*8!7XqX@O$V1>0{Doq%TR|K)e`4D!HIupH!rRbUM$0PDA$DPzCZxMs1*rh*KR1+e0IsTpOrmaSVJ?~d?Bx1s)st$h`%#%MMLEx;}O=cZQrAGY=$(t8KI zbp@;(KA!=$peASpIsso00wO^iNCuO@bg%%d06V~5a2T8c*TG%z7<>mt*ky509#rS? zR{WU&v@LI>-)CoE$;t~2pRN4kgt|6XHu?Em^dJ5ob@u_?#I-j3Ua}<1k|kSm#e*wG z#s*9YB{=jJS^(2R2@uml=$+7vD4}-(Y#gdU1n=Eni-9RT|g=m)|zxXe2fnHs;h@dbz*vw?u-5yh`|`* z)+|G;3zMR>E^#DGDnfWHmokK1l0pdyk`z^LJY6PZI%Z=I=3zb-F;p6HB~~MEa;cJ3 zBzOm@-Pq%{Mwi&msD>X&9~W08sUT~8AxW_@=gC~e&$vbh?*D|aU-iu!vvNVHnBN1c zf5q>3yiE))C>3Ia1ytEzkw?Ov!osCuiIFsjMj^x@ zo)$T)*>q5ol05un|D3W*^b41wBFfUN0xHo|o>{3x_a+-@V$YywMzw3XJu}4BaAh+- zmDgFD&UMfL4bd3Q&=RfD&V3G|VT4q%Tp!W{Fo>$5#Nm+Foz^KccO|ZWnC`9!DK=~z zO($RqX1I;awvLdZViuBK;+|%dlQmxCjVh8N+3{D?EFGJ$h34N7zr!}X502hG?8`>=)6zF58f9j}&x7 zXPSIU%-yP@LyT0U+(1%8FwA`frj=RvVBWMSKic#puPk=ONW~mu={5mVFaxs~LVi9@ zj{M!NskIPuT}<^-tf0vnV%{X06k@AxA@z;>1jK042|Gz=sDnlqyPh{F^RZob-nRPJrDDRDNgE? zy=xzP?lwSb9EZmYv49XPAIPGU_wy2L6C$5%qH8?rpCOLKNyB?BV~iD8g*8}*_1K8b z*ov?5EnL`v-PnVJIE-UBjk7q9i})E=n06*H=TUAFTZ`}-@Y%^L6n;D1wl*_Nsc0;ed~VcwZK$VRFN5$D=JlWEGD}QtFR6mu^C_Io6>@!oO$<>J}7n- zRZfSOib}E8<77_aG|mfCF=++EZY-u;5S%L}ooIN2iaWT6Uy+5!c#0QzjknP4;uC!M z!XI`7BNPP@fhcjRIG;?E+@;AZL$MrkHbjO9OOzV3A-W|>CFPwlQL0L~L`gZhZn^FL zstGG0Rdsfx`zQDm-O&@h(H{db7{f3EBQXkNFdh>z8B;Nx>C7h1!+b2nA}qmjLiuA8a%p2oKDGqauNHs-se27+PBOIlrSgp%d zuQYp(^HWki(Hs3S7(*}&BQO%9Fb3l>5mPZ8Gcgf^-aN`AIL;`iR>+DpMVtKy@*c z%-6FYRXkU-%1ZCXRG_vJs-PNbp>Dp`4`?0pA?Y^mX&ywi=I&Rn3FV|>){Zp$1l`e7 zbSS6n8iUG7RbxIQGZd+CVl;z|Bj#)cmpE5Wsu(eg)LaNGz?bgp6p7{8?beW9FPfB> z0wa_c(cN3Qmxw33<{i0RVsv>n>n5?TycFBvYdU?4@39jZ*vr@li9aG|8uIbqeH6Kz z{Qhss%QT~<+XWAKc_+#3ClcSyK0&-I6)Sy-5wGF~Zs8t&MHU{jR{7b=obEo?W$#KK z#du}#e8UH3Sl|a+27j`i7$PDn$S(|0y@K*$8(KjsSv!u3coao(l)^hGi}I*|_fUmF za%L-Uw`lLY*}Ftm1?41VNRmp%G^2G(e1vxBfKKkcMT;bMy?&$zk(M8fd}7ZXTue)n z>NrxVc492XVFFF35T_xp=?g|OEF`r=M7$?8Ube{${Au>Mdc;2u_Bl2%2mttJ-Mga z3~{ca^hJ}`bbbrn9uA@~!WUN95rP7c5ROPhqcGz3XflfuizDyBZ?DAFZ+%jYsLn~t zYfm|JUD@MOB=;kWwz9H^g;ka+CVWV*HfW0!bi^n4l#zN8b4GKC)XKcMA5O}N(NvGi zSAC>1Pr!6ivqe^AsbFb&^;?$Zy;qUTk3P!(PD{QH$yq^$NU9<=F1L^o7GVjNV>QyT z30v?LzQK2HkJKf0RpE&_O8Nv&QKjrlr`*;iv{gBb{Y>>0WajH#t}4ABkj}#IYVXWd zZZA?Xv?_bzbE;qB4K#b%HqgU+Z{AjSi94()ESPi{X?Y%xhUT3RT_U2I`bKWlr3bkW z-X&^RY7lMOm);m9n?buG(=-GVVy0AEk$B=saQ;R zioGf3Y~-Aag`9GR7+PJ5atx-)FpPA6AUt1bro3&iN$jF&!~~j6!3_1m<$LEZUeF|X zKGh4c#C02oOP0^TgcZmb@# zhNGlTh#NJO6T(x*OuRtlC0wOO-rULYf97ph9z_15YxSB^Y~o#--^W8dqFe6U3f{rU zo9L0al{bW#Ra1&G{Y_KNKIJW2O{o}%mpe73nu%62{;(qu!3aY^L?CLP`<~IF7B7*~ zq|1q+wWJ{ zgJ~V@pv?i<$Q2q_qGr5R^umMmOC0Ce&9aq8XNVAAxre_0`rXF?sJ9ct=4x zOgUEeyrWEH%YYMZgPme9R;6tqooHca3j`w!1rdQL#KM6hD29?KgK~HmNvMd*sEX>S ziQ1?mUN?|Joh@1X2<`AOI-nD}pgVe^H~M2RhF}CnVid;Ub4xV%Yis49w6QjkkhEh#-JbNSQP`BA+ z=3pM?V-c1h4J)x4Ymtr(*n}TG9FiHGktZ-a>njjRGTlVTB!m2u2tRA_7r}g#$$nYBGxv zb6=fePcl342c(jz%1b{^mhU-`Zj1K$suG(})s=J)^vYM&t%+38I+*k@jKCSoI=Gk%)(qOz?WEzrC5d)ScNrMkB!)jt@sw(u>-rY$9)bw?bWpT zPZy(#Kjwl>qe=ODp03)dm!eKH$!fks#|OwdDB3sUgK?J_+DwW~_>0tYyu=%iar55| zJJ5{NtmzQj9`-|S8;>cZWIWB?6J5g8T#BkL@1QBy+^&Tw&usRXGvvqC7CwbS`de>U zC5DPbA%=@17De83(v4y4V zLb|(X*g`6(+`OyS-~B-hZ6TEm?oW$>7>r>U$sl9IE_$#5FSX#tz;v>F)jE1M=H**O z@Q2)!UPgKq)?gjhV`IK%sUPy5atG<%?rAoby!mrJ0N~Q)L#brxLo_>zlQca`R9;)$ z+t1x*baOu_aIMyoIbNmdb=<-|_qB-DEt%sVr2oQmyrlXKQFGY+nb^}(Ik#SFDZN|T zLX8ap2sy0Dl!)Pw=Mv5JsvPs*;ji9GdbeIYO^Tv8O5q)prMvsoOEq|^bk9g~yYtRN z+VbRi?v`+gJ*}iN!8PdqKI-BFG(j_AYOOpmj@DAKhz?{rp$od9hx^Jzuh#4qpOGG( zFTI$r@$x?TKx^gwS6CaVSkhP;O~52f!8FXmTr9wsSj-^Hh^vwNfJAB=_OTtLc8g_g zl!@(XBfT4Zh|Ccj#R;6E^EqMqD0}K3@qFDNdkc5)Am98te8j2X3F+tVX*N+xue|pm z@+)s;iY!m?VU;WY|O)ank^zOf!w_BZ*Cu|i0bX7B1y`DYI*jh zdw{-BhKc*H}fX3tUXtDd#T!w!!*r%?$1$WKSlanzO>6{3WuTcq~4<{H_e_TzbJFdVjUwU)S%h>s87>I#AGyu zyn$`$d55D-!rp;{Fl z%m8IcRS@GkE0+x0JM(71B}#YUgS~20)Icq~kGiOjMrexW_zDZdWmhroqE#HWFjpnsT3~n^Y!-ze^T91GB*2CQ}Y%^0_l_1bcVx zo$}|(qSLUFCj7}V<@Kex>UOVG-rA?!U+}%UbNbjy)34#ej(jU$*IhY?9O*6s3Rckdv15^viVPPoqWOW|;e; zMGvW@@;*?$IV>Lv+;1zo#Nr-OXs|qr=KK=|&;Art{-lLZL3=PC`5mMt;8&$*sfaVV z+d|Ig{|1P)r}VD!u60XT-nPgPEqY27oA4LeqW=Pal`Z-u_{(h3+G89pVT1`*1Rw}u zD2OP;!hwWin#@FEX_ON;7?!;?qL;LWaG;k|(bcVE_i< zGYrLWq{4~O7>fz4bP91A|JQchZ)CW{slL1%>eKuKB%`_e8ichU_k%ws-3eXLHD7gWs;&J=55^D- z6ASt&Uv{|DPpa8%9GMB2gejPY8JLAHFc%B(C4(*{=4@1Xho0Hya_+OH?D)I$?)b>% z~jM~2u%$tlm){WD}`_6PRob)r6F;R$7B zY2AO!m)E@OcE^f#nfG(|@pO5dl;e4y;>l&tdRzOGqS-(xjJfn0sBR+n&vG)v+<_dr zl&)?cG0A1OmlLn1s{^G@&O2IVSl)Bz60ZlbuYW-o zfiJQ2g!{`dX)u?S>7+O1OLrSA6-oS#^!M0Fm2&KHKbf(j{BP%W4VGd{AE5am9K{Lu z5owxl>uuK^LkvOhoLE{55?;t%H><>p;8e?don5Lh|c%~pQ0xNo-vWF>$^i{(+SneZp07vMY zbHyz z6HSIof#sAT9$d`b+;Um|UXf?_YG>Sz24cZ*-smVzyXBqWHi=`yr6TQ4^O!E+3U1&I ze!=f}f){uVuT$(Nr})k)(H}W$lnE8L$-AE@4COynO%?#z6 zIr0qieZf;~P33zPV`x4e6ET%;{5$`=y@RgRZavH0xkS0s894oOJ=^ks$ z-);0rsf>La&G`GY(HS(|Pdq3NjO58V$>M4E-r~hbE*5?!ea$`1T;#6}j~fQ*DlM58d` z5RalLfl?@YT9a9USQ*vDgHckM(#ipW9a_F6dCYXqQYhpjQ){X}LOXm+mrlgIsW%+W z%POZ^_BU$f&-=Dm`p|3u%|9dN>}K*yjc-xYMLG1y-zU{+eA859hW(UQdG13k;W$IX|S@9EOPvH#C;Q}trlf0d?CtWDyqNQ6% zeX1JKBw0N8TzTJa7^l2rP8}!JtlpZ6kI)Vs&|5h`1GbdPWmth#Sc7#~kB!)huj%X}ZpSWhYP>SDtnqxy&Lt8jNR6wX zq2e4a;4-e_I&R<=?%*DNMHYiSCjN<>?Z~~~37l+v&vFnGHzx3cl&`Cl#aUG6tWY)nra2PR4ZPjJ!!`C$pC< zB9(^aG|Q=SiOQ2XT&^d*IbXV$oOY4kkuSYiPVXaq$Sut&gS6oU=`-T-WaY&cF-0nv zbeYU`8r~w_L+)C6Ao*Tkl>4@F|BSfb6n4%>ba{g3c;&V}?rTipJZ?P4#UZTcG?{jy z`{?plT5`+6G?mZhC7MK{kSIM>3RK=~YqsTo(aT4hyk1?VO0f~e=#hxhD2F6gme*v} zRK62Zn{+)iph`ZJY$^4-+b9pFN@dD5r}{&*L0hz^Y0e=`K2ypkVfn6}dycc@G%2>S zTqWO-$f=U=8_2oLeZTxeZp-A%{5i38np8#^BKr6H`KuTrqtLB?$jLWv^Q}t6Oy{hd zA<9kXJI&phdmjwM5Ts%ZCSWRN^0?*@7hnliV6B)XkKhukrb}fa_K`V&BRGm<`L^fN z>B>c7@C+rCn;})Le2GR^aUC~s2lwy*S#*9({1fu0aqj7=~$BgqsN1$)1HK=!mIs;Ya+8B)& z5o~<>&w(<#oxC|`p*{Ly0+wPke!^1}$lzuux?(71U^&j>IZE(lrl#nP37CiPaSpff zC(L~LB?9Hp8vQW_UpV=(9GkHZXK({g;Iof+aY(`>9Kb_Z_=x@&bmNohaX5@8NaRD* z=IDri_#6lD7+!p+7K{?8g8KLn&W`-(jnA|e(3(YYQOK<`&;Lk1BBB+F(n2zsp4GG*< z>x@Ad4}o;-##vZu^!V^=&=sJLJ_nl@)&C(V%-?1Es!~N4c*@ zd5}l{As+R^J?cm0);qHw;l$iTc6q8td8Wqzb3MunJlZevC@=LWugK}oZ%J1k;aZOd z>pjYwayqE(zxF6^^Ju@rw9DQSMCH>Ar$)9t{R~l!s7u zZ=dQ>KgOeeoJV=0M|oN#zca{vlg{>NFwdjBz@xl`vU~q!9`&m|>eqYJZ;Ir12Dv|? ztsV`&^B7={U;vf9`5a(oNoKNCnyi_=rF{iJi?_u^r+mJj<(81kb3MxQDZ4+%i#_Vsc$7DIl(%~H|1Phbeg5C!(cy=@ z2FlrSpGW;+kNRUC^`|}RFL>1doLjG+|F3&AxaU!R=uv*`QU1%L{KBLB+N10ht?pwv z=YJy!_f2f^DBC^CAvpu6r|*Iu+NKEdhHpoB+*(jMjV9_=f7)K~K;*Yapz*Q4I~fk%TT z9_1Du<<=hM_8#R<9_3Fx$~`H|Tj-*6B1)qil294dQ5*Hp5KYhmtapDLXP*Bi zEVMvtv_l7UK{xb5KMcZ9q+&G2VG^cc7Up6B7GoJ!VI4MNE55~c?7|)#z!4lv=Eo_V z!zEnB4cx&4WZ^NM;sstqdzmc)6RfZ!7-5JIyVgmEoc&_7$!oD5o3It%z=iLz3mMpl zgE)fIIERb4jBB`uUyy}A@Dwlc7G8z4$wruAM+gccvJk(IGKPg3sErTM7){XvtgMs)A!;p$G7>9|Ng6WurIS^Qg#Yn>ntj0QQD8%oh+|0r@?8aUkz;T?zMO?-; z+`?Tvz(f3w=Xee8SZ%Ti7Wg9oq2SwB$x$c-2b!V|MT-ps%j{s-XRsMAg5>XZv zQ3LgmjFxDRF6fB?7>3c9h#8oN#aM~;_zK&RfkQZsbGU+A_yv#g{Hn&8`IZImYnn_e zf>02JkbqKn7gbOjAD|i9paVWd9}Gq+KF1Wy#sZ{a4L0Fh?8H9&h*P+D&8f+}&ca<} z;R*hR*L6*%2{wcx5)Kqc8N7$;sEfw<5be+zJf9wkv8l~D@~&=jqag0AR|K^TFtn2cF)&gaKctj0!s zgB{q5BRGi*xQ07;h(GZX+8dfoANV5#5h#pecn3+ShB|137HEr3(DIQJy!mm}??aK5 zAqi)vSbm)zChyUtbSL&gUkt=&u#+D~OvNaS#RN=-TsMt46JKB+oLOiDK1Wsbus1OZl`5@A7w<4=vSAHz|2`W4PQH~5s}CLdbBRcfD< z);OZyQm<$u&rKDJMl*b=mtuhW5n+`HSpfwRjtE2|3eku`Ay-yF?6V49f9qU#8^n$` zxOz8<{UO#>YEbMC-?{!MRyd=W>zkH^_jYp)*;Dwrk88+}h0`Qg_?^O$om^GwI@ZSW zc5|U-mL@&@tyYuL{By4qt!ApG$~@i<6McOQ$#M)-V*z4L`)9PTQ$?$^udlXGP}`MN zW0D%HsIjIR>#DJ#k3lr<=_pn(Qui6DoT25Z%$=xHhVCvl_H=}n?aLZ+s^vBJ7?i!{ z!RnfYI^rTVE+dM}o{k#2NZrq(axX`yd5+rjiH~UC%Mp^a$YaR)C$ooKuC$BP1(~uR zd@(haQe!zWx0j7y&N?~a&Jda*hO`vyOp{~-F~&r5s}*45gK;e zV-CyjWj_JGh>cX2ex&w3rS`g_#vAHrzpL?y8efR3y&VOGt&bxp$w$b~VWiG#mLEo> zEN6ldO=OkzEc~4|9LQ*wG zaIc1pBQ&A8+U$3Ag-_g?%X>i14$@wv z%FR2g&0na^-?%lGNB^Qt_Ll7}cG3KUfogM|Z}yYyqeiP51JoF*#&BZJWaYIldoO#q z@r)xL^>q{w(f#<5+|Ln|FkcmigJQ@n6+` zkJR|oA{q^Fgd`MD>LYb8)%;tc`xf~5Q1;8EkeJ4B_2OynUftqRc6AALHDT85X-BFt zmYCCCetvHD%HE*tJPemqP3`ur+WdPp?pEVTx9;-u5fYNUI(D9c94?xB%%)TO?CLh^ zu-DXKZmID;QS=?i3-+a&spXeF-+F3nBo;GpOp03ULd==Iy#BPg*((?(vS^kt%47YZ z8QIn2)%EvMhZ!Ui2XPqSZv^pLovInDrYEX#nrKIL^+js+mufmqjjPm{uEx!3{9cW_ z)wo|QW*GZgzIajZxHgBM&lhxQljcwG}Lyd#gI9!dRMCBnI z^7{^P1jVGNV=YwcR;e*vjhn@^A?(9@)Z#&+`@{KWTlT{lEUwZle1tmMDYf}|HC`r) z=R+K2O6n32~|f6S7QuOY#GYq zDx+p9sGmQK2qbFt$|7L-BGt3bZreB!9 zzEiX8diM39`_y<;jaSroLydRU_-l3)mxnt{Ue5zvZ-zU5@vc>a>zG=waaHy{u`U`Rf^sxs z5r<@QC5WZbl;!655Un7>$2!83dr;FG{V))pQZs_+#2AdjL`*?%>ZTKCVIa$Mh{NRe z7>Q936UREDoyVy;g|oPT+0cBY%f@9|0@}Aq3%wL^SkV z*coAl6*dGSj=B(HapF5Di}Lfm!lEm%5Kl!hlt5{eg%~l>@j-MIYWq^EMI1mJjG-6- zC&c}Uj#|$5sjZ9p*iZHle#9|+K<*r|De)q)6|p@ovz$Wgh|c%~pW+$$?!>o5FRsfB z@PRM%{Q*ZP|e+)!B8Vn{5!AN|L$>>ggDzQItI&l~=6?0e~ zMVv>RkA+CXY;tokAImAP#A>WXI==BUIFnseZlJIUTkw@!fivX4BW}a@*ohyIfxXy| zAMq1T<05oitNl#OgfGi}up5L2f(3Oglg!x4#J$^M4Nh^AZ! zZ&)r&j6*z%BA836;>33l!Eyy+G%*%&NWgpKDxnIh$;j0p)|IhbpZLK7j!TVLNXEw` zJE99dMN@Kpi38A_duBt;M!S zdt3KV_eA$f7o-o@7uFZom(^F&*V2EWZ?12v@2u~sAE+Op|6D&+KT|(nzf`|Qzgh3n z@75pEpU_{>U)SH$|E_d_TJ$AjrUICUhf~hPZNLk zzUlqI`?2?PZ;jE%SkquL7BB|sqmA*#Qp5_zs>ToM7#kZ~8b3C6C85*zG3xj^nB|ei zF-BvcPCwZ=o18T;m}U9@`IOR(D~%h7Utyau!?=m5>hy<=;#6cpyz?Yko&J>ZlJSPo zU;lv8W8*X9TcgoO&ilcirOk%P#vq??pBR=Cd`j~3T}(Ds^?7To;}fJ0(l_>L?$g$% zBiZighao;reomlvhL2!rsm~gp%|74x{NQuY=cvyapUXbCe17fb^QX_>K01@HDbQ5V z6l*GGDr2f>s%dIqYG!I<>S*e2>Teom8e^Jlnr&KWT5d`=eP!Bi`oW~rA2b~|oi|-G z-8KD2=`XxOzW>eF4Zh|;b3t>gxtLjgxb;M4WP;II$}Bf4tNeUV`6-tl8*7;Bnv=~f z%f4a|NgWX3a9z4X@Yrzd4|$Mn1l37m8CW2&1RQ*xA~Cyg!zK`I{AC% zEb~+IYqP=E>Kp7E;p^}%>idrGd%iV%+q3QKiyLzsQC>gt>wMH1y(drWpZzH5wb}6h z-0Gk8{7-VBUe}ei*7e@c?r>U}XOPpb4>0`fd(&6mRKM`^58nX8GvBwq21}6M*Aid} zwPf!uI(?L-2+JicWh@!=uV|@hnM{0ZYCu+}Z)#~{>0s$*>1P>aNwti#Od~tj5)l{~ z7!?>D7!z11FgEa?l$`%l<$s~h`9I9ESFeQwqGiA|*gL;m9@#>Riiwwbn* zHk&=!KGS~EZVRXuFd|@Qz^i~tfg=KU2EGcc6f`1eThPOx_~1{2R|a1Rw%Ec$l0#;O zq=jTw54mBAF?TR8GG8%Y_73;G>|F)f|Bv)d)23;E@O_h;sbXnwk?WRNu2~|j?X63! z*Q{bo^MLr!NWV1ghko<;xeW6E7yK@Jhq`tzckJ=%(AYo4ud81lKl!pp{=dE9GrwVe z^3o{3iGH^XGpWh`Uq0u|_e-O0wciH6Eq>cr%AoeJ-*Kh=M#EXZi+)ymUZZ>`r=RH8 zF(J_T*0k9Wko!u@@_Xd>oJNz48vh`@kH5cvC{dn5kmV0n6XhT0U&3FWgq$nSQay7G zbGly@|M%5)I(;LSKJ;%xT}LICGtIonl~v9fJ$G_>=x+YK{RjJx^#8^%-haCPT>tvM zUn;9y;lJMhYyWM^pg;H@B=?iQKi8ON{jcP%NbY}|l1~57|4D9EF2C~E+vI4m1=+%F zI(=cAd_m)^Yc5VY@Bgw?Rmzu<>vZ~Bwg%KRwYA~rsl55CJv-XE*m~La8~-p4Q2GwH zjkJybhf(s@lDp)cJ%E^%mXK6u6PMQ6Ym*yjf1tXi+@AlsEDu(*x|T=xAN?=y zA92#Z9!0K?ZGXx***H&mq_+%9nDJ`cM%&jm+y8Ra^5fm?KiSxhOROOM4qNV}U^%aF zrBI@~=3md~KUitrhL(4JdF8j1E5Z<`^?y0)zuxxQ>)K~K%mL_Mw^D07Um$YYeX{a9 z6`lT~?XvBb?N{4l+Y4JG<;9}4=M62le{0&J?p;B8lf97HVt1H>?BVvp_Tu)k_LBBW z_QK{wyIfO@np*Y*d#t&*{R4YYb9-BRXM1CNOZ&(6uJ)$(KK9S-^3xLUblNAltt4-C z|LM{UyZCp;{byU|KV5ZpfA{UXl&P$-Z?=E;4^#RNPD4M~58990&)cur?=o!GKMXCu z<&-DkuYYQPVc%@f2ABfu0igi}1H##3Vgrf=lnJP44yYNRK3@41U)k9ppjkln)cuuR zqyg3PI3JO{=GRGSoZvIrxZh~!ZEldhM}Y4C=4foYM%xak~>F=w2-(UI!of@euqU|^NkA< zqrP(Zxcc69+}2XTM^ax=VGD{YD@v6OG`mLLb-be&X-6Dp@zpPmyRM7BI({?QnuMGv zex~@BVHb*PUurelm!gy4Nn{)BEvhbs3_4b zu)I78zF>655id6`Apa;VGN%N@xf(rjj0q8s*ks;6z4dyt?X9ThA18^vadCy@te+U= z#|C-FH?B=?(MN1Kof2RZzwLAc$;AS$3~QXE6OujdEAfsguCVKo zJ?@ECB>Tnb#il3na}yL7?b;F)$0i?WWOMZW%JGhfw#XYMgC#bC&DB06E<_nUVBTSe zO;ihu3vk&A#8or7w#CE^_VQ{H;wn`*&g>;J5AaZUpyBe?E|5D(oBR;^iwvK*NY|($ zad!<~UpEz*VLaD!65}Q{(|#f%N5{PsDIG#$!h`wsi`!>W1@tPAjCs5Ay z*S7bW8Y;K#>1Ebyj|LnSqmRc0I=ctN6flw9?{&!QN3UaEC%w*io%b?p!~CBb7uZ+X z!u)FnCi)r!muZZ&y5x1m>$=xXuRFAe@G4^K=e67Wf-WNXo}ySgmX1n(Z+S*DSG$9&uRCD{zLXsv6j`&ie}sk_D7qZQsmy>IE|j+eX| zXvXTs>n7=@>de|d1Dw`ML9c@YOn(Mk_TH_PJNDOZ(tW8hYpHlNqN=S9_%ak1|n(l7Si=zB=x<5RzO=BIu+`-xM2G=+S;q;|fmg2U7&V~78n5V`pqqt>UQ?j3za zWuqO@%(Bc~#UZ!5kQ{32Rv3{}g`nu$GLU+zmI^dkrqQ7>OWt6hC*eB7PXwH5R zLAoGyq_3C@ZTTiHlQG?n>ZZn#h1$I{0lKdS6~#T9V2fGZ$Iom?;9txDGq zO)mA%jU7gmhRW|w&%@m&se3w?CE8wp5mvx%SH$YrF9aq3V*7#{?lb~4R zG5-i(y-#CdI1^XZnQS~}4zqr1C~937lx#e0H-()E%raNBei8KAyun`Dr=7L8wX*LW z`*VBAz^*~5L6HHezK{KQ^`#~tXlPi@PATt`8%|@Eu`w+Cs<{wwyJ4C$OG@aexK+YnNqd#j`PU-lEv)Q zPSk#3OSDW>QdhONXk16r(Dz|b9c9ogdA^$4eod586<&KEX)0Sj(#X?zW6@fxlj*8; z**?(gOkI`79%7bZ<&`v-_F+9+gG39 zH_2;;U(3KgwnO6WxwxpBCU2eXvjP#8t$x=Hrwn_8uPV=3Yrk?{yMwZK+p#+N9#9!h zJL>{1qBYLsYAn=7eKf&`_!ynh4Sg{jqc8ze zF$?ps7|XE+8?Y7MVFxmB1jlg(7jX;sk%d3-BHF1*_KDFXN8m$rLvIYg5Ts%%)?fp+ z;ydg>1`gmfF5n7o;6AeO2VO&8NRu3lNEAT{ltVStF2j%dXpH6$Ejk=C=zw3?WZ zovY-ZbAe#KS&$Dx|(*L_9G6L+3h;5e46&<>YUQxmD*)n#JCr6HKR7K3tfJ2-BfK` zzggP+!;Ie8dG%6p??qfh$w?~*rY%~T)BE&_MQNkBoXj41^GfTo1GyuQSu=Fmd{O3Q zT#~_W-QUX`qTkE7h^RrE(>E>IoYUjt7xULib8<=*=lF_^FXO@tdsl`n`&k@)85djT z_tmE5mDb6R^n9P3cFoouT5ex2=hmdjKfABXsoB3~JRM{Hj&s;Yue-3ksM6@s8oALq z(em%Ol1+9gJ-pY-se8+F`VCrtf90_aa;{9(oSb3frZxVXa!Q3ZhOaHLNpA7&#++Q+ zjW^d$5f}cBE8_fRlNVLPy}nKP$WLz6c)9%Zi)A@YpLU2!`MSHD)33?N**?F~VcwLS z(pMYGuCBUKZt=JBRQoAUvSV5PXX4Dpur-f27Snguw#a=#uXpb*QeVaSCM2yGnHHmT zE8S5Z=(e(lv{$C0?<;5CQGON@TVKV68cwcCTlS7P_KK5Wi8ZB{b^1hZQ8{JkUW82jtce8IzpT1(>`ka-N6^CBORjgiY zgS7H}Kn_x6FuI>#WYx#JNl9Ir7N!H zPW-2}+m_!@*40Z{*B0do|Lc>SZBuvkZ_A$NZfN_4UaKCsF1(HFqIGul*CcmGFZ9I# z48~B5fD>ad4ihm2`bbSmPopNKW=&1XY|0C;2uraXtFQsvk%2=viyQb2f5XehdB&!x zl5Ark6p?VCILe?J>Yx!?pe;I~JNjWDJ`<0%MFO4ES)7YSSb=nG#1^>l0}kLYj^a4Z z;Ucc%E*{_!p5YC+PD$3o1PlBTf-pp*2#O-n&bhD@3zbm|4bTWp&>St%2JO%pJMiX;_0z*otk~fgi93`*GCHx$rm(=WqqL@C&l=2!G-wv;mwm;R`vkI)fa@G1JpodzQnqc9fZF$q&K1GBLJi?CE|)fEY? zwTH#SIEvG_ge$m?n|Oc@X_}PcD>W%q;KfPOhkto_u&^d&v?=?PI>Dwd;*U0n%)cB>_D2A?|B+k(1rdoD6h;vg zMIuU}49X)3l~5HmP#bm80FBTD&CwEV&<-i+gikaYujFoGZvXfqKJw#WjA+-%{_)iU zBi7H!z9-X^Ie$GTTNKM$3>1muOy_r~>8E+^ngyCgniZOLnk|}bnjbX#GzT@uG^aHeHCHvaHTN{X zYyQ%_(Rgc3+CXgqZKO6%TU`5&ww$(tc3=qqE*w)aA1kp1J8>9ik%@syRo1q(-(Gx7Sz}z@#2s8d!}L(8&hm89{fqME5#`k=ZZ^D;(J(EP~3#C zNqsAtJdN*U-9zyxPLMh!_B@TRW4cQ57R5Wl{#X21>tl*u_L`5(qMQm`Jy%Fzp|q8%lPtIy%_Q`zM6Fp9iHNEI-6VvUdH#(>g63*JT6@{ zMs#=;KfxXm!9V{;e|#39u}yQmcoiSzWnV*iJzhZEGK{;n+VgfFz#!qJ+{j_4Y#u?=xe^C4&F?Mdbg{Dc$aGhL<42{W|TrM=K1TfJo$!me=$~NWhHpZebvHGSh4R;w4~rwg34QFB zDPF@Z+#~&qt6oS#53T8M$`uRC-FAg0usvH9=GQ1t=ATy=Na$f&P4R1rF3~M4;i!2Z z#ak2~7IsyU5;3x9`!h55 z&U)tD3v##(Y8XyEJ+ZP#H?59%3cf}M2aC%ddpF)Ngo7Evy`imYqP%I1#TC9;iRvQG z-h+MupAmhoR#g|_*4OA+^qkJEF3$Rt&BcF&l{Hzjrs15RmQhE`i$Il;DneC7DF*vp zBFtmv{hDf?6k{wu!KMkr2~>}y=w*kX66z5((EVzO(;hnv{~V5!{h*rE6|wg3=wm1# zGS%9;;^yI%(7J&#?lHpdHAknL14&BblM&(Z7X-P)>4 z3(?bhjQi9voZ@;*3sKU?E?tL~6*iN7sSdRk1=a=Br>^1n>yvH7S<`y4E*By`Q*CN1 z9$RUsMvv+(?ZgzHvfsM1>+x4h>KTrrc6D0>-Jqk`VVb788X;QRdx*{YY-@ePsi>+) za(y*K!_aWGHc||*qtRXvOVm%9okX-X8l8Yn)craM27R^j=W4r|nhGFF6_3Pb5n5XPKd?DO~ z+vL8D_-ifdOdnBE|JYNMGVK=$IzVJ2!|A2X-fj~z8h;Pc$+S+3;V!bX@%!+YthmbV zBMy5jHs-IsK%>Tn)0`?)ZoH_b4#bFQDyO%osQUI5)9q;HBtjBPrRv`(|D!7n5+ywLFL=AA>{e658K@f!7RNnhU&p7yUC1Xd-Hd0OyUi7K^)~h1nbl;++?ov|^D~yb-kzCt_#H z^7lTeZf;XQOcAw{LRYUlnlT{mSm@Ll$1_@ooe1qe@MK2i%3p-GYMoiX)pK8lzEmlz z{!2e)g>FdwDx+)6snG0~vV-DQWoOJSc_*muuXi%Kbi5n%{oK15lL8(D1^)3Mqj&y8 z{^8;u8BNdrvGIeSAJ=d5^v|tBA3VvJcsEJQ&R_IvFr|AE`ezO4gEsFmCsqNe8PaC~YsSgrGU9XQ(-)L_%H3o68 zGzaZ#eK(P7)5^hHwsw#6lWF3#kN4HqJb|HH8@e%V+^6f+B#~*_ui#r?2ial0dzSF= z*k|x6Ve|#VXn6I}WcL=1!TUlo*%Ezqj)?Zy@8VCur!Czg##|A3y5@XQ+q4_Cr8f&p znUSuU!4vBcUalQ)-p+6u>9kZ)(zH6`6YxZ}Ws!*WEeD4+kQLCLmuc_5XYf*y?5%?~fL0Jj9Ip2*6)jD>Cq5IB$(E@n%fuA>eRMBqq62DH znmF!v61|SzX|K917lXVH@yGBKe8TDe=|juK29I4PoX#|)l5NwYSBcL|`!N1o2Rfx4 z45zYgplH{u2KWR#NgZ0l<6tFv82w70Tq~kY`+NKnl)?0XRqAx49mc1rUGIu~ zdl|YCG|>^YYn?dl_YIndKJ2LHtrs77?BWspJt|0wFr0O&`6jW$-h-ZiOrmUkZROL_!(+h7+gS?G$^g*E@4^(bek7P7!ClkA8%HtcLFr5BxqyucLQ6>k%J{OQ!vl;N>p# z(z_VWtGa832>19;#b>}ZvWH#t`j5q@KBb?JGIAXF3=T&b&h02YX1}=M@fHwxUglgv z%FFH<7;=Ei*Lw&*0-mlsAG^B!+vcB%vmS3FyeaGfO@2XF`rK{E?%=JvF+N^5w@(y& zgolD1kMF<_Qj4oyM@3*Mw;x`dyUg^s|8H04kBaKnY3842xl5|pF%j!`1IY%J~FKX zxK9uFLzZ<)yklA+J-7t$>3Y|{M7U`!!;j-<^rLJMX9jNM6E+Jk0H0nw=k)z==~G)(deP3{i7M?o5pFL)7sFDbG@bjM zP^PmEzmDIhwsqUYKZnQ{^v3VSbki=@+sKK4sNU|Li3{8^)&x8q-=}6>q$_+9J%wiL zGZ*=cVqL^5$G9EK#LMoEgvaoR#OJE4DTlBy^VAl*6tpMwh_~qE+f%j`+;lgnot|)BAF1IIVB-TCsHN z_0j8Y0~r%*Qj5hXZMi#CPC_)V8~H3Qwcw(Mzh*e?^qv9W)=k-%n5auju0O zn*I5caqudP?C2y3RF8ZS zWj9A#K^Rdxy)EDU01}O_z_+M=_eF~LGxRW=gj2*{>wEX<p!CfcH#7n6~u-yZxXem_nvEH1m)B?j}L zfl`AF$E$OTOZL7JJ`8_dEqG2Iw^Go}=ynxjNo{|M9)hz(7gYs6xyO4O{S|zM&|VHP z99v!WlgV~Pv@(PcNgZEOawoUNlkrvBvSn4%n~raXvv8jLx@zq&4_m*XrG~ogOtDgu zcBRHp+9iCn?%~KNkAEUQ9j=i*8mhLJmObrK!;GBLP<|LKwz2}`G;au63mQW+;+A?< zfV&l4@TE9sp)4=&zZ>5RH_7e~Q?tv-_I9yXc??6zSJ@$brkwo9v?}0n_(Z+n1~L;>h$9-JMMd}EPQdrzC-s?1GQ})+5x)X~BMhhZ z2tM*~M*D8l<15R$KK^Y8vfwl`^GE3IRpbYzRca*n`AEYVqx)5pCCxI^@Xh!xsxNCV z>XEcSO|33FSw~1RnUkfns!JcU+nP3Q~JJOL+$_I zKSHh1^w-p;S~AS)I+}X`jnTJi$<3w}ho|G8>Q!}Qd5`xiJR5??aMH)PE#aYha)Y-w z8VjRfEb&AgUtflJO3%cT;XGU;|9y=9prQQTwEf5O8fiJxxxMfJq4$U&gn-@WId1fI(`G%kK@rc&h1?^50&ZmWOP2H5-rzvLuIO2dLzCW zuD~_&ym5MA3t8ObEfLR{k5l_OG=-jsQfoZM7AK?Bvx4kUEoi>3@oM1RT)xB^TU=Gp6I>>y}8bN%R z_Q$|@IT%hnZT)s#0<2DBE1vl3<%&8l0N zIsWcmJ=?EFf4}UaqAXMmabKc!;jANDi)OVi%)Z%QltmV1IVXy;m(_0fC3+Ojs?QGw z-2dDo*{3jT8(NeND9pM?6lH_?8ibb^UN|dZXwj@Oh1tx6qHIE8w(PB<>^0S+ldM`S zky+DKVkgtA?@Np6c-AzIcl%hsC2hiiz10pug7tyJy`8K^df$%=YeciG*v-z3%W z8B6?2-|ZoXdF&iKVlwYGnYHx5-g1~}PscU>1+|0wDgintMlLh$^8^)Nqn&@va2o0v zvGTC#?SMzZ97rKwqr3Lw+2;QVejD=0p1h{F_m|y#yro{J)rR*W{dKnkW(|@%JoZg| zz#FukZ_ozlql0Pb>{a+0I82tMFAkBnJ!LQB*I@7z?$jxC(5MVWT)3Yv=;w#agQop9 z=`F}(3159~1V_kvaVkCbsfN={TchMO^QDn^I{pdOz16AAdVIHPoYg6!q7X5u$-kItIJ-EWtlMw13(r@245 z$bL=osdx>Z2l-@v`sy1}n|8(N><$c@&NEnVnkpxF?8W$R@PMqet~AYExAqJ^K0(q9 z!&#~8%;2){X5f1v5Aw-O%|UP1F7tUVavh?JW13>fNbaUUp~v4VXqYpAY#m(6l$= zC*c&?_qxdvInuO##w*V@oH}~TQo2NT#BA2V7f{>(wKeMCG`V&4a=FHNo9r6%?oq3| z2Y`F0j6S$rmf=%HG7m?nmuxsK^wkw|ifQ-87sCp&&3ecxPKp0M{1)VqJxSJotd?(@ zR^S{i?>UAuPS02)3rzp%coLi?yER8Id|S3L?E-?3xxC9YQo zFQK|8^Y8!M=InjdU>&W=Hq~*R47WaHi9Ia2Po=HnA$}CiM6=Yzb+Vat7Cn#Bm0K^H z`CUhEqPOO%{_AB^zkK`w{%Edx8&}=e%jeaV^|HGaFptAO&v0H+O*Y7uwnXcp4X923 zcR`&{uWXd#td@k6nK@gX*(d{wrJ;Rw?i?}9R4>0L7g?(b@|a&-=f3AYC(EYr2{*-X z=BeEGrS`5u*T5!7C*GmAZQ-K!X5!gUZ$90w`RH4X~RsJs7!~c82i*Stvau=wcAIdKN_wampK=x>X+Ww(@&nmHyBeu}}Ml$9j+0tr= zHbeQ4@sW(R!qF(St9tYi=VbsIhYnHAGw92YMaRQrqSsYgh8$qeLgzp#(PH%|Lnc*z z2i*W$UxK%_y$aZrRm7On3}3Ss+W@+byH~FQQlA8d2^-)#YP3rgR?u zE8K_2(&QkoKNsh z=7lfjW9edEh3YQ%&z@6?K6KDsZV~gUF5#+PVmPr>ce#J|x<=~OM_8`OIOh2*<#W9edf3v;1k?~c|Sou>jo}+w(iWOMcZ6Ucg=LSr9SgA;p{TQ zd0e>MKf7-wb@o>*XFF-!nXo6#a4zfSU&~UabsI0g+;FO>9;ansQr#CAX&m@HjD*h% zt2)8XM3`T`a9Tl7{-_t&rp-8W$QAUFnkGH>oF8={h~eZ!t_vh69zWmjTL%yd9=_7L2||0?xeI0tmo6Z*7ZNMkq+&$nbOZ;-7RwH7Wz(nS_>SM1-K7inVz zeF0s<7_|gyw&mzRn|5pjYy_)4Idp-w@Hw1d{e0^4;p_y|=)jQfa0Y60WH4aJ0{0ZS zN7Ibp=)t83&i~3tMu3KRW(@80Y%j)iE;_7#UsxN+4!~~^-^BE;)c?34EPOyu$ObGYhWjM1~VKE3zwsoqRyu7 zgCC&G5H=d#gF6sBltH7xcNpyh%eIFgrrX0#sDVGFMo>$?%K4xD>a#cenc8zW8vx%y zvk|<=NQT&f{qQZsu#z+C*=f2!jTp@s3g9+MC#mC^A327R-oYIE%;!)u;0lZw$5COK zwbVagW_J;o_ymsEBqpYS zyRu6X(~IF_rdJl$Dib-&kO{$)jG%9r7f(G0SD2nSnX3+(zs4nv@1Xj=&J76*-*~qC zH-&ZilxH3=m1b>f661?AF+Y(JwpnrVo3w4y*Z}ZO=MBl`QG;jDbb-MA-ZFxAfV;du z>raM9FnlKW7MYu8GTnaGvt?FMuR*KX+_Z3v`9?CsRl#rWb?mOR9dYJxB;Yr2UnFua zV~#_*s&igeHg~G7=Vkfo2T8t$e}nsg`!Dq=_|Ic(C~&i;Y5{B5DQFFvhn3WlYTtR; zEGadGmIeCHXK*LT2mb}M!EgqC24f*jBD8@%Faq9$m7t*sFZnI?Cn%oE;}GgXJMswX zAeaGbVK00ESK!xFLk4wVq30HHG@vol?n>R59t1N%;>)S;sZ|$b<%ZjtoW7W|2(K)m z(SlY>xvU@>f|l{#V5G4hkdvlvT#(&rhb(_~1r;;q7_Ch<)R68ki!S?07|VX*CYkGF N3~sR8nCX7S{(lXxFGc_W diff --git a/deps/icu-small/source/i18n/affixpatternparser.cpp b/deps/icu-small/source/i18n/affixpatternparser.cpp deleted file mode 100644 index d9e122953af53e..00000000000000 --- a/deps/icu-small/source/i18n/affixpatternparser.cpp +++ /dev/null @@ -1,698 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: affixpatternparser.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/dcfmtsym.h" -#include "unicode/plurrule.h" -#include "unicode/strenum.h" -#include "unicode/ucurr.h" -#include "unicode/ustring.h" -#include "affixpatternparser.h" -#include "charstr.h" -#include "precision.h" -#include "uassert.h" -#include "unistrappender.h" - -static const UChar gDefaultSymbols[] = {0xa4, 0xa4, 0xa4}; - -static const UChar gPercent = 0x25; -static const UChar gPerMill = 0x2030; -static const UChar gNegative = 0x2D; -static const UChar gPositive = 0x2B; - -#define PACK_TOKEN_AND_LENGTH(t, l) ((UChar) (((t) << 8) | (l & 0xFF))) - -#define UNPACK_TOKEN(c) ((AffixPattern::ETokenType) (((c) >> 8) & 0x7F)) - -#define UNPACK_LONG(c) (((c) >> 8) & 0x80) - -#define UNPACK_LENGTH(c) ((c) & 0xFF) - -U_NAMESPACE_BEGIN - -static int32_t -nextToken(const UChar *buffer, int32_t idx, int32_t len, UChar *token) { - if (buffer[idx] != 0x27 || idx + 1 == len) { - *token = buffer[idx]; - return 1; - } - *token = buffer[idx + 1]; - if (buffer[idx + 1] == 0xA4) { - int32_t i = 2; - for (; idx + i < len && i < 4 && buffer[idx + i] == buffer[idx + 1]; ++i) - ; - return i; - } - return 2; -} - -static int32_t -nextUserToken(const UChar *buffer, int32_t idx, int32_t len, UChar *token) { - *token = buffer[idx]; - int32_t max; - switch (buffer[idx]) { - case 0x27: - max = 2; - break; - case 0xA4: - max = 3; - break; - default: - max = 1; - break; - } - int32_t i = 1; - for (; idx + i < len && i < max && buffer[idx + i] == buffer[idx]; ++i) - ; - return i; -} - -CurrencyAffixInfo::CurrencyAffixInfo() - : fSymbol(gDefaultSymbols, 1), - fISO(gDefaultSymbols, 2), - fLong(DigitAffix(gDefaultSymbols, 3)), - fIsDefault(TRUE) { -} - -void -CurrencyAffixInfo::set( - const char *locale, - const PluralRules *rules, - const UChar *currency, - UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - fIsDefault = FALSE; - if (currency == NULL) { - fSymbol.setTo(gDefaultSymbols, 1); - fISO.setTo(gDefaultSymbols, 2); - fLong.remove(); - fLong.append(gDefaultSymbols, 3); - fIsDefault = TRUE; - return; - } - int32_t len; - UBool unusedIsChoice; - const UChar *symbol = ucurr_getName( - currency, locale, UCURR_SYMBOL_NAME, &unusedIsChoice, - &len, &status); - if (U_FAILURE(status)) { - return; - } - fSymbol.setTo(symbol, len); - fISO.setTo(currency, u_strlen(currency)); - fLong.remove(); - StringEnumeration* keywords = rules->getKeywords(status); - if (U_FAILURE(status)) { - return; - } - const UnicodeString* pluralCount; - while ((pluralCount = keywords->snext(status)) != NULL) { - CharString pCount; - pCount.appendInvariantChars(*pluralCount, status); - const UChar *pluralName = ucurr_getPluralName( - currency, locale, &unusedIsChoice, pCount.data(), - &len, &status); - fLong.setVariant(pCount.data(), UnicodeString(pluralName, len), status); - } - delete keywords; -} - -void -CurrencyAffixInfo::adjustPrecision( - const UChar *currency, const UCurrencyUsage usage, - FixedPrecision &precision, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - - int32_t digitCount = ucurr_getDefaultFractionDigitsForUsage( - currency, usage, &status); - precision.fMin.setFracDigitCount(digitCount); - precision.fMax.setFracDigitCount(digitCount); - double increment = ucurr_getRoundingIncrementForUsage( - currency, usage, &status); - if (increment == 0.0) { - precision.fRoundingIncrement.clear(); - } else { - precision.fRoundingIncrement.set(increment); - // guard against round-off error - precision.fRoundingIncrement.round(6); - } -} - -void -AffixPattern::addLiteral( - const UChar *literal, int32_t start, int32_t len) { - char32Count += u_countChar32(literal + start, len); - literals.append(literal, start, len); - int32_t tlen = tokens.length(); - // Takes 4 UChars to encode maximum literal length. - UChar *tokenChars = tokens.getBuffer(tlen + 4); - - // find start of literal size. May be tlen if there is no literal. - // While finding start of literal size, compute literal length - int32_t literalLength = 0; - int32_t tLiteralStart = tlen; - while (tLiteralStart > 0 && UNPACK_TOKEN(tokenChars[tLiteralStart - 1]) == kLiteral) { - tLiteralStart--; - literalLength <<= 8; - literalLength |= UNPACK_LENGTH(tokenChars[tLiteralStart]); - } - // Add number of chars we just added to literal - literalLength += len; - - // Now encode the new length starting at tLiteralStart - tlen = tLiteralStart; - tokenChars[tlen++] = PACK_TOKEN_AND_LENGTH(kLiteral, literalLength & 0xFF); - literalLength >>= 8; - while (literalLength) { - tokenChars[tlen++] = PACK_TOKEN_AND_LENGTH(kLiteral | 0x80, literalLength & 0xFF); - literalLength >>= 8; - } - tokens.releaseBuffer(tlen); -} - -void -AffixPattern::add(ETokenType t) { - add(t, 1); -} - -void -AffixPattern::addCurrency(uint8_t count) { - add(kCurrency, count); -} - -void -AffixPattern::add(ETokenType t, uint8_t count) { - U_ASSERT(t != kLiteral); - char32Count += count; - switch (t) { - case kCurrency: - hasCurrencyToken = TRUE; - break; - case kPercent: - hasPercentToken = TRUE; - break; - case kPerMill: - hasPermillToken = TRUE; - break; - default: - // Do nothing - break; - } - tokens.append(PACK_TOKEN_AND_LENGTH(t, count)); -} - -AffixPattern & -AffixPattern::append(const AffixPattern &other) { - AffixPatternIterator iter; - other.iterator(iter); - UnicodeString literal; - while (iter.nextToken()) { - switch (iter.getTokenType()) { - case kLiteral: - iter.getLiteral(literal); - addLiteral(literal.getBuffer(), 0, literal.length()); - break; - case kCurrency: - addCurrency(static_cast(iter.getTokenLength())); - break; - default: - add(iter.getTokenType()); - break; - } - } - return *this; -} - -void -AffixPattern::remove() { - tokens.remove(); - literals.remove(); - hasCurrencyToken = FALSE; - hasPercentToken = FALSE; - hasPermillToken = FALSE; - char32Count = 0; -} - -// escapes literals for strings where special characters are NOT escaped -// except for apostrophe. -static void escapeApostropheInLiteral( - const UnicodeString &literal, UnicodeStringAppender &appender) { - int32_t len = literal.length(); - const UChar *buffer = literal.getBuffer(); - for (int32_t i = 0; i < len; ++i) { - UChar ch = buffer[i]; - switch (ch) { - case 0x27: - appender.append((UChar) 0x27); - appender.append((UChar) 0x27); - break; - default: - appender.append(ch); - break; - } - } -} - - -// escapes literals for user strings where special characters in literals -// are escaped with apostrophe. -static void escapeLiteral( - const UnicodeString &literal, UnicodeStringAppender &appender) { - int32_t len = literal.length(); - const UChar *buffer = literal.getBuffer(); - for (int32_t i = 0; i < len; ++i) { - UChar ch = buffer[i]; - switch (ch) { - case 0x27: - appender.append((UChar) 0x27); - appender.append((UChar) 0x27); - break; - case 0x25: - appender.append((UChar) 0x27); - appender.append((UChar) 0x25); - appender.append((UChar) 0x27); - break; - case 0x2030: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2030); - appender.append((UChar) 0x27); - break; - case 0xA4: - appender.append((UChar) 0x27); - appender.append((UChar) 0xA4); - appender.append((UChar) 0x27); - break; - case 0x2D: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2D); - appender.append((UChar) 0x27); - break; - case 0x2B: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2B); - appender.append((UChar) 0x27); - break; - default: - appender.append(ch); - break; - } - } -} - -UnicodeString & -AffixPattern::toString(UnicodeString &appendTo) const { - AffixPatternIterator iter; - iterator(iter); - UnicodeStringAppender appender(appendTo); - UnicodeString literal; - while (iter.nextToken()) { - switch (iter.getTokenType()) { - case kLiteral: - escapeApostropheInLiteral(iter.getLiteral(literal), appender); - break; - case kPercent: - appender.append((UChar) 0x27); - appender.append((UChar) 0x25); - break; - case kPerMill: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2030); - break; - case kCurrency: - { - appender.append((UChar) 0x27); - int32_t cl = iter.getTokenLength(); - for (int32_t i = 0; i < cl; ++i) { - appender.append((UChar) 0xA4); - } - } - break; - case kNegative: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2D); - break; - case kPositive: - appender.append((UChar) 0x27); - appender.append((UChar) 0x2B); - break; - default: - U_ASSERT(FALSE); - break; - } - } - return appendTo; -} - -UnicodeString & -AffixPattern::toUserString(UnicodeString &appendTo) const { - AffixPatternIterator iter; - iterator(iter); - UnicodeStringAppender appender(appendTo); - UnicodeString literal; - while (iter.nextToken()) { - switch (iter.getTokenType()) { - case kLiteral: - escapeLiteral(iter.getLiteral(literal), appender); - break; - case kPercent: - appender.append((UChar) 0x25); - break; - case kPerMill: - appender.append((UChar) 0x2030); - break; - case kCurrency: - { - int32_t cl = iter.getTokenLength(); - for (int32_t i = 0; i < cl; ++i) { - appender.append((UChar) 0xA4); - } - } - break; - case kNegative: - appender.append((UChar) 0x2D); - break; - case kPositive: - appender.append((UChar) 0x2B); - break; - default: - U_ASSERT(FALSE); - break; - } - } - return appendTo; -} - -class AffixPatternAppender : public UMemory { -public: - AffixPatternAppender(AffixPattern &dest) : fDest(&dest), fIdx(0) { } - - inline void append(UChar x) { - if (fIdx == UPRV_LENGTHOF(fBuffer)) { - fDest->addLiteral(fBuffer, 0, fIdx); - fIdx = 0; - } - fBuffer[fIdx++] = x; - } - - inline void append(UChar32 x) { - if (fIdx >= UPRV_LENGTHOF(fBuffer) - 1) { - fDest->addLiteral(fBuffer, 0, fIdx); - fIdx = 0; - } - U16_APPEND_UNSAFE(fBuffer, fIdx, x); - } - - inline void flush() { - if (fIdx) { - fDest->addLiteral(fBuffer, 0, fIdx); - } - fIdx = 0; - } - - /** - * flush the buffer when we go out of scope. - */ - ~AffixPatternAppender() { - flush(); - } -private: - AffixPattern *fDest; - int32_t fIdx; - UChar fBuffer[32]; - AffixPatternAppender(const AffixPatternAppender &other); - AffixPatternAppender &operator=(const AffixPatternAppender &other); -}; - - -AffixPattern & -AffixPattern::parseUserAffixString( - const UnicodeString &affixStr, - AffixPattern &appendTo, - UErrorCode &status) { - if (U_FAILURE(status)) { - return appendTo; - } - int32_t len = affixStr.length(); - const UChar *buffer = affixStr.getBuffer(); - // 0 = not quoted; 1 = quoted. - int32_t state = 0; - AffixPatternAppender appender(appendTo); - for (int32_t i = 0; i < len; ) { - UChar token; - int32_t tokenSize = nextUserToken(buffer, i, len, &token); - i += tokenSize; - if (token == 0x27 && tokenSize == 1) { // quote - state = 1 - state; - continue; - } - if (state == 0) { - switch (token) { - case 0x25: - appender.flush(); - appendTo.add(kPercent, 1); - break; - case 0x27: // double quote - appender.append((UChar) 0x27); - break; - case 0x2030: - appender.flush(); - appendTo.add(kPerMill, 1); - break; - case 0x2D: - appender.flush(); - appendTo.add(kNegative, 1); - break; - case 0x2B: - appender.flush(); - appendTo.add(kPositive, 1); - break; - case 0xA4: - appender.flush(); - appendTo.add(kCurrency, static_cast(tokenSize)); - break; - default: - appender.append(token); - break; - } - } else { - switch (token) { - case 0x27: // double quote - appender.append((UChar) 0x27); - break; - case 0xA4: // included b/c tokenSize can be > 1 - for (int32_t j = 0; j < tokenSize; ++j) { - appender.append((UChar) 0xA4); - } - break; - default: - appender.append(token); - break; - } - } - } - return appendTo; -} - -AffixPattern & -AffixPattern::parseAffixString( - const UnicodeString &affixStr, - AffixPattern &appendTo, - UErrorCode &status) { - if (U_FAILURE(status)) { - return appendTo; - } - int32_t len = affixStr.length(); - const UChar *buffer = affixStr.getBuffer(); - for (int32_t i = 0; i < len; ) { - UChar token; - int32_t tokenSize = nextToken(buffer, i, len, &token); - if (tokenSize == 1) { - int32_t literalStart = i; - ++i; - while (i < len && (tokenSize = nextToken(buffer, i, len, &token)) == 1) { - ++i; - } - appendTo.addLiteral(buffer, literalStart, i - literalStart); - - // If we reached end of string, we are done - if (i == len) { - return appendTo; - } - } - i += tokenSize; - switch (token) { - case 0x25: - appendTo.add(kPercent, 1); - break; - case 0x2030: - appendTo.add(kPerMill, 1); - break; - case 0x2D: - appendTo.add(kNegative, 1); - break; - case 0x2B: - appendTo.add(kPositive, 1); - break; - case 0xA4: - { - if (tokenSize - 1 > 3) { - status = U_PARSE_ERROR; - return appendTo; - } - appendTo.add(kCurrency, tokenSize - 1); - } - break; - default: - appendTo.addLiteral(&token, 0, 1); - break; - } - } - return appendTo; -} - -AffixPatternIterator & -AffixPattern::iterator(AffixPatternIterator &result) const { - result.nextLiteralIndex = 0; - result.lastLiteralLength = 0; - result.nextTokenIndex = 0; - result.tokens = &tokens; - result.literals = &literals; - return result; -} - -UBool -AffixPatternIterator::nextToken() { - int32_t tlen = tokens->length(); - if (nextTokenIndex == tlen) { - return FALSE; - } - ++nextTokenIndex; - const UChar *tokenBuffer = tokens->getBuffer(); - if (UNPACK_TOKEN(tokenBuffer[nextTokenIndex - 1]) == - AffixPattern::kLiteral) { - while (nextTokenIndex < tlen && - UNPACK_LONG(tokenBuffer[nextTokenIndex])) { - ++nextTokenIndex; - } - lastLiteralLength = 0; - int32_t i = nextTokenIndex - 1; - for (; UNPACK_LONG(tokenBuffer[i]); --i) { - lastLiteralLength <<= 8; - lastLiteralLength |= UNPACK_LENGTH(tokenBuffer[i]); - } - lastLiteralLength <<= 8; - lastLiteralLength |= UNPACK_LENGTH(tokenBuffer[i]); - nextLiteralIndex += lastLiteralLength; - } - return TRUE; -} - -AffixPattern::ETokenType -AffixPatternIterator::getTokenType() const { - return UNPACK_TOKEN(tokens->charAt(nextTokenIndex - 1)); -} - -UnicodeString & -AffixPatternIterator::getLiteral(UnicodeString &result) const { - const UChar *buffer = literals->getBuffer(); - result.setTo(buffer + (nextLiteralIndex - lastLiteralLength), lastLiteralLength); - return result; -} - -int32_t -AffixPatternIterator::getTokenLength() const { - const UChar *tokenBuffer = tokens->getBuffer(); - AffixPattern::ETokenType type = UNPACK_TOKEN(tokenBuffer[nextTokenIndex - 1]); - return type == AffixPattern::kLiteral ? lastLiteralLength : UNPACK_LENGTH(tokenBuffer[nextTokenIndex - 1]); -} - -AffixPatternParser::AffixPatternParser() - : fPercent(gPercent), fPermill(gPerMill), fNegative(gNegative), fPositive(gPositive) { -} - -AffixPatternParser::AffixPatternParser( - const DecimalFormatSymbols &symbols) { - setDecimalFormatSymbols(symbols); -} - -void -AffixPatternParser::setDecimalFormatSymbols( - const DecimalFormatSymbols &symbols) { - fPercent = symbols.getConstSymbol(DecimalFormatSymbols::kPercentSymbol); - fPermill = symbols.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); - fNegative = symbols.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - fPositive = symbols.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); -} - -PluralAffix & -AffixPatternParser::parse( - const AffixPattern &affixPattern, - const CurrencyAffixInfo ¤cyAffixInfo, - PluralAffix &appendTo, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return appendTo; - } - AffixPatternIterator iter; - affixPattern.iterator(iter); - UnicodeString literal; - while (iter.nextToken()) { - switch (iter.getTokenType()) { - case AffixPattern::kPercent: - appendTo.append(fPercent, UNUM_PERCENT_FIELD); - break; - case AffixPattern::kPerMill: - appendTo.append(fPermill, UNUM_PERMILL_FIELD); - break; - case AffixPattern::kNegative: - appendTo.append(fNegative, UNUM_SIGN_FIELD); - break; - case AffixPattern::kPositive: - appendTo.append(fPositive, UNUM_SIGN_FIELD); - break; - case AffixPattern::kCurrency: - switch (iter.getTokenLength()) { - case 1: - appendTo.append( - currencyAffixInfo.getSymbol(), UNUM_CURRENCY_FIELD); - break; - case 2: - appendTo.append( - currencyAffixInfo.getISO(), UNUM_CURRENCY_FIELD); - break; - case 3: - appendTo.append( - currencyAffixInfo.getLong(), UNUM_CURRENCY_FIELD, status); - break; - default: - U_ASSERT(FALSE); - break; - } - break; - case AffixPattern::kLiteral: - appendTo.append(iter.getLiteral(literal)); - break; - default: - U_ASSERT(FALSE); - break; - } - } - return appendTo; -} - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/affixpatternparser.h b/deps/icu-small/source/i18n/affixpatternparser.h deleted file mode 100644 index b54c749c700816..00000000000000 --- a/deps/icu-small/source/i18n/affixpatternparser.h +++ /dev/null @@ -1,402 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* affixpatternparser.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __AFFIX_PATTERN_PARSER_H__ -#define __AFFIX_PATTERN_PARSER_H__ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unistr.h" -#include "unicode/uobject.h" -#include "pluralaffix.h" - -U_NAMESPACE_BEGIN - -class PluralRules; -class FixedPrecision; -class DecimalFormatSymbols; - -/** - * A representation of the various forms of a particular currency according - * to some locale and usage context. - * - * Includes the symbol, ISO code form, and long form(s) of the currency name - * for each plural variation. - */ -class U_I18N_API CurrencyAffixInfo : public UMemory { -public: - /** - * Symbol is \u00a4; ISO form is \u00a4\u00a4; - * long form is \u00a4\u00a4\u00a4. - */ - CurrencyAffixInfo(); - - const UnicodeString &getSymbol() const { return fSymbol; } - const UnicodeString &getISO() const { return fISO; } - const PluralAffix &getLong() const { return fLong; } - void setSymbol(const UnicodeString &symbol) { - fSymbol = symbol; - fIsDefault = FALSE; - } - void setISO(const UnicodeString &iso) { - fISO = iso; - fIsDefault = FALSE; - } - UBool - equals(const CurrencyAffixInfo &other) const { - return (fSymbol == other.fSymbol) - && (fISO == other.fISO) - && (fLong.equals(other.fLong)) - && (fIsDefault == other.fIsDefault); - } - - /** - * Intializes this instance. - * - * @param locale the locale for the currency forms. - * @param rules The plural rules for the locale. - * @param currency the null terminated, 3 character ISO code of the - * currency. If NULL, resets this instance as if it were just created. - * In this case, the first 2 parameters may be NULL as well. - * @param status any error returned here. - */ - void set( - const char *locale, const PluralRules *rules, - const UChar *currency, UErrorCode &status); - - /** - * Returns true if this instance is the default. That is has no real - * currency. For instance never initialized with set() - * or reset with set(NULL, NULL, NULL, status). - */ - UBool isDefault() const { return fIsDefault; } - - /** - * Adjusts the precision used for a particular currency. - * @param currency the null terminated, 3 character ISO code of the - * currency. - * @param usage the usage of the currency - * @param precision min/max fraction digits and rounding increment - * adjusted. - * @params status any error reported here. - */ - static void adjustPrecision( - const UChar *currency, const UCurrencyUsage usage, - FixedPrecision &precision, UErrorCode &status); - -private: - /** - * The symbol form of the currency. - */ - UnicodeString fSymbol; - - /** - * The ISO form of the currency, usually three letter abbreviation. - */ - UnicodeString fISO; - - /** - * The long forms of the currency keyed by plural variation. - */ - PluralAffix fLong; - - UBool fIsDefault; - -}; - -class AffixPatternIterator; - -/** - * A locale agnostic representation of an affix pattern. - */ -class U_I18N_API AffixPattern : public UMemory { -public: - - /** - * The token types that can appear in an affix pattern. - */ - enum ETokenType { - kLiteral, - kPercent, - kPerMill, - kCurrency, - kNegative, - kPositive - }; - - /** - * An empty affix pattern. - */ - AffixPattern() - : tokens(), literals(), hasCurrencyToken(FALSE), - hasPercentToken(FALSE), hasPermillToken(FALSE), char32Count(0) { - } - - /** - * Adds a string literal to this affix pattern. - */ - void addLiteral(const UChar *, int32_t start, int32_t len); - - /** - * Adds a token to this affix pattern. t must not be kLiteral as - * the addLiteral() method adds literals. - * @param t the token type to add - */ - void add(ETokenType t); - - /** - * Adds a currency token with specific count to this affix pattern. - * @param count the token count. Used to distinguish between - * one, two, or three currency symbols. Note that adding a currency - * token with count=2 (Use ISO code) is different than adding two - * currency tokens each with count=1 (two currency symbols). - */ - void addCurrency(uint8_t count); - - /** - * Makes this instance be an empty affix pattern. - */ - void remove(); - - /** - * Provides an iterator over the tokens in this instance. - * @param result this is initialized to point just before the - * first token of this instance. Caller must call nextToken() - * on the iterator once it is set up to have it actually point - * to the first token. This first call to nextToken() will return - * FALSE if the AffixPattern being iterated over is empty. - * @return result - */ - AffixPatternIterator &iterator(AffixPatternIterator &result) const; - - /** - * Returns TRUE if this instance has currency tokens in it. - */ - UBool usesCurrency() const { - return hasCurrencyToken; - } - - UBool usesPercent() const { - return hasPercentToken; - } - - UBool usesPermill() const { - return hasPermillToken; - } - - /** - * Returns the number of code points a string of this instance - * would have if none of the special tokens were escaped. - * Used to compute the padding size. - */ - int32_t countChar32() const { - return char32Count; - } - - /** - * Appends other to this instance mutating this instance in place. - * @param other The pattern appended to the end of this one. - * @return a reference to this instance for chaining. - */ - AffixPattern &append(const AffixPattern &other); - - /** - * Converts this AffixPattern back into a user string. - * It is the inverse of parseUserAffixString. - */ - UnicodeString &toUserString(UnicodeString &appendTo) const; - - /** - * Converts this AffixPattern back into a string. - * It is the inverse of parseAffixString. - */ - UnicodeString &toString(UnicodeString &appendTo) const; - - /** - * Parses an affix pattern string appending it to an AffixPattern. - * Parses affix pattern strings produced from using - * DecimalFormatPatternParser to parse a format pattern. Affix patterns - * include the positive prefix and suffix and the negative prefix - * and suffix. This method expects affix patterns strings to be in the - * same format that DecimalFormatPatternParser produces. Namely special - * characters in the affix that correspond to a field type must be - * prefixed with an apostrophe ('). These special character sequences - * inluce minus (-), percent (%), permile (U+2030), plus (+), - * short currency (U+00a4), medium currency (u+00a4 * 2), - * long currency (u+a4 * 3), and apostrophe (') - * (apostrophe does not correspond to a field type but has to be escaped - * because it itself is the escape character). - * Since the expansion of these special character - * sequences is locale dependent, these sequences are not expanded in - * an AffixPattern instance. - * If these special characters are not prefixed with an apostrophe in - * the affix pattern string, then they are treated verbatim just as - * any other character. If an apostrophe prefixes a non special - * character in the affix pattern, the apostrophe is simply ignored. - * - * @param affixStr the string from DecimalFormatPatternParser - * @param appendTo parsed result appended here. - * @param status any error parsing returned here. - */ - static AffixPattern &parseAffixString( - const UnicodeString &affixStr, - AffixPattern &appendTo, - UErrorCode &status); - - /** - * Parses an affix pattern string appending it to an AffixPattern. - * Parses affix pattern strings as the user would supply them. - * In this function, quoting makes special characters like normal - * characters whereas in parseAffixString, quoting makes special - * characters special. - * - * @param affixStr the string from the user - * @param appendTo parsed result appended here. - * @param status any error parsing returned here. - */ - static AffixPattern &parseUserAffixString( - const UnicodeString &affixStr, - AffixPattern &appendTo, - UErrorCode &status); - - UBool equals(const AffixPattern &other) const { - return (tokens == other.tokens) - && (literals == other.literals) - && (hasCurrencyToken == other.hasCurrencyToken) - && (hasPercentToken == other.hasPercentToken) - && (hasPermillToken == other.hasPermillToken) - && (char32Count == other.char32Count); - } - -private: - /* - * Tokens stored here. Each UChar generally stands for one token. A - * Each token is of form 'etttttttllllllll' llllllll is the length of - * the token and ranges from 0-255. ttttttt is the token type and ranges - * from 0-127. If e is set it means this is an extendo token (to be - * described later). To accomodate token lengths above 255, each normal - * token (e=0) can be followed by 0 or more extendo tokens (e=1) with - * the same type. Right now only kLiteral Tokens have extendo tokens. - * Each extendo token provides the next 8 higher bits for the length. - * If a kLiteral token is followed by 2 extendo tokens then, then the - * llllllll of the next extendo token contains bits 8-15 of the length - * and the last extendo token contains bits 16-23 of the length. - */ - UnicodeString tokens; - - /* - * The characters of the kLiteral tokens are concatenated together here. - * The first characters go with the first kLiteral token, the next - * characters go with the next kLiteral token etc. - */ - UnicodeString literals; - UBool hasCurrencyToken; - UBool hasPercentToken; - UBool hasPermillToken; - int32_t char32Count; - void add(ETokenType t, uint8_t count); - -}; - -/** - * An iterator over the tokens in an AffixPattern instance. - */ -class U_I18N_API AffixPatternIterator : public UMemory { -public: - - /** - * Using an iterator without first calling iterator on an AffixPattern - * instance to initialize the iterator results in - * undefined behavior. - */ - AffixPatternIterator() : nextLiteralIndex(0), lastLiteralLength(0), nextTokenIndex(0), tokens(NULL), literals(NULL) { } - /** - * Advances this iterator to the next token. Returns FALSE when there - * are no more tokens. Calling the other methods after nextToken() - * returns FALSE results in undefined behavior. - */ - UBool nextToken(); - - /** - * Returns the type of token. - */ - AffixPattern::ETokenType getTokenType() const; - - /** - * For literal tokens, returns the literal string. Calling this for - * other token types results in undefined behavior. - * @param result replaced with a read-only alias to the literal string. - * @return result - */ - UnicodeString &getLiteral(UnicodeString &result) const; - - /** - * Returns the token length. Usually 1, but for currency tokens may - * be 2 for ISO code and 3 for long form. - */ - int32_t getTokenLength() const; -private: - int32_t nextLiteralIndex; - int32_t lastLiteralLength; - int32_t nextTokenIndex; - const UnicodeString *tokens; - const UnicodeString *literals; - friend class AffixPattern; - AffixPatternIterator(const AffixPatternIterator &); - AffixPatternIterator &operator=(const AffixPatternIterator &); -}; - -/** - * A locale aware class that converts locale independent AffixPattern - * instances into locale dependent PluralAffix instances. - */ -class U_I18N_API AffixPatternParser : public UMemory { -public: -AffixPatternParser(); -AffixPatternParser(const DecimalFormatSymbols &symbols); -void setDecimalFormatSymbols(const DecimalFormatSymbols &symbols); - -/** - * Parses affixPattern appending the result to appendTo. - * @param affixPattern The affix pattern. - * @param currencyAffixInfo contains the currency forms. - * @param appendTo The result of parsing affixPattern is appended here. - * @param status any error returned here. - * @return appendTo. - */ -PluralAffix &parse( - const AffixPattern &affixPattern, - const CurrencyAffixInfo ¤cyAffixInfo, - PluralAffix &appendTo, - UErrorCode &status) const; - -UBool equals(const AffixPatternParser &other) const { - return (fPercent == other.fPercent) - && (fPermill == other.fPermill) - && (fNegative == other.fNegative) - && (fPositive == other.fPositive); -} - -private: -UnicodeString fPercent; -UnicodeString fPermill; -UnicodeString fNegative; -UnicodeString fPositive; -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __AFFIX_PATTERN_PARSER_H__ diff --git a/deps/icu-small/source/i18n/collationfcd.cpp b/deps/icu-small/source/i18n/collationfcd.cpp index 19841ee6487ad2..1aff936dee1d2a 100644 --- a/deps/icu-small/source/i18n/collationfcd.cpp +++ b/deps/icu-small/source/i18n/collationfcd.cpp @@ -22,27 +22,27 @@ const uint8_t CollationFCD::lcccIndex[2048]={ 0,0,0,0,0,0,0,0,1,1,2,3,0,0,0,0, 0,0,0,0,4,0,0,0,0,0,0,0,5,6,7,0, 8,0,9,0xa,0,0,0xb,0xc,0xd,0xe,0xf,0,0,0,0,0x10, -0x11,0x12,0x13,0,0,0,0x14,0x15,0,0x16,0x17,0,0,0x16,0x18,0, +0x11,0x12,0x13,0,0,0,0x14,0x15,0,0x16,0x17,0,0,0x16,0x18,0x19, 0,0x16,0x18,0,0,0x16,0x18,0,0,0x16,0x18,0,0,0,0x18,0, -0,0,0x19,0,0,0x16,0x18,0,0,0x1a,0x18,0,0,0,0x1b,0, -0,0x1c,0x1d,0,0,0x1e,0x1d,0,0x1e,0x1f,0,0x20,0x21,0,0x22,0, -0,0x23,0,0,0x18,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0x24,0,0,0,0,0, +0,0,0x1a,0,0,0x16,0x18,0,0,0x1b,0x18,0,0,0,0x1c,0, +0,0x1d,0x1e,0,0,0x1f,0x1e,0,0x1f,0x20,0,0x21,0x22,0,0x23,0, +0,0x24,0,0,0x18,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0x25,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0x25,0x25,0,0,0,0,0x26,0, -0,0,0,0,0,0x27,0,0,0,0x13,0,0,0,0,0,0, -0x28,0,0,0x29,0,0x2a,0,0,0,0x25,0x2b,0x10,0,0x2c,0,0x2d, -0,0x2e,0,0,0,0,0x2f,0x30,0,0,0,0,0,0,1,0x31, +0,0,0,0,0,0,0,0,0x26,0x26,0,0,0,0,0x27,0, +0,0,0,0,0,0x28,0,0,0,0x13,0,0,0,0,0,0, +0x29,0,0,0x2a,0,0x2b,0,0,0,0x26,0x2c,0x2d,0,0x2e,0,0x2f, +0,0x30,0,0,0,0,0x31,0x32,0,0,0,0,0,0,1,0x33, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0x32,0x33,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0x34,0x35,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0x34,0,0,0,0x35,0,0,0,1, +0,0,0,0,0,0,0,0x36,0,0,0,0x37,0,0,0,1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0x36,0,0,0x37,0,0,0,0,0,0,0,0,0,0,0, +0,0x38,0,0,0x39,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -101,9 +101,9 @@ const uint8_t CollationFCD::lcccIndex[2048]={ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0x38,0x39,0,0,0x3a,0,0,0,0,0,0,0,0, -0x22,0,0,0,0,0,0x2b,0x3b,0,0x3c,0x3d,0,0,0x3d,0x3e,0, -0,0,0,0,0,0x3f,0x40,0x41,0,0,0,0,0,0,0,0x18, +0,0,0,0x3a,0x3b,0,0,0x3c,0,0,0,0,0,0,0,0, +0x23,0,0,0,0,0,0x2c,0x3d,0,0x3e,0x3f,0,0,0x3f,0x40,0, +0,0,0,0,0,0x41,0x42,0x43,0,0,0,0,0,0,0,0x18, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -126,7 +126,7 @@ const uint8_t CollationFCD::lcccIndex[2048]={ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0x42,0x43,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0x44,0x45,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, @@ -143,17 +143,17 @@ const uint8_t CollationFCD::lcccIndex[2048]={ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0x44,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0x19,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; -const uint32_t CollationFCD::lcccBits[69]={ +const uint32_t CollationFCD::lcccBits[70]={ 0,0xffffffff,0xffff7fff,0xffff,0xf8,0xfffe0000,0xbfffffff,0xb6,0x7ff0000,0xfffff800,0x10000,0x9fc00000,0x3d9f,0x20000,0xffff0000,0x7ff, -0xff800,0xfbc00000,0x3eef,0xe000000,0xfff00000,0xfffffffb,0x10000000,0x1e2000,0x2000,0x602000,0x18000000,0x400,0x7000000,0xf00,0x3000000,0x2a00000, -0x3c3e0000,0xdf,0x40,0x6800000,0xe0000000,0x100000,0x20040000,0x200,0x1800000,0x9fe00001,0x3fff0000,0x10,0xc00,0xc0040,0x800000,0xfff70000, -0x31021fd,0xfbffffff,0x1fff0000,0x1ffe2,0x38000,0x80000000,0xfc00,0x6000000,0x3ff08000,0xc0000000,0x30000,0x3ffff,0x3800,0x80000,1,0xc19d0000, -2,0x400000,0x40000f5,0x5108000,0x40000000 +0x200ff800,0xfbc00000,0x3eef,0xe000000,0xfff80000,0xfffffffb,0x10000000,0x1e2000,0x2000,0x40000000,0x602000,0x18000000,0x400,0x7000000,0xf00,0x3000000, +0x2a00000,0x3c3e0000,0xdf,0x40,0x6800000,0xe0000000,0x100000,0x20040000,0x200,0x1800000,0x9fe00001,0x3fff0000,0x10,0xff800,0xc00,0xc0040, +0x800000,0xfff70000,0x31021fd,0xfbffffff,0x1fff0000,0x1ffe2,0x38000,0x80000000,0xfc00,0x6000000,0x3ff08000,0xc0000000,0x30000,0x3ffff,0x3800,0x80000, +1,0xc19d0000,2,0x400000,0x40000fd,0x5108000 }; const uint8_t CollationFCD::tcccIndex[2048]={ @@ -161,27 +161,27 @@ const uint8_t CollationFCD::tcccIndex[2048]={ 0xb,0xc,0,0,0,0,0,0,1,1,0xd,0xe,0xf,0x10,0x11,0, 0x12,0x13,0x14,0x15,0x16,0,0x17,0x18,0,0,0,0,0x19,0x1a,0x1b,0, 0x1c,0x1d,0x1e,0x1f,0,0,0x20,0x21,0x22,0x23,0x24,0,0,0,0,0x25, -0x26,0x27,0x28,0,0,0,0x29,0x2a,0,0x2b,0x2c,0,0,0x2d,0x2e,0, -0,0x2f,0x30,0,0,0x2d,0x31,0,0,0x2d,0x32,0,0,0,0x31,0, -0,0,0x33,0,0,0x2d,0x31,0,0,0x34,0x31,0,0,0,0x35,0, -0,0x36,0x37,0,0,0x38,0x37,0,0x38,0x39,0,0x3a,0x3b,0,0x3c,0, -0,0x3d,0,0,0x31,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0,0,0x3e,0,0,0,0,0, +0x26,0x27,0x28,0,0,0,0x29,0x2a,0,0x2b,0x2c,0,0,0x2d,0x2e,0x2f, +0,0x30,0x31,0,0,0x2d,0x32,0,0,0x2d,0x33,0,0,0,0x32,0, +0,0,0x34,0,0,0x2d,0x32,0,0,0x35,0x32,0,0,0,0x36,0, +0,0x37,0x38,0,0,0x39,0x38,0,0x39,0x3a,0,0x3b,0x3c,0,0x3d,0, +0,0x3e,0,0,0x32,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0x3f,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0x3f,0x3f,0,0,0,0,0x40,0, -0,0,0,0,0,0x41,0,0,0,0x28,0,0,0,0,0,0, -0x42,0,0,0x43,0,0x44,0,0,0,0x3f,0x45,0x25,0,0x46,0,0x47, -0,0x48,0,0,0,0,0x49,0x4a,0,0,0,0,0,0,1,0x4b, -1,1,1,1,0x4c,1,1,0x4d,0x4e,1,0x4f,0x50,1,0x51,0x52,0x53, -0,0,0,0,0,0,0x54,0x55,0,0x56,0,0,0x57,0x58,0x59,0, -0x5a,0x5b,0x5c,0x5d,0x5e,0x5f,0,0x60,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0x40,0x40,0,0,0,0,0x41,0, +0,0,0,0,0,0x42,0,0,0,0x28,0,0,0,0,0,0, +0x43,0,0,0x44,0,0x45,0,0,0,0x40,0x46,0x47,0,0x48,0,0x49, +0,0x4a,0,0,0,0,0x4b,0x4c,0,0,0,0,0,0,1,0x4d, +1,1,1,1,0x4e,1,1,0x4f,0x50,1,0x51,0x52,1,0x53,0x54,0x55, +0,0,0,0,0,0,0x56,0x57,0,0x58,0,0,0x59,0x5a,0x5b,0, +0x5c,0x5d,0x5e,0x5f,0x60,0x61,0,0x62,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0x2d,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0x61,0,0,0,0x62,0,0,0,1, +0,0,0,0,0,0,0,0x63,0,0,0,0x64,0,0,0,1, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0x63,0x64,0x65,0x66,0x64,0x65,0x67,0,0,0,0,0,0,0,0, +0,0x65,0x66,0x67,0x68,0x66,0x67,0x69,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -240,9 +240,9 @@ const uint8_t CollationFCD::tcccIndex[2048]={ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0x68,0x69,0,0,0x6a,0,0,0,0,0,0,0,0, -0x3c,0,0,0,0,0,0x45,0x6b,0,0x6c,0x6d,0,0,0x6d,0x6e,0, -0,0,0,0,0,0x6f,0x70,0x71,0,0,0,0,0,0,0,0x31, +0,0,0,0x6a,0x6b,0,0,0x6c,0,0,0,0,0,0,0,0, +0x3d,0,0,0,0,0,0x46,0x6d,0,0x6e,0x6f,0,0,0x6f,0x70,0, +0,0,0,0,0,0x71,0x72,0x73,0,0,0,0,0,0,0,0x32, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -265,7 +265,7 @@ const uint8_t CollationFCD::tcccIndex[2048]={ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0x72,0x73,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0x74,0x75,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, @@ -282,20 +282,20 @@ const uint8_t CollationFCD::tcccIndex[2048]={ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, -0,0,0,0,0,0,0,0,0x3e,0x74,0x75,0,0,0,0,0, +0,0,0,0,0,0,0,0,0x3f,0x76,0x77,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0xe,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; -const uint32_t CollationFCD::tcccBits[118]={ +const uint32_t CollationFCD::tcccBits[120]={ 0,0xffffffff,0x3e7effbf,0xbe7effbf,0xfffcffff,0x7ef1ff3f,0xfff3f1f8,0x7fffff3f,0x18003,0xdfffe000,0xff31ffcf,0xcfffffff,0xfffc0,0xffff7fff,0xffff,0x1d760, 0x1fc00,0x187c00,0x200708b,0x2000000,0x708b0000,0xc00000,0xf8,0xfccf0006,0x33ffcfc,0xfffe0000,0xbfffffff,0xb6,0x7ff0000,0x7c,0xfffff800,0x10000, -0x9fc80005,0x3d9f,0x20000,0xffff0000,0x7ff,0xff800,0xfbc00000,0x3eef,0xe000000,0xfff00000,0xfffffffb,0x10120200,0xff1e2000,0x10000000,0xb0002000,0x10480000, -0x4e002000,0x2000,0x30002000,0x602100,0x18000000,0x24000400,0x7000000,0xf00,0x3000000,0x2a00000,0x3d7e0000,0xdf,0x40,0x6800000,0xe0000000,0x100000, -0x20040000,0x200,0x1800000,0x9fe00001,0x3fff0000,0x10,0xc00,0xc0040,0x800000,0xfff70000,0x31021fd,0xfbffffff,0xbffffff,0x3ffffff,0x3f3fffff,0xaaff3f3f, -0x3fffffff,0x1fdfffff,0xefcfffde,0x1fdc7fff,0x1fff0000,0x1ffe2,0x800,0xc000000,0x4000,0xe000,0x1210,0x50,0x292,0x333e005,0x333,0xf000, -0x3c0f,0x38000,0x80000000,0xfc00,0x55555000,0x36db02a5,0x46100000,0x47900000,0x3ff08000,0xc0000000,0x30000,0x3ffff,0x3800,0x80000,1,0xc19d0000, -2,0x400000,0x40000f5,0x5108000,0x5f7ffc00,0x7fdb +0x9fc80005,0x3d9f,0x20000,0xffff0000,0x7ff,0x200ff800,0xfbc00000,0x3eef,0xe000000,0xfff80000,0xfffffffb,0x10120200,0xff1e2000,0x10000000,0xb0002000,0x40000000, +0x10480000,0x4e002000,0x2000,0x30002000,0x602100,0x18000000,0x24000400,0x7000000,0xf00,0x3000000,0x2a00000,0x3d7e0000,0xdf,0x40,0x6800000,0xe0000000, +0x100000,0x20040000,0x200,0x1800000,0x9fe00001,0x3fff0000,0x10,0xff800,0xc00,0xc0040,0x800000,0xfff70000,0x31021fd,0xfbffffff,0xbffffff,0x3ffffff, +0x3f3fffff,0xaaff3f3f,0x3fffffff,0x1fdfffff,0xefcfffde,0x1fdc7fff,0x1fff0000,0x1ffe2,0x800,0xc000000,0x4000,0xe000,0x1210,0x50,0x292,0x333e005, +0x333,0xf000,0x3c0f,0x38000,0x80000000,0xfc00,0x55555000,0x36db02a5,0x46100000,0x47900000,0x3ff08000,0xc0000000,0x30000,0x3ffff,0x3800,0x80000, +1,0xc19d0000,2,0x400000,0x40000fd,0x5108000,0x5f7ffc00,0x7fdb }; U_NAMESPACE_END diff --git a/deps/icu-small/source/i18n/compactdecimalformat.cpp b/deps/icu-small/source/i18n/compactdecimalformat.cpp index b2aacc45cda49c..4dd2241b23d0a6 100644 --- a/deps/icu-small/source/i18n/compactdecimalformat.cpp +++ b/deps/icu-small/source/i18n/compactdecimalformat.cpp @@ -1,1013 +1,75 @@ -// © 2016 and later: Unicode, Inc. and others. +// © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -* -* File COMPACTDECIMALFORMAT.CPP -* -******************************************************************************** -*/ + #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING -#include "charstr.h" -#include "cstring.h" -#include "digitlst.h" -#include "mutex.h" -#include "unicode/compactdecimalformat.h" -#include "unicode/numsys.h" -#include "unicode/plurrule.h" -#include "unicode/ures.h" -#include "ucln_in.h" -#include "uhash.h" -#include "umutex.h" -#include "unicode/ures.h" -#include "uresimp.h" - -// Maps locale name to CDFLocaleData struct. -static UHashtable* gCompactDecimalData = NULL; -static UMutex gCompactDecimalMetaLock = U_MUTEX_INITIALIZER; - -U_NAMESPACE_BEGIN - -static const int32_t MAX_DIGITS = 15; -static const char gOther[] = "other"; -static const char gLatnTag[] = "latn"; -static const char gNumberElementsTag[] = "NumberElements"; -static const char gDecimalFormatTag[] = "decimalFormat"; -static const char gPatternsShort[] = "patternsShort"; -static const char gPatternsLong[] = "patternsLong"; -static const char gLatnPath[] = "NumberElements/latn"; - -static const UChar u_0 = 0x30; -static const UChar u_apos = 0x27; - -static const UChar kZero[] = {u_0}; - -// Used to unescape single quotes. -enum QuoteState { - OUTSIDE, - INSIDE_EMPTY, - INSIDE_FULL -}; - -enum FallbackFlags { - ANY = 0, - MUST = 1, - NOT_ROOT = 2 - // Next one will be 4 then 6 etc. -}; - - -// CDFUnit represents a prefix-suffix pair for a particular variant -// and log10 value. -struct CDFUnit : public UMemory { - UnicodeString prefix; - UnicodeString suffix; - inline CDFUnit() : prefix(), suffix() { - prefix.setToBogus(); - } - inline ~CDFUnit() {} - inline UBool isSet() const { - return !prefix.isBogus(); - } - inline void markAsSet() { - prefix.remove(); - } -}; - -// CDFLocaleStyleData contains formatting data for a particular locale -// and style. -class CDFLocaleStyleData : public UMemory { - public: - // What to divide by for each log10 value when formatting. These values - // will be powers of 10. For English, would be: - // 1, 1, 1, 1000, 1000, 1000, 1000000, 1000000, 1000000, 1000000000 ... - double divisors[MAX_DIGITS]; - // Maps plural variants to CDFUnit[MAX_DIGITS] arrays. - // To format a number x, - // first compute log10(x). Compute displayNum = (x / divisors[log10(x)]). - // Compute the plural variant for displayNum - // (e.g zero, one, two, few, many, other). - // Compute cdfUnits = unitsByVariant[pluralVariant]. - // Prefix and suffix to use at cdfUnits[log10(x)] - UHashtable* unitsByVariant; - // A flag for whether or not this CDFLocaleStyleData was loaded from the - // Latin numbering system as a fallback from the locale numbering system. - // This value is meaningless if the object is bogus or empty. - UBool fromFallback; - inline CDFLocaleStyleData() : unitsByVariant(NULL), fromFallback(FALSE) { - uprv_memset(divisors, 0, sizeof(divisors)); - } - ~CDFLocaleStyleData(); - // Init initializes this object. - void Init(UErrorCode& status); - inline UBool isBogus() const { - return unitsByVariant == NULL; - } - void setToBogus(); - UBool isEmpty() { - return unitsByVariant == NULL || unitsByVariant->count == 0; - } - private: - CDFLocaleStyleData(const CDFLocaleStyleData&); - CDFLocaleStyleData& operator=(const CDFLocaleStyleData&); -}; - -// CDFLocaleData contains formatting data for a particular locale. -struct CDFLocaleData : public UMemory { - CDFLocaleStyleData shortData; - CDFLocaleStyleData longData; - inline CDFLocaleData() : shortData(), longData() { } - inline ~CDFLocaleData() { } - // Init initializes this object. - void Init(UErrorCode& status); -}; - -U_NAMESPACE_END - -U_CDECL_BEGIN - -static UBool U_CALLCONV cdf_cleanup(void) { - if (gCompactDecimalData != NULL) { - uhash_close(gCompactDecimalData); - gCompactDecimalData = NULL; - } - return TRUE; -} - -static void U_CALLCONV deleteCDFUnits(void* ptr) { - delete [] (icu::CDFUnit*) ptr; -} - -static void U_CALLCONV deleteCDFLocaleData(void* ptr) { - delete (icu::CDFLocaleData*) ptr; -} - -U_CDECL_END +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT -U_NAMESPACE_BEGIN +#include "unicode/compactdecimalformat.h" +#include "number_mapper.h" +#include "number_decimfmtprops.h" -static UBool divisors_equal(const double* lhs, const double* rhs); -static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status); +using namespace icu; -static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status); -static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status); -static void load(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status); -static int32_t populatePrefixSuffix(const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UBool overwrite, UErrorCode& status); -static double calculateDivisor(double power10, int32_t numZeros); -static UBool onlySpaces(UnicodeString u); -static void fixQuotes(UnicodeString& s); -static void checkForOtherVariants(CDFLocaleStyleData* result, UErrorCode& status); -static void fillInMissing(CDFLocaleStyleData* result); -static int32_t computeLog10(double x, UBool inRange); -static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status); -static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value); UOBJECT_DEFINE_RTTI_IMPLEMENTATION(CompactDecimalFormat) -CompactDecimalFormat::CompactDecimalFormat( - const DecimalFormat& decimalFormat, - const UHashtable* unitsByVariant, - const double* divisors, - PluralRules* pluralRules) - : DecimalFormat(decimalFormat), _unitsByVariant(unitsByVariant), _divisors(divisors), _pluralRules(pluralRules) { -} - -CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) - : DecimalFormat(source), _unitsByVariant(source._unitsByVariant), _divisors(source._divisors), _pluralRules(source._pluralRules->clone()) { -} - -CompactDecimalFormat* U_EXPORT2 -CompactDecimalFormat::createInstance( - const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { - LocalPointer decfmt((DecimalFormat*) NumberFormat::makeInstance(inLocale, UNUM_DECIMAL, TRUE, status)); - if (U_FAILURE(status)) { - return NULL; - } - LocalPointer pluralRules(PluralRules::forLocale(inLocale, status)); - if (U_FAILURE(status)) { - return NULL; - } - const CDFLocaleStyleData* data = getCDFLocaleStyleData(inLocale, style, status); - if (U_FAILURE(status)) { - return NULL; - } - CompactDecimalFormat* result = - new CompactDecimalFormat(*decfmt, data->unitsByVariant, data->divisors, pluralRules.getAlias()); - if (result == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - pluralRules.orphan(); - result->setMaximumSignificantDigits(3); - result->setSignificantDigitsUsed(TRUE); - result->setGroupingUsed(FALSE); - return result; -} - -CompactDecimalFormat& -CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) { - if (this != &rhs) { - DecimalFormat::operator=(rhs); - _unitsByVariant = rhs._unitsByVariant; - _divisors = rhs._divisors; - delete _pluralRules; - _pluralRules = rhs._pluralRules->clone(); - } - return *this; -} - -CompactDecimalFormat::~CompactDecimalFormat() { - delete _pluralRules; -} - -Format* -CompactDecimalFormat::clone(void) const { - return new CompactDecimalFormat(*this); +CompactDecimalFormat* +CompactDecimalFormat::createInstance(const Locale& inLocale, UNumberCompactStyle style, + UErrorCode& status) { + return new CompactDecimalFormat(inLocale, style, status); } -UBool -CompactDecimalFormat::operator==(const Format& that) const { - if (this == &that) { - return TRUE; - } - return (DecimalFormat::operator==(that) && eqHelper((const CompactDecimalFormat&) that)); -} - -UBool -CompactDecimalFormat::eqHelper(const CompactDecimalFormat& that) const { - return uhash_equals(_unitsByVariant, that._unitsByVariant) && divisors_equal(_divisors, that._divisors) && (*_pluralRules == *that._pluralRules); -} - -UnicodeString& -CompactDecimalFormat::format( - double number, - UnicodeString& appendTo, - FieldPosition& pos) const { - UErrorCode status = U_ZERO_ERROR; - return format(number, appendTo, pos, status); -} - -UnicodeString& -CompactDecimalFormat::format( - double number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return appendTo; - } - DigitList orig, rounded; - orig.set(number); - UBool isNegative; - _round(orig, rounded, isNegative, status); - if (U_FAILURE(status)) { - return appendTo; - } - double roundedDouble = rounded.getDouble(); - if (isNegative) { - roundedDouble = -roundedDouble; - } - int32_t baseIdx = computeLog10(roundedDouble, TRUE); - double numberToFormat = roundedDouble / _divisors[baseIdx]; - UnicodeString variant = _pluralRules->select(numberToFormat); - if (isNegative) { - numberToFormat = -numberToFormat; - } - const CDFUnit* unit = getCDFUnitFallback(_unitsByVariant, variant, baseIdx); - appendTo += unit->prefix; - DecimalFormat::format(numberToFormat, appendTo, pos); - appendTo += unit->suffix; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format( - double /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format( - int32_t number, - UnicodeString& appendTo, - FieldPosition& pos) const { - return format((double) number, appendTo, pos); -} - -UnicodeString& -CompactDecimalFormat::format( - int32_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - return format((double) number, appendTo, pos, status); -} - -UnicodeString& -CompactDecimalFormat::format( - int32_t /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} - -UnicodeString& -CompactDecimalFormat::format( - int64_t number, - UnicodeString& appendTo, - FieldPosition& pos) const { - return format((double) number, appendTo, pos); -} - -UnicodeString& -CompactDecimalFormat::format( - int64_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - return format((double) number, appendTo, pos, status); +CompactDecimalFormat::CompactDecimalFormat(const Locale& inLocale, UNumberCompactStyle style, + UErrorCode& status) + : DecimalFormat(new DecimalFormatSymbols(inLocale, status), status) { + if (U_FAILURE(status)) return; + // Minimal properties: let the non-shim code path do most of the logic for us. + fields->properties->compactStyle = style; + fields->properties->groupingSize = -2; // do not forward grouping information + fields->properties->minimumGroupingDigits = 2; + touch(status); } -UnicodeString& -CompactDecimalFormat::format( - int64_t /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} +CompactDecimalFormat::CompactDecimalFormat(const CompactDecimalFormat& source) = default; -UnicodeString& -CompactDecimalFormat::format( - StringPiece /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; -} +CompactDecimalFormat::~CompactDecimalFormat() = default; -UnicodeString& -CompactDecimalFormat::format( - const DigitList& /* number */, - UnicodeString& appendTo, - FieldPositionIterator* /* posIter */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; +CompactDecimalFormat& CompactDecimalFormat::operator=(const CompactDecimalFormat& rhs) { + DecimalFormat::operator=(rhs); + return *this; } -UnicodeString& -CompactDecimalFormat::format(const DigitList& /* number */, - UnicodeString& appendTo, - FieldPosition& /* pos */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; - return appendTo; +Format* CompactDecimalFormat::clone() const { + return new CompactDecimalFormat(*this); } void CompactDecimalFormat::parse( - const UnicodeString& /* text */, - Formattable& /* result */, - ParsePosition& /* parsePosition */) const { + const UnicodeString& /* text */, + Formattable& /* result */, + ParsePosition& /* parsePosition */) const { } void CompactDecimalFormat::parse( - const UnicodeString& /* text */, - Formattable& /* result */, - UErrorCode& status) const { - status = U_UNSUPPORTED_ERROR; + const UnicodeString& /* text */, + Formattable& /* result */, + UErrorCode& status) const { + status = U_UNSUPPORTED_ERROR; } CurrencyAmount* CompactDecimalFormat::parseCurrency( - const UnicodeString& /* text */, - ParsePosition& /* pos */) const { - return NULL; -} - -void CDFLocaleStyleData::Init(UErrorCode& status) { - if (unitsByVariant != NULL) { - return; - } - unitsByVariant = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); - if (U_FAILURE(status)) { - return; - } - uhash_setKeyDeleter(unitsByVariant, uprv_free); - uhash_setValueDeleter(unitsByVariant, deleteCDFUnits); -} - -CDFLocaleStyleData::~CDFLocaleStyleData() { - setToBogus(); -} - -void CDFLocaleStyleData::setToBogus() { - if (unitsByVariant != NULL) { - uhash_close(unitsByVariant); - unitsByVariant = NULL; - } -} - -void CDFLocaleData::Init(UErrorCode& status) { - shortData.Init(status); - if (U_FAILURE(status)) { - return; - } - longData.Init(status); -} - -// Helper method for operator= -static UBool divisors_equal(const double* lhs, const double* rhs) { - for (int32_t i = 0; i < MAX_DIGITS; ++i) { - if (lhs[i] != rhs[i]) { - return FALSE; - } - } - return TRUE; -} - -// getCDFLocaleStyleData returns pointer to formatting data for given locale and -// style within the global cache. On cache miss, getCDFLocaleStyleData loads -// the data from CLDR into the global cache before returning the pointer. If a -// UNUM_LONG data is requested for a locale, and that locale does not have -// UNUM_LONG data, getCDFLocaleStyleData will fall back to UNUM_SHORT data for -// that locale. -static const CDFLocaleStyleData* getCDFLocaleStyleData(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status) { - if (U_FAILURE(status)) { - return NULL; - } - CDFLocaleData* result = NULL; - const char* key = inLocale.getName(); - { - Mutex lock(&gCompactDecimalMetaLock); - if (gCompactDecimalData == NULL) { - gCompactDecimalData = uhash_open(uhash_hashChars, uhash_compareChars, NULL, &status); - if (U_FAILURE(status)) { - return NULL; - } - uhash_setKeyDeleter(gCompactDecimalData, uprv_free); - uhash_setValueDeleter(gCompactDecimalData, deleteCDFLocaleData); - ucln_i18n_registerCleanup(UCLN_I18N_CDFINFO, cdf_cleanup); - } else { - result = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); - } - } - if (result != NULL) { - return extractDataByStyleEnum(*result, style, status); - } - - result = loadCDFLocaleData(inLocale, status); - if (U_FAILURE(status)) { - return NULL; - } - - { - Mutex lock(&gCompactDecimalMetaLock); - CDFLocaleData* temp = (CDFLocaleData*) uhash_get(gCompactDecimalData, key); - if (temp != NULL) { - delete result; - result = temp; - } else { - uhash_put(gCompactDecimalData, uprv_strdup(key), (void*) result, &status); - if (U_FAILURE(status)) { - return NULL; - } - } - } - return extractDataByStyleEnum(*result, style, status); -} - -static const CDFLocaleStyleData* extractDataByStyleEnum(const CDFLocaleData& data, UNumberCompactStyle style, UErrorCode& status) { - switch (style) { - case UNUM_SHORT: - return &data.shortData; - case UNUM_LONG: - if (!data.longData.isBogus()) { - return &data.longData; - } - return &data.shortData; - default: - status = U_ILLEGAL_ARGUMENT_ERROR; - return NULL; - } -} - -// loadCDFLocaleData loads formatting data from CLDR for a given locale. The -// caller owns the returned pointer. -static CDFLocaleData* loadCDFLocaleData(const Locale& inLocale, UErrorCode& status) { - if (U_FAILURE(status)) { - return NULL; - } - CDFLocaleData* result = new CDFLocaleData; - if (result == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - result->Init(status); - if (U_FAILURE(status)) { - delete result; - return NULL; - } - - load(inLocale, result, status); - - if (U_FAILURE(status)) { - delete result; - return NULL; - } - return result; -} - -namespace { - -struct CmptDecDataSink : public ResourceSink { - - CDFLocaleData& dataBundle; // Where to save values when they are read - UBool isLatin; // Whether or not we are traversing the Latin tree - UBool isFallback; // Whether or not we are traversing the Latin tree as fallback - - enum EPatternsTableKey { PATTERNS_SHORT, PATTERNS_LONG }; - enum EFormatsTableKey { DECIMAL_FORMAT, CURRENCY_FORMAT }; - - /* - * NumberElements{ <-- top (numbering system table) - * latn{ <-- patternsTable (one per numbering system) - * patternsLong{ <-- formatsTable (one per pattern) - * decimalFormat{ <-- powersOfTenTable (one per format) - * 1000{ <-- pluralVariantsTable (one per power of ten) - * one{"0 thousand"} <-- plural variant and template - */ - - CmptDecDataSink(CDFLocaleData& _dataBundle) - : dataBundle(_dataBundle), isLatin(FALSE), isFallback(FALSE) {} - virtual ~CmptDecDataSink(); - - virtual void put(const char *key, ResourceValue &value, UBool isRoot, UErrorCode &errorCode) { - // SPECIAL CASE: Don't consume root in the non-Latin numbering system - if (isRoot && !isLatin) { return; } - - ResourceTable patternsTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i1 = 0; patternsTable.getKeyAndValue(i1, key, value); ++i1) { - - // Check for patternsShort or patternsLong - EPatternsTableKey patternsTableKey; - if (uprv_strcmp(key, gPatternsShort) == 0) { - patternsTableKey = PATTERNS_SHORT; - } else if (uprv_strcmp(key, gPatternsLong) == 0) { - patternsTableKey = PATTERNS_LONG; - } else { - continue; - } - - // Traverse into the formats table - ResourceTable formatsTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i2 = 0; formatsTable.getKeyAndValue(i2, key, value); ++i2) { - - // Check for decimalFormat or currencyFormat - EFormatsTableKey formatsTableKey; - if (uprv_strcmp(key, gDecimalFormatTag) == 0) { - formatsTableKey = DECIMAL_FORMAT; - // TODO: Enable this statement when currency support is added - // } else if (uprv_strcmp(key, gCurrencyFormat) == 0) { - // formatsTableKey = CURRENCY_FORMAT; - } else { - continue; - } - - // Set the current style and destination based on the two keys - UNumberCompactStyle style; - CDFLocaleStyleData* destination = NULL; - if (patternsTableKey == PATTERNS_LONG - && formatsTableKey == DECIMAL_FORMAT) { - style = UNUM_LONG; - destination = &dataBundle.longData; - } else if (patternsTableKey == PATTERNS_SHORT - && formatsTableKey == DECIMAL_FORMAT) { - style = UNUM_SHORT; - destination = &dataBundle.shortData; - // TODO: Enable the following statements when currency support is added - // } else if (patternsTableKey == PATTERNS_SHORT - // && formatsTableKey == CURRENCY_FORMAT) { - // style = UNUM_SHORT_CURRENCY; // or whatever the enum gets named - // destination = &dataBundle.shortCurrencyData; - // } else { - // // Silently ignore this case - // continue; - } - - // SPECIAL CASE: RULES FOR WHETHER OR NOT TO CONSUME THIS TABLE: - // 1) Don't consume longData if shortData was consumed from the non-Latin - // locale numbering system - // 2) Don't consume longData for the first time if this is the root bundle and - // shortData is already populated from a more specific locale. Note that if - // both longData and shortData are both only in root, longData will be - // consumed since it is alphabetically before shortData in the bundle. - if (isFallback - && style == UNUM_LONG - && !dataBundle.shortData.isEmpty() - && !dataBundle.shortData.fromFallback) { - continue; - } - if (isRoot - && style == UNUM_LONG - && dataBundle.longData.isEmpty() - && !dataBundle.shortData.isEmpty()) { - continue; - } - - // Set the "fromFallback" flag on the data object - destination->fromFallback = isFallback; - - // Traverse into the powers of ten table - ResourceTable powersOfTenTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i3 = 0; powersOfTenTable.getKeyAndValue(i3, key, value); ++i3) { - - // The key will always be some even power of 10. e.g 10000. - char* endPtr = NULL; - double power10 = uprv_strtod(key, &endPtr); - if (*endPtr != 0) { - errorCode = U_INTERNAL_PROGRAM_ERROR; - return; - } - int32_t log10Value = computeLog10(power10, FALSE); - - // Silently ignore divisors that are too big. - if (log10Value >= MAX_DIGITS) continue; - - // Iterate over the plural variants ("one", "other", etc) - ResourceTable pluralVariantsTable = value.getTable(errorCode); - if (U_FAILURE(errorCode)) { return; } - for (int i4 = 0; pluralVariantsTable.getKeyAndValue(i4, key, value); ++i4) { - const char* pluralVariant = key; - const UnicodeString formatStr = value.getUnicodeString(errorCode); - - // Copy the data into the in-memory data bundle (do not overwrite - // existing values) - int32_t numZeros = populatePrefixSuffix( - pluralVariant, log10Value, formatStr, - destination->unitsByVariant, FALSE, errorCode); - - // If populatePrefixSuffix returns -1, it means that this key has been - // encountered already. - if (numZeros < 0) { - continue; - } - - // Set the divisor, which is based on the number of zeros in the template - // string. If the divisor from here is different from the one previously - // stored, it means that the number of zeros in different plural variants - // differs; throw an exception. - // TODO: How should I check for floating-point errors here? - // Is there a good reason why "divisor" is double and not long like Java? - double divisor = calculateDivisor(power10, numZeros); - if (destination->divisors[log10Value] != 0.0 - && destination->divisors[log10Value] != divisor) { - errorCode = U_INTERNAL_PROGRAM_ERROR; - return; - } - destination->divisors[log10Value] = divisor; - } - } - } - } - } -}; - -// Virtual destructors must be defined out of line. -CmptDecDataSink::~CmptDecDataSink() {} - -} // namespace - -static void load(const Locale& inLocale, CDFLocaleData* result, UErrorCode& status) { - LocalPointer ns(NumberingSystem::createInstance(inLocale, status)); - if (U_FAILURE(status)) { - return; - } - const char* nsName = ns->getName(); - - LocalUResourceBundlePointer resource(ures_open(NULL, inLocale.getName(), &status)); - if (U_FAILURE(status)) { - return; - } - CmptDecDataSink sink(*result); - sink.isFallback = FALSE; - - // First load the number elements data if nsName is not Latin. - if (uprv_strcmp(nsName, gLatnTag) != 0) { - sink.isLatin = FALSE; - CharString path; - path.append(gNumberElementsTag, status) - .append('/', status) - .append(nsName, status); - ures_getAllItemsWithFallback(resource.getAlias(), path.data(), sink, status); - if (status == U_MISSING_RESOURCE_ERROR) { - // Silently ignore and use Latin - status = U_ZERO_ERROR; - } else if (U_FAILURE(status)) { - return; - } - sink.isFallback = TRUE; - } - - // Now load Latin. - sink.isLatin = TRUE; - ures_getAllItemsWithFallback(resource.getAlias(), gLatnPath, sink, status); - if (U_FAILURE(status)) return; - - // If longData is empty, default it to be equal to shortData - if (result->longData.isEmpty()) { - result->longData.setToBogus(); - } - - // Check for "other" variants in each of the three data classes, and resolve missing elements. - - if (!result->longData.isBogus()) { - checkForOtherVariants(&result->longData, status); - if (U_FAILURE(status)) return; - fillInMissing(&result->longData); - } - - checkForOtherVariants(&result->shortData, status); - if (U_FAILURE(status)) return; - fillInMissing(&result->shortData); - - // TODO: Enable this statement when currency support is added - // checkForOtherVariants(&result->shortCurrencyData, status); - // if (U_FAILURE(status)) return; - // fillInMissing(&result->shortCurrencyData); -} - -// populatePrefixSuffix Adds a specific prefix-suffix pair to result for a -// given variant and log10 value. -// variant is 'zero', 'one', 'two', 'few', 'many', or 'other'. -// formatStr is the format string from which the prefix and suffix are -// extracted. It is usually of form 'Pefix 000 suffix'. -// populatePrefixSuffix returns the number of 0's found in formatStr -// before the decimal point. -// In the special case that formatStr contains only spaces for prefix -// and suffix, populatePrefixSuffix returns log10Value + 1. -static int32_t populatePrefixSuffix( - const char* variant, int32_t log10Value, const UnicodeString& formatStr, UHashtable* result, UBool overwrite, UErrorCode& status) { - if (U_FAILURE(status)) { - return 0; - } - - // ICU 59 HACK: Ignore negative part of format string, mimicking ICU 58 behavior. - // TODO(sffc): Make sure this is fixed during the overhaul port in ICU 60. - int32_t semiPos = formatStr.indexOf(';', 0); - if (semiPos == -1) { - semiPos = formatStr.length(); - } - UnicodeString positivePart = formatStr.tempSubString(0, semiPos); - - int32_t firstIdx = positivePart.indexOf(kZero, UPRV_LENGTHOF(kZero), 0); - // We must have 0's in format string. - if (firstIdx == -1) { - status = U_INTERNAL_PROGRAM_ERROR; - return 0; - } - int32_t lastIdx = positivePart.lastIndexOf(kZero, UPRV_LENGTHOF(kZero), firstIdx); - CDFUnit* unit = createCDFUnit(variant, log10Value, result, status); - if (U_FAILURE(status)) { - return 0; - } - - // Return -1 if we are not overwriting an existing value - if (unit->isSet() && !overwrite) { - return -1; - } - unit->markAsSet(); - - // Everything up to first 0 is the prefix - unit->prefix = positivePart.tempSubString(0, firstIdx); - fixQuotes(unit->prefix); - // Everything beyond the last 0 is the suffix - unit->suffix = positivePart.tempSubString(lastIdx + 1); - fixQuotes(unit->suffix); - - // If there is effectively no prefix or suffix, ignore the actual number of - // 0's and act as if the number of 0's matches the size of the number. - if (onlySpaces(unit->prefix) && onlySpaces(unit->suffix)) { - return log10Value + 1; - } - - // Calculate number of zeros before decimal point - int32_t idx = firstIdx + 1; - while (idx <= lastIdx && positivePart.charAt(idx) == u_0) { - ++idx; - } - return (idx - firstIdx); -} - -// Calculate a divisor based on the magnitude and number of zeros in the -// template string. -static double calculateDivisor(double power10, int32_t numZeros) { - double divisor = power10; - for (int32_t i = 1; i < numZeros; ++i) { - divisor /= 10.0; - } - return divisor; -} - -static UBool onlySpaces(UnicodeString u) { - return u.trim().length() == 0; + const UnicodeString& /* text */, + ParsePosition& /* pos */) const { + return nullptr; } -// fixQuotes unescapes single quotes. Don''t -> Don't. Letter 'j' -> Letter j. -// Modifies s in place. -static void fixQuotes(UnicodeString& s) { - QuoteState state = OUTSIDE; - int32_t len = s.length(); - int32_t dest = 0; - for (int32_t i = 0; i < len; ++i) { - UChar ch = s.charAt(i); - if (ch == u_apos) { - if (state == INSIDE_EMPTY) { - s.setCharAt(dest, ch); - ++dest; - } - } else { - s.setCharAt(dest, ch); - ++dest; - } - - // Update state - switch (state) { - case OUTSIDE: - state = ch == u_apos ? INSIDE_EMPTY : OUTSIDE; - break; - case INSIDE_EMPTY: - case INSIDE_FULL: - state = ch == u_apos ? OUTSIDE : INSIDE_FULL; - break; - default: - break; - } - } - s.truncate(dest); -} - -// Checks to make sure that an "other" variant is present in all -// powers of 10. -static void checkForOtherVariants(CDFLocaleStyleData* result, - UErrorCode& status) { - if (result == NULL || result->unitsByVariant == NULL) { - return; - } - - const CDFUnit* otherByBase = - (const CDFUnit*) uhash_get(result->unitsByVariant, gOther); - if (otherByBase == NULL) { - status = U_INTERNAL_PROGRAM_ERROR; - return; - } - - // Check all other plural variants, and make sure that if - // any of them are populated, then other is also populated - int32_t pos = UHASH_FIRST; - const UHashElement* element; - while ((element = uhash_nextElement(result->unitsByVariant, &pos)) != NULL) { - CDFUnit* variantsByBase = (CDFUnit*) element->value.pointer; - if (variantsByBase == otherByBase) continue; - for (int32_t log10Value = 0; log10Value < MAX_DIGITS; ++log10Value) { - if (variantsByBase[log10Value].isSet() - && !otherByBase[log10Value].isSet()) { - status = U_INTERNAL_PROGRAM_ERROR; - return; - } - } - } -} - -// fillInMissing ensures that the data in result is complete. -// result data is complete if for each variant in result, there exists -// a prefix-suffix pair for each log10 value and there also exists -// a divisor for each log10 value. -// -// First this function figures out for which log10 values, the other -// variant already had data. These are the same log10 values defined -// in CLDR. -// -// For each log10 value not defined in CLDR, it uses the divisor for -// the last defined log10 value or 1. -// -// Then for each variant, it does the following. For each log10 -// value not defined in CLDR, copy the prefix-suffix pair from the -// previous log10 value. If log10 value is defined in CLDR but is -// missing from given variant, copy the prefix-suffix pair for that -// log10 value from the 'other' variant. -static void fillInMissing(CDFLocaleStyleData* result) { - const CDFUnit* otherUnits = - (const CDFUnit*) uhash_get(result->unitsByVariant, gOther); - UBool definedInCLDR[MAX_DIGITS]; - double lastDivisor = 1.0; - for (int32_t i = 0; i < MAX_DIGITS; ++i) { - if (!otherUnits[i].isSet()) { - result->divisors[i] = lastDivisor; - definedInCLDR[i] = FALSE; - } else { - lastDivisor = result->divisors[i]; - definedInCLDR[i] = TRUE; - } - } - // Iterate over each variant. - int32_t pos = UHASH_FIRST; - const UHashElement* element = uhash_nextElement(result->unitsByVariant, &pos); - for (;element != NULL; element = uhash_nextElement(result->unitsByVariant, &pos)) { - CDFUnit* units = (CDFUnit*) element->value.pointer; - for (int32_t i = 0; i < MAX_DIGITS; ++i) { - if (definedInCLDR[i]) { - if (!units[i].isSet()) { - units[i] = otherUnits[i]; - } - } else { - if (i == 0) { - units[0].markAsSet(); - } else { - units[i] = units[i - 1]; - } - } - } - } -} - -// computeLog10 computes floor(log10(x)). If inRange is TRUE, the biggest -// value computeLog10 will return MAX_DIGITS -1 even for -// numbers > 10^MAX_DIGITS. If inRange is FALSE, computeLog10 will return -// up to MAX_DIGITS. -static int32_t computeLog10(double x, UBool inRange) { - int32_t result = 0; - int32_t max = inRange ? MAX_DIGITS - 1 : MAX_DIGITS; - while (x >= 10.0) { - x /= 10.0; - ++result; - if (result == max) { - break; - } - } - return result; -} - -// createCDFUnit returns a pointer to the prefix-suffix pair for a given -// variant and log10 value within table. If no such prefix-suffix pair is -// stored in table, one is created within table before returning pointer. -static CDFUnit* createCDFUnit(const char* variant, int32_t log10Value, UHashtable* table, UErrorCode& status) { - if (U_FAILURE(status)) { - return NULL; - } - CDFUnit *cdfUnit = (CDFUnit*) uhash_get(table, variant); - if (cdfUnit == NULL) { - cdfUnit = new CDFUnit[MAX_DIGITS]; - if (cdfUnit == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - uhash_put(table, uprv_strdup(variant), cdfUnit, &status); - if (U_FAILURE(status)) { - return NULL; - } - } - CDFUnit* result = &cdfUnit[log10Value]; - return result; -} - -// getCDFUnitFallback returns a pointer to the prefix-suffix pair for a given -// variant and log10 value within table. If the given variant doesn't exist, it -// falls back to the OTHER variant. Therefore, this method will always return -// some non-NULL value. -static const CDFUnit* getCDFUnitFallback(const UHashtable* table, const UnicodeString& variant, int32_t log10Value) { - CharString cvariant; - UErrorCode status = U_ZERO_ERROR; - const CDFUnit *cdfUnit = NULL; - cvariant.appendInvariantChars(variant, status); - if (!U_FAILURE(status)) { - cdfUnit = (const CDFUnit*) uhash_get(table, cvariant.data()); - } - if (cdfUnit == NULL) { - cdfUnit = (const CDFUnit*) uhash_get(table, gOther); - } - return &cdfUnit[log10Value]; -} -U_NAMESPACE_END -#endif +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/currpinf.cpp b/deps/icu-small/source/i18n/currpinf.cpp index 5d3ca620891500..6b1efd5f4da721 100644 --- a/deps/icu-small/source/i18n/currpinf.cpp +++ b/deps/icu-small/source/i18n/currpinf.cpp @@ -203,6 +203,9 @@ CurrencyPluralInfo::setCurrencyPluralPattern(const UnicodeString& pluralCount, const UnicodeString& pattern, UErrorCode& status) { if (U_SUCCESS(status)) { + UnicodeString* oldValue = static_cast( + fPluralCountToCurrencyUnitPattern->get(pluralCount)); + delete oldValue; fPluralCountToCurrencyUnitPattern->put(pluralCount, new UnicodeString(pattern), status); } } diff --git a/deps/icu-small/source/i18n/currunit.cpp b/deps/icu-small/source/i18n/currunit.cpp index 83429c01694acc..6d8d1cd6c6f97f 100644 --- a/deps/icu-small/source/i18n/currunit.cpp +++ b/deps/icu-small/source/i18n/currunit.cpp @@ -17,21 +17,32 @@ #include "unicode/currunit.h" #include "unicode/ustring.h" #include "cstring.h" +#include "uinvchar.h" + +static constexpr char16_t kDefaultCurrency[] = u"XXX"; U_NAMESPACE_BEGIN CurrencyUnit::CurrencyUnit(ConstChar16Ptr _isoCode, UErrorCode& ec) { - *isoCode = 0; - if (U_SUCCESS(ec)) { - if (_isoCode != nullptr && u_strlen(_isoCode)==3) { - u_strcpy(isoCode, _isoCode); - char simpleIsoCode[4]; - u_UCharsToChars(isoCode, simpleIsoCode, 4); - initCurrency(simpleIsoCode); - } else { - ec = U_ILLEGAL_ARGUMENT_ERROR; - } + // The constructor always leaves the CurrencyUnit in a valid state (with a 3-character currency code). + // Note: in ICU4J Currency.getInstance(), we check string length for 3, but in ICU4C we allow a + // non-NUL-terminated string to be passed as an argument, so it is not possible to check length. + const char16_t* isoCodeToUse; + if (U_FAILURE(ec) || _isoCode == nullptr) { + isoCodeToUse = kDefaultCurrency; + } else if (!uprv_isInvariantUString(_isoCode, 3)) { + // TODO: Perform a more strict ASCII check like in ICU4J isAlpha3Code? + isoCodeToUse = kDefaultCurrency; + ec = U_INVARIANT_CONVERSION_ERROR; + } else { + isoCodeToUse = _isoCode; } + // TODO: Perform uppercasing here like in ICU4J Currency.getInstance()? + uprv_memcpy(isoCode, isoCodeToUse, sizeof(UChar) * 3); + isoCode[3] = 0; + char simpleIsoCode[4]; + u_UCharsToChars(isoCode, simpleIsoCode, 4); + initCurrency(simpleIsoCode); } CurrencyUnit::CurrencyUnit(const CurrencyUnit& other) : MeasureUnit(other) { @@ -52,7 +63,7 @@ CurrencyUnit::CurrencyUnit(const MeasureUnit& other, UErrorCode& ec) : MeasureUn } CurrencyUnit::CurrencyUnit() : MeasureUnit() { - u_strcpy(isoCode, u"XXX"); + u_strcpy(isoCode, kDefaultCurrency); char simpleIsoCode[4]; u_UCharsToChars(isoCode, simpleIsoCode, 4); initCurrency(simpleIsoCode); diff --git a/deps/icu-small/source/i18n/dcfmtimp.h b/deps/icu-small/source/i18n/dcfmtimp.h deleted file mode 100644 index e582efb344b3b8..00000000000000 --- a/deps/icu-small/source/i18n/dcfmtimp.h +++ /dev/null @@ -1,54 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************** -* Copyright (C) 2012-2014, International Business Machines -* Corporation and others. All Rights Reserved. -********************************************************************************/ - -#ifndef DCFMTIMP_H -#define DCFMTIMP_H - -#include "unicode/utypes.h" - - -#if UCONFIG_FORMAT_FASTPATHS_49 - -U_NAMESPACE_BEGIN - -enum EDecimalFormatFastpathStatus { - kFastpathNO = 0, - kFastpathYES = 1, - kFastpathUNKNOWN = 2, /* not yet set */ - kFastpathMAYBE = 3 /* depends on value being formatted. */ -}; - -/** - * Must be smaller than DecimalFormat::fReserved - */ -struct DecimalFormatInternal { - uint8_t fFastFormatStatus; - uint8_t fFastParseStatus; - - DecimalFormatInternal &operator=(const DecimalFormatInternal& rhs) { - fFastParseStatus = rhs.fFastParseStatus; - fFastFormatStatus = rhs.fFastFormatStatus; - return *this; - } -#ifdef FMT_DEBUG - void dump() const { - printf("DecimalFormatInternal: fFastFormatStatus=%c, fFastParseStatus=%c\n", - "NY?"[(int)fFastFormatStatus&3], - "NY?"[(int)fFastParseStatus&3] - ); - } -#endif -}; - - - -U_NAMESPACE_END - -#endif - -#endif diff --git a/deps/icu-small/source/i18n/dcfmtsym.cpp b/deps/icu-small/source/i18n/dcfmtsym.cpp index 680c3120a1e0f9..5a432aec8e4df0 100644 --- a/deps/icu-small/source/i18n/dcfmtsym.cpp +++ b/deps/icu-small/source/i18n/dcfmtsym.cpp @@ -66,7 +66,7 @@ static const UChar INTL_CURRENCY_SYMBOL_STR[] = {0xa4, 0xa4, 0}; static const char *gNumberElementKeys[DecimalFormatSymbols::kFormatSymbolCount] = { "decimal", "group", - "list", + NULL, /* #11897: the symbol is NOT the pattern separator symbol */ "percentSign", NULL, /* Native zero digit is deprecated from CLDR - get it from the numbering system */ NULL, /* Pattern digit character is deprecated from CLDR - use # by default always */ @@ -98,7 +98,7 @@ static const char *gNumberElementKeys[DecimalFormatSymbols::kFormatSymbolCount] // Initializes this with the decimal format symbols in the default locale. DecimalFormatSymbols::DecimalFormatSymbols(UErrorCode& status) - : UObject(), locale() { + : UObject(), locale(), currPattern(NULL) { initialize(locale, status, TRUE); } @@ -106,12 +106,12 @@ DecimalFormatSymbols::DecimalFormatSymbols(UErrorCode& status) // Initializes this with the decimal format symbols in the desired locale. DecimalFormatSymbols::DecimalFormatSymbols(const Locale& loc, UErrorCode& status) - : UObject(), locale(loc) { + : UObject(), locale(loc), currPattern(NULL) { initialize(locale, status); } DecimalFormatSymbols::DecimalFormatSymbols(const Locale& loc, const NumberingSystem& ns, UErrorCode& status) - : UObject(), locale(loc) { + : UObject(), locale(loc), currPattern(NULL) { initialize(locale, status, FALSE, &ns); } @@ -349,7 +349,6 @@ DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status, { if (U_FAILURE(status)) { return; } *validLocale = *actualLocale = 0; - currPattern = NULL; // First initialize all the symbols to the fallbacks for anything we can't find initialize(); @@ -477,6 +476,7 @@ DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status, UErrorCode localStatus = U_ZERO_ERROR; uccLen = ucurr_forLocale(locName, ucc, uccLen, &localStatus); + // TODO: Currency pattern data loading is duplicated in number_formatimpl.cpp if(U_SUCCESS(localStatus) && uccLen > 0) { char cc[4]={0}; u_UCharsToChars(ucc, cc, uccLen); diff --git a/deps/icu-small/source/i18n/decfmtst.cpp b/deps/icu-small/source/i18n/decfmtst.cpp deleted file mode 100644 index 5943affad4eb06..00000000000000 --- a/deps/icu-small/source/i18n/decfmtst.cpp +++ /dev/null @@ -1,251 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2009-2016, International Business Machines Corporation and -* others. All Rights Reserved. -******************************************************************************* -* -* This file contains the class DecimalFormatStaticSets -* -* DecimalFormatStaticSets holds the UnicodeSets that are needed for lenient -* parsing of decimal and group separators. -******************************************************************************** -*/ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unistr.h" -#include "unicode/uniset.h" -#include "unicode/uchar.h" -#include "cmemory.h" -#include "cstring.h" -#include "uassert.h" -#include "ucln_in.h" -#include "umutex.h" - -#include "decfmtst.h" - -U_NAMESPACE_BEGIN - - -//------------------------------------------------------------------------------ -// -// Unicode Set pattern strings for all of the required constant sets. -// Initialized with hex values for portability to EBCDIC based machines. -// Really ugly, but there's no good way to avoid it. -// -//------------------------------------------------------------------------------ - -static const UChar gDotEquivalentsPattern[] = { - // [ . \u2024 \u3002 \uFE12 \uFE52 \uFF0E \uFF61 ] - 0x005B, 0x002E, 0x2024, 0x3002, 0xFE12, 0xFE52, 0xFF0E, 0xFF61, 0x005D, 0x0000}; - -static const UChar gCommaEquivalentsPattern[] = { - // [ , \u060C \u066B \u3001 \uFE10 \uFE11 \uFE50 \uFE51 \uFF0C \uFF64 ] - 0x005B, 0x002C, 0x060C, 0x066B, 0x3001, 0xFE10, 0xFE11, 0xFE50, 0xFE51, 0xFF0C, 0xFF64, 0x005D, 0x0000}; - -static const UChar gOtherGroupingSeparatorsPattern[] = { - // [ \ SPACE ' NBSP \u066C \u2000 - \u200A \u2018 \u2019 \u202F \u205F \u3000 \uFF07 ] - 0x005B, 0x005C, 0x0020, 0x0027, 0x00A0, 0x066C, 0x2000, 0x002D, 0x200A, 0x2018, 0x2019, 0x202F, 0x205F, 0x3000, 0xFF07, 0x005D, 0x0000}; - -static const UChar gDashEquivalentsPattern[] = { - // [ \ - HYPHEN F_DASH N_DASH MINUS ] - 0x005B, 0x005C, 0x002D, 0x2010, 0x2012, 0x2013, 0x2212, 0x005D, 0x0000}; - -static const UChar gStrictDotEquivalentsPattern[] = { - // [ . \u2024 \uFE52 \uFF0E \uFF61 ] - 0x005B, 0x002E, 0x2024, 0xFE52, 0xFF0E, 0xFF61, 0x005D, 0x0000}; - -static const UChar gStrictCommaEquivalentsPattern[] = { - // [ , \u066B \uFE10 \uFE50 \uFF0C ] - 0x005B, 0x002C, 0x066B, 0xFE10, 0xFE50, 0xFF0C, 0x005D, 0x0000}; - -static const UChar gStrictOtherGroupingSeparatorsPattern[] = { - // [ \ SPACE ' NBSP \u066C \u2000 - \u200A \u2018 \u2019 \u202F \u205F \u3000 \uFF07 ] - 0x005B, 0x005C, 0x0020, 0x0027, 0x00A0, 0x066C, 0x2000, 0x002D, 0x200A, 0x2018, 0x2019, 0x202F, 0x205F, 0x3000, 0xFF07, 0x005D, 0x0000}; - -static const UChar gStrictDashEquivalentsPattern[] = { - // [ \ - MINUS ] - 0x005B, 0x005C, 0x002D, 0x2212, 0x005D, 0x0000}; - -static const UChar32 gMinusSigns[] = { - 0x002D, - 0x207B, - 0x208B, - 0x2212, - 0x2796, - 0xFE63, - 0xFF0D}; - -static const UChar32 gPlusSigns[] = { - 0x002B, - 0x207A, - 0x208A, - 0x2795, - 0xfB29, - 0xFE62, - 0xFF0B}; - -static void initUnicodeSet(const UChar32 *raw, int32_t len, UnicodeSet *s) { - for (int32_t i = 0; i < len; ++i) { - s->add(raw[i]); - } -} - -DecimalFormatStaticSets::DecimalFormatStaticSets(UErrorCode &status) -: fDotEquivalents(NULL), - fCommaEquivalents(NULL), - fOtherGroupingSeparators(NULL), - fDashEquivalents(NULL), - fStrictDotEquivalents(NULL), - fStrictCommaEquivalents(NULL), - fStrictOtherGroupingSeparators(NULL), - fStrictDashEquivalents(NULL), - fDefaultGroupingSeparators(NULL), - fStrictDefaultGroupingSeparators(NULL), - fMinusSigns(NULL), - fPlusSigns(NULL) -{ - fDotEquivalents = new UnicodeSet(UnicodeString(TRUE, gDotEquivalentsPattern, -1), status); - fCommaEquivalents = new UnicodeSet(UnicodeString(TRUE, gCommaEquivalentsPattern, -1), status); - fOtherGroupingSeparators = new UnicodeSet(UnicodeString(TRUE, gOtherGroupingSeparatorsPattern, -1), status); - fDashEquivalents = new UnicodeSet(UnicodeString(TRUE, gDashEquivalentsPattern, -1), status); - - fStrictDotEquivalents = new UnicodeSet(UnicodeString(TRUE, gStrictDotEquivalentsPattern, -1), status); - fStrictCommaEquivalents = new UnicodeSet(UnicodeString(TRUE, gStrictCommaEquivalentsPattern, -1), status); - fStrictOtherGroupingSeparators = new UnicodeSet(UnicodeString(TRUE, gStrictOtherGroupingSeparatorsPattern, -1), status); - fStrictDashEquivalents = new UnicodeSet(UnicodeString(TRUE, gStrictDashEquivalentsPattern, -1), status); - - - fDefaultGroupingSeparators = new UnicodeSet(*fDotEquivalents); - fDefaultGroupingSeparators->addAll(*fCommaEquivalents); - fDefaultGroupingSeparators->addAll(*fOtherGroupingSeparators); - - fStrictDefaultGroupingSeparators = new UnicodeSet(*fStrictDotEquivalents); - fStrictDefaultGroupingSeparators->addAll(*fStrictCommaEquivalents); - fStrictDefaultGroupingSeparators->addAll(*fStrictOtherGroupingSeparators); - - fMinusSigns = new UnicodeSet(); - fPlusSigns = new UnicodeSet(); - - // Check for null pointers - if (fDotEquivalents == NULL || fCommaEquivalents == NULL || fOtherGroupingSeparators == NULL || fDashEquivalents == NULL || - fStrictDotEquivalents == NULL || fStrictCommaEquivalents == NULL || fStrictOtherGroupingSeparators == NULL || fStrictDashEquivalents == NULL || - fDefaultGroupingSeparators == NULL || fStrictOtherGroupingSeparators == NULL || - fMinusSigns == NULL || fPlusSigns == NULL) { - cleanup(); - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - - initUnicodeSet( - gMinusSigns, - UPRV_LENGTHOF(gMinusSigns), - fMinusSigns); - initUnicodeSet( - gPlusSigns, - UPRV_LENGTHOF(gPlusSigns), - fPlusSigns); - - // Freeze all the sets - fDotEquivalents->freeze(); - fCommaEquivalents->freeze(); - fOtherGroupingSeparators->freeze(); - fDashEquivalents->freeze(); - fStrictDotEquivalents->freeze(); - fStrictCommaEquivalents->freeze(); - fStrictOtherGroupingSeparators->freeze(); - fStrictDashEquivalents->freeze(); - fDefaultGroupingSeparators->freeze(); - fStrictDefaultGroupingSeparators->freeze(); - fMinusSigns->freeze(); - fPlusSigns->freeze(); -} - -DecimalFormatStaticSets::~DecimalFormatStaticSets() { - cleanup(); -} - -void DecimalFormatStaticSets::cleanup() { // Be sure to clean up newly added fields! - delete fDotEquivalents; fDotEquivalents = NULL; - delete fCommaEquivalents; fCommaEquivalents = NULL; - delete fOtherGroupingSeparators; fOtherGroupingSeparators = NULL; - delete fDashEquivalents; fDashEquivalents = NULL; - delete fStrictDotEquivalents; fStrictDotEquivalents = NULL; - delete fStrictCommaEquivalents; fStrictCommaEquivalents = NULL; - delete fStrictOtherGroupingSeparators; fStrictOtherGroupingSeparators = NULL; - delete fStrictDashEquivalents; fStrictDashEquivalents = NULL; - delete fDefaultGroupingSeparators; fDefaultGroupingSeparators = NULL; - delete fStrictDefaultGroupingSeparators; fStrictDefaultGroupingSeparators = NULL; - delete fStrictOtherGroupingSeparators; fStrictOtherGroupingSeparators = NULL; - delete fMinusSigns; fMinusSigns = NULL; - delete fPlusSigns; fPlusSigns = NULL; -} - -static DecimalFormatStaticSets *gStaticSets; -static icu::UInitOnce gStaticSetsInitOnce = U_INITONCE_INITIALIZER; - - -//------------------------------------------------------------------------------ -// -// decfmt_cleanup Memory cleanup function, free/delete all -// cached memory. Called by ICU's u_cleanup() function. -// -//------------------------------------------------------------------------------ -U_CDECL_BEGIN -static UBool U_CALLCONV -decimfmt_cleanup(void) -{ - delete gStaticSets; - gStaticSets = NULL; - gStaticSetsInitOnce.reset(); - return TRUE; -} - -static void U_CALLCONV initSets(UErrorCode &status) { - U_ASSERT(gStaticSets == NULL); - ucln_i18n_registerCleanup(UCLN_I18N_DECFMT, decimfmt_cleanup); - gStaticSets = new DecimalFormatStaticSets(status); - if (U_FAILURE(status)) { - delete gStaticSets; - gStaticSets = NULL; - return; - } - if (gStaticSets == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - } -} -U_CDECL_END - -const DecimalFormatStaticSets *DecimalFormatStaticSets::getStaticSets(UErrorCode &status) { - umtx_initOnce(gStaticSetsInitOnce, initSets, status); - return gStaticSets; -} - - -const UnicodeSet *DecimalFormatStaticSets::getSimilarDecimals(UChar32 decimal, UBool strictParse) -{ - UErrorCode status = U_ZERO_ERROR; - umtx_initOnce(gStaticSetsInitOnce, initSets, status); - if (U_FAILURE(status)) { - return NULL; - } - - if (gStaticSets->fDotEquivalents->contains(decimal)) { - return strictParse ? gStaticSets->fStrictDotEquivalents : gStaticSets->fDotEquivalents; - } - - if (gStaticSets->fCommaEquivalents->contains(decimal)) { - return strictParse ? gStaticSets->fStrictCommaEquivalents : gStaticSets->fCommaEquivalents; - } - - // if there is no match, return NULL - return NULL; -} - - -U_NAMESPACE_END -#endif // !UCONFIG_NO_FORMATTING diff --git a/deps/icu-small/source/i18n/decfmtst.h b/deps/icu-small/source/i18n/decfmtst.h deleted file mode 100644 index 63ae50c6df904a..00000000000000 --- a/deps/icu-small/source/i18n/decfmtst.h +++ /dev/null @@ -1,69 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2009-2016, International Business Machines Corporation and -* others. All Rights Reserved. -******************************************************************************* -* -* This file contains declarations for the class DecimalFormatStaticSets -* -* DecimalFormatStaticSets holds the UnicodeSets that are needed for lenient -* parsing of decimal and group separators. -******************************************************************************** -*/ - -#ifndef DECFMTST_H -#define DECFMTST_H - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" - -U_NAMESPACE_BEGIN - -class UnicodeSet; - - -class DecimalFormatStaticSets : public UMemory -{ -public: - // Constructor and Destructor not for general use. - // Public to permit access from plain C implementation functions. - DecimalFormatStaticSets(UErrorCode &status); - ~DecimalFormatStaticSets(); - - /** - * Return a pointer to a lazy-initialized singleton instance of this class. - */ - static const DecimalFormatStaticSets *getStaticSets(UErrorCode &status); - - static const UnicodeSet *getSimilarDecimals(UChar32 decimal, UBool strictParse); - - UnicodeSet *fDotEquivalents; - UnicodeSet *fCommaEquivalents; - UnicodeSet *fOtherGroupingSeparators; - UnicodeSet *fDashEquivalents; - - UnicodeSet *fStrictDotEquivalents; - UnicodeSet *fStrictCommaEquivalents; - UnicodeSet *fStrictOtherGroupingSeparators; - UnicodeSet *fStrictDashEquivalents; - - UnicodeSet *fDefaultGroupingSeparators; - UnicodeSet *fStrictDefaultGroupingSeparators; - - UnicodeSet *fMinusSigns; - UnicodeSet *fPlusSigns; -private: - void cleanup(); - -}; - - -U_NAMESPACE_END - -#endif // !UCONFIG_NO_FORMATTING -#endif // DECFMTST_H diff --git a/deps/icu-small/source/i18n/decimalformatpattern.cpp b/deps/icu-small/source/i18n/decimalformatpattern.cpp deleted file mode 100644 index 80a1870f33ef8d..00000000000000 --- a/deps/icu-small/source/i18n/decimalformatpattern.cpp +++ /dev/null @@ -1,656 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -*/ - -#include "uassert.h" -#include "decimalformatpattern.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/dcfmtsym.h" -#include "unicode/format.h" -#include "unicode/utf16.h" -#include "decimalformatpatternimpl.h" - - -#ifdef FMT_DEBUG -#define debug(x) printf("%s:%d: %s\n", __FILE__,__LINE__, x); -#else -#define debug(x) -#endif - -U_NAMESPACE_BEGIN - -// TODO: Travis Keep: Copied from numfmt.cpp -static int32_t kDoubleIntegerDigits = 309; -static int32_t kDoubleFractionDigits = 340; - - -// TODO: Travis Keep: Copied from numfmt.cpp -static int32_t gDefaultMaxIntegerDigits = 2000000000; - -// TODO: Travis Keep: This function was copied from format.cpp -static void syntaxError(const UnicodeString& pattern, - int32_t pos, - UParseError& parseError) { - parseError.offset = pos; - parseError.line=0; // we are not using line number - - // for pre-context - int32_t start = (pos < U_PARSE_CONTEXT_LEN)? 0 : (pos - (U_PARSE_CONTEXT_LEN-1 - /* subtract 1 so that we have room for null*/)); - int32_t stop = pos; - pattern.extract(start,stop-start,parseError.preContext,0); - //null terminate the buffer - parseError.preContext[stop-start] = 0; - - //for post-context - start = pattern.moveIndex32(pos, 1); - stop = pos + U_PARSE_CONTEXT_LEN - 1; - if (stop > pattern.length()) { - stop = pattern.length(); - } - pattern.extract(start, stop - start, parseError.postContext, 0); - //null terminate the buffer - parseError.postContext[stop-start]= 0; -} - -DecimalFormatPattern::DecimalFormatPattern() - : fMinimumIntegerDigits(1), - fMaximumIntegerDigits(gDefaultMaxIntegerDigits), - fMinimumFractionDigits(0), - fMaximumFractionDigits(3), - fUseSignificantDigits(FALSE), - fMinimumSignificantDigits(1), - fMaximumSignificantDigits(6), - fUseExponentialNotation(FALSE), - fMinExponentDigits(0), - fExponentSignAlwaysShown(FALSE), - fCurrencySignCount(fgCurrencySignCountZero), - fGroupingUsed(TRUE), - fGroupingSize(0), - fGroupingSize2(0), - fMultiplier(1), - fDecimalSeparatorAlwaysShown(FALSE), - fFormatWidth(0), - fRoundingIncrementUsed(FALSE), - fRoundingIncrement(), - fPad(kDefaultPad), - fNegPatternsBogus(TRUE), - fPosPatternsBogus(TRUE), - fNegPrefixPattern(), - fNegSuffixPattern(), - fPosPrefixPattern(), - fPosSuffixPattern(), - fPadPosition(DecimalFormatPattern::kPadBeforePrefix) { -} - - -DecimalFormatPatternParser::DecimalFormatPatternParser() : - fZeroDigit(kPatternZeroDigit), - fSigDigit(kPatternSignificantDigit), - fGroupingSeparator((UChar)kPatternGroupingSeparator), - fDecimalSeparator((UChar)kPatternDecimalSeparator), - fPercent((UChar)kPatternPercent), - fPerMill((UChar)kPatternPerMill), - fDigit((UChar)kPatternDigit), - fSeparator((UChar)kPatternSeparator), - fExponent((UChar)kPatternExponent), - fPlus((UChar)kPatternPlus), - fMinus((UChar)kPatternMinus), - fPadEscape((UChar)kPatternPadEscape) { -} - -void DecimalFormatPatternParser::useSymbols( - const DecimalFormatSymbols& symbols) { - fZeroDigit = symbols.getConstSymbol( - DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - fSigDigit = symbols.getConstSymbol( - DecimalFormatSymbols::kSignificantDigitSymbol).char32At(0); - fGroupingSeparator = symbols.getConstSymbol( - DecimalFormatSymbols::kGroupingSeparatorSymbol); - fDecimalSeparator = symbols.getConstSymbol( - DecimalFormatSymbols::kDecimalSeparatorSymbol); - fPercent = symbols.getConstSymbol( - DecimalFormatSymbols::kPercentSymbol); - fPerMill = symbols.getConstSymbol( - DecimalFormatSymbols::kPerMillSymbol); - fDigit = symbols.getConstSymbol( - DecimalFormatSymbols::kDigitSymbol); - fSeparator = symbols.getConstSymbol( - DecimalFormatSymbols::kPatternSeparatorSymbol); - fExponent = symbols.getConstSymbol( - DecimalFormatSymbols::kExponentialSymbol); - fPlus = symbols.getConstSymbol( - DecimalFormatSymbols::kPlusSignSymbol); - fMinus = symbols.getConstSymbol( - DecimalFormatSymbols::kMinusSignSymbol); - fPadEscape = symbols.getConstSymbol( - DecimalFormatSymbols::kPadEscapeSymbol); -} - -void -DecimalFormatPatternParser::applyPatternWithoutExpandAffix( - const UnicodeString& pattern, - DecimalFormatPattern& out, - UParseError& parseError, - UErrorCode& status) { - if (U_FAILURE(status)) - { - return; - } - out = DecimalFormatPattern(); - - // Clear error struct - parseError.offset = -1; - parseError.preContext[0] = parseError.postContext[0] = (UChar)0; - - // TODO: Travis Keep: This won't always work. - UChar nineDigit = (UChar)(fZeroDigit + 9); - int32_t digitLen = fDigit.length(); - int32_t groupSepLen = fGroupingSeparator.length(); - int32_t decimalSepLen = fDecimalSeparator.length(); - - int32_t pos = 0; - int32_t patLen = pattern.length(); - // Part 0 is the positive pattern. Part 1, if present, is the negative - // pattern. - for (int32_t part=0; part<2 && pos 0 || sigDigitCount > 0) { - ++digitRightCount; - } else { - ++digitLeftCount; - } - if (groupingCount >= 0 && decimalPos < 0) { - ++groupingCount; - } - pos += digitLen; - } else if ((ch >= fZeroDigit && ch <= nineDigit) || - ch == fSigDigit) { - if (digitRightCount > 0) { - // Unexpected '0' - debug("Unexpected '0'") - status = U_UNEXPECTED_TOKEN; - syntaxError(pattern,pos,parseError); - return; - } - if (ch == fSigDigit) { - ++sigDigitCount; - } else { - if (ch != fZeroDigit && roundingPos < 0) { - roundingPos = digitLeftCount + zeroDigitCount; - } - if (roundingPos >= 0) { - roundingInc.append((char)(ch - fZeroDigit + '0')); - } - ++zeroDigitCount; - } - if (groupingCount >= 0 && decimalPos < 0) { - ++groupingCount; - } - pos += U16_LENGTH(ch); - } else if (pattern.compare(pos, groupSepLen, fGroupingSeparator) == 0) { - if (decimalPos >= 0) { - // Grouping separator after decimal - debug("Grouping separator after decimal") - status = U_UNEXPECTED_TOKEN; - syntaxError(pattern,pos,parseError); - return; - } - groupingCount2 = groupingCount; - groupingCount = 0; - pos += groupSepLen; - } else if (pattern.compare(pos, decimalSepLen, fDecimalSeparator) == 0) { - if (decimalPos >= 0) { - // Multiple decimal separators - debug("Multiple decimal separators") - status = U_MULTIPLE_DECIMAL_SEPARATORS; - syntaxError(pattern,pos,parseError); - return; - } - // Intentionally incorporate the digitRightCount, - // even though it is illegal for this to be > 0 - // at this point. We check pattern syntax below. - decimalPos = digitLeftCount + zeroDigitCount + digitRightCount; - pos += decimalSepLen; - } else { - if (pattern.compare(pos, fExponent.length(), fExponent) == 0) { - if (expDigits >= 0) { - // Multiple exponential symbols - debug("Multiple exponential symbols") - status = U_MULTIPLE_EXPONENTIAL_SYMBOLS; - syntaxError(pattern,pos,parseError); - return; - } - if (groupingCount >= 0) { - // Grouping separator in exponential pattern - debug("Grouping separator in exponential pattern") - status = U_MALFORMED_EXPONENTIAL_PATTERN; - syntaxError(pattern,pos,parseError); - return; - } - pos += fExponent.length(); - // Check for positive prefix - if (pos < patLen - && pattern.compare(pos, fPlus.length(), fPlus) == 0) { - expSignAlways = TRUE; - pos += fPlus.length(); - } - // Use lookahead to parse out the exponential part of the - // pattern, then jump into suffix subpart. - expDigits = 0; - while (pos < patLen && - pattern.char32At(pos) == fZeroDigit) { - ++expDigits; - pos += U16_LENGTH(fZeroDigit); - } - - // 1. Require at least one mantissa pattern digit - // 2. Disallow "#+ @" in mantissa - // 3. Require at least one exponent pattern digit - if (((digitLeftCount + zeroDigitCount) < 1 && - (sigDigitCount + digitRightCount) < 1) || - (sigDigitCount > 0 && digitLeftCount > 0) || - expDigits < 1) { - // Malformed exponential pattern - debug("Malformed exponential pattern") - status = U_MALFORMED_EXPONENTIAL_PATTERN; - syntaxError(pattern,pos,parseError); - return; - } - } - // Transition to suffix subpart - subpart = 2; // suffix subpart - affix = &suffix; - sub0Limit = pos; - continue; - } - break; - case 1: // Prefix subpart - case 2: // Suffix subpart - // Process the prefix / suffix characters - // Process unquoted characters seen in prefix or suffix - // subpart. - - // Several syntax characters implicitly begins the - // next subpart if we are in the prefix; otherwise - // they are illegal if unquoted. - if (!pattern.compare(pos, digitLen, fDigit) || - !pattern.compare(pos, groupSepLen, fGroupingSeparator) || - !pattern.compare(pos, decimalSepLen, fDecimalSeparator) || - (ch >= fZeroDigit && ch <= nineDigit) || - ch == fSigDigit) { - if (subpart == 1) { // prefix subpart - subpart = 0; // pattern proper subpart - sub0Start = pos; // Reprocess this character - continue; - } else { - status = U_UNQUOTED_SPECIAL; - syntaxError(pattern,pos,parseError); - return; - } - } else if (ch == kCurrencySign) { - affix->append(kQuote); // Encode currency - // Use lookahead to determine if the currency sign is - // doubled or not. - U_ASSERT(U16_LENGTH(kCurrencySign) == 1); - if ((pos+1) < pattern.length() && pattern[pos+1] == kCurrencySign) { - affix->append(kCurrencySign); - ++pos; // Skip over the doubled character - if ((pos+1) < pattern.length() && - pattern[pos+1] == kCurrencySign) { - affix->append(kCurrencySign); - ++pos; // Skip over the doubled character - out.fCurrencySignCount = fgCurrencySignCountInPluralFormat; - } else { - out.fCurrencySignCount = fgCurrencySignCountInISOFormat; - } - } else { - out.fCurrencySignCount = fgCurrencySignCountInSymbolFormat; - } - // Fall through to append(ch) - } else if (ch == kQuote) { - // A quote outside quotes indicates either the opening - // quote or two quotes, which is a quote literal. That is, - // we have the first quote in 'do' or o''clock. - U_ASSERT(U16_LENGTH(kQuote) == 1); - ++pos; - if (pos < pattern.length() && pattern[pos] == kQuote) { - affix->append(kQuote); // Encode quote - // Fall through to append(ch) - } else { - subpart += 2; // open quote - continue; - } - } else if (pattern.compare(pos, fSeparator.length(), fSeparator) == 0) { - // Don't allow separators in the prefix, and don't allow - // separators in the second pattern (part == 1). - if (subpart == 1 || part == 1) { - // Unexpected separator - debug("Unexpected separator") - status = U_UNEXPECTED_TOKEN; - syntaxError(pattern,pos,parseError); - return; - } - sub2Limit = pos; - isPartDone = TRUE; // Go to next part - pos += fSeparator.length(); - break; - } else if (pattern.compare(pos, fPercent.length(), fPercent) == 0) { - // Next handle characters which are appended directly. - if (multiplier != 1) { - // Too many percent/perMill characters - debug("Too many percent characters") - status = U_MULTIPLE_PERCENT_SYMBOLS; - syntaxError(pattern,pos,parseError); - return; - } - affix->append(kQuote); // Encode percent/perMill - affix->append(kPatternPercent); // Use unlocalized pattern char - multiplier = 100; - pos += fPercent.length(); - break; - } else if (pattern.compare(pos, fPerMill.length(), fPerMill) == 0) { - // Next handle characters which are appended directly. - if (multiplier != 1) { - // Too many percent/perMill characters - debug("Too many perMill characters") - status = U_MULTIPLE_PERMILL_SYMBOLS; - syntaxError(pattern,pos,parseError); - return; - } - affix->append(kQuote); // Encode percent/perMill - affix->append(kPatternPerMill); // Use unlocalized pattern char - multiplier = 1000; - pos += fPerMill.length(); - break; - } else if (pattern.compare(pos, fPadEscape.length(), fPadEscape) == 0) { - if (padPos >= 0 || // Multiple pad specifiers - (pos+1) == pattern.length()) { // Nothing after padEscape - debug("Multiple pad specifiers") - status = U_MULTIPLE_PAD_SPECIFIERS; - syntaxError(pattern,pos,parseError); - return; - } - padPos = pos; - pos += fPadEscape.length(); - padChar = pattern.char32At(pos); - pos += U16_LENGTH(padChar); - break; - } else if (pattern.compare(pos, fMinus.length(), fMinus) == 0) { - affix->append(kQuote); // Encode minus - affix->append(kPatternMinus); - pos += fMinus.length(); - break; - } else if (pattern.compare(pos, fPlus.length(), fPlus) == 0) { - affix->append(kQuote); // Encode plus - affix->append(kPatternPlus); - pos += fPlus.length(); - break; - } - // Unquoted, non-special characters fall through to here, as - // well as other code which needs to append something to the - // affix. - affix->append(ch); - pos += U16_LENGTH(ch); - break; - case 3: // Prefix subpart, in quote - case 4: // Suffix subpart, in quote - // A quote within quotes indicates either the closing - // quote or two quotes, which is a quote literal. That is, - // we have the second quote in 'do' or 'don''t'. - if (ch == kQuote) { - ++pos; - if (pos < pattern.length() && pattern[pos] == kQuote) { - affix->append(kQuote); // Encode quote - // Fall through to append(ch) - } else { - subpart -= 2; // close quote - continue; - } - } - affix->append(ch); - pos += U16_LENGTH(ch); - break; - } - } - - if (sub0Limit == 0) { - sub0Limit = pattern.length(); - } - - if (sub2Limit == 0) { - sub2Limit = pattern.length(); - } - - /* Handle patterns with no '0' pattern character. These patterns - * are legal, but must be recodified to make sense. "##.###" -> - * "#0.###". ".###" -> ".0##". - * - * We allow patterns of the form "####" to produce a zeroDigitCount - * of zero (got that?); although this seems like it might make it - * possible for format() to produce empty strings, format() checks - * for this condition and outputs a zero digit in this situation. - * Having a zeroDigitCount of zero yields a minimum integer digits - * of zero, which allows proper round-trip patterns. We don't want - * "#" to become "#0" when toPattern() is called (even though that's - * what it really is, semantically). - */ - if (zeroDigitCount == 0 && sigDigitCount == 0 && - digitLeftCount > 0 && decimalPos >= 0) { - // Handle "###.###" and "###." and ".###" - int n = decimalPos; - if (n == 0) - ++n; // Handle ".###" - digitRightCount = digitLeftCount - n; - digitLeftCount = n - 1; - zeroDigitCount = 1; - } - - // Do syntax checking on the digits, decimal points, and quotes. - if ((decimalPos < 0 && digitRightCount > 0 && sigDigitCount == 0) || - (decimalPos >= 0 && - (sigDigitCount > 0 || - decimalPos < digitLeftCount || - decimalPos > (digitLeftCount + zeroDigitCount))) || - groupingCount == 0 || groupingCount2 == 0 || - (sigDigitCount > 0 && zeroDigitCount > 0) || - subpart > 2) - { // subpart > 2 == unmatched quote - debug("Syntax error") - status = U_PATTERN_SYNTAX_ERROR; - syntaxError(pattern,pos,parseError); - return; - } - - // Make sure pad is at legal position before or after affix. - if (padPos >= 0) { - if (padPos == start) { - padPos = DecimalFormatPattern::kPadBeforePrefix; - } else if (padPos+2 == sub0Start) { - padPos = DecimalFormatPattern::kPadAfterPrefix; - } else if (padPos == sub0Limit) { - padPos = DecimalFormatPattern::kPadBeforeSuffix; - } else if (padPos+2 == sub2Limit) { - padPos = DecimalFormatPattern::kPadAfterSuffix; - } else { - // Illegal pad position - debug("Illegal pad position") - status = U_ILLEGAL_PAD_POSITION; - syntaxError(pattern,pos,parseError); - return; - } - } - - if (part == 0) { - out.fPosPatternsBogus = FALSE; - out.fPosPrefixPattern = prefix; - out.fPosSuffixPattern = suffix; - out.fNegPatternsBogus = TRUE; - out.fNegPrefixPattern.remove(); - out.fNegSuffixPattern.remove(); - - out.fUseExponentialNotation = (expDigits >= 0); - if (out.fUseExponentialNotation) { - out.fMinExponentDigits = expDigits; - } - out.fExponentSignAlwaysShown = expSignAlways; - int32_t digitTotalCount = digitLeftCount + zeroDigitCount + digitRightCount; - // The effectiveDecimalPos is the position the decimal is at or - // would be at if there is no decimal. Note that if - // decimalPos<0, then digitTotalCount == digitLeftCount + - // zeroDigitCount. - int32_t effectiveDecimalPos = decimalPos >= 0 ? decimalPos : digitTotalCount; - UBool isSigDig = (sigDigitCount > 0); - out.fUseSignificantDigits = isSigDig; - if (isSigDig) { - out.fMinimumSignificantDigits = sigDigitCount; - out.fMaximumSignificantDigits = sigDigitCount + digitRightCount; - } else { - int32_t minInt = effectiveDecimalPos - digitLeftCount; - out.fMinimumIntegerDigits = minInt; - out.fMaximumIntegerDigits = out.fUseExponentialNotation - ? digitLeftCount + out.fMinimumIntegerDigits - : gDefaultMaxIntegerDigits; - out.fMaximumFractionDigits = decimalPos >= 0 - ? (digitTotalCount - decimalPos) : 0; - out.fMinimumFractionDigits = decimalPos >= 0 - ? (digitLeftCount + zeroDigitCount - decimalPos) : 0; - } - out.fGroupingUsed = groupingCount > 0; - out.fGroupingSize = (groupingCount > 0) ? groupingCount : 0; - out.fGroupingSize2 = (groupingCount2 > 0 && groupingCount2 != groupingCount) - ? groupingCount2 : 0; - out.fMultiplier = multiplier; - out.fDecimalSeparatorAlwaysShown = decimalPos == 0 - || decimalPos == digitTotalCount; - if (padPos >= 0) { - out.fPadPosition = (DecimalFormatPattern::EPadPosition) padPos; - // To compute the format width, first set up sub0Limit - - // sub0Start. Add in prefix/suffix length later. - - // fFormatWidth = prefix.length() + suffix.length() + - // sub0Limit - sub0Start; - out.fFormatWidth = sub0Limit - sub0Start; - out.fPad = padChar; - } else { - out.fFormatWidth = 0; - } - if (roundingPos >= 0) { - out.fRoundingIncrementUsed = TRUE; - roundingInc.setDecimalAt(effectiveDecimalPos - roundingPos); - out.fRoundingIncrement = roundingInc; - } else { - out.fRoundingIncrementUsed = FALSE; - } - } else { - out.fNegPatternsBogus = FALSE; - out.fNegPrefixPattern = prefix; - out.fNegSuffixPattern = suffix; - } - } - - if (pattern.length() == 0) { - out.fNegPatternsBogus = TRUE; - out.fNegPrefixPattern.remove(); - out.fNegSuffixPattern.remove(); - out.fPosPatternsBogus = FALSE; - out.fPosPrefixPattern.remove(); - out.fPosSuffixPattern.remove(); - - out.fMinimumIntegerDigits = 0; - out.fMaximumIntegerDigits = kDoubleIntegerDigits; - out.fMinimumFractionDigits = 0; - out.fMaximumFractionDigits = kDoubleFractionDigits; - - out.fUseExponentialNotation = FALSE; - out.fCurrencySignCount = fgCurrencySignCountZero; - out.fGroupingUsed = FALSE; - out.fGroupingSize = 0; - out.fGroupingSize2 = 0; - out.fMultiplier = 1; - out.fDecimalSeparatorAlwaysShown = FALSE; - out.fFormatWidth = 0; - out.fRoundingIncrementUsed = FALSE; - } - - // If there was no negative pattern, or if the negative pattern is - // identical to the positive pattern, then prepend the minus sign to the - // positive pattern to form the negative pattern. - if (out.fNegPatternsBogus || - (out.fNegPrefixPattern == out.fPosPrefixPattern - && out.fNegSuffixPattern == out.fPosSuffixPattern)) { - out.fNegPatternsBogus = FALSE; - out.fNegSuffixPattern = out.fPosSuffixPattern; - out.fNegPrefixPattern.remove(); - out.fNegPrefixPattern.append(kQuote).append(kPatternMinus) - .append(out.fPosPrefixPattern); - } - // TODO: Deprecate/Remove out.fNegSuffixPattern and 3 other fields. - AffixPattern::parseAffixString( - out.fNegSuffixPattern, out.fNegSuffixAffix, status); - AffixPattern::parseAffixString( - out.fPosSuffixPattern, out.fPosSuffixAffix, status); - AffixPattern::parseAffixString( - out.fNegPrefixPattern, out.fNegPrefixAffix, status); - AffixPattern::parseAffixString( - out.fPosPrefixPattern, out.fPosPrefixAffix, status); -} - -U_NAMESPACE_END - -#endif /* !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/decimalformatpattern.h b/deps/icu-small/source/i18n/decimalformatpattern.h deleted file mode 100644 index 1c297575ead1e7..00000000000000 --- a/deps/icu-small/source/i18n/decimalformatpattern.h +++ /dev/null @@ -1,106 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -*/ -#ifndef _DECIMAL_FORMAT_PATTERN -#define _DECIMAL_FORMAT_PATTERN - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" -#include "unicode/unistr.h" -#include "digitlst.h" -#include "affixpatternparser.h" - -U_NAMESPACE_BEGIN - -// currency sign count -enum CurrencySignCount { - fgCurrencySignCountZero, - fgCurrencySignCountInSymbolFormat, - fgCurrencySignCountInISOFormat, - fgCurrencySignCountInPluralFormat -}; - -class DecimalFormatSymbols; - -struct DecimalFormatPattern : public UMemory { - enum EPadPosition { - kPadBeforePrefix, - kPadAfterPrefix, - kPadBeforeSuffix, - kPadAfterSuffix - }; - - DecimalFormatPattern(); - - int32_t fMinimumIntegerDigits; - int32_t fMaximumIntegerDigits; - int32_t fMinimumFractionDigits; - int32_t fMaximumFractionDigits; - UBool fUseSignificantDigits; - int32_t fMinimumSignificantDigits; - int32_t fMaximumSignificantDigits; - UBool fUseExponentialNotation; - int32_t fMinExponentDigits; - UBool fExponentSignAlwaysShown; - int32_t fCurrencySignCount; - UBool fGroupingUsed; - int32_t fGroupingSize; - int32_t fGroupingSize2; - int32_t fMultiplier; - UBool fDecimalSeparatorAlwaysShown; - int32_t fFormatWidth; - UBool fRoundingIncrementUsed; - DigitList fRoundingIncrement; - UChar32 fPad; - UBool fNegPatternsBogus; - UBool fPosPatternsBogus; - UnicodeString fNegPrefixPattern; - UnicodeString fNegSuffixPattern; - UnicodeString fPosPrefixPattern; - UnicodeString fPosSuffixPattern; - AffixPattern fNegPrefixAffix; - AffixPattern fNegSuffixAffix; - AffixPattern fPosPrefixAffix; - AffixPattern fPosSuffixAffix; - EPadPosition fPadPosition; -}; - -class DecimalFormatPatternParser : public UMemory { - public: - DecimalFormatPatternParser(); - void useSymbols(const DecimalFormatSymbols& symbols); - - void applyPatternWithoutExpandAffix( - const UnicodeString& pattern, - DecimalFormatPattern& out, - UParseError& parseError, - UErrorCode& status); - private: - DecimalFormatPatternParser(const DecimalFormatPatternParser&); - DecimalFormatPatternParser& operator=(DecimalFormatPatternParser& rhs); - UChar32 fZeroDigit; - UChar32 fSigDigit; - UnicodeString fGroupingSeparator; - UnicodeString fDecimalSeparator; - UnicodeString fPercent; - UnicodeString fPerMill; - UnicodeString fDigit; - UnicodeString fSeparator; - UnicodeString fExponent; - UnicodeString fPlus; - UnicodeString fMinus; - UnicodeString fPadEscape; -}; - -U_NAMESPACE_END - -#endif /* !UCONFIG_NO_FORMATTING */ -#endif diff --git a/deps/icu-small/source/i18n/decimalformatpatternimpl.h b/deps/icu-small/source/i18n/decimalformatpatternimpl.h deleted file mode 100644 index 8cecc8cca02c72..00000000000000 --- a/deps/icu-small/source/i18n/decimalformatpatternimpl.h +++ /dev/null @@ -1,35 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************** -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************** -* -* File decimalformatpatternimpl.h -******************************************************************************** -*/ - -#ifndef DECIMALFORMATPATTERNIMPL_H -#define DECIMALFORMATPATTERNIMPL_H - -#include "unicode/utypes.h" - -#define kPatternZeroDigit ((UChar)0x0030) /*'0'*/ -#define kPatternSignificantDigit ((UChar)0x0040) /*'@'*/ -#define kPatternGroupingSeparator ((UChar)0x002C) /*','*/ -#define kPatternDecimalSeparator ((UChar)0x002E) /*'.'*/ -#define kPatternPerMill ((UChar)0x2030) -#define kPatternPercent ((UChar)0x0025) /*'%'*/ -#define kPatternDigit ((UChar)0x0023) /*'#'*/ -#define kPatternSeparator ((UChar)0x003B) /*';'*/ -#define kPatternExponent ((UChar)0x0045) /*'E'*/ -#define kPatternPlus ((UChar)0x002B) /*'+'*/ -#define kPatternMinus ((UChar)0x002D) /*'-'*/ -#define kPatternPadEscape ((UChar)0x002A) /*'*'*/ -#define kQuote ((UChar)0x0027) /*'\''*/ - -#define kCurrencySign ((UChar)0x00A4) -#define kDefaultPad ((UChar)0x0020) /* */ - -#endif diff --git a/deps/icu-small/source/i18n/decimfmt.cpp b/deps/icu-small/source/i18n/decimfmt.cpp index 80a9446e146f6c..a2638bb74298e3 100644 --- a/deps/icu-small/source/i18n/decimfmt.cpp +++ b/deps/icu-small/source/i18n/decimfmt.cpp @@ -1,3295 +1,1383 @@ -// © 2016 and later: Unicode, Inc. and others. +// © 2018 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 1997-2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -* -* File DECIMFMT.CPP -* -* Modification History: -* -* Date Name Description -* 02/19/97 aliu Converted from java. -* 03/20/97 clhuang Implemented with new APIs. -* 03/31/97 aliu Moved isLONG_MIN to DigitList, and fixed it. -* 04/3/97 aliu Rewrote parsing and formatting completely, and -* cleaned up and debugged. Actually works now. -* Implemented NAN and INF handling, for both parsing -* and formatting. Extensive testing & debugging. -* 04/10/97 aliu Modified to compile on AIX. -* 04/16/97 aliu Rewrote to use DigitList, which has been resurrected. -* Changed DigitCount to int per code review. -* 07/09/97 helena Made ParsePosition into a class. -* 08/26/97 aliu Extensive changes to applyPattern; completely -* rewritten from the Java. -* 09/09/97 aliu Ported over support for exponential formats. -* 07/20/98 stephen JDK 1.2 sync up. -* Various instances of '0' replaced with 'NULL' -* Check for grouping size in subFormat() -* Brought subParse() in line with Java 1.2 -* Added method appendAffix() -* 08/24/1998 srl Removed Mutex calls. This is not a thread safe class! -* 02/22/99 stephen Removed character literals for EBCDIC safety -* 06/24/99 helena Integrated Alan's NF enhancements and Java2 bug fixes -* 06/28/99 stephen Fixed bugs in toPattern(). -* 06/29/99 stephen Fixed operator= to copy fFormatWidth, fPad, -* fPadPosition -******************************************************************************** -*/ #include "unicode/utypes.h" #if !UCONFIG_NO_FORMATTING -#include "unicode/uniset.h" -#include "unicode/currpinf.h" -#include "unicode/plurrule.h" -#include "unicode/utf16.h" -#include "unicode/numsys.h" -#include "unicode/localpointer.h" -#include "unicode/ustring.h" -#include "uresimp.h" -#include "ucurrimp.h" -#include "charstr.h" -#include "patternprops.h" -#include "cstring.h" -#include "uassert.h" -#include "hash.h" -#include "decfmtst.h" -#include "plurrule_impl.h" -#include "decimalformatpattern.h" -#include "fmtableimp.h" -#include "decimfmtimpl.h" -#include "visibledigits.h" - -/* - * On certain platforms, round is a macro defined in math.h - * This undefine is to avoid conflict between the macro and - * the function defined below. - */ -#ifdef round -#undef round -#endif - - -U_NAMESPACE_BEGIN - -#ifdef FMT_DEBUG -#include -static void _debugout(const char *f, int l, const UnicodeString& s) { - char buf[2000]; - s.extract((int32_t) 0, s.length(), buf, "utf-8"); - printf("%s:%d: %s\n", f,l, buf); -} -#define debugout(x) _debugout(__FILE__,__LINE__,x) -#define debug(x) printf("%s:%d: %s\n", __FILE__,__LINE__, x); -static const UnicodeString dbg_null("",""); -#define DEREFSTR(x) ((x!=NULL)?(*x):(dbg_null)) +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include +#include +#include +#include "unicode/errorcode.h" +#include "unicode/decimfmt.h" +#include "number_decimalquantity.h" +#include "number_types.h" +#include "numparse_impl.h" +#include "number_mapper.h" +#include "number_patternstring.h" +#include "putilimp.h" +#include "number_utils.h" +#include "number_utypes.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; +using namespace icu::numparse; +using namespace icu::numparse::impl; +using ERoundingMode = icu::DecimalFormat::ERoundingMode; +using EPadPosition = icu::DecimalFormat::EPadPosition; + +// MSVC warns C4805 when comparing bool with UBool +// TODO: Move this macro into a better place? +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +#define UBOOL_TO_BOOL(b) static_cast(b) #else -#define debugout(x) -#define debug(x) -#endif - - -/* For currency parsing purose, - * Need to remember all prefix patterns and suffix patterns of - * every currency format pattern, - * including the pattern of default currecny style - * and plural currency style. And the patterns are set through applyPattern. - */ -struct AffixPatternsForCurrency : public UMemory { - // negative prefix pattern - UnicodeString negPrefixPatternForCurrency; - // negative suffix pattern - UnicodeString negSuffixPatternForCurrency; - // positive prefix pattern - UnicodeString posPrefixPatternForCurrency; - // positive suffix pattern - UnicodeString posSuffixPatternForCurrency; - int8_t patternType; - - AffixPatternsForCurrency(const UnicodeString& negPrefix, - const UnicodeString& negSuffix, - const UnicodeString& posPrefix, - const UnicodeString& posSuffix, - int8_t type) { - negPrefixPatternForCurrency = negPrefix; - negSuffixPatternForCurrency = negSuffix; - posPrefixPatternForCurrency = posPrefix; - posSuffixPatternForCurrency = posSuffix; - patternType = type; - } -#ifdef FMT_DEBUG - void dump() const { - debugout( UnicodeString("AffixPatternsForCurrency( -=\"") + - negPrefixPatternForCurrency + (UnicodeString)"\"/\"" + - negSuffixPatternForCurrency + (UnicodeString)"\" +=\"" + - posPrefixPatternForCurrency + (UnicodeString)"\"/\"" + - posSuffixPatternForCurrency + (UnicodeString)"\" )"); - } +#define UBOOL_TO_BOOL(b) b #endif -}; - -/* affix for currency formatting when the currency sign in the pattern - * equals to 3, such as the pattern contains 3 currency sign or - * the formatter style is currency plural format style. - */ -struct AffixesForCurrency : public UMemory { - // negative prefix - UnicodeString negPrefixForCurrency; - // negative suffix - UnicodeString negSuffixForCurrency; - // positive prefix - UnicodeString posPrefixForCurrency; - // positive suffix - UnicodeString posSuffixForCurrency; - - int32_t formatWidth; - - AffixesForCurrency(const UnicodeString& negPrefix, - const UnicodeString& negSuffix, - const UnicodeString& posPrefix, - const UnicodeString& posSuffix) { - negPrefixForCurrency = negPrefix; - negSuffixForCurrency = negSuffix; - posPrefixForCurrency = posPrefix; - posSuffixForCurrency = posSuffix; - } -#ifdef FMT_DEBUG - void dump() const { - debugout( UnicodeString("AffixesForCurrency( -=\"") + - negPrefixForCurrency + (UnicodeString)"\"/\"" + - negSuffixForCurrency + (UnicodeString)"\" +=\"" + - posPrefixForCurrency + (UnicodeString)"\"/\"" + - posSuffixForCurrency + (UnicodeString)"\" )"); - } -#endif -}; - -U_CDECL_BEGIN - -/** - * @internal ICU 4.2 - */ -static UBool U_CALLCONV decimfmtAffixPatternValueComparator(UHashTok val1, UHashTok val2); - - -static UBool -U_CALLCONV decimfmtAffixPatternValueComparator(UHashTok val1, UHashTok val2) { - const AffixPatternsForCurrency* affix_1 = - (AffixPatternsForCurrency*)val1.pointer; - const AffixPatternsForCurrency* affix_2 = - (AffixPatternsForCurrency*)val2.pointer; - return affix_1->negPrefixPatternForCurrency == - affix_2->negPrefixPatternForCurrency && - affix_1->negSuffixPatternForCurrency == - affix_2->negSuffixPatternForCurrency && - affix_1->posPrefixPatternForCurrency == - affix_2->posPrefixPatternForCurrency && - affix_1->posSuffixPatternForCurrency == - affix_2->posSuffixPatternForCurrency && - affix_1->patternType == affix_2->patternType; -} - -U_CDECL_END - - -// ***************************************************************************** -// class DecimalFormat -// ***************************************************************************** - UOBJECT_DEFINE_RTTI_IMPLEMENTATION(DecimalFormat) -// Constants for characters used in programmatic (unlocalized) patterns. -#define kPatternZeroDigit ((UChar)0x0030) /*'0'*/ -#define kPatternSignificantDigit ((UChar)0x0040) /*'@'*/ -#define kPatternGroupingSeparator ((UChar)0x002C) /*','*/ -#define kPatternDecimalSeparator ((UChar)0x002E) /*'.'*/ -#define kPatternPerMill ((UChar)0x2030) -#define kPatternPercent ((UChar)0x0025) /*'%'*/ -#define kPatternDigit ((UChar)0x0023) /*'#'*/ -#define kPatternSeparator ((UChar)0x003B) /*';'*/ -#define kPatternExponent ((UChar)0x0045) /*'E'*/ -#define kPatternPlus ((UChar)0x002B) /*'+'*/ -#define kPatternMinus ((UChar)0x002D) /*'-'*/ -#define kPatternPadEscape ((UChar)0x002A) /*'*'*/ -#define kQuote ((UChar)0x0027) /*'\''*/ -/** - * The CURRENCY_SIGN is the standard Unicode symbol for currency. It - * is used in patterns and substitued with either the currency symbol, - * or if it is doubled, with the international currency symbol. If the - * CURRENCY_SIGN is seen in a pattern, then the decimal separator is - * replaced with the monetary decimal separator. - */ -#define kCurrencySign ((UChar)0x00A4) -#define kDefaultPad ((UChar)0x0020) /* */ - -const int32_t DecimalFormat::kDoubleIntegerDigits = 309; -const int32_t DecimalFormat::kDoubleFractionDigits = 340; - -const int32_t DecimalFormat::kMaxScientificIntegerDigits = 8; - -/** - * These are the tags we expect to see in normal resource bundle files associated - * with a locale. - */ -const char DecimalFormat::fgNumberPatterns[]="NumberPatterns"; // Deprecated - not used -static const char fgNumberElements[]="NumberElements"; -static const char fgLatn[]="latn"; -static const char fgPatterns[]="patterns"; -static const char fgDecimalFormat[]="decimalFormat"; -static const char fgCurrencyFormat[]="currencyFormat"; - -inline int32_t _min(int32_t a, int32_t b) { return (a adoptedSymbols(symbolsToAdopt); - if (U_FAILURE(status)) - return; - - if (adoptedSymbols.isNull()) - { - adoptedSymbols.adoptInstead( - new DecimalFormatSymbols(Locale::getDefault(), status)); - if (adoptedSymbols.isNull() && U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - if (U_FAILURE(status)) { - return; - } - } - fStaticSets = DecimalFormatStaticSets::getStaticSets(status); - if (U_FAILURE(status)) { - return; - } - - UnicodeString str; - // Uses the default locale's number format pattern if there isn't - // one specified. - if (pattern == NULL) - { - UErrorCode nsStatus = U_ZERO_ERROR; - LocalPointer ns( - NumberingSystem::createInstance(nsStatus)); - if (U_FAILURE(nsStatus)) { - status = nsStatus; - return; - } - - int32_t len = 0; - UResourceBundle *top = ures_open(NULL, Locale::getDefault().getName(), &status); - - UResourceBundle *resource = ures_getByKeyWithFallback(top, fgNumberElements, NULL, &status); - resource = ures_getByKeyWithFallback(resource, ns->getName(), resource, &status); - resource = ures_getByKeyWithFallback(resource, fgPatterns, resource, &status); - const UChar *resStr = ures_getStringByKeyWithFallback(resource, fgDecimalFormat, &len, &status); - if ( status == U_MISSING_RESOURCE_ERROR && uprv_strcmp(fgLatn,ns->getName())) { - status = U_ZERO_ERROR; - resource = ures_getByKeyWithFallback(top, fgNumberElements, resource, &status); - resource = ures_getByKeyWithFallback(resource, fgLatn, resource, &status); - resource = ures_getByKeyWithFallback(resource, fgPatterns, resource, &status); - resStr = ures_getStringByKeyWithFallback(resource, fgDecimalFormat, &len, &status); - } - str.setTo(TRUE, resStr, len); - pattern = &str; - ures_close(resource); - ures_close(top); - } - - fImpl = new DecimalFormatImpl(this, *pattern, adoptedSymbols.getAlias(), parseErr, status); - if (fImpl) { - adoptedSymbols.orphan(); - } else if (U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - if (U_FAILURE(status)) { - return; - } - - if (U_FAILURE(status)) - { - return; - } - - const UnicodeString* patternUsed; - UnicodeString currencyPluralPatternForOther; - // apply pattern - if (fStyle == UNUM_CURRENCY_PLURAL) { - fCurrencyPluralInfo = new CurrencyPluralInfo(fImpl->fSymbols->getLocale(), status); - if (U_FAILURE(status)) { - return; - } - - // the pattern used in format is not fixed until formatting, - // in which, the number is known and - // will be used to pick the right pattern based on plural count. - // Here, set the pattern as the pattern of plural count == "other". - // For most locale, the patterns are probably the same for all - // plural count. If not, the right pattern need to be re-applied - // during format. - fCurrencyPluralInfo->getCurrencyPluralPattern(UNICODE_STRING("other", 5), currencyPluralPatternForOther); - // TODO(refactor): Revisit, we are setting the pattern twice. - fImpl->applyPatternFavorCurrencyPrecision( - currencyPluralPatternForOther, status); - patternUsed = ¤cyPluralPatternForOther; +DecimalFormat::DecimalFormat(UErrorCode& status) + : DecimalFormat(nullptr, status) { + // Use the default locale and decimal pattern. + const char* localeName = Locale::getDefault().getName(); + LocalPointer ns(NumberingSystem::createInstance(status)); + UnicodeString patternString = utils::getPatternForStyle( + localeName, + ns->getName(), + CLDR_PATTERN_STYLE_DECIMAL, + status); + setPropertiesFromPattern(patternString, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, UErrorCode& status) + : DecimalFormat(nullptr, status) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} + +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UNumberFormatStyle style, UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + // If choice is a currency type, ignore the rounding information. + if (style == UNumberFormatStyle::UNUM_CURRENCY || style == UNumberFormatStyle::UNUM_CURRENCY_ISO || + style == UNumberFormatStyle::UNUM_CURRENCY_ACCOUNTING || + style == UNumberFormatStyle::UNUM_CASH_CURRENCY || + style == UNumberFormatStyle::UNUM_CURRENCY_STANDARD || + style == UNumberFormatStyle::UNUM_CURRENCY_PLURAL) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_ALWAYS, status); } else { - patternUsed = pattern; + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); } - - if (patternUsed->indexOf(kCurrencySign) != -1) { - // initialize for currency, not only for plural format, - // but also for mix parsing - handleCurrencySignInPattern(status); + // Note: in Java, CurrencyPluralInfo is set in NumberFormat.java, but in C++, it is not set there, + // so we have to set it here. + if (style == UNumberFormatStyle::UNUM_CURRENCY_PLURAL) { + LocalPointer cpi( + new CurrencyPluralInfo(fields->symbols->getLocale(), status), + status); + if (U_FAILURE(status)) { return; } + fields->properties->currencyPluralInfo.fPtr.adoptInstead(cpi.orphan()); } + touch(status); } -void -DecimalFormat::handleCurrencySignInPattern(UErrorCode& status) { - // initialize for currency, not only for plural format, - // but also for mix parsing +DecimalFormat::DecimalFormat(const DecimalFormatSymbols* symbolsToAdopt, UErrorCode& status) { + LocalPointer adoptedSymbols(symbolsToAdopt); + fields = new DecimalFormatFields(); if (U_FAILURE(status)) { return; } - if (fCurrencyPluralInfo == NULL) { - fCurrencyPluralInfo = new CurrencyPluralInfo(fImpl->fSymbols->getLocale(), status); - if (U_FAILURE(status)) { - return; - } - } - // need it for mix parsing - if (fAffixPatternsForCurrency == NULL) { - setupCurrencyAffixPatterns(status); - } -} - -static void -applyPatternWithNoSideEffects( - const UnicodeString& pattern, - UParseError& parseError, - UnicodeString &negPrefix, - UnicodeString &negSuffix, - UnicodeString &posPrefix, - UnicodeString &posSuffix, - UErrorCode& status) { - if (U_FAILURE(status)) - { + if (fields == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; return; } - DecimalFormatPatternParser patternParser; - DecimalFormatPattern out; - patternParser.applyPatternWithoutExpandAffix( - pattern, - out, - parseError, - status); - if (U_FAILURE(status)) { - return; + fields->properties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); + fields->exportedProperties.adoptInsteadAndCheckErrorCode(new DecimalFormatProperties(), status); + if (adoptedSymbols.isNull()) { + fields->symbols.adoptInsteadAndCheckErrorCode(new DecimalFormatSymbols(status), status); + } else { + fields->symbols.adoptInsteadAndCheckErrorCode(adoptedSymbols.orphan(), status); } - negPrefix = out.fNegPrefixPattern; - negSuffix = out.fNegSuffixPattern; - posPrefix = out.fPosPrefixPattern; - posSuffix = out.fPosSuffixPattern; } -void -DecimalFormat::setupCurrencyAffixPatterns(UErrorCode& status) { - if (U_FAILURE(status)) { - return; - } - UParseError parseErr; - fAffixPatternsForCurrency = initHashForAffixPattern(status); - if (U_FAILURE(status)) { - return; - } - - NumberingSystem *ns = NumberingSystem::createInstance(fImpl->fSymbols->getLocale(),status); - if (U_FAILURE(status)) { - return; - } +#if UCONFIG_HAVE_PARSEALLINPUT - // Save the default currency patterns of this locale. - // Here, chose onlyApplyPatternWithoutExpandAffix without - // expanding the affix patterns into affixes. - UnicodeString currencyPattern; - UErrorCode error = U_ZERO_ERROR; - - UResourceBundle *resource = ures_open(NULL, fImpl->fSymbols->getLocale().getName(), &error); - UResourceBundle *numElements = ures_getByKeyWithFallback(resource, fgNumberElements, NULL, &error); - resource = ures_getByKeyWithFallback(numElements, ns->getName(), resource, &error); - resource = ures_getByKeyWithFallback(resource, fgPatterns, resource, &error); - int32_t patLen = 0; - const UChar *patResStr = ures_getStringByKeyWithFallback(resource, fgCurrencyFormat, &patLen, &error); - if ( error == U_MISSING_RESOURCE_ERROR && uprv_strcmp(ns->getName(),fgLatn)) { - error = U_ZERO_ERROR; - resource = ures_getByKeyWithFallback(numElements, fgLatn, resource, &error); - resource = ures_getByKeyWithFallback(resource, fgPatterns, resource, &error); - patResStr = ures_getStringByKeyWithFallback(resource, fgCurrencyFormat, &patLen, &error); - } - ures_close(numElements); - ures_close(resource); - delete ns; - - if (U_SUCCESS(error)) { - UnicodeString negPrefix; - UnicodeString negSuffix; - UnicodeString posPrefix; - UnicodeString posSuffix; - applyPatternWithNoSideEffects(UnicodeString(patResStr, patLen), - parseErr, - negPrefix, negSuffix, posPrefix, posSuffix, status); - AffixPatternsForCurrency* affixPtn = new AffixPatternsForCurrency( - negPrefix, - negSuffix, - posPrefix, - posSuffix, - UCURR_SYMBOL_NAME); - fAffixPatternsForCurrency->put(UNICODE_STRING("default", 7), affixPtn, status); - } - - // save the unique currency plural patterns of this locale. - Hashtable* pluralPtn = fCurrencyPluralInfo->fPluralCountToCurrencyUnitPattern; - const UHashElement* element = NULL; - int32_t pos = UHASH_FIRST; - Hashtable pluralPatternSet; - while ((element = pluralPtn->nextElement(pos)) != NULL) { - const UHashTok valueTok = element->value; - const UnicodeString* value = (UnicodeString*)valueTok.pointer; - const UHashTok keyTok = element->key; - const UnicodeString* key = (UnicodeString*)keyTok.pointer; - if (pluralPatternSet.geti(*value) != 1) { - UnicodeString negPrefix; - UnicodeString negSuffix; - UnicodeString posPrefix; - UnicodeString posSuffix; - pluralPatternSet.puti(*value, 1, status); - applyPatternWithNoSideEffects( - *value, parseErr, - negPrefix, negSuffix, posPrefix, posSuffix, status); - AffixPatternsForCurrency* affixPtn = new AffixPatternsForCurrency( - negPrefix, - negSuffix, - posPrefix, - posSuffix, - UCURR_LONG_NAME); - fAffixPatternsForCurrency->put(*key, affixPtn, status); - } - } +void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) { + if (value == fields->properties->parseAllInput) { return; } + fields->properties->parseAllInput = value; } +#endif -//------------------------------------------------------------------------------ +DecimalFormat& +DecimalFormat::setAttribute(UNumberFormatAttribute attr, int32_t newValue, UErrorCode& status) { + if (U_FAILURE(status)) { return *this; } -DecimalFormat::~DecimalFormat() -{ - deleteHashForAffixPattern(); - delete fCurrencyPluralInfo; - delete fImpl; -} + switch (attr) { + case UNUM_LENIENT_PARSE: + setLenient(newValue != 0); + break; -//------------------------------------------------------------------------------ -// copy constructor + case UNUM_PARSE_INT_ONLY: + setParseIntegerOnly(newValue != 0); + break; -DecimalFormat::DecimalFormat(const DecimalFormat &source) : - NumberFormat(source) { - init(); - *this = source; -} + case UNUM_GROUPING_USED: + setGroupingUsed(newValue != 0); + break; -//------------------------------------------------------------------------------ -// assignment operator + case UNUM_DECIMAL_ALWAYS_SHOWN: + setDecimalSeparatorAlwaysShown(newValue != 0); + break; -template -static void _clone_ptr(T** pdest, const T* source) { - delete *pdest; - if (source == NULL) { - *pdest = NULL; - } else { - *pdest = static_cast(source->clone()); - } -} + case UNUM_MAX_INTEGER_DIGITS: + setMaximumIntegerDigits(newValue); + break; -DecimalFormat& -DecimalFormat::operator=(const DecimalFormat& rhs) -{ - if(this != &rhs) { - UErrorCode status = U_ZERO_ERROR; - NumberFormat::operator=(rhs); - if (fImpl == NULL) { - fImpl = new DecimalFormatImpl(this, *rhs.fImpl, status); - } else { - fImpl->assign(*rhs.fImpl, status); - } - fStaticSets = DecimalFormatStaticSets::getStaticSets(status); - fStyle = rhs.fStyle; - _clone_ptr(&fCurrencyPluralInfo, rhs.fCurrencyPluralInfo); - deleteHashForAffixPattern(); - if (rhs.fAffixPatternsForCurrency) { - UErrorCode status = U_ZERO_ERROR; - fAffixPatternsForCurrency = initHashForAffixPattern(status); - copyHashForAffixPattern(rhs.fAffixPatternsForCurrency, - fAffixPatternsForCurrency, status); - } - } + case UNUM_MIN_INTEGER_DIGITS: + setMinimumIntegerDigits(newValue); + break; - return *this; -} + case UNUM_INTEGER_DIGITS: + setMinimumIntegerDigits(newValue); + setMaximumIntegerDigits(newValue); + break; -//------------------------------------------------------------------------------ + case UNUM_MAX_FRACTION_DIGITS: + setMaximumFractionDigits(newValue); + break; -UBool -DecimalFormat::operator==(const Format& that) const -{ - if (this == &that) - return TRUE; + case UNUM_MIN_FRACTION_DIGITS: + setMinimumFractionDigits(newValue); + break; - // NumberFormat::operator== guarantees this cast is safe - const DecimalFormat* other = (DecimalFormat*)&that; + case UNUM_FRACTION_DIGITS: + setMinimumFractionDigits(newValue); + setMaximumFractionDigits(newValue); + break; - return ( - NumberFormat::operator==(that) && - fBoolFlags.getAll() == other->fBoolFlags.getAll() && - *fImpl == *other->fImpl); + case UNUM_SIGNIFICANT_DIGITS_USED: + setSignificantDigitsUsed(newValue != 0); + break; -} + case UNUM_MAX_SIGNIFICANT_DIGITS: + setMaximumSignificantDigits(newValue); + break; -//------------------------------------------------------------------------------ + case UNUM_MIN_SIGNIFICANT_DIGITS: + setMinimumSignificantDigits(newValue); + break; -Format* -DecimalFormat::clone() const -{ - return new DecimalFormat(*this); -} + case UNUM_MULTIPLIER: + setMultiplier(newValue); + break; + case UNUM_SCALE: + setMultiplierScale(newValue); + break; -FixedDecimal -DecimalFormat::getFixedDecimal(double number, UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - if (U_FAILURE(status)) { - return FixedDecimal(); - } - return FixedDecimal(digits.getMantissa()); -} + case UNUM_GROUPING_SIZE: + setGroupingSize(newValue); + break; -VisibleDigitsWithExponent & -DecimalFormat::initVisibleDigitsWithExponent( - double number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - return fImpl->initVisibleDigitsWithExponent(number, digits, status); -} + case UNUM_ROUNDING_MODE: + setRoundingMode((DecimalFormat::ERoundingMode) newValue); + break; -FixedDecimal -DecimalFormat::getFixedDecimal(const Formattable &number, UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - if (U_FAILURE(status)) { - return FixedDecimal(); - } - return FixedDecimal(digits.getMantissa()); -} + case UNUM_FORMAT_WIDTH: + setFormatWidth(newValue); + break; -VisibleDigitsWithExponent & -DecimalFormat::initVisibleDigitsWithExponent( - const Formattable &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - if (!number.isNumeric()) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return digits; - } + case UNUM_PADDING_POSITION: + /** The position at which padding will take place. */ + setPadPosition((DecimalFormat::EPadPosition) newValue); + break; - DigitList *dl = number.getDigitList(); - if (dl != NULL) { - DigitList dlCopy(*dl); - return fImpl->initVisibleDigitsWithExponent( - dlCopy, digits, status); - } + case UNUM_SECONDARY_GROUPING_SIZE: + setSecondaryGroupingSize(newValue); + break; - Formattable::Type type = number.getType(); - if (type == Formattable::kDouble || type == Formattable::kLong) { - return fImpl->initVisibleDigitsWithExponent( - number.getDouble(status), digits, status); - } - return fImpl->initVisibleDigitsWithExponent( - number.getInt64(), digits, status); -} +#if UCONFIG_HAVE_PARSEALLINPUT + case UNUM_PARSE_ALL_INPUT: + setParseAllInput((UNumberFormatAttributeValue) newValue); + break; +#endif + case UNUM_PARSE_NO_EXPONENT: + setParseNoExponent((UBool) newValue); + break; -// Create a fixed decimal from a DigitList. -// The digit list may be modified. -// Internal function only. -FixedDecimal -DecimalFormat::getFixedDecimal(DigitList &number, UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - if (U_FAILURE(status)) { - return FixedDecimal(); - } - return FixedDecimal(digits.getMantissa()); -} + case UNUM_PARSE_DECIMAL_MARK_REQUIRED: + setDecimalPatternMatchRequired((UBool) newValue); + break; -VisibleDigitsWithExponent & -DecimalFormat::initVisibleDigitsWithExponent( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - return fImpl->initVisibleDigitsWithExponent( - number, digits, status); -} + case UNUM_CURRENCY_USAGE: + setCurrencyUsage((UCurrencyUsage) newValue, &status); + break; + case UNUM_MINIMUM_GROUPING_DIGITS: + setMinimumGroupingDigits(newValue); + break; -//------------------------------------------------------------------------------ + case UNUM_PARSE_CASE_SENSITIVE: + setParseCaseSensitive(static_cast(newValue)); + break; -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - UErrorCode status = U_ZERO_ERROR; - return fImpl->format(number, appendTo, fieldPosition, status); -} + case UNUM_SIGN_ALWAYS_SHOWN: + setSignAlwaysShown(static_cast(newValue)); + break; -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, fieldPosition, status); -} + case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: + setFormatFailIfMoreThanMaxDigits(static_cast(newValue)); + break; -UnicodeString& -DecimalFormat::format(int32_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, posIter, status); + default: + status = U_UNSUPPORTED_ERROR; + break; + } + return *this; } +int32_t DecimalFormat::getAttribute(UNumberFormatAttribute attr, UErrorCode& status) const { + if (U_FAILURE(status)) { return -1; } + switch (attr) { + case UNUM_LENIENT_PARSE: + return isLenient(); -//------------------------------------------------------------------------------ + case UNUM_PARSE_INT_ONLY: + return isParseIntegerOnly(); -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - UErrorCode status = U_ZERO_ERROR; /* ignored */ - return fImpl->format(number, appendTo, fieldPosition, status); -} + case UNUM_GROUPING_USED: + return isGroupingUsed(); -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, fieldPosition, status); -} + case UNUM_DECIMAL_ALWAYS_SHOWN: + return isDecimalSeparatorAlwaysShown(); -UnicodeString& -DecimalFormat::format(int64_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, posIter, status); -} + case UNUM_MAX_INTEGER_DIGITS: + return getMaximumIntegerDigits(); -//------------------------------------------------------------------------------ + case UNUM_MIN_INTEGER_DIGITS: + return getMinimumIntegerDigits(); -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPosition& fieldPosition) const -{ - UErrorCode status = U_ZERO_ERROR; /* ignored */ - return fImpl->format(number, appendTo, fieldPosition, status); -} + case UNUM_INTEGER_DIGITS: + // TBD: what should this return? + return getMinimumIntegerDigits(); -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPosition& fieldPosition, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, fieldPosition, status); -} + case UNUM_MAX_FRACTION_DIGITS: + return getMaximumFractionDigits(); -UnicodeString& -DecimalFormat::format( double number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const -{ - return fImpl->format(number, appendTo, posIter, status); -} + case UNUM_MIN_FRACTION_DIGITS: + return getMinimumFractionDigits(); -//------------------------------------------------------------------------------ + case UNUM_FRACTION_DIGITS: + // TBD: what should this return? + return getMinimumFractionDigits(); + case UNUM_SIGNIFICANT_DIGITS_USED: + return areSignificantDigitsUsed(); -UnicodeString& -DecimalFormat::format(StringPiece number, - UnicodeString &toAppendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const -{ - return fImpl->format(number, toAppendTo, posIter, status); -} + case UNUM_MAX_SIGNIFICANT_DIGITS: + return getMaximumSignificantDigits(); + case UNUM_MIN_SIGNIFICANT_DIGITS: + return getMinimumSignificantDigits(); -UnicodeString& -DecimalFormat::format(const DigitList &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - return fImpl->format(number, appendTo, posIter, status); -} + case UNUM_MULTIPLIER: + return getMultiplier(); + case UNUM_SCALE: + return getMultiplierScale(); -UnicodeString& -DecimalFormat::format(const DigitList &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - return fImpl->format(number, appendTo, pos, status); -} + case UNUM_GROUPING_SIZE: + return getGroupingSize(); -UnicodeString& -DecimalFormat::format(const VisibleDigitsWithExponent &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - return fImpl->format(number, appendTo, posIter, status); -} + case UNUM_ROUNDING_MODE: + return getRoundingMode(); + case UNUM_FORMAT_WIDTH: + return getFormatWidth(); -UnicodeString& -DecimalFormat::format(const VisibleDigitsWithExponent &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const { - return fImpl->format(number, appendTo, pos, status); -} + case UNUM_PADDING_POSITION: + return getPadPosition(); -DigitList& -DecimalFormat::_round(const DigitList& number, DigitList& adjustedNum, UBool& isNegative, UErrorCode& status) const { - adjustedNum = number; - fImpl->round(adjustedNum, status); - isNegative = !adjustedNum.isPositive(); - return adjustedNum; -} + case UNUM_SECONDARY_GROUPING_SIZE: + return getSecondaryGroupingSize(); -void -DecimalFormat::parse(const UnicodeString& text, - Formattable& result, - ParsePosition& parsePosition) const { - parse(text, result, parsePosition, NULL); -} - -CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, - ParsePosition& pos) const { - Formattable parseResult; - int32_t start = pos.getIndex(); - UChar curbuf[4] = {}; - parse(text, parseResult, pos, curbuf); - if (pos.getIndex() != start) { - UErrorCode ec = U_ZERO_ERROR; - LocalPointer currAmt(new CurrencyAmount(parseResult, curbuf, ec), ec); - if (U_FAILURE(ec)) { - pos.setIndex(start); // indicate failure - } else { - return currAmt.orphan(); - } - } - return NULL; -} - -/** - * Parses the given text as a number, optionally providing a currency amount. - * @param text the string to parse - * @param result output parameter for the numeric result. - * @param parsePosition input-output position; on input, the - * position within text to match; must have 0 <= pos.getIndex() < - * text.length(); on output, the position after the last matched - * character. If the parse fails, the position in unchanged upon - * output. - * @param currency if non-NULL, it should point to a 4-UChar buffer. - * In this case the text is parsed as a currency format, and the - * ISO 4217 code for the parsed currency is put into the buffer. - * Otherwise the text is parsed as a non-currency format. - */ -void DecimalFormat::parse(const UnicodeString& text, - Formattable& result, - ParsePosition& parsePosition, - UChar* currency) const { - int32_t startIdx, backup; - int32_t i = startIdx = backup = parsePosition.getIndex(); - - // clear any old contents in the result. In particular, clears any DigitList - // that it may be holding. - result.setLong(0); - if (currency != NULL) { - for (int32_t ci=0; ci<4; ci++) { - currency[ci] = 0; - } - } + case UNUM_PARSE_NO_EXPONENT: + return isParseNoExponent(); - // Handle NaN as a special case: - int32_t formatWidth = fImpl->getOldFormatWidth(); + case UNUM_PARSE_DECIMAL_MARK_REQUIRED: + return isDecimalPatternMatchRequired(); - // Skip padding characters, if around prefix - if (formatWidth > 0 && ( - fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforePrefix || - fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterPrefix)) { - i = skipPadding(text, i); - } + case UNUM_CURRENCY_USAGE: + return getCurrencyUsage(); - if (isLenient()) { - // skip any leading whitespace - i = backup = skipUWhiteSpace(text, i); - } + case UNUM_MINIMUM_GROUPING_DIGITS: + return getMinimumGroupingDigits(); - // If the text is composed of the representation of NaN, returns NaN.length - const UnicodeString *nan = &fImpl->getConstSymbol(DecimalFormatSymbols::kNaNSymbol); - int32_t nanLen = (text.compare(i, nan->length(), *nan) - ? 0 : nan->length()); - if (nanLen) { - i += nanLen; - if (formatWidth > 0 && (fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforeSuffix || fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterSuffix)) { - i = skipPadding(text, i); - } - parsePosition.setIndex(i); - result.setDouble(uprv_getNaN()); - return; - } + case UNUM_PARSE_CASE_SENSITIVE: + return isParseCaseSensitive(); - // NaN parse failed; start over - i = backup; - parsePosition.setIndex(i); + case UNUM_SIGN_ALWAYS_SHOWN: + return isSignAlwaysShown(); - // status is used to record whether a number is infinite. - UBool status[fgStatusLength]; + case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: + return isFormatFailIfMoreThanMaxDigits(); - DigitList *digits = result.getInternalDigitList(); // get one from the stack buffer - if (digits == NULL) { - return; // no way to report error from here. + default: + status = U_UNSUPPORTED_ERROR; + break; } - if (fImpl->fMonetary) { - if (!parseForCurrency(text, parsePosition, *digits, - status, currency)) { - return; - } - } else { - if (!subparse(text, - &fImpl->fAffixes.fNegativePrefix.getOtherVariant().toString(), - &fImpl->fAffixes.fNegativeSuffix.getOtherVariant().toString(), - &fImpl->fAffixes.fPositivePrefix.getOtherVariant().toString(), - &fImpl->fAffixes.fPositiveSuffix.getOtherVariant().toString(), - FALSE, UCURR_SYMBOL_NAME, - parsePosition, *digits, status, currency)) { - debug("!subparse(...) - rewind"); - parsePosition.setIndex(startIdx); - return; - } - } + return -1; /* undefined */ +} - // Handle infinity - if (status[fgStatusInfinite]) { - double inf = uprv_getInfinity(); - result.setDouble(digits->isPositive() ? inf : -inf); - // TODO: set the dl to infinity, and let it fall into the code below. - } +void DecimalFormat::setGroupingUsed(UBool enabled) { + if (UBOOL_TO_BOOL(enabled) == fields->properties->groupingUsed) { return; } + NumberFormat::setGroupingUsed(enabled); // to set field for compatibility + fields->properties->groupingUsed = enabled; + touchNoError(); +} - else { +void DecimalFormat::setParseIntegerOnly(UBool value) { + if (UBOOL_TO_BOOL(value) == fields->properties->parseIntegerOnly) { return; } + NumberFormat::setParseIntegerOnly(value); // to set field for compatibility + fields->properties->parseIntegerOnly = value; + touchNoError(); +} - if (!fImpl->fMultiplier.isZero()) { - UErrorCode ec = U_ZERO_ERROR; - digits->div(fImpl->fMultiplier, ec); - } +void DecimalFormat::setLenient(UBool enable) { + ParseMode mode = enable ? PARSE_MODE_LENIENT : PARSE_MODE_STRICT; + if (!fields->properties->parseMode.isNull() && mode == fields->properties->parseMode.getNoError()) { return; } + NumberFormat::setLenient(enable); // to set field for compatibility + fields->properties->parseMode = mode; + touchNoError(); +} - if (fImpl->fScale != 0) { - DigitList ten; - ten.set((int32_t)10); - if (fImpl->fScale > 0) { - for (int32_t i = fImpl->fScale; i > 0; i--) { - UErrorCode ec = U_ZERO_ERROR; - digits->div(ten,ec); - } - } else { - for (int32_t i = fImpl->fScale; i < 0; i++) { - UErrorCode ec = U_ZERO_ERROR; - digits->mult(ten,ec); - } - } - } +DecimalFormat::DecimalFormat(const UnicodeString& pattern, DecimalFormatSymbols* symbolsToAdopt, + UParseError&, UErrorCode& status) + : DecimalFormat(symbolsToAdopt, status) { + // TODO: What is parseError for? + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} - // Negative zero special case: - // if parsing integerOnly, change to +0, which goes into an int32 in a Formattable. - // if not parsing integerOnly, leave as -0, which a double can represent. - if (digits->isZero() && !digits->isPositive() && isParseIntegerOnly()) { - digits->setPositive(TRUE); - } - result.adoptDigitList(digits); - } -} - - - -UBool -DecimalFormat::parseForCurrency(const UnicodeString& text, - ParsePosition& parsePosition, - DigitList& digits, - UBool* status, - UChar* currency) const { - UnicodeString positivePrefix; - UnicodeString positiveSuffix; - UnicodeString negativePrefix; - UnicodeString negativeSuffix; - fImpl->fPositivePrefixPattern.toString(positivePrefix); - fImpl->fPositiveSuffixPattern.toString(positiveSuffix); - fImpl->fNegativePrefixPattern.toString(negativePrefix); - fImpl->fNegativeSuffixPattern.toString(negativeSuffix); - - int origPos = parsePosition.getIndex(); - int maxPosIndex = origPos; - int maxErrorPos = -1; - // First, parse against current pattern. - // Since current pattern could be set by applyPattern(), - // it could be an arbitrary pattern, and it may not be the one - // defined in current locale. - UBool tmpStatus[fgStatusLength]; - ParsePosition tmpPos(origPos); - DigitList tmpDigitList; - UBool found; - if (fStyle == UNUM_CURRENCY_PLURAL) { - found = subparse(text, - &negativePrefix, &negativeSuffix, - &positivePrefix, &positiveSuffix, - TRUE, UCURR_LONG_NAME, - tmpPos, tmpDigitList, tmpStatus, currency); - } else { - found = subparse(text, - &negativePrefix, &negativeSuffix, - &positivePrefix, &positiveSuffix, - TRUE, UCURR_SYMBOL_NAME, - tmpPos, tmpDigitList, tmpStatus, currency); - } - if (found) { - if (tmpPos.getIndex() > maxPosIndex) { - maxPosIndex = tmpPos.getIndex(); - for (int32_t i = 0; i < fgStatusLength; ++i) { - status[i] = tmpStatus[i]; - } - digits = tmpDigitList; - } - } else { - maxErrorPos = tmpPos.getErrorIndex(); - } - // Then, parse against affix patterns. - // Those are currency patterns and currency plural patterns. - int32_t pos = UHASH_FIRST; - const UHashElement* element = NULL; - while ( (element = fAffixPatternsForCurrency->nextElement(pos)) != NULL ) { - const UHashTok valueTok = element->value; - const AffixPatternsForCurrency* affixPtn = (AffixPatternsForCurrency*)valueTok.pointer; - UBool tmpStatus[fgStatusLength]; - ParsePosition tmpPos(origPos); - DigitList tmpDigitList; - -#ifdef FMT_DEBUG - debug("trying affix for currency.."); - affixPtn->dump(); -#endif +DecimalFormat::DecimalFormat(const UnicodeString& pattern, const DecimalFormatSymbols& symbols, + UErrorCode& status) + : DecimalFormat(new DecimalFormatSymbols(symbols), status) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_IF_CURRENCY, status); + touch(status); +} - UBool result = subparse(text, - &affixPtn->negPrefixPatternForCurrency, - &affixPtn->negSuffixPatternForCurrency, - &affixPtn->posPrefixPatternForCurrency, - &affixPtn->posSuffixPatternForCurrency, - TRUE, affixPtn->patternType, - tmpPos, tmpDigitList, tmpStatus, currency); - if (result) { - found = true; - if (tmpPos.getIndex() > maxPosIndex) { - maxPosIndex = tmpPos.getIndex(); - for (int32_t i = 0; i < fgStatusLength; ++i) { - status[i] = tmpStatus[i]; - } - digits = tmpDigitList; - } - } else { - maxErrorPos = (tmpPos.getErrorIndex() > maxErrorPos) ? - tmpPos.getErrorIndex() : maxErrorPos; - } +DecimalFormat::DecimalFormat(const DecimalFormat& source) : NumberFormat(source) { + // Note: it is not safe to copy fields->formatter or fWarehouse directly because fields->formatter might have + // dangling pointers to fields inside fWarehouse. The safe thing is to re-construct fields->formatter from + // the property bag, despite being somewhat slower. + fields = new DecimalFormatFields(); + if (fields == nullptr) { + return; } - // Finally, parse against simple affix to find the match. - // For example, in TestMonster suite, - // if the to-be-parsed text is "-\u00A40,00". - // complexAffixCompare will not find match, - // since there is no ISO code matches "\u00A4", - // and the parse stops at "\u00A4". - // We will just use simple affix comparison (look for exact match) - // to pass it. - // - // TODO: We should parse against simple affix first when - // output currency is not requested. After the complex currency - // parsing implementation was introduced, the default currency - // instance parsing slowed down because of the new code flow. - // I filed #10312 - Yoshito - UBool tmpStatus_2[fgStatusLength]; - ParsePosition tmpPos_2(origPos); - DigitList tmpDigitList_2; - - // Disable complex currency parsing and try it again. - UBool result = subparse(text, - &fImpl->fAffixes.fNegativePrefix.getOtherVariant().toString(), - &fImpl->fAffixes.fNegativeSuffix.getOtherVariant().toString(), - &fImpl->fAffixes.fPositivePrefix.getOtherVariant().toString(), - &fImpl->fAffixes.fPositiveSuffix.getOtherVariant().toString(), - FALSE /* disable complex currency parsing */, UCURR_SYMBOL_NAME, - tmpPos_2, tmpDigitList_2, tmpStatus_2, - currency); - if (result) { - if (tmpPos_2.getIndex() > maxPosIndex) { - maxPosIndex = tmpPos_2.getIndex(); - for (int32_t i = 0; i < fgStatusLength; ++i) { - status[i] = tmpStatus_2[i]; - } - digits = tmpDigitList_2; - } - found = true; - } else { - maxErrorPos = (tmpPos_2.getErrorIndex() > maxErrorPos) ? - tmpPos_2.getErrorIndex() : maxErrorPos; + fields->properties.adoptInstead(new DecimalFormatProperties(*source.fields->properties)); + fields->symbols.adoptInstead(new DecimalFormatSymbols(*source.fields->symbols)); + fields->exportedProperties.adoptInstead(new DecimalFormatProperties()); + if (fields->properties == nullptr || fields->symbols == nullptr || fields->exportedProperties == nullptr) { + return; } + touchNoError(); +} - if (!found) { - //parsePosition.setIndex(origPos); - parsePosition.setErrorIndex(maxErrorPos); - } else { - parsePosition.setIndex(maxPosIndex); - parsePosition.setErrorIndex(-1); - } - return found; -} - - -/** - * Parse the given text into a number. The text is parsed beginning at - * parsePosition, until an unparseable character is seen. - * @param text the string to parse. - * @param negPrefix negative prefix. - * @param negSuffix negative suffix. - * @param posPrefix positive prefix. - * @param posSuffix positive suffix. - * @param complexCurrencyParsing whether it is complex currency parsing or not. - * @param type the currency type to parse against, LONG_NAME only or not. - * @param parsePosition The position at which to being parsing. Upon - * return, the first unparsed character. - * @param digits the DigitList to set to the parsed value. - * @param status output param containing boolean status flags indicating - * whether the value was infinite and whether it was positive. - * @param currency return value for parsed currency, for generic - * currency parsing mode, or NULL for normal parsing. In generic - * currency parsing mode, any currency is parsed, not just the - * currency that this formatter is set to. - */ -UBool DecimalFormat::subparse(const UnicodeString& text, - const UnicodeString* negPrefix, - const UnicodeString* negSuffix, - const UnicodeString* posPrefix, - const UnicodeString* posSuffix, - UBool complexCurrencyParsing, - int8_t type, - ParsePosition& parsePosition, - DigitList& digits, UBool* status, - UChar* currency) const -{ - // The parsing process builds up the number as char string, in the neutral format that - // will be acceptable to the decNumber library, then at the end passes that string - // off for conversion to a decNumber. - UErrorCode err = U_ZERO_ERROR; - CharString parsedNum; - digits.setToZero(); - - int32_t position = parsePosition.getIndex(); - int32_t oldStart = position; - int32_t textLength = text.length(); // One less pointer to follow - UBool strictParse = !isLenient(); - UChar32 zero = fImpl->getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - const UnicodeString *groupingString = &fImpl->getConstSymbol( - !fImpl->fMonetary ? - DecimalFormatSymbols::kGroupingSeparatorSymbol : DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol); - UChar32 groupingChar = groupingString->char32At(0); - int32_t groupingStringLength = groupingString->length(); - int32_t groupingCharLength = U16_LENGTH(groupingChar); - UBool groupingUsed = isGroupingUsed(); -#ifdef FMT_DEBUG - UChar dbgbuf[300]; - UnicodeString s(dbgbuf,0,300);; - s.append((UnicodeString)"PARSE \"").append(text.tempSubString(position)).append((UnicodeString)"\" " ); -#define DBGAPPD(x) if(x) { s.append(UnicodeString(#x "=")); if(x->isEmpty()) { s.append(UnicodeString("")); } else { s.append(*x); } s.append(UnicodeString(" ")); } else { s.append(UnicodeString(#x "=NULL ")); } - DBGAPPD(negPrefix); - DBGAPPD(negSuffix); - DBGAPPD(posPrefix); - DBGAPPD(posSuffix); - debugout(s); -#endif - - UBool fastParseOk = false; /* TRUE iff fast parse is OK */ - // UBool fastParseHadDecimal = FALSE; /* true if fast parse saw a decimal point. */ - if((fImpl->isParseFastpath()) && !fImpl->fMonetary && - text.length()>0 && - text.length()<32 && - (posPrefix==NULL||posPrefix->isEmpty()) && - (posSuffix==NULL||posSuffix->isEmpty()) && - // (negPrefix==NULL||negPrefix->isEmpty()) && - // (negSuffix==NULL||(negSuffix->isEmpty()) ) && - TRUE) { // optimized path - int j=position; - int l=text.length(); - int digitCount=0; - UChar32 ch = text.char32At(j); - const UnicodeString *decimalString = &fImpl->getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); - UChar32 decimalChar = 0; - UBool intOnly = FALSE; - UChar32 lookForGroup = (groupingUsed&&intOnly&&strictParse)?groupingChar:0; - - int32_t decimalCount = decimalString->countChar32(0,3); - if(isParseIntegerOnly()) { - decimalChar = 0; // not allowed - intOnly = TRUE; // Don't look for decimals. - } else if(decimalCount==1) { - decimalChar = decimalString->char32At(0); // Look for this decimal - } else if(decimalCount==0) { - decimalChar=0; // NO decimal set - } else { - j=l+1;//Set counter to end of line, so that we break. Unknown decimal situation. - } - -#ifdef FMT_DEBUG - printf("Preparing to do fastpath parse: decimalChar=U+%04X, groupingChar=U+%04X, first ch=U+%04X intOnly=%c strictParse=%c\n", - decimalChar, groupingChar, ch, - (intOnly)?'y':'n', - (strictParse)?'y':'n'); -#endif - if(ch==0x002D) { // '-' - j=l+1;//=break - negative number. - - /* - parsedNum.append('-',err); - j+=U16_LENGTH(ch); - if(j=0 && digit <= 9) { - parsedNum.append((char)(digit + '0'), err); - if((digitCount>0) || digit!=0 || j==(l-1)) { - digitCount++; - } - } else if(ch == 0) { // break out - digitCount=-1; - break; - } else if(ch == decimalChar) { - parsedNum.append((char)('.'), err); - decimalChar=0; // no more decimals. - // fastParseHadDecimal=TRUE; - } else if(ch == lookForGroup) { - // ignore grouping char. No decimals, so it has to be an ignorable grouping sep - } else if(intOnly && (lookForGroup!=0) && !u_isdigit(ch)) { - // parsing integer only and can fall through - } else { - digitCount=-1; // fail - fall through to slow parse - break; - } - j+=U16_LENGTH(ch); - ch = text.char32At(j); // for next - } - if( - ((j==l)||intOnly) // end OR only parsing integer - && (digitCount>0)) { // and have at least one digit - fastParseOk=true; // Fast parse OK! - -#ifdef SKIP_OPT - debug("SKIP_OPT"); - /* for testing, try it the slow way. also */ - fastParseOk=false; - parsedNum.clear(); -#else - parsePosition.setIndex(position=j); - status[fgStatusInfinite]=false; -#endif - } else { - // was not OK. reset, retry -#ifdef FMT_DEBUG - printf("Fall through: j=%d, l=%d, digitCount=%d\n", j, l, digitCount); -#endif - parsedNum.clear(); - } - } else { -#ifdef FMT_DEBUG - printf("Could not fastpath parse. "); - printf("text.length()=%d ", text.length()); - printf("posPrefix=%p posSuffix=%p ", posPrefix, posSuffix); +DecimalFormat& DecimalFormat::operator=(const DecimalFormat& rhs) { + *fields->properties = *rhs.fields->properties; + fields->exportedProperties->clear(); + fields->symbols.adoptInstead(new DecimalFormatSymbols(*rhs.fields->symbols)); + touchNoError(); + return *this; +} - printf("\n"); -#endif - } +DecimalFormat::~DecimalFormat() { + delete fields->atomicParser.exchange(nullptr); + delete fields->atomicCurrencyParser.exchange(nullptr); + delete fields; +} - UnicodeString formatPattern; - toPattern(formatPattern); +Format* DecimalFormat::clone() const { + return new DecimalFormat(*this); +} - if(!fastParseOk -#if UCONFIG_HAVE_PARSEALLINPUT - && fParseAllInput!=UNUM_YES -#endif - ) - { - int32_t formatWidth = fImpl->getOldFormatWidth(); - // Match padding before prefix - if (formatWidth > 0 && fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforePrefix) { - position = skipPadding(text, position); - } - - // Match positive and negative prefixes; prefer longest match. - int32_t posMatch = compareAffix(text, position, FALSE, TRUE, posPrefix, complexCurrencyParsing, type, currency); - int32_t negMatch = compareAffix(text, position, TRUE, TRUE, negPrefix, complexCurrencyParsing, type, currency); - if (posMatch >= 0 && negMatch >= 0) { - if (posMatch > negMatch) { - negMatch = -1; - } else if (negMatch > posMatch) { - posMatch = -1; - } - } - if (posMatch >= 0) { - position += posMatch; - parsedNum.append('+', err); - } else if (negMatch >= 0) { - position += negMatch; - parsedNum.append('-', err); - } else if (strictParse){ - parsePosition.setErrorIndex(position); - return FALSE; - } else { - // Temporary set positive. This might be changed after checking suffix - parsedNum.append('+', err); +UBool DecimalFormat::operator==(const Format& other) const { + auto* otherDF = dynamic_cast(&other); + if (otherDF == nullptr) { + return false; } + return *fields->properties == *otherDF->fields->properties && *fields->symbols == *otherDF->fields->symbols; +} - // Match padding before prefix - if (formatWidth > 0 && fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterPrefix) { - position = skipPadding(text, position); +UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos) const { + if (pos.getField() == FieldPosition::DONT_CARE && fastFormatDouble(number, appendTo)) { + return appendTo; } + UErrorCode localStatus = U_ZERO_ERROR; + FormattedNumber output = fields->formatter->formatDouble(number, localStatus); + fieldPositionHelper(output, pos, appendTo.length(), localStatus); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} - if (! strictParse) { - position = skipUWhiteSpace(text, position); +UnicodeString& DecimalFormat::format(double number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const { + if (pos.getField() == FieldPosition::DONT_CARE && fastFormatDouble(number, appendTo)) { + return appendTo; } + FormattedNumber output = fields->formatter->formatDouble(number, status); + fieldPositionHelper(output, pos, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} - // process digits or Inf, find decimal position - const UnicodeString *inf = &fImpl->getConstSymbol(DecimalFormatSymbols::kInfinitySymbol); - int32_t infLen = (text.compare(position, inf->length(), *inf) - ? 0 : inf->length()); - position += infLen; // infLen is non-zero when it does equal to infinity - status[fgStatusInfinite] = infLen != 0; - - if (infLen != 0) { - parsedNum.append("Infinity", err); - } else { - // We now have a string of digits, possibly with grouping symbols, - // and decimal points. We want to process these into a DigitList. - // We don't want to put a bunch of leading zeros into the DigitList - // though, so we keep track of the location of the decimal point, - // put only significant digits into the DigitList, and adjust the - // exponent as needed. - - - UBool strictFail = FALSE; // did we exit with a strict parse failure? - int32_t lastGroup = -1; // after which digit index did we last see a grouping separator? - int32_t currGroup = -1; // for temporary storage the digit index of the current grouping separator - int32_t gs2 = fImpl->fEffGrouping.fGrouping2 == 0 ? fImpl->fEffGrouping.fGrouping : fImpl->fEffGrouping.fGrouping2; - - const UnicodeString *decimalString; - if (fImpl->fMonetary) { - decimalString = &fImpl->getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); - } else { - decimalString = &fImpl->getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); - } - UChar32 decimalChar = decimalString->char32At(0); - int32_t decimalStringLength = decimalString->length(); - int32_t decimalCharLength = U16_LENGTH(decimalChar); - - UBool sawDecimal = FALSE; - UChar32 sawDecimalChar = 0xFFFF; - UBool sawGrouping = FALSE; - UChar32 sawGroupingChar = 0xFFFF; - UBool sawDigit = FALSE; - int32_t backup = -1; - int32_t digit; - - // equivalent grouping and decimal support - const UnicodeSet *decimalSet = NULL; - const UnicodeSet *groupingSet = NULL; - - if (decimalCharLength == decimalStringLength) { - decimalSet = DecimalFormatStaticSets::getSimilarDecimals(decimalChar, strictParse); - } - - if (groupingCharLength == groupingStringLength) { - if (strictParse) { - groupingSet = fStaticSets->fStrictDefaultGroupingSeparators; - } else { - groupingSet = fStaticSets->fDefaultGroupingSeparators; - } - } - - // We need to test groupingChar and decimalChar separately from groupingSet and decimalSet, if the sets are even initialized. - // If sawDecimal is TRUE, only consider sawDecimalChar and NOT decimalSet - // If a character matches decimalSet, don't consider it to be a member of the groupingSet. - - // We have to track digitCount ourselves, because digits.fCount will - // pin when the maximum allowable digits is reached. - int32_t digitCount = 0; - int32_t integerDigitCount = 0; - - for (; position < textLength; ) - { - UChar32 ch = text.char32At(position); - - /* We recognize all digit ranges, not only the Latin digit range - * '0'..'9'. We do so by using the Character.digit() method, - * which converts a valid Unicode digit to the range 0..9. - * - * The character 'ch' may be a digit. If so, place its value - * from 0 to 9 in 'digit'. First try using the locale digit, - * which may or MAY NOT be a standard Unicode digit range. If - * this fails, try using the standard Unicode digit ranges by - * calling Character.digit(). If this also fails, digit will - * have a value outside the range 0..9. - */ - digit = ch - zero; - if (digit < 0 || digit > 9) - { - digit = u_charDigitValue(ch); - } - - // As a last resort, look through the localized digits if the zero digit - // is not a "standard" Unicode digit. - if ( (digit < 0 || digit > 9) && u_charDigitValue(zero) != 0) { - digit = 0; - if ( fImpl->getConstSymbol((DecimalFormatSymbols::ENumberFormatSymbol)(DecimalFormatSymbols::kZeroDigitSymbol)).char32At(0) == ch ) { - break; - } - for (digit = 1 ; digit < 10 ; digit++ ) { - if ( fImpl->getConstSymbol((DecimalFormatSymbols::ENumberFormatSymbol)(DecimalFormatSymbols::kOneDigitSymbol+digit-1)).char32At(0) == ch ) { - break; - } - } - } - - if (digit >= 0 && digit <= 9) - { - if (strictParse && backup != -1) { - // comma followed by digit, so group before comma is a - // secondary group. If there was a group separator - // before that, the group must == the secondary group - // length, else it can be <= the the secondary group - // length. - if ((lastGroup != -1 && currGroup - lastGroup != gs2) || - (lastGroup == -1 && digitCount - 1 > gs2)) { - strictFail = TRUE; - break; - } - - lastGroup = currGroup; - } - - // Cancel out backup setting (see grouping handler below) - currGroup = -1; - backup = -1; - sawDigit = TRUE; - - // Note: this will append leading zeros - parsedNum.append((char)(digit + '0'), err); - - // count any digit that's not a leading zero - if (digit > 0 || digitCount > 0 || sawDecimal) { - digitCount += 1; - - // count any integer digit that's not a leading zero - if (! sawDecimal) { - integerDigitCount += 1; - } - } - - position += U16_LENGTH(ch); - } - else if (groupingStringLength > 0 && - matchGrouping(groupingChar, sawGrouping, sawGroupingChar, groupingSet, - decimalChar, decimalSet, - ch) && groupingUsed) - { - if (sawDecimal) { - break; - } - - if (strictParse) { - if ((!sawDigit || backup != -1)) { - // leading group, or two group separators in a row - strictFail = TRUE; - break; - } - } - - // Ignore grouping characters, if we are using them, but require - // that they be followed by a digit. Otherwise we backup and - // reprocess them. - currGroup = digitCount; - backup = position; - position += groupingStringLength; - sawGrouping=TRUE; - // Once we see a grouping character, we only accept that grouping character from then on. - sawGroupingChar=ch; - } - else if (matchDecimal(decimalChar,sawDecimal,sawDecimalChar, decimalSet, ch)) - { - if (strictParse) { - if (backup != -1 || - (lastGroup != -1 && digitCount - lastGroup != fImpl->fEffGrouping.fGrouping)) { - strictFail = TRUE; - break; - } - } - - // If we're only parsing integers, or if we ALREADY saw the - // decimal, then don't parse this one. - if (isParseIntegerOnly() || sawDecimal) { - break; - } - - parsedNum.append('.', err); - position += decimalStringLength; - sawDecimal = TRUE; - // Once we see a decimal character, we only accept that decimal character from then on. - sawDecimalChar=ch; - // decimalSet is considered to consist of (ch,ch) - } - else { - - if(!fBoolFlags.contains(UNUM_PARSE_NO_EXPONENT) || // don't parse if this is set unless.. - isScientificNotation()) { // .. it's an exponent format - ignore setting and parse anyways - const UnicodeString *tmp; - tmp = &fImpl->getConstSymbol(DecimalFormatSymbols::kExponentialSymbol); - // TODO: CASE - if (!text.caseCompare(position, tmp->length(), *tmp, U_FOLD_CASE_DEFAULT)) // error code is set below if !sawDigit - { - // Parse sign, if present - int32_t pos = position + tmp->length(); - char exponentSign = '+'; - - if (pos < textLength) - { - tmp = &fImpl->getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - if (!text.compare(pos, tmp->length(), *tmp)) - { - pos += tmp->length(); - } - else { - tmp = &fImpl->getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - if (!text.compare(pos, tmp->length(), *tmp)) - { - exponentSign = '-'; - pos += tmp->length(); - } - } - } - - UBool sawExponentDigit = FALSE; - while (pos < textLength) { - ch = text.char32At(pos); - digit = ch - zero; - - if (digit < 0 || digit > 9) { - digit = u_charDigitValue(ch); - } - if (0 <= digit && digit <= 9) { - if (!sawExponentDigit) { - parsedNum.append('E', err); - parsedNum.append(exponentSign, err); - sawExponentDigit = TRUE; - } - pos += U16_LENGTH(ch); - parsedNum.append((char)(digit + '0'), err); - } else { - break; - } - } - - if (sawExponentDigit) { - position = pos; // Advance past the exponent - } - - break; // Whether we fail or succeed, we exit this loop - } else { - break; - } - } else { // not parsing exponent - break; - } - } - } - - // if we didn't see a decimal and it is required, check to see if the pattern had one - if(!sawDecimal && isDecimalPatternMatchRequired()) - { - if(formatPattern.indexOf(kPatternDecimalSeparator) != -1) - { - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(position); - debug("decimal point match required fail!"); - return FALSE; - } - } - - if (backup != -1) - { - position = backup; - } - - if (strictParse && !sawDecimal) { - if (lastGroup != -1 && digitCount - lastGroup != fImpl->fEffGrouping.fGrouping) { - strictFail = TRUE; - } - } +UnicodeString& +DecimalFormat::format(double number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const { + if (posIter == nullptr && fastFormatDouble(number, appendTo)) { + return appendTo; + } + FormattedNumber output = fields->formatter->formatDouble(number, status); + fieldPositionIteratorHelper(output, posIter, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} - if (strictFail) { - // only set with strictParse and a grouping separator error +UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos) const { + return format(static_cast (number), appendTo, pos); +} - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(position); - debug("strictFail!"); - return FALSE; - } +UnicodeString& DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const { + return format(static_cast (number), appendTo, pos, status); +} - // If there was no decimal point we have an integer +UnicodeString& +DecimalFormat::format(int32_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const { + return format(static_cast (number), appendTo, posIter, status); +} - // If none of the text string was recognized. For example, parse - // "x" with pattern "#0.00" (return index and error index both 0) - // parse "$" with pattern "$#0.00". (return index 0 and error index - // 1). - if (!sawDigit && digitCount == 0) { -#ifdef FMT_DEBUG - debug("none of text rec"); - printf("position=%d\n",position); -#endif - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(oldStart); - return FALSE; - } +UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos) const { + if (pos.getField() == FieldPosition::DONT_CARE && fastFormatInt64(number, appendTo)) { + return appendTo; } + UErrorCode localStatus = U_ZERO_ERROR; + FormattedNumber output = fields->formatter->formatInt(number, localStatus); + fieldPositionHelper(output, pos, appendTo.length(), localStatus); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} - // Match padding before suffix - if (formatWidth > 0 && fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforeSuffix) { - position = skipPadding(text, position); +UnicodeString& DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const { + if (pos.getField() == FieldPosition::DONT_CARE && fastFormatInt64(number, appendTo)) { + return appendTo; } + FormattedNumber output = fields->formatter->formatInt(number, status); + fieldPositionHelper(output, pos, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} - int32_t posSuffixMatch = -1, negSuffixMatch = -1; - - // Match positive and negative suffixes; prefer longest match. - if (posMatch >= 0 || (!strictParse && negMatch < 0)) { - posSuffixMatch = compareAffix(text, position, FALSE, FALSE, posSuffix, complexCurrencyParsing, type, currency); - } - if (negMatch >= 0) { - negSuffixMatch = compareAffix(text, position, TRUE, FALSE, negSuffix, complexCurrencyParsing, type, currency); - } - if (posSuffixMatch >= 0 && negSuffixMatch >= 0) { - if (posSuffixMatch > negSuffixMatch) { - negSuffixMatch = -1; - } else if (negSuffixMatch > posSuffixMatch) { - posSuffixMatch = -1; - } +UnicodeString& +DecimalFormat::format(int64_t number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const { + if (posIter == nullptr && fastFormatInt64(number, appendTo)) { + return appendTo; } + FormattedNumber output = fields->formatter->formatInt(number, status); + fieldPositionIteratorHelper(output, posIter, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} - // Fail if neither or both - if (strictParse && ((posSuffixMatch >= 0) == (negSuffixMatch >= 0))) { - parsePosition.setErrorIndex(position); - debug("neither or both"); - return FALSE; - } +UnicodeString& +DecimalFormat::format(StringPiece number, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const { + FormattedNumber output = fields->formatter->formatDecimal(number, status); + fieldPositionIteratorHelper(output, posIter, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} - position += (posSuffixMatch >= 0 ? posSuffixMatch : (negSuffixMatch >= 0 ? negSuffixMatch : 0)); +UnicodeString& DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, + FieldPositionIterator* posIter, UErrorCode& status) const { + FormattedNumber output = fields->formatter->formatDecimalQuantity(number, status); + fieldPositionIteratorHelper(output, posIter, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} - // Match padding before suffix - if (formatWidth > 0 && fImpl->fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterSuffix) { - position = skipPadding(text, position); +UnicodeString& +DecimalFormat::format(const DecimalQuantity& number, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const { + FormattedNumber output = fields->formatter->formatDecimalQuantity(number, status); + fieldPositionHelper(output, pos, appendTo.length(), status); + auto appendable = UnicodeStringAppendable(appendTo); + output.appendTo(appendable); + return appendTo; +} + +void DecimalFormat::parse(const UnicodeString& text, Formattable& output, + ParsePosition& parsePosition) const { + if (parsePosition.getIndex() < 0 || parsePosition.getIndex() >= text.length()) { + return; } - parsePosition.setIndex(position); - - parsedNum.data()[0] = (posSuffixMatch >= 0 || (!strictParse && negMatch < 0 && negSuffixMatch < 0)) ? '+' : '-'; -#ifdef FMT_DEBUG -printf("PP -> %d, SLOW = [%s]! pp=%d, os=%d, err=%s\n", position, parsedNum.data(), parsePosition.getIndex(),oldStart,u_errorName(err)); -#endif - } /* end SLOW parse */ - if(parsePosition.getIndex() == oldStart) - { -#ifdef FMT_DEBUG - printf(" PP didnt move, err\n"); -#endif - parsePosition.setErrorIndex(position); - return FALSE; - } -#if UCONFIG_HAVE_PARSEALLINPUT - else if (fParseAllInput==UNUM_YES&&parsePosition.getIndex()!=textLength) - { -#ifdef FMT_DEBUG - printf(" PP didnt consume all (UNUM_YES), err\n"); -#endif - parsePosition.setErrorIndex(position); - return FALSE; - } -#endif - // uint32_t bits = (fastParseOk?kFastpathOk:0) | - // (fastParseHadDecimal?0:kNoDecimal); - //printf("FPOK=%d, FPHD=%d, bits=%08X\n", fastParseOk, fastParseHadDecimal, bits); - digits.set(parsedNum.toStringPiece(), - err, - 0//bits - ); - - if (U_FAILURE(err)) { -#ifdef FMT_DEBUG - printf(" err setting %s\n", u_errorName(err)); -#endif - parsePosition.setErrorIndex(position); - return FALSE; - } - - // check if we missed a required decimal point - if(fastParseOk && isDecimalPatternMatchRequired()) - { - if(formatPattern.indexOf(kPatternDecimalSeparator) != -1) - { - parsePosition.setIndex(oldStart); - parsePosition.setErrorIndex(position); - debug("decimal point match required fail!"); - return FALSE; - } + ErrorCode status; + ParsedNumber result; + // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the + // parseCurrency method (backwards compatibility) + int32_t startIndex = parsePosition.getIndex(); + const NumberParserImpl* parser = getParser(status); + if (U_FAILURE(status)) { return; } + parser->parse(text, startIndex, true, result, status); + // TODO: Do we need to check for fImpl->properties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here? + if (result.success()) { + parsePosition.setIndex(result.charEnd); + result.populateFormattable(output, parser->getParseFlags()); + } else { + parsePosition.setErrorIndex(startIndex + result.charEnd); } +} - - return TRUE; -} - -/** - * Starting at position, advance past a run of pad characters, if any. - * Return the index of the first character after position that is not a pad - * character. Result is >= position. - */ -int32_t DecimalFormat::skipPadding(const UnicodeString& text, int32_t position) const { - int32_t padLen = U16_LENGTH(fImpl->fAffixes.fPadChar); - while (position < text.length() && - text.char32At(position) == fImpl->fAffixes.fPadChar) { - position += padLen; - } - return position; -} - -/** - * Return the length matched by the given affix, or -1 if none. - * Runs of white space in the affix, match runs of white space in - * the input. Pattern white space and input white space are - * determined differently; see code. - * @param text input text - * @param pos offset into input at which to begin matching - * @param isNegative - * @param isPrefix - * @param affixPat affix pattern used for currency affix comparison. - * @param complexCurrencyParsing whether it is currency parsing or not - * @param type the currency type to parse against, LONG_NAME only or not. - * @param currency return value for parsed currency, for generic - * currency parsing mode, or null for normal parsing. In generic - * currency parsing mode, any currency is parsed, not just the - * currency that this formatter is set to. - * @return length of input that matches, or -1 if match failure - */ -int32_t DecimalFormat::compareAffix(const UnicodeString& text, - int32_t pos, - UBool isNegative, - UBool isPrefix, - const UnicodeString* affixPat, - UBool complexCurrencyParsing, - int8_t type, - UChar* currency) const -{ - const UnicodeString *patternToCompare; - if (currency != NULL || - (fImpl->fMonetary && complexCurrencyParsing)) { - - if (affixPat != NULL) { - return compareComplexAffix(*affixPat, text, pos, type, currency); - } +CurrencyAmount* DecimalFormat::parseCurrency(const UnicodeString& text, ParsePosition& parsePosition) const { + if (parsePosition.getIndex() < 0 || parsePosition.getIndex() >= text.length()) { + return nullptr; } - if (isNegative) { - if (isPrefix) { - patternToCompare = &fImpl->fAffixes.fNegativePrefix.getOtherVariant().toString(); - } - else { - patternToCompare = &fImpl->fAffixes.fNegativeSuffix.getOtherVariant().toString(); - } - } - else { - if (isPrefix) { - patternToCompare = &fImpl->fAffixes.fPositivePrefix.getOtherVariant().toString(); - } - else { - patternToCompare = &fImpl->fAffixes.fPositiveSuffix.getOtherVariant().toString(); - } + ErrorCode status; + ParsedNumber result; + // Note: if this is a currency instance, currencies will be matched despite the fact that we are not in the + // parseCurrency method (backwards compatibility) + int32_t startIndex = parsePosition.getIndex(); + const NumberParserImpl* parser = getCurrencyParser(status); + if (U_FAILURE(status)) { return nullptr; } + parser->parse(text, startIndex, true, result, status); + // TODO: Do we need to check for fImpl->properties->parseAllInput (UCONFIG_HAVE_PARSEALLINPUT) here? + if (result.success()) { + parsePosition.setIndex(result.charEnd); + Formattable formattable; + result.populateFormattable(formattable, parser->getParseFlags()); + return new CurrencyAmount(formattable, result.currencyCode, status); + } else { + parsePosition.setErrorIndex(startIndex + result.charEnd); + return nullptr; } - return compareSimpleAffix(*patternToCompare, text, pos, isLenient()); } -UBool DecimalFormat::equalWithSignCompatibility(UChar32 lhs, UChar32 rhs) const { - if (lhs == rhs) { - return TRUE; - } - U_ASSERT(fStaticSets != NULL); // should already be loaded - const UnicodeSet *minusSigns = fStaticSets->fMinusSigns; - const UnicodeSet *plusSigns = fStaticSets->fPlusSigns; - return (minusSigns->contains(lhs) && minusSigns->contains(rhs)) || - (plusSigns->contains(lhs) && plusSigns->contains(rhs)); +const DecimalFormatSymbols* DecimalFormat::getDecimalFormatSymbols(void) const { + return fields->symbols.getAlias(); } -// check for LRM 0x200E, RLM 0x200F, ALM 0x061C -#define IS_BIDI_MARK(c) (c==0x200E || c==0x200F || c==0x061C) +void DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) { + if (symbolsToAdopt == nullptr) { + return; // do not allow caller to set fields->symbols to NULL + } + fields->symbols.adoptInstead(symbolsToAdopt); + touchNoError(); +} -#define TRIM_BUFLEN 32 -UnicodeString& DecimalFormat::trimMarksFromAffix(const UnicodeString& affix, UnicodeString& trimmedAffix) { - UChar trimBuf[TRIM_BUFLEN]; - int32_t affixLen = affix.length(); - int32_t affixPos, trimLen = 0; +void DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) { + fields->symbols.adoptInstead(new DecimalFormatSymbols(symbols)); + touchNoError(); +} - for (affixPos = 0; affixPos < affixLen; affixPos++) { - UChar c = affix.charAt(affixPos); - if (!IS_BIDI_MARK(c)) { - if (trimLen < TRIM_BUFLEN) { - trimBuf[trimLen++] = c; - } else { - trimLen = 0; - break; - } - } - } - return (trimLen > 0)? trimmedAffix.setTo(trimBuf, trimLen): trimmedAffix.setTo(affix); -} - -/** - * Return the length matched by the given affix, or -1 if none. - * Runs of white space in the affix, match runs of white space in - * the input. Pattern white space and input white space are - * determined differently; see code. - * @param affix pattern string, taken as a literal - * @param input input text - * @param pos offset into input at which to begin matching - * @return length of input that matches, or -1 if match failure - */ -int32_t DecimalFormat::compareSimpleAffix(const UnicodeString& affix, - const UnicodeString& input, - int32_t pos, - UBool lenient) const { - int32_t start = pos; - UnicodeString trimmedAffix; - // For more efficiency we should keep lazily-created trimmed affixes around in - // instance variables instead of trimming each time they are used (the next step) - trimMarksFromAffix(affix, trimmedAffix); - UChar32 affixChar = trimmedAffix.char32At(0); - int32_t affixLength = trimmedAffix.length(); - int32_t inputLength = input.length(); - int32_t affixCharLength = U16_LENGTH(affixChar); - UnicodeSet *affixSet; - UErrorCode status = U_ZERO_ERROR; - - U_ASSERT(fStaticSets != NULL); // should already be loaded +const CurrencyPluralInfo* DecimalFormat::getCurrencyPluralInfo(void) const { + return fields->properties->currencyPluralInfo.fPtr.getAlias(); +} - if (U_FAILURE(status)) { - return -1; - } - if (!lenient) { - affixSet = fStaticSets->fStrictDashEquivalents; - - // If the trimmedAffix is exactly one character long and that character - // is in the dash set and the very next input character is also - // in the dash set, return a match. - if (affixCharLength == affixLength && affixSet->contains(affixChar)) { - UChar32 ic = input.char32At(pos); - if (affixSet->contains(ic)) { - pos += U16_LENGTH(ic); - pos = skipBidiMarks(input, pos); // skip any trailing bidi marks - return pos - start; - } - } +void DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) { + fields->properties->currencyPluralInfo.fPtr.adoptInstead(toAdopt); + touchNoError(); +} - for (int32_t i = 0; i < affixLength; ) { - UChar32 c = trimmedAffix.char32At(i); - int32_t len = U16_LENGTH(c); - if (PatternProps::isWhiteSpace(c)) { - // We may have a pattern like: \u200F \u0020 - // and input text like: \u200F \u0020 - // Note that U+200F and U+0020 are Pattern_White_Space but only - // U+0020 is UWhiteSpace. So we have to first do a direct - // match of the run of Pattern_White_Space in the pattern, - // then match any extra characters. - UBool literalMatch = FALSE; - while (pos < inputLength) { - UChar32 ic = input.char32At(pos); - if (ic == c) { - literalMatch = TRUE; - i += len; - pos += len; - if (i == affixLength) { - break; - } - c = trimmedAffix.char32At(i); - len = U16_LENGTH(c); - if (!PatternProps::isWhiteSpace(c)) { - break; - } - } else if (IS_BIDI_MARK(ic)) { - pos ++; // just skip over this input text - } else { - break; - } - } - - // Advance over run in pattern - i = skipPatternWhiteSpace(trimmedAffix, i); - - // Advance over run in input text - // Must see at least one white space char in input, - // unless we've already matched some characters literally. - int32_t s = pos; - pos = skipUWhiteSpace(input, pos); - if (pos == s && !literalMatch) { - return -1; - } - - // If we skip UWhiteSpace in the input text, we need to skip it in the pattern. - // Otherwise, the previous lines may have skipped over text (such as U+00A0) that - // is also in the trimmedAffix. - i = skipUWhiteSpace(trimmedAffix, i); - } else { - UBool match = FALSE; - while (pos < inputLength) { - UChar32 ic = input.char32At(pos); - if (!match && ic == c) { - i += len; - pos += len; - match = TRUE; - } else if (IS_BIDI_MARK(ic)) { - pos++; // just skip over this input text - } else { - break; - } - } - if (!match) { - return -1; - } - } - } +void DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) { + if (fields->properties->currencyPluralInfo.fPtr.isNull()) { + fields->properties->currencyPluralInfo.fPtr.adoptInstead(info.clone()); } else { - UBool match = FALSE; + *fields->properties->currencyPluralInfo.fPtr = info; // copy-assignment operator + } + touchNoError(); +} - affixSet = fStaticSets->fDashEquivalents; +UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const { + ErrorCode localStatus; + fields->formatter->getAffixImpl(true, false, result, localStatus); + return result; +} - if (affixCharLength == affixLength && affixSet->contains(affixChar)) { - pos = skipUWhiteSpaceAndMarks(input, pos); - UChar32 ic = input.char32At(pos); +void DecimalFormat::setPositivePrefix(const UnicodeString& newValue) { + if (newValue == fields->properties->positivePrefix) { return; } + fields->properties->positivePrefix = newValue; + touchNoError(); +} - if (affixSet->contains(ic)) { - pos += U16_LENGTH(ic); - pos = skipBidiMarks(input, pos); - return pos - start; - } - } +UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const { + ErrorCode localStatus; + fields->formatter->getAffixImpl(true, true, result, localStatus); + return result; +} - for (int32_t i = 0; i < affixLength; ) - { - //i = skipRuleWhiteSpace(trimmedAffix, i); - i = skipUWhiteSpace(trimmedAffix, i); - pos = skipUWhiteSpaceAndMarks(input, pos); +void DecimalFormat::setNegativePrefix(const UnicodeString& newValue) { + if (newValue == fields->properties->negativePrefix) { return; } + fields->properties->negativePrefix = newValue; + touchNoError(); +} - if (i >= affixLength || pos >= inputLength) { - break; - } +UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const { + ErrorCode localStatus; + fields->formatter->getAffixImpl(false, false, result, localStatus); + return result; +} - UChar32 c = trimmedAffix.char32At(i); - UChar32 ic = input.char32At(pos); +void DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) { + if (newValue == fields->properties->positiveSuffix) { return; } + fields->properties->positiveSuffix = newValue; + touchNoError(); +} - if (!equalWithSignCompatibility(ic, c)) { - return -1; - } +UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const { + ErrorCode localStatus; + fields->formatter->getAffixImpl(false, true, result, localStatus); + return result; +} - match = TRUE; - i += U16_LENGTH(c); - pos += U16_LENGTH(ic); - pos = skipBidiMarks(input, pos); - } +void DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) { + if (newValue == fields->properties->negativeSuffix) { return; } + fields->properties->negativeSuffix = newValue; + touchNoError(); +} - if (affixLength > 0 && ! match) { - return -1; - } - } - return pos - start; +UBool DecimalFormat::isSignAlwaysShown() const { + return fields->properties->signAlwaysShown; } -/** - * Skip over a run of zero or more Pattern_White_Space characters at - * pos in text. - */ -int32_t DecimalFormat::skipPatternWhiteSpace(const UnicodeString& text, int32_t pos) { - const UChar* s = text.getBuffer(); - return (int32_t)(PatternProps::skipWhiteSpace(s + pos, text.length() - pos) - s); +void DecimalFormat::setSignAlwaysShown(UBool value) { + if (UBOOL_TO_BOOL(value) == fields->properties->signAlwaysShown) { return; } + fields->properties->signAlwaysShown = value; + touchNoError(); } -/** - * Skip over a run of zero or more isUWhiteSpace() characters at pos - * in text. - */ -int32_t DecimalFormat::skipUWhiteSpace(const UnicodeString& text, int32_t pos) { - while (pos < text.length()) { - UChar32 c = text.char32At(pos); - if (!u_isUWhiteSpace(c)) { - break; - } - pos += U16_LENGTH(c); +int32_t DecimalFormat::getMultiplier(void) const { + if (fields->properties->multiplier != 1) { + return fields->properties->multiplier; + } else if (fields->properties->magnitudeMultiplier != 0) { + return static_cast(uprv_pow10(fields->properties->magnitudeMultiplier)); + } else { + return 1; } - return pos; } -/** - * Skip over a run of zero or more isUWhiteSpace() characters or bidi marks at pos - * in text. - */ -int32_t DecimalFormat::skipUWhiteSpaceAndMarks(const UnicodeString& text, int32_t pos) { - while (pos < text.length()) { - UChar32 c = text.char32At(pos); - if (!u_isUWhiteSpace(c) && !IS_BIDI_MARK(c)) { // u_isUWhiteSpace doesn't include LRM,RLM,ALM - break; - } - pos += U16_LENGTH(c); +void DecimalFormat::setMultiplier(int32_t multiplier) { + if (multiplier == 0) { + multiplier = 1; // one being the benign default value for a multiplier. } - return pos; -} -/** - * Skip over a run of zero or more bidi marks at pos in text. - */ -int32_t DecimalFormat::skipBidiMarks(const UnicodeString& text, int32_t pos) { - while (pos < text.length()) { - UChar c = text.charAt(pos); - if (!IS_BIDI_MARK(c)) { + // Try to convert to a magnitude multiplier first + int delta = 0; + int value = multiplier; + while (value != 1) { + delta++; + int temp = value / 10; + if (temp * 10 != value) { + delta = -1; break; } - pos++; - } - return pos; -} - -/** - * Return the length matched by the given affix, or -1 if none. - * @param affixPat pattern string - * @param input input text - * @param pos offset into input at which to begin matching - * @param type the currency type to parse against, LONG_NAME only or not. - * @param currency return value for parsed currency, for generic - * currency parsing mode, or null for normal parsing. In generic - * currency parsing mode, any currency is parsed, not just the - * currency that this formatter is set to. - * @return length of input that matches, or -1 if match failure - */ -int32_t DecimalFormat::compareComplexAffix(const UnicodeString& affixPat, - const UnicodeString& text, - int32_t pos, - int8_t type, - UChar* currency) const -{ - int32_t start = pos; - U_ASSERT(currency != NULL || fImpl->fMonetary); - - for (int32_t i=0; - i= 0; ) { - UChar32 c = affixPat.char32At(i); - i += U16_LENGTH(c); - - if (c == kQuote) { - U_ASSERT(i <= affixPat.length()); - c = affixPat.char32At(i); - i += U16_LENGTH(c); - - const UnicodeString* affix = NULL; - - switch (c) { - case kCurrencySign: { - // since the currency names in choice format is saved - // the same way as other currency names, - // do not need to do currency choice parsing here. - // the general currency parsing parse against all names, - // including names in choice format. - UBool intl = igetLocale().getName(); - ParsePosition ppos(pos); - UChar curr[4]; - UErrorCode ec = U_ZERO_ERROR; - // Delegate parse of display name => ISO code to Currency - uprv_parseCurrency(loc, text, ppos, type, curr, ec); - - // If parse succeeds, populate currency[0] - if (U_SUCCESS(ec) && ppos.getIndex() != pos) { - if (currency) { - u_strcpy(currency, curr); - } else { - // The formatter is currency-style but the client has not requested - // the value of the parsed currency. In this case, if that value does - // not match the formatter's current value, then the parse fails. - UChar effectiveCurr[4]; - getEffectiveCurrency(effectiveCurr, ec); - if ( U_FAILURE(ec) || u_strncmp(curr,effectiveCurr,4) != 0 ) { - pos = -1; - continue; - } - } - pos = ppos.getIndex(); - } else if (!isLenient()){ - pos = -1; - } - continue; - } - case kPatternPercent: - affix = &fImpl->getConstSymbol(DecimalFormatSymbols::kPercentSymbol); - break; - case kPatternPerMill: - affix = &fImpl->getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); - break; - case kPatternPlus: - affix = &fImpl->getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - break; - case kPatternMinus: - affix = &fImpl->getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - break; - default: - // fall through to affix!=0 test, which will fail - break; - } - - if (affix != NULL) { - pos = match(text, pos, *affix); - continue; - } - } - - pos = match(text, pos, c); - if (PatternProps::isWhiteSpace(c)) { - i = skipPatternWhiteSpace(affixPat, i); - } + value = temp; } - return pos - start; -} - -/** - * Match a single character at text[pos] and return the index of the - * next character upon success. Return -1 on failure. If - * ch is a Pattern_White_Space then match a run of white space in text. - */ -int32_t DecimalFormat::match(const UnicodeString& text, int32_t pos, UChar32 ch) { - if (PatternProps::isWhiteSpace(ch)) { - // Advance over run of white space in input text - // Must see at least one white space char in input - int32_t s = pos; - pos = skipPatternWhiteSpace(text, pos); - if (pos == s) { - return -1; - } - return pos; - } - return (pos >= 0 && text.char32At(pos) == ch) ? - (pos + U16_LENGTH(ch)) : -1; -} - -/** - * Match a string at text[pos] and return the index of the next - * character upon success. Return -1 on failure. Match a run of - * white space in str with a run of white space in text. - */ -int32_t DecimalFormat::match(const UnicodeString& text, int32_t pos, const UnicodeString& str) { - for (int32_t i=0; i= 0; ) { - UChar32 ch = str.char32At(i); - i += U16_LENGTH(ch); - if (PatternProps::isWhiteSpace(ch)) { - i = skipPatternWhiteSpace(str, i); - } - pos = match(text, pos, ch); - } - return pos; -} - -UBool DecimalFormat::matchSymbol(const UnicodeString &text, int32_t position, int32_t length, const UnicodeString &symbol, - UnicodeSet *sset, UChar32 schar) -{ - if (sset != NULL) { - return sset->contains(schar); - } - - return text.compare(position, length, symbol) == 0; -} - -UBool DecimalFormat::matchDecimal(UChar32 symbolChar, - UBool sawDecimal, UChar32 sawDecimalChar, - const UnicodeSet *sset, UChar32 schar) { - if(sawDecimal) { - return schar==sawDecimalChar; - } else if(schar==symbolChar) { - return TRUE; - } else if(sset!=NULL) { - return sset->contains(schar); - } else { - return FALSE; - } -} - -UBool DecimalFormat::matchGrouping(UChar32 groupingChar, - UBool sawGrouping, UChar32 sawGroupingChar, - const UnicodeSet *sset, - UChar32 /*decimalChar*/, const UnicodeSet *decimalSet, - UChar32 schar) { - if(sawGrouping) { - return schar==sawGroupingChar; // previously found - } else if(schar==groupingChar) { - return TRUE; // char from symbols - } else if(sset!=NULL) { - return sset->contains(schar) && // in groupingSet but... - ((decimalSet==NULL) || !decimalSet->contains(schar)); // Exclude decimalSet from groupingSet + if (delta != -1) { + fields->properties->magnitudeMultiplier = delta; + fields->properties->multiplier = 1; } else { - return FALSE; + fields->properties->magnitudeMultiplier = 0; + fields->properties->multiplier = multiplier; } + touchNoError(); } +int32_t DecimalFormat::getMultiplierScale() const { + return fields->properties->multiplierScale; +} +void DecimalFormat::setMultiplierScale(int32_t newValue) { + if (newValue == fields->properties->multiplierScale) { return; } + fields->properties->multiplierScale = newValue; + touchNoError(); +} -//------------------------------------------------------------------------------ -// Gets the pointer to the localized decimal format symbols +double DecimalFormat::getRoundingIncrement(void) const { + return fields->exportedProperties->roundingIncrement; +} -const DecimalFormatSymbols* -DecimalFormat::getDecimalFormatSymbols() const -{ - return &fImpl->getDecimalFormatSymbols(); +void DecimalFormat::setRoundingIncrement(double newValue) { + if (newValue == fields->properties->roundingIncrement) { return; } + fields->properties->roundingIncrement = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -// De-owning the current localized symbols and adopt the new symbols. +ERoundingMode DecimalFormat::getRoundingMode(void) const { + // UNumberFormatRoundingMode and ERoundingMode have the same values. + return static_cast(fields->exportedProperties->roundingMode.getNoError()); +} -void -DecimalFormat::adoptDecimalFormatSymbols(DecimalFormatSymbols* symbolsToAdopt) -{ - if (symbolsToAdopt == NULL) { - return; // do not allow caller to set fSymbols to NULL +void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) { + auto uRoundingMode = static_cast(roundingMode); + if (!fields->properties->roundingMode.isNull() && uRoundingMode == fields->properties->roundingMode.getNoError()) { + return; } - fImpl->adoptDecimalFormatSymbols(symbolsToAdopt); + NumberFormat::setMaximumIntegerDigits(roundingMode); // to set field for compatibility + fields->properties->roundingMode = uRoundingMode; + touchNoError(); } -//------------------------------------------------------------------------------ -// Setting the symbols is equlivalent to adopting a newly created localized -// symbols. -void -DecimalFormat::setDecimalFormatSymbols(const DecimalFormatSymbols& symbols) -{ - adoptDecimalFormatSymbols(new DecimalFormatSymbols(symbols)); +int32_t DecimalFormat::getFormatWidth(void) const { + return fields->properties->formatWidth; } - -const CurrencyPluralInfo* -DecimalFormat::getCurrencyPluralInfo(void) const -{ - return fCurrencyPluralInfo; +void DecimalFormat::setFormatWidth(int32_t width) { + if (width == fields->properties->formatWidth) { return; } + fields->properties->formatWidth = width; + touchNoError(); } - -void -DecimalFormat::adoptCurrencyPluralInfo(CurrencyPluralInfo* toAdopt) -{ - if (toAdopt != NULL) { - delete fCurrencyPluralInfo; - fCurrencyPluralInfo = toAdopt; - // re-set currency affix patterns and currency affixes. - if (fImpl->fMonetary) { - UErrorCode status = U_ZERO_ERROR; - if (fAffixPatternsForCurrency) { - deleteHashForAffixPattern(); - } - setupCurrencyAffixPatterns(status); - } +UnicodeString DecimalFormat::getPadCharacterString() const { + if (fields->properties->padString.isBogus()) { + // Readonly-alias the static string kFallbackPaddingString + return {TRUE, kFallbackPaddingString, -1}; + } else { + return fields->properties->padString; } } -void -DecimalFormat::setCurrencyPluralInfo(const CurrencyPluralInfo& info) -{ - adoptCurrencyPluralInfo(info.clone()); +void DecimalFormat::setPadCharacter(const UnicodeString& padChar) { + if (padChar == fields->properties->padString) { return; } + if (padChar.length() > 0) { + fields->properties->padString = UnicodeString(padChar.char32At(0)); + } else { + fields->properties->padString.setToBogus(); + } + touchNoError(); } - -//------------------------------------------------------------------------------ -// Gets the positive prefix of the number pattern. - -UnicodeString& -DecimalFormat::getPositivePrefix(UnicodeString& result) const -{ - return fImpl->getPositivePrefix(result); +EPadPosition DecimalFormat::getPadPosition(void) const { + if (fields->properties->padPosition.isNull()) { + return EPadPosition::kPadBeforePrefix; + } else { + // UNumberFormatPadPosition and EPadPosition have the same values. + return static_cast(fields->properties->padPosition.getNoError()); + } } -//------------------------------------------------------------------------------ -// Sets the positive prefix of the number pattern. - -void -DecimalFormat::setPositivePrefix(const UnicodeString& newValue) -{ - fImpl->setPositivePrefix(newValue); +void DecimalFormat::setPadPosition(EPadPosition padPos) { + auto uPadPos = static_cast(padPos); + if (!fields->properties->padPosition.isNull() && uPadPos == fields->properties->padPosition.getNoError()) { + return; + } + fields->properties->padPosition = uPadPos; + touchNoError(); } -//------------------------------------------------------------------------------ -// Gets the negative prefix of the number pattern. - -UnicodeString& -DecimalFormat::getNegativePrefix(UnicodeString& result) const -{ - return fImpl->getNegativePrefix(result); +UBool DecimalFormat::isScientificNotation(void) const { + return fields->properties->minimumExponentDigits != -1; } -//------------------------------------------------------------------------------ -// Gets the negative prefix of the number pattern. - -void -DecimalFormat::setNegativePrefix(const UnicodeString& newValue) -{ - fImpl->setNegativePrefix(newValue); +void DecimalFormat::setScientificNotation(UBool useScientific) { + int32_t minExp = useScientific ? 1 : -1; + if (fields->properties->minimumExponentDigits == minExp) { return; } + if (useScientific) { + fields->properties->minimumExponentDigits = 1; + } else { + fields->properties->minimumExponentDigits = -1; + } + touchNoError(); } -//------------------------------------------------------------------------------ -// Gets the positive suffix of the number pattern. - -UnicodeString& -DecimalFormat::getPositiveSuffix(UnicodeString& result) const -{ - return fImpl->getPositiveSuffix(result); +int8_t DecimalFormat::getMinimumExponentDigits(void) const { + return static_cast(fields->properties->minimumExponentDigits); } -//------------------------------------------------------------------------------ -// Sets the positive suffix of the number pattern. - -void -DecimalFormat::setPositiveSuffix(const UnicodeString& newValue) -{ - fImpl->setPositiveSuffix(newValue); +void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) { + if (minExpDig == fields->properties->minimumExponentDigits) { return; } + fields->properties->minimumExponentDigits = minExpDig; + touchNoError(); } -//------------------------------------------------------------------------------ -// Gets the negative suffix of the number pattern. - -UnicodeString& -DecimalFormat::getNegativeSuffix(UnicodeString& result) const -{ - return fImpl->getNegativeSuffix(result); +UBool DecimalFormat::isExponentSignAlwaysShown(void) const { + return fields->properties->exponentSignAlwaysShown; } -//------------------------------------------------------------------------------ -// Sets the negative suffix of the number pattern. - -void -DecimalFormat::setNegativeSuffix(const UnicodeString& newValue) -{ - fImpl->setNegativeSuffix(newValue); +void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) { + if (UBOOL_TO_BOOL(expSignAlways) == fields->properties->exponentSignAlwaysShown) { return; } + fields->properties->exponentSignAlwaysShown = expSignAlways; + touchNoError(); } -//------------------------------------------------------------------------------ -// Gets the multiplier of the number pattern. -// Multipliers are stored as decimal numbers (DigitLists) because that -// is the most convenient for muliplying or dividing the numbers to be formatted. -// A NULL multiplier implies one, and the scaling operations are skipped. - -int32_t -DecimalFormat::getMultiplier() const -{ - return fImpl->getMultiplier(); +int32_t DecimalFormat::getGroupingSize(void) const { + if (fields->properties->groupingSize < 0) { + return 0; + } + return fields->properties->groupingSize; } -//------------------------------------------------------------------------------ -// Sets the multiplier of the number pattern. -void -DecimalFormat::setMultiplier(int32_t newValue) -{ - fImpl->setMultiplier(newValue); -} - -/** - * Get the rounding increment. - * @return A positive rounding increment, or 0.0 if rounding - * is not in effect. - * @see #setRoundingIncrement - * @see #getRoundingMode - * @see #setRoundingMode - */ -double DecimalFormat::getRoundingIncrement() const { - return fImpl->getRoundingIncrement(); -} - -/** - * Set the rounding increment. This method also controls whether - * rounding is enabled. - * @param newValue A positive rounding increment, or 0.0 to disable rounding. - * Negative increments are equivalent to 0.0. - * @see #getRoundingIncrement - * @see #getRoundingMode - * @see #setRoundingMode - */ -void DecimalFormat::setRoundingIncrement(double newValue) { - fImpl->setRoundingIncrement(newValue); -} - -/** - * Get the rounding mode. - * @return A rounding mode - * @see #setRoundingIncrement - * @see #getRoundingIncrement - * @see #setRoundingMode - */ -DecimalFormat::ERoundingMode DecimalFormat::getRoundingMode() const { - return fImpl->getRoundingMode(); -} - -/** - * Set the rounding mode. This has no effect unless the rounding - * increment is greater than zero. - * @param roundingMode A rounding mode - * @see #setRoundingIncrement - * @see #getRoundingIncrement - * @see #getRoundingMode - */ -void DecimalFormat::setRoundingMode(ERoundingMode roundingMode) { - fImpl->setRoundingMode(roundingMode); -} - -/** - * Get the width to which the output of format() is padded. - * @return the format width, or zero if no padding is in effect - * @see #setFormatWidth - * @see #getPadCharacter - * @see #setPadCharacter - * @see #getPadPosition - * @see #setPadPosition - */ -int32_t DecimalFormat::getFormatWidth() const { - return fImpl->getFormatWidth(); -} - -/** - * Set the width to which the output of format() is padded. - * This method also controls whether padding is enabled. - * @param width the width to which to pad the result of - * format(), or zero to disable padding. A negative - * width is equivalent to 0. - * @see #getFormatWidth - * @see #getPadCharacter - * @see #setPadCharacter - * @see #getPadPosition - * @see #setPadPosition - */ -void DecimalFormat::setFormatWidth(int32_t width) { - int32_t formatWidth = (width > 0) ? width : 0; - fImpl->setFormatWidth(formatWidth); +void DecimalFormat::setGroupingSize(int32_t newValue) { + if (newValue == fields->properties->groupingSize) { return; } + fields->properties->groupingSize = newValue; + touchNoError(); } -UnicodeString DecimalFormat::getPadCharacterString() const { - return UnicodeString(fImpl->getPadCharacter()); +int32_t DecimalFormat::getSecondaryGroupingSize(void) const { + int grouping2 = fields->properties->secondaryGroupingSize; + if (grouping2 < 0) { + return 0; + } + return grouping2; } -void DecimalFormat::setPadCharacter(const UnicodeString &padChar) { - UChar32 pad; - if (padChar.length() > 0) { - pad = padChar.char32At(0); - } - else { - pad = kDefaultPad; - } - fImpl->setPadCharacter(pad); -} - -static DecimalFormat::EPadPosition fromPadPosition(DigitAffixesAndPadding::EPadPosition padPos) { - switch (padPos) { - case DigitAffixesAndPadding::kPadBeforePrefix: - return DecimalFormat::kPadBeforePrefix; - case DigitAffixesAndPadding::kPadAfterPrefix: - return DecimalFormat::kPadAfterPrefix; - case DigitAffixesAndPadding::kPadBeforeSuffix: - return DecimalFormat::kPadBeforeSuffix; - case DigitAffixesAndPadding::kPadAfterSuffix: - return DecimalFormat::kPadAfterSuffix; - default: - U_ASSERT(FALSE); - break; - } - return DecimalFormat::kPadBeforePrefix; -} - -/** - * Get the position at which padding will take place. This is the location - * at which padding will be inserted if the result of format() - * is shorter than the format width. - * @return the pad position, one of kPadBeforePrefix, - * kPadAfterPrefix, kPadBeforeSuffix, or - * kPadAfterSuffix. - * @see #setFormatWidth - * @see #getFormatWidth - * @see #setPadCharacter - * @see #getPadCharacter - * @see #setPadPosition - * @see #kPadBeforePrefix - * @see #kPadAfterPrefix - * @see #kPadBeforeSuffix - * @see #kPadAfterSuffix - */ -DecimalFormat::EPadPosition DecimalFormat::getPadPosition() const { - return fromPadPosition(fImpl->getPadPosition()); -} - -static DigitAffixesAndPadding::EPadPosition toPadPosition(DecimalFormat::EPadPosition padPos) { - switch (padPos) { - case DecimalFormat::kPadBeforePrefix: - return DigitAffixesAndPadding::kPadBeforePrefix; - case DecimalFormat::kPadAfterPrefix: - return DigitAffixesAndPadding::kPadAfterPrefix; - case DecimalFormat::kPadBeforeSuffix: - return DigitAffixesAndPadding::kPadBeforeSuffix; - case DecimalFormat::kPadAfterSuffix: - return DigitAffixesAndPadding::kPadAfterSuffix; - default: - U_ASSERT(FALSE); - break; - } - return DigitAffixesAndPadding::kPadBeforePrefix; -} - -/** - * NEW - * Set the position at which padding will take place. This is the location - * at which padding will be inserted if the result of format() - * is shorter than the format width. This has no effect unless padding is - * enabled. - * @param padPos the pad position, one of kPadBeforePrefix, - * kPadAfterPrefix, kPadBeforeSuffix, or - * kPadAfterSuffix. - * @see #setFormatWidth - * @see #getFormatWidth - * @see #setPadCharacter - * @see #getPadCharacter - * @see #getPadPosition - * @see #kPadBeforePrefix - * @see #kPadAfterPrefix - * @see #kPadBeforeSuffix - * @see #kPadAfterSuffix - */ -void DecimalFormat::setPadPosition(EPadPosition padPos) { - fImpl->setPadPosition(toPadPosition(padPos)); -} - -/** - * Return whether or not scientific notation is used. - * @return TRUE if this object formats and parses scientific notation - * @see #setScientificNotation - * @see #getMinimumExponentDigits - * @see #setMinimumExponentDigits - * @see #isExponentSignAlwaysShown - * @see #setExponentSignAlwaysShown - */ -UBool DecimalFormat::isScientificNotation() const { - return fImpl->isScientificNotation(); -} - -/** - * Set whether or not scientific notation is used. - * @param useScientific TRUE if this object formats and parses scientific - * notation - * @see #isScientificNotation - * @see #getMinimumExponentDigits - * @see #setMinimumExponentDigits - * @see #isExponentSignAlwaysShown - * @see #setExponentSignAlwaysShown - */ -void DecimalFormat::setScientificNotation(UBool useScientific) { - fImpl->setScientificNotation(useScientific); -} - -/** - * Return the minimum exponent digits that will be shown. - * @return the minimum exponent digits that will be shown - * @see #setScientificNotation - * @see #isScientificNotation - * @see #setMinimumExponentDigits - * @see #isExponentSignAlwaysShown - * @see #setExponentSignAlwaysShown - */ -int8_t DecimalFormat::getMinimumExponentDigits() const { - return fImpl->getMinimumExponentDigits(); -} - -/** - * Set the minimum exponent digits that will be shown. This has no - * effect unless scientific notation is in use. - * @param minExpDig a value >= 1 indicating the fewest exponent digits - * that will be shown. Values less than 1 will be treated as 1. - * @see #setScientificNotation - * @see #isScientificNotation - * @see #getMinimumExponentDigits - * @see #isExponentSignAlwaysShown - * @see #setExponentSignAlwaysShown - */ -void DecimalFormat::setMinimumExponentDigits(int8_t minExpDig) { - int32_t minExponentDigits = (int8_t)((minExpDig > 0) ? minExpDig : 1); - fImpl->setMinimumExponentDigits(minExponentDigits); -} - -/** - * Return whether the exponent sign is always shown. - * @return TRUE if the exponent is always prefixed with either the - * localized minus sign or the localized plus sign, false if only negative - * exponents are prefixed with the localized minus sign. - * @see #setScientificNotation - * @see #isScientificNotation - * @see #setMinimumExponentDigits - * @see #getMinimumExponentDigits - * @see #setExponentSignAlwaysShown - */ -UBool DecimalFormat::isExponentSignAlwaysShown() const { - return fImpl->isExponentSignAlwaysShown(); -} - -/** - * Set whether the exponent sign is always shown. This has no effect - * unless scientific notation is in use. - * @param expSignAlways TRUE if the exponent is always prefixed with either - * the localized minus sign or the localized plus sign, false if only - * negative exponents are prefixed with the localized minus sign. - * @see #setScientificNotation - * @see #isScientificNotation - * @see #setMinimumExponentDigits - * @see #getMinimumExponentDigits - * @see #isExponentSignAlwaysShown - */ -void DecimalFormat::setExponentSignAlwaysShown(UBool expSignAlways) { - fImpl->setExponentSignAlwaysShown(expSignAlways); +void DecimalFormat::setSecondaryGroupingSize(int32_t newValue) { + if (newValue == fields->properties->secondaryGroupingSize) { return; } + fields->properties->secondaryGroupingSize = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -// Gets the grouping size of the number pattern. For example, thousand or 10 -// thousand groupings. - -int32_t -DecimalFormat::getGroupingSize() const -{ - return fImpl->getGroupingSize(); +int32_t DecimalFormat::getMinimumGroupingDigits() const { + return fields->properties->minimumGroupingDigits; } -//------------------------------------------------------------------------------ -// Gets the grouping size of the number pattern. - -void -DecimalFormat::setGroupingSize(int32_t newValue) -{ - fImpl->setGroupingSize(newValue); +void DecimalFormat::setMinimumGroupingDigits(int32_t newValue) { + if (newValue == fields->properties->minimumGroupingDigits) { return; } + fields->properties->minimumGroupingDigits = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ - -int32_t -DecimalFormat::getSecondaryGroupingSize() const -{ - return fImpl->getSecondaryGroupingSize(); +UBool DecimalFormat::isDecimalSeparatorAlwaysShown(void) const { + return fields->properties->decimalSeparatorAlwaysShown; } -//------------------------------------------------------------------------------ - -void -DecimalFormat::setSecondaryGroupingSize(int32_t newValue) -{ - fImpl->setSecondaryGroupingSize(newValue); +void DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) { + if (UBOOL_TO_BOOL(newValue) == fields->properties->decimalSeparatorAlwaysShown) { return; } + fields->properties->decimalSeparatorAlwaysShown = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ - -int32_t -DecimalFormat::getMinimumGroupingDigits() const -{ - return fImpl->getMinimumGroupingDigits(); +UBool DecimalFormat::isDecimalPatternMatchRequired(void) const { + return fields->properties->decimalPatternMatchRequired; } -//------------------------------------------------------------------------------ - -void -DecimalFormat::setMinimumGroupingDigits(int32_t newValue) -{ - fImpl->setMinimumGroupingDigits(newValue); +void DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) { + if (UBOOL_TO_BOOL(newValue) == fields->properties->decimalPatternMatchRequired) { return; } + fields->properties->decimalPatternMatchRequired = newValue; + touchNoError(); } -//------------------------------------------------------------------------------ -// Checks if to show the decimal separator. - -UBool -DecimalFormat::isDecimalSeparatorAlwaysShown() const -{ - return fImpl->isDecimalSeparatorAlwaysShown(); +UBool DecimalFormat::isParseNoExponent() const { + return fields->properties->parseNoExponent; } -//------------------------------------------------------------------------------ -// Sets to always show the decimal separator. - -void -DecimalFormat::setDecimalSeparatorAlwaysShown(UBool newValue) -{ - fImpl->setDecimalSeparatorAlwaysShown(newValue); +void DecimalFormat::setParseNoExponent(UBool value) { + if (UBOOL_TO_BOOL(value) == fields->properties->parseNoExponent) { return; } + fields->properties->parseNoExponent = value; + touchNoError(); } -//------------------------------------------------------------------------------ -// Checks if decimal point pattern match is required -UBool -DecimalFormat::isDecimalPatternMatchRequired(void) const -{ - return static_cast(fBoolFlags.contains(UNUM_PARSE_DECIMAL_MARK_REQUIRED)); +UBool DecimalFormat::isParseCaseSensitive() const { + return fields->properties->parseCaseSensitive; } -//------------------------------------------------------------------------------ -// Checks if decimal point pattern match is required - -void -DecimalFormat::setDecimalPatternMatchRequired(UBool newValue) -{ - fBoolFlags.set(UNUM_PARSE_DECIMAL_MARK_REQUIRED, newValue); +void DecimalFormat::setParseCaseSensitive(UBool value) { + if (UBOOL_TO_BOOL(value) == fields->properties->parseCaseSensitive) { return; } + fields->properties->parseCaseSensitive = value; + touchNoError(); } - -//------------------------------------------------------------------------------ -// Emits the pattern of this DecimalFormat instance. - -UnicodeString& -DecimalFormat::toPattern(UnicodeString& result) const -{ - return fImpl->toPattern(result); +UBool DecimalFormat::isFormatFailIfMoreThanMaxDigits() const { + return fields->properties->formatFailIfMoreThanMaxDigits; } -//------------------------------------------------------------------------------ -// Emits the localized pattern this DecimalFormat instance. - -UnicodeString& -DecimalFormat::toLocalizedPattern(UnicodeString& result) const -{ - // toLocalizedPattern is deprecated, so we just make it the same as - // toPattern. - return fImpl->toPattern(result); +void DecimalFormat::setFormatFailIfMoreThanMaxDigits(UBool value) { + if (UBOOL_TO_BOOL(value) == fields->properties->formatFailIfMoreThanMaxDigits) { return; } + fields->properties->formatFailIfMoreThanMaxDigits = value; + touchNoError(); } -//------------------------------------------------------------------------------ - -void -DecimalFormat::applyPattern(const UnicodeString& pattern, UErrorCode& status) -{ - if (pattern.indexOf(kCurrencySign) != -1) { - handleCurrencySignInPattern(status); +UnicodeString& DecimalFormat::toPattern(UnicodeString& result) const { + // Pull some properties from exportedProperties and others from properties + // to keep affix patterns intact. In particular, pull rounding properties + // so that CurrencyUsage is reflected properly. + // TODO: Consider putting this logic in number_patternstring.cpp instead. + ErrorCode localStatus; + DecimalFormatProperties tprops(*fields->properties); + bool useCurrency = ((!tprops.currency.isNull()) || !tprops.currencyPluralInfo.fPtr.isNull() || + !tprops.currencyUsage.isNull() || AffixUtils::hasCurrencySymbols( + tprops.positivePrefixPattern, localStatus) || AffixUtils::hasCurrencySymbols( + tprops.positiveSuffixPattern, localStatus) || AffixUtils::hasCurrencySymbols( + tprops.negativePrefixPattern, localStatus) || AffixUtils::hasCurrencySymbols( + tprops.negativeSuffixPattern, localStatus)); + if (useCurrency) { + tprops.minimumFractionDigits = fields->exportedProperties->minimumFractionDigits; + tprops.maximumFractionDigits = fields->exportedProperties->maximumFractionDigits; + tprops.roundingIncrement = fields->exportedProperties->roundingIncrement; } - fImpl->applyPattern(pattern, status); + result = PatternStringUtils::propertiesToPatternString(tprops, localStatus); + return result; } -//------------------------------------------------------------------------------ +UnicodeString& DecimalFormat::toLocalizedPattern(UnicodeString& result) const { + ErrorCode localStatus; + result = toPattern(result); + result = PatternStringUtils::convertLocalized(result, *fields->symbols, true, localStatus); + return result; +} -void -DecimalFormat::applyPattern(const UnicodeString& pattern, - UParseError& parseError, - UErrorCode& status) -{ - if (pattern.indexOf(kCurrencySign) != -1) { - handleCurrencySignInPattern(status); - } - fImpl->applyPattern(pattern, parseError, status); +void DecimalFormat::applyPattern(const UnicodeString& pattern, UParseError&, UErrorCode& status) { + // TODO: What is parseError for? + applyPattern(pattern, status); } -//------------------------------------------------------------------------------ -void -DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, UErrorCode& status) -{ - if (pattern.indexOf(kCurrencySign) != -1) { - handleCurrencySignInPattern(status); - } - fImpl->applyLocalizedPattern(pattern, status); +void DecimalFormat::applyPattern(const UnicodeString& pattern, UErrorCode& status) { + setPropertiesFromPattern(pattern, IGNORE_ROUNDING_NEVER, status); + touch(status); } -//------------------------------------------------------------------------------ +void DecimalFormat::applyLocalizedPattern(const UnicodeString& localizedPattern, UParseError&, + UErrorCode& status) { + // TODO: What is parseError for? + applyLocalizedPattern(localizedPattern, status); +} -void -DecimalFormat::applyLocalizedPattern(const UnicodeString& pattern, - UParseError& parseError, - UErrorCode& status) -{ - if (pattern.indexOf(kCurrencySign) != -1) { - handleCurrencySignInPattern(status); +void DecimalFormat::applyLocalizedPattern(const UnicodeString& localizedPattern, UErrorCode& status) { + if (U_SUCCESS(status)) { + UnicodeString pattern = PatternStringUtils::convertLocalized( + localizedPattern, *fields->symbols, false, status); + applyPattern(pattern, status); } - fImpl->applyLocalizedPattern(pattern, parseError, status); } -//------------------------------------------------------------------------------ - -/** - * Sets the maximum number of digits allowed in the integer portion of a - * number. - * @see NumberFormat#setMaximumIntegerDigits - */ void DecimalFormat::setMaximumIntegerDigits(int32_t newValue) { - newValue = _min(newValue, gDefaultMaxIntegerDigits); - NumberFormat::setMaximumIntegerDigits(newValue); - fImpl->updatePrecision(); + if (newValue == fields->properties->maximumIntegerDigits) { return; } + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t min = fields->properties->minimumIntegerDigits; + if (min >= 0 && min > newValue) { + fields->properties->minimumIntegerDigits = newValue; + } + fields->properties->maximumIntegerDigits = newValue; + touchNoError(); } -/** - * Sets the minimum number of digits allowed in the integer portion of a - * number. This override limits the integer digit count to 309. - * @see NumberFormat#setMinimumIntegerDigits - */ void DecimalFormat::setMinimumIntegerDigits(int32_t newValue) { - newValue = _min(newValue, kDoubleIntegerDigits); - NumberFormat::setMinimumIntegerDigits(newValue); - fImpl->updatePrecision(); + if (newValue == fields->properties->minimumIntegerDigits) { return; } + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t max = fields->properties->maximumIntegerDigits; + if (max >= 0 && max < newValue) { + fields->properties->maximumIntegerDigits = newValue; + } + fields->properties->minimumIntegerDigits = newValue; + touchNoError(); } -/** - * Sets the maximum number of digits allowed in the fraction portion of a - * number. This override limits the fraction digit count to 340. - * @see NumberFormat#setMaximumFractionDigits - */ void DecimalFormat::setMaximumFractionDigits(int32_t newValue) { - newValue = _min(newValue, kDoubleFractionDigits); - NumberFormat::setMaximumFractionDigits(newValue); - fImpl->updatePrecision(); + if (newValue == fields->properties->maximumFractionDigits) { return; } + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t min = fields->properties->minimumFractionDigits; + if (min >= 0 && min > newValue) { + fields->properties->minimumFractionDigits = newValue; + } + fields->properties->maximumFractionDigits = newValue; + touchNoError(); } -/** - * Sets the minimum number of digits allowed in the fraction portion of a - * number. This override limits the fraction digit count to 340. - * @see NumberFormat#setMinimumFractionDigits - */ void DecimalFormat::setMinimumFractionDigits(int32_t newValue) { - newValue = _min(newValue, kDoubleFractionDigits); - NumberFormat::setMinimumFractionDigits(newValue); - fImpl->updatePrecision(); + if (newValue == fields->properties->minimumFractionDigits) { return; } + // For backwards compatibility, conflicting min/max need to keep the most recent setting. + int32_t max = fields->properties->maximumFractionDigits; + if (max >= 0 && max < newValue) { + fields->properties->maximumFractionDigits = newValue; + } + fields->properties->minimumFractionDigits = newValue; + touchNoError(); } int32_t DecimalFormat::getMinimumSignificantDigits() const { - return fImpl->getMinimumSignificantDigits(); + return fields->exportedProperties->minimumSignificantDigits; } int32_t DecimalFormat::getMaximumSignificantDigits() const { - return fImpl->getMaximumSignificantDigits(); + return fields->exportedProperties->maximumSignificantDigits; } -void DecimalFormat::setMinimumSignificantDigits(int32_t min) { - if (min < 1) { - min = 1; +void DecimalFormat::setMinimumSignificantDigits(int32_t value) { + if (value == fields->properties->minimumSignificantDigits) { return; } + int32_t max = fields->properties->maximumSignificantDigits; + if (max >= 0 && max < value) { + fields->properties->maximumSignificantDigits = value; } - // pin max sig dig to >= min - int32_t max = _max(fImpl->fMaxSigDigits, min); - fImpl->setMinMaxSignificantDigits(min, max); + fields->properties->minimumSignificantDigits = value; + touchNoError(); } -void DecimalFormat::setMaximumSignificantDigits(int32_t max) { - if (max < 1) { - max = 1; +void DecimalFormat::setMaximumSignificantDigits(int32_t value) { + if (value == fields->properties->maximumSignificantDigits) { return; } + int32_t min = fields->properties->minimumSignificantDigits; + if (min >= 0 && min > value) { + fields->properties->minimumSignificantDigits = value; } - // pin min sig dig to 1..max - U_ASSERT(fImpl->fMinSigDigits >= 1); - int32_t min = _min(fImpl->fMinSigDigits, max); - fImpl->setMinMaxSignificantDigits(min, max); + fields->properties->maximumSignificantDigits = value; + touchNoError(); } UBool DecimalFormat::areSignificantDigitsUsed() const { - return fImpl->areSignificantDigitsUsed(); + return fields->properties->minimumSignificantDigits != -1 || fields->properties->maximumSignificantDigits != -1; } void DecimalFormat::setSignificantDigitsUsed(UBool useSignificantDigits) { - fImpl->setSignificantDigitsUsed(useSignificantDigits); -} - -void DecimalFormat::setCurrency(const UChar* theCurrency, UErrorCode& ec) { - // set the currency before compute affixes to get the right currency names - NumberFormat::setCurrency(theCurrency, ec); - fImpl->updateCurrency(ec); -} - -void DecimalFormat::setCurrencyUsage(UCurrencyUsage newContext, UErrorCode* ec){ - fImpl->setCurrencyUsage(newContext, *ec); -} - -UCurrencyUsage DecimalFormat::getCurrencyUsage() const { - return fImpl->getCurrencyUsage(); -} - -// Deprecated variant with no UErrorCode parameter -void DecimalFormat::setCurrency(const UChar* theCurrency) { - UErrorCode ec = U_ZERO_ERROR; - setCurrency(theCurrency, ec); + // These are the default values from the old implementation. + int32_t minSig = useSignificantDigits ? 1 : -1; + int32_t maxSig = useSignificantDigits ? 6 : -1; + if (fields->properties->minimumSignificantDigits == minSig && + fields->properties->maximumSignificantDigits == maxSig) { + return; + } + fields->properties->minimumSignificantDigits = minSig; + fields->properties->maximumSignificantDigits = maxSig; + touchNoError(); } -void DecimalFormat::getEffectiveCurrency(UChar* result, UErrorCode& ec) const { - if (fImpl->fSymbols == NULL) { - ec = U_MEMORY_ALLOCATION_ERROR; +void DecimalFormat::setCurrency(const char16_t* theCurrency, UErrorCode& ec) { + CurrencyUnit currencyUnit(theCurrency, ec); + if (U_FAILURE(ec)) { return; } + if (!fields->properties->currency.isNull() && fields->properties->currency.getNoError() == currencyUnit) { return; } - ec = U_ZERO_ERROR; - const UChar* c = getCurrency(); - if (*c == 0) { - const UnicodeString &intl = - fImpl->getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); - c = intl.getBuffer(); // ok for intl to go out of scope - } - u_strncpy(result, c, 3); - result[3] = 0; + NumberFormat::setCurrency(theCurrency, ec); // to set field for compatibility + fields->properties->currency = currencyUnit; + // TODO: Set values in fields->symbols, too? + touchNoError(); } -Hashtable* -DecimalFormat::initHashForAffixPattern(UErrorCode& status) { - if ( U_FAILURE(status) ) { - return NULL; - } - Hashtable* hTable; - if ( (hTable = new Hashtable(TRUE, status)) == NULL ) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - if ( U_FAILURE(status) ) { - delete hTable; - return NULL; - } - hTable->setValueComparator(decimfmtAffixPatternValueComparator); - return hTable; +void DecimalFormat::setCurrency(const char16_t* theCurrency) { + ErrorCode localStatus; + setCurrency(theCurrency, localStatus); } -void -DecimalFormat::deleteHashForAffixPattern() -{ - if ( fAffixPatternsForCurrency == NULL ) { +void DecimalFormat::setCurrencyUsage(UCurrencyUsage newUsage, UErrorCode* ec) { + if (U_FAILURE(*ec)) { return; } - int32_t pos = UHASH_FIRST; - const UHashElement* element = NULL; - while ( (element = fAffixPatternsForCurrency->nextElement(pos)) != NULL ) { - const UHashTok valueTok = element->value; - const AffixPatternsForCurrency* value = (AffixPatternsForCurrency*)valueTok.pointer; - delete value; + if (!fields->properties->currencyUsage.isNull() && newUsage == fields->properties->currencyUsage.getNoError()) { + return; } - delete fAffixPatternsForCurrency; - fAffixPatternsForCurrency = NULL; + fields->properties->currencyUsage = newUsage; + touch(*ec); } - -void -DecimalFormat::copyHashForAffixPattern(const Hashtable* source, - Hashtable* target, - UErrorCode& status) { - if ( U_FAILURE(status) ) { - return; - } - int32_t pos = UHASH_FIRST; - const UHashElement* element = NULL; - if ( source ) { - while ( (element = source->nextElement(pos)) != NULL ) { - const UHashTok keyTok = element->key; - const UnicodeString* key = (UnicodeString*)keyTok.pointer; - const UHashTok valueTok = element->value; - const AffixPatternsForCurrency* value = (AffixPatternsForCurrency*)valueTok.pointer; - AffixPatternsForCurrency* copy = new AffixPatternsForCurrency( - value->negPrefixPatternForCurrency, - value->negSuffixPatternForCurrency, - value->posPrefixPatternForCurrency, - value->posSuffixPatternForCurrency, - value->patternType); - target->put(UnicodeString(*key), copy, status); - if ( U_FAILURE(status) ) { - return; - } - } +UCurrencyUsage DecimalFormat::getCurrencyUsage() const { + // CurrencyUsage is not exported, so we have to get it from the input property bag. + // TODO: Should we export CurrencyUsage instead? + if (fields->properties->currencyUsage.isNull()) { + return UCURR_USAGE_STANDARD; } + return fields->properties->currencyUsage.getNoError(); } void -DecimalFormat::setGroupingUsed(UBool newValue) { - NumberFormat::setGroupingUsed(newValue); - fImpl->updateGrouping(); +DecimalFormat::formatToDecimalQuantity(double number, DecimalQuantity& output, UErrorCode& status) const { + fields->formatter->formatDouble(number, status).getDecimalQuantity(output, status); } -void -DecimalFormat::setParseIntegerOnly(UBool newValue) { - NumberFormat::setParseIntegerOnly(newValue); +void DecimalFormat::formatToDecimalQuantity(const Formattable& number, DecimalQuantity& output, + UErrorCode& status) const { + UFormattedNumberData obj; + number.populateDecimalQuantity(obj.quantity, status); + fields->formatter->formatImpl(&obj, status); + output = std::move(obj.quantity); } -void -DecimalFormat::setContext(UDisplayContext value, UErrorCode& status) { - NumberFormat::setContext(value, status); +const number::LocalizedNumberFormatter& DecimalFormat::toNumberFormatter() const { + return *fields->formatter; } -DecimalFormat& DecimalFormat::setAttribute( UNumberFormatAttribute attr, - int32_t newValue, - UErrorCode &status) { - if(U_FAILURE(status)) return *this; - - switch(attr) { - case UNUM_LENIENT_PARSE: - setLenient(newValue!=0); - break; - - case UNUM_PARSE_INT_ONLY: - setParseIntegerOnly(newValue!=0); - break; - - case UNUM_GROUPING_USED: - setGroupingUsed(newValue!=0); - break; - - case UNUM_DECIMAL_ALWAYS_SHOWN: - setDecimalSeparatorAlwaysShown(newValue!=0); - break; - - case UNUM_MAX_INTEGER_DIGITS: - setMaximumIntegerDigits(newValue); - break; - - case UNUM_MIN_INTEGER_DIGITS: - setMinimumIntegerDigits(newValue); - break; - - case UNUM_INTEGER_DIGITS: - setMinimumIntegerDigits(newValue); - setMaximumIntegerDigits(newValue); - break; - - case UNUM_MAX_FRACTION_DIGITS: - setMaximumFractionDigits(newValue); - break; - - case UNUM_MIN_FRACTION_DIGITS: - setMinimumFractionDigits(newValue); - break; - - case UNUM_FRACTION_DIGITS: - setMinimumFractionDigits(newValue); - setMaximumFractionDigits(newValue); - break; - - case UNUM_SIGNIFICANT_DIGITS_USED: - setSignificantDigitsUsed(newValue!=0); - break; - - case UNUM_MAX_SIGNIFICANT_DIGITS: - setMaximumSignificantDigits(newValue); - break; - - case UNUM_MIN_SIGNIFICANT_DIGITS: - setMinimumSignificantDigits(newValue); - break; - - case UNUM_MULTIPLIER: - setMultiplier(newValue); - break; - - case UNUM_GROUPING_SIZE: - setGroupingSize(newValue); - break; - - case UNUM_ROUNDING_MODE: - setRoundingMode((DecimalFormat::ERoundingMode)newValue); - break; - - case UNUM_FORMAT_WIDTH: - setFormatWidth(newValue); - break; - - case UNUM_PADDING_POSITION: - /** The position at which padding will take place. */ - setPadPosition((DecimalFormat::EPadPosition)newValue); - break; - - case UNUM_SECONDARY_GROUPING_SIZE: - setSecondaryGroupingSize(newValue); - break; - -#if UCONFIG_HAVE_PARSEALLINPUT - case UNUM_PARSE_ALL_INPUT: - setParseAllInput((UNumberFormatAttributeValue)newValue); - break; -#endif +/** Rebuilds the formatter object from the property bag. */ +void DecimalFormat::touch(UErrorCode& status) { + if (fields->exportedProperties == nullptr) { + // fields->exportedProperties is null only when the formatter is not ready yet. + // The only time when this happens is during legacy deserialization. + return; + } - /* These are stored in fBoolFlags */ - case UNUM_PARSE_NO_EXPONENT: - case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: - case UNUM_PARSE_DECIMAL_MARK_REQUIRED: - if(!fBoolFlags.isValidValue(newValue)) { - status = U_ILLEGAL_ARGUMENT_ERROR; - } else { - if (attr == UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS) { - fImpl->setFailIfMoreThanMaxDigits((UBool) newValue); - } - fBoolFlags.set(attr, newValue); - } - break; + // In C++, fields->symbols is the source of truth for the locale. + Locale locale = fields->symbols->getLocale(); - case UNUM_SCALE: - fImpl->setScale(newValue); - break; + // Note: The formatter is relatively cheap to create, and we need it to populate fields->exportedProperties, + // so automatically compute it here. The parser is a bit more expensive and is not needed until the + // parse method is called, so defer that until needed. + // TODO: Only update the pieces that changed instead of re-computing the whole formatter? + fields->formatter.adoptInstead( + new LocalizedNumberFormatter( + NumberPropertyMapper::create( + *fields->properties, *fields->symbols, fields->warehouse, *fields->exportedProperties, status).locale( + locale))); - case UNUM_CURRENCY_USAGE: - setCurrencyUsage((UCurrencyUsage)newValue, &status); - break; + // Do this after fields->exportedProperties are set up + setupFastFormat(); - case UNUM_MINIMUM_GROUPING_DIGITS: - setMinimumGroupingDigits(newValue); - break; + // Delete the parsers if they were made previously + delete fields->atomicParser.exchange(nullptr); + delete fields->atomicCurrencyParser.exchange(nullptr); - default: - status = U_UNSUPPORTED_ERROR; - break; - } - return *this; + // In order for the getters to work, we need to populate some fields in NumberFormat. + NumberFormat::setCurrency(fields->exportedProperties->currency.get(status).getISOCurrency(), status); + NumberFormat::setMaximumIntegerDigits(fields->exportedProperties->maximumIntegerDigits); + NumberFormat::setMinimumIntegerDigits(fields->exportedProperties->minimumIntegerDigits); + NumberFormat::setMaximumFractionDigits(fields->exportedProperties->maximumFractionDigits); + NumberFormat::setMinimumFractionDigits(fields->exportedProperties->minimumFractionDigits); + // fImpl->properties, not fields->exportedProperties, since this information comes from the pattern: + NumberFormat::setGroupingUsed(fields->properties->groupingUsed); } -int32_t DecimalFormat::getAttribute( UNumberFormatAttribute attr, - UErrorCode &status ) const { - if(U_FAILURE(status)) return -1; - switch(attr) { - case UNUM_LENIENT_PARSE: - return isLenient(); - - case UNUM_PARSE_INT_ONLY: - return isParseIntegerOnly(); - - case UNUM_GROUPING_USED: - return isGroupingUsed(); - - case UNUM_DECIMAL_ALWAYS_SHOWN: - return isDecimalSeparatorAlwaysShown(); +void DecimalFormat::touchNoError() { + UErrorCode localStatus = U_ZERO_ERROR; + touch(localStatus); +} - case UNUM_MAX_INTEGER_DIGITS: - return getMaximumIntegerDigits(); +void DecimalFormat::setPropertiesFromPattern(const UnicodeString& pattern, int32_t ignoreRounding, + UErrorCode& status) { + if (U_SUCCESS(status)) { + // Cast workaround to get around putting the enum in the public header file + auto actualIgnoreRounding = static_cast(ignoreRounding); + PatternParser::parseToExistingProperties(pattern, *fields->properties, actualIgnoreRounding, status); + } +} - case UNUM_MIN_INTEGER_DIGITS: - return getMinimumIntegerDigits(); +const numparse::impl::NumberParserImpl* DecimalFormat::getParser(UErrorCode& status) const { + if (U_FAILURE(status)) { return nullptr; } - case UNUM_INTEGER_DIGITS: - // TBD: what should this return? - return getMinimumIntegerDigits(); + // First try to get the pre-computed parser + auto* ptr = fields->atomicParser.load(); + if (ptr != nullptr) { + return ptr; + } - case UNUM_MAX_FRACTION_DIGITS: - return getMaximumFractionDigits(); + // Try computing the parser on our own + auto* temp = NumberParserImpl::createParserFromProperties(*fields->properties, *fields->symbols, false, status); + if (temp == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + // although we may still dereference, call sites should be guarded + } - case UNUM_MIN_FRACTION_DIGITS: - return getMinimumFractionDigits(); + // Note: ptr starts as nullptr; during compare_exchange, it is set to what is actually stored in the + // atomic if another thread beat us to computing the parser object. + auto* nonConstThis = const_cast(this); + if (!nonConstThis->fields->atomicParser.compare_exchange_strong(ptr, temp)) { + // Another thread beat us to computing the parser + delete temp; + return ptr; + } else { + // Our copy of the parser got stored in the atomic + return temp; + } +} - case UNUM_FRACTION_DIGITS: - // TBD: what should this return? - return getMinimumFractionDigits(); +const numparse::impl::NumberParserImpl* DecimalFormat::getCurrencyParser(UErrorCode& status) const { + if (U_FAILURE(status)) { return nullptr; } - case UNUM_SIGNIFICANT_DIGITS_USED: - return areSignificantDigitsUsed(); + // First try to get the pre-computed parser + auto* ptr = fields->atomicCurrencyParser.load(); + if (ptr != nullptr) { + return ptr; + } - case UNUM_MAX_SIGNIFICANT_DIGITS: - return getMaximumSignificantDigits(); + // Try computing the parser on our own + auto* temp = NumberParserImpl::createParserFromProperties(*fields->properties, *fields->symbols, true, status); + if (temp == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + // although we may still dereference, call sites should be guarded + } - case UNUM_MIN_SIGNIFICANT_DIGITS: - return getMinimumSignificantDigits(); + // Note: ptr starts as nullptr; during compare_exchange, it is set to what is actually stored in the + // atomic if another thread beat us to computing the parser object. + auto* nonConstThis = const_cast(this); + if (!nonConstThis->fields->atomicCurrencyParser.compare_exchange_strong(ptr, temp)) { + // Another thread beat us to computing the parser + delete temp; + return ptr; + } else { + // Our copy of the parser got stored in the atomic + return temp; + } +} - case UNUM_MULTIPLIER: - return getMultiplier(); +void +DecimalFormat::fieldPositionHelper(const number::FormattedNumber& formatted, FieldPosition& fieldPosition, + int32_t offset, UErrorCode& status) { + // always return first occurrence: + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + bool found = formatted.nextFieldPosition(fieldPosition, status); + if (found && offset != 0) { + FieldPositionOnlyHandler fpoh(fieldPosition); + fpoh.shiftLast(offset); + } +} - case UNUM_GROUPING_SIZE: - return getGroupingSize(); +void +DecimalFormat::fieldPositionIteratorHelper(const number::FormattedNumber& formatted, FieldPositionIterator* fpi, + int32_t offset, UErrorCode& status) { + if (fpi != nullptr) { + FieldPositionIteratorHandler fpih(fpi, status); + fpih.setShift(offset); + formatted.getAllFieldPositionsImpl(fpih, status); + } +} - case UNUM_ROUNDING_MODE: - return getRoundingMode(); +// To debug fast-format, change void(x) to printf(x) +#define trace(x) void(x) - case UNUM_FORMAT_WIDTH: - return getFormatWidth(); +void DecimalFormat::setupFastFormat() { + // Check the majority of properties: + if (!fields->properties->equalsDefaultExceptFastFormat()) { + trace("no fast format: equality\n"); + fields->canUseFastFormat = false; + return; + } - case UNUM_PADDING_POSITION: - return getPadPosition(); + // Now check the remaining properties. + // Nontrivial affixes: + UBool trivialPP = fields->properties->positivePrefixPattern.isEmpty(); + UBool trivialPS = fields->properties->positiveSuffixPattern.isEmpty(); + UBool trivialNP = fields->properties->negativePrefixPattern.isBogus() || ( + fields->properties->negativePrefixPattern.length() == 1 && + fields->properties->negativePrefixPattern.charAt(0) == u'-'); + UBool trivialNS = fields->properties->negativeSuffixPattern.isEmpty(); + if (!trivialPP || !trivialPS || !trivialNP || !trivialNS) { + trace("no fast format: affixes\n"); + fields->canUseFastFormat = false; + return; + } - case UNUM_SECONDARY_GROUPING_SIZE: - return getSecondaryGroupingSize(); + // Grouping (secondary grouping is forbidden in equalsDefaultExceptFastFormat): + bool groupingUsed = fields->properties->groupingUsed; + int32_t groupingSize = fields->properties->groupingSize; + bool unusualGroupingSize = groupingSize > 0 && groupingSize != 3; + const UnicodeString& groupingString = fields->symbols->getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); + if (groupingUsed && (unusualGroupingSize || groupingString.length() != 1)) { + trace("no fast format: grouping\n"); + fields->canUseFastFormat = false; + return; + } - /* These are stored in fBoolFlags */ - case UNUM_PARSE_NO_EXPONENT: - case UNUM_FORMAT_FAIL_IF_MORE_THAN_MAX_DIGITS: - case UNUM_PARSE_DECIMAL_MARK_REQUIRED: - return fBoolFlags.get(attr); + // Integer length: + int32_t minInt = fields->exportedProperties->minimumIntegerDigits; + int32_t maxInt = fields->exportedProperties->maximumIntegerDigits; + // Fastpath supports up to only 10 digits (length of INT32_MIN) + if (minInt > 10) { + trace("no fast format: integer\n"); + fields->canUseFastFormat = false; + return; + } - case UNUM_SCALE: - return fImpl->fScale; + // Fraction length (no fraction part allowed in fast path): + int32_t minFrac = fields->exportedProperties->minimumFractionDigits; + if (minFrac > 0) { + trace("no fast format: fraction\n"); + fields->canUseFastFormat = false; + return; + } - case UNUM_CURRENCY_USAGE: - return fImpl->getCurrencyUsage(); + // Other symbols: + const UnicodeString& minusSignString = fields->symbols->getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); + UChar32 codePointZero = fields->symbols->getCodePointZero(); + if (minusSignString.length() != 1 || U16_LENGTH(codePointZero) != 1) { + trace("no fast format: symbols\n"); + fields->canUseFastFormat = false; + return; + } - case UNUM_MINIMUM_GROUPING_DIGITS: - return getMinimumGroupingDigits(); + // Good to go! + trace("can use fast format!\n"); + fields->canUseFastFormat = true; + fields->fastData.cpZero = static_cast(codePointZero); + fields->fastData.cpGroupingSeparator = groupingUsed && groupingSize == 3 ? groupingString.charAt(0) : 0; + fields->fastData.cpMinusSign = minusSignString.charAt(0); + fields->fastData.minInt = (minInt < 0 || minInt > 127) ? 0 : static_cast(minInt); + fields->fastData.maxInt = (maxInt < 0 || maxInt > 127) ? 127 : static_cast(maxInt); +} - default: - status = U_UNSUPPORTED_ERROR; - break; - } +bool DecimalFormat::fastFormatDouble(double input, UnicodeString& output) const { + if (!fields->canUseFastFormat) { + return false; + } + if (std::isnan(input) + || std::trunc(input) != input + || input <= INT32_MIN + || input > INT32_MAX) { + return false; + } + doFastFormatInt32(static_cast(input), std::signbit(input), output); + return true; +} - return -1; /* undefined */ +bool DecimalFormat::fastFormatInt64(int64_t input, UnicodeString& output) const { + if (!fields->canUseFastFormat) { + return false; + } + if (input <= INT32_MIN || input > INT32_MAX) { + return false; + } + doFastFormatInt32(static_cast(input), input < 0, output); + return true; } -#if UCONFIG_HAVE_PARSEALLINPUT -void DecimalFormat::setParseAllInput(UNumberFormatAttributeValue value) { - fParseAllInput = value; +void DecimalFormat::doFastFormatInt32(int32_t input, bool isNegative, UnicodeString& output) const { + U_ASSERT(fields->canUseFastFormat); + if (isNegative) { + output.append(fields->fastData.cpMinusSign); + U_ASSERT(input != INT32_MIN); // handled by callers + input = -input; + } + // Cap at int32_t to make the buffer small and operations fast. + // Longest string: "2,147,483,648" (13 chars in length) + static constexpr int32_t localCapacity = 13; + char16_t localBuffer[localCapacity]; + char16_t* ptr = localBuffer + localCapacity; + int8_t group = 0; + for (int8_t i = 0; i < fields->fastData.maxInt && (input != 0 || i < fields->fastData.minInt); i++) { + if (group++ == 3 && fields->fastData.cpGroupingSeparator != 0) { + *(--ptr) = fields->fastData.cpGroupingSeparator; + group = 1; + } + std::div_t res = std::div(input, 10); + *(--ptr) = static_cast(fields->fastData.cpZero + res.rem); + input = res.quot; + } + int32_t len = localCapacity - static_cast(ptr - localBuffer); + output.append(ptr, len); } -#endif -U_NAMESPACE_END #endif /* #if !UCONFIG_NO_FORMATTING */ - -//eof diff --git a/deps/icu-small/source/i18n/decimfmtimpl.cpp b/deps/icu-small/source/i18n/decimfmtimpl.cpp deleted file mode 100644 index ef44eab0101453..00000000000000 --- a/deps/icu-small/source/i18n/decimfmtimpl.cpp +++ /dev/null @@ -1,1596 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: decimfmtimpl.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include -#include "unicode/numfmt.h" -#include "unicode/plurrule.h" -#include "unicode/ustring.h" -#include "decimalformatpattern.h" -#include "decimalformatpatternimpl.h" -#include "decimfmtimpl.h" -#include "fphdlimp.h" -#include "plurrule_impl.h" -#include "valueformatter.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -static const int32_t kMaxScientificIntegerDigits = 8; - -static const int32_t kFormattingPosPrefix = (1 << 0); -static const int32_t kFormattingNegPrefix = (1 << 1); -static const int32_t kFormattingPosSuffix = (1 << 2); -static const int32_t kFormattingNegSuffix = (1 << 3); -static const int32_t kFormattingSymbols = (1 << 4); -static const int32_t kFormattingCurrency = (1 << 5); -static const int32_t kFormattingUsesCurrency = (1 << 6); -static const int32_t kFormattingPluralRules = (1 << 7); -static const int32_t kFormattingAffixParser = (1 << 8); -static const int32_t kFormattingCurrencyAffixInfo = (1 << 9); -static const int32_t kFormattingAll = (1 << 10) - 1; -static const int32_t kFormattingAffixes = - kFormattingPosPrefix | kFormattingPosSuffix | - kFormattingNegPrefix | kFormattingNegSuffix; -static const int32_t kFormattingAffixParserWithCurrency = - kFormattingAffixParser | kFormattingCurrencyAffixInfo; - -DecimalFormatImpl::DecimalFormatImpl( - NumberFormat *super, - const Locale &locale, - const UnicodeString &pattern, - UErrorCode &status) - : fSuper(super), - fScale(0), - fRoundingMode(DecimalFormat::kRoundHalfEven), - fSymbols(NULL), - fCurrencyUsage(UCURR_USAGE_STANDARD), - fRules(NULL), - fMonetary(FALSE) { - if (U_FAILURE(status)) { - return; - } - fSymbols = new DecimalFormatSymbols( - locale, status); - if (fSymbols == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - UParseError parseError; - applyPattern(pattern, FALSE, parseError, status); - updateAll(status); -} - -DecimalFormatImpl::DecimalFormatImpl( - NumberFormat *super, - const UnicodeString &pattern, - DecimalFormatSymbols *symbolsToAdopt, - UParseError &parseError, - UErrorCode &status) - : fSuper(super), - fScale(0), - fRoundingMode(DecimalFormat::kRoundHalfEven), - fSymbols(symbolsToAdopt), - fCurrencyUsage(UCURR_USAGE_STANDARD), - fRules(NULL), - fMonetary(FALSE) { - applyPattern(pattern, FALSE, parseError, status); - updateAll(status); -} - -DecimalFormatImpl::DecimalFormatImpl( - NumberFormat *super, const DecimalFormatImpl &other, UErrorCode &status) : - fSuper(super), - fMultiplier(other.fMultiplier), - fScale(other.fScale), - fRoundingMode(other.fRoundingMode), - fMinSigDigits(other.fMinSigDigits), - fMaxSigDigits(other.fMaxSigDigits), - fUseScientific(other.fUseScientific), - fUseSigDigits(other.fUseSigDigits), - fGrouping(other.fGrouping), - fPositivePrefixPattern(other.fPositivePrefixPattern), - fNegativePrefixPattern(other.fNegativePrefixPattern), - fPositiveSuffixPattern(other.fPositiveSuffixPattern), - fNegativeSuffixPattern(other.fNegativeSuffixPattern), - fSymbols(other.fSymbols), - fCurrencyUsage(other.fCurrencyUsage), - fRules(NULL), - fMonetary(other.fMonetary), - fAffixParser(other.fAffixParser), - fCurrencyAffixInfo(other.fCurrencyAffixInfo), - fEffPrecision(other.fEffPrecision), - fEffGrouping(other.fEffGrouping), - fOptions(other.fOptions), - fFormatter(other.fFormatter), - fAffixes(other.fAffixes) { - fSymbols = new DecimalFormatSymbols(*fSymbols); - if (fSymbols == NULL && U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - if (other.fRules != NULL) { - fRules = new PluralRules(*other.fRules); - if (fRules == NULL && U_SUCCESS(status)) { - status = U_MEMORY_ALLOCATION_ERROR; - } - } -} - - -DecimalFormatImpl & -DecimalFormatImpl::assign(const DecimalFormatImpl &other, UErrorCode &status) { - if (U_FAILURE(status) || this == &other) { - return (*this); - } - UObject::operator=(other); - fMultiplier = other.fMultiplier; - fScale = other.fScale; - fRoundingMode = other.fRoundingMode; - fMinSigDigits = other.fMinSigDigits; - fMaxSigDigits = other.fMaxSigDigits; - fUseScientific = other.fUseScientific; - fUseSigDigits = other.fUseSigDigits; - fGrouping = other.fGrouping; - fPositivePrefixPattern = other.fPositivePrefixPattern; - fNegativePrefixPattern = other.fNegativePrefixPattern; - fPositiveSuffixPattern = other.fPositiveSuffixPattern; - fNegativeSuffixPattern = other.fNegativeSuffixPattern; - fCurrencyUsage = other.fCurrencyUsage; - fMonetary = other.fMonetary; - fAffixParser = other.fAffixParser; - fCurrencyAffixInfo = other.fCurrencyAffixInfo; - fEffPrecision = other.fEffPrecision; - fEffGrouping = other.fEffGrouping; - fOptions = other.fOptions; - fFormatter = other.fFormatter; - fAffixes = other.fAffixes; - *fSymbols = *other.fSymbols; - if (fRules != NULL && other.fRules != NULL) { - *fRules = *other.fRules; - } else { - delete fRules; - fRules = other.fRules; - if (fRules != NULL) { - fRules = new PluralRules(*fRules); - if (fRules == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return *this; - } - } - } - return *this; -} - -UBool -DecimalFormatImpl::operator==(const DecimalFormatImpl &other) const { - if (this == &other) { - return TRUE; - } - return (fMultiplier == other.fMultiplier) - && (fScale == other.fScale) - && (fRoundingMode == other.fRoundingMode) - && (fMinSigDigits == other.fMinSigDigits) - && (fMaxSigDigits == other.fMaxSigDigits) - && (fUseScientific == other.fUseScientific) - && (fUseSigDigits == other.fUseSigDigits) - && fGrouping.equals(other.fGrouping) - && fPositivePrefixPattern.equals(other.fPositivePrefixPattern) - && fNegativePrefixPattern.equals(other.fNegativePrefixPattern) - && fPositiveSuffixPattern.equals(other.fPositiveSuffixPattern) - && fNegativeSuffixPattern.equals(other.fNegativeSuffixPattern) - && fCurrencyUsage == other.fCurrencyUsage - && fAffixParser.equals(other.fAffixParser) - && fCurrencyAffixInfo.equals(other.fCurrencyAffixInfo) - && fEffPrecision.equals(other.fEffPrecision) - && fEffGrouping.equals(other.fEffGrouping) - && fOptions.equals(other.fOptions) - && fFormatter.equals(other.fFormatter) - && fAffixes.equals(other.fAffixes) - && (*fSymbols == *other.fSymbols) - && ((fRules == other.fRules) || ( - (fRules != NULL) && (other.fRules != NULL) - && (*fRules == *other.fRules))) - && (fMonetary == other.fMonetary); -} - -DecimalFormatImpl::~DecimalFormatImpl() { - delete fSymbols; - delete fRules; -} - -ValueFormatter & -DecimalFormatImpl::prepareValueFormatter(ValueFormatter &vf) const { - if (fUseScientific) { - vf.prepareScientificFormatting( - fFormatter, fEffPrecision, fOptions); - return vf; - } - vf.prepareFixedDecimalFormatting( - fFormatter, fEffGrouping, fEffPrecision.fMantissa, fOptions.fMantissa); - return vf; -} - -int32_t -DecimalFormatImpl::getPatternScale() const { - UBool usesPercent = fPositivePrefixPattern.usesPercent() || - fPositiveSuffixPattern.usesPercent() || - fNegativePrefixPattern.usesPercent() || - fNegativeSuffixPattern.usesPercent(); - if (usesPercent) { - return 2; - } - UBool usesPermill = fPositivePrefixPattern.usesPermill() || - fPositiveSuffixPattern.usesPermill() || - fNegativePrefixPattern.usesPermill() || - fNegativeSuffixPattern.usesPermill(); - if (usesPermill) { - return 3; - } - return 0; -} - -void -DecimalFormatImpl::setMultiplierScale(int32_t scale) { - if (scale == 0) { - // Needed to preserve equality. fMultiplier == 0 means - // multiplier is 1. - fMultiplier.set((int32_t)0); - } else { - fMultiplier.set((int32_t)1); - fMultiplier.shiftDecimalRight(scale); - } -} - -UnicodeString & -DecimalFormatImpl::format( - int32_t number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - return formatInt32(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - int32_t number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - return formatInt32(number, appendTo, handler, status); -} - -template -UBool DecimalFormatImpl::maybeFormatWithDigitList( - T number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - if (!fMultiplier.isZero()) { - DigitList digits; - digits.set(number); - digits.mult(fMultiplier, status); - digits.shiftDecimalRight(fScale); - formatAdjustedDigitList(digits, appendTo, handler, status); - return TRUE; - } - if (fScale != 0) { - DigitList digits; - digits.set(number); - digits.shiftDecimalRight(fScale); - formatAdjustedDigitList(digits, appendTo, handler, status); - return TRUE; - } - return FALSE; -} - -template -UBool DecimalFormatImpl::maybeInitVisibleDigitsFromDigitList( - T number, - VisibleDigitsWithExponent &visibleDigits, - UErrorCode &status) const { - if (!fMultiplier.isZero()) { - DigitList digits; - digits.set(number); - digits.mult(fMultiplier, status); - digits.shiftDecimalRight(fScale); - initVisibleDigitsFromAdjusted(digits, visibleDigits, status); - return TRUE; - } - if (fScale != 0) { - DigitList digits; - digits.set(number); - digits.shiftDecimalRight(fScale); - initVisibleDigitsFromAdjusted(digits, visibleDigits, status); - return TRUE; - } - return FALSE; -} - -UnicodeString & -DecimalFormatImpl::formatInt32( - int32_t number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - if (maybeFormatWithDigitList(number, appendTo, handler, status)) { - return appendTo; - } - ValueFormatter vf; - return fAffixes.formatInt32( - number, - prepareValueFormatter(vf), - handler, - fRules, - appendTo, - status); -} - -UnicodeString & -DecimalFormatImpl::formatInt64( - int64_t number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - if (number >= INT32_MIN && number <= INT32_MAX) { - return formatInt32((int32_t) number, appendTo, handler, status); - } - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::formatDouble( - double number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - double number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - return formatDouble(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - const DigitList &number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - DigitList dl(number); - FieldPositionOnlyHandler handler(pos); - return formatDigitList(dl, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - int64_t number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - return formatInt64(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - int64_t number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - return formatInt64(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - double number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - return formatDouble(number, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - const DigitList &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - DigitList dl(number); - FieldPositionIteratorHandler handler(posIter, status); - return formatDigitList(dl, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - StringPiece number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - DigitList dl; - dl.set(number, status); - FieldPositionIteratorHandler handler(posIter, status); - return formatDigitList(dl, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const { - FieldPositionOnlyHandler handler(pos); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::format( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const { - FieldPositionIteratorHandler handler(posIter, status); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -DigitList & -DecimalFormatImpl::adjustDigitList( - DigitList &number, UErrorCode &status) const { - number.setRoundingMode(fRoundingMode); - if (!fMultiplier.isZero()) { - number.mult(fMultiplier, status); - } - if (fScale != 0) { - number.shiftDecimalRight(fScale); - } - number.reduce(); - return number; -} - -UnicodeString & -DecimalFormatImpl::formatDigitList( - DigitList &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - VisibleDigitsWithExponent digits; - initVisibleDigitsWithExponent(number, digits, status); - return formatVisibleDigitsWithExponent( - digits, appendTo, handler, status); -} - -UnicodeString & -DecimalFormatImpl::formatAdjustedDigitList( - DigitList &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - ValueFormatter vf; - return fAffixes.format( - number, - prepareValueFormatter(vf), - handler, - fRules, - appendTo, - status); -} - -UnicodeString & -DecimalFormatImpl::formatVisibleDigitsWithExponent( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const { - ValueFormatter vf; - return fAffixes.format( - digits, - prepareValueFormatter(vf), - handler, - fRules, - appendTo, - status); -} - -static FixedDecimal &initFixedDecimal( - const VisibleDigits &digits, FixedDecimal &result) { - result.source = 0.0; - result.isNegative = digits.isNegative(); - result._isNaN = digits.isNaN(); - result._isInfinite = digits.isInfinite(); - digits.getFixedDecimal( - result.source, result.intValue, result.decimalDigits, - result.decimalDigitsWithoutTrailingZeros, - result.visibleDecimalDigitCount, result.hasIntegerValue); - return result; -} - -FixedDecimal & -DecimalFormatImpl::getFixedDecimal(double number, FixedDecimal &result, UErrorCode &status) const { - if (U_FAILURE(status)) { - return result; - } - VisibleDigits digits; - fEffPrecision.fMantissa.initVisibleDigits(number, digits, status); - return initFixedDecimal(digits, result); -} - -FixedDecimal & -DecimalFormatImpl::getFixedDecimal( - DigitList &number, FixedDecimal &result, UErrorCode &status) const { - if (U_FAILURE(status)) { - return result; - } - VisibleDigits digits; - fEffPrecision.fMantissa.initVisibleDigits(number, digits, status); - return initFixedDecimal(digits, result); -} - -VisibleDigitsWithExponent & -DecimalFormatImpl::initVisibleDigitsWithExponent( - int64_t number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (maybeInitVisibleDigitsFromDigitList( - number, digits, status)) { - return digits; - } - if (fUseScientific) { - fEffPrecision.initVisibleDigitsWithExponent( - number, digits, status); - } else { - fEffPrecision.fMantissa.initVisibleDigitsWithExponent( - number, digits, status); - } - return digits; -} - -VisibleDigitsWithExponent & -DecimalFormatImpl::initVisibleDigitsWithExponent( - double number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (maybeInitVisibleDigitsFromDigitList( - number, digits, status)) { - return digits; - } - if (fUseScientific) { - fEffPrecision.initVisibleDigitsWithExponent( - number, digits, status); - } else { - fEffPrecision.fMantissa.initVisibleDigitsWithExponent( - number, digits, status); - } - return digits; -} - -VisibleDigitsWithExponent & -DecimalFormatImpl::initVisibleDigitsWithExponent( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - adjustDigitList(number, status); - return initVisibleDigitsFromAdjusted(number, digits, status); -} - -VisibleDigitsWithExponent & -DecimalFormatImpl::initVisibleDigitsFromAdjusted( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (fUseScientific) { - fEffPrecision.initVisibleDigitsWithExponent( - number, digits, status); - } else { - fEffPrecision.fMantissa.initVisibleDigitsWithExponent( - number, digits, status); - } - return digits; -} - -DigitList & -DecimalFormatImpl::round( - DigitList &number, UErrorCode &status) const { - if (number.isNaN() || number.isInfinite()) { - return number; - } - adjustDigitList(number, status); - ValueFormatter vf; - prepareValueFormatter(vf); - return vf.round(number, status); -} - -void -DecimalFormatImpl::setMinimumSignificantDigits(int32_t newValue) { - fMinSigDigits = newValue; - fUseSigDigits = TRUE; // ticket 9936 - updatePrecision(); -} - -void -DecimalFormatImpl::setMaximumSignificantDigits(int32_t newValue) { - fMaxSigDigits = newValue; - fUseSigDigits = TRUE; // ticket 9936 - updatePrecision(); -} - -void -DecimalFormatImpl::setMinMaxSignificantDigits(int32_t min, int32_t max) { - fMinSigDigits = min; - fMaxSigDigits = max; - fUseSigDigits = TRUE; // ticket 9936 - updatePrecision(); -} - -void -DecimalFormatImpl::setScientificNotation(UBool newValue) { - fUseScientific = newValue; - updatePrecision(); -} - -void -DecimalFormatImpl::setSignificantDigitsUsed(UBool newValue) { - fUseSigDigits = newValue; - updatePrecision(); -} - -void -DecimalFormatImpl::setGroupingSize(int32_t newValue) { - fGrouping.fGrouping = newValue; - updateGrouping(); -} - -void -DecimalFormatImpl::setSecondaryGroupingSize(int32_t newValue) { - fGrouping.fGrouping2 = newValue; - updateGrouping(); -} - -void -DecimalFormatImpl::setMinimumGroupingDigits(int32_t newValue) { - fGrouping.fMinGrouping = newValue; - updateGrouping(); -} - -void -DecimalFormatImpl::setCurrencyUsage( - UCurrencyUsage currencyUsage, UErrorCode &status) { - fCurrencyUsage = currencyUsage; - updateFormatting(kFormattingCurrency, status); -} - -void -DecimalFormatImpl::setRoundingIncrement(double d) { - if (d > 0.0) { - fEffPrecision.fMantissa.fRoundingIncrement.set(d); - } else { - fEffPrecision.fMantissa.fRoundingIncrement.set(0.0); - } -} - -double -DecimalFormatImpl::getRoundingIncrement() const { - return fEffPrecision.fMantissa.fRoundingIncrement.getDouble(); -} - -int32_t -DecimalFormatImpl::getMultiplier() const { - if (fMultiplier.isZero()) { - return 1; - } - return (int32_t) fMultiplier.getDouble(); -} - -void -DecimalFormatImpl::setMultiplier(int32_t m) { - if (m == 0 || m == 1) { - fMultiplier.set((int32_t)0); - } else { - fMultiplier.set(m); - } -} - -void -DecimalFormatImpl::setPositivePrefix(const UnicodeString &str) { - fPositivePrefixPattern.remove(); - fPositivePrefixPattern.addLiteral(str.getBuffer(), 0, str.length()); - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingPosPrefix, status); -} - -void -DecimalFormatImpl::setPositiveSuffix(const UnicodeString &str) { - fPositiveSuffixPattern.remove(); - fPositiveSuffixPattern.addLiteral(str.getBuffer(), 0, str.length()); - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingPosSuffix, status); -} - -void -DecimalFormatImpl::setNegativePrefix(const UnicodeString &str) { - fNegativePrefixPattern.remove(); - fNegativePrefixPattern.addLiteral(str.getBuffer(), 0, str.length()); - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingNegPrefix, status); -} - -void -DecimalFormatImpl::setNegativeSuffix(const UnicodeString &str) { - fNegativeSuffixPattern.remove(); - fNegativeSuffixPattern.addLiteral(str.getBuffer(), 0, str.length()); - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingNegSuffix, status); -} - -UnicodeString & -DecimalFormatImpl::getPositivePrefix(UnicodeString &result) const { - result = fAffixes.fPositivePrefix.getOtherVariant().toString(); - return result; -} - -UnicodeString & -DecimalFormatImpl::getPositiveSuffix(UnicodeString &result) const { - result = fAffixes.fPositiveSuffix.getOtherVariant().toString(); - return result; -} - -UnicodeString & -DecimalFormatImpl::getNegativePrefix(UnicodeString &result) const { - result = fAffixes.fNegativePrefix.getOtherVariant().toString(); - return result; -} - -UnicodeString & -DecimalFormatImpl::getNegativeSuffix(UnicodeString &result) const { - result = fAffixes.fNegativeSuffix.getOtherVariant().toString(); - return result; -} - -void -DecimalFormatImpl::adoptDecimalFormatSymbols(DecimalFormatSymbols *symbolsToAdopt) { - if (symbolsToAdopt == NULL) { - return; - } - delete fSymbols; - fSymbols = symbolsToAdopt; - UErrorCode status = U_ZERO_ERROR; - updateFormatting(kFormattingSymbols, status); -} - -void -DecimalFormatImpl::applyPatternFavorCurrencyPrecision( - const UnicodeString &pattern, UErrorCode &status) { - UParseError perror; - applyPattern(pattern, FALSE, perror, status); - updateForApplyPatternFavorCurrencyPrecision(status); -} - -void -DecimalFormatImpl::applyPattern( - const UnicodeString &pattern, UErrorCode &status) { - UParseError perror; - applyPattern(pattern, FALSE, perror, status); - updateForApplyPattern(status); -} - -void -DecimalFormatImpl::applyPattern( - const UnicodeString &pattern, - UParseError &perror, UErrorCode &status) { - applyPattern(pattern, FALSE, perror, status); - updateForApplyPattern(status); -} - -void -DecimalFormatImpl::applyLocalizedPattern( - const UnicodeString &pattern, UErrorCode &status) { - UParseError perror; - applyPattern(pattern, TRUE, perror, status); - updateForApplyPattern(status); -} - -void -DecimalFormatImpl::applyLocalizedPattern( - const UnicodeString &pattern, - UParseError &perror, UErrorCode &status) { - applyPattern(pattern, TRUE, perror, status); - updateForApplyPattern(status); -} - -void -DecimalFormatImpl::applyPattern( - const UnicodeString &pattern, - UBool localized, UParseError &perror, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - DecimalFormatPatternParser patternParser; - if (localized) { - patternParser.useSymbols(*fSymbols); - } - DecimalFormatPattern out; - patternParser.applyPatternWithoutExpandAffix( - pattern, out, perror, status); - if (U_FAILURE(status)) { - return; - } - fUseScientific = out.fUseExponentialNotation; - fUseSigDigits = out.fUseSignificantDigits; - fSuper->NumberFormat::setMinimumIntegerDigits(out.fMinimumIntegerDigits); - fSuper->NumberFormat::setMaximumIntegerDigits(out.fMaximumIntegerDigits); - fSuper->NumberFormat::setMinimumFractionDigits(out.fMinimumFractionDigits); - fSuper->NumberFormat::setMaximumFractionDigits(out.fMaximumFractionDigits); - fMinSigDigits = out.fMinimumSignificantDigits; - fMaxSigDigits = out.fMaximumSignificantDigits; - fEffPrecision.fMinExponentDigits = out.fMinExponentDigits; - fOptions.fExponent.fAlwaysShowSign = out.fExponentSignAlwaysShown; - fSuper->NumberFormat::setGroupingUsed(out.fGroupingUsed); - fGrouping.fGrouping = out.fGroupingSize; - fGrouping.fGrouping2 = out.fGroupingSize2; - fOptions.fMantissa.fAlwaysShowDecimal = out.fDecimalSeparatorAlwaysShown; - if (out.fRoundingIncrementUsed) { - fEffPrecision.fMantissa.fRoundingIncrement = out.fRoundingIncrement; - } else { - fEffPrecision.fMantissa.fRoundingIncrement.clear(); - } - fAffixes.fPadChar = out.fPad; - fNegativePrefixPattern = out.fNegPrefixAffix; - fNegativeSuffixPattern = out.fNegSuffixAffix; - fPositivePrefixPattern = out.fPosPrefixAffix; - fPositiveSuffixPattern = out.fPosSuffixAffix; - - // Work around. Pattern parsing code and DecimalFormat code don't agree - // on the definition of field width, so we have to translate from - // pattern field width to decimal format field width here. - fAffixes.fWidth = out.fFormatWidth == 0 ? 0 : - out.fFormatWidth + fPositivePrefixPattern.countChar32() - + fPositiveSuffixPattern.countChar32(); - switch (out.fPadPosition) { - case DecimalFormatPattern::kPadBeforePrefix: - fAffixes.fPadPosition = DigitAffixesAndPadding::kPadBeforePrefix; - break; - case DecimalFormatPattern::kPadAfterPrefix: - fAffixes.fPadPosition = DigitAffixesAndPadding::kPadAfterPrefix; - break; - case DecimalFormatPattern::kPadBeforeSuffix: - fAffixes.fPadPosition = DigitAffixesAndPadding::kPadBeforeSuffix; - break; - case DecimalFormatPattern::kPadAfterSuffix: - fAffixes.fPadPosition = DigitAffixesAndPadding::kPadAfterSuffix; - break; - default: - break; - } -} - -void -DecimalFormatImpl::updatePrecision() { - if (fUseScientific) { - updatePrecisionForScientific(); - } else { - updatePrecisionForFixed(); - } -} - -static void updatePrecisionForScientificMinMax( - const DigitInterval &min, - const DigitInterval &max, - DigitInterval &resultMin, - DigitInterval &resultMax, - SignificantDigitInterval &resultSignificant) { - resultMin.setIntDigitCount(0); - resultMin.setFracDigitCount(0); - resultSignificant.clear(); - resultMax.clear(); - - int32_t maxIntDigitCount = max.getIntDigitCount(); - int32_t minIntDigitCount = min.getIntDigitCount(); - int32_t maxFracDigitCount = max.getFracDigitCount(); - int32_t minFracDigitCount = min.getFracDigitCount(); - - - // Not in spec: maxIntDigitCount > 8 assume - // maxIntDigitCount = minIntDigitCount. Current DecimalFormat API has - // no provision for unsetting maxIntDigitCount which would be useful for - // scientific notation. The best we can do is assume that if - // maxIntDigitCount is the default of 2000000000 or is "big enough" then - // user did not intend to explicitly set it. The 8 was derived emperically - // by extensive testing of legacy code. - if (maxIntDigitCount > 8) { - maxIntDigitCount = minIntDigitCount; - } - - // Per the spec, exponent grouping happens if maxIntDigitCount is more - // than 1 and more than minIntDigitCount. - UBool bExponentGrouping = maxIntDigitCount > 1 && minIntDigitCount < maxIntDigitCount; - if (bExponentGrouping) { - resultMax.setIntDigitCount(maxIntDigitCount); - - // For exponent grouping minIntDigits is always treated as 1 even - // if it wasn't set to 1! - resultMin.setIntDigitCount(1); - } else { - // Fixed digit count left of decimal. minIntDigitCount doesn't have - // to equal maxIntDigitCount i.e minIntDigitCount == 0 while - // maxIntDigitCount == 1. - int32_t fixedIntDigitCount = maxIntDigitCount; - - // If fixedIntDigitCount is 0 but - // min or max fraction count is 0 too then use 1. This way we can get - // unlimited precision for X.XXXEX - if (fixedIntDigitCount == 0 && (minFracDigitCount == 0 || maxFracDigitCount == 0)) { - fixedIntDigitCount = 1; - } - resultMax.setIntDigitCount(fixedIntDigitCount); - resultMin.setIntDigitCount(fixedIntDigitCount); - } - // Spec says this is how we compute significant digits. 0 means - // unlimited significant digits. - int32_t maxSigDigits = minIntDigitCount + maxFracDigitCount; - if (maxSigDigits > 0) { - int32_t minSigDigits = minIntDigitCount + minFracDigitCount; - resultSignificant.setMin(minSigDigits); - resultSignificant.setMax(maxSigDigits); - } -} - -void -DecimalFormatImpl::updatePrecisionForScientific() { - FixedPrecision *result = &fEffPrecision.fMantissa; - if (fUseSigDigits) { - result->fMax.setFracDigitCount(-1); - result->fMax.setIntDigitCount(1); - result->fMin.setFracDigitCount(0); - result->fMin.setIntDigitCount(1); - result->fSignificant.clear(); - extractSigDigits(result->fSignificant); - return; - } - DigitInterval max; - DigitInterval min; - extractMinMaxDigits(min, max); - updatePrecisionForScientificMinMax( - min, max, - result->fMin, result->fMax, result->fSignificant); -} - -void -DecimalFormatImpl::updatePrecisionForFixed() { - FixedPrecision *result = &fEffPrecision.fMantissa; - if (!fUseSigDigits) { - extractMinMaxDigits(result->fMin, result->fMax); - result->fSignificant.clear(); - } else { - extractSigDigits(result->fSignificant); - result->fMin.setIntDigitCount(1); - result->fMin.setFracDigitCount(0); - result->fMax.clear(); - } -} - -void - DecimalFormatImpl::extractMinMaxDigits( - DigitInterval &min, DigitInterval &max) const { - min.setIntDigitCount(fSuper->getMinimumIntegerDigits()); - max.setIntDigitCount(fSuper->getMaximumIntegerDigits()); - min.setFracDigitCount(fSuper->getMinimumFractionDigits()); - max.setFracDigitCount(fSuper->getMaximumFractionDigits()); -} - -void - DecimalFormatImpl::extractSigDigits( - SignificantDigitInterval &sig) const { - sig.setMin(fMinSigDigits < 0 ? 0 : fMinSigDigits); - sig.setMax(fMaxSigDigits < 0 ? 0 : fMaxSigDigits); -} - -void -DecimalFormatImpl::updateGrouping() { - if (fSuper->isGroupingUsed()) { - fEffGrouping = fGrouping; - } else { - fEffGrouping.clear(); - } -} - -void -DecimalFormatImpl::updateCurrency(UErrorCode &status) { - updateFormatting(kFormattingCurrency, TRUE, status); -} - -void -DecimalFormatImpl::updateFormatting( - int32_t changedFormattingFields, - UErrorCode &status) { - updateFormatting(changedFormattingFields, TRUE, status); -} - -void -DecimalFormatImpl::updateFormatting( - int32_t changedFormattingFields, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - // Each function updates one field. Order matters. For instance, - // updatePluralRules comes before updateCurrencyAffixInfo because the - // fRules field is needed to update the fCurrencyAffixInfo field. - updateFormattingUsesCurrency(changedFormattingFields); - updateFormattingFixedPointFormatter(changedFormattingFields); - updateFormattingAffixParser(changedFormattingFields); - updateFormattingPluralRules(changedFormattingFields, status); - updateFormattingCurrencyAffixInfo( - changedFormattingFields, - updatePrecisionBasedOnCurrency, - status); - updateFormattingLocalizedPositivePrefix( - changedFormattingFields, status); - updateFormattingLocalizedPositiveSuffix( - changedFormattingFields, status); - updateFormattingLocalizedNegativePrefix( - changedFormattingFields, status); - updateFormattingLocalizedNegativeSuffix( - changedFormattingFields, status); -} - -void -DecimalFormatImpl::updateFormattingUsesCurrency( - int32_t &changedFormattingFields) { - if ((changedFormattingFields & kFormattingAffixes) == 0) { - // If no affixes changed, don't need to do any work - return; - } - UBool newUsesCurrency = - fPositivePrefixPattern.usesCurrency() || - fPositiveSuffixPattern.usesCurrency() || - fNegativePrefixPattern.usesCurrency() || - fNegativeSuffixPattern.usesCurrency(); - if (fMonetary != newUsesCurrency) { - fMonetary = newUsesCurrency; - changedFormattingFields |= kFormattingUsesCurrency; - } -} - -void -DecimalFormatImpl::updateFormattingPluralRules( - int32_t &changedFormattingFields, UErrorCode &status) { - if ((changedFormattingFields & (kFormattingSymbols | kFormattingUsesCurrency)) == 0) { - // No work to do if both fSymbols and fMonetary - // fields are unchanged - return; - } - if (U_FAILURE(status)) { - return; - } - PluralRules *newRules = NULL; - if (fMonetary) { - newRules = PluralRules::forLocale(fSymbols->getLocale(), status); - if (U_FAILURE(status)) { - return; - } - } - // Its ok to say a field has changed when it really hasn't but not - // the other way around. Here we assume the field changed unless it - // was NULL before and is still NULL now - if (fRules != newRules) { - delete fRules; - fRules = newRules; - changedFormattingFields |= kFormattingPluralRules; - } -} - -void -DecimalFormatImpl::updateFormattingCurrencyAffixInfo( - int32_t &changedFormattingFields, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status) { - if ((changedFormattingFields & ( - kFormattingSymbols | kFormattingCurrency | - kFormattingUsesCurrency | kFormattingPluralRules)) == 0) { - // If all these fields are unchanged, no work to do. - return; - } - if (U_FAILURE(status)) { - return; - } - if (!fMonetary) { - if (fCurrencyAffixInfo.isDefault()) { - // In this case don't have to do any work - return; - } - fCurrencyAffixInfo.set(NULL, NULL, NULL, status); - if (U_FAILURE(status)) { - return; - } - changedFormattingFields |= kFormattingCurrencyAffixInfo; - } else { - const UChar *currency = fSuper->getCurrency(); - UChar localeCurr[4]; - if (currency[0] == 0) { - ucurr_forLocale(fSymbols->getLocale().getName(), localeCurr, UPRV_LENGTHOF(localeCurr), &status); - if (U_SUCCESS(status)) { - currency = localeCurr; - fSuper->NumberFormat::setCurrency(currency, status); - } else { - currency = NULL; - status = U_ZERO_ERROR; - } - } - fCurrencyAffixInfo.set( - fSymbols->getLocale().getName(), fRules, currency, status); - if (U_FAILURE(status)) { - return; - } - UBool customCurrencySymbol = FALSE; - // If DecimalFormatSymbols has custom currency symbol, prefer - // that over what we just read from the resource bundles - if (fSymbols->isCustomCurrencySymbol()) { - fCurrencyAffixInfo.setSymbol( - fSymbols->getConstSymbol(DecimalFormatSymbols::kCurrencySymbol)); - customCurrencySymbol = TRUE; - } - if (fSymbols->isCustomIntlCurrencySymbol()) { - fCurrencyAffixInfo.setISO( - fSymbols->getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol)); - customCurrencySymbol = TRUE; - } - changedFormattingFields |= kFormattingCurrencyAffixInfo; - if (currency && !customCurrencySymbol && updatePrecisionBasedOnCurrency) { - FixedPrecision precision; - CurrencyAffixInfo::adjustPrecision( - currency, fCurrencyUsage, precision, status); - if (U_FAILURE(status)) { - return; - } - fSuper->NumberFormat::setMinimumFractionDigits( - precision.fMin.getFracDigitCount()); - fSuper->NumberFormat::setMaximumFractionDigits( - precision.fMax.getFracDigitCount()); - updatePrecision(); - fEffPrecision.fMantissa.fRoundingIncrement = - precision.fRoundingIncrement; - } - - } -} - -void -DecimalFormatImpl::updateFormattingFixedPointFormatter( - int32_t &changedFormattingFields) { - if ((changedFormattingFields & (kFormattingSymbols | kFormattingUsesCurrency)) == 0) { - // No work to do if fSymbols is unchanged - return; - } - if (fMonetary) { - fFormatter.setDecimalFormatSymbolsForMonetary(*fSymbols); - } else { - fFormatter.setDecimalFormatSymbols(*fSymbols); - } -} - -void -DecimalFormatImpl::updateFormattingAffixParser( - int32_t &changedFormattingFields) { - if ((changedFormattingFields & kFormattingSymbols) == 0) { - // No work to do if fSymbols is unchanged - return; - } - fAffixParser.setDecimalFormatSymbols(*fSymbols); - changedFormattingFields |= kFormattingAffixParser; -} - -void -DecimalFormatImpl::updateFormattingLocalizedPositivePrefix( - int32_t &changedFormattingFields, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if ((changedFormattingFields & ( - kFormattingPosPrefix | kFormattingAffixParserWithCurrency)) == 0) { - // No work to do - return; - } - fAffixes.fPositivePrefix.remove(); - fAffixParser.parse( - fPositivePrefixPattern, - fCurrencyAffixInfo, - fAffixes.fPositivePrefix, - status); -} - -void -DecimalFormatImpl::updateFormattingLocalizedPositiveSuffix( - int32_t &changedFormattingFields, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if ((changedFormattingFields & ( - kFormattingPosSuffix | kFormattingAffixParserWithCurrency)) == 0) { - // No work to do - return; - } - fAffixes.fPositiveSuffix.remove(); - fAffixParser.parse( - fPositiveSuffixPattern, - fCurrencyAffixInfo, - fAffixes.fPositiveSuffix, - status); -} - -void -DecimalFormatImpl::updateFormattingLocalizedNegativePrefix( - int32_t &changedFormattingFields, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if ((changedFormattingFields & ( - kFormattingNegPrefix | kFormattingAffixParserWithCurrency)) == 0) { - // No work to do - return; - } - fAffixes.fNegativePrefix.remove(); - fAffixParser.parse( - fNegativePrefixPattern, - fCurrencyAffixInfo, - fAffixes.fNegativePrefix, - status); -} - -void -DecimalFormatImpl::updateFormattingLocalizedNegativeSuffix( - int32_t &changedFormattingFields, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if ((changedFormattingFields & ( - kFormattingNegSuffix | kFormattingAffixParserWithCurrency)) == 0) { - // No work to do - return; - } - fAffixes.fNegativeSuffix.remove(); - fAffixParser.parse( - fNegativeSuffixPattern, - fCurrencyAffixInfo, - fAffixes.fNegativeSuffix, - status); -} - -void -DecimalFormatImpl::updateForApplyPatternFavorCurrencyPrecision( - UErrorCode &status) { - updateAll(kFormattingAll & ~kFormattingSymbols, TRUE, status); -} - -void -DecimalFormatImpl::updateForApplyPattern(UErrorCode &status) { - updateAll(kFormattingAll & ~kFormattingSymbols, FALSE, status); -} - -void -DecimalFormatImpl::updateAll(UErrorCode &status) { - updateAll(kFormattingAll, TRUE, status); -} - -void -DecimalFormatImpl::updateAll( - int32_t formattingFlags, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - updatePrecision(); - updateGrouping(); - updateFormatting( - formattingFlags, updatePrecisionBasedOnCurrency, status); - setMultiplierScale(getPatternScale()); -} - - -static int32_t -getMinimumLengthToDescribeGrouping(const DigitGrouping &grouping) { - if (grouping.fGrouping <= 0) { - return 0; - } - if (grouping.fGrouping2 <= 0) { - return grouping.fGrouping + 1; - } - return grouping.fGrouping + grouping.fGrouping2 + 1; -} - -/** - * Given a grouping policy, calculates how many digits are needed left of - * the decimal point to achieve a desired length left of the - * decimal point. - * @param grouping the grouping policy - * @param desiredLength number of characters needed left of decimal point - * @param minLeftDigits at least this many digits is returned - * @param leftDigits the number of digits needed stored here - * which is >= minLeftDigits. - * @return true if a perfect fit or false if having leftDigits would exceed - * desiredLength - */ -static UBool -getLeftDigitsForLeftLength( - const DigitGrouping &grouping, - int32_t desiredLength, - int32_t minLeftDigits, - int32_t &leftDigits) { - leftDigits = minLeftDigits; - int32_t lengthSoFar = leftDigits + grouping.getSeparatorCount(leftDigits); - while (lengthSoFar < desiredLength) { - lengthSoFar += grouping.isSeparatorAt(leftDigits + 1, leftDigits) ? 2 : 1; - ++leftDigits; - } - return (lengthSoFar == desiredLength); -} - -int32_t -DecimalFormatImpl::computeExponentPatternLength() const { - if (fUseScientific) { - return 1 + (fOptions.fExponent.fAlwaysShowSign ? 1 : 0) + fEffPrecision.fMinExponentDigits; - } - return 0; -} - -int32_t -DecimalFormatImpl::countFractionDigitAndDecimalPatternLength( - int32_t fracDigitCount) const { - if (!fOptions.fMantissa.fAlwaysShowDecimal && fracDigitCount == 0) { - return 0; - } - return fracDigitCount + 1; -} - -UnicodeString& -DecimalFormatImpl::toNumberPattern( - UBool hasPadding, int32_t minimumLength, UnicodeString& result) const { - // Get a grouping policy like the one in this object that does not - // have minimum grouping since toPattern doesn't support it. - DigitGrouping grouping(fEffGrouping); - grouping.fMinGrouping = 0; - - // Only for fixed digits, these are the digits that get 0's. - DigitInterval minInterval; - - // Only for fixed digits, these are the digits that get #'s. - DigitInterval maxInterval; - - // Only for significant digits - int32_t sigMin = 0; /* initialize to avoid compiler warning */ - int32_t sigMax = 0; /* initialize to avoid compiler warning */ - - // These are all the digits to be displayed. For significant digits, - // this interval always starts at the 1's place an extends left. - DigitInterval fullInterval; - - // Digit range of rounding increment. If rounding increment is .025. - // then roundingIncrementLowerExp = -3 and roundingIncrementUpperExp = -1 - int32_t roundingIncrementLowerExp = 0; - int32_t roundingIncrementUpperExp = 0; - - if (fUseSigDigits) { - SignificantDigitInterval sigInterval; - extractSigDigits(sigInterval); - sigMax = sigInterval.getMax(); - sigMin = sigInterval.getMin(); - fullInterval.setFracDigitCount(0); - fullInterval.setIntDigitCount(sigMax); - } else { - extractMinMaxDigits(minInterval, maxInterval); - if (fUseScientific) { - if (maxInterval.getIntDigitCount() > kMaxScientificIntegerDigits) { - maxInterval.setIntDigitCount(1); - minInterval.shrinkToFitWithin(maxInterval); - } - } else if (hasPadding) { - // Make max int digits match min int digits for now, we - // compute necessary padding later. - maxInterval.setIntDigitCount(minInterval.getIntDigitCount()); - } else { - // For some reason toPattern adds at least one leading '#' - maxInterval.setIntDigitCount(minInterval.getIntDigitCount() + 1); - } - if (!fEffPrecision.fMantissa.fRoundingIncrement.isZero()) { - roundingIncrementLowerExp = - fEffPrecision.fMantissa.fRoundingIncrement.getLowerExponent(); - roundingIncrementUpperExp = - fEffPrecision.fMantissa.fRoundingIncrement.getUpperExponent(); - // We have to include the rounding increment in what we display - maxInterval.expandToContainDigit(roundingIncrementLowerExp); - maxInterval.expandToContainDigit(roundingIncrementUpperExp - 1); - } - fullInterval = maxInterval; - } - // We have to include enough digits to show grouping strategy - int32_t minLengthToDescribeGrouping = - getMinimumLengthToDescribeGrouping(grouping); - if (minLengthToDescribeGrouping > 0) { - fullInterval.expandToContainDigit( - getMinimumLengthToDescribeGrouping(grouping) - 1); - } - - // If we have a minimum length, we have to add digits to the left to - // depict padding. - if (hasPadding) { - // For non scientific notation, - // minimumLengthForMantissa = minimumLength - int32_t minimumLengthForMantissa = - minimumLength - computeExponentPatternLength(); - int32_t mininumLengthForMantissaIntPart = - minimumLengthForMantissa - - countFractionDigitAndDecimalPatternLength( - fullInterval.getFracDigitCount()); - // Because of grouping, we may need fewer than expected digits to - // achieve the length we need. - int32_t digitsNeeded; - if (getLeftDigitsForLeftLength( - grouping, - mininumLengthForMantissaIntPart, - fullInterval.getIntDigitCount(), - digitsNeeded)) { - - // In this case, we achieved the exact length that we want. - fullInterval.setIntDigitCount(digitsNeeded); - } else if (digitsNeeded > fullInterval.getIntDigitCount()) { - - // Having digitsNeeded digits goes over desired length which - // means that to have desired length would mean starting on a - // grouping sepearator e.g ,###,### so add a '#' and use one - // less digit. This trick gives ####,### but that is the best - // we can do. - result.append(kPatternDigit); - fullInterval.setIntDigitCount(digitsNeeded - 1); - } - } - int32_t maxDigitPos = fullInterval.getMostSignificantExclusive(); - int32_t minDigitPos = fullInterval.getLeastSignificantInclusive(); - for (int32_t i = maxDigitPos - 1; i >= minDigitPos; --i) { - if (!fOptions.fMantissa.fAlwaysShowDecimal && i == -1) { - result.append(kPatternDecimalSeparator); - } - if (fUseSigDigits) { - // Use digit symbol - if (i >= sigMax || i < sigMax - sigMin) { - result.append(kPatternDigit); - } else { - result.append(kPatternSignificantDigit); - } - } else { - if (i < roundingIncrementUpperExp && i >= roundingIncrementLowerExp) { - result.append((UChar)(fEffPrecision.fMantissa.fRoundingIncrement.getDigitByExponent(i) + kPatternZeroDigit)); - } else if (minInterval.contains(i)) { - result.append(kPatternZeroDigit); - } else { - result.append(kPatternDigit); - } - } - if (grouping.isSeparatorAt(i + 1, i)) { - result.append(kPatternGroupingSeparator); - } - if (fOptions.fMantissa.fAlwaysShowDecimal && i == 0) { - result.append(kPatternDecimalSeparator); - } - } - if (fUseScientific) { - result.append(kPatternExponent); - if (fOptions.fExponent.fAlwaysShowSign) { - result.append(kPatternPlus); - } - for (int32_t i = 0; i < 1 || i < fEffPrecision.fMinExponentDigits; ++i) { - result.append(kPatternZeroDigit); - } - } - return result; -} - -UnicodeString& -DecimalFormatImpl::toPattern(UnicodeString& result) const { - result.remove(); - UnicodeString padSpec; - if (fAffixes.fWidth > 0) { - padSpec.append(kPatternPadEscape); - padSpec.append(fAffixes.fPadChar); - } - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforePrefix) { - result.append(padSpec); - } - fPositivePrefixPattern.toUserString(result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterPrefix) { - result.append(padSpec); - } - toNumberPattern( - fAffixes.fWidth > 0, - fAffixes.fWidth - fPositivePrefixPattern.countChar32() - fPositiveSuffixPattern.countChar32(), - result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforeSuffix) { - result.append(padSpec); - } - fPositiveSuffixPattern.toUserString(result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterSuffix) { - result.append(padSpec); - } - AffixPattern withNegative; - withNegative.add(AffixPattern::kNegative); - withNegative.append(fPositivePrefixPattern); - if (!fPositiveSuffixPattern.equals(fNegativeSuffixPattern) || - !withNegative.equals(fNegativePrefixPattern)) { - result.append(kPatternSeparator); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforePrefix) { - result.append(padSpec); - } - fNegativePrefixPattern.toUserString(result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterPrefix) { - result.append(padSpec); - } - toNumberPattern( - fAffixes.fWidth > 0, - fAffixes.fWidth - fNegativePrefixPattern.countChar32() - fNegativeSuffixPattern.countChar32(), - result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadBeforeSuffix) { - result.append(padSpec); - } - fNegativeSuffixPattern.toUserString(result); - if (fAffixes.fPadPosition == DigitAffixesAndPadding::kPadAfterSuffix) { - result.append(padSpec); - } - } - return result; -} - -int32_t -DecimalFormatImpl::getOldFormatWidth() const { - if (fAffixes.fWidth == 0) { - return 0; - } - return fAffixes.fWidth - fPositiveSuffixPattern.countChar32() - fPositivePrefixPattern.countChar32(); -} - -const UnicodeString & -DecimalFormatImpl::getConstSymbol( - DecimalFormatSymbols::ENumberFormatSymbol symbol) const { - return fSymbols->getConstSymbol(symbol); -} - -UBool -DecimalFormatImpl::isParseFastpath() const { - AffixPattern negative; - negative.add(AffixPattern::kNegative); - - return fAffixes.fWidth == 0 && - fPositivePrefixPattern.countChar32() == 0 && - fNegativePrefixPattern.equals(negative) && - fPositiveSuffixPattern.countChar32() == 0 && - fNegativeSuffixPattern.countChar32() == 0; -} - - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/decimfmtimpl.h b/deps/icu-small/source/i18n/decimfmtimpl.h deleted file mode 100644 index b4438cba9e1ea8..00000000000000 --- a/deps/icu-small/source/i18n/decimfmtimpl.h +++ /dev/null @@ -1,549 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************** -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************** -* -* File decimfmtimpl.h -******************************************************************************** -*/ - -#ifndef DECIMFMTIMPL_H -#define DECIMFMTIMPL_H - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/decimfmt.h" -#include "unicode/uobject.h" -#include "affixpatternparser.h" -#include "digitaffixesandpadding.h" -#include "digitformatter.h" -#include "digitgrouping.h" -#include "precision.h" - -U_NAMESPACE_BEGIN - -class UnicodeString; -class FieldPosition; -class ValueFormatter; -class FieldPositionHandler; -class FixedDecimal; - -/** - * DecimalFormatImpl is the glue code between the legacy DecimalFormat class - * and the new decimal formatting classes. DecimalFormat still handles - * parsing directly. However, DecimalFormat uses attributes of this class - * for parsing when possible. - * - * The public API of this class closely mirrors the legacy API of the - * legacy DecimalFormat deviating only when the legacy API does not make - * sense. For example, although DecimalFormat has a - * getPadCharacterString() method, DecimalFormatImpl has a getPadCharacter() - * method because formatting uses only a single pad character for padding. - * - * Each legacy DecimalFormat instance heap allocates its own instance of - * this class. Most DecimalFormat methods that deal with formatting simply - * delegate to the DecimalFormat's DecimalFormatImpl method. - * - * Because DecimalFormat extends NumberFormat, Each instance of this class - * "borrows" a pointer to the NumberFormat part of its enclosing DecimalFormat - * instance. This way each DecimalFormatImpl instance can read or even modify - * the NumberFormat portion of its enclosing DecimalFormat instance. - * - * Directed acyclic graph (DAG): - * - * This class can be represented as a directed acyclic graph (DAG) where each - * vertex is an attribute, and each directed edge indicates that the value - * of the destination attribute is calculated from the value of the source - * attribute. Attributes with setter methods reside at the bottom of the - * DAG. That is, no edges point to them. We call these independent attributes - * because their values can be set independently of one another. The rest of - * the attributes are derived attributes because their values depend on the - * independent attributes. DecimalFormatImpl often uses the derived - * attributes, not the independent attributes, when formatting numbers. - * - * The independent attributes at the bottom of the DAG correspond to the legacy - * attributes of DecimalFormat while the attributes at the top of the DAG - * correspond to the attributes of the new code. The edges of the DAG - * correspond to the code that handles the complex interaction among all the - * legacy attributes of the DecimalFormat API. - * - * We use a DAG for three reasons. - * - * First, the DAG preserves backward compatibility. Clients of the legacy - * DecimalFormat expect existing getters and setters of each attribute to be - * consistent. That means if a client sets a particular attribute to a new - * value, the attribute should retain that value until the client sets it to - * a new value. The DAG allows these attributes to remain consistent even - * though the new code may not use them when formatting. - * - * Second, the DAG obviates the need to recalculate derived attributes with - * each format. Instead, the DAG "remembers" the values of all derived - * attributes. Only setting an independent attribute requires a recalculation. - * Moreover, setting an independent attribute recalculates only the affected - * dependent attributes rather than all dependent attributes. - * - * Third, the DAG abstracts away the complex interaction among the legacy - * attributes of the DecimalFormat API. - * - * Only the independent attributes of the DAG have setters and getters. - * Derived attributes have no setters (and often no getters either). - * - * Copy and assign: - * - * For copy and assign, DecimalFormatImpl copies and assigns every attribute - * regardless of whether or not it is independent. We do this for simplicity. - * - * Implementation of the DAG: - * - * The DAG consists of three smaller DAGs: - * 1. Grouping attributes - * 2. Precision attributes - * 3. Formatting attributes. - * - * The first two DAGs are simple in that setting any independent attribute - * in the DAG recalculates all the dependent attributes in that DAG. - * The updateGrouping() and updatePrecision() perform the respective - * recalculations. - * - * Because some of the derived formatting attributes are expensive to - * calculate, the formatting attributes DAG is more complex. The - * updateFormatting() method is composed of many updateFormattingXXX() - * methods, each of which recalculates a single derived attribute. The - * updateFormatting() method accepts a bitfield of recently changed - * attributes and passes this bitfield by reference to each of the - * updateFormattingXXX() methods. Each updateFormattingXXX() method checks - * the bitfield to see if any of the attributes it uses to compute the XXX - * attribute changed. If none of them changed, it exists immediately. However, - * if at least one of them changed, it recalculates the XXX attribute and - * sets the corresponding bit in the bitfield. In this way, each - * updateFormattingXXX() method encodes the directed edges in the formatting - * DAG that point to the attribute its calculating. - * - * Maintenance of the updateFormatting() method. - * - * Use care when changing the updateFormatting() method. - * The updateFormatting() method must call each updateFormattingXXX() in the - * same partial order that the formatting DAG prescribes. That is, the - * attributes near the bottom of the DAG must be calculated before attributes - * further up. As we mentioned in the prvious paragraph, the directed edges of - * the formatting DAG are encoded within each updateFormattingXXX() method. - * Finally, adding new attributes may involve adding to the bitmap that the - * updateFormatting() method uses. The top most attributes in the DAG, - * those that do not point to any attributes but only have attributes - * pointing to it, need not have a slot in the bitmap. - * - * Keep in mind that most of the code that makes the legacy DecimalFormat API - * work the way it always has before can be found in these various updateXXX() - * methods. For example the updatePrecisionForScientific() method - * handles the complex interactions amoung the various precision attributes - * when formatting in scientific notation. Changing the way attributes - * interract, often means changing one of these updateXXX() methods. - * - * Conclusion: - * - * The DecimFmtImpl class is the glue code between the legacy and new - * number formatting code. It uses a direct acyclic graph (DAG) to - * maintain backward compatibility, to make the code efficient, and to - * abstract away the complex interraction among legacy attributs. - */ - - -class DecimalFormatImpl : public UObject { -public: - -DecimalFormatImpl( - NumberFormat *super, - const Locale &locale, - const UnicodeString &pattern, - UErrorCode &status); -DecimalFormatImpl( - NumberFormat *super, - const UnicodeString &pattern, - DecimalFormatSymbols *symbolsToAdopt, - UParseError &parseError, - UErrorCode &status); -DecimalFormatImpl( - NumberFormat *super, - const DecimalFormatImpl &other, - UErrorCode &status); -DecimalFormatImpl &assign( - const DecimalFormatImpl &other, UErrorCode &status); -virtual ~DecimalFormatImpl(); -void adoptDecimalFormatSymbols(DecimalFormatSymbols *symbolsToAdopt); -const DecimalFormatSymbols &getDecimalFormatSymbols() const { - return *fSymbols; -} -UnicodeString &format( - int32_t number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - int32_t number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - int64_t number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - double number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - const DigitList &number, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - int64_t number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - double number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - const DigitList &number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - StringPiece number, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; -UnicodeString &format( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPosition &pos, - UErrorCode &status) const; -UnicodeString &format( - const VisibleDigitsWithExponent &digits, - UnicodeString &appendTo, - FieldPositionIterator *posIter, - UErrorCode &status) const; - -UBool operator==(const DecimalFormatImpl &) const; - -UBool operator!=(const DecimalFormatImpl &other) const { - return !(*this == other); -} - -void setRoundingMode(DecimalFormat::ERoundingMode mode) { - fRoundingMode = mode; - fEffPrecision.fMantissa.fExactOnly = (fRoundingMode == DecimalFormat::kRoundUnnecessary); - fEffPrecision.fMantissa.fRoundingMode = mode; -} -DecimalFormat::ERoundingMode getRoundingMode() const { - return fRoundingMode; -} -void setFailIfMoreThanMaxDigits(UBool b) { - fEffPrecision.fMantissa.fFailIfOverMax = b; -} -UBool isFailIfMoreThanMaxDigits() const { return fEffPrecision.fMantissa.fFailIfOverMax; } -void setMinimumSignificantDigits(int32_t newValue); -void setMaximumSignificantDigits(int32_t newValue); -void setMinMaxSignificantDigits(int32_t min, int32_t max); -void setScientificNotation(UBool newValue); -void setSignificantDigitsUsed(UBool newValue); - -int32_t getMinimumSignificantDigits() const { - return fMinSigDigits; } -int32_t getMaximumSignificantDigits() const { - return fMaxSigDigits; } -UBool isScientificNotation() const { return fUseScientific; } -UBool areSignificantDigitsUsed() const { return fUseSigDigits; } -void setGroupingSize(int32_t newValue); -void setSecondaryGroupingSize(int32_t newValue); -void setMinimumGroupingDigits(int32_t newValue); -int32_t getGroupingSize() const { return fGrouping.fGrouping; } -int32_t getSecondaryGroupingSize() const { return fGrouping.fGrouping2; } -int32_t getMinimumGroupingDigits() const { return fGrouping.fMinGrouping; } -void applyPattern(const UnicodeString &pattern, UErrorCode &status); -void applyPatternFavorCurrencyPrecision( - const UnicodeString &pattern, UErrorCode &status); -void applyPattern( - const UnicodeString &pattern, UParseError &perror, UErrorCode &status); -void applyLocalizedPattern(const UnicodeString &pattern, UErrorCode &status); -void applyLocalizedPattern( - const UnicodeString &pattern, UParseError &perror, UErrorCode &status); -void setCurrencyUsage(UCurrencyUsage usage, UErrorCode &status); -UCurrencyUsage getCurrencyUsage() const { return fCurrencyUsage; } -void setRoundingIncrement(double d); -double getRoundingIncrement() const; -int32_t getMultiplier() const; -void setMultiplier(int32_t m); -UChar32 getPadCharacter() const { return fAffixes.fPadChar; } -void setPadCharacter(UChar32 c) { fAffixes.fPadChar = c; } -int32_t getFormatWidth() const { return fAffixes.fWidth; } -void setFormatWidth(int32_t x) { fAffixes.fWidth = x; } -DigitAffixesAndPadding::EPadPosition getPadPosition() const { - return fAffixes.fPadPosition; -} -void setPadPosition(DigitAffixesAndPadding::EPadPosition x) { - fAffixes.fPadPosition = x; -} -int32_t getMinimumExponentDigits() const { - return fEffPrecision.fMinExponentDigits; -} -void setMinimumExponentDigits(int32_t x) { - fEffPrecision.fMinExponentDigits = x; -} -UBool isExponentSignAlwaysShown() const { - return fOptions.fExponent.fAlwaysShowSign; -} -void setExponentSignAlwaysShown(UBool x) { - fOptions.fExponent.fAlwaysShowSign = x; -} -UBool isDecimalSeparatorAlwaysShown() const { - return fOptions.fMantissa.fAlwaysShowDecimal; -} -void setDecimalSeparatorAlwaysShown(UBool x) { - fOptions.fMantissa.fAlwaysShowDecimal = x; -} -UnicodeString &getPositivePrefix(UnicodeString &result) const; -UnicodeString &getPositiveSuffix(UnicodeString &result) const; -UnicodeString &getNegativePrefix(UnicodeString &result) const; -UnicodeString &getNegativeSuffix(UnicodeString &result) const; -void setPositivePrefix(const UnicodeString &str); -void setPositiveSuffix(const UnicodeString &str); -void setNegativePrefix(const UnicodeString &str); -void setNegativeSuffix(const UnicodeString &str); -UnicodeString &toPattern(UnicodeString& result) const; -FixedDecimal &getFixedDecimal(double value, FixedDecimal &result, UErrorCode &status) const; -FixedDecimal &getFixedDecimal(DigitList &number, FixedDecimal &result, UErrorCode &status) const; -DigitList &round(DigitList &number, UErrorCode &status) const; - -VisibleDigitsWithExponent & -initVisibleDigitsWithExponent( - int64_t number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; -VisibleDigitsWithExponent & -initVisibleDigitsWithExponent( - double number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; -VisibleDigitsWithExponent & -initVisibleDigitsWithExponent( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -void updatePrecision(); -void updateGrouping(); -void updateCurrency(UErrorCode &status); - - -private: -// Disallow copy and assign -DecimalFormatImpl(const DecimalFormatImpl &other); -DecimalFormatImpl &operator=(const DecimalFormatImpl &other); -NumberFormat *fSuper; -DigitList fMultiplier; -int32_t fScale; - -DecimalFormat::ERoundingMode fRoundingMode; - -// These fields include what the user can see and set. -// When the user updates these fields, it triggers automatic updates of -// other fields that may be invisible to user - -// Updating any of the following fields triggers an update to -// fEffPrecision.fMantissa.fMin, -// fEffPrecision.fMantissa.fMax, -// fEffPrecision.fMantissa.fSignificant fields -// We have this two phase update because of backward compatibility. -// DecimalFormat has to remember all settings even if those settings are -// invalid or disabled. -int32_t fMinSigDigits; -int32_t fMaxSigDigits; -UBool fUseScientific; -UBool fUseSigDigits; -// In addition to these listed above, changes to min/max int digits and -// min/max frac digits from fSuper also trigger an update. - -// Updating any of the following fields triggers an update to -// fEffGrouping field Again we do it this way because original -// grouping settings have to be retained if grouping is turned off. -DigitGrouping fGrouping; -// In addition to these listed above, changes to isGroupingUsed in -// fSuper also triggers an update to fEffGrouping. - -// Updating any of the following fields triggers updates on the following: -// fMonetary, fRules, fAffixParser, fCurrencyAffixInfo, -// fFormatter, fAffixes.fPositivePrefiix, fAffixes.fPositiveSuffix, -// fAffixes.fNegativePrefiix, fAffixes.fNegativeSuffix -// We do this two phase update because localizing the affix patterns -// and formatters can be expensive. Better to do it once with the setters -// than each time within format. -AffixPattern fPositivePrefixPattern; -AffixPattern fNegativePrefixPattern; -AffixPattern fPositiveSuffixPattern; -AffixPattern fNegativeSuffixPattern; -DecimalFormatSymbols *fSymbols; -UCurrencyUsage fCurrencyUsage; -// In addition to these listed above, changes to getCurrency() in -// fSuper also triggers an update. - -// Optional may be NULL -PluralRules *fRules; - -// These fields are totally hidden from user and are used to derive the affixes -// in fAffixes below from the four affix patterns above. -UBool fMonetary; -AffixPatternParser fAffixParser; -CurrencyAffixInfo fCurrencyAffixInfo; - -// The actual precision used when formatting -ScientificPrecision fEffPrecision; - -// The actual grouping used when formatting -DigitGrouping fEffGrouping; -SciFormatterOptions fOptions; // Encapsulates fixed precision options -DigitFormatter fFormatter; -DigitAffixesAndPadding fAffixes; - -UnicodeString &formatInt32( - int32_t number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -UnicodeString &formatInt64( - int64_t number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -UnicodeString &formatDouble( - double number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -// Scales for precent or permille symbols -UnicodeString &formatDigitList( - DigitList &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -// Does not scale for precent or permille symbols -UnicodeString &formatAdjustedDigitList( - DigitList &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -UnicodeString &formatVisibleDigitsWithExponent( - const VisibleDigitsWithExponent &number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -VisibleDigitsWithExponent & -initVisibleDigitsFromAdjusted( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -template -UBool maybeFormatWithDigitList( - T number, - UnicodeString &appendTo, - FieldPositionHandler &handler, - UErrorCode &status) const; - -template -UBool maybeInitVisibleDigitsFromDigitList( - T number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -DigitList &adjustDigitList(DigitList &number, UErrorCode &status) const; - -void applyPattern( - const UnicodeString &pattern, - UBool localized, UParseError &perror, UErrorCode &status); - -ValueFormatter &prepareValueFormatter(ValueFormatter &vf) const; -void setMultiplierScale(int32_t s); -int32_t getPatternScale() const; -void setScale(int32_t s) { fScale = s; } -int32_t getScale() const { return fScale; } - -// Updates everything -void updateAll(UErrorCode &status); -void updateAll( - int32_t formattingFlags, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status); - -// Updates from formatting pattern changes -void updateForApplyPattern(UErrorCode &status); -void updateForApplyPatternFavorCurrencyPrecision(UErrorCode &status); - -// Updates from changes to third group of attributes -void updateFormatting(int32_t changedFormattingFields, UErrorCode &status); -void updateFormatting( - int32_t changedFormattingFields, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status); - -// Helper functions for updatePrecision -void updatePrecisionForScientific(); -void updatePrecisionForFixed(); -void extractMinMaxDigits(DigitInterval &min, DigitInterval &max) const; -void extractSigDigits(SignificantDigitInterval &sig) const; - -// Helper functions for updateFormatting -void updateFormattingUsesCurrency(int32_t &changedFormattingFields); -void updateFormattingPluralRules( - int32_t &changedFormattingFields, UErrorCode &status); -void updateFormattingAffixParser(int32_t &changedFormattingFields); -void updateFormattingCurrencyAffixInfo( - int32_t &changedFormattingFields, - UBool updatePrecisionBasedOnCurrency, - UErrorCode &status); -void updateFormattingFixedPointFormatter( - int32_t &changedFormattingFields); -void updateFormattingLocalizedPositivePrefix( - int32_t &changedFormattingFields, UErrorCode &status); -void updateFormattingLocalizedPositiveSuffix( - int32_t &changedFormattingFields, UErrorCode &status); -void updateFormattingLocalizedNegativePrefix( - int32_t &changedFormattingFields, UErrorCode &status); -void updateFormattingLocalizedNegativeSuffix( - int32_t &changedFormattingFields, UErrorCode &status); - -int32_t computeExponentPatternLength() const; -int32_t countFractionDigitAndDecimalPatternLength(int32_t fracDigitCount) const; -UnicodeString &toNumberPattern( - UBool hasPadding, int32_t minimumLength, UnicodeString& result) const; - -int32_t getOldFormatWidth() const; -const UnicodeString &getConstSymbol( - DecimalFormatSymbols::ENumberFormatSymbol symbol) const; -UBool isParseFastpath() const; - -friend class DecimalFormat; - -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // DECIMFMTIMPL_H -//eof diff --git a/deps/icu-small/source/i18n/digitaffix.cpp b/deps/icu-small/source/i18n/digitaffix.cpp deleted file mode 100644 index 396df2cf1d75eb..00000000000000 --- a/deps/icu-small/source/i18n/digitaffix.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitaffix.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "digitaffix.h" -#include "fphdlimp.h" -#include "uassert.h" -#include "unistrappender.h" - -U_NAMESPACE_BEGIN - -DigitAffix::DigitAffix() : fAffix(), fAnnotations() { -} - -DigitAffix::DigitAffix( - const UChar *value, int32_t charCount, int32_t fieldId) - : fAffix(value, charCount), - fAnnotations(charCount, (UChar) fieldId, charCount) { -} - -void -DigitAffix::remove() { - fAffix.remove(); - fAnnotations.remove(); -} - -void -DigitAffix::appendUChar(UChar value, int32_t fieldId) { - fAffix.append(value); - fAnnotations.append((UChar) fieldId); -} - -void -DigitAffix::append(const UnicodeString &value, int32_t fieldId) { - fAffix.append(value); - { - UnicodeStringAppender appender(fAnnotations); - int32_t len = value.length(); - for (int32_t i = 0; i < len; ++i) { - appender.append((UChar) fieldId); - } - } -} - -void -DigitAffix::setTo(const UnicodeString &value, int32_t fieldId) { - fAffix = value; - fAnnotations.remove(); - { - UnicodeStringAppender appender(fAnnotations); - int32_t len = value.length(); - for (int32_t i = 0; i < len; ++i) { - appender.append((UChar) fieldId); - } - } -} - -void -DigitAffix::append(const UChar *value, int32_t charCount, int32_t fieldId) { - fAffix.append(value, charCount); - { - UnicodeStringAppender appender(fAnnotations); - for (int32_t i = 0; i < charCount; ++i) { - appender.append((UChar) fieldId); - } - } -} - -UnicodeString & -DigitAffix::format(FieldPositionHandler &handler, UnicodeString &appendTo) const { - int32_t len = fAffix.length(); - if (len == 0) { - return appendTo; - } - if (!handler.isRecording()) { - return appendTo.append(fAffix); - } - U_ASSERT(fAffix.length() == fAnnotations.length()); - int32_t appendToStart = appendTo.length(); - int32_t lastId = (int32_t) fAnnotations.charAt(0); - int32_t lastIdStart = 0; - for (int32_t i = 1; i < len; ++i) { - int32_t id = (int32_t) fAnnotations.charAt(i); - if (id != lastId) { - if (lastId != UNUM_FIELD_COUNT) { - handler.addAttribute(lastId, appendToStart + lastIdStart, appendToStart + i); - } - lastId = id; - lastIdStart = i; - } - } - if (lastId != UNUM_FIELD_COUNT) { - handler.addAttribute(lastId, appendToStart + lastIdStart, appendToStart + len); - } - return appendTo.append(fAffix); -} - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/digitaffix.h b/deps/icu-small/source/i18n/digitaffix.h deleted file mode 100644 index 005c36f8488d8f..00000000000000 --- a/deps/icu-small/source/i18n/digitaffix.h +++ /dev/null @@ -1,104 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitaffix.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __DIGITAFFIX_H__ -#define __DIGITAFFIX_H__ - -#include "unicode/uobject.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unistr.h" -#include "unicode/unum.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -class FieldPositionHandler; - -/** - * A prefix or suffix of a formatted number. - */ -class U_I18N_API DigitAffix : public UMemory { -public: - - /** - * Creates an empty DigitAffix. - */ - DigitAffix(); - - /** - * Creates a DigitAffix containing given UChars where all of it has - * a field type of fieldId. - */ - DigitAffix( - const UChar *value, - int32_t charCount, - int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Makes this affix be the empty string. - */ - void remove(); - - /** - * Append value to this affix. If fieldId is present, the appended - * string is considered to be the type fieldId. - */ - void appendUChar(UChar value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append value to this affix. If fieldId is present, the appended - * string is considered to be the type fieldId. - */ - void append(const UnicodeString &value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Sets this affix to given string. The entire string - * is considered to be the type fieldId. - */ - void setTo(const UnicodeString &value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append value to this affix. If fieldId is present, the appended - * string is considered to be the type fieldId. - */ - void append(const UChar *value, int32_t charCount, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Formats this affix. - */ - UnicodeString &format( - FieldPositionHandler &handler, UnicodeString &appendTo) const; - int32_t countChar32() const { return fAffix.countChar32(); } - - /** - * Returns this affix as a unicode string. - */ - const UnicodeString & toString() const { return fAffix; } - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const DigitAffix &rhs) const { - return ((fAffix == rhs.fAffix) && (fAnnotations == rhs.fAnnotations)); - } -private: - UnicodeString fAffix; - UnicodeString fAnnotations; -}; - - -U_NAMESPACE_END -#endif // #if !UCONFIG_NO_FORMATTING -#endif // __DIGITAFFIX_H__ diff --git a/deps/icu-small/source/i18n/digitaffixesandpadding.cpp b/deps/icu-small/source/i18n/digitaffixesandpadding.cpp deleted file mode 100644 index 487d9a345d3e21..00000000000000 --- a/deps/icu-small/source/i18n/digitaffixesandpadding.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitaffixesandpadding.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/plurrule.h" -#include "charstr.h" -#include "digitaffix.h" -#include "digitaffixesandpadding.h" -#include "digitlst.h" -#include "uassert.h" -#include "valueformatter.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -UBool -DigitAffixesAndPadding::needsPluralRules() const { - return ( - fPositivePrefix.hasMultipleVariants() || - fPositiveSuffix.hasMultipleVariants() || - fNegativePrefix.hasMultipleVariants() || - fNegativeSuffix.hasMultipleVariants()); -} - -UnicodeString & -DigitAffixesAndPadding::formatInt32( - int32_t value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return appendTo; - } - if (optPluralRules != NULL || fWidth > 0 || !formatter.isFastFormattable(value)) { - VisibleDigitsWithExponent digits; - formatter.toVisibleDigitsWithExponent( - (int64_t) value, digits, status); - return format( - digits, - formatter, - handler, - optPluralRules, - appendTo, - status); - } - UBool bPositive = value >= 0; - const DigitAffix *prefix = bPositive ? &fPositivePrefix.getOtherVariant() : &fNegativePrefix.getOtherVariant(); - const DigitAffix *suffix = bPositive ? &fPositiveSuffix.getOtherVariant() : &fNegativeSuffix.getOtherVariant(); - if (value < 0) { - value = -value; - } - prefix->format(handler, appendTo); - formatter.formatInt32(value, handler, appendTo); - return suffix->format(handler, appendTo); -} - -static UnicodeString & -formatAffix( - const DigitAffix *affix, - FieldPositionHandler &handler, - UnicodeString &appendTo) { - if (affix) { - affix->format(handler, appendTo); - } - return appendTo; -} - -static int32_t -countAffixChar32(const DigitAffix *affix) { - if (affix) { - return affix->countChar32(); - } - return 0; -} - -UnicodeString & -DigitAffixesAndPadding::format( - const VisibleDigitsWithExponent &digits, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return appendTo; - } - const DigitAffix *prefix = NULL; - const DigitAffix *suffix = NULL; - if (!digits.isNaN()) { - UBool bPositive = !digits.isNegative(); - const PluralAffix *pluralPrefix = bPositive ? &fPositivePrefix : &fNegativePrefix; - const PluralAffix *pluralSuffix = bPositive ? &fPositiveSuffix : &fNegativeSuffix; - if (optPluralRules == NULL || digits.isInfinite()) { - prefix = &pluralPrefix->getOtherVariant(); - suffix = &pluralSuffix->getOtherVariant(); - } else { - UnicodeString count(optPluralRules->select(digits)); - prefix = &pluralPrefix->getByCategory(count); - suffix = &pluralSuffix->getByCategory(count); - } - } - if (fWidth <= 0) { - formatAffix(prefix, handler, appendTo); - formatter.format(digits, handler, appendTo); - return formatAffix(suffix, handler, appendTo); - } - int32_t codePointCount = countAffixChar32(prefix) + formatter.countChar32(digits) + countAffixChar32(suffix); - int32_t paddingCount = fWidth - codePointCount; - switch (fPadPosition) { - case kPadBeforePrefix: - appendPadding(paddingCount, appendTo); - formatAffix(prefix, handler, appendTo); - formatter.format(digits, handler, appendTo); - return formatAffix(suffix, handler, appendTo); - case kPadAfterPrefix: - formatAffix(prefix, handler, appendTo); - appendPadding(paddingCount, appendTo); - formatter.format(digits, handler, appendTo); - return formatAffix(suffix, handler, appendTo); - case kPadBeforeSuffix: - formatAffix(prefix, handler, appendTo); - formatter.format(digits, handler, appendTo); - appendPadding(paddingCount, appendTo); - return formatAffix(suffix, handler, appendTo); - case kPadAfterSuffix: - formatAffix(prefix, handler, appendTo); - formatter.format(digits, handler, appendTo); - formatAffix(suffix, handler, appendTo); - return appendPadding(paddingCount, appendTo); - default: - U_ASSERT(FALSE); - return appendTo; - } -} - -UnicodeString & -DigitAffixesAndPadding::format( - DigitList &value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const { - VisibleDigitsWithExponent digits; - formatter.toVisibleDigitsWithExponent( - value, digits, status); - if (U_FAILURE(status)) { - return appendTo; - } - return format( - digits, formatter, handler, optPluralRules, appendTo, status); -} - -UnicodeString & -DigitAffixesAndPadding::appendPadding(int32_t paddingCount, UnicodeString &appendTo) const { - for (int32_t i = 0; i < paddingCount; ++i) { - appendTo.append(fPadChar); - } - return appendTo; -} - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/digitaffixesandpadding.h b/deps/icu-small/source/i18n/digitaffixesandpadding.h deleted file mode 100644 index d570599d180e77..00000000000000 --- a/deps/icu-small/source/i18n/digitaffixesandpadding.h +++ /dev/null @@ -1,179 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitaffixesandpadding.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __DIGITAFFIXESANDPADDING_H__ -#define __DIGITAFFIXESANDPADDING_H__ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" -#include "pluralaffix.h" - -U_NAMESPACE_BEGIN - -class DigitList; -class ValueFormatter; -class UnicodeString; -class FieldPositionHandler; -class PluralRules; -class VisibleDigitsWithExponent; - -/** - * A formatter of numbers. This class can format any numerical value - * except for not a number (NaN), positive infinity, and negative infinity. - * This class manages prefixes, suffixes, and padding but delegates the - * formatting of actual positive values to a ValueFormatter. - */ -class U_I18N_API DigitAffixesAndPadding : public UMemory { -public: - -/** - * Equivalent to DecimalFormat EPadPosition, but redeclared here to prevent - * depending on DecimalFormat which would cause a circular dependency. - */ -enum EPadPosition { - kPadBeforePrefix, - kPadAfterPrefix, - kPadBeforeSuffix, - kPadAfterSuffix -}; - -/** - * The positive prefix - */ -PluralAffix fPositivePrefix; - -/** - * The positive suffix - */ -PluralAffix fPositiveSuffix; - -/** - * The negative suffix - */ -PluralAffix fNegativePrefix; - -/** - * The negative suffix - */ -PluralAffix fNegativeSuffix; - -/** - * The padding position - */ -EPadPosition fPadPosition; - -/** - * The padding character. - */ -UChar32 fPadChar; - -/** - * The field width in code points. The format method inserts instances of - * the padding character as needed in the desired padding position so that - * the entire formatted string contains this many code points. If the - * formatted string already exceeds this many code points, the format method - * inserts no padding. - */ -int32_t fWidth; - -/** - * Pad position is before prefix; padding character is '*' field width is 0. - * The affixes are all the empty string with no annotated fields with just - * the 'other' plural variation. - */ -DigitAffixesAndPadding() - : fPadPosition(kPadBeforePrefix), fPadChar(0x2a), fWidth(0) { } - -/** - * Returns TRUE if this object is equal to rhs. - */ -UBool equals(const DigitAffixesAndPadding &rhs) const { - return (fPositivePrefix.equals(rhs.fPositivePrefix) && - fPositiveSuffix.equals(rhs.fPositiveSuffix) && - fNegativePrefix.equals(rhs.fNegativePrefix) && - fNegativeSuffix.equals(rhs.fNegativeSuffix) && - fPadPosition == rhs.fPadPosition && - fWidth == rhs.fWidth && - fPadChar == rhs.fPadChar); -} - -/** - * Returns TRUE if a plural rules instance is needed to complete the - * formatting by detecting if any of the affixes have multiple plural - * variations. - */ -UBool needsPluralRules() const; - -/** - * Formats value and appends to appendTo. - * - * @param value the value to format. May be NaN or ininite. - * @param formatter handles the details of formatting the actual value. - * @param handler records field positions - * @param optPluralRules the plural rules, but may be NULL if - * needsPluralRules returns FALSE. - * @appendTo formatted string appended here. - * @status any error returned here. - */ -UnicodeString &format( - const VisibleDigitsWithExponent &value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const; - -/** - * For testing only. - */ -UnicodeString &format( - DigitList &value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const; - -/** - * Formats a 32-bit integer and appends to appendTo. When formatting an - * integer, this method is preferred to plain format as it can run - * several times faster under certain conditions. - * - * @param value the value to format. - * @param formatter handles the details of formatting the actual value. - * @param handler records field positions - * @param optPluralRules the plural rules, but may be NULL if - * needsPluralRules returns FALSE. - * @appendTo formatted string appended here. - * @status any error returned here. - */ -UnicodeString &formatInt32( - int32_t value, - const ValueFormatter &formatter, - FieldPositionHandler &handler, - const PluralRules *optPluralRules, - UnicodeString &appendTo, - UErrorCode &status) const; - -private: -UnicodeString &appendPadding(int32_t paddingCount, UnicodeString &appendTo) const; - -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __DIGITAFFIXANDPADDING_H__ diff --git a/deps/icu-small/source/i18n/digitformatter.cpp b/deps/icu-small/source/i18n/digitformatter.cpp deleted file mode 100644 index 0d857f8f6873c6..00000000000000 --- a/deps/icu-small/source/i18n/digitformatter.cpp +++ /dev/null @@ -1,417 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitformatter.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/dcfmtsym.h" -#include "unicode/unum.h" - -#include "digitformatter.h" -#include "digitgrouping.h" -#include "digitinterval.h" -#include "digitlst.h" -#include "fphdlimp.h" -#include "smallintformatter.h" -#include "unistrappender.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -DigitFormatter::DigitFormatter() - : fGroupingSeparator(",", -1, US_INV), fDecimal(".", -1, US_INV), - fNegativeSign("-", -1, US_INV), fPositiveSign("+", -1, US_INV), - fIsStandardDigits(TRUE), fExponent("E", -1, US_INV) { - for (int32_t i = 0; i < 10; ++i) { - fLocalizedDigits[i] = (UChar32) (0x30 + i); - } - fInfinity.setTo(UnicodeString("Inf", -1, US_INV), UNUM_INTEGER_FIELD); - fNan.setTo(UnicodeString("Nan", -1, US_INV), UNUM_INTEGER_FIELD); -} - -DigitFormatter::DigitFormatter(const DecimalFormatSymbols &symbols) { - setDecimalFormatSymbols(symbols); -} - -void -DigitFormatter::setOtherDecimalFormatSymbols( - const DecimalFormatSymbols &symbols) { - fLocalizedDigits[0] = symbols.getConstSymbol(DecimalFormatSymbols::kZeroDigitSymbol).char32At(0); - fLocalizedDigits[1] = symbols.getConstSymbol(DecimalFormatSymbols::kOneDigitSymbol).char32At(0); - fLocalizedDigits[2] = symbols.getConstSymbol(DecimalFormatSymbols::kTwoDigitSymbol).char32At(0); - fLocalizedDigits[3] = symbols.getConstSymbol(DecimalFormatSymbols::kThreeDigitSymbol).char32At(0); - fLocalizedDigits[4] = symbols.getConstSymbol(DecimalFormatSymbols::kFourDigitSymbol).char32At(0); - fLocalizedDigits[5] = symbols.getConstSymbol(DecimalFormatSymbols::kFiveDigitSymbol).char32At(0); - fLocalizedDigits[6] = symbols.getConstSymbol(DecimalFormatSymbols::kSixDigitSymbol).char32At(0); - fLocalizedDigits[7] = symbols.getConstSymbol(DecimalFormatSymbols::kSevenDigitSymbol).char32At(0); - fLocalizedDigits[8] = symbols.getConstSymbol(DecimalFormatSymbols::kEightDigitSymbol).char32At(0); - fLocalizedDigits[9] = symbols.getConstSymbol(DecimalFormatSymbols::kNineDigitSymbol).char32At(0); - fIsStandardDigits = isStandardDigits(); - fNegativeSign = symbols.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); - fPositiveSign = symbols.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); - fInfinity.setTo(symbols.getConstSymbol(DecimalFormatSymbols::kInfinitySymbol), UNUM_INTEGER_FIELD); - fNan.setTo(symbols.getConstSymbol(DecimalFormatSymbols::kNaNSymbol), UNUM_INTEGER_FIELD); - fExponent = symbols.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol); -} - -void -DigitFormatter::setDecimalFormatSymbolsForMonetary( - const DecimalFormatSymbols &symbols) { - setOtherDecimalFormatSymbols(symbols); - fGroupingSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol); - fDecimal = symbols.getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); -} - -void -DigitFormatter::setDecimalFormatSymbols( - const DecimalFormatSymbols &symbols) { - setOtherDecimalFormatSymbols(symbols); - fGroupingSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); - fDecimal = symbols.getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); -} - -static void appendField( - int32_t fieldId, - const UnicodeString &value, - FieldPositionHandler &handler, - UnicodeString &appendTo) { - int32_t currentLength = appendTo.length(); - appendTo.append(value); - handler.addAttribute( - fieldId, - currentLength, - appendTo.length()); -} - -int32_t DigitFormatter::countChar32( - const DigitGrouping &grouping, - const DigitInterval &interval, - const DigitFormatterOptions &options) const { - int32_t result = interval.length(); - - // We always emit '0' in lieu of no digits. - if (result == 0) { - result = 1; - } - if (options.fAlwaysShowDecimal || interval.getLeastSignificantInclusive() < 0) { - result += fDecimal.countChar32(); - } - result += grouping.getSeparatorCount(interval.getIntDigitCount()) * fGroupingSeparator.countChar32(); - return result; -} - -int32_t -DigitFormatter::countChar32( - const VisibleDigits &digits, - const DigitGrouping &grouping, - const DigitFormatterOptions &options) const { - if (digits.isNaN()) { - return countChar32ForNaN(); - } - if (digits.isInfinite()) { - return countChar32ForInfinity(); - } - return countChar32( - grouping, - digits.getInterval(), - options); -} - -int32_t -DigitFormatter::countChar32( - const VisibleDigitsWithExponent &digits, - const SciFormatterOptions &options) const { - if (digits.isNaN()) { - return countChar32ForNaN(); - } - if (digits.isInfinite()) { - return countChar32ForInfinity(); - } - const VisibleDigits *exponent = digits.getExponent(); - if (exponent == NULL) { - DigitGrouping grouping; - return countChar32( - grouping, - digits.getMantissa().getInterval(), - options.fMantissa); - } - return countChar32( - *exponent, digits.getMantissa().getInterval(), options); -} - -int32_t -DigitFormatter::countChar32( - const VisibleDigits &exponent, - const DigitInterval &mantissaInterval, - const SciFormatterOptions &options) const { - DigitGrouping grouping; - int32_t count = countChar32( - grouping, mantissaInterval, options.fMantissa); - count += fExponent.countChar32(); - count += countChar32ForExponent( - exponent, options.fExponent); - return count; -} - -UnicodeString &DigitFormatter::format( - const VisibleDigits &digits, - const DigitGrouping &grouping, - const DigitFormatterOptions &options, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - if (digits.isNaN()) { - return formatNaN(handler, appendTo); - } - if (digits.isInfinite()) { - return formatInfinity(handler, appendTo); - } - - const DigitInterval &interval = digits.getInterval(); - int32_t digitsLeftOfDecimal = interval.getMostSignificantExclusive(); - int32_t lastDigitPos = interval.getLeastSignificantInclusive(); - int32_t intBegin = appendTo.length(); - int32_t fracBegin = 0; /* initialize to avoid compiler warning */ - - // Emit "0" instead of empty string. - if (digitsLeftOfDecimal == 0 && lastDigitPos == 0) { - appendTo.append(fLocalizedDigits[0]); - handler.addAttribute(UNUM_INTEGER_FIELD, intBegin, appendTo.length()); - if (options.fAlwaysShowDecimal) { - appendField( - UNUM_DECIMAL_SEPARATOR_FIELD, - fDecimal, - handler, - appendTo); - } - return appendTo; - } - { - UnicodeStringAppender appender(appendTo); - for (int32_t i = interval.getMostSignificantExclusive() - 1; - i >= interval.getLeastSignificantInclusive(); --i) { - if (i == -1) { - appender.flush(); - appendField( - UNUM_DECIMAL_SEPARATOR_FIELD, - fDecimal, - handler, - appendTo); - fracBegin = appendTo.length(); - } - appender.append(fLocalizedDigits[digits.getDigitByExponent(i)]); - if (grouping.isSeparatorAt(digitsLeftOfDecimal, i)) { - appender.flush(); - appendField( - UNUM_GROUPING_SEPARATOR_FIELD, - fGroupingSeparator, - handler, - appendTo); - } - if (i == 0) { - appender.flush(); - if (digitsLeftOfDecimal > 0) { - handler.addAttribute(UNUM_INTEGER_FIELD, intBegin, appendTo.length()); - } - } - } - if (options.fAlwaysShowDecimal && lastDigitPos == 0) { - appender.flush(); - appendField( - UNUM_DECIMAL_SEPARATOR_FIELD, - fDecimal, - handler, - appendTo); - } - } - // lastDigitPos is never > 0 so we are guaranteed that kIntegerField - // is already added. - if (lastDigitPos < 0) { - handler.addAttribute(UNUM_FRACTION_FIELD, fracBegin, appendTo.length()); - } - return appendTo; -} - -UnicodeString & -DigitFormatter::format( - const VisibleDigitsWithExponent &digits, - const SciFormatterOptions &options, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - DigitGrouping grouping; - format( - digits.getMantissa(), - grouping, - options.fMantissa, - handler, - appendTo); - const VisibleDigits *exponent = digits.getExponent(); - if (exponent == NULL) { - return appendTo; - } - int32_t expBegin = appendTo.length(); - appendTo.append(fExponent); - handler.addAttribute( - UNUM_EXPONENT_SYMBOL_FIELD, expBegin, appendTo.length()); - return formatExponent( - *exponent, - options.fExponent, - UNUM_EXPONENT_SIGN_FIELD, - UNUM_EXPONENT_FIELD, - handler, - appendTo); -} - -static int32_t formatInt( - int32_t value, uint8_t *digits) { - int32_t idx = 0; - while (value > 0) { - digits[idx++] = (uint8_t) (value % 10); - value /= 10; - } - return idx; -} - -UnicodeString & -DigitFormatter::formatDigits( - const uint8_t *digits, - int32_t count, - const IntDigitCountRange &range, - int32_t intField, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - int32_t i = range.pin(count) - 1; - int32_t begin = appendTo.length(); - - // Always emit '0' as placeholder for empty string. - if (i == -1) { - appendTo.append(fLocalizedDigits[0]); - handler.addAttribute(intField, begin, appendTo.length()); - return appendTo; - } - { - UnicodeStringAppender appender(appendTo); - for (; i >= count; --i) { - appender.append(fLocalizedDigits[0]); - } - for (; i >= 0; --i) { - appender.append(fLocalizedDigits[digits[i]]); - } - } - handler.addAttribute(intField, begin, appendTo.length()); - return appendTo; -} - -UnicodeString & -DigitFormatter::formatExponent( - const VisibleDigits &digits, - const DigitFormatterIntOptions &options, - int32_t signField, - int32_t intField, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - UBool neg = digits.isNegative(); - if (neg || options.fAlwaysShowSign) { - appendField( - signField, - neg ? fNegativeSign : fPositiveSign, - handler, - appendTo); - } - int32_t begin = appendTo.length(); - DigitGrouping grouping; - DigitFormatterOptions expOptions; - FieldPosition fpos(FieldPosition::DONT_CARE); - FieldPositionOnlyHandler noHandler(fpos); - format( - digits, - grouping, - expOptions, - noHandler, - appendTo); - handler.addAttribute(intField, begin, appendTo.length()); - return appendTo; -} - -int32_t -DigitFormatter::countChar32ForExponent( - const VisibleDigits &exponent, - const DigitFormatterIntOptions &options) const { - int32_t result = 0; - UBool neg = exponent.isNegative(); - if (neg || options.fAlwaysShowSign) { - result += neg ? fNegativeSign.countChar32() : fPositiveSign.countChar32(); - } - DigitGrouping grouping; - DigitFormatterOptions expOptions; - result += countChar32(grouping, exponent.getInterval(), expOptions); - return result; -} - -UnicodeString & -DigitFormatter::formatPositiveInt32( - int32_t positiveValue, - const IntDigitCountRange &range, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - // super fast path - if (fIsStandardDigits && SmallIntFormatter::canFormat(positiveValue, range)) { - int32_t begin = appendTo.length(); - SmallIntFormatter::format(positiveValue, range, appendTo); - handler.addAttribute(UNUM_INTEGER_FIELD, begin, appendTo.length()); - return appendTo; - } - uint8_t digits[10]; - int32_t count = formatInt(positiveValue, digits); - return formatDigits( - digits, - count, - range, - UNUM_INTEGER_FIELD, - handler, - appendTo); -} - -UBool DigitFormatter::isStandardDigits() const { - UChar32 cdigit = 0x30; - for (int32_t i = 0; i < UPRV_LENGTHOF(fLocalizedDigits); ++i) { - if (fLocalizedDigits[i] != cdigit) { - return FALSE; - } - ++cdigit; - } - return TRUE; -} - -UBool -DigitFormatter::equals(const DigitFormatter &rhs) const { - UBool result = (fGroupingSeparator == rhs.fGroupingSeparator) && - (fDecimal == rhs.fDecimal) && - (fNegativeSign == rhs.fNegativeSign) && - (fPositiveSign == rhs.fPositiveSign) && - (fInfinity.equals(rhs.fInfinity)) && - (fNan.equals(rhs.fNan)) && - (fIsStandardDigits == rhs.fIsStandardDigits) && - (fExponent == rhs.fExponent); - - if (!result) { - return FALSE; - } - for (int32_t i = 0; i < UPRV_LENGTHOF(fLocalizedDigits); ++i) { - if (fLocalizedDigits[i] != rhs.fLocalizedDigits[i]) { - return FALSE; - } - } - return TRUE; -} - - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/digitformatter.h b/deps/icu-small/source/i18n/digitformatter.h deleted file mode 100644 index 54a54c3639a629..00000000000000 --- a/deps/icu-small/source/i18n/digitformatter.h +++ /dev/null @@ -1,288 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitformatter.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __DIGITFORMATTER_H__ -#define __DIGITFORMATTER_H__ - -#include "unicode/uobject.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/utypes.h" -#include "unicode/unistr.h" -#include "digitaffix.h" - -U_NAMESPACE_BEGIN - -class DecimalFormatSymbols; -class DigitList; -class DigitGrouping; -class DigitInterval; -class UnicodeString; -class FieldPositionHandler; -class IntDigitCountRange; -class VisibleDigits; -class VisibleDigitsWithExponent; - -/** - * Various options for formatting in fixed point. - */ -class U_I18N_API DigitFormatterOptions : public UMemory { - public: - DigitFormatterOptions() : fAlwaysShowDecimal(FALSE) { } - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const DigitFormatterOptions &rhs) const { - return ( - fAlwaysShowDecimal == rhs.fAlwaysShowDecimal); - } - - /** - * Returns TRUE if these options allow for fast formatting of - * integers. - */ - UBool isFastFormattable() const { - return (fAlwaysShowDecimal == FALSE); - } - - /** - * If TRUE, show the decimal separator even when there are no fraction - * digits. default is FALSE. - */ - UBool fAlwaysShowDecimal; -}; - -/** - * Various options for formatting an integer. - */ -class U_I18N_API DigitFormatterIntOptions : public UMemory { - public: - DigitFormatterIntOptions() : fAlwaysShowSign(FALSE) { } - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const DigitFormatterIntOptions &rhs) const { - return (fAlwaysShowSign == rhs.fAlwaysShowSign); - } - - /** - * If TRUE, always prefix the integer with its sign even if the number is - * positive. Default is FALSE. - */ - UBool fAlwaysShowSign; -}; - -/** - * Options for formatting in scientific notation. - */ -class U_I18N_API SciFormatterOptions : public UMemory { - public: - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const SciFormatterOptions &rhs) const { - return (fMantissa.equals(rhs.fMantissa) && - fExponent.equals(rhs.fExponent)); - } - - /** - * Options for formatting the mantissa. - */ - DigitFormatterOptions fMantissa; - - /** - * Options for formatting the exponent. - */ - DigitFormatterIntOptions fExponent; -}; - - -/** - * Does fixed point formatting. - * - * This class only does fixed point formatting. It does no rounding before - * formatting. - */ -class U_I18N_API DigitFormatter : public UMemory { -public: - -/** - * Decimal separator is period (.), Plus sign is plus (+), - * minus sign is minus (-), grouping separator is comma (,), digits are 0-9. - */ -DigitFormatter(); - -/** - * Let symbols determine the digits, decimal separator, - * plus and mius sign, grouping separator, and possibly other settings. - */ -DigitFormatter(const DecimalFormatSymbols &symbols); - -/** - * Change what this instance uses for digits, decimal separator, - * plus and mius sign, grouping separator, and possibly other settings - * according to symbols. - */ -void setDecimalFormatSymbols(const DecimalFormatSymbols &symbols); - -/** - * Change what this instance uses for digits, decimal separator, - * plus and mius sign, grouping separator, and possibly other settings - * according to symbols in the context of monetary amounts. - */ -void setDecimalFormatSymbolsForMonetary(const DecimalFormatSymbols &symbols); - -/** - * Fixed point formatting. - * - * @param positiveDigits the value to format - * Negative sign can be present, but it won't show. - * @param grouping controls how digit grouping is done - * @param options formatting options - * @param handler records field positions - * @param appendTo formatted value appended here. - * @return appendTo - */ -UnicodeString &format( - const VisibleDigits &positiveDigits, - const DigitGrouping &grouping, - const DigitFormatterOptions &options, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -/** - * formats in scientifc notation. - * @param positiveDigits the value to format. - * Negative sign can be present, but it won't show. - * @param options formatting options - * @param handler records field positions. - * @param appendTo formatted value appended here. - */ -UnicodeString &format( - const VisibleDigitsWithExponent &positiveDigits, - const SciFormatterOptions &options, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -/** - * Fixed point formatting of integers. - * Always performed with no grouping and no decimal point. - * - * @param positiveValue the value to format must be positive. - * @param range specifies minimum and maximum number of digits. - * @param handler records field positions - * @param appendTo formatted value appended here. - * @return appendTo - */ -UnicodeString &formatPositiveInt32( - int32_t positiveValue, - const IntDigitCountRange &range, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -/** - * Counts how many code points are needed for fixed formatting. - * If digits is negative, the negative sign is not included in the count. - */ -int32_t countChar32( - const VisibleDigits &digits, - const DigitGrouping &grouping, - const DigitFormatterOptions &options) const; - -/** - * Counts how many code points are needed for scientific formatting. - * If digits is negative, the negative sign is not included in the count. - */ -int32_t countChar32( - const VisibleDigitsWithExponent &digits, - const SciFormatterOptions &options) const; - -/** - * Returns TRUE if this object equals rhs. - */ -UBool equals(const DigitFormatter &rhs) const; - -private: -UChar32 fLocalizedDigits[10]; -UnicodeString fGroupingSeparator; -UnicodeString fDecimal; -UnicodeString fNegativeSign; -UnicodeString fPositiveSign; -DigitAffix fInfinity; -DigitAffix fNan; -UBool fIsStandardDigits; -UnicodeString fExponent; -UBool isStandardDigits() const; - -UnicodeString &formatDigits( - const uint8_t *digits, - int32_t count, - const IntDigitCountRange &range, - int32_t intField, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -void setOtherDecimalFormatSymbols(const DecimalFormatSymbols &symbols); - -int32_t countChar32( - const VisibleDigits &exponent, - const DigitInterval &mantissaInterval, - const SciFormatterOptions &options) const; - -UnicodeString &formatNaN( - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - return fNan.format(handler, appendTo); -} - -int32_t countChar32ForNaN() const { - return fNan.toString().countChar32(); -} - -UnicodeString &formatInfinity( - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - return fInfinity.format(handler, appendTo); -} - -int32_t countChar32ForInfinity() const { - return fInfinity.toString().countChar32(); -} - -UnicodeString &formatExponent( - const VisibleDigits &digits, - const DigitFormatterIntOptions &options, - int32_t signField, - int32_t intField, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - -int32_t countChar32( - const DigitGrouping &grouping, - const DigitInterval &interval, - const DigitFormatterOptions &options) const; - -int32_t countChar32ForExponent( - const VisibleDigits &exponent, - const DigitFormatterIntOptions &options) const; - -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __DIGITFORMATTER_H__ diff --git a/deps/icu-small/source/i18n/digitgrouping.cpp b/deps/icu-small/source/i18n/digitgrouping.cpp deleted file mode 100644 index cffa122b6ceaf6..00000000000000 --- a/deps/icu-small/source/i18n/digitgrouping.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitgrouping.cpp - */ - -#include "unicode/utypes.h" - -#include "digitgrouping.h" -#include "smallintformatter.h" - -U_NAMESPACE_BEGIN - -UBool DigitGrouping::isSeparatorAt( - int32_t digitsLeftOfDecimal, int32_t digitPos) const { - if (!isGroupingEnabled(digitsLeftOfDecimal) || digitPos < fGrouping) { - return FALSE; - } - return ((digitPos - fGrouping) % getGrouping2() == 0); -} - -int32_t DigitGrouping::getSeparatorCount(int32_t digitsLeftOfDecimal) const { - if (!isGroupingEnabled(digitsLeftOfDecimal)) { - return 0; - } - return (digitsLeftOfDecimal - 1 - fGrouping) / getGrouping2() + 1; -} - -UBool DigitGrouping::isGroupingEnabled(int32_t digitsLeftOfDecimal) const { - return (isGroupingUsed() - && digitsLeftOfDecimal >= fGrouping + getMinGrouping()); -} - -UBool DigitGrouping::isNoGrouping( - int32_t positiveValue, const IntDigitCountRange &range) const { - return getSeparatorCount( - SmallIntFormatter::estimateDigitCount(positiveValue, range)) == 0; -} - -int32_t DigitGrouping::getGrouping2() const { - return (fGrouping2 > 0 ? fGrouping2 : fGrouping); -} - -int32_t DigitGrouping::getMinGrouping() const { - return (fMinGrouping > 0 ? fMinGrouping : 1); -} - -void -DigitGrouping::clear() { - fMinGrouping = 0; - fGrouping = 0; - fGrouping2 = 0; -} - -U_NAMESPACE_END diff --git a/deps/icu-small/source/i18n/digitgrouping.h b/deps/icu-small/source/i18n/digitgrouping.h deleted file mode 100644 index f3f8679b879e95..00000000000000 --- a/deps/icu-small/source/i18n/digitgrouping.h +++ /dev/null @@ -1,112 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitgrouping.h -* -* created on: 2015jan6 -* created by: Travis Keep -*/ - -#ifndef __DIGITGROUPING_H__ -#define __DIGITGROUPING_H__ - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -class IntDigitCountRange; - -/** - * The digit grouping policy. - */ -class U_I18N_API DigitGrouping : public UMemory { -public: - /** - * Default is no digit grouping. - */ - DigitGrouping() : fGrouping(0), fGrouping2(0), fMinGrouping(0) { } - - /** - * Returns TRUE if this object is equal to rhs. - */ - UBool equals(const DigitGrouping &rhs) const { - return ((fGrouping == rhs.fGrouping) && - (fGrouping2 == rhs.fGrouping2) && - (fMinGrouping == rhs.fMinGrouping)); - } - - /** - * Returns true if a separator is needed after a particular digit. - * @param digitsLeftOfDecimal the total count of digits left of the - * decimal. - * @param digitPos 0 is the one's place; 1 is the 10's place; -1 is the - * 1/10's place etc. - */ - UBool isSeparatorAt(int32_t digitsLeftOfDecimal, int32_t digitPos) const; - - /** - * Returns the total number of separators to be used to format a particular - * number. - * @param digitsLeftOfDecimal the total number of digits to the left of - * the decimal. - */ - int32_t getSeparatorCount(int32_t digitsLeftOfDecimal) const; - - /** - * Returns true if grouping is used FALSE otherwise. When - * isGroupingUsed() returns FALSE; isSeparatorAt always returns FALSE - * and getSeparatorCount always returns 0. - */ - UBool isGroupingUsed() const { return fGrouping > 0; } - - /** - * Returns TRUE if this instance would not add grouping separators - * when formatting value using the given constraint on digit count. - * - * @param value the value to format. - * @param range the minimum and maximum digits for formatting value. - */ - UBool isNoGrouping( - int32_t positiveValue, const IntDigitCountRange &range) const; - - /** - * Clears this instance so that digit grouping is not in effect. - */ - void clear(); - -public: - - /** - * Primary grouping size. A value of 0, the default, or a negative - * number causes isGroupingUsed() to return FALSE. - */ - int32_t fGrouping; - - /** - * Secondary grouping size. If > 0, this size is used instead of - * 'fGrouping' for all but the group just to the left of the decimal - * point. The default value of 0, or a negative value indicates that - * there is no secondary grouping size. - */ - int32_t fGrouping2; - - /** - * If set (that is > 0), uses no grouping separators if fewer than - * (fGrouping + fMinGrouping) digits appear left of the decimal place. - * The default value for this field is 0. - */ - int32_t fMinGrouping; -private: - UBool isGroupingEnabled(int32_t digitsLeftOfDecimal) const; - int32_t getGrouping2() const; - int32_t getMinGrouping() const; -}; - -U_NAMESPACE_END - -#endif // __DIGITGROUPING_H__ diff --git a/deps/icu-small/source/i18n/digitinterval.cpp b/deps/icu-small/source/i18n/digitinterval.cpp deleted file mode 100644 index 32d952e0267950..00000000000000 --- a/deps/icu-small/source/i18n/digitinterval.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: digitinterval.cpp - */ - -#include "unicode/utypes.h" - -#include "digitinterval.h" - -U_NAMESPACE_BEGIN - -void DigitInterval::expandToContain(const DigitInterval &rhs) { - if (fSmallestInclusive > rhs.fSmallestInclusive) { - fSmallestInclusive = rhs.fSmallestInclusive; - } - if (fLargestExclusive < rhs.fLargestExclusive) { - fLargestExclusive = rhs.fLargestExclusive; - } -} - -void DigitInterval::shrinkToFitWithin(const DigitInterval &rhs) { - if (fSmallestInclusive < rhs.fSmallestInclusive) { - fSmallestInclusive = rhs.fSmallestInclusive; - } - if (fLargestExclusive > rhs.fLargestExclusive) { - fLargestExclusive = rhs.fLargestExclusive; - } -} - -void DigitInterval::setIntDigitCount(int32_t count) { - fLargestExclusive = count < 0 ? INT32_MAX : count; -} - -void DigitInterval::setFracDigitCount(int32_t count) { - fSmallestInclusive = count < 0 ? INT32_MIN : -count; -} - -void DigitInterval::expandToContainDigit(int32_t digitExponent) { - if (fLargestExclusive <= digitExponent) { - fLargestExclusive = digitExponent + 1; - } else if (fSmallestInclusive > digitExponent) { - fSmallestInclusive = digitExponent; - } -} - -UBool DigitInterval::contains(int32_t x) const { - return (x < fLargestExclusive && x >= fSmallestInclusive); -} - - -U_NAMESPACE_END diff --git a/deps/icu-small/source/i18n/digitinterval.h b/deps/icu-small/source/i18n/digitinterval.h deleted file mode 100644 index 95d406da206f4c..00000000000000 --- a/deps/icu-small/source/i18n/digitinterval.h +++ /dev/null @@ -1,159 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* digitinterval.h -* -* created on: 2015jan6 -* created by: Travis Keep -*/ - -#ifndef __DIGITINTERVAL_H__ -#define __DIGITINTERVAL_H__ - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -/** - * An interval of digits. - * DigitIntervals are for fixed point formatting. A DigitInterval specifies - * zero or more integer digits and zero or more fractional digits. This class - * specifies particular digits in a number by their power of 10. For example, - * the digit position just to the left of the decimal is 0, and the digit - * position just left of that is 1. The digit position just to the right of - * the decimal is -1. The digit position just to the right of that is -2. - */ -class U_I18N_API DigitInterval : public UMemory { -public: - - /** - * Spans all integer and fraction digits - */ - DigitInterval() - : fLargestExclusive(INT32_MAX), fSmallestInclusive(INT32_MIN) { } - - - /** - * Makes this instance span all digits. - */ - void clear() { - fLargestExclusive = INT32_MAX; - fSmallestInclusive = INT32_MIN; - } - - /** - * Returns TRUE if this interval contains this digit position. - */ - UBool contains(int32_t digitPosition) const; - - /** - * Returns true if this object is the same as rhs. - */ - UBool equals(const DigitInterval &rhs) const { - return ((fLargestExclusive == rhs.fLargestExclusive) && - (fSmallestInclusive == rhs.fSmallestInclusive)); - } - - /** - * Expand this interval so that it contains all of rhs. - */ - void expandToContain(const DigitInterval &rhs); - - /** - * Shrink this interval so that it contains no more than rhs. - */ - void shrinkToFitWithin(const DigitInterval &rhs); - - /** - * Expand this interval as necessary to contain digit with given exponent - * After this method returns, this interval is guaranteed to contain - * digitExponent. - */ - void expandToContainDigit(int32_t digitExponent); - - /** - * Changes the number of digits to the left of the decimal point that - * this interval spans. If count is negative, it means span all digits - * to the left of the decimal point. - */ - void setIntDigitCount(int32_t count); - - /** - * Changes the number of digits to the right of the decimal point that - * this interval spans. If count is negative, it means span all digits - * to the right of the decimal point. - */ - void setFracDigitCount(int32_t count); - - /** - * Sets the least significant inclusive value to smallest. If smallest >= 0 - * then least significant inclusive value becomes 0. - */ - void setLeastSignificantInclusive(int32_t smallest) { - fSmallestInclusive = smallest < 0 ? smallest : 0; - } - - /** - * Sets the most significant exclusive value to largest. - * If largest <= 0 then most significant exclusive value becomes 0. - */ - void setMostSignificantExclusive(int32_t largest) { - fLargestExclusive = largest > 0 ? largest : 0; - } - - /** - * If returns 8, the most significant digit in interval is the 10^7 digit. - * Returns INT32_MAX if this interval spans all digits to left of - * decimal point. - */ - int32_t getMostSignificantExclusive() const { - return fLargestExclusive; - } - - /** - * Returns number of digits to the left of the decimal that this - * interval includes. This is a synonym for getMostSignificantExclusive(). - */ - int32_t getIntDigitCount() const { - return fLargestExclusive; - } - - /** - * Returns number of digits to the right of the decimal that this - * interval includes. - */ - int32_t getFracDigitCount() const { - return fSmallestInclusive == INT32_MIN ? INT32_MAX : -fSmallestInclusive; - } - - /** - * Returns the total number of digits that this interval spans. - * Caution: If this interval spans all digits to the left or right of - * decimal point instead of some fixed number, then what length() - * returns is undefined. - */ - int32_t length() const { - return fLargestExclusive - fSmallestInclusive; - } - - /** - * If returns -3, the least significant digit in interval is the 10^-3 - * digit. Returns INT32_MIN if this interval spans all digits to right of - * decimal point. - */ - int32_t getLeastSignificantInclusive() const { - return fSmallestInclusive; - } -private: - int32_t fLargestExclusive; - int32_t fSmallestInclusive; -}; - -U_NAMESPACE_END - -#endif // __DIGITINTERVAL_H__ diff --git a/deps/icu-small/source/i18n/digitlst.cpp b/deps/icu-small/source/i18n/digitlst.cpp deleted file mode 100644 index 37760defd708bc..00000000000000 --- a/deps/icu-small/source/i18n/digitlst.cpp +++ /dev/null @@ -1,1143 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -********************************************************************** -* Copyright (C) 1997-2015, International Business Machines -* Corporation and others. All Rights Reserved. -********************************************************************** -* -* File DIGITLST.CPP -* -* Modification History: -* -* Date Name Description -* 03/21/97 clhuang Converted from java. -* 03/21/97 clhuang Implemented with new APIs. -* 03/27/97 helena Updated to pass the simple test after code review. -* 03/31/97 aliu Moved isLONG_MIN to here, and fixed it. -* 04/15/97 aliu Changed MAX_COUNT to DBL_DIG. Changed Digit to char. -* Reworked representation by replacing fDecimalAt -* with fExponent. -* 04/16/97 aliu Rewrote set() and getDouble() to use sprintf/atof -* to do digit conversion. -* 09/09/97 aliu Modified for exponential notation support. -* 08/02/98 stephen Added nearest/even rounding -* Fixed bug in fitsIntoLong -****************************************************************************** -*/ - -#if defined(__CYGWIN__) && !defined(_GNU_SOURCE) -#define _GNU_SOURCE -#endif - -#include "digitlst.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/putil.h" -#include "charstr.h" -#include "cmemory.h" -#include "cstring.h" -#include "mutex.h" -#include "putilimp.h" -#include "uassert.h" -#include "digitinterval.h" -#include "ucln_in.h" -#include "umutex.h" -#include "double-conversion.h" -#include -#include -#include -#include -#include - -using icu::double_conversion::DoubleToStringConverter; - -#if !defined(U_USE_STRTOD_L) -# if U_PLATFORM_USES_ONLY_WIN32_API -# define U_USE_STRTOD_L 1 -# define U_HAVE_XLOCALE_H 0 -# elif defined(U_HAVE_STRTOD_L) -# define U_USE_STRTOD_L U_HAVE_STRTOD_L -# else -# define U_USE_STRTOD_L 0 -# endif -#endif - -#if U_USE_STRTOD_L -# if U_HAVE_XLOCALE_H -# include -# else -# include -# endif -#endif - -// *************************************************************************** -// class DigitList -// A wrapper onto decNumber. -// Used to be standalone. -// *************************************************************************** - -/** - * This is the zero digit. The base for the digits returned by getDigit() - * Note that it is the platform invariant digit, and is not Unicode. - */ -#define kZero '0' - - -/* Only for 32 bit numbers. Ignore the negative sign. */ -//static const char LONG_MIN_REP[] = "2147483648"; -//static const char I64_MIN_REP[] = "9223372036854775808"; - - -U_NAMESPACE_BEGIN - -// ------------------------------------- -// default constructor - -DigitList::DigitList() -{ - uprv_decContextDefault(&fContext, DEC_INIT_BASE); - fContext.traps = 0; - uprv_decContextSetRounding(&fContext, DEC_ROUND_HALF_EVEN); - fContext.digits = fStorage.getCapacity(); - - fDecNumber = fStorage.getAlias(); - uprv_decNumberZero(fDecNumber); - - internalSetDouble(0.0); -} - -// ------------------------------------- - -DigitList::~DigitList() -{ -} - -// ------------------------------------- -// copy constructor - -DigitList::DigitList(const DigitList &other) -{ - fDecNumber = fStorage.getAlias(); - *this = other; -} - - -// ------------------------------------- -// assignment operator - -DigitList& -DigitList::operator=(const DigitList& other) -{ - if (this != &other) - { - uprv_memcpy(&fContext, &other.fContext, sizeof(decContext)); - - if (other.fStorage.getCapacity() > fStorage.getCapacity()) { - fDecNumber = fStorage.resize(other.fStorage.getCapacity()); - } - // Always reset the fContext.digits, even if fDecNumber was not reallocated, - // because above we copied fContext from other.fContext. - fContext.digits = fStorage.getCapacity(); - uprv_decNumberCopy(fDecNumber, other.fDecNumber); - - { - // fDouble is lazily created and cached. - // Avoid potential races with that happening with other.fDouble - // while we are doing the assignment. - Mutex mutex; - - if(other.fHave==kDouble) { - fUnion.fDouble = other.fUnion.fDouble; - } - fHave = other.fHave; - } - } - return *this; -} - -// ------------------------------------- -// operator == (does not exactly match the old DigitList function) - -UBool -DigitList::operator==(const DigitList& that) const -{ - if (this == &that) { - return TRUE; - } - decNumber n; // Has space for only a none digit value. - decContext c; - uprv_decContextDefault(&c, DEC_INIT_BASE); - c.digits = 1; - c.traps = 0; - - uprv_decNumberCompare(&n, this->fDecNumber, that.fDecNumber, &c); - UBool result = decNumberIsZero(&n); - return result; -} - -// ------------------------------------- -// comparison function. Returns -// Not Comparable : -2 -// < : -1 -// == : 0 -// > : +1 -int32_t DigitList::compare(const DigitList &other) { - decNumber result; - int32_t savedDigits = fContext.digits; - fContext.digits = 1; - uprv_decNumberCompare(&result, this->fDecNumber, other.fDecNumber, &fContext); - fContext.digits = savedDigits; - if (decNumberIsZero(&result)) { - return 0; - } else if (decNumberIsSpecial(&result)) { - return -2; - } else if (result.bits & DECNEG) { - return -1; - } else { - return 1; - } -} - - -// ------------------------------------- -// Reduce - remove trailing zero digits. -void -DigitList::reduce() { - uprv_decNumberReduce(fDecNumber, fDecNumber, &fContext); -} - - -// ------------------------------------- -// trim - remove trailing fraction zero digits. -void -DigitList::trim() { - uprv_decNumberTrim(fDecNumber); -} - -// ------------------------------------- -// Resets the digit list; sets all the digits to zero. - -void -DigitList::clear() -{ - uprv_decNumberZero(fDecNumber); - uprv_decContextSetRounding(&fContext, DEC_ROUND_HALF_EVEN); - internalSetDouble(0.0); -} - - -/** - * Formats a int64_t number into a base 10 string representation, and NULL terminates it. - * @param number The number to format - * @param outputStr The string to output to. Must be at least MAX_DIGITS+2 in length (21), - * to hold the longest int64_t value. - * @return the number of digits written, not including the sign. - */ -static int32_t -formatBase10(int64_t number, char *outputStr) { - // The number is output backwards, starting with the LSD. - // Fill the buffer from the far end. After the number is complete, - // slide the string contents to the front. - - const int32_t MAX_IDX = MAX_DIGITS+2; - int32_t destIdx = MAX_IDX; - outputStr[--destIdx] = 0; - - int64_t n = number; - if (number < 0) { // Negative numbers are slightly larger than a postive - outputStr[--destIdx] = (char)(-(n % 10) + kZero); - n /= -10; - } - do { - outputStr[--destIdx] = (char)(n % 10 + kZero); - n /= 10; - } while (n > 0); - - if (number < 0) { - outputStr[--destIdx] = '-'; - } - - // Slide the number to the start of the output str - U_ASSERT(destIdx >= 0); - int32_t length = MAX_IDX - destIdx; - uprv_memmove(outputStr, outputStr+MAX_IDX-length, length); - - return length; -} - - -// ------------------------------------- -// -// setRoundingMode() -// For most modes, the meaning and names are the same between the decNumber library -// (which DigitList follows) and the ICU Formatting Rounding Mode values. -// The flag constants are different, however. -// -// Note that ICU's kRoundingUnnecessary is not implemented directly by DigitList. -// This mode, inherited from Java, means that numbers that would not format exactly -// will return an error when formatting is attempted. - -void -DigitList::setRoundingMode(DecimalFormat::ERoundingMode m) { - enum rounding r; - - switch (m) { - case DecimalFormat::kRoundCeiling: r = DEC_ROUND_CEILING; break; - case DecimalFormat::kRoundFloor: r = DEC_ROUND_FLOOR; break; - case DecimalFormat::kRoundDown: r = DEC_ROUND_DOWN; break; - case DecimalFormat::kRoundUp: r = DEC_ROUND_UP; break; - case DecimalFormat::kRoundHalfEven: r = DEC_ROUND_HALF_EVEN; break; - case DecimalFormat::kRoundHalfDown: r = DEC_ROUND_HALF_DOWN; break; - case DecimalFormat::kRoundHalfUp: r = DEC_ROUND_HALF_UP; break; - case DecimalFormat::kRoundUnnecessary: r = DEC_ROUND_HALF_EVEN; break; - default: - // TODO: how to report the problem? - // Leave existing mode unchanged. - r = uprv_decContextGetRounding(&fContext); - } - uprv_decContextSetRounding(&fContext, r); - -} - - -// ------------------------------------- - -void -DigitList::setPositive(UBool s) { - if (s) { - fDecNumber->bits &= ~DECNEG; - } else { - fDecNumber->bits |= DECNEG; - } - internalClear(); -} -// ------------------------------------- - -void -DigitList::setDecimalAt(int32_t d) { - U_ASSERT((fDecNumber->bits & DECSPECIAL) == 0); // Not Infinity or NaN - U_ASSERT(d-1>-999999999); - U_ASSERT(d-1< 999999999); - int32_t adjustedDigits = fDecNumber->digits; - if (decNumberIsZero(fDecNumber)) { - // Account for difference in how zero is represented between DigitList & decNumber. - adjustedDigits = 0; - } - fDecNumber->exponent = d - adjustedDigits; - internalClear(); -} - -int32_t -DigitList::getDecimalAt() { - U_ASSERT((fDecNumber->bits & DECSPECIAL) == 0); // Not Infinity or NaN - if (decNumberIsZero(fDecNumber) || ((fDecNumber->bits & DECSPECIAL) != 0)) { - return fDecNumber->exponent; // Exponent should be zero for these cases. - } - return fDecNumber->exponent + fDecNumber->digits; -} - -void -DigitList::setCount(int32_t c) { - U_ASSERT(c <= fContext.digits); - if (c == 0) { - // For a value of zero, DigitList sets all fields to zero, while - // decNumber keeps one digit (with that digit being a zero) - c = 1; - fDecNumber->lsu[0] = 0; - } - fDecNumber->digits = c; - internalClear(); -} - -int32_t -DigitList::getCount() const { - if (decNumberIsZero(fDecNumber) && fDecNumber->exponent==0) { - // The extra test for exponent==0 is needed because parsing sometimes appends - // zero digits. It's bogus, decimalFormatter parsing needs to be cleaned up. - return 0; - } else { - return fDecNumber->digits; - } -} - -void -DigitList::setDigit(int32_t i, char v) { - int32_t count = fDecNumber->digits; - U_ASSERT(i='0' && v<='9'); - v &= 0x0f; - fDecNumber->lsu[count-i-1] = v; - internalClear(); -} - -char -DigitList::getDigit(int32_t i) { - int32_t count = fDecNumber->digits; - U_ASSERT(ilsu[count-i-1] + '0'; -} - -// copied from DigitList::getDigit() -uint8_t -DigitList::getDigitValue(int32_t i) { - int32_t count = fDecNumber->digits; - U_ASSERT(ilsu[count-i-1]; -} - -// ------------------------------------- -// Appends the digit to the digit list if it's not out of scope. -// Ignores the digit, otherwise. -// -// This function is horribly inefficient to implement with decNumber because -// the digits are stored least significant first, which requires moving all -// existing digits down one to make space for the new one to be appended. -// -void -DigitList::append(char digit) -{ - U_ASSERT(digit>='0' && digit<='9'); - // Ignore digits which exceed the precision we can represent - // And don't fix for larger precision. Fix callers instead. - if (decNumberIsZero(fDecNumber)) { - // Zero needs to be special cased because of the difference in the way - // that the old DigitList and decNumber represent it. - // digit cout was zero for digitList, is one for decNumber - fDecNumber->lsu[0] = digit & 0x0f; - fDecNumber->digits = 1; - fDecNumber->exponent--; // To match the old digit list implementation. - } else { - int32_t nDigits = fDecNumber->digits; - if (nDigits < fContext.digits) { - int i; - for (i=nDigits; i>0; i--) { - fDecNumber->lsu[i] = fDecNumber->lsu[i-1]; - } - fDecNumber->lsu[0] = digit & 0x0f; - fDecNumber->digits++; - // DigitList emulation - appending doesn't change the magnitude of existing - // digits. With decNumber's decimal being after the - // least signficant digit, we need to adjust the exponent. - fDecNumber->exponent--; - } - } - internalClear(); -} - -// ------------------------------------- - -/** - * Currently, getDouble() depends on strtod() to do its conversion. - * - * WARNING!! - * This is an extremely costly function. ~1/2 of the conversion time - * can be linked to this function. - */ -double -DigitList::getDouble() const -{ - { - Mutex mutex; - if (fHave == kDouble) { - return fUnion.fDouble; - } - } - - double tDouble = 0.0; - if (isZero()) { - tDouble = 0.0; - if (decNumberIsNegative(fDecNumber)) { - tDouble /= -1; - } - } else if (isInfinite()) { - if (std::numeric_limits::has_infinity) { - tDouble = std::numeric_limits::infinity(); - } else { - tDouble = std::numeric_limits::max(); - } - if (!isPositive()) { - tDouble = -tDouble; //this was incorrectly "-fDouble" originally. - } - } else { - MaybeStackArray s; - // Note: 14 is a magic constant from the decNumber library documentation, - // the max number of extra characters beyond the number of digits - // needed to represent the number in string form. Add a few more - // for the additional digits we retain. - - // Round down to appx. double precision, if the number is longer than that. - // Copy the number first, so that we don't modify the original. - if (getCount() > MAX_DBL_DIGITS + 3) { - DigitList numToConvert(*this); - numToConvert.reduce(); // Removes any trailing zeros, so that digit count is good. - numToConvert.round(MAX_DBL_DIGITS+3); - uprv_decNumberToString(numToConvert.fDecNumber, s.getAlias()); - // TODO: how many extra digits should be included for an accurate conversion? - } else { - uprv_decNumberToString(this->fDecNumber, s.getAlias()); - } - U_ASSERT(uprv_strlen(&s[0]) < MAX_DBL_DIGITS+18); - - char *end = NULL; - tDouble = decimalStrToDouble(s.getAlias(), &end); - } - { - Mutex mutex; - DigitList *nonConstThis = const_cast(this); - nonConstThis->internalSetDouble(tDouble); - } - return tDouble; -} - -#if U_USE_STRTOD_L && U_PLATFORM_USES_ONLY_WIN32_API -# define locale_t _locale_t -# define freelocale _free_locale -# define strtod_l _strtod_l -#endif - -#if U_USE_STRTOD_L -static locale_t gCLocale = (locale_t)0; -#endif -static icu::UInitOnce gCLocaleInitOnce = U_INITONCE_INITIALIZER; - -U_CDECL_BEGIN -// Cleanup callback func -static UBool U_CALLCONV digitList_cleanup(void) -{ -#if U_USE_STRTOD_L - if (gCLocale != (locale_t)0) { - freelocale(gCLocale); - } -#endif - return TRUE; -} -// C Locale initialization func -static void U_CALLCONV initCLocale(void) { - ucln_i18n_registerCleanup(UCLN_I18N_DIGITLIST, digitList_cleanup); -#if U_USE_STRTOD_L -# if U_PLATFORM_USES_ONLY_WIN32_API - gCLocale = _create_locale(LC_ALL, "C"); -# else - gCLocale = newlocale(LC_ALL_MASK, "C", (locale_t)0); -# endif -#endif -} -U_CDECL_END - -double -DigitList::decimalStrToDouble(char *decstr, char **end) { - umtx_initOnce(gCLocaleInitOnce, &initCLocale); -#if U_USE_STRTOD_L - return strtod_l(decstr, end, gCLocale); -#else - char *decimalPt = strchr(decstr, '.'); - if (decimalPt) { - // We need to know the decimal separator character that will be used with strtod(). - // Depends on the C runtime global locale. - // Most commonly is '.' - char rep[MAX_DIGITS]; - sprintf(rep, "%+1.1f", 1.0); - *decimalPt = rep[2]; - } - return uprv_strtod(decstr, end); -#endif -} - -// ------------------------------------- - -/** - * convert this number to an int32_t. Round if there is a fractional part. - * Return zero if the number cannot be represented. - */ -int32_t DigitList::getLong() /*const*/ -{ - int32_t result = 0; - if (getUpperExponent() > 10) { - // Overflow, absolute value too big. - return result; - } - if (fDecNumber->exponent != 0) { - // Force to an integer, with zero exponent, rounding if necessary. - // (decNumberToInt32 will only work if the exponent is exactly zero.) - DigitList copy(*this); - DigitList zero; - uprv_decNumberQuantize(copy.fDecNumber, copy.fDecNumber, zero.fDecNumber, &fContext); - result = uprv_decNumberToInt32(copy.fDecNumber, &fContext); - } else { - result = uprv_decNumberToInt32(fDecNumber, &fContext); - } - return result; -} - - -/** - * convert this number to an int64_t. Truncate if there is a fractional part. - * Return zero if the number cannot be represented. - */ -int64_t DigitList::getInt64() /*const*/ { - // TODO: fast conversion if fHave == fDouble - - // Truncate if non-integer. - // Return 0 if out of range. - // Range of in64_t is -9223372036854775808 to 9223372036854775807 (19 digits) - // - if (getUpperExponent() > 19) { - // Overflow, absolute value too big. - return 0; - } - - // The number of integer digits may differ from the number of digits stored - // in the decimal number. - // for 12.345 numIntDigits = 2, number->digits = 5 - // for 12E4 numIntDigits = 6, number->digits = 2 - // The conversion ignores the fraction digits in the first case, - // and fakes up extra zero digits in the second. - // TODO: It would be faster to store a table of powers of ten to multiply by - // instead of looping over zero digits, multiplying each time. - - int32_t numIntDigits = getUpperExponent(); - uint64_t value = 0; - for (int32_t i = 0; i < numIntDigits; i++) { - // Loop is iterating over digits starting with the most significant. - // Numbers are stored with the least significant digit at index zero. - int32_t digitIndex = fDecNumber->digits - i - 1; - int32_t v = (digitIndex >= 0) ? fDecNumber->lsu[digitIndex] : 0; - value = value * (uint64_t)10 + (uint64_t)v; - } - - if (decNumberIsNegative(fDecNumber)) { - value = ~value; - value += 1; - } - int64_t svalue = (int64_t)value; - - // Check overflow. It's convenient that the MSD is 9 only on overflow, the amount of - // overflow can't wrap too far. The test will also fail -0, but - // that does no harm; the right answer is 0. - if (numIntDigits == 19) { - if (( decNumberIsNegative(fDecNumber) && svalue>0) || - (!decNumberIsNegative(fDecNumber) && svalue<0)) { - svalue = 0; - } - } - - return svalue; -} - - -/** - * Return a string form of this number. - * Format is as defined by the decNumber library, for interchange of - * decimal numbers. - */ -void DigitList::getDecimal(CharString &str, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - - // A decimal number in string form can, worst case, be 14 characters longer - // than the number of digits. So says the decNumber library doc. - int32_t maxLength = fDecNumber->digits + 14; - int32_t capacity = 0; - char *buffer = str.clear().getAppendBuffer(maxLength, 0, capacity, status); - if (U_FAILURE(status)) { - return; // Memory allocation error on growing the string. - } - U_ASSERT(capacity >= maxLength); - uprv_decNumberToString(this->fDecNumber, buffer); - U_ASSERT((int32_t)uprv_strlen(buffer) <= maxLength); - str.append(buffer, -1, status); -} - -/** - * Return true if this is an integer value that can be held - * by an int32_t type. - */ -UBool -DigitList::fitsIntoLong(UBool ignoreNegativeZero) /*const*/ -{ - if (decNumberIsSpecial(this->fDecNumber)) { - // NaN or Infinity. Does not fit in int32. - return FALSE; - } - uprv_decNumberTrim(this->fDecNumber); - if (fDecNumber->exponent < 0) { - // Number contains fraction digits. - return FALSE; - } - if (decNumberIsZero(this->fDecNumber) && !ignoreNegativeZero && - (fDecNumber->bits & DECNEG) != 0) { - // Negative Zero, not ingored. Cannot represent as a long. - return FALSE; - } - if (getUpperExponent() < 10) { - // The number is 9 or fewer digits. - // The max and min int32 are 10 digts, so this number fits. - // This is the common case. - return TRUE; - } - - // TODO: Should cache these constants; construction is relatively costly. - // But not of huge consequence; they're only needed for 10 digit ints. - UErrorCode status = U_ZERO_ERROR; - DigitList min32; min32.set("-2147483648", status); - if (this->compare(min32) < 0) { - return FALSE; - } - DigitList max32; max32.set("2147483647", status); - if (this->compare(max32) > 0) { - return FALSE; - } - if (U_FAILURE(status)) { - return FALSE; - } - return true; -} - - - -/** - * Return true if the number represented by this object can fit into - * a long. - */ -UBool -DigitList::fitsIntoInt64(UBool ignoreNegativeZero) /*const*/ -{ - if (decNumberIsSpecial(this->fDecNumber)) { - // NaN or Infinity. Does not fit in int32. - return FALSE; - } - uprv_decNumberTrim(this->fDecNumber); - if (fDecNumber->exponent < 0) { - // Number contains fraction digits. - return FALSE; - } - if (decNumberIsZero(this->fDecNumber) && !ignoreNegativeZero && - (fDecNumber->bits & DECNEG) != 0) { - // Negative Zero, not ingored. Cannot represent as a long. - return FALSE; - } - if (getUpperExponent() < 19) { - // The number is 18 or fewer digits. - // The max and min int64 are 19 digts, so this number fits. - // This is the common case. - return TRUE; - } - - // TODO: Should cache these constants; construction is relatively costly. - // But not of huge consequence; they're only needed for 19 digit ints. - UErrorCode status = U_ZERO_ERROR; - DigitList min64; min64.set("-9223372036854775808", status); - if (this->compare(min64) < 0) { - return FALSE; - } - DigitList max64; max64.set("9223372036854775807", status); - if (this->compare(max64) > 0) { - return FALSE; - } - if (U_FAILURE(status)) { - return FALSE; - } - return true; -} - - -// ------------------------------------- - -void -DigitList::set(int32_t source) -{ - set((int64_t)source); - internalSetDouble(source); -} - -// ------------------------------------- -/** - * Set an int64, via decnumber - */ -void -DigitList::set(int64_t source) -{ - char str[MAX_DIGITS+2]; // Leave room for sign and trailing nul. - formatBase10(source, str); - U_ASSERT(uprv_strlen(str) < sizeof(str)); - - uprv_decNumberFromString(fDecNumber, str, &fContext); - internalSetDouble(static_cast(source)); -} - -// ------------------------------------- -/** - * Set the DigitList from a decimal number string. - * - * The incoming string _must_ be nul terminated, even though it is arriving - * as a StringPiece because that is what the decNumber library wants. - * We can get away with this for an internal function; it would not - * be acceptable for a public API. - */ -void -DigitList::set(StringPiece source, UErrorCode &status, uint32_t /*fastpathBits*/) { - if (U_FAILURE(status)) { - return; - } - -#if 0 - if(fastpathBits==(kFastpathOk|kNoDecimal)) { - int32_t size = source.size(); - const char *data = source.data(); - int64_t r = 0; - int64_t m = 1; - // fast parse - while(size>0) { - char ch = data[--size]; - if(ch=='+') { - break; - } else if(ch=='-') { - r = -r; - break; - } else { - int64_t d = ch-'0'; - //printf("CH[%d]=%c, %d, *=%d\n", size,ch, (int)d, (int)m); - r+=(d)*m; - m *= 10; - } - } - //printf("R=%d\n", r); - set(r); - } else -#endif - { - // Figure out a max number of digits to use during the conversion, and - // resize the number up if necessary. - int32_t numDigits = source.length(); - if (numDigits > fContext.digits) { - // fContext.digits == fStorage.getCapacity() - decNumber *t = fStorage.resize(numDigits, fStorage.getCapacity()); - if (t == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - fDecNumber = t; - fContext.digits = numDigits; - } - - fContext.status = 0; - uprv_decNumberFromString(fDecNumber, source.data(), &fContext); - if ((fContext.status & DEC_Conversion_syntax) != 0) { - status = U_DECIMAL_NUMBER_SYNTAX_ERROR; - } - } - internalClear(); -} - -/** - * Set the digit list to a representation of the given double value. - * This method supports both fixed-point and exponential notation. - * @param source Value to be converted. - */ -void -DigitList::set(double source) -{ - // for now, simple implementation; later, do proper IEEE stuff - char rep[MAX_DIGITS + 8]; // Extra space for '+', '.', e+NNN, and '\0' (actually +8 is enough) - - // Generate a representation of the form /[+-][0-9].[0-9]+e[+-][0-9]+/ - // Can also generate /[+-]nan/ or /[+-]inf/ - // TODO: Use something other than sprintf() here, since it's behavior is somewhat platform specific. - // That is why infinity is special cased here. - if (uprv_isInfinite(source)) { - if (uprv_isNegativeInfinity(source)) { - uprv_strcpy(rep,"-inf"); // Handle negative infinity - } else { - uprv_strcpy(rep,"inf"); - } - } else if (uprv_isNaN(source)) { - uprv_strcpy(rep, "NaN"); - } else { - bool sign; - int32_t length; - int32_t point; - DoubleToStringConverter::DoubleToAscii( - source, - DoubleToStringConverter::DtoaMode::SHORTEST, - 0, - rep + 1, - sizeof(rep), - &sign, - &length, - &point - ); - - // Convert the raw buffer into a string for decNumber - int32_t power = point - length; - if (sign) { - rep[0] = '-'; - } else { - rep[0] = '0'; - } - length++; - rep[length++] = 'E'; - if (power < 0) { - rep[length++] = '-'; - power = -power; - } else { - rep[length++] = '+'; - } - if (power < 10) { - rep[length++] = power + '0'; - } else if (power < 100) { - rep[length++] = (power / 10) + '0'; - rep[length++] = (power % 10) + '0'; - } else { - U_ASSERT(power < 1000); - rep[length + 2] = (power % 10) + '0'; - power /= 10; - rep[length + 1] = (power % 10) + '0'; - power /= 10; - rep[length] = power + '0'; - length += 3; - } - rep[length++] = 0; - } - U_ASSERT(uprv_strlen(rep) < sizeof(rep)); - - // uprv_decNumberFromString() will parse the string expecting '.' as a - // decimal separator, however sprintf() can use ',' in certain locales. - // Overwrite a ',' with '.' here before proceeding. - char *decimalSeparator = strchr(rep, ','); - if (decimalSeparator != NULL) { - *decimalSeparator = '.'; - } - - // Create a decNumber from the string. - uprv_decNumberFromString(fDecNumber, rep, &fContext); - uprv_decNumberTrim(fDecNumber); - internalSetDouble(source); -} - -// ------------------------------------- - -/* - * Multiply - * The number will be expanded if need be to retain full precision. - * In practice, for formatting, multiply is by 10, 100 or 1000, so more digits - * will not be required for this use. - */ -void -DigitList::mult(const DigitList &other, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - fContext.status = 0; - int32_t requiredDigits = this->digits() + other.digits(); - if (requiredDigits > fContext.digits) { - reduce(); // Remove any trailing zeros - int32_t requiredDigits = this->digits() + other.digits(); - ensureCapacity(requiredDigits, status); - } - uprv_decNumberMultiply(fDecNumber, fDecNumber, other.fDecNumber, &fContext); - internalClear(); -} - -// ------------------------------------- - -/* - * Divide - * The number will _not_ be expanded for inexact results. - * TODO: probably should expand some, for rounding increments that - * could add a few digits, e.g. .25, but not expand arbitrarily. - */ -void -DigitList::div(const DigitList &other, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - uprv_decNumberDivide(fDecNumber, fDecNumber, other.fDecNumber, &fContext); - internalClear(); -} - -// ------------------------------------- - -/* - * ensureCapacity. Grow the digit storage for the number if it's less than the requested - * amount. Never reduce it. Available size is kept in fContext.digits. - */ -void -DigitList::ensureCapacity(int32_t requestedCapacity, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - if (requestedCapacity <= 0) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return; - } - if (requestedCapacity > DEC_MAX_DIGITS) { - // Don't report an error for requesting too much. - // Arithemetic Results will be rounded to what can be supported. - // At 999,999,999 max digits, exceeding the limit is not too likely! - requestedCapacity = DEC_MAX_DIGITS; - } - if (requestedCapacity > fContext.digits) { - decNumber *newBuffer = fStorage.resize(requestedCapacity, fStorage.getCapacity()); - if (newBuffer == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - fContext.digits = requestedCapacity; - fDecNumber = newBuffer; - } -} - -// ------------------------------------- - -/** - * Round the representation to the given number of digits. - * @param maximumDigits The maximum number of digits to be shown. - * Upon return, count will be less than or equal to maximumDigits. - */ -void -DigitList::round(int32_t maximumDigits) -{ - reduce(); - if (maximumDigits >= fDecNumber->digits) { - return; - } - int32_t savedDigits = fContext.digits; - fContext.digits = maximumDigits; - uprv_decNumberPlus(fDecNumber, fDecNumber, &fContext); - fContext.digits = savedDigits; - uprv_decNumberTrim(fDecNumber); - reduce(); - internalClear(); -} - - -void -DigitList::roundFixedPoint(int32_t maximumFractionDigits) { - reduce(); // Remove trailing zeros. - if (fDecNumber->exponent >= -maximumFractionDigits) { - return; - } - decNumber scale; // Dummy decimal number, but with the desired number of - uprv_decNumberZero(&scale); // fraction digits. - scale.exponent = -maximumFractionDigits; - scale.lsu[0] = 1; - - uprv_decNumberQuantize(fDecNumber, fDecNumber, &scale, &fContext); - reduce(); - internalClear(); -} - -// ------------------------------------- - -void -DigitList::toIntegralValue() { - uprv_decNumberToIntegralValue(fDecNumber, fDecNumber, &fContext); -} - - -// ------------------------------------- -UBool -DigitList::isZero() const -{ - return decNumberIsZero(fDecNumber); -} - -// ------------------------------------- -int32_t -DigitList::getUpperExponent() const { - return fDecNumber->digits + fDecNumber->exponent; -} - -DigitInterval & -DigitList::getSmallestInterval(DigitInterval &result) const { - result.setLeastSignificantInclusive(fDecNumber->exponent); - result.setMostSignificantExclusive(getUpperExponent()); - return result; -} - -uint8_t -DigitList::getDigitByExponent(int32_t exponent) const { - int32_t idx = exponent - fDecNumber->exponent; - if (idx < 0 || idx >= fDecNumber->digits) { - return 0; - } - return fDecNumber->lsu[idx]; -} - -void -DigitList::appendDigitsTo(CharString &str, UErrorCode &status) const { - str.append((const char *) fDecNumber->lsu, fDecNumber->digits, status); -} - -void -DigitList::roundAtExponent(int32_t exponent, int32_t maxSigDigits) { - reduce(); - if (maxSigDigits < fDecNumber->digits) { - int32_t minExponent = getUpperExponent() - maxSigDigits; - if (exponent < minExponent) { - exponent = minExponent; - } - } - if (exponent <= fDecNumber->exponent) { - return; - } - int32_t digits = getUpperExponent() - exponent; - if (digits > 0) { - round(digits); - } else { - roundFixedPoint(-exponent); - } -} - -void -DigitList::quantize(const DigitList &quantity, UErrorCode &status) { - if (U_FAILURE(status)) { - return; - } - div(quantity, status); - roundAtExponent(0); - mult(quantity, status); - reduce(); -} - -int32_t -DigitList::getScientificExponent( - int32_t minIntDigitCount, int32_t exponentMultiplier) const { - // The exponent for zero is always zero. - if (isZero()) { - return 0; - } - int32_t intDigitCount = getUpperExponent(); - int32_t exponent; - if (intDigitCount >= minIntDigitCount) { - int32_t maxAdjustment = intDigitCount - minIntDigitCount; - exponent = (maxAdjustment / exponentMultiplier) * exponentMultiplier; - } else { - int32_t minAdjustment = minIntDigitCount - intDigitCount; - exponent = ((minAdjustment + exponentMultiplier - 1) / exponentMultiplier) * -exponentMultiplier; - } - return exponent; -} - -int32_t -DigitList::toScientific( - int32_t minIntDigitCount, int32_t exponentMultiplier) { - int32_t exponent = getScientificExponent( - minIntDigitCount, exponentMultiplier); - shiftDecimalRight(-exponent); - return exponent; -} - -void -DigitList::shiftDecimalRight(int32_t n) { - fDecNumber->exponent += n; - internalClear(); -} - -U_NAMESPACE_END -#endif // #if !UCONFIG_NO_FORMATTING - -//eof diff --git a/deps/icu-small/source/i18n/digitlst.h b/deps/icu-small/source/i18n/digitlst.h deleted file mode 100644 index 6befaf32e6f340..00000000000000 --- a/deps/icu-small/source/i18n/digitlst.h +++ /dev/null @@ -1,529 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -****************************************************************************** -* -* Copyright (C) 1997-2015, International Business Machines -* Corporation and others. All Rights Reserved. -* -****************************************************************************** -* -* File DIGITLST.H -* -* Modification History: -* -* Date Name Description -* 02/25/97 aliu Converted from java. -* 03/21/97 clhuang Updated per C++ implementation. -* 04/15/97 aliu Changed MAX_COUNT to DBL_DIG. Changed Digit to char. -* 09/09/97 aliu Adapted for exponential notation support. -* 08/02/98 stephen Added nearest/even rounding -* 06/29/99 stephen Made LONG_DIGITS a macro to satisfy SUN compiler -* 07/09/99 stephen Removed kMaxCount (unused, for HP compiler) -****************************************************************************** -*/ - -#ifndef DIGITLST_H -#define DIGITLST_H - -#include "unicode/uobject.h" - -#if !UCONFIG_NO_FORMATTING -#include "unicode/decimfmt.h" -#include -#include "decContext.h" -#include "decNumber.h" -#include "cmemory.h" - -// Decimal digits in a 64-bit int -#define INT64_DIGITS 19 - -typedef enum EDigitListValues { - MAX_DBL_DIGITS = DBL_DIG, - MAX_I64_DIGITS = INT64_DIGITS, - MAX_DIGITS = MAX_I64_DIGITS, - MAX_EXPONENT = DBL_DIG, - DIGIT_PADDING = 3, - DEFAULT_DIGITS = 40, // Initial storage size, will grow as needed. - - // "+." + fDigits + "e" + fDecimalAt - MAX_DEC_DIGITS = MAX_DIGITS + DIGIT_PADDING + MAX_EXPONENT -} EDigitListValues; - -U_NAMESPACE_BEGIN - -class CharString; -class DigitInterval; - -// Export an explicit template instantiation of the MaybeStackHeaderAndArray that -// is used as a data member of DigitList. -// -// MSVC requires this, even though it should not be necessary. -// No direct access to the MaybeStackHeaderAndArray leaks out of the i18n library. -// -// Macintosh produces duplicate definition linker errors with the explicit template -// instantiation. -// -#if !U_PLATFORM_IS_DARWIN_BASED -template class U_I18N_API MaybeStackHeaderAndArray; -#endif - - -enum EStackMode { kOnStack }; - -enum EFastpathBits { kFastpathOk = 1, kNoDecimal = 2 }; - -/** - * Digit List is actually a Decimal Floating Point number. - * The original implementation has been replaced by a thin wrapper onto a - * decimal number from the decNumber library. - * - * The original DigitList API has been retained, to minimize the impact of - * the change on the rest of the ICU formatting code. - * - * The change to decNumber enables support for big decimal numbers, and - * allows rounding computations to be done directly in decimal, avoiding - * extra, and inaccurate, conversions to and from doubles. - * - * Original DigitList comments: - * - * Digit List utility class. Private to DecimalFormat. Handles the transcoding - * between numeric values and strings of characters. Only handles - * non-negative numbers. The division of labor between DigitList and - * DecimalFormat is that DigitList handles the radix 10 representation - * issues; DecimalFormat handles the locale-specific issues such as - * positive/negative, grouping, decimal point, currency, and so on. - *

- * A DigitList is really a representation of a floating point value. - * It may be an integer value; we assume that a double has sufficient - * precision to represent all digits of a long. - *

- * The DigitList representation consists of a string of characters, - * which are the digits radix 10, from '0' to '9'. It also has a radix - * 10 exponent associated with it. The value represented by a DigitList - * object can be computed by mulitplying the fraction f, where 0 <= f < 1, - * derived by placing all the digits of the list to the right of the - * decimal point, by 10^exponent. - * - * -------- - * - * DigitList vs. decimalNumber: - * - * DigitList stores digits with the most significant first. - * decNumber stores digits with the least significant first. - * - * DigitList, decimal point is before the most significant. - * decNumber, decimal point is after the least signficant digit. - * - * digitList: 0.ddddd * 10 ^ exp - * decNumber: ddddd. * 10 ^ exp - * - * digitList exponent = decNumber exponent + digit count - * - * digitList, digits are platform invariant chars, '0' - '9' - * decNumber, digits are binary, one per byte, 0 - 9. - * - * (decNumber library is configurable in how digits are stored, ICU has configured - * it this way for convenience in replacing the old DigitList implementation.) - */ -class U_I18N_API DigitList : public UMemory { // Declare external to make compiler happy -public: - - DigitList(); - ~DigitList(); - - /* copy constructor - * @param DigitList The object to be copied. - * @return the newly created object. - */ - DigitList(const DigitList&); // copy constructor - - /* assignment operator - * @param DigitList The object to be copied. - * @return the newly created object. - */ - DigitList& operator=(const DigitList&); // assignment operator - - /** - * Return true if another object is semantically equal to this one. - * @param other The DigitList to be compared for equality - * @return true if another object is semantically equal to this one. - * return false otherwise. - */ - UBool operator==(const DigitList& other) const; - - int32_t compare(const DigitList& other); - - - inline UBool operator!=(const DigitList& other) const { return !operator==(other); } - - /** - * Clears out the digits. - * Use before appending them. - * Typically, you set a series of digits with append, then at the point - * you hit the decimal point, you set myDigitList.fDecimalAt = myDigitList.fCount; - * then go on appending digits. - */ - void clear(void); - - /** - * Remove, by rounding, any fractional part of the decimal number, - * leaving an integer value. - */ - void toIntegralValue(); - - /** - * Appends digits to the list. - * CAUTION: this function is not recommended for new code. - * In the original DigitList implementation, decimal numbers were - * parsed by appending them to a digit list as they were encountered. - * With the revamped DigitList based on decNumber, append is very - * inefficient, and the interaction with the exponent value is confusing. - * Best avoided. - * TODO: remove this function once all use has been replaced. - * TODO: describe alternative to append() - * @param digit The digit to be appended. - */ - void append(char digit); - - /** - * Utility routine to get the value of the digit list - * Returns 0.0 if zero length. - * @return the value of the digit list. - */ - double getDouble(void) const; - - /** - * Utility routine to get the value of the digit list - * Make sure that fitsIntoLong() is called before calling this function. - * Returns 0 if zero length. - * @return the value of the digit list, return 0 if it is zero length - */ - int32_t getLong(void) /*const*/; - - /** - * Utility routine to get the value of the digit list - * Make sure that fitsIntoInt64() is called before calling this function. - * Returns 0 if zero length. - * @return the value of the digit list, return 0 if it is zero length - */ - int64_t getInt64(void) /*const*/; - - /** - * Utility routine to get the value of the digit list as a decimal string. - */ - void getDecimal(CharString &str, UErrorCode &status); - - /** - * Return true if the number represented by this object can fit into - * a long. - * @param ignoreNegativeZero True if negative zero is ignored. - * @return true if the number represented by this object can fit into - * a long, return false otherwise. - */ - UBool fitsIntoLong(UBool ignoreNegativeZero) /*const*/; - - /** - * Return true if the number represented by this object can fit into - * an int64_t. - * @param ignoreNegativeZero True if negative zero is ignored. - * @return true if the number represented by this object can fit into - * a long, return false otherwise. - */ - UBool fitsIntoInt64(UBool ignoreNegativeZero) /*const*/; - - /** - * Utility routine to set the value of the digit list from a double. - * @param source The value to be set - */ - void set(double source); - - /** - * Utility routine to set the value of the digit list from a long. - * If a non-zero maximumDigits is specified, no more than that number of - * significant digits will be produced. - * @param source The value to be set - */ - void set(int32_t source); - - /** - * Utility routine to set the value of the digit list from an int64. - * If a non-zero maximumDigits is specified, no more than that number of - * significant digits will be produced. - * @param source The value to be set - */ - void set(int64_t source); - - /** - * Utility routine to set the value of the digit list from an int64. - * Does not set the decnumber unless requested later - * If a non-zero maximumDigits is specified, no more than that number of - * significant digits will be produced. - * @param source The value to be set - */ - void setInteger(int64_t source); - - /** - * Utility routine to set the value of the digit list from a decimal number - * string. - * @param source The value to be set. The string must be nul-terminated. - * @param fastpathBits special flags for fast parsing - */ - void set(StringPiece source, UErrorCode &status, uint32_t fastpathBits = 0); - - /** - * Multiply this = this * arg - * This digitlist will be expanded if necessary to accomodate the result. - * @param arg the number to multiply by. - */ - void mult(const DigitList &arg, UErrorCode &status); - - /** - * Divide this = this / arg - */ - void div(const DigitList &arg, UErrorCode &status); - - // The following functions replace direct access to the original DigitList implmentation - // data structures. - - void setRoundingMode(DecimalFormat::ERoundingMode m); - - /** Test a number for zero. - * @return TRUE if the number is zero - */ - UBool isZero(void) const; - - /** Test for a Nan - * @return TRUE if the number is a NaN - */ - UBool isNaN(void) const {return decNumberIsNaN(fDecNumber);} - - UBool isInfinite() const {return decNumberIsInfinite(fDecNumber);} - - /** Reduce, or normalize. Removes trailing zeroes, adjusts exponent appropriately. */ - void reduce(); - - /** Remove trailing fraction zeros, adjust exponent accordingly. */ - void trim(); - - /** Set to zero */ - void setToZero() {uprv_decNumberZero(fDecNumber);} - - /** get the number of digits in the decimal number */ - int32_t digits() const {return fDecNumber->digits;} - - /** - * Round the number to the given number of digits. - * @param maximumDigits The maximum number of digits to be shown. - * Upon return, count will be less than or equal to maximumDigits. - * result is guaranteed to be trimmed. - */ - void round(int32_t maximumDigits); - - void roundFixedPoint(int32_t maximumFractionDigits); - - /** Ensure capacity for digits. Grow the storage if it is currently less than - * the requested size. Capacity is not reduced if it is already greater - * than requested. - */ - void ensureCapacity(int32_t requestedSize, UErrorCode &status); - - UBool isPositive(void) const { return decNumberIsNegative(fDecNumber) == 0;} - void setPositive(UBool s); - - void setDecimalAt(int32_t d); - int32_t getDecimalAt(); - - void setCount(int32_t c); - int32_t getCount() const; - - /** - * Set the digit in platform (invariant) format, from '0'..'9' - * @param i index of digit - * @param v digit value, from '0' to '9' in platform invariant format - */ - void setDigit(int32_t i, char v); - - /** - * Get the digit in platform (invariant) format, from '0'..'9' inclusive - * @param i index of digit - * @return invariant format of the digit - */ - char getDigit(int32_t i); - - - /** - * Get the digit's value, as an integer from 0..9 inclusive. - * Note that internally this value is a decNumberUnit, but ICU configures it to be a uint8_t. - * @param i index of digit - * @return value of that digit - */ - uint8_t getDigitValue(int32_t i); - - /** - * Gets the upper bound exponent for this value. For 987, returns 3 - * because 10^3 is the smallest power of 10 that is just greater than - * 987. - */ - int32_t getUpperExponent() const; - - /** - * Gets the lower bound exponent for this value. For 98.7, returns -1 - * because the right most digit, is the 10^-1 place. - */ - int32_t getLowerExponent() const { return fDecNumber->exponent; } - - /** - * Sets result to the smallest DigitInterval needed to display this - * DigitList in fixed point form and returns result. - */ - DigitInterval& getSmallestInterval(DigitInterval &result) const; - - /** - * Like getDigitValue, but the digit is identified by exponent. - * For example, getDigitByExponent(7) returns the 10^7 place of this - * DigitList. Unlike getDigitValue, there are no upper or lower bounds - * for passed parameter. Instead, getDigitByExponent returns 0 if - * the exponent falls outside the interval for this DigitList. - */ - uint8_t getDigitByExponent(int32_t exponent) const; - - /** - * Appends the digits in this object to a CharString. - * 3 is appended as (char) 3, not '3' - */ - void appendDigitsTo(CharString &str, UErrorCode &status) const; - - /** - * Equivalent to roundFixedPoint(-digitExponent) except unlike - * roundFixedPoint, this works for any digitExponent value. - * If maxSigDigits is set then this instance is rounded to have no more - * than maxSigDigits. The end result is guaranteed to be trimmed. - */ - void roundAtExponent(int32_t digitExponent, int32_t maxSigDigits=INT32_MAX); - - /** - * Quantizes according to some amount and rounds according to the - * context of this instance. Quantizing 3.233 with 0.05 gives 3.25. - */ - void quantize(const DigitList &amount, UErrorCode &status); - - /** - * Like toScientific but only returns the exponent - * leaving this instance unchanged. - */ - int32_t getScientificExponent( - int32_t minIntDigitCount, int32_t exponentMultiplier) const; - - /** - * Converts this instance to scientific notation. This instance - * becomes the mantissa and the exponent is returned. - * @param minIntDigitCount minimum integer digits in mantissa - * Exponent is set so that the actual number of integer digits - * in mantissa is as close to the minimum as possible. - * @param exponentMultiplier The exponent is always a multiple of - * This number. Usually 1, but set to 3 for engineering notation. - * @return exponent - */ - int32_t toScientific( - int32_t minIntDigitCount, int32_t exponentMultiplier); - - /** - * Shifts decimal to the right. - */ - void shiftDecimalRight(int32_t numPlaces); - -private: - /* - * These data members are intentionally public and can be set directly. - *

- * The value represented is given by placing the decimal point before - * fDigits[fDecimalAt]. If fDecimalAt is < 0, then leading zeros between - * the decimal point and the first nonzero digit are implied. If fDecimalAt - * is > fCount, then trailing zeros between the fDigits[fCount-1] and the - * decimal point are implied. - *

- * Equivalently, the represented value is given by f * 10^fDecimalAt. Here - * f is a value 0.1 <= f < 1 arrived at by placing the digits in fDigits to - * the right of the decimal. - *

- * DigitList is normalized, so if it is non-zero, fDigits[0] is non-zero. We - * don't allow denormalized numbers because our exponent is effectively of - * unlimited magnitude. The fCount value contains the number of significant - * digits present in fDigits[]. - *

- * Zero is represented by any DigitList with fCount == 0 or with each fDigits[i] - * for all i <= fCount == '0'. - * - * int32_t fDecimalAt; - * int32_t fCount; - * UBool fIsPositive; - * char *fDigits; - * DecimalFormat::ERoundingMode fRoundingMode; - */ - -public: - decContext fContext; // public access to status flags. - -private: - decNumber *fDecNumber; - MaybeStackHeaderAndArray fStorage; - - /* Cached double value corresponding to this decimal number. - * This is an optimization for the formatting implementation, which may - * ask for the double value multiple times. - */ - union DoubleOrInt64 { - double fDouble; - int64_t fInt64; - } fUnion; - enum EHave { - kNone=0, - kDouble - } fHave; - - - - UBool shouldRoundUp(int32_t maximumDigits) const; - - public: - -#if U_OVERRIDE_CXX_ALLOCATION - using UMemory::operator new; - using UMemory::operator delete; -#else - static inline void * U_EXPORT2 operator new(size_t size) U_NO_THROW { return ::operator new(size); }; - static inline void U_EXPORT2 operator delete(void *ptr ) U_NO_THROW { ::operator delete(ptr); }; -#endif - - static double U_EXPORT2 decimalStrToDouble(char *decstr, char **end); - - /** - * Placement new for stack usage - * @internal - */ - static inline void * U_EXPORT2 operator new(size_t /*size*/, void * onStack, EStackMode /*mode*/) U_NO_THROW { return onStack; } - - /** - * Placement delete for stack usage - * @internal - */ - static inline void U_EXPORT2 operator delete(void * /*ptr*/, void * /*onStack*/, EStackMode /*mode*/) U_NO_THROW {} - - private: - inline void internalSetDouble(double d) { - fHave = kDouble; - fUnion.fDouble=d; - } - inline void internalClear() { - fHave = kNone; - } -}; - - -U_NAMESPACE_END - -#endif // #if !UCONFIG_NO_FORMATTING -#endif // _DIGITLST - -//eof diff --git a/deps/icu-small/source/i18n/double-conversion-strtod.cpp b/deps/icu-small/source/i18n/double-conversion-strtod.cpp new file mode 100644 index 00000000000000..be9b0b3bce0e76 --- /dev/null +++ b/deps/icu-small/source/i18n/double-conversion-strtod.cpp @@ -0,0 +1,574 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +// +// From the double-conversion library. Original license: +// +// Copyright 2010 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ICU PATCH: ifdef around UCONFIG_NO_FORMATTING +#include "unicode/utypes.h" +#if !UCONFIG_NO_FORMATTING + +#include +#include + +// ICU PATCH: Customize header file paths for ICU. +// The file fixed-dtoa.h is not needed. + +#include "double-conversion-strtod.h" +#include "double-conversion-bignum.h" +#include "double-conversion-cached-powers.h" +#include "double-conversion-ieee.h" + +// ICU PATCH: Wrap in ICU namespace +U_NAMESPACE_BEGIN + +namespace double_conversion { + +// 2^53 = 9007199254740992. +// Any integer with at most 15 decimal digits will hence fit into a double +// (which has a 53bit significand) without loss of precision. +static const int kMaxExactDoubleIntegerDecimalDigits = 15; +// 2^64 = 18446744073709551616 > 10^19 +static const int kMaxUint64DecimalDigits = 19; + +// Max double: 1.7976931348623157 x 10^308 +// Min non-zero double: 4.9406564584124654 x 10^-324 +// Any x >= 10^309 is interpreted as +infinity. +// Any x <= 10^-324 is interpreted as 0. +// Note that 2.5e-324 (despite being smaller than the min double) will be read +// as non-zero (equal to the min non-zero double). +static const int kMaxDecimalPower = 309; +static const int kMinDecimalPower = -324; + +// 2^64 = 18446744073709551616 +static const uint64_t kMaxUint64 = UINT64_2PART_C(0xFFFFFFFF, FFFFFFFF); + + +static const double exact_powers_of_ten[] = { + 1.0, // 10^0 + 10.0, + 100.0, + 1000.0, + 10000.0, + 100000.0, + 1000000.0, + 10000000.0, + 100000000.0, + 1000000000.0, + 10000000000.0, // 10^10 + 100000000000.0, + 1000000000000.0, + 10000000000000.0, + 100000000000000.0, + 1000000000000000.0, + 10000000000000000.0, + 100000000000000000.0, + 1000000000000000000.0, + 10000000000000000000.0, + 100000000000000000000.0, // 10^20 + 1000000000000000000000.0, + // 10^22 = 0x21e19e0c9bab2400000 = 0x878678326eac9 * 2^22 + 10000000000000000000000.0 +}; +static const int kExactPowersOfTenSize = ARRAY_SIZE(exact_powers_of_ten); + +// Maximum number of significant digits in the decimal representation. +// In fact the value is 772 (see conversions.cc), but to give us some margin +// we round up to 780. +static const int kMaxSignificantDecimalDigits = 780; + +static Vector TrimLeadingZeros(Vector buffer) { + for (int i = 0; i < buffer.length(); i++) { + if (buffer[i] != '0') { + return buffer.SubVector(i, buffer.length()); + } + } + return Vector(buffer.start(), 0); +} + + +static Vector TrimTrailingZeros(Vector buffer) { + for (int i = buffer.length() - 1; i >= 0; --i) { + if (buffer[i] != '0') { + return buffer.SubVector(0, i + 1); + } + } + return Vector(buffer.start(), 0); +} + + +static void CutToMaxSignificantDigits(Vector buffer, + int exponent, + char* significant_buffer, + int* significant_exponent) { + for (int i = 0; i < kMaxSignificantDecimalDigits - 1; ++i) { + significant_buffer[i] = buffer[i]; + } + // The input buffer has been trimmed. Therefore the last digit must be + // different from '0'. + ASSERT(buffer[buffer.length() - 1] != '0'); + // Set the last digit to be non-zero. This is sufficient to guarantee + // correct rounding. + significant_buffer[kMaxSignificantDecimalDigits - 1] = '1'; + *significant_exponent = + exponent + (buffer.length() - kMaxSignificantDecimalDigits); +} + + +// Trims the buffer and cuts it to at most kMaxSignificantDecimalDigits. +// If possible the input-buffer is reused, but if the buffer needs to be +// modified (due to cutting), then the input needs to be copied into the +// buffer_copy_space. +static void TrimAndCut(Vector buffer, int exponent, + char* buffer_copy_space, int space_size, + Vector* trimmed, int* updated_exponent) { + Vector left_trimmed = TrimLeadingZeros(buffer); + Vector right_trimmed = TrimTrailingZeros(left_trimmed); + exponent += left_trimmed.length() - right_trimmed.length(); + if (right_trimmed.length() > kMaxSignificantDecimalDigits) { + (void) space_size; // Mark variable as used. + ASSERT(space_size >= kMaxSignificantDecimalDigits); + CutToMaxSignificantDigits(right_trimmed, exponent, + buffer_copy_space, updated_exponent); + *trimmed = Vector(buffer_copy_space, + kMaxSignificantDecimalDigits); + } else { + *trimmed = right_trimmed; + *updated_exponent = exponent; + } +} + + +// Reads digits from the buffer and converts them to a uint64. +// Reads in as many digits as fit into a uint64. +// When the string starts with "1844674407370955161" no further digit is read. +// Since 2^64 = 18446744073709551616 it would still be possible read another +// digit if it was less or equal than 6, but this would complicate the code. +static uint64_t ReadUint64(Vector buffer, + int* number_of_read_digits) { + uint64_t result = 0; + int i = 0; + while (i < buffer.length() && result <= (kMaxUint64 / 10 - 1)) { + int digit = buffer[i++] - '0'; + ASSERT(0 <= digit && digit <= 9); + result = 10 * result + digit; + } + *number_of_read_digits = i; + return result; +} + + +// Reads a DiyFp from the buffer. +// The returned DiyFp is not necessarily normalized. +// If remaining_decimals is zero then the returned DiyFp is accurate. +// Otherwise it has been rounded and has error of at most 1/2 ulp. +static void ReadDiyFp(Vector buffer, + DiyFp* result, + int* remaining_decimals) { + int read_digits; + uint64_t significand = ReadUint64(buffer, &read_digits); + if (buffer.length() == read_digits) { + *result = DiyFp(significand, 0); + *remaining_decimals = 0; + } else { + // Round the significand. + if (buffer[read_digits] >= '5') { + significand++; + } + // Compute the binary exponent. + int exponent = 0; + *result = DiyFp(significand, exponent); + *remaining_decimals = buffer.length() - read_digits; + } +} + + +static bool DoubleStrtod(Vector trimmed, + int exponent, + double* result) { +#if !defined(DOUBLE_CONVERSION_CORRECT_DOUBLE_OPERATIONS) + // On x86 the floating-point stack can be 64 or 80 bits wide. If it is + // 80 bits wide (as is the case on Linux) then double-rounding occurs and the + // result is not accurate. + // We know that Windows32 uses 64 bits and is therefore accurate. + // Note that the ARM simulator is compiled for 32bits. It therefore exhibits + // the same problem. + return false; +#endif + if (trimmed.length() <= kMaxExactDoubleIntegerDecimalDigits) { + int read_digits; + // The trimmed input fits into a double. + // If the 10^exponent (resp. 10^-exponent) fits into a double too then we + // can compute the result-double simply by multiplying (resp. dividing) the + // two numbers. + // This is possible because IEEE guarantees that floating-point operations + // return the best possible approximation. + if (exponent < 0 && -exponent < kExactPowersOfTenSize) { + // 10^-exponent fits into a double. + *result = static_cast(ReadUint64(trimmed, &read_digits)); + ASSERT(read_digits == trimmed.length()); + *result /= exact_powers_of_ten[-exponent]; + return true; + } + if (0 <= exponent && exponent < kExactPowersOfTenSize) { + // 10^exponent fits into a double. + *result = static_cast(ReadUint64(trimmed, &read_digits)); + ASSERT(read_digits == trimmed.length()); + *result *= exact_powers_of_ten[exponent]; + return true; + } + int remaining_digits = + kMaxExactDoubleIntegerDecimalDigits - trimmed.length(); + if ((0 <= exponent) && + (exponent - remaining_digits < kExactPowersOfTenSize)) { + // The trimmed string was short and we can multiply it with + // 10^remaining_digits. As a result the remaining exponent now fits + // into a double too. + *result = static_cast(ReadUint64(trimmed, &read_digits)); + ASSERT(read_digits == trimmed.length()); + *result *= exact_powers_of_ten[remaining_digits]; + *result *= exact_powers_of_ten[exponent - remaining_digits]; + return true; + } + } + return false; +} + + +// Returns 10^exponent as an exact DiyFp. +// The given exponent must be in the range [1; kDecimalExponentDistance[. +static DiyFp AdjustmentPowerOfTen(int exponent) { + ASSERT(0 < exponent); + ASSERT(exponent < PowersOfTenCache::kDecimalExponentDistance); + // Simply hardcode the remaining powers for the given decimal exponent + // distance. + ASSERT(PowersOfTenCache::kDecimalExponentDistance == 8); + switch (exponent) { + case 1: return DiyFp(UINT64_2PART_C(0xa0000000, 00000000), -60); + case 2: return DiyFp(UINT64_2PART_C(0xc8000000, 00000000), -57); + case 3: return DiyFp(UINT64_2PART_C(0xfa000000, 00000000), -54); + case 4: return DiyFp(UINT64_2PART_C(0x9c400000, 00000000), -50); + case 5: return DiyFp(UINT64_2PART_C(0xc3500000, 00000000), -47); + case 6: return DiyFp(UINT64_2PART_C(0xf4240000, 00000000), -44); + case 7: return DiyFp(UINT64_2PART_C(0x98968000, 00000000), -40); + default: + UNREACHABLE(); + } +} + + +// If the function returns true then the result is the correct double. +// Otherwise it is either the correct double or the double that is just below +// the correct double. +static bool DiyFpStrtod(Vector buffer, + int exponent, + double* result) { + DiyFp input; + int remaining_decimals; + ReadDiyFp(buffer, &input, &remaining_decimals); + // Since we may have dropped some digits the input is not accurate. + // If remaining_decimals is different than 0 than the error is at most + // .5 ulp (unit in the last place). + // We don't want to deal with fractions and therefore keep a common + // denominator. + const int kDenominatorLog = 3; + const int kDenominator = 1 << kDenominatorLog; + // Move the remaining decimals into the exponent. + exponent += remaining_decimals; + uint64_t error = (remaining_decimals == 0 ? 0 : kDenominator / 2); + + int old_e = input.e(); + input.Normalize(); + error <<= old_e - input.e(); + + ASSERT(exponent <= PowersOfTenCache::kMaxDecimalExponent); + if (exponent < PowersOfTenCache::kMinDecimalExponent) { + *result = 0.0; + return true; + } + DiyFp cached_power; + int cached_decimal_exponent; + PowersOfTenCache::GetCachedPowerForDecimalExponent(exponent, + &cached_power, + &cached_decimal_exponent); + + if (cached_decimal_exponent != exponent) { + int adjustment_exponent = exponent - cached_decimal_exponent; + DiyFp adjustment_power = AdjustmentPowerOfTen(adjustment_exponent); + input.Multiply(adjustment_power); + if (kMaxUint64DecimalDigits - buffer.length() >= adjustment_exponent) { + // The product of input with the adjustment power fits into a 64 bit + // integer. + ASSERT(DiyFp::kSignificandSize == 64); + } else { + // The adjustment power is exact. There is hence only an error of 0.5. + error += kDenominator / 2; + } + } + + input.Multiply(cached_power); + // The error introduced by a multiplication of a*b equals + // error_a + error_b + error_a*error_b/2^64 + 0.5 + // Substituting a with 'input' and b with 'cached_power' we have + // error_b = 0.5 (all cached powers have an error of less than 0.5 ulp), + // error_ab = 0 or 1 / kDenominator > error_a*error_b/ 2^64 + int error_b = kDenominator / 2; + int error_ab = (error == 0 ? 0 : 1); // We round up to 1. + int fixed_error = kDenominator / 2; + error += error_b + error_ab + fixed_error; + + old_e = input.e(); + input.Normalize(); + error <<= old_e - input.e(); + + // See if the double's significand changes if we add/subtract the error. + int order_of_magnitude = DiyFp::kSignificandSize + input.e(); + int effective_significand_size = + Double::SignificandSizeForOrderOfMagnitude(order_of_magnitude); + int precision_digits_count = + DiyFp::kSignificandSize - effective_significand_size; + if (precision_digits_count + kDenominatorLog >= DiyFp::kSignificandSize) { + // This can only happen for very small denormals. In this case the + // half-way multiplied by the denominator exceeds the range of an uint64. + // Simply shift everything to the right. + int shift_amount = (precision_digits_count + kDenominatorLog) - + DiyFp::kSignificandSize + 1; + input.set_f(input.f() >> shift_amount); + input.set_e(input.e() + shift_amount); + // We add 1 for the lost precision of error, and kDenominator for + // the lost precision of input.f(). + error = (error >> shift_amount) + 1 + kDenominator; + precision_digits_count -= shift_amount; + } + // We use uint64_ts now. This only works if the DiyFp uses uint64_ts too. + ASSERT(DiyFp::kSignificandSize == 64); + ASSERT(precision_digits_count < 64); + uint64_t one64 = 1; + uint64_t precision_bits_mask = (one64 << precision_digits_count) - 1; + uint64_t precision_bits = input.f() & precision_bits_mask; + uint64_t half_way = one64 << (precision_digits_count - 1); + precision_bits *= kDenominator; + half_way *= kDenominator; + DiyFp rounded_input(input.f() >> precision_digits_count, + input.e() + precision_digits_count); + if (precision_bits >= half_way + error) { + rounded_input.set_f(rounded_input.f() + 1); + } + // If the last_bits are too close to the half-way case than we are too + // inaccurate and round down. In this case we return false so that we can + // fall back to a more precise algorithm. + + *result = Double(rounded_input).value(); + if (half_way - error < precision_bits && precision_bits < half_way + error) { + // Too imprecise. The caller will have to fall back to a slower version. + // However the returned number is guaranteed to be either the correct + // double, or the next-lower double. + return false; + } else { + return true; + } +} + + +// Returns +// - -1 if buffer*10^exponent < diy_fp. +// - 0 if buffer*10^exponent == diy_fp. +// - +1 if buffer*10^exponent > diy_fp. +// Preconditions: +// buffer.length() + exponent <= kMaxDecimalPower + 1 +// buffer.length() + exponent > kMinDecimalPower +// buffer.length() <= kMaxDecimalSignificantDigits +static int CompareBufferWithDiyFp(Vector buffer, + int exponent, + DiyFp diy_fp) { + ASSERT(buffer.length() + exponent <= kMaxDecimalPower + 1); + ASSERT(buffer.length() + exponent > kMinDecimalPower); + ASSERT(buffer.length() <= kMaxSignificantDecimalDigits); + // Make sure that the Bignum will be able to hold all our numbers. + // Our Bignum implementation has a separate field for exponents. Shifts will + // consume at most one bigit (< 64 bits). + // ln(10) == 3.3219... + ASSERT(((kMaxDecimalPower + 1) * 333 / 100) < Bignum::kMaxSignificantBits); + Bignum buffer_bignum; + Bignum diy_fp_bignum; + buffer_bignum.AssignDecimalString(buffer); + diy_fp_bignum.AssignUInt64(diy_fp.f()); + if (exponent >= 0) { + buffer_bignum.MultiplyByPowerOfTen(exponent); + } else { + diy_fp_bignum.MultiplyByPowerOfTen(-exponent); + } + if (diy_fp.e() > 0) { + diy_fp_bignum.ShiftLeft(diy_fp.e()); + } else { + buffer_bignum.ShiftLeft(-diy_fp.e()); + } + return Bignum::Compare(buffer_bignum, diy_fp_bignum); +} + + +// Returns true if the guess is the correct double. +// Returns false, when guess is either correct or the next-lower double. +static bool ComputeGuess(Vector trimmed, int exponent, + double* guess) { + if (trimmed.length() == 0) { + *guess = 0.0; + return true; + } + if (exponent + trimmed.length() - 1 >= kMaxDecimalPower) { + *guess = Double::Infinity(); + return true; + } + if (exponent + trimmed.length() <= kMinDecimalPower) { + *guess = 0.0; + return true; + } + + if (DoubleStrtod(trimmed, exponent, guess) || + DiyFpStrtod(trimmed, exponent, guess)) { + return true; + } + if (*guess == Double::Infinity()) { + return true; + } + return false; +} + +double Strtod(Vector buffer, int exponent) { + char copy_buffer[kMaxSignificantDecimalDigits]; + Vector trimmed; + int updated_exponent; + TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits, + &trimmed, &updated_exponent); + exponent = updated_exponent; + + double guess; + bool is_correct = ComputeGuess(trimmed, exponent, &guess); + if (is_correct) return guess; + + DiyFp upper_boundary = Double(guess).UpperBoundary(); + int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary); + if (comparison < 0) { + return guess; + } else if (comparison > 0) { + return Double(guess).NextDouble(); + } else if ((Double(guess).Significand() & 1) == 0) { + // Round towards even. + return guess; + } else { + return Double(guess).NextDouble(); + } +} + +float Strtof(Vector buffer, int exponent) { + char copy_buffer[kMaxSignificantDecimalDigits]; + Vector trimmed; + int updated_exponent; + TrimAndCut(buffer, exponent, copy_buffer, kMaxSignificantDecimalDigits, + &trimmed, &updated_exponent); + exponent = updated_exponent; + + double double_guess; + bool is_correct = ComputeGuess(trimmed, exponent, &double_guess); + + float float_guess = static_cast(double_guess); + if (float_guess == double_guess) { + // This shortcut triggers for integer values. + return float_guess; + } + + // We must catch double-rounding. Say the double has been rounded up, and is + // now a boundary of a float, and rounds up again. This is why we have to + // look at previous too. + // Example (in decimal numbers): + // input: 12349 + // high-precision (4 digits): 1235 + // low-precision (3 digits): + // when read from input: 123 + // when rounded from high precision: 124. + // To do this we simply look at the neigbors of the correct result and see + // if they would round to the same float. If the guess is not correct we have + // to look at four values (since two different doubles could be the correct + // double). + + double double_next = Double(double_guess).NextDouble(); + double double_previous = Double(double_guess).PreviousDouble(); + + float f1 = static_cast(double_previous); + float f2 = float_guess; + float f3 = static_cast(double_next); + float f4; + if (is_correct) { + f4 = f3; + } else { + double double_next2 = Double(double_next).NextDouble(); + f4 = static_cast(double_next2); + } + (void) f2; // Mark variable as used. + ASSERT(f1 <= f2 && f2 <= f3 && f3 <= f4); + + // If the guess doesn't lie near a single-precision boundary we can simply + // return its float-value. + if (f1 == f4) { + return float_guess; + } + + ASSERT((f1 != f2 && f2 == f3 && f3 == f4) || + (f1 == f2 && f2 != f3 && f3 == f4) || + (f1 == f2 && f2 == f3 && f3 != f4)); + + // guess and next are the two possible canditates (in the same way that + // double_guess was the lower candidate for a double-precision guess). + float guess = f1; + float next = f4; + DiyFp upper_boundary; + if (guess == 0.0f) { + float min_float = 1e-45f; + upper_boundary = Double(static_cast(min_float) / 2).AsDiyFp(); + } else { + upper_boundary = Single(guess).UpperBoundary(); + } + int comparison = CompareBufferWithDiyFp(trimmed, exponent, upper_boundary); + if (comparison < 0) { + return guess; + } else if (comparison > 0) { + return next; + } else if ((Single(guess).Significand() & 1) == 0) { + // Round towards even. + return guess; + } else { + return next; + } +} + +} // namespace double_conversion + +// ICU PATCH: Close ICU namespace +U_NAMESPACE_END +#endif // ICU PATCH: close #if !UCONFIG_NO_FORMATTING diff --git a/deps/icu-small/source/i18n/double-conversion-strtod.h b/deps/icu-small/source/i18n/double-conversion-strtod.h new file mode 100644 index 00000000000000..e2d6d3c2fe5d7d --- /dev/null +++ b/deps/icu-small/source/i18n/double-conversion-strtod.h @@ -0,0 +1,63 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html +// +// From the double-conversion library. Original license: +// +// Copyright 2010 the V8 project authors. All rights reserved. +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// ICU PATCH: ifdef around UCONFIG_NO_FORMATTING +#include "unicode/utypes.h" +#if !UCONFIG_NO_FORMATTING + +#ifndef DOUBLE_CONVERSION_STRTOD_H_ +#define DOUBLE_CONVERSION_STRTOD_H_ + +// ICU PATCH: Customize header file paths for ICU. + +#include "double-conversion-utils.h" + +// ICU PATCH: Wrap in ICU namespace +U_NAMESPACE_BEGIN + +namespace double_conversion { + +// The buffer must only contain digits in the range [0-9]. It must not +// contain a dot or a sign. It must not start with '0', and must not be empty. +double Strtod(Vector buffer, int exponent); + +// The buffer must only contain digits in the range [0-9]. It must not +// contain a dot or a sign. It must not start with '0', and must not be empty. +float Strtof(Vector buffer, int exponent); + +} // namespace double_conversion + +// ICU PATCH: Close ICU namespace +U_NAMESPACE_END + +#endif // DOUBLE_CONVERSION_STRTOD_H_ +#endif // ICU PATCH: close #if !UCONFIG_NO_FORMATTING diff --git a/deps/icu-small/source/i18n/double-conversion-utils.h b/deps/icu-small/source/i18n/double-conversion-utils.h index 02795b4bc565ae..57fc49b231a3a0 100644 --- a/deps/icu-small/source/i18n/double-conversion-utils.h +++ b/deps/icu-small/source/i18n/double-conversion-utils.h @@ -75,9 +75,9 @@ inline void abort_noreturn() { abort(); } // the output of the division with the expected result. (Inlining must be // disabled.) // On Linux,x86 89255e-22 != Div_double(89255.0/1e22) -// ICU PATCH: Enable ARM builds for Windows with 'defined(_M_ARM)'. +// ICU PATCH: Enable ARM32 & ARM64 builds for Windows with 'defined(_M_ARM) || defined(_M_ARM64)'. #if defined(_M_X64) || defined(__x86_64__) || \ - defined(__ARMEL__) || defined(__avr32__) || defined(_M_ARM) || \ + defined(__ARMEL__) || defined(__avr32__) || defined(_M_ARM) || defined(_M_ARM64) || \ defined(__hppa__) || defined(__ia64__) || \ defined(__mips__) || \ defined(__powerpc__) || defined(__ppc__) || defined(__ppc64__) || \ diff --git a/deps/icu-small/source/i18n/double-conversion.cpp b/deps/icu-small/source/i18n/double-conversion.cpp index 8629284aa0e0f5..570a05bc42946c 100644 --- a/deps/icu-small/source/i18n/double-conversion.cpp +++ b/deps/icu-small/source/i18n/double-conversion.cpp @@ -38,13 +38,14 @@ #include // ICU PATCH: Customize header file paths for ICU. -// The files fixed-dtoa.h and strtod.h are not needed. +// The file fixed-dtoa.h is not needed. #include "double-conversion.h" #include "double-conversion-bignum-dtoa.h" #include "double-conversion-fast-dtoa.h" #include "double-conversion-ieee.h" +#include "double-conversion-strtod.h" #include "double-conversion-utils.h" // ICU PATCH: Wrap in ICU namespace @@ -431,7 +432,6 @@ void DoubleToStringConverter::DoubleToAscii(double v, } -#if 0 // not needed for ICU // Consumes the given substring from the iterator. // Returns false, if the substring does not match. template @@ -469,6 +469,7 @@ static const uc16 kWhitespaceTable16[] = { static const int kWhitespaceTable16Length = ARRAY_SIZE(kWhitespaceTable16); + static bool isWhitespace(int x) { if (x < 128) { for (int i = 0; i < kWhitespaceTable7Length; i++) { @@ -647,7 +648,6 @@ static double RadixStringToIeee(Iterator* current, return Double(DiyFp(number, exponent)).value(); } - template double StringToDoubleConverter::StringToIeee( Iterator input, @@ -996,7 +996,6 @@ float StringToDoubleConverter::StringToFloat( return static_cast(StringToIeee(buffer, length, false, processed_characters_count)); } -#endif // not needed for ICU } // namespace double_conversion diff --git a/deps/icu-small/source/i18n/double-conversion.h b/deps/icu-small/source/i18n/double-conversion.h index 0939412734a6bb..200537a360a7cc 100644 --- a/deps/icu-small/source/i18n/double-conversion.h +++ b/deps/icu-small/source/i18n/double-conversion.h @@ -391,6 +391,7 @@ class DoubleToStringConverter { const int decimal_in_shortest_high_; const int max_leading_padding_zeroes_in_precision_mode_; const int max_trailing_padding_zeroes_in_precision_mode_; +#endif // not needed for ICU DISALLOW_IMPLICIT_CONSTRUCTORS(DoubleToStringConverter); }; @@ -554,7 +555,6 @@ class StringToDoubleConverter { int* processed_characters_count) const; DISALLOW_IMPLICIT_CONSTRUCTORS(StringToDoubleConverter); -#endif // not needed for ICU }; } // namespace double_conversion diff --git a/deps/icu-small/source/i18n/fmtable.cpp b/deps/icu-small/source/i18n/fmtable.cpp index 73f9b66ab6f950..cb6134cb4b2423 100644 --- a/deps/icu-small/source/i18n/fmtable.cpp +++ b/deps/icu-small/source/i18n/fmtable.cpp @@ -19,6 +19,7 @@ #if !UCONFIG_NO_FORMATTING +#include #include #include "unicode/fmtable.h" #include "unicode/ustring.h" @@ -28,9 +29,8 @@ #include "charstr.h" #include "cmemory.h" #include "cstring.h" -#include "decNumber.h" -#include "digitlst.h" #include "fmtableimp.h" +#include "number_decimalquantity.h" // ***************************************************************************** // class Formattable @@ -40,6 +40,8 @@ U_NAMESPACE_BEGIN UOBJECT_DEFINE_RTTI_IMPLEMENTATION(Formattable) +using number::impl::DecimalQuantity; + //-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-. @@ -103,7 +105,7 @@ void Formattable::init() { fValue.fInt64 = 0; fType = kLong; fDecimalStr = NULL; - fDecimalNum = NULL; + fDecimalQuantity = NULL; fBogus.setToBogus(); } @@ -257,8 +259,8 @@ Formattable::operator=(const Formattable& source) } UErrorCode status = U_ZERO_ERROR; - if (source.fDecimalNum != NULL) { - fDecimalNum = new DigitList(*source.fDecimalNum); // TODO: use internal digit list + if (source.fDecimalQuantity != NULL) { + fDecimalQuantity = new DecimalQuantity(*source.fDecimalQuantity); } if (source.fDecimalStr != NULL) { fDecimalStr = new CharString(*source.fDecimalStr, status); @@ -357,13 +359,8 @@ void Formattable::dispose() delete fDecimalStr; fDecimalStr = NULL; - FmtStackData *stackData = (FmtStackData*)fStackData; - if(fDecimalNum != &(stackData->stackDecimalNum)) { - delete fDecimalNum; - } else { - fDecimalNum->~DigitList(); // destruct, don't deallocate - } - fDecimalNum = NULL; + delete fDecimalQuantity; + fDecimalQuantity = NULL; } Formattable * @@ -465,13 +462,13 @@ Formattable::getInt64(UErrorCode& status) const } else if (fValue.fDouble < (double)U_INT64_MIN) { status = U_INVALID_FORMAT_ERROR; return U_INT64_MIN; - } else if (fabs(fValue.fDouble) > U_DOUBLE_MAX_EXACT_INT && fDecimalNum != NULL) { - int64_t val = fDecimalNum->getInt64(); - if (val != 0) { - return val; + } else if (fabs(fValue.fDouble) > U_DOUBLE_MAX_EXACT_INT && fDecimalQuantity != NULL) { + if (fDecimalQuantity->fitsInLong(true)) { + return fDecimalQuantity->toLong(); } else { + // Unexpected status = U_INVALID_FORMAT_ERROR; - return fValue.fDouble > 0 ? U_INT64_MAX : U_INT64_MIN; + return fDecimalQuantity->isNegative() ? U_INT64_MIN : U_INT64_MAX; } } else { return (int64_t)fValue.fDouble; @@ -714,84 +711,85 @@ StringPiece Formattable::getDecimalNumber(UErrorCode &status) { CharString *Formattable::internalGetCharString(UErrorCode &status) { if(fDecimalStr == NULL) { - if (fDecimalNum == NULL) { + if (fDecimalQuantity == NULL) { // No decimal number for the formattable yet. Which means the value was // set directly by the user as an int, int64 or double. If the value came // from parsing, or from the user setting a decimal number, fDecimalNum // would already be set. // - fDecimalNum = new DigitList; // TODO: use internal digit list - if (fDecimalNum == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return NULL; - } - - switch (fType) { - case kDouble: - fDecimalNum->set(this->getDouble()); - break; - case kLong: - fDecimalNum->set(this->getLong()); - break; - case kInt64: - fDecimalNum->set(this->getInt64()); - break; - default: - // The formattable's value is not a numeric type. - status = U_INVALID_STATE_ERROR; - return NULL; - } + LocalPointer dq(new DecimalQuantity(), status); + if (U_FAILURE(status)) { return nullptr; } + populateDecimalQuantity(*dq, status); + if (U_FAILURE(status)) { return nullptr; } + fDecimalQuantity = dq.orphan(); } - fDecimalStr = new CharString; + fDecimalStr = new CharString(); if (fDecimalStr == NULL) { status = U_MEMORY_ALLOCATION_ERROR; return NULL; } - fDecimalNum->getDecimal(*fDecimalStr, status); + // Older ICUs called uprv_decNumberToString here, which is not exactly the same as + // DecimalQuantity::toScientificString(). The biggest difference is that uprv_decNumberToString does + // not print scientific notation for magnitudes greater than -5 and smaller than some amount (+5?). + if (fDecimalQuantity->isZero()) { + fDecimalStr->append("0", -1, status); + } else if (std::abs(fDecimalQuantity->getMagnitude()) < 5) { + fDecimalStr->appendInvariantChars(fDecimalQuantity->toPlainString(), status); + } else { + fDecimalStr->appendInvariantChars(fDecimalQuantity->toScientificString(), status); + } } return fDecimalStr; } +void +Formattable::populateDecimalQuantity(number::impl::DecimalQuantity& output, UErrorCode& status) const { + if (fDecimalQuantity != nullptr) { + output = *fDecimalQuantity; + return; + } -DigitList * -Formattable::getInternalDigitList() { - FmtStackData *stackData = (FmtStackData*)fStackData; - if(fDecimalNum != &(stackData->stackDecimalNum)) { - delete fDecimalNum; - fDecimalNum = new (&(stackData->stackDecimalNum), kOnStack) DigitList(); - } else { - fDecimalNum->clear(); - } - return fDecimalNum; + switch (fType) { + case kDouble: + output.setToDouble(this->getDouble()); + output.roundToInfinity(); + break; + case kLong: + output.setToInt(this->getLong()); + break; + case kInt64: + output.setToLong(this->getInt64()); + break; + default: + // The formattable's value is not a numeric type. + status = U_INVALID_STATE_ERROR; + } } // --------------------------------------- void -Formattable::adoptDigitList(DigitList *dl) { - if(fDecimalNum==dl) { - fDecimalNum = NULL; // don't delete - } - dispose(); - - fDecimalNum = dl; - - if(dl==NULL) { // allow adoptDigitList(NULL) to clear - return; - } +Formattable::adoptDecimalQuantity(DecimalQuantity *dq) { + if (fDecimalQuantity != NULL) { + delete fDecimalQuantity; + } + fDecimalQuantity = dq; + if (dq == NULL) { // allow adoptDigitList(NULL) to clear + return; + } // Set the value into the Union of simple type values. - // Cannot use the set() functions because they would delete the fDecimalNum value, - - if (fDecimalNum->fitsIntoLong(FALSE)) { - fType = kLong; - fValue.fInt64 = fDecimalNum->getLong(); - } else if (fDecimalNum->fitsIntoInt64(FALSE)) { - fType = kInt64; - fValue.fInt64 = fDecimalNum->getInt64(); + // Cannot use the set() functions because they would delete the fDecimalNum value. + if (fDecimalQuantity->fitsInLong()) { + fValue.fInt64 = fDecimalQuantity->toLong(); + if (fValue.fInt64 <= INT32_MAX && fValue.fInt64 >= INT32_MIN) { + fType = kLong; + } else { + fType = kInt64; + } } else { fType = kDouble; - fValue.fDouble = fDecimalNum->getDouble(); + fValue.fDouble = fDecimalQuantity->toDouble(); } } @@ -804,24 +802,12 @@ Formattable::setDecimalNumber(StringPiece numberString, UErrorCode &status) { } dispose(); - // Copy the input string and nul-terminate it. - // The decNumber library requires nul-terminated input. StringPiece input - // is not guaranteed nul-terminated. Too bad. - // CharString automatically adds the nul. - DigitList *dnum = new DigitList(); // TODO: use getInternalDigitList - if (dnum == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; - return; - } - dnum->set(CharString(numberString, status).toStringPiece(), status); - if (U_FAILURE(status)) { - delete dnum; - return; // String didn't contain a decimal number. - } - adoptDigitList(dnum); + auto* dq = new DecimalQuantity(); + dq->setToDecNumber(numberString, status); + adoptDecimalQuantity(dq); // Note that we do not hang on to the caller's input string. - // If we are asked for the string, we will regenerate one from fDecimalNum. + // If we are asked for the string, we will regenerate one from fDecimalQuantity. } #if 0 diff --git a/deps/icu-small/source/i18n/fmtableimp.h b/deps/icu-small/source/i18n/fmtableimp.h index 0e6ccd24da7f02..78b7caff548e82 100644 --- a/deps/icu-small/source/i18n/fmtableimp.h +++ b/deps/icu-small/source/i18n/fmtableimp.h @@ -10,22 +10,12 @@ #ifndef FMTABLEIMP_H #define FMTABLEIMP_H -#include "digitlst.h" +#include "number_decimalquantity.h" #if !UCONFIG_NO_FORMATTING U_NAMESPACE_BEGIN -/** - * @internal - */ -struct FmtStackData { - DigitList stackDecimalNum; // 128 - //CharString stackDecimalStr; // 64 - // ----- - // 192 total -}; - /** * Maximum int64_t value that can be stored in a double without chancing losing precision. * IEEE doubles have 53 bits of mantissa, 10 bits exponent, 1 bit sign. diff --git a/deps/icu-small/source/i18n/fphdlimp.cpp b/deps/icu-small/source/i18n/fphdlimp.cpp index abcec97ee3171c..c4015fae1bbaff 100644 --- a/deps/icu-small/source/i18n/fphdlimp.cpp +++ b/deps/icu-small/source/i18n/fphdlimp.cpp @@ -22,17 +22,8 @@ U_NAMESPACE_BEGIN FieldPositionHandler::~FieldPositionHandler() { } -void -FieldPositionHandler::addAttribute(int32_t, int32_t, int32_t) { -} - -void -FieldPositionHandler::shiftLast(int32_t) { -} - -UBool -FieldPositionHandler::isRecording(void) const { - return FALSE; +void FieldPositionHandler::setShift(int32_t delta) { + fShift = delta; } @@ -48,8 +39,8 @@ FieldPositionOnlyHandler::~FieldPositionOnlyHandler() { void FieldPositionOnlyHandler::addAttribute(int32_t id, int32_t start, int32_t limit) { if (pos.getField() == id) { - pos.setBeginIndex(start); - pos.setEndIndex(limit); + pos.setBeginIndex(start + fShift); + pos.setEndIndex(limit + fShift); } } @@ -91,8 +82,8 @@ FieldPositionIteratorHandler::addAttribute(int32_t id, int32_t start, int32_t li if (iter && U_SUCCESS(status) && start < limit) { int32_t size = vec->size(); vec->addElement(id, status); - vec->addElement(start, status); - vec->addElement(limit, status); + vec->addElement(start + fShift, status); + vec->addElement(limit + fShift, status); if (!U_SUCCESS(status)) { vec->setSize(size); } diff --git a/deps/icu-small/source/i18n/fphdlimp.h b/deps/icu-small/source/i18n/fphdlimp.h index f3ac12c2bacb9a..2e9d5622b1b5b0 100644 --- a/deps/icu-small/source/i18n/fphdlimp.h +++ b/deps/icu-small/source/i18n/fphdlimp.h @@ -22,11 +22,16 @@ U_NAMESPACE_BEGIN // base class, null implementation class U_I18N_API FieldPositionHandler: public UMemory { + protected: + int32_t fShift = 0; + public: virtual ~FieldPositionHandler(); - virtual void addAttribute(int32_t id, int32_t start, int32_t limit); - virtual void shiftLast(int32_t delta); - virtual UBool isRecording(void) const; + virtual void addAttribute(int32_t id, int32_t start, int32_t limit) = 0; + virtual void shiftLast(int32_t delta) = 0; + virtual UBool isRecording(void) const = 0; + + void setShift(int32_t delta); }; @@ -39,9 +44,9 @@ class FieldPositionOnlyHandler : public FieldPositionHandler { FieldPositionOnlyHandler(FieldPosition& pos); virtual ~FieldPositionOnlyHandler(); - virtual void addAttribute(int32_t id, int32_t start, int32_t limit); - virtual void shiftLast(int32_t delta); - virtual UBool isRecording(void) const; + void addAttribute(int32_t id, int32_t start, int32_t limit) U_OVERRIDE; + void shiftLast(int32_t delta) U_OVERRIDE; + UBool isRecording(void) const U_OVERRIDE; }; @@ -63,9 +68,9 @@ class FieldPositionIteratorHandler : public FieldPositionHandler { FieldPositionIteratorHandler(FieldPositionIterator* posIter, UErrorCode& status); ~FieldPositionIteratorHandler(); - virtual void addAttribute(int32_t id, int32_t start, int32_t limit); - virtual void shiftLast(int32_t delta); - virtual UBool isRecording(void) const; + void addAttribute(int32_t id, int32_t start, int32_t limit) U_OVERRIDE; + void shiftLast(int32_t delta) U_OVERRIDE; + UBool isRecording(void) const U_OVERRIDE; }; U_NAMESPACE_END diff --git a/deps/icu-small/source/i18n/measunit.cpp b/deps/icu-small/source/i18n/measunit.cpp index e21afcba029e04..dc156aa720aae0 100644 --- a/deps/icu-small/source/i18n/measunit.cpp +++ b/deps/icu-small/source/i18n/measunit.cpp @@ -41,21 +41,21 @@ static const int32_t gOffsets[] = { 16, 20, 24, - 285, - 295, - 306, - 310, - 316, - 320, - 340, - 341, + 321, + 331, + 342, + 346, 352, - 355, - 361, - 366, - 370, - 374, - 399 + 356, + 376, + 377, + 388, + 391, + 397, + 402, + 406, + 410, + 435 }; static const int32_t gIndexes[] = { @@ -136,15 +136,18 @@ static const char * const gSubTypes[] = { "AED", "AFA", "AFN", + "ALK", "ALL", "AMD", "ANG", "AOA", + "AOK", "AON", "AOR", "ARA", "ARP", "ARS", + "ARY", "ATS", "AUD", "AWG", @@ -158,6 +161,8 @@ static const char * const gSubTypes[] = { "BEC", "BEF", "BEL", + "BGJ", + "BGK", "BGL", "BGN", "BHD", @@ -165,7 +170,9 @@ static const char * const gSubTypes[] = { "BMD", "BND", "BOB", + "BOP", "BOV", + "BRB", "BRC", "BRE", "BRL", @@ -173,6 +180,7 @@ static const char * const gSubTypes[] = { "BRR", "BSD", "BTN", + "BUK", "BWP", "BYB", "BYN", @@ -191,6 +199,7 @@ static const char * const gSubTypes[] = { "COU", "CRC", "CSD", + "CSJ", "CSK", "CUC", "CUP", @@ -225,10 +234,13 @@ static const char * const gSubTypes[] = { "GHS", "GIP", "GMD", + "GNE", "GNF", + "GNS", "GQE", "GRD", "GTQ", + "GWE", "GWP", "GYD", "HKD", @@ -239,10 +251,13 @@ static const char * const gSubTypes[] = { "HUF", "IDR", "IEP", + "ILP", + "ILR", "ILS", "INR", "IQD", "IRR", + "ISJ", "ISK", "ITL", "JMD", @@ -257,11 +272,13 @@ static const char * const gSubTypes[] = { "KWD", "KYD", "KZT", + "LAJ", "LAK", "LBP", "LKR", "LRD", "LSL", + "LSM", "LTL", "LTT", "LUC", @@ -280,17 +297,23 @@ static const char * const gSubTypes[] = { "MNT", "MOP", "MRO", + "MRU", "MTL", + "MTP", "MUR", + "MVQ", "MVR", "MWK", "MXN", + "MXP", "MXV", "MYR", + "MZE", "MZM", "MZN", "NAD", "NGN", + "NIC", "NIO", "NLG", "NOK", @@ -298,6 +321,7 @@ static const char * const gSubTypes[] = { "NZD", "OMR", "PAB", + "PEH", "PEI", "PEN", "PES", @@ -309,6 +333,8 @@ static const char * const gSubTypes[] = { "PTE", "PYG", "QAR", + "RHD", + "ROK", "ROL", "RON", "RSD", @@ -320,6 +346,7 @@ static const char * const gSubTypes[] = { "SCR", "SDD", "SDG", + "SDP", "SEK", "SGD", "SHP", @@ -331,6 +358,8 @@ static const char * const gSubTypes[] = { "SRG", "SSP", "STD", + "STN", + "SUR", "SVC", "SYP", "SZL", @@ -349,15 +378,20 @@ static const char * const gSubTypes[] = { "TZS", "UAH", "UAK", + "UGS", + "UGW", "UGX", "USD", "USN", "USS", "UYI", + "UYN", + "UYP", "UYU", "UZS", "VEB", "VEF", + "VNC", "VND", "VUV", "WST", @@ -381,6 +415,7 @@ static const char * const gSubTypes[] = { "XXX", "YDD", "YER", + "YUD", "YUM", "YUN", "ZAL", @@ -389,6 +424,7 @@ static const char * const gSubTypes[] = { "ZMW", "ZRN", "ZRZ", + "ZWC", "ZWD", "ZWL", "ZWN", @@ -511,16 +547,20 @@ static const char * const gSubTypes[] = { // Must be sorted by first value and then second value. static int32_t unitPerUnitToSingleUnit[][4] = { - {327, 297, 17, 0}, - {329, 303, 17, 2}, - {331, 297, 17, 3}, - {331, 388, 4, 2}, - {331, 389, 4, 3}, - {346, 386, 3, 1}, - {349, 11, 16, 4}, - {391, 327, 4, 1} + {363, 333, 17, 0}, + {365, 339, 17, 2}, + {367, 333, 17, 3}, + {367, 424, 4, 2}, + {367, 425, 4, 3}, + {382, 422, 3, 1}, + {385, 11, 16, 4}, + {427, 363, 4, 1} }; +// Shortcuts to the base unit in order to make the default constructor fast +static const int32_t kBaseTypeIdx = 14; +static const int32_t kBaseSubTypeIdx = 0; + MeasureUnit *MeasureUnit::createGForce(UErrorCode &status) { return MeasureUnit::create(0, 0, status); } @@ -1082,7 +1122,8 @@ static int32_t binarySearch( MeasureUnit::MeasureUnit() { fCurrency[0] = 0; - initNoUnit("base"); + fTypeId = kBaseTypeIdx; + fSubTypeId = kBaseSubTypeIdx; } MeasureUnit::MeasureUnit(const MeasureUnit &other) diff --git a/deps/icu-small/source/i18n/msgfmt.cpp b/deps/icu-small/source/i18n/msgfmt.cpp index 064585665ae5e6..8b3807e67148a4 100644 --- a/deps/icu-small/source/i18n/msgfmt.cpp +++ b/deps/icu-small/source/i18n/msgfmt.cpp @@ -31,6 +31,7 @@ #include "unicode/decimfmt.h" #include "unicode/localpointer.h" #include "unicode/msgfmt.h" +#include "unicode/numberformatter.h" #include "unicode/plurfmt.h" #include "unicode/rbnf.h" #include "unicode/selfmt.h" @@ -48,7 +49,7 @@ #include "ustrfmt.h" #include "util.h" #include "uvector.h" -#include "visibledigits.h" +#include "number_decimalquantity.h" // ***************************************************************************** // class MessageFormat @@ -1700,12 +1701,21 @@ Format* MessageFormat::createAppropriateFormat(UnicodeString& type, UnicodeStrin formattableType = Formattable::kLong; fmt = createIntegerFormat(fLocale, ec); break; - default: // pattern - fmt = NumberFormat::createInstance(fLocale, ec); - if (fmt) { - DecimalFormat* decfmt = dynamic_cast(fmt); - if (decfmt != NULL) { - decfmt->applyPattern(style,parseError,ec); + default: // pattern or skeleton + int32_t i = 0; + for (; PatternProps::isWhiteSpace(style.charAt(i)); i++); + if (style.compare(i, 2, u"::", 0, 2) == 0) { + // Skeleton + UnicodeString skeleton = style.tempSubString(i + 2); + fmt = number::NumberFormatter::forSkeleton(skeleton, ec).locale(fLocale).toFormat(ec); + } else { + // Pattern + fmt = NumberFormat::createInstance(fLocale, ec); + if (fmt) { + auto* decfmt = dynamic_cast(fmt); + if (decfmt != nullptr) { + decfmt->applyPattern(style, parseError, ec); + } } } break; @@ -1959,14 +1969,14 @@ UnicodeString MessageFormat::PluralSelectorProvider::select(void *ctx, double nu return UnicodeString(FALSE, OTHER_STRING, 5); } context.formatter->format(context.number, context.numberString, ec); - const DecimalFormat *decFmt = dynamic_cast(context.formatter); + auto* decFmt = dynamic_cast(context.formatter); if(decFmt != NULL) { - VisibleDigitsWithExponent digits; - decFmt->initVisibleDigitsWithExponent(context.number, digits, ec); + number::impl::DecimalQuantity dq; + decFmt->formatToDecimalQuantity(context.number, dq, ec); if (U_FAILURE(ec)) { return UnicodeString(FALSE, OTHER_STRING, 5); } - return rules->select(digits); + return rules->select(dq); } else { return rules->select(number); } diff --git a/deps/icu-small/source/i18n/nfsubs.cpp b/deps/icu-small/source/i18n/nfsubs.cpp index ea817453d87c18..3733f0ca74d3e3 100644 --- a/deps/icu-small/source/i18n/nfsubs.cpp +++ b/deps/icu-small/source/i18n/nfsubs.cpp @@ -19,8 +19,9 @@ #include "utypeinfo.h" // for 'typeid' to work #include "nfsubs.h" -#include "digitlst.h" #include "fmtableimp.h" +#include "putilimp.h" +#include "number_decimalquantity.h" #if U_HAVE_RBNF @@ -47,6 +48,8 @@ static const UChar gGreaterGreaterThan[] = U_NAMESPACE_BEGIN +using number::impl::DecimalQuantity; + class SameValueSubstitution : public NFSubstitution { public: SameValueSubstitution(int32_t pos, @@ -1069,13 +1072,12 @@ FractionalPartSubstitution::doSubstitution(double number, UnicodeString& toInser // numberToFormat /= 10; // } - DigitList dl; - dl.set(number); - dl.roundFixedPoint(20); // round to 20 fraction digits. - dl.reduce(); // Removes any trailing zeros. + DecimalQuantity dl; + dl.setToDouble(number); + dl.roundToMagnitude(-20, UNUM_ROUND_HALFEVEN, status); // round to 20 fraction digits. UBool pad = FALSE; - for (int32_t didx = dl.getCount()-1; didx>=dl.getDecimalAt(); didx--) { + for (int32_t didx = dl.getLowerDisplayMagnitude(); didx<0; didx++) { // Loop iterates over fraction digits, starting with the LSD. // include both real digits from the number, and zeros // to the left of the MSD but to the right of the decimal point. @@ -1084,7 +1086,7 @@ FractionalPartSubstitution::doSubstitution(double number, UnicodeString& toInser } else { pad = TRUE; } - int64_t digit = didx>=0 ? dl.getDigit(didx) - '0' : 0; + int64_t digit = dl.getDigit(didx); getRuleSet()->format(digit, toInsertInto, _pos + getPos(), recursionCount, status); } @@ -1142,7 +1144,8 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, int32_t digit; // double p10 = 0.1; - DigitList dl; + DecimalQuantity dl; + int32_t totalDigits = 0; NumberFormat* fmt = NULL; while (workText.length() > 0 && workPos.getIndex() != 0) { workPos.setIndex(0); @@ -1170,7 +1173,8 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, } if (workPos.getIndex() != 0) { - dl.append((char)('0' + digit)); + dl.appendDigit(static_cast(digit), 0, true); + totalDigits++; // result += digit * p10; // p10 /= 10; parsePosition.setIndex(parsePosition.getIndex() + workPos.getIndex()); @@ -1183,7 +1187,8 @@ FractionalPartSubstitution::doParse(const UnicodeString& text, } delete fmt; - result = dl.getCount() == 0 ? 0 : dl.getDouble(); + dl.adjustMagnitude(-totalDigits); + result = dl.toDouble(); result = composeRuleValue(result, baseValue); resVal.setDouble(result); return TRUE; diff --git a/deps/icu-small/source/i18n/number_affixutils.cpp b/deps/icu-small/source/i18n/number_affixutils.cpp index df4b267af5a004..8da29a03d52d56 100644 --- a/deps/icu-small/source/i18n/number_affixutils.cpp +++ b/deps/icu-small/source/i18n/number_affixutils.cpp @@ -3,21 +3,25 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "number_affixutils.h" #include "unicode/utf16.h" +#include "unicode/uniset.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; -int32_t AffixUtils::estimateLength(const CharSequence &patternString, UErrorCode &status) { +TokenConsumer::~TokenConsumer() = default; +SymbolProvider::~SymbolProvider() = default; + +int32_t AffixUtils::estimateLength(const UnicodeString &patternString, UErrorCode &status) { AffixPatternState state = STATE_BASE; int32_t offset = 0; int32_t length = 0; for (; offset < patternString.length();) { - UChar32 cp = patternString.codePointAt(offset); + UChar32 cp = patternString.char32At(offset); switch (state) { case STATE_BASE: @@ -78,12 +82,12 @@ int32_t AffixUtils::estimateLength(const CharSequence &patternString, UErrorCode return length; } -UnicodeString AffixUtils::escape(const CharSequence &input) { +UnicodeString AffixUtils::escape(const UnicodeString &input) { AffixPatternState state = STATE_BASE; int32_t offset = 0; UnicodeString output; for (; offset < input.length();) { - UChar32 cp = input.codePointAt(offset); + UChar32 cp = input.char32At(offset); switch (cp) { case u'\'': @@ -153,7 +157,7 @@ Field AffixUtils::getFieldForType(AffixPatternType type) { } int32_t -AffixUtils::unescape(const CharSequence &affixPattern, NumberStringBuilder &output, int32_t position, +AffixUtils::unescape(const UnicodeString &affixPattern, NumberStringBuilder &output, int32_t position, const SymbolProvider &provider, UErrorCode &status) { int32_t length = 0; AffixTag tag; @@ -173,7 +177,7 @@ AffixUtils::unescape(const CharSequence &affixPattern, NumberStringBuilder &outp return length; } -int32_t AffixUtils::unescapedCodePointCount(const CharSequence &affixPattern, +int32_t AffixUtils::unescapedCodePointCount(const UnicodeString &affixPattern, const SymbolProvider &provider, UErrorCode &status) { int32_t length = 0; AffixTag tag; @@ -192,7 +196,7 @@ int32_t AffixUtils::unescapedCodePointCount(const CharSequence &affixPattern, } bool -AffixUtils::containsType(const CharSequence &affixPattern, AffixPatternType type, UErrorCode &status) { +AffixUtils::containsType(const UnicodeString &affixPattern, AffixPatternType type, UErrorCode &status) { if (affixPattern.length() == 0) { return false; } @@ -207,7 +211,7 @@ AffixUtils::containsType(const CharSequence &affixPattern, AffixPatternType type return false; } -bool AffixUtils::hasCurrencySymbols(const CharSequence &affixPattern, UErrorCode &status) { +bool AffixUtils::hasCurrencySymbols(const UnicodeString &affixPattern, UErrorCode &status) { if (affixPattern.length() == 0) { return false; } @@ -222,9 +226,9 @@ bool AffixUtils::hasCurrencySymbols(const CharSequence &affixPattern, UErrorCode return false; } -UnicodeString AffixUtils::replaceType(const CharSequence &affixPattern, AffixPatternType type, +UnicodeString AffixUtils::replaceType(const UnicodeString &affixPattern, AffixPatternType type, char16_t replacementChar, UErrorCode &status) { - UnicodeString output = affixPattern.toUnicodeString(); + UnicodeString output(affixPattern); // copy if (affixPattern.length() == 0) { return output; }; @@ -239,11 +243,41 @@ UnicodeString AffixUtils::replaceType(const CharSequence &affixPattern, AffixPat return output; } -AffixTag AffixUtils::nextToken(AffixTag tag, const CharSequence &patternString, UErrorCode &status) { +bool AffixUtils::containsOnlySymbolsAndIgnorables(const UnicodeString& affixPattern, + const UnicodeSet& ignorables, UErrorCode& status) { + if (affixPattern.length() == 0) { + return true; + }; + AffixTag tag; + while (hasNext(tag, affixPattern)) { + tag = nextToken(tag, affixPattern, status); + if (U_FAILURE(status)) { return false; } + if (tag.type == TYPE_CODEPOINT && !ignorables.contains(tag.codePoint)) { + return false; + } + } + return true; +} + +void AffixUtils::iterateWithConsumer(const UnicodeString& affixPattern, TokenConsumer& consumer, + UErrorCode& status) { + if (affixPattern.length() == 0) { + return; + }; + AffixTag tag; + while (hasNext(tag, affixPattern)) { + tag = nextToken(tag, affixPattern, status); + if (U_FAILURE(status)) { return; } + consumer.consumeToken(tag.type, tag.codePoint, status); + if (U_FAILURE(status)) { return; } + } +} + +AffixTag AffixUtils::nextToken(AffixTag tag, const UnicodeString &patternString, UErrorCode &status) { int32_t offset = tag.offset; int32_t state = tag.state; for (; offset < patternString.length();) { - UChar32 cp = patternString.codePointAt(offset); + UChar32 cp = patternString.char32At(offset); int32_t count = U16_LENGTH(cp); switch (state) { @@ -382,7 +416,7 @@ AffixTag AffixUtils::nextToken(AffixTag tag, const CharSequence &patternString, } } -bool AffixUtils::hasNext(const AffixTag &tag, const CharSequence &string) { +bool AffixUtils::hasNext(const AffixTag &tag, const UnicodeString &string) { // First check for the {-1} and default initializer syntax. if (tag.offset < 0) { return false; diff --git a/deps/icu-small/source/i18n/number_affixutils.h b/deps/icu-small/source/i18n/number_affixutils.h index fd76c99b975566..1d7e1a115e046a 100644 --- a/deps/icu-small/source/i18n/number_affixutils.h +++ b/deps/icu-small/source/i18n/number_affixutils.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_AFFIXUTILS_H__ #define __NUMBER_AFFIXUTILS_H__ @@ -12,6 +12,7 @@ #include "unicode/stringpiece.h" #include "unicode/unistr.h" #include "number_stringbuilder.h" +#include "unicode/uniset.h" U_NAMESPACE_BEGIN namespace number { namespace impl { @@ -37,19 +38,27 @@ struct AffixTag { AffixPatternState state; AffixPatternType type; - AffixTag() : offset(0), state(STATE_BASE) {} + AffixTag() + : offset(0), state(STATE_BASE) {} - AffixTag(int32_t offset) : offset(offset) {} + AffixTag(int32_t offset) + : offset(offset) {} AffixTag(int32_t offset, UChar32 codePoint, AffixPatternState state, AffixPatternType type) - : offset(offset), codePoint(codePoint), state(state), type(type) - {} + : offset(offset), codePoint(codePoint), state(state), type(type) {} +}; + +class TokenConsumer { + public: + virtual ~TokenConsumer(); + + virtual void consumeToken(AffixPatternType type, UChar32 cp, UErrorCode& status) = 0; }; // Exported as U_I18N_API because it is a base class for other exported types class U_I18N_API SymbolProvider { public: - virtual ~SymbolProvider() = default; + virtual ~SymbolProvider(); // TODO: Could this be more efficient if it returned by reference? virtual UnicodeString getSymbol(AffixPatternType type) const = 0; @@ -107,7 +116,7 @@ class U_I18N_API AffixUtils { * @param patternString The original string whose width will be estimated. * @return The length of the unescaped string. */ - static int32_t estimateLength(const CharSequence &patternString, UErrorCode &status); + static int32_t estimateLength(const UnicodeString& patternString, UErrorCode& status); /** * Takes a string and escapes (quotes) characters that have special meaning in the affix pattern @@ -118,7 +127,7 @@ class U_I18N_API AffixUtils { * @param input The string to be escaped. * @return The resulting UnicodeString. */ - static UnicodeString escape(const CharSequence &input); + static UnicodeString escape(const UnicodeString& input); static Field getFieldForType(AffixPatternType type); @@ -134,9 +143,8 @@ class U_I18N_API AffixUtils { * @param position The index into the NumberStringBuilder to insert the string. * @param provider An object to generate locale symbols. */ - static int32_t - unescape(const CharSequence &affixPattern, NumberStringBuilder &output, int32_t position, - const SymbolProvider &provider, UErrorCode &status); + static int32_t unescape(const UnicodeString& affixPattern, NumberStringBuilder& output, + int32_t position, const SymbolProvider& provider, UErrorCode& status); /** * Sames as {@link #unescape}, but only calculates the code point count. More efficient than {@link #unescape} @@ -146,8 +154,8 @@ class U_I18N_API AffixUtils { * @param provider An object to generate locale symbols. * @return The same return value as if you called {@link #unescape}. */ - static int32_t unescapedCodePointCount(const CharSequence &affixPattern, - const SymbolProvider &provider, UErrorCode &status); + static int32_t unescapedCodePointCount(const UnicodeString& affixPattern, + const SymbolProvider& provider, UErrorCode& status); /** * Checks whether the given affix pattern contains at least one token of the given type, which is @@ -157,8 +165,7 @@ class U_I18N_API AffixUtils { * @param type The token type. * @return true if the affix pattern contains the given token type; false otherwise. */ - static bool - containsType(const CharSequence &affixPattern, AffixPatternType type, UErrorCode &status); + static bool containsType(const UnicodeString& affixPattern, AffixPatternType type, UErrorCode& status); /** * Checks whether the specified affix pattern has any unquoted currency symbols ("¤"). @@ -166,7 +173,7 @@ class U_I18N_API AffixUtils { * @param affixPattern The string to check for currency symbols. * @return true if the literal has at least one unquoted currency symbol; false otherwise. */ - static bool hasCurrencySymbols(const CharSequence &affixPattern, UErrorCode &status); + static bool hasCurrencySymbols(const UnicodeString& affixPattern, UErrorCode& status); /** * Replaces all occurrences of tokens with the given type with the given replacement char. @@ -176,9 +183,21 @@ class U_I18N_API AffixUtils { * @param replacementChar The char to substitute in place of chars of the given token type. * @return A string containing the new affix pattern. */ - static UnicodeString - replaceType(const CharSequence &affixPattern, AffixPatternType type, char16_t replacementChar, - UErrorCode &status); + static UnicodeString replaceType(const UnicodeString& affixPattern, AffixPatternType type, + char16_t replacementChar, UErrorCode& status); + + /** + * Returns whether the given affix pattern contains only symbols and ignorables as defined by the + * given ignorables set. + */ + static bool containsOnlySymbolsAndIgnorables(const UnicodeString& affixPattern, + const UnicodeSet& ignorables, UErrorCode& status); + + /** + * Iterates over the affix pattern, calling the TokenConsumer for each token. + */ + static void iterateWithConsumer(const UnicodeString& affixPattern, TokenConsumer& consumer, + UErrorCode& status); /** * Returns the next token from the affix pattern. @@ -190,7 +209,7 @@ class U_I18N_API AffixUtils { * (never negative), or -1 if there were no more tokens in the affix pattern. * @see #hasNext */ - static AffixTag nextToken(AffixTag tag, const CharSequence &patternString, UErrorCode &status); + static AffixTag nextToken(AffixTag tag, const UnicodeString& patternString, UErrorCode& status); /** * Returns whether the affix pattern string has any more tokens to be retrieved from a call to @@ -200,7 +219,7 @@ class U_I18N_API AffixUtils { * @param string The affix pattern. * @return true if there are more tokens to consume; false otherwise. */ - static bool hasNext(const AffixTag &tag, const CharSequence &string); + static bool hasNext(const AffixTag& tag, const UnicodeString& string); private: /** @@ -208,8 +227,8 @@ class U_I18N_API AffixUtils { * The order of the arguments is consistent with Java, but the order of the stored * fields is not necessarily the same. */ - static inline AffixTag - makeTag(int32_t offset, AffixPatternType type, AffixPatternState state, UChar32 cp) { + static inline AffixTag makeTag(int32_t offset, AffixPatternType type, AffixPatternState state, + UChar32 cp) { return {offset, cp, state, type}; } }; diff --git a/deps/icu-small/source/i18n/number_asformat.cpp b/deps/icu-small/source/i18n/number_asformat.cpp new file mode 100644 index 00000000000000..c6bb538932cec8 --- /dev/null +++ b/deps/icu-small/source/i18n/number_asformat.cpp @@ -0,0 +1,105 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include +#include +#include "number_asformat.h" +#include "number_types.h" +#include "number_utils.h" +#include "fphdlimp.h" +#include "number_utypes.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(LocalizedNumberFormatterAsFormat) + +LocalizedNumberFormatterAsFormat::LocalizedNumberFormatterAsFormat( + const LocalizedNumberFormatter& formatter, const Locale& locale) + : fFormatter(formatter), fLocale(locale) { + const char* localeName = locale.getName(); + setLocaleIDs(localeName, localeName); +} + +LocalizedNumberFormatterAsFormat::~LocalizedNumberFormatterAsFormat() = default; + +UBool LocalizedNumberFormatterAsFormat::operator==(const Format& other) const { + auto* _other = dynamic_cast(&other); + if (_other == nullptr) { + return false; + } + // TODO: Change this to use LocalizedNumberFormatter::operator== if it is ever proposed. + // This implementation is fine, but not particularly efficient. + UErrorCode localStatus = U_ZERO_ERROR; + return fFormatter.toSkeleton(localStatus) == _other->fFormatter.toSkeleton(localStatus); +} + +Format* LocalizedNumberFormatterAsFormat::clone() const { + return new LocalizedNumberFormatterAsFormat(*this); +} + +UnicodeString& LocalizedNumberFormatterAsFormat::format(const Formattable& obj, UnicodeString& appendTo, + FieldPosition& pos, UErrorCode& status) const { + if (U_FAILURE(status)) { return appendTo; } + UFormattedNumberData data; + obj.populateDecimalQuantity(data.quantity, status); + if (U_FAILURE(status)) { + return appendTo; + } + fFormatter.formatImpl(&data, status); + if (U_FAILURE(status)) { + return appendTo; + } + // always return first occurrence: + pos.setBeginIndex(0); + pos.setEndIndex(0); + bool found = data.string.nextFieldPosition(pos, status); + if (found && appendTo.length() != 0) { + pos.setBeginIndex(pos.getBeginIndex() + appendTo.length()); + pos.setEndIndex(pos.getEndIndex() + appendTo.length()); + } + appendTo.append(data.string.toTempUnicodeString()); + return appendTo; +} + +UnicodeString& LocalizedNumberFormatterAsFormat::format(const Formattable& obj, UnicodeString& appendTo, + FieldPositionIterator* posIter, + UErrorCode& status) const { + if (U_FAILURE(status)) { return appendTo; } + UFormattedNumberData data; + obj.populateDecimalQuantity(data.quantity, status); + if (U_FAILURE(status)) { + return appendTo; + } + fFormatter.formatImpl(&data, status); + if (U_FAILURE(status)) { + return appendTo; + } + appendTo.append(data.string.toTempUnicodeString()); + if (posIter != nullptr) { + FieldPositionIteratorHandler fpih(posIter, status); + data.string.getAllFieldPositions(fpih, status); + } + return appendTo; +} + +void LocalizedNumberFormatterAsFormat::parseObject(const UnicodeString&, Formattable&, + ParsePosition& parse_pos) const { + // Not supported. + parse_pos.setErrorIndex(0); +} + +const LocalizedNumberFormatter& LocalizedNumberFormatterAsFormat::getNumberFormatter() const { + return fFormatter; +} + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_asformat.h b/deps/icu-small/source/i18n/number_asformat.h new file mode 100644 index 00000000000000..bf82d72ae302a4 --- /dev/null +++ b/deps/icu-small/source/i18n/number_asformat.h @@ -0,0 +1,107 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMBER_ASFORMAT_H__ +#define __NUMBER_ASFORMAT_H__ + +#include "unicode/numberformatter.h" +#include "number_types.h" +#include "number_decimalquantity.h" +#include "number_scientific.h" +#include "number_patternstring.h" +#include "number_modifiers.h" +#include "number_multiplier.h" +#include "number_roundingutils.h" +#include "decNumber.h" +#include "charstr.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + +/** + * A wrapper around LocalizedNumberFormatter implementing the Format interface, enabling improved + * compatibility with other APIs. + * + * @draft ICU 62 + * @see NumberFormatter + */ +class U_I18N_API LocalizedNumberFormatterAsFormat : public Format { + public: + LocalizedNumberFormatterAsFormat(const LocalizedNumberFormatter& formatter, const Locale& locale); + + /** + * Destructor. + */ + ~LocalizedNumberFormatterAsFormat() U_OVERRIDE; + + /** + * Equals operator. + */ + UBool operator==(const Format& other) const U_OVERRIDE; + + /** + * Creates a copy of this object. + */ + Format* clone() const U_OVERRIDE; + + /** + * Formats a Number using the wrapped LocalizedNumberFormatter. The provided formattable must be a + * number type. + */ + UnicodeString& format(const Formattable& obj, UnicodeString& appendTo, FieldPosition& pos, + UErrorCode& status) const U_OVERRIDE; + + /** + * Formats a Number using the wrapped LocalizedNumberFormatter. The provided formattable must be a + * number type. + */ + UnicodeString& format(const Formattable& obj, UnicodeString& appendTo, FieldPositionIterator* posIter, + UErrorCode& status) const U_OVERRIDE; + + /** + * Not supported: sets an error index and returns. + */ + void parseObject(const UnicodeString& source, Formattable& result, + ParsePosition& parse_pos) const U_OVERRIDE; + + /** + * Gets the LocalizedNumberFormatter that this wrapper class uses to format numbers. + * + * For maximum efficiency, this function returns by const reference. You must copy the return value + * into a local variable if you want to use it beyond the lifetime of the current object: + * + *

+     * LocalizedNumberFormatter localFormatter = fmt->getNumberFormatter();
+     * 
+ * + * You can however use the return value directly when chaining: + * + *
+     * FormattedNumber result = fmt->getNumberFormatter().formatDouble(514.23, status);
+     * 
+ * + * @return The unwrapped LocalizedNumberFormatter. + */ + const LocalizedNumberFormatter& getNumberFormatter() const; + + UClassID getDynamicClassID() const U_OVERRIDE; + static UClassID U_EXPORT2 getStaticClassID(); + + private: + LocalizedNumberFormatter fFormatter; + + // Even though the locale is inside the LocalizedNumberFormatter, we have to keep it here, too, because + // LocalizedNumberFormatter doesn't have a getLocale() method, and ICU-TC didn't want to add one. + Locale fLocale; +}; + +} // namespace impl +} // namespace number +U_NAMESPACE_END + +#endif // __NUMBER_ASFORMAT_H__ + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_capi.cpp b/deps/icu-small/source/i18n/number_capi.cpp new file mode 100644 index 00000000000000..37ad8bd76fcd72 --- /dev/null +++ b/deps/icu-small/source/i18n/number_capi.cpp @@ -0,0 +1,213 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "fphdlimp.h" +#include "number_utypes.h" +#include "numparse_types.h" +#include "unicode/numberformatter.h" +#include "unicode/unumberformatter.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + +////////////////////////////////// +/// C API CONVERSION FUNCTIONS /// +////////////////////////////////// + +UNumberFormatterData* UNumberFormatterData::validate(UNumberFormatter* input, UErrorCode& status) { + auto* constInput = static_cast(input); + auto* validated = validate(constInput, status); + return const_cast(validated); +} + +const UNumberFormatterData* +UNumberFormatterData::validate(const UNumberFormatter* input, UErrorCode& status) { + if (U_FAILURE(status)) { + return nullptr; + } + if (input == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return nullptr; + } + auto* impl = reinterpret_cast(input); + if (impl->fMagic != UNumberFormatterData::kMagic) { + status = U_INVALID_FORMAT_ERROR; + return nullptr; + } + return impl; +} + +UNumberFormatter* UNumberFormatterData::exportForC() { + return reinterpret_cast(this); +} + +UFormattedNumberData* UFormattedNumberData::validate(UFormattedNumber* input, UErrorCode& status) { + auto* constInput = static_cast(input); + auto* validated = validate(constInput, status); + return const_cast(validated); +} + +const UFormattedNumberData* +UFormattedNumberData::validate(const UFormattedNumber* input, UErrorCode& status) { + if (U_FAILURE(status)) { + return nullptr; + } + if (input == nullptr) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return nullptr; + } + auto* impl = reinterpret_cast(input); + if (impl->fMagic != UFormattedNumberData::kMagic) { + status = U_INVALID_FORMAT_ERROR; + return nullptr; + } + return impl; +} + +UFormattedNumber* UFormattedNumberData::exportForC() { + return reinterpret_cast(this); +} + +///////////////////////////////////// +/// END CAPI CONVERSION FUNCTIONS /// +///////////////////////////////////// + + +U_CAPI UNumberFormatter* U_EXPORT2 +unumf_openForSkeletonAndLocale(const UChar* skeleton, int32_t skeletonLen, const char* locale, + UErrorCode* ec) { + auto* impl = new UNumberFormatterData(); + if (impl == nullptr) { + *ec = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + // Readonly-alias constructor (first argument is whether we are NUL-terminated) + UnicodeString skeletonString(skeletonLen == -1, skeleton, skeletonLen); + impl->fFormatter = NumberFormatter::forSkeleton(skeletonString, *ec).locale(locale); + return impl->exportForC(); +} + +U_CAPI UFormattedNumber* U_EXPORT2 +unumf_openResult(UErrorCode* ec) { + auto* impl = new UFormattedNumberData(); + if (impl == nullptr) { + *ec = U_MEMORY_ALLOCATION_ERROR; + return nullptr; + } + return impl->exportForC(); +} + +U_CAPI void U_EXPORT2 +unumf_formatInt(const UNumberFormatter* uformatter, int64_t value, UFormattedNumber* uresult, + UErrorCode* ec) { + const UNumberFormatterData* formatter = UNumberFormatterData::validate(uformatter, *ec); + UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + result->string.clear(); + result->quantity.setToLong(value); + formatter->fFormatter.formatImpl(result, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_formatDouble(const UNumberFormatter* uformatter, double value, UFormattedNumber* uresult, + UErrorCode* ec) { + const UNumberFormatterData* formatter = UNumberFormatterData::validate(uformatter, *ec); + UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + result->string.clear(); + result->quantity.setToDouble(value); + formatter->fFormatter.formatImpl(result, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_formatDecimal(const UNumberFormatter* uformatter, const char* value, int32_t valueLen, + UFormattedNumber* uresult, UErrorCode* ec) { + const UNumberFormatterData* formatter = UNumberFormatterData::validate(uformatter, *ec); + UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + result->string.clear(); + result->quantity.setToDecNumber({value, valueLen}, *ec); + if (U_FAILURE(*ec)) { return; } + formatter->fFormatter.formatImpl(result, *ec); +} + +U_CAPI int32_t U_EXPORT2 +unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t bufferCapacity, + UErrorCode* ec) { + const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return 0; } + + if (buffer == nullptr ? bufferCapacity != 0 : bufferCapacity < 0) { + *ec = U_ILLEGAL_ARGUMENT_ERROR; + return 0; + } + + return result->string.toTempUnicodeString().extract(buffer, bufferCapacity, *ec); +} + +U_CAPI UBool U_EXPORT2 +unumf_resultNextFieldPosition(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec) { + const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return FALSE; } + + if (ufpos == nullptr) { + *ec = U_ILLEGAL_ARGUMENT_ERROR; + return FALSE; + } + + FieldPosition fp; + fp.setField(ufpos->field); + fp.setBeginIndex(ufpos->beginIndex); + fp.setEndIndex(ufpos->endIndex); + bool retval = result->string.nextFieldPosition(fp, *ec); + ufpos->beginIndex = fp.getBeginIndex(); + ufpos->endIndex = fp.getEndIndex(); + // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool + return retval ? TRUE : FALSE; +} + +U_CAPI void U_EXPORT2 +unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, + UErrorCode* ec) { + const UFormattedNumberData* result = UFormattedNumberData::validate(uresult, *ec); + if (U_FAILURE(*ec)) { return; } + + if (ufpositer == nullptr) { + *ec = U_ILLEGAL_ARGUMENT_ERROR; + return; + } + + auto* fpi = reinterpret_cast(ufpositer); + FieldPositionIteratorHandler fpih(fpi, *ec); + result->string.getAllFieldPositions(fpih, *ec); +} + +U_CAPI void U_EXPORT2 +unumf_closeResult(UFormattedNumber* uresult) { + UErrorCode localStatus = U_ZERO_ERROR; + const UFormattedNumberData* impl = UFormattedNumberData::validate(uresult, localStatus); + delete impl; +} + +U_CAPI void U_EXPORT2 +unumf_close(UNumberFormatter* f) { + UErrorCode localStatus = U_ZERO_ERROR; + const UNumberFormatterData* impl = UNumberFormatterData::validate(f, localStatus); + delete impl; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_compact.cpp b/deps/icu-small/source/i18n/number_compact.cpp index cc0d8fd2a20cce..40278e1a012e54 100644 --- a/deps/icu-small/source/i18n/number_compact.cpp +++ b/deps/icu-small/source/i18n/number_compact.cpp @@ -3,14 +3,15 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING -#include "resource.h" -#include "number_compact.h" #include "unicode/ustring.h" #include "unicode/ures.h" #include "cstring.h" #include "charstr.h" +#include "resource.h" +#include "number_compact.h" +#include "number_microprops.h" #include "uresimp.h" using namespace icu; @@ -275,15 +276,15 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr int magnitude; if (quantity.isZero()) { magnitude = 0; - micros.rounding.apply(quantity, status); + micros.rounder.apply(quantity, status); } else { // TODO: Revisit chooseMultiplierAndApply - int multiplier = micros.rounding.chooseMultiplierAndApply(quantity, data, status); + int multiplier = micros.rounder.chooseMultiplierAndApply(quantity, data, status); magnitude = quantity.isZero() ? 0 : quantity.getMagnitude(); magnitude -= multiplier; } - StandardPlural::Form plural = quantity.getStandardPlural(rules); + StandardPlural::Form plural = utils::getStandardPlural(rules, quantity); const UChar *patternString = data.getPattern(magnitude, plural); if (patternString == nullptr) { // Use the default (non-compact) modifier. @@ -313,7 +314,7 @@ void CompactHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micr } // We already performed rounding. Do not perform it again. - micros.rounding = Rounder::constructPassThrough(); + micros.rounder = RoundingImpl::passThrough(); } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_compact.h b/deps/icu-small/source/i18n/number_compact.h index f7adf36416e92f..dda5f9f9b2dc91 100644 --- a/deps/icu-small/source/i18n/number_compact.h +++ b/deps/icu-small/source/i18n/number_compact.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_COMPACT_H__ #define __NUMBER_COMPACT_H__ diff --git a/deps/icu-small/source/i18n/number_currencysymbols.cpp b/deps/icu-small/source/i18n/number_currencysymbols.cpp new file mode 100644 index 00000000000000..0b79d6596f18c0 --- /dev/null +++ b/deps/icu-small/source/i18n/number_currencysymbols.cpp @@ -0,0 +1,123 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "number_currencysymbols.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + +CurrencySymbols::CurrencySymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status) + : fCurrency(currency), fLocaleName(locale.getName(), status) { + fCurrencySymbol.setToBogus(); + fIntlCurrencySymbol.setToBogus(); +} + +CurrencySymbols::CurrencySymbols(CurrencyUnit currency, const Locale& locale, + const DecimalFormatSymbols& symbols, UErrorCode& status) + : CurrencySymbols(currency, locale, status) { + // If either of the overrides is present, save it in the local UnicodeString. + if (symbols.isCustomCurrencySymbol()) { + fCurrencySymbol = symbols.getConstSymbol(DecimalFormatSymbols::kCurrencySymbol); + } + if (symbols.isCustomIntlCurrencySymbol()) { + fIntlCurrencySymbol = symbols.getConstSymbol(DecimalFormatSymbols::kIntlCurrencySymbol); + } +} + +const char16_t* CurrencySymbols::getIsoCode() const { + return fCurrency.getISOCurrency(); +} + +UnicodeString CurrencySymbols::getNarrowCurrencySymbol(UErrorCode& status) const { + // Note: currently no override is available for narrow currency symbol + return loadSymbol(UCURR_NARROW_SYMBOL_NAME, status); +} + +UnicodeString CurrencySymbols::getCurrencySymbol(UErrorCode& status) const { + if (!fCurrencySymbol.isBogus()) { + return fCurrencySymbol; + } + return loadSymbol(UCURR_SYMBOL_NAME, status); +} + +UnicodeString CurrencySymbols::loadSymbol(UCurrNameStyle selector, UErrorCode& status) const { + const char16_t* isoCode = fCurrency.getISOCurrency(); + UBool ignoredIsChoiceFormatFillIn = FALSE; + int32_t symbolLen = 0; + const char16_t* symbol = ucurr_getName( + isoCode, + fLocaleName.data(), + selector, + &ignoredIsChoiceFormatFillIn, + &symbolLen, + &status); + // If given an unknown currency, ucurr_getName returns the input string, which we can't alias safely! + // Otherwise, symbol points to a resource bundle, and we can use readonly-aliasing constructor. + if (symbol == isoCode) { + return UnicodeString(isoCode, 3); + } else { + return UnicodeString(TRUE, symbol, symbolLen); + } +} + +UnicodeString CurrencySymbols::getIntlCurrencySymbol(UErrorCode&) const { + if (!fIntlCurrencySymbol.isBogus()) { + return fIntlCurrencySymbol; + } + // Note: Not safe to use readonly-aliasing constructor here because the buffer belongs to this object, + // which could be destructed or moved during the lifetime of the return value. + return UnicodeString(fCurrency.getISOCurrency(), 3); +} + +UnicodeString CurrencySymbols::getPluralName(StandardPlural::Form plural, UErrorCode& status) const { + const char16_t* isoCode = fCurrency.getISOCurrency(); + UBool isChoiceFormat = FALSE; + int32_t symbolLen = 0; + const char16_t* symbol = ucurr_getPluralName( + isoCode, + fLocaleName.data(), + &isChoiceFormat, + StandardPlural::getKeyword(plural), + &symbolLen, + &status); + // If given an unknown currency, ucurr_getName returns the input string, which we can't alias safely! + // Otherwise, symbol points to a resource bundle, and we can use readonly-aliasing constructor. + if (symbol == isoCode) { + return UnicodeString(isoCode, 3); + } else { + return UnicodeString(TRUE, symbol, symbolLen); + } +} + + +CurrencyUnit +icu::number::impl::resolveCurrency(const DecimalFormatProperties& properties, const Locale& locale, + UErrorCode& status) { + if (!properties.currency.isNull()) { + return properties.currency.getNoError(); + } else { + UErrorCode localStatus = U_ZERO_ERROR; + char16_t buf[4] = {}; + ucurr_forLocale(locale.getName(), buf, 4, &localStatus); + if (U_SUCCESS(localStatus)) { + return CurrencyUnit(buf, status); + } else { + // Default currency (XXX) + return CurrencyUnit(); + } + } +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_currencysymbols.h b/deps/icu-small/source/i18n/number_currencysymbols.h new file mode 100644 index 00000000000000..9996bf96ae08a1 --- /dev/null +++ b/deps/icu-small/source/i18n/number_currencysymbols.h @@ -0,0 +1,65 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __SOURCE_NUMBER_CURRENCYSYMBOLS_H__ +#define __SOURCE_NUMBER_CURRENCYSYMBOLS_H__ + +#include "numparse_types.h" +#include "charstr.h" +#include "number_decimfmtprops.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + + +// Exported as U_I18N_API for tests +class U_I18N_API CurrencySymbols : public UMemory { + public: + CurrencySymbols() = default; // default constructor: leaves class in valid but undefined state + + /** Creates an instance in which all symbols are loaded from data. */ + CurrencySymbols(CurrencyUnit currency, const Locale& locale, UErrorCode& status); + + /** Creates an instance in which some symbols might be pre-populated. */ + CurrencySymbols(CurrencyUnit currency, const Locale& locale, const DecimalFormatSymbols& symbols, + UErrorCode& status); + + const char16_t* getIsoCode() const; + + UnicodeString getNarrowCurrencySymbol(UErrorCode& status) const; + + UnicodeString getCurrencySymbol(UErrorCode& status) const; + + UnicodeString getIntlCurrencySymbol(UErrorCode& status) const; + + UnicodeString getPluralName(StandardPlural::Form plural, UErrorCode& status) const; + + protected: + // Required fields: + CurrencyUnit fCurrency; + CharString fLocaleName; + + // Optional fields: + UnicodeString fCurrencySymbol; + UnicodeString fIntlCurrencySymbol; + + UnicodeString loadSymbol(UCurrNameStyle selector, UErrorCode& status) const; +}; + + +/** + * Resolves the effective currency from the property bag. + */ +CurrencyUnit +resolveCurrency(const DecimalFormatProperties& properties, const Locale& locale, UErrorCode& status); + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__SOURCE_NUMBER_CURRENCYSYMBOLS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_decimalquantity.cpp b/deps/icu-small/source/i18n/number_decimalquantity.cpp index b68df26ba26167..9d80e3349cb8aa 100644 --- a/deps/icu-small/source/i18n/number_decimalquantity.cpp +++ b/deps/icu-small/source/i18n/number_decimalquantity.cpp @@ -3,25 +3,30 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING -#include "uassert.h" +#include #include -#include "cmemory.h" -#include "decNumber.h" #include +#include + +#include "unicode/plurrule.h" +#include "cmemory.h" +#include "number_decnum.h" +#include "putilimp.h" #include "number_decimalquantity.h" -#include "decContext.h" -#include "decNumber.h" #include "number_roundingutils.h" #include "double-conversion.h" -#include "unicode/plurrule.h" +#include "charstr.h" +#include "number_utils.h" +#include "uassert.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; using icu::double_conversion::DoubleToStringConverter; +using icu::double_conversion::StringToDoubleConverter; namespace { @@ -29,25 +34,6 @@ int8_t NEGATIVE_FLAG = 1; int8_t INFINITY_FLAG = 2; int8_t NAN_FLAG = 4; -static constexpr int32_t DEFAULT_DIGITS = 34; -typedef MaybeStackHeaderAndArray DecNumberWithStorage; - -/** Helper function to convert a decNumber-compatible string into a decNumber. */ -void stringToDecNumber(StringPiece n, DecNumberWithStorage &dn) { - decContext set; - uprv_decContextDefault(&set, DEC_INIT_BASE); - uprv_decContextSetRounding(&set, DEC_ROUND_HALF_EVEN); - set.traps = 0; // no traps, thank you - if (n.length() > DEFAULT_DIGITS) { - dn.resize(n.length(), 0); - set.digits = n.length(); - } else { - set.digits = DEFAULT_DIGITS; - } - uprv_decNumberFromString(dn.getAlias(), n.data(), &set); - U_ASSERT(DECDPUN == 1); -} - /** Helper function for safe subtraction (no overflow). */ inline int32_t safeSubtract(int32_t a, int32_t b) { // Note: In C++, signed integer subtraction is undefined behavior. @@ -83,6 +69,7 @@ static double DOUBLE_MULTIPLIERS[] = { } // namespace +icu::IFixedDecimal::~IFixedDecimal() = default; DecimalQuantity::DecimalQuantity() { setBcdToZero(); @@ -101,11 +88,30 @@ DecimalQuantity::DecimalQuantity(const DecimalQuantity &other) { *this = other; } +DecimalQuantity::DecimalQuantity(DecimalQuantity&& src) U_NOEXCEPT { + *this = std::move(src); +} + DecimalQuantity &DecimalQuantity::operator=(const DecimalQuantity &other) { if (this == &other) { return *this; } copyBcdFrom(other); + copyFieldsFrom(other); + return *this; +} + +DecimalQuantity& DecimalQuantity::operator=(DecimalQuantity&& src) U_NOEXCEPT { + if (this == &src) { + return *this; + } + moveBcdFrom(src); + copyFieldsFrom(src); + return *this; +} + +void DecimalQuantity::copyFieldsFrom(const DecimalQuantity& other) { + bogus = other.bogus; lOptPos = other.lOptPos; lReqPos = other.lReqPos; rReqPos = other.rReqPos; @@ -116,7 +122,6 @@ DecimalQuantity &DecimalQuantity::operator=(const DecimalQuantity &other) { origDouble = other.origDouble; origDelta = other.origDelta; isApproximate = other.isApproximate; - return *this; } void DecimalQuantity::clear() { @@ -129,10 +134,16 @@ void DecimalQuantity::clear() { } void DecimalQuantity::setIntegerLength(int32_t minInt, int32_t maxInt) { - // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class. + // Validation should happen outside of DecimalQuantity, e.g., in the Precision class. U_ASSERT(minInt >= 0); U_ASSERT(maxInt >= minInt); + // Special behavior: do not set minInt to be less than what is already set. + // This is so significant digits rounding can set the integer length. + if (minInt < lReqPos) { + minInt = lReqPos; + } + // Save values into internal state // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE lOptPos = maxInt; @@ -140,7 +151,7 @@ void DecimalQuantity::setIntegerLength(int32_t minInt, int32_t maxInt) { } void DecimalQuantity::setFractionLength(int32_t minFrac, int32_t maxFrac) { - // Validation should happen outside of DecimalQuantity, e.g., in the Rounder class. + // Validation should happen outside of DecimalQuantity, e.g., in the Precision class. U_ASSERT(minFrac >= 0); U_ASSERT(maxFrac >= minFrac); @@ -160,29 +171,53 @@ uint64_t DecimalQuantity::getPositionFingerprint() const { } void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode roundingMode, - int32_t minMaxFrac, UErrorCode& status) { - // TODO: This is innefficient. Improve? - // TODO: Should we convert to decNumber instead? + int32_t maxFrac, UErrorCode& status) { + // TODO(13701): This is innefficient. Improve? + // TODO(13701): Should we convert to decNumber instead? + roundToInfinity(); double temp = toDouble(); temp /= roundingIncrement; - setToDouble(temp); - roundToMagnitude(0, roundingMode, status); - temp = toDouble(); + // Use another DecimalQuantity to perform the actual rounding... + DecimalQuantity dq; + dq.setToDouble(temp); + dq.roundToMagnitude(0, roundingMode, status); + temp = dq.toDouble(); temp *= roundingIncrement; setToDouble(temp); // Since we reset the value to a double, we need to specify the rounding boundary // in order to get the DecimalQuantity out of approximation mode. - roundToMagnitude(-minMaxFrac, roundingMode, status); + // NOTE: In Java, we have minMaxFrac, but in C++, the two are differentiated. + roundToMagnitude(-maxFrac, roundingMode, status); } -void DecimalQuantity::multiplyBy(int32_t multiplicand) { +void DecimalQuantity::multiplyBy(const DecNum& multiplicand, UErrorCode& status) { if (isInfinite() || isZero() || isNaN()) { return; } - // TODO: Should we convert to decNumber instead? - double temp = toDouble(); - temp *= multiplicand; - setToDouble(temp); + // Convert to DecNum, multiply, and convert back. + DecNum decnum; + toDecNum(decnum, status); + if (U_FAILURE(status)) { return; } + decnum.multiplyBy(multiplicand, status); + if (U_FAILURE(status)) { return; } + setToDecNum(decnum, status); +} + +void DecimalQuantity::divideBy(const DecNum& divisor, UErrorCode& status) { + if (isInfinite() || isZero() || isNaN()) { + return; + } + // Convert to DecNum, multiply, and convert back. + DecNum decnum; + toDecNum(decnum, status); + if (U_FAILURE(status)) { return; } + decnum.divideBy(divisor, status); + if (U_FAILURE(status)) { return; } + setToDecNum(decnum, status); +} + +void DecimalQuantity::negate() { + flags ^= NEGATIVE_FLAG; } int32_t DecimalQuantity::getMagnitude() const { @@ -190,21 +225,17 @@ int32_t DecimalQuantity::getMagnitude() const { return scale + precision - 1; } -void DecimalQuantity::adjustMagnitude(int32_t delta) { +bool DecimalQuantity::adjustMagnitude(int32_t delta) { if (precision != 0) { - scale += delta; - origDelta += delta; - } -} - -StandardPlural::Form DecimalQuantity::getStandardPlural(const PluralRules *rules) const { - if (rules == nullptr) { - // Fail gracefully if the user didn't provide a PluralRules - return StandardPlural::Form::OTHER; - } else { - UnicodeString ruleString = rules->select(*this); - return StandardPlural::orOtherFromString(ruleString); + // i.e., scale += delta; origDelta += delta + bool overflow = uprv_add32_overflow(scale, delta, &scale); + overflow = uprv_add32_overflow(origDelta, delta, &origDelta) || overflow; + // Make sure that precision + scale won't overflow, either + int32_t dummy; + overflow = overflow || uprv_add32_overflow(scale, precision, &dummy); + return overflow; } + return false; } double DecimalQuantity::getPluralOperand(PluralOperand operand) const { @@ -214,7 +245,8 @@ double DecimalQuantity::getPluralOperand(PluralOperand operand) const { switch (operand) { case PLURAL_OPERAND_I: - return static_cast(toLong()); + // Invert the negative sign if necessary + return static_cast(isNegative() ? -toLong(true) : toLong(true)); case PLURAL_OPERAND_F: return static_cast(toFractionLong(true)); case PLURAL_OPERAND_T: @@ -228,6 +260,10 @@ double DecimalQuantity::getPluralOperand(PluralOperand operand) const { } } +bool DecimalQuantity::hasIntegerValue() const { + return scale >= 0; +} + int32_t DecimalQuantity::getUpperDisplayMagnitude() const { // If this assertion fails, you need to call roundToInfinity() or some other rounding method. // See the comment in the header file explaining the "isApproximate" field. @@ -287,7 +323,10 @@ bool DecimalQuantity::isZero() const { DecimalQuantity &DecimalQuantity::setToInt(int32_t n) { setBcdToZero(); flags = 0; - if (n < 0) { + if (n == INT32_MIN) { + flags |= NEGATIVE_FLAG; + // leave as INT32_MIN; handled below in _setToInt() + } else if (n < 0) { flags |= NEGATIVE_FLAG; n = -n; } @@ -309,7 +348,7 @@ void DecimalQuantity::_setToInt(int32_t n) { DecimalQuantity &DecimalQuantity::setToLong(int64_t n) { setBcdToZero(); flags = 0; - if (n < 0) { + if (n < 0 && n > INT64_MIN) { flags |= NEGATIVE_FLAG; n = -n; } @@ -322,10 +361,12 @@ DecimalQuantity &DecimalQuantity::setToLong(int64_t n) { void DecimalQuantity::_setToLong(int64_t n) { if (n == INT64_MIN) { - static const char *int64minStr = "9.223372036854775808E+18"; - DecNumberWithStorage dn; - stringToDecNumber(int64minStr, dn); - readDecNumberToBcd(dn.getAlias()); + DecNum decnum; + UErrorCode localStatus = U_ZERO_ERROR; + decnum.setTo("9.223372036854775808E+18", localStatus); + if (U_FAILURE(localStatus)) { return; } // unexpected + flags |= NEGATIVE_FLAG; + readDecNumberToBcd(decnum); } else if (n <= INT32_MAX) { readIntToBcd(static_cast(n)); } else { @@ -337,7 +378,7 @@ DecimalQuantity &DecimalQuantity::setToDouble(double n) { setBcdToZero(); flags = 0; // signbit() from handles +0.0 vs -0.0 - if (std::signbit(n) != 0) { + if (std::signbit(n)) { flags |= NEGATIVE_FLAG; n = -n; } @@ -424,51 +465,107 @@ void DecimalQuantity::convertToAccurateDouble() { explicitExactDouble = true; } -DecimalQuantity &DecimalQuantity::setToDecNumber(StringPiece n) { +DecimalQuantity &DecimalQuantity::setToDecNumber(StringPiece n, UErrorCode& status) { setBcdToZero(); flags = 0; - DecNumberWithStorage dn; - stringToDecNumber(n, dn); + // Compute the decNumber representation + DecNum decnum; + decnum.setTo(n, status); - // The code path for decNumber is modeled after BigDecimal in Java. - if (decNumberIsNegative(dn.getAlias())) { - flags |= NEGATIVE_FLAG; - } - if (!decNumberIsZero(dn.getAlias())) { - _setToDecNumber(dn.getAlias()); - } + _setToDecNum(decnum, status); return *this; } -void DecimalQuantity::_setToDecNumber(decNumber *n) { - // Java fastpaths for ints here. In C++, just always read directly from the decNumber. - readDecNumberToBcd(n); - compact(); +DecimalQuantity& DecimalQuantity::setToDecNum(const DecNum& decnum, UErrorCode& status) { + setBcdToZero(); + flags = 0; + + _setToDecNum(decnum, status); + return *this; } -int64_t DecimalQuantity::toLong() const { - int64_t result = 0L; - for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { +void DecimalQuantity::_setToDecNum(const DecNum& decnum, UErrorCode& status) { + if (U_FAILURE(status)) { return; } + if (decnum.isNegative()) { + flags |= NEGATIVE_FLAG; + } + if (!decnum.isZero()) { + readDecNumberToBcd(decnum); + compact(); + } +} + +int64_t DecimalQuantity::toLong(bool truncateIfOverflow) const { + // NOTE: Call sites should be guarded by fitsInLong(), like this: + // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } + // Fallback behavior upon truncateIfOverflow is to truncate at 17 digits. + uint64_t result = 0L; + int32_t upperMagnitude = std::min(scale + precision, lOptPos) - 1; + if (truncateIfOverflow) { + upperMagnitude = std::min(upperMagnitude, 17); + } + for (int32_t magnitude = upperMagnitude; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } - return result; + if (isNegative()) { + return static_cast(0LL - result); // i.e., -result + } + return static_cast(result); } -int64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const { - int64_t result = 0L; +uint64_t DecimalQuantity::toFractionLong(bool includeTrailingZeros) const { + uint64_t result = 0L; int32_t magnitude = -1; - for (; (magnitude >= scale || (includeTrailingZeros && magnitude >= rReqPos)) && - magnitude >= rOptPos; magnitude--) { + int32_t lowerMagnitude = std::max(scale, rOptPos); + if (includeTrailingZeros) { + lowerMagnitude = std::min(lowerMagnitude, rReqPos); + } + for (; magnitude >= lowerMagnitude && result <= 1e18L; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); } + // Remove trailing zeros; this can happen during integer overflow cases. + if (!includeTrailingZeros) { + while (result > 0 && (result % 10) == 0) { + result /= 10; + } + } return result; } -double DecimalQuantity::toDouble() const { - if (isApproximate) { - return toDoubleFromOriginal(); +bool DecimalQuantity::fitsInLong(bool ignoreFraction) const { + if (isZero()) { + return true; + } + if (scale < 0 && !ignoreFraction) { + return false; } + int magnitude = getMagnitude(); + if (magnitude < 18) { + return true; + } + if (magnitude > 18) { + return false; + } + // Hard case: the magnitude is 10^18. + // The largest int64 is: 9,223,372,036,854,775,807 + for (int p = 0; p < precision; p++) { + int8_t digit = getDigit(18 - p); + static int8_t INT64_BCD[] = { 9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8 }; + if (digit < INT64_BCD[p]) { + return true; + } else if (digit > INT64_BCD[p]) { + return false; + } + } + // Exactly equal to max long plus one. + return isNegative(); +} + +double DecimalQuantity::toDouble() const { + // If this assertion fails, you need to call roundToInfinity() or some other rounding method. + // See the comment in the header file explaining the "isApproximate" field. + U_ASSERT(!isApproximate); if (isNaN()) { return NAN; @@ -476,42 +573,37 @@ double DecimalQuantity::toDouble() const { return isNegative() ? -INFINITY : INFINITY; } - int64_t tempLong = 0L; - int32_t lostDigits = precision - (precision < 17 ? precision : 17); - for (int shift = precision - 1; shift >= lostDigits; shift--) { - tempLong = tempLong * 10 + getDigitPos(shift); + // We are processing well-formed input, so we don't need any special options to StringToDoubleConverter. + StringToDoubleConverter converter(0, 0, 0, "", ""); + UnicodeString numberString = this->toScientificString(); + int32_t count; + return converter.StringToDouble( + reinterpret_cast(numberString.getBuffer()), + numberString.length(), + &count); +} + +void DecimalQuantity::toDecNum(DecNum& output, UErrorCode& status) const { + // Special handling for zero + if (precision == 0) { + output.setTo("0", status); } - double result = static_cast(tempLong); - int32_t _scale = scale + lostDigits; - if (_scale >= 0) { - // 1e22 is the largest exact double. - int32_t i = _scale; - for (; i >= 22; i -= 22) result *= 1e22; - result *= DOUBLE_MULTIPLIERS[i]; - } else { - // 1e22 is the largest exact double. - int32_t i = _scale; - for (; i <= -22; i += 22) result /= 1e22; - result /= DOUBLE_MULTIPLIERS[-i]; + + // Use the BCD constructor. We need to do a little bit of work to convert, though. + // The decNumber constructor expects most-significant first, but we store least-significant first. + MaybeStackArray ubcd(precision); + for (int32_t m = 0; m < precision; m++) { + ubcd[precision - m - 1] = static_cast(getDigitPos(m)); } - if (isNegative()) { result = -result; } - return result; + output.setTo(ubcd.getAlias(), precision, scale, isNegative(), status); } -double DecimalQuantity::toDoubleFromOriginal() const { - double result = origDouble; - int32_t delta = origDelta; - if (delta >= 0) { - // 1e22 is the largest exact double. - for (; delta >= 22; delta -= 22) result *= 1e22; - result *= DOUBLE_MULTIPLIERS[delta]; - } else { - // 1e22 is the largest exact double. - for (; delta <= -22; delta += 22) result /= 1e22; - result /= DOUBLE_MULTIPLIERS[-delta]; +void DecimalQuantity::truncate() { + if (scale < 0) { + shiftRight(-scale); + scale = 0; + compact(); } - if (isNegative()) { result *= -1; } - return result; } void DecimalQuantity::roundToMagnitude(int32_t magnitude, RoundingMode roundingMode, UErrorCode& status) { @@ -689,17 +781,63 @@ void DecimalQuantity::appendDigit(int8_t value, int32_t leadingZeros, bool appen } UnicodeString DecimalQuantity::toPlainString() const { + U_ASSERT(!isApproximate); UnicodeString sb; if (isNegative()) { sb.append(u'-'); } + if (precision == 0 || getMagnitude() < 0) { + sb.append(u'0'); + } for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) { + if (m == -1) { sb.append(u'.'); } sb.append(getDigit(m) + u'0'); - if (m == 0) { sb.append(u'.'); } } return sb; } +UnicodeString DecimalQuantity::toScientificString() const { + U_ASSERT(!isApproximate); + UnicodeString result; + if (isNegative()) { + result.append(u'-'); + } + if (precision == 0) { + result.append(u"0E+0", -1); + return result; + } + // NOTE: It is not safe to add to lOptPos (aka maxInt) or subtract from + // rOptPos (aka -maxFrac) due to overflow. + int32_t upperPos = std::min(precision + scale, lOptPos) - scale - 1; + int32_t lowerPos = std::max(scale, rOptPos) - scale; + int32_t p = upperPos; + result.append(u'0' + getDigitPos(p)); + if ((--p) >= lowerPos) { + result.append(u'.'); + for (; p >= lowerPos; p--) { + result.append(u'0' + getDigitPos(p)); + } + } + result.append(u'E'); + int32_t _scale = upperPos + scale; + if (_scale < 0) { + _scale *= -1; + result.append(u'-'); + } else { + result.append(u'+'); + } + if (_scale == 0) { + result.append(u'0'); + } + int32_t insertIndex = result.length(); + while (_scale > 0) { + std::div_t res = std::div(_scale, 10); + result.insert(insertIndex, u'0' + res.rem); + _scale = res.quot; + } + return result; +} + //////////////////////////////////////////////////// /// End of DecimalQuantity_AbstractBCD.java /// /// Start of DecimalQuantity_DualStorageBCD.java /// @@ -707,7 +845,7 @@ UnicodeString DecimalQuantity::toPlainString() const { int8_t DecimalQuantity::getDigitPos(int32_t position) const { if (usingBytes) { - if (position < 0 || position > precision) { return 0; } + if (position < 0 || position >= precision) { return 0; } return fBCD.bcdBytes.ptr[position]; } else { if (position < 0 || position >= 16) { return 0; } @@ -819,7 +957,8 @@ void DecimalQuantity::readLongToBcd(int64_t n) { } } -void DecimalQuantity::readDecNumberToBcd(decNumber *dn) { +void DecimalQuantity::readDecNumberToBcd(const DecNum& decnum) { + const decNumber* dn = decnum.getRawDecNumber(); if (dn->digits > 16) { ensureCapacity(dn->digits); for (int32_t i = 0; i < dn->digits; i++) { @@ -919,7 +1058,7 @@ void DecimalQuantity::ensureCapacity(int32_t capacity) { auto bcd1 = static_cast(uprv_malloc(capacity * 2 * sizeof(int8_t))); uprv_memcpy(bcd1, fBCD.bcdBytes.ptr, oldCapacity * sizeof(int8_t)); // Initialize the rest of the byte array to zeros (this is done automatically in Java) - uprv_memset(fBCD.bcdBytes.ptr + oldCapacity, 0, (capacity - oldCapacity) * sizeof(int8_t)); + uprv_memset(bcd1 + oldCapacity, 0, (capacity - oldCapacity) * sizeof(int8_t)); uprv_free(fBCD.bcdBytes.ptr); fBCD.bcdBytes.ptr = bcd1; fBCD.bcdBytes.len = capacity * 2; @@ -962,6 +1101,20 @@ void DecimalQuantity::copyBcdFrom(const DecimalQuantity &other) { } } +void DecimalQuantity::moveBcdFrom(DecimalQuantity &other) { + setBcdToZero(); + if (other.usingBytes) { + usingBytes = true; + fBCD.bcdBytes.ptr = other.fBCD.bcdBytes.ptr; + fBCD.bcdBytes.len = other.fBCD.bcdBytes.len; + // Take ownership away from the old instance: + other.fBCD.bcdBytes.ptr = nullptr; + other.usingBytes = false; + } else { + fBCD.bcdLong = other.fBCD.bcdLong; + } +} + const char16_t* DecimalQuantity::checkHealth() const { if (usingBytes) { if (precision == 0) { return u"Zero precision but we are in byte mode"; } @@ -1000,6 +1153,11 @@ const char16_t* DecimalQuantity::checkHealth() const { return nullptr; } +bool DecimalQuantity::operator==(const DecimalQuantity& other) const { + // FIXME: Make a faster implementation. + return toString() == other.toString(); +} + UnicodeString DecimalQuantity::toString() const { MaybeStackArray digits(precision + 1); for (int32_t i = 0; i < precision; i++) { @@ -1010,25 +1168,17 @@ UnicodeString DecimalQuantity::toString() const { snprintf( buffer8, sizeof(buffer8), - "", + "", (lOptPos > 999 ? 999 : lOptPos), lReqPos, rReqPos, (rOptPos < -999 ? -999 : rOptPos), (usingBytes ? "bytes" : "long"), + (isNegative() ? "-" : ""), (precision == 0 ? "0" : digits.getAlias()), "E", scale); return UnicodeString(buffer8, -1, US_INV); } -UnicodeString DecimalQuantity::toNumberString() const { - MaybeStackArray digits(precision + 11); - for (int32_t i = 0; i < precision; i++) { - digits[i] = getDigitPos(precision - i - 1) + '0'; - } - snprintf(digits.getAlias() + precision, 11, "E%d", scale); - return UnicodeString(digits.getAlias(), -1, US_INV); -} - #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_decimalquantity.h b/deps/icu-small/source/i18n/number_decimalquantity.h index 4309c3c6380ac4..8e04dea7eb5c43 100644 --- a/deps/icu-small/source/i18n/number_decimalquantity.h +++ b/deps/icu-small/source/i18n/number_decimalquantity.h @@ -3,13 +3,12 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_DECIMALQUANTITY_H__ #define __NUMBER_DECIMALQUANTITY_H__ #include #include "unicode/umachine.h" -#include "decNumber.h" #include "standardplural.h" #include "plurrule_impl.h" #include "number_types.h" @@ -17,6 +16,9 @@ U_NAMESPACE_BEGIN namespace number { namespace impl { +// Forward-declare (maybe don't want number_utils.h included here): +class DecNum; + /** * An class for representing a number to be processed by the decimal formatting pipeline. Includes * methods for rounding, plural rules, and decimal digit extraction. @@ -33,9 +35,12 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** Copy constructor. */ DecimalQuantity(const DecimalQuantity &other); + /** Move constructor. */ + DecimalQuantity(DecimalQuantity &&src) U_NOEXCEPT; + DecimalQuantity(); - ~DecimalQuantity(); + ~DecimalQuantity() override; /** * Sets this instance to be equal to another instance. @@ -44,6 +49,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { */ DecimalQuantity &operator=(const DecimalQuantity &other); + /** Move assignment */ + DecimalQuantity &operator=(DecimalQuantity&& src) U_NOEXCEPT; + /** * Sets the minimum and maximum integer digits that this {@link DecimalQuantity} should generate. * This method does not perform rounding. @@ -71,7 +79,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { * @param mathContext The {@link RoundingMode} to use if rounding is necessary. */ void roundToIncrement(double roundingIncrement, RoundingMode roundingMode, - int32_t minMaxFrac, UErrorCode& status); + int32_t maxFrac, UErrorCode& status); + + /** Removes all fraction digits. */ + void truncate(); /** * Rounds the number to a specified magnitude (power of ten). @@ -89,19 +100,30 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void roundToInfinity(); /** - * Multiply the internal value. + * Multiply the internal value. Uses decNumber. + * + * @param multiplicand The value by which to multiply. + */ + void multiplyBy(const DecNum& multiplicand, UErrorCode& status); + + /** + * Divide the internal value. Uses decNumber. * * @param multiplicand The value by which to multiply. */ - void multiplyBy(int32_t multiplicand); + void divideBy(const DecNum& divisor, UErrorCode& status); + + /** Flips the sign from positive to negative and back. */ + void negate(); /** * Scales the number by a power of ten. For example, if the value is currently "1234.56", calling * this method with delta=-3 will change the value to "1.23456". * * @param delta The number of magnitudes of ten to change by. + * @return true if integer overflow occured; false otherwise. */ - void adjustMagnitude(int32_t delta); + bool adjustMagnitude(int32_t delta); /** * @return The power of ten corresponding to the most significant nonzero digit. @@ -124,13 +146,23 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** @return Whether the value represented by this {@link DecimalQuantity} is not a number. */ bool isNaN() const U_OVERRIDE; - int64_t toLong() const; + /** @param truncateIfOverflow if false and the number does NOT fit, fails with an assertion error. */ + int64_t toLong(bool truncateIfOverflow = false) const; - int64_t toFractionLong(bool includeTrailingZeros) const; + uint64_t toFractionLong(bool includeTrailingZeros) const; + + /** + * Returns whether or not a Long can fully represent the value stored in this DecimalQuantity. + * @param ignoreFraction if true, silently ignore digits after the decimal place. + */ + bool fitsInLong(bool ignoreFraction = false) const; /** @return The value contained in this {@link DecimalQuantity} approximated as a double. */ double toDouble() const; + /** Computes a DecNum representation of this DecimalQuantity, saving it to the output parameter. */ + void toDecNum(DecNum& output, UErrorCode& status) const; + DecimalQuantity &setToInt(int32_t n); DecimalQuantity &setToLong(int64_t n); @@ -138,8 +170,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { DecimalQuantity &setToDouble(double n); /** decNumber is similar to BigDecimal in Java. */ + DecimalQuantity &setToDecNumber(StringPiece n, UErrorCode& status); - DecimalQuantity &setToDecNumber(StringPiece n); + /** Internal method if the caller already has a DecNum. */ + DecimalQuantity &setToDecNum(const DecNum& n, UErrorCode& status); /** * Appends a digit, optionally with one or more leading zeros, to the end of the value represented @@ -160,17 +194,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { */ void appendDigit(int8_t value, int32_t leadingZeros, bool appendAsInteger); - /** - * Computes the plural form for this number based on the specified set of rules. - * - * @param rules A {@link PluralRules} object representing the set of rules. - * @return The {@link StandardPlural} according to the PluralRules. If the plural form is not in - * the set of standard plurals, {@link StandardPlural#OTHER} is returned instead. - */ - StandardPlural::Form getStandardPlural(const PluralRules *rules) const; - double getPluralOperand(PluralOperand operand) const U_OVERRIDE; + bool hasIntegerValue() const U_OVERRIDE; + /** * Gets the digit at the specified magnitude. For example, if the represented number is 12.3, * getDigit(-1) returns 3, since 3 is the digit corresponding to 10^-1. @@ -223,10 +250,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { UnicodeString toString() const; - /* Returns the string in exponential notation. */ - UnicodeString toNumberString() const; + /** Returns the string in standard exponential notation. */ + UnicodeString toScientificString() const; - /* Returns the string without exponential notation. Slightly slower than toNumberString(). */ + /** Returns the string without exponential notation. Slightly slower than toScientificString(). */ UnicodeString toPlainString() const; /** Visible for testing */ @@ -235,6 +262,17 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** Visible for testing */ inline bool isExplicitExactDouble() { return explicitExactDouble; }; + bool operator==(const DecimalQuantity& other) const; + + inline bool operator!=(const DecimalQuantity& other) const { + return !(*this == other); + } + + /** + * Bogus flag for when a DecimalQuantity is stored on the stack. + */ + bool bogus = false; + private: /** * The power of ten corresponding to the least significant digit in the BCD. For example, if this @@ -396,12 +434,16 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { */ void readLongToBcd(int64_t n); - void readDecNumberToBcd(decNumber *dn); + void readDecNumberToBcd(const DecNum& dn); void readDoubleConversionToBcd(const char* buffer, int32_t length, int32_t point); + void copyFieldsFrom(const DecimalQuantity& other); + void copyBcdFrom(const DecimalQuantity &other); + void moveBcdFrom(DecimalQuantity& src); + /** * Removes trailing zeros from the BCD (adjusting the scale as required) and then computes the * precision. The precision is the number of digits in the number up through the greatest nonzero @@ -418,12 +460,10 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void _setToDoubleFast(double n); - void _setToDecNumber(decNumber *n); + void _setToDecNum(const DecNum& dn, UErrorCode& status); void convertToAccurateDouble(); - double toDoubleFromOriginal() const; - /** Ensure that a byte array of at least 40 digits is allocated. */ void ensureCapacity(); diff --git a/deps/icu-small/source/i18n/number_decimfmtprops.cpp b/deps/icu-small/source/i18n/number_decimfmtprops.cpp index cc57cfce6ac1fa..6754fe19eca56c 100644 --- a/deps/icu-small/source/i18n/number_decimfmtprops.cpp +++ b/deps/icu-small/source/i18n/number_decimfmtprops.cpp @@ -3,14 +3,29 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "number_decimfmtprops.h" +#include "umutex.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; + +namespace { + +char kRawDefaultProperties[sizeof(DecimalFormatProperties)]; + +icu::UInitOnce gDefaultPropertiesInitOnce = U_INITONCE_INITIALIZER; + +void U_CALLCONV initDefaultProperties(UErrorCode&) { + new(kRawDefaultProperties) DecimalFormatProperties(); // set to the default instance +} + +} + + DecimalFormatProperties::DecimalFormatProperties() { clear(); } @@ -23,8 +38,10 @@ void DecimalFormatProperties::clear() { decimalPatternMatchRequired = false; decimalSeparatorAlwaysShown = false; exponentSignAlwaysShown = false; + formatFailIfMoreThanMaxDigits = false; formatWidth = -1; groupingSize = -1; + groupingUsed = true; magnitudeMultiplier = 0; maximumFractionDigits = -1; maximumIntegerDigits = -1; @@ -34,7 +51,8 @@ void DecimalFormatProperties::clear() { minimumGroupingDigits = -1; minimumIntegerDigits = -1; minimumSignificantDigits = -1; - multiplier = 0; + multiplier = 1; + multiplierScale = 0; negativePrefix.setToBogus(); negativePrefixPattern.setToBogus(); negativeSuffix.setToBogus(); @@ -43,9 +61,10 @@ void DecimalFormatProperties::clear() { padString.setToBogus(); parseCaseSensitive = false; parseIntegerOnly = false; - parseLenient = false; + parseMode.nullify(); parseNoExponent = false; parseToBigDecimal = false; + parseAllInput = UNUM_MAYBE; positivePrefix.setToBogus(); positivePrefixPattern.setToBogus(); positiveSuffix.setToBogus(); @@ -56,47 +75,70 @@ void DecimalFormatProperties::clear() { signAlwaysShown = false; } -bool DecimalFormatProperties::operator==(const DecimalFormatProperties &other) const { +bool +DecimalFormatProperties::_equals(const DecimalFormatProperties& other, bool ignoreForFastFormat) const { bool eq = true; + + // Properties that must be equal both normally and for fast-path formatting eq = eq && compactStyle == other.compactStyle; eq = eq && currency == other.currency; eq = eq && currencyPluralInfo.fPtr.getAlias() == other.currencyPluralInfo.fPtr.getAlias(); eq = eq && currencyUsage == other.currencyUsage; - eq = eq && decimalPatternMatchRequired == other.decimalPatternMatchRequired; eq = eq && decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown; eq = eq && exponentSignAlwaysShown == other.exponentSignAlwaysShown; + eq = eq && formatFailIfMoreThanMaxDigits == other.formatFailIfMoreThanMaxDigits; eq = eq && formatWidth == other.formatWidth; - eq = eq && groupingSize == other.groupingSize; eq = eq && magnitudeMultiplier == other.magnitudeMultiplier; - eq = eq && maximumFractionDigits == other.maximumFractionDigits; - eq = eq && maximumIntegerDigits == other.maximumIntegerDigits; eq = eq && maximumSignificantDigits == other.maximumSignificantDigits; eq = eq && minimumExponentDigits == other.minimumExponentDigits; - eq = eq && minimumFractionDigits == other.minimumFractionDigits; eq = eq && minimumGroupingDigits == other.minimumGroupingDigits; - eq = eq && minimumIntegerDigits == other.minimumIntegerDigits; eq = eq && minimumSignificantDigits == other.minimumSignificantDigits; eq = eq && multiplier == other.multiplier; + eq = eq && multiplierScale == other.multiplierScale; eq = eq && negativePrefix == other.negativePrefix; - eq = eq && negativePrefixPattern == other.negativePrefixPattern; eq = eq && negativeSuffix == other.negativeSuffix; - eq = eq && negativeSuffixPattern == other.negativeSuffixPattern; eq = eq && padPosition == other.padPosition; eq = eq && padString == other.padString; - eq = eq && parseCaseSensitive == other.parseCaseSensitive; - eq = eq && parseIntegerOnly == other.parseIntegerOnly; - eq = eq && parseLenient == other.parseLenient; - eq = eq && parseNoExponent == other.parseNoExponent; - eq = eq && parseToBigDecimal == other.parseToBigDecimal; eq = eq && positivePrefix == other.positivePrefix; - eq = eq && positivePrefixPattern == other.positivePrefixPattern; eq = eq && positiveSuffix == other.positiveSuffix; - eq = eq && positiveSuffixPattern == other.positiveSuffixPattern; eq = eq && roundingIncrement == other.roundingIncrement; eq = eq && roundingMode == other.roundingMode; eq = eq && secondaryGroupingSize == other.secondaryGroupingSize; eq = eq && signAlwaysShown == other.signAlwaysShown; + + if (ignoreForFastFormat) { + return eq; + } + + // Properties ignored by fast-path formatting + // Formatting (special handling required): + eq = eq && groupingSize == other.groupingSize; + eq = eq && groupingUsed == other.groupingUsed; + eq = eq && minimumFractionDigits == other.minimumFractionDigits; + eq = eq && maximumFractionDigits == other.maximumFractionDigits; + eq = eq && maximumIntegerDigits == other.maximumIntegerDigits; + eq = eq && minimumIntegerDigits == other.minimumIntegerDigits; + eq = eq && negativePrefixPattern == other.negativePrefixPattern; + eq = eq && negativeSuffixPattern == other.negativeSuffixPattern; + eq = eq && positivePrefixPattern == other.positivePrefixPattern; + eq = eq && positiveSuffixPattern == other.positiveSuffixPattern; + + // Parsing (always safe to ignore): + eq = eq && decimalPatternMatchRequired == other.decimalPatternMatchRequired; + eq = eq && parseCaseSensitive == other.parseCaseSensitive; + eq = eq && parseIntegerOnly == other.parseIntegerOnly; + eq = eq && parseMode == other.parseMode; + eq = eq && parseNoExponent == other.parseNoExponent; + eq = eq && parseToBigDecimal == other.parseToBigDecimal; + eq = eq && parseAllInput == other.parseAllInput; + return eq; } +bool DecimalFormatProperties::equalsDefaultExceptFastFormat() const { + UErrorCode localStatus = U_ZERO_ERROR; + umtx_initOnce(gDefaultPropertiesInitOnce, &initDefaultProperties, localStatus); + return _equals(*reinterpret_cast(kRawDefaultProperties), true); +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_decimfmtprops.h b/deps/icu-small/source/i18n/number_decimfmtprops.h index 96356cad45321d..f288b6e0d97f58 100644 --- a/deps/icu-small/source/i18n/number_decimfmtprops.h +++ b/deps/icu-small/source/i18n/number_decimfmtprops.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_DECIMFMTPROPS_H__ #define __NUMBER_DECIMFMTPROPS_H__ @@ -30,21 +30,65 @@ template class U_I18N_API LocalPointer; namespace number { namespace impl { -// TODO: Figure out a nicer way to deal with CurrencyPluralInfo. // Exported as U_I18N_API because it is a public member field of exported DecimalFormatProperties -struct U_I18N_API CurrencyPluralInfoWrapper { +// Using this wrapper is rather unfortunate, but is needed on Windows platforms in order to allow +// for DLL-exporting an fully specified template instantiation. +class U_I18N_API CurrencyPluralInfoWrapper { +public: LocalPointer fPtr; - CurrencyPluralInfoWrapper() {} + CurrencyPluralInfoWrapper() = default; + CurrencyPluralInfoWrapper(const CurrencyPluralInfoWrapper& other) { if (!other.fPtr.isNull()) { fPtr.adoptInstead(new CurrencyPluralInfo(*other.fPtr)); } } + + CurrencyPluralInfoWrapper& operator=(const CurrencyPluralInfoWrapper& other) { + if (!other.fPtr.isNull()) { + fPtr.adoptInstead(new CurrencyPluralInfo(*other.fPtr)); + } + return *this; + } +}; + +/** Controls the set of rules for parsing a string from the old DecimalFormat API. */ +enum ParseMode { + /** + * Lenient mode should be used if you want to accept malformed user input. It will use heuristics + * to attempt to parse through typographical errors in the string. + */ + PARSE_MODE_LENIENT, + + /** + * Strict mode should be used if you want to require that the input is well-formed. More + * specifically, it differs from lenient mode in the following ways: + * + *
    + *
  • Grouping widths must match the grouping settings. For example, "12,3,45" will fail if the + * grouping width is 3, as in the pattern "#,##0". + *
  • The string must contain a complete prefix and suffix. For example, if the pattern is + * "{#};(#)", then "{123}" or "(123)" would match, but "{123", "123}", and "123" would all fail. + * (The latter strings would be accepted in lenient mode.) + *
  • Whitespace may not appear at arbitrary places in the string. In lenient mode, whitespace + * is allowed to occur arbitrarily before and after prefixes and exponent separators. + *
  • Leading grouping separators are not allowed, as in ",123". + *
  • Minus and plus signs can only appear if specified in the pattern. In lenient mode, a plus + * or minus sign can always precede a number. + *
  • The set of characters that can be interpreted as a decimal or grouping separator is + * smaller. + *
  • If currency parsing is enabled, currencies must only appear where + * specified in either the current pattern string or in a valid pattern string for the current + * locale. For example, if the pattern is "¤0.00", then "$1.23" would match, but "1.23$" would + * fail to match. + *
+ */ + PARSE_MODE_STRICT, }; // Exported as U_I18N_API because it is needed for the unit test PatternStringTest -struct U_I18N_API DecimalFormatProperties { +struct U_I18N_API DecimalFormatProperties : public UMemory { public: NullableValue compactStyle; @@ -54,9 +98,11 @@ struct U_I18N_API DecimalFormatProperties { bool decimalPatternMatchRequired; bool decimalSeparatorAlwaysShown; bool exponentSignAlwaysShown; + bool formatFailIfMoreThanMaxDigits; // ICU4C-only int32_t formatWidth; int32_t groupingSize; - int32_t magnitudeMultiplier; + bool groupingUsed; + int32_t magnitudeMultiplier; // internal field like multiplierScale but separate to avoid conflict int32_t maximumFractionDigits; int32_t maximumIntegerDigits; int32_t maximumSignificantDigits; @@ -66,6 +112,7 @@ struct U_I18N_API DecimalFormatProperties { int32_t minimumIntegerDigits; int32_t minimumSignificantDigits; int32_t multiplier; + int32_t multiplierScale; // ICU4C-only UnicodeString negativePrefix; UnicodeString negativePrefixPattern; UnicodeString negativeSuffix; @@ -74,9 +121,10 @@ struct U_I18N_API DecimalFormatProperties { UnicodeString padString; bool parseCaseSensitive; bool parseIntegerOnly; - bool parseLenient; + NullableValue parseMode; bool parseNoExponent; - bool parseToBigDecimal; + bool parseToBigDecimal; // TODO: Not needed in ICU4C? + UNumberFormatAttributeValue parseAllInput; // ICU4C-only //PluralRules pluralRules; UnicodeString positivePrefix; UnicodeString positivePrefixPattern; @@ -89,13 +137,20 @@ struct U_I18N_API DecimalFormatProperties { DecimalFormatProperties(); - //DecimalFormatProperties(const DecimalFormatProperties &other) = default; + inline bool operator==(const DecimalFormatProperties& other) const { + return _equals(other, false); + } - DecimalFormatProperties &operator=(const DecimalFormatProperties &other) = default; + void clear(); - bool operator==(const DecimalFormatProperties &other) const; + /** + * Checks for equality to the default DecimalFormatProperties, but ignores the prescribed set of + * options for fast-path formatting. + */ + bool equalsDefaultExceptFastFormat() const; - void clear(); + private: + bool _equals(const DecimalFormatProperties& other, bool ignoreForFastFormat) const; }; } // namespace impl diff --git a/deps/icu-small/source/i18n/number_decnum.h b/deps/icu-small/source/i18n/number_decnum.h new file mode 100644 index 00000000000000..a7793470b55695 --- /dev/null +++ b/deps/icu-small/source/i18n/number_decnum.h @@ -0,0 +1,77 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMBER_DECNUM_H__ +#define __NUMBER_DECNUM_H__ + +#include "decNumber.h" +#include "charstr.h" + +U_NAMESPACE_BEGIN + +#define DECNUM_INITIAL_CAPACITY 34 + +// Export an explicit template instantiation of the MaybeStackHeaderAndArray that is used as a data member of DecNum. +// When building DLLs for Windows this is required even though no direct access to the MaybeStackHeaderAndArray leaks out of the i18n library. +// (See digitlst.h, pluralaffix.h, datefmt.h, and others for similar examples.) +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +template class U_I18N_API MaybeStackHeaderAndArray; +#endif + +namespace number { +namespace impl { + +/** A very thin C++ wrapper around decNumber.h */ +// Exported as U_I18N_API for tests +class U_I18N_API DecNum : public UMemory { + public: + DecNum(); // leaves object in valid but undefined state + + // Copy-like constructor; use the default move operators. + DecNum(const DecNum& other, UErrorCode& status); + + /** Sets the decNumber to the StringPiece. */ + void setTo(StringPiece str, UErrorCode& status); + + /** Sets the decNumber to the NUL-terminated char string. */ + void setTo(const char* str, UErrorCode& status); + + /** Uses double_conversion to set this decNumber to the given double. */ + void setTo(double d, UErrorCode& status); + + /** Sets the decNumber to the BCD representation. */ + void setTo(const uint8_t* bcd, int32_t length, int32_t scale, bool isNegative, UErrorCode& status); + + void normalize(); + + void multiplyBy(const DecNum& rhs, UErrorCode& status); + + void divideBy(const DecNum& rhs, UErrorCode& status); + + bool isNegative() const; + + bool isZero() const; + + inline const decNumber* getRawDecNumber() const { + return fData.getAlias(); + } + + private: + static constexpr int32_t kDefaultDigits = DECNUM_INITIAL_CAPACITY; + MaybeStackHeaderAndArray fData; + decContext fContext; + + void _setTo(const char* str, int32_t maxDigits, UErrorCode& status); +}; + +} // namespace impl +} // namespace number + +U_NAMESPACE_END + +#endif // __NUMBER_DECNUM_H__ + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_fluent.cpp b/deps/icu-small/source/i18n/number_fluent.cpp index 27113106c50451..687adb6b5babd2 100644 --- a/deps/icu-small/source/i18n/number_fluent.cpp +++ b/deps/icu-small/source/i18n/number_fluent.cpp @@ -3,20 +3,26 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "uassert.h" #include "unicode/numberformatter.h" #include "number_decimalquantity.h" #include "number_formatimpl.h" #include "umutex.h" +#include "number_asformat.h" +#include "number_skeletons.h" +#include "number_utils.h" +#include "number_utypes.h" +#include "util.h" +#include "fphdlimp.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; template -Derived NumberFormatterSettings::notation(const Notation ¬ation) const { +Derived NumberFormatterSettings::notation(const Notation& notation) const& { Derived copy(*this); // NOTE: Slicing is OK. copy.fMacros.notation = notation; @@ -24,7 +30,15 @@ Derived NumberFormatterSettings::notation(const Notation ¬ation) con } template -Derived NumberFormatterSettings::unit(const icu::MeasureUnit &unit) const { +Derived NumberFormatterSettings::notation(const Notation& notation)&& { + Derived move(std::move(*this)); + // NOTE: Slicing is OK. + move.fMacros.notation = notation; + return move; +} + +template +Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit) const& { Derived copy(*this); // NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit. // TimeUnit may be affected, but TimeUnit is not as relevant to number formatting. @@ -33,21 +47,41 @@ Derived NumberFormatterSettings::unit(const icu::MeasureUnit &unit) con } template -Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit *unit) const { +Derived NumberFormatterSettings::unit(const icu::MeasureUnit& unit)&& { + Derived move(std::move(*this)); + // See comments above about slicing. + move.fMacros.unit = unit; + return move; +} + +template +Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit) const& { Derived copy(*this); - // Just copy the unit into the MacroProps by value, and delete it since we have ownership. + // Just move the unit into the MacroProps by value, and delete it since we have ownership. // NOTE: Slicing occurs here. However, CurrencyUnit can be restored from MeasureUnit. // TimeUnit may be affected, but TimeUnit is not as relevant to number formatting. if (unit != nullptr) { - // TODO: On nullptr, reset to default value? - copy.fMacros.unit = *unit; + // TODO: On nullptr, reset to default value? + copy.fMacros.unit = std::move(*unit); delete unit; } return copy; } template -Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit &perUnit) const { +Derived NumberFormatterSettings::adoptUnit(icu::MeasureUnit* unit)&& { + Derived move(std::move(*this)); + // See comments above about slicing and ownership. + if (unit != nullptr) { + // TODO: On nullptr, reset to default value? + move.fMacros.unit = std::move(*unit); + delete unit; + } + return move; +} + +template +Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit) const& { Derived copy(*this); // See comments above about slicing. copy.fMacros.perUnit = perUnit; @@ -55,27 +89,69 @@ Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit &perUni } template -Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit *perUnit) const { +Derived NumberFormatterSettings::perUnit(const icu::MeasureUnit& perUnit)&& { + Derived move(std::move(*this)); + // See comments above about slicing. + move.fMacros.perUnit = perUnit; + return move; +} + +template +Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit) const& { Derived copy(*this); // See comments above about slicing and ownership. if (perUnit != nullptr) { - // TODO: On nullptr, reset to default value? - copy.fMacros.perUnit = *perUnit; + // TODO: On nullptr, reset to default value? + copy.fMacros.perUnit = std::move(*perUnit); delete perUnit; } return copy; } template -Derived NumberFormatterSettings::rounding(const Rounder &rounder) const { +Derived NumberFormatterSettings::adoptPerUnit(icu::MeasureUnit* perUnit)&& { + Derived move(std::move(*this)); + // See comments above about slicing and ownership. + if (perUnit != nullptr) { + // TODO: On nullptr, reset to default value? + move.fMacros.perUnit = std::move(*perUnit); + delete perUnit; + } + return move; +} + +template +Derived NumberFormatterSettings::precision(const Precision& precision) const& { Derived copy(*this); // NOTE: Slicing is OK. - copy.fMacros.rounder = rounder; + copy.fMacros.precision = precision; return copy; } template -Derived NumberFormatterSettings::grouping(const UGroupingStrategy &strategy) const { +Derived NumberFormatterSettings::precision(const Precision& precision)&& { + Derived move(std::move(*this)); + // NOTE: Slicing is OK. + move.fMacros.precision = precision; + return move; +} + +template +Derived NumberFormatterSettings::roundingMode(UNumberFormatRoundingMode roundingMode) const& { + Derived copy(*this); + copy.fMacros.roundingMode = roundingMode; + return copy; +} + +template +Derived NumberFormatterSettings::roundingMode(UNumberFormatRoundingMode roundingMode)&& { + Derived move(std::move(*this)); + move.fMacros.roundingMode = roundingMode; + return move; +} + +template +Derived NumberFormatterSettings::grouping(UGroupingStrategy strategy) const& { Derived copy(*this); // NOTE: This is slightly different than how the setting is stored in Java // because we want to put it on the stack. @@ -84,61 +160,174 @@ Derived NumberFormatterSettings::grouping(const UGroupingStrategy &stra } template -Derived NumberFormatterSettings::integerWidth(const IntegerWidth &style) const { +Derived NumberFormatterSettings::grouping(UGroupingStrategy strategy)&& { + Derived move(std::move(*this)); + move.fMacros.grouper = Grouper::forStrategy(strategy); + return move; +} + +template +Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style) const& { Derived copy(*this); copy.fMacros.integerWidth = style; return copy; } template -Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols &symbols) const { +Derived NumberFormatterSettings::integerWidth(const IntegerWidth& style)&& { + Derived move(std::move(*this)); + move.fMacros.integerWidth = style; + return move; +} + +template +Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols) const& { Derived copy(*this); copy.fMacros.symbols.setTo(symbols); return copy; } template -Derived NumberFormatterSettings::adoptSymbols(NumberingSystem *ns) const { +Derived NumberFormatterSettings::symbols(const DecimalFormatSymbols& symbols)&& { + Derived move(std::move(*this)); + move.fMacros.symbols.setTo(symbols); + return move; +} + +template +Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns) const& { Derived copy(*this); copy.fMacros.symbols.setTo(ns); return copy; } template -Derived NumberFormatterSettings::unitWidth(const UNumberUnitWidth &width) const { +Derived NumberFormatterSettings::adoptSymbols(NumberingSystem* ns)&& { + Derived move(std::move(*this)); + move.fMacros.symbols.setTo(ns); + return move; +} + +template +Derived NumberFormatterSettings::unitWidth(UNumberUnitWidth width) const& { Derived copy(*this); copy.fMacros.unitWidth = width; return copy; } template -Derived NumberFormatterSettings::sign(const UNumberSignDisplay &style) const { +Derived NumberFormatterSettings::unitWidth(UNumberUnitWidth width)&& { + Derived move(std::move(*this)); + move.fMacros.unitWidth = width; + return move; +} + +template +Derived NumberFormatterSettings::sign(UNumberSignDisplay style) const& { Derived copy(*this); copy.fMacros.sign = style; return copy; } template -Derived NumberFormatterSettings::decimal(const UNumberDecimalSeparatorDisplay &style) const { +Derived NumberFormatterSettings::sign(UNumberSignDisplay style)&& { + Derived move(std::move(*this)); + move.fMacros.sign = style; + return move; +} + +template +Derived NumberFormatterSettings::decimal(UNumberDecimalSeparatorDisplay style) const& { Derived copy(*this); copy.fMacros.decimal = style; return copy; } template -Derived NumberFormatterSettings::padding(const Padder &padder) const { +Derived NumberFormatterSettings::decimal(UNumberDecimalSeparatorDisplay style)&& { + Derived move(std::move(*this)); + move.fMacros.decimal = style; + return move; +} + +template +Derived NumberFormatterSettings::scale(const Scale& scale) const& { + Derived copy(*this); + copy.fMacros.scale = scale; + return copy; +} + +template +Derived NumberFormatterSettings::scale(const Scale& scale)&& { + Derived move(std::move(*this)); + move.fMacros.scale = scale; + return move; +} + +template +Derived NumberFormatterSettings::padding(const Padder& padder) const& { Derived copy(*this); copy.fMacros.padder = padder; return copy; } template -Derived NumberFormatterSettings::threshold(int32_t threshold) const { +Derived NumberFormatterSettings::padding(const Padder& padder)&& { + Derived move(std::move(*this)); + move.fMacros.padder = padder; + return move; +} + +template +Derived NumberFormatterSettings::threshold(int32_t threshold) const& { Derived copy(*this); copy.fMacros.threshold = threshold; return copy; } +template +Derived NumberFormatterSettings::threshold(int32_t threshold)&& { + Derived move(std::move(*this)); + move.fMacros.threshold = threshold; + return move; +} + +template +Derived NumberFormatterSettings::macros(const impl::MacroProps& macros) const& { + Derived copy(*this); + copy.fMacros = macros; + return copy; +} + +template +Derived NumberFormatterSettings::macros(const impl::MacroProps& macros)&& { + Derived move(std::move(*this)); + move.fMacros = macros; + return move; +} + +template +Derived NumberFormatterSettings::macros(impl::MacroProps&& macros) const& { + Derived copy(*this); + copy.fMacros = std::move(macros); + return copy; +} + +template +Derived NumberFormatterSettings::macros(impl::MacroProps&& macros)&& { + Derived move(std::move(*this)); + move.fMacros = std::move(macros); + return move; +} + +template +UnicodeString NumberFormatterSettings::toSkeleton(UErrorCode& status) const { + if (fMacros.copyErrorTo(status)) { + return ICU_Utility::makeBogusString(); + } + return skeleton::generate(fMacros, status); +} + // Declare all classes that implement NumberFormatterSettings // See https://stackoverflow.com/a/495056/1407170 template @@ -152,38 +341,135 @@ UnlocalizedNumberFormatter NumberFormatter::with() { return result; } -LocalizedNumberFormatter NumberFormatter::withLocale(const Locale &locale) { +LocalizedNumberFormatter NumberFormatter::withLocale(const Locale& locale) { return with().locale(locale); } -// Make the child class constructor that takes the parent class call the parent class's copy constructor -UnlocalizedNumberFormatter::UnlocalizedNumberFormatter( - const NumberFormatterSettings &other) - : NumberFormatterSettings(other) { +UnlocalizedNumberFormatter +NumberFormatter::forSkeleton(const UnicodeString& skeleton, UErrorCode& status) { + return skeleton::create(skeleton, status); +} + + +template using NFS = NumberFormatterSettings; +using LNF = LocalizedNumberFormatter; +using UNF = UnlocalizedNumberFormatter; + +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(const UNF& other) + : UNF(static_cast&>(other)) {} + +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(const NFS& other) + : NFS(other) { + // No additional fields to assign +} + +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(UNF&& src) U_NOEXCEPT + : UNF(static_cast&&>(src)) {} + +UnlocalizedNumberFormatter::UnlocalizedNumberFormatter(NFS&& src) U_NOEXCEPT + : NFS(std::move(src)) { + // No additional fields to assign +} + +UnlocalizedNumberFormatter& UnlocalizedNumberFormatter::operator=(const UNF& other) { + NFS::operator=(static_cast&>(other)); + // No additional fields to assign + return *this; +} + +UnlocalizedNumberFormatter& UnlocalizedNumberFormatter::operator=(UNF&& src) U_NOEXCEPT { + NFS::operator=(static_cast&&>(src)); + // No additional fields to assign + return *this; +} + +LocalizedNumberFormatter::LocalizedNumberFormatter(const LNF& other) + : LNF(static_cast&>(other)) {} + +LocalizedNumberFormatter::LocalizedNumberFormatter(const NFS& other) + : NFS(other) { + // No additional fields to assign (let call count and compiled formatter reset to defaults) +} + +LocalizedNumberFormatter::LocalizedNumberFormatter(LocalizedNumberFormatter&& src) U_NOEXCEPT + : LNF(static_cast&&>(src)) {} + +LocalizedNumberFormatter::LocalizedNumberFormatter(NFS&& src) U_NOEXCEPT + : NFS(std::move(src)) { + // For the move operators, copy over the compiled formatter. + // Note: if the formatter is not compiled, call count information is lost. + if (static_cast(src).fCompiled != nullptr) { + lnfMoveHelper(static_cast(src)); + } +} + +LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(const LNF& other) { + NFS::operator=(static_cast&>(other)); + // No additional fields to assign (let call count and compiled formatter reset to defaults) + return *this; +} + +LocalizedNumberFormatter& LocalizedNumberFormatter::operator=(LNF&& src) U_NOEXCEPT { + NFS::operator=(static_cast&&>(src)); + // For the move operators, copy over the compiled formatter. + // Note: if the formatter is not compiled, call count information is lost. + if (static_cast(src).fCompiled != nullptr) { + // Formatter is compiled + lnfMoveHelper(static_cast(src)); + } else { + // Reset to default values. + auto* callCount = reinterpret_cast(fUnsafeCallCount); + umtx_storeRelease(*callCount, 0); + fCompiled = nullptr; + } + return *this; +} + +void LocalizedNumberFormatter::lnfMoveHelper(LNF&& src) { + // Copy over the compiled formatter and set call count to INT32_MIN as in computeCompiled(). + // Don't copy the call count directly because doing so requires a loadAcquire/storeRelease. + // The bits themselves appear to be platform-dependent, so copying them might not be safe. + auto* callCount = reinterpret_cast(fUnsafeCallCount); + umtx_storeRelease(*callCount, INT32_MIN); + fCompiled = src.fCompiled; + // Reset the source object to leave it in a safe state. + auto* srcCallCount = reinterpret_cast(src.fUnsafeCallCount); + umtx_storeRelease(*srcCallCount, 0); + src.fCompiled = nullptr; } -// Make the child class constructor that takes the parent class call the parent class's copy constructor -// For LocalizedNumberFormatter, also copy over the extra fields -LocalizedNumberFormatter::LocalizedNumberFormatter( - const NumberFormatterSettings &other) - : NumberFormatterSettings(other) { - // No additional copies required + +LocalizedNumberFormatter::~LocalizedNumberFormatter() { + delete fCompiled; } -LocalizedNumberFormatter::LocalizedNumberFormatter(const MacroProps ¯os, const Locale &locale) { +LocalizedNumberFormatter::LocalizedNumberFormatter(const MacroProps& macros, const Locale& locale) { fMacros = macros; fMacros.locale = locale; } -LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale &locale) const { +LocalizedNumberFormatter::LocalizedNumberFormatter(MacroProps&& macros, const Locale& locale) { + fMacros = std::move(macros); + fMacros.locale = locale; +} + +LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale) const& { return LocalizedNumberFormatter(fMacros, locale); } -SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper &other) { +LocalizedNumberFormatter UnlocalizedNumberFormatter::locale(const Locale& locale)&& { + return LocalizedNumberFormatter(std::move(fMacros), locale); +} + +SymbolsWrapper::SymbolsWrapper(const SymbolsWrapper& other) { doCopyFrom(other); } -SymbolsWrapper &SymbolsWrapper::operator=(const SymbolsWrapper &other) { +SymbolsWrapper::SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT { + doMoveFrom(std::move(src)); +} + +SymbolsWrapper& SymbolsWrapper::operator=(const SymbolsWrapper& other) { if (this == &other) { return *this; } @@ -192,23 +478,32 @@ SymbolsWrapper &SymbolsWrapper::operator=(const SymbolsWrapper &other) { return *this; } +SymbolsWrapper& SymbolsWrapper::operator=(SymbolsWrapper&& src) U_NOEXCEPT { + if (this == &src) { + return *this; + } + doCleanup(); + doMoveFrom(std::move(src)); + return *this; +} + SymbolsWrapper::~SymbolsWrapper() { doCleanup(); } -void SymbolsWrapper::setTo(const DecimalFormatSymbols &dfs) { +void SymbolsWrapper::setTo(const DecimalFormatSymbols& dfs) { doCleanup(); fType = SYMPTR_DFS; fPtr.dfs = new DecimalFormatSymbols(dfs); } -void SymbolsWrapper::setTo(const NumberingSystem *ns) { +void SymbolsWrapper::setTo(const NumberingSystem* ns) { doCleanup(); fType = SYMPTR_NS; fPtr.ns = ns; } -void SymbolsWrapper::doCopyFrom(const SymbolsWrapper &other) { +void SymbolsWrapper::doCopyFrom(const SymbolsWrapper& other) { fType = other.fType; switch (fType) { case SYMPTR_NONE: @@ -233,6 +528,23 @@ void SymbolsWrapper::doCopyFrom(const SymbolsWrapper &other) { } } +void SymbolsWrapper::doMoveFrom(SymbolsWrapper&& src) { + fType = src.fType; + switch (fType) { + case SYMPTR_NONE: + // No action necessary + break; + case SYMPTR_DFS: + fPtr.dfs = src.fPtr.dfs; + src.fPtr.dfs = nullptr; + break; + case SYMPTR_NS: + fPtr.ns = src.fPtr.ns; + src.fPtr.ns = nullptr; + break; + } +} + void SymbolsWrapper::doCleanup() { switch (fType) { case SYMPTR_NONE: @@ -265,53 +577,122 @@ const NumberingSystem* SymbolsWrapper::getNumberingSystem() const { return fPtr.ns; } -LocalizedNumberFormatter::~LocalizedNumberFormatter() { - delete fCompiled; -} -FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode &status) const { +FormattedNumber LocalizedNumberFormatter::formatInt(int64_t value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } - auto results = new NumberFormatterResults(); + auto results = new UFormattedNumberData(); if (results == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return FormattedNumber(status); } results->quantity.setToLong(value); - return formatImpl(results, status); + formatImpl(results, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + return FormattedNumber(results); + } else { + delete results; + return FormattedNumber(status); + } } -FormattedNumber LocalizedNumberFormatter::formatDouble(double value, UErrorCode &status) const { +FormattedNumber LocalizedNumberFormatter::formatDouble(double value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } - auto results = new NumberFormatterResults(); + auto results = new UFormattedNumberData(); if (results == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return FormattedNumber(status); } results->quantity.setToDouble(value); - return formatImpl(results, status); + formatImpl(results, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + return FormattedNumber(results); + } else { + delete results; + return FormattedNumber(status); + } } -FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErrorCode &status) const { +FormattedNumber LocalizedNumberFormatter::formatDecimal(StringPiece value, UErrorCode& status) const { if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } - auto results = new NumberFormatterResults(); + auto results = new UFormattedNumberData(); if (results == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return FormattedNumber(status); } - results->quantity.setToDecNumber(value); - return formatImpl(results, status); + results->quantity.setToDecNumber(value, status); + formatImpl(results, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + return FormattedNumber(results); + } else { + delete results; + return FormattedNumber(status); + } } FormattedNumber -LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults *results, UErrorCode &status) const { +LocalizedNumberFormatter::formatDecimalQuantity(const DecimalQuantity& dq, UErrorCode& status) const { + if (U_FAILURE(status)) { return FormattedNumber(U_ILLEGAL_ARGUMENT_ERROR); } + auto results = new UFormattedNumberData(); + if (results == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return FormattedNumber(status); + } + results->quantity = dq; + formatImpl(results, status); + + // Do not save the results object if we encountered a failure. + if (U_SUCCESS(status)) { + return FormattedNumber(results); + } else { + delete results; + return FormattedNumber(status); + } +} + +void LocalizedNumberFormatter::formatImpl(impl::UFormattedNumberData* results, UErrorCode& status) const { + if (computeCompiled(status)) { + fCompiled->apply(results->quantity, results->string, status); + } else { + NumberFormatterImpl::applyStatic(fMacros, results->quantity, results->string, status); + } +} + +void LocalizedNumberFormatter::getAffixImpl(bool isPrefix, bool isNegative, UnicodeString& result, + UErrorCode& status) const { + NumberStringBuilder string; + auto signum = static_cast(isNegative ? -1 : 1); + // Always return affixes for plural form OTHER. + static const StandardPlural::Form plural = StandardPlural::OTHER; + int32_t prefixLength; + if (computeCompiled(status)) { + prefixLength = fCompiled->getPrefixSuffix(signum, plural, string, status); + } else { + prefixLength = NumberFormatterImpl::getPrefixSuffixStatic(fMacros, signum, plural, string, status); + } + result.remove(); + if (isPrefix) { + result.append(string.toTempUnicodeString().tempSubStringBetween(0, prefixLength)); + } else { + result.append(string.toTempUnicodeString().tempSubStringBetween(prefixLength, string.length())); + } +} + +bool LocalizedNumberFormatter::computeCompiled(UErrorCode& status) const { // fUnsafeCallCount contains memory to be interpreted as an atomic int, most commonly // std::atomic. Since the type of atomic int is platform-dependent, we cast the // bytes in fUnsafeCallCount to u_atomic_int32_t, a typedef for the platform-dependent // atomic int type defined in umutex.h. - static_assert(sizeof(u_atomic_int32_t) <= sizeof(fUnsafeCallCount), - "Atomic integer size on this platform exceeds the size allocated by fUnsafeCallCount"); - u_atomic_int32_t* callCount = reinterpret_cast( - const_cast(this)->fUnsafeCallCount); + static_assert( + sizeof(u_atomic_int32_t) <= sizeof(fUnsafeCallCount), + "Atomic integer size on this platform exceeds the size allocated by fUnsafeCallCount"); + auto* callCount = reinterpret_cast( + const_cast(this)->fUnsafeCallCount); // A positive value in the atomic int indicates that the data structure is not yet ready; // a negative value indicates that it is ready. If, after the increment, the atomic int @@ -325,64 +706,144 @@ LocalizedNumberFormatter::formatImpl(impl::NumberFormatterResults *results, UErr if (currentCount == fMacros.threshold && fMacros.threshold > 0) { // Build the data structure and then use it (slow to fast path). - const NumberFormatterImpl* compiled = - NumberFormatterImpl::fromMacros(fMacros, status); + const NumberFormatterImpl* compiled = NumberFormatterImpl::fromMacros(fMacros, status); U_ASSERT(fCompiled == nullptr); - const_cast(this)->fCompiled = compiled; + const_cast(this)->fCompiled = compiled; umtx_storeRelease(*callCount, INT32_MIN); - compiled->apply(results->quantity, results->string, status); + return true; } else if (currentCount < 0) { // The data structure is already built; use it (fast path). U_ASSERT(fCompiled != nullptr); - fCompiled->apply(results->quantity, results->string, status); + return true; } else { // Format the number without building the data structure (slow path). - NumberFormatterImpl::applyStatic(fMacros, results->quantity, results->string, status); + return false; } +} - // Do not save the results object if we encountered a failure. - if (U_SUCCESS(status)) { - return FormattedNumber(results); - } else { - delete results; - return FormattedNumber(status); - } +const impl::NumberFormatterImpl* LocalizedNumberFormatter::getCompiled() const { + return fCompiled; +} + +int32_t LocalizedNumberFormatter::getCallCount() const { + auto* callCount = reinterpret_cast( + const_cast(this)->fUnsafeCallCount); + return umtx_loadAcquire(*callCount); +} + +Format* LocalizedNumberFormatter::toFormat(UErrorCode& status) const { + LocalPointer retval( + new LocalizedNumberFormatterAsFormat(*this, fMacros.locale), status); + return retval.orphan(); +} + + +FormattedNumber::FormattedNumber(FormattedNumber&& src) U_NOEXCEPT + : fResults(src.fResults), fErrorCode(src.fErrorCode) { + // Disown src.fResults to prevent double-deletion + src.fResults = nullptr; + src.fErrorCode = U_INVALID_STATE_ERROR; +} + +FormattedNumber& FormattedNumber::operator=(FormattedNumber&& src) U_NOEXCEPT { + delete fResults; + fResults = src.fResults; + fErrorCode = src.fErrorCode; + // Disown src.fResults to prevent double-deletion + src.fResults = nullptr; + src.fErrorCode = U_INVALID_STATE_ERROR; + return *this; } UnicodeString FormattedNumber::toString() const { + UErrorCode localStatus = U_ZERO_ERROR; + return toString(localStatus); +} + +UnicodeString FormattedNumber::toString(UErrorCode& status) const { + if (U_FAILURE(status)) { + return ICU_Utility::makeBogusString(); + } if (fResults == nullptr) { - // TODO: http://bugs.icu-project.org/trac/ticket/13437 - return {}; + status = fErrorCode; + return ICU_Utility::makeBogusString(); } return fResults->string.toUnicodeString(); } -Appendable &FormattedNumber::appendTo(Appendable &appendable) { +Appendable& FormattedNumber::appendTo(Appendable& appendable) { + UErrorCode localStatus = U_ZERO_ERROR; + return appendTo(appendable, localStatus); +} + +Appendable& FormattedNumber::appendTo(Appendable& appendable, UErrorCode& status) { + if (U_FAILURE(status)) { + return appendable; + } if (fResults == nullptr) { - // TODO: http://bugs.icu-project.org/trac/ticket/13437 + status = fErrorCode; return appendable; } appendable.appendString(fResults->string.chars(), fResults->string.length()); return appendable; } -void FormattedNumber::populateFieldPosition(FieldPosition &fieldPosition, UErrorCode &status) { - if (U_FAILURE(status)) { return; } +void FormattedNumber::populateFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + if (fResults == nullptr) { + status = fErrorCode; + return; + } + // in case any users were depending on the old behavior: + fieldPosition.setBeginIndex(0); + fieldPosition.setEndIndex(0); + fResults->string.nextFieldPosition(fieldPosition, status); +} + +UBool FormattedNumber::nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const { + if (U_FAILURE(status)) { + return FALSE; + } + if (fResults == nullptr) { + status = fErrorCode; + return FALSE; + } + // NOTE: MSVC sometimes complains when implicitly converting between bool and UBool + return fResults->string.nextFieldPosition(fieldPosition, status) ? TRUE : FALSE; +} + +void FormattedNumber::populateFieldPositionIterator(FieldPositionIterator& iterator, UErrorCode& status) { + getAllFieldPositions(iterator, status); +} + +void FormattedNumber::getAllFieldPositions(FieldPositionIterator& iterator, UErrorCode& status) const { + FieldPositionIteratorHandler fpih(&iterator, status); + getAllFieldPositionsImpl(fpih, status); +} + +void FormattedNumber::getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih, + UErrorCode& status) const { + if (U_FAILURE(status)) { + return; + } if (fResults == nullptr) { status = fErrorCode; return; } - fResults->string.populateFieldPosition(fieldPosition, 0, status); + fResults->string.getAllFieldPositions(fpih, status); } -void -FormattedNumber::populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status) { - if (U_FAILURE(status)) { return; } +void FormattedNumber::getDecimalQuantity(DecimalQuantity& output, UErrorCode& status) const { + if (U_FAILURE(status)) { + return; + } if (fResults == nullptr) { status = fErrorCode; return; } - fResults->string.populateFieldPositionIterator(iterator, status); + output = fResults->quantity; } FormattedNumber::~FormattedNumber() { diff --git a/deps/icu-small/source/i18n/number_formatimpl.cpp b/deps/icu-small/source/i18n/number_formatimpl.cpp index bc96cb15dabf90..3f887128bcc668 100644 --- a/deps/icu-small/source/i18n/number_formatimpl.cpp +++ b/deps/icu-small/source/i18n/number_formatimpl.cpp @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "cstring.h" #include "unicode/ures.h" @@ -26,88 +26,28 @@ using namespace icu::number::impl; namespace { -// NOTE: In Java, the method to get a pattern from the resource bundle exists in NumberFormat. -// In C++, we have to implement that logic here. -// TODO: Make Java and C++ consistent? - -enum CldrPatternStyle { - CLDR_PATTERN_STYLE_DECIMAL, - CLDR_PATTERN_STYLE_CURRENCY, - CLDR_PATTERN_STYLE_ACCOUNTING, - CLDR_PATTERN_STYLE_PERCENT - // TODO: Consider scientific format. -}; - -const char16_t * -doGetPattern(UResourceBundle *res, const char *nsName, const char *patternKey, UErrorCode &publicStatus, - UErrorCode &localStatus) { - // Construct the path into the resource bundle - CharString key; - key.append("NumberElements/", publicStatus); - key.append(nsName, publicStatus); - key.append("/patterns/", publicStatus); - key.append(patternKey, publicStatus); - if (U_FAILURE(publicStatus)) { - return u""; - } - return ures_getStringByKeyWithFallback(res, key.data(), nullptr, &localStatus); -} - -const char16_t *getPatternForStyle(const Locale &locale, const char *nsName, CldrPatternStyle style, - UErrorCode &status) { - const char *patternKey; - switch (style) { - case CLDR_PATTERN_STYLE_DECIMAL: - patternKey = "decimalFormat"; - break; - case CLDR_PATTERN_STYLE_CURRENCY: - patternKey = "currencyFormat"; - break; - case CLDR_PATTERN_STYLE_ACCOUNTING: - patternKey = "accountingFormat"; - break; - case CLDR_PATTERN_STYLE_PERCENT: - default: - patternKey = "percentFormat"; - break; - } - LocalUResourceBundlePointer res(ures_open(nullptr, locale.getName(), &status)); - if (U_FAILURE(status)) { return u""; } - - // Attempt to get the pattern with the native numbering system. - UErrorCode localStatus = U_ZERO_ERROR; - const char16_t *pattern; - pattern = doGetPattern(res.getAlias(), nsName, patternKey, status, localStatus); - if (U_FAILURE(status)) { return u""; } - - // Fall back to latn if native numbering system does not have the right pattern - if (U_FAILURE(localStatus) && uprv_strcmp("latn", nsName) != 0) { - localStatus = U_ZERO_ERROR; - pattern = doGetPattern(res.getAlias(), "latn", patternKey, status, localStatus); - if (U_FAILURE(status)) { return u""; } - } - - return pattern; -} - struct CurrencyFormatInfoResult { bool exists; const char16_t* pattern; const char16_t* decimalSeparator; const char16_t* groupingSeparator; }; -CurrencyFormatInfoResult getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) { + +CurrencyFormatInfoResult +getCurrencyFormatInfo(const Locale& locale, const char* isoCode, UErrorCode& status) { // TODO: Load this data in a centralized location like ICU4J? + // TODO: Move this into the CurrencySymbols class? // TODO: Parts of this same data are loaded in dcfmtsym.cpp; should clean up. - CurrencyFormatInfoResult result = { false, nullptr, nullptr, nullptr }; - if (U_FAILURE(status)) return result; + CurrencyFormatInfoResult result = {false, nullptr, nullptr, nullptr}; + if (U_FAILURE(status)) { return result; } CharString key; key.append("Currencies/", status); key.append(isoCode, status); UErrorCode localStatus = status; LocalUResourceBundlePointer bundle(ures_open(U_ICUDATA_CURR, locale.getName(), &localStatus)); ures_getByKeyWithFallback(bundle.getAlias(), key.data(), bundle.getAlias(), &localStatus); - if (U_SUCCESS(localStatus) && ures_getSize(bundle.getAlias())>2) { // the length is 3 if more data is present + if (U_SUCCESS(localStatus) && + ures_getSize(bundle.getAlias()) > 2) { // the length is 3 if more data is present ures_getByIndex(bundle.getAlias(), 2, bundle.getAlias(), &localStatus); int32_t dummy; result.exists = true; @@ -121,65 +61,84 @@ CurrencyFormatInfoResult getCurrencyFormatInfo(const Locale& locale, const char* return result; } -inline bool unitIsCurrency(const MeasureUnit &unit) { - return uprv_strcmp("currency", unit.getType()) == 0; -} - -inline bool unitIsNoUnit(const MeasureUnit &unit) { - return uprv_strcmp("none", unit.getType()) == 0; -} +} // namespace -inline bool unitIsPercent(const MeasureUnit &unit) { - return uprv_strcmp("percent", unit.getSubtype()) == 0; -} -inline bool unitIsPermille(const MeasureUnit &unit) { - return uprv_strcmp("permille", unit.getSubtype()) == 0; -} +MicroPropsGenerator::~MicroPropsGenerator() = default; -} // namespace -NumberFormatterImpl *NumberFormatterImpl::fromMacros(const MacroProps ¯os, UErrorCode &status) { +NumberFormatterImpl* NumberFormatterImpl::fromMacros(const MacroProps& macros, UErrorCode& status) { return new NumberFormatterImpl(macros, true, status); } -void NumberFormatterImpl::applyStatic(const MacroProps ¯os, DecimalQuantity &inValue, - NumberStringBuilder &outString, UErrorCode &status) { +void NumberFormatterImpl::applyStatic(const MacroProps& macros, DecimalQuantity& inValue, + NumberStringBuilder& outString, UErrorCode& status) { NumberFormatterImpl impl(macros, false, status); impl.applyUnsafe(inValue, outString, status); } +int32_t NumberFormatterImpl::getPrefixSuffixStatic(const MacroProps& macros, int8_t signum, + StandardPlural::Form plural, + NumberStringBuilder& outString, UErrorCode& status) { + NumberFormatterImpl impl(macros, false, status); + return impl.getPrefixSuffixUnsafe(signum, plural, outString, status); +} + // NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA: // The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance. // The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation. // See MicroProps::processQuantity() for details. -void NumberFormatterImpl::apply(DecimalQuantity &inValue, NumberStringBuilder &outString, - UErrorCode &status) const { +void NumberFormatterImpl::apply(DecimalQuantity& inValue, NumberStringBuilder& outString, + UErrorCode& status) const { if (U_FAILURE(status)) { return; } MicroProps micros; + if (!fMicroPropsGenerator) { return; } fMicroPropsGenerator->processQuantity(inValue, micros, status); if (U_FAILURE(status)) { return; } microsToString(micros, inValue, outString, status); } -void NumberFormatterImpl::applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, - UErrorCode &status) { +void NumberFormatterImpl::applyUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString, + UErrorCode& status) { if (U_FAILURE(status)) { return; } fMicroPropsGenerator->processQuantity(inValue, fMicros, status); if (U_FAILURE(status)) { return; } microsToString(fMicros, inValue, outString, status); } -NumberFormatterImpl::NumberFormatterImpl(const MacroProps ¯os, bool safe, UErrorCode &status) { +int32_t NumberFormatterImpl::getPrefixSuffix(int8_t signum, StandardPlural::Form plural, + NumberStringBuilder& outString, UErrorCode& status) const { + if (U_FAILURE(status)) { return 0; } + // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier). + // Safe path: use fImmutablePatternModifier. + const Modifier* modifier = fImmutablePatternModifier->getModifier(signum, plural); + modifier->apply(outString, 0, 0, status); + if (U_FAILURE(status)) { return 0; } + return modifier->getPrefixLength(status); +} + +int32_t NumberFormatterImpl::getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural, + NumberStringBuilder& outString, UErrorCode& status) { + if (U_FAILURE(status)) { return 0; } + // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle, aka pattern modifier). + // Unsafe path: use fPatternModifier. + fPatternModifier->setNumberProperties(signum, plural); + fPatternModifier->apply(outString, 0, 0, status); + if (U_FAILURE(status)) { return 0; } + return fPatternModifier->getPrefixLength(status); +} + +NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) { fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status); } ////////// -const MicroPropsGenerator * -NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, UErrorCode &status) { - const MicroPropsGenerator *chain = &fMicros; +const MicroPropsGenerator* +NumberFormatterImpl::macrosToMicroGenerator(const MacroProps& macros, bool safe, UErrorCode& status) { + if (U_FAILURE(status)) { return nullptr; } + const MicroPropsGenerator* chain = &fMicros; // Check that macros is error-free before continuing. if (macros.copyErrorTo(status)) { @@ -189,18 +148,26 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, // TODO: Accept currency symbols from DecimalFormatSymbols? // Pre-compute a few values for efficiency. - bool isCurrency = unitIsCurrency(macros.unit); - bool isNoUnit = unitIsNoUnit(macros.unit); - bool isPercent = isNoUnit && unitIsPercent(macros.unit); - bool isPermille = isNoUnit && unitIsPermille(macros.unit); + bool isCurrency = utils::unitIsCurrency(macros.unit); + bool isNoUnit = utils::unitIsNoUnit(macros.unit); + bool isPercent = isNoUnit && utils::unitIsPercent(macros.unit); + bool isPermille = isNoUnit && utils::unitIsPermille(macros.unit); bool isCldrUnit = !isCurrency && !isNoUnit; - bool isAccounting = macros.sign == UNUM_SIGN_ACCOUNTING - || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS - || macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; - CurrencyUnit currency(kDefaultCurrency, status); + bool isAccounting = + macros.sign == UNUM_SIGN_ACCOUNTING || macros.sign == UNUM_SIGN_ACCOUNTING_ALWAYS || + macros.sign == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; + CurrencyUnit currency(nullptr, status); if (isCurrency) { currency = CurrencyUnit(macros.unit, status); // Restore CurrencyUnit from MeasureUnit } + const CurrencySymbols* currencySymbols; + if (macros.currencySymbols != nullptr) { + // Used by the DecimalFormat code path + currencySymbols = macros.currencySymbols; + } else { + fWarehouse.fCurrencySymbols = {currency, macros.locale, status}; + currencySymbols = &fWarehouse.fCurrencySymbols; + } UNumberUnitWidth unitWidth = UNUM_UNIT_WIDTH_SHORT; if (macros.unitWidth != UNUM_UNIT_WIDTH_COUNT) { unitWidth = macros.unitWidth; @@ -208,7 +175,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, // Select the numbering system. LocalPointer nsLocal; - const NumberingSystem *ns; + const NumberingSystem* ns; if (macros.symbols.isNumberingSystem()) { ns = macros.symbols.getNumberingSystem(); } else { @@ -217,7 +184,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, // Give ownership to the function scope. nsLocal.adoptInstead(ns); } - const char *nsName = U_SUCCESS(status) ? ns->getName() : "latn"; + const char* nsName = U_SUCCESS(status) ? ns->getName() : "latn"; // Resolve the symbols. Do this here because currency may need to customize them. if (macros.symbols.isDecimalFormatSymbols()) { @@ -232,21 +199,22 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, // If we are formatting currency, check for a currency-specific pattern. const char16_t* pattern = nullptr; if (isCurrency) { - CurrencyFormatInfoResult info = getCurrencyFormatInfo(macros.locale, currency.getSubtype(), status); + CurrencyFormatInfoResult info = getCurrencyFormatInfo( + macros.locale, currency.getSubtype(), status); if (info.exists) { pattern = info.pattern; // It's clunky to clone an object here, but this code is not frequently executed. - DecimalFormatSymbols* symbols = new DecimalFormatSymbols(*fMicros.symbols); + auto* symbols = new DecimalFormatSymbols(*fMicros.symbols); fMicros.symbols = symbols; fSymbols.adoptInstead(symbols); symbols->setSymbol( - DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol, - UnicodeString(info.decimalSeparator), - FALSE); + DecimalFormatSymbols::ENumberFormatSymbol::kMonetarySeparatorSymbol, + UnicodeString(info.decimalSeparator), + FALSE); symbols->setSymbol( - DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol, - UnicodeString(info.groupingSeparator), - FALSE); + DecimalFormatSymbols::ENumberFormatSymbol::kMonetaryGroupingSeparatorSymbol, + UnicodeString(info.groupingSeparator), + FALSE); } } if (pattern == nullptr) { @@ -262,7 +230,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, } else { patternStyle = CLDR_PATTERN_STYLE_CURRENCY; } - pattern = getPatternForStyle(macros.locale, nsName, patternStyle, status); + pattern = utils::getPatternForStyle(macros.locale, nsName, patternStyle, status); } auto patternInfo = new ParsedPatternInfo(); fPatternInfo.adoptInstead(patternInfo); @@ -272,17 +240,31 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, /// START POPULATING THE DEFAULT MICROPROPS AND BUILDING THE MICROPROPS GENERATOR /// ///////////////////////////////////////////////////////////////////////////////////// + // Multiplier + if (macros.scale.isValid()) { + fMicros.helpers.multiplier.setAndChain(macros.scale, chain); + chain = &fMicros.helpers.multiplier; + } + // Rounding strategy - if (!macros.rounder.isBogus()) { - fMicros.rounding = macros.rounder; + Precision precision; + if (!macros.precision.isBogus()) { + precision = macros.precision; } else if (macros.notation.fType == Notation::NTN_COMPACT) { - fMicros.rounding = Rounder::integer().withMinDigits(2); + precision = Precision::integer().withMinDigits(2); } else if (isCurrency) { - fMicros.rounding = Rounder::currency(UCURR_USAGE_STANDARD); + precision = Precision::currency(UCURR_USAGE_STANDARD); + } else { + precision = Precision::maxFraction(6); + } + UNumberFormatRoundingMode roundingMode; + if (macros.roundingMode != kDefaultMode) { + roundingMode = macros.roundingMode; } else { - fMicros.rounding = Rounder::maxFraction(6); + // Temporary until ICU 64 + roundingMode = precision.fRoundingMode; } - fMicros.rounding.setLocaleData(currency, status); + fMicros.rounder = {precision, roundingMode, currency, status}; // Grouping strategy if (!macros.grouper.isBogus()) { @@ -306,7 +288,7 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, if (!macros.integerWidth.isBogus()) { fMicros.integerWidth = macros.integerWidth; } else { - fMicros.integerWidth = IntegerWidth::zeroFillTo(1); + fMicros.integerWidth = IntegerWidth::standard(); } // Sign display @@ -338,16 +320,18 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, // Middle modifier (patterns, positive/negative, currency symbols, percent) auto patternModifier = new MutablePatternModifier(false); fPatternModifier.adoptInstead(patternModifier); - patternModifier->setPatternInfo(fPatternInfo.getAlias()); + patternModifier->setPatternInfo( + macros.affixProvider != nullptr ? macros.affixProvider + : static_cast(fPatternInfo.getAlias())); patternModifier->setPatternAttributes(fMicros.sign, isPermille); if (patternModifier->needsPlurals()) { patternModifier->setSymbols( fMicros.symbols, - currency, + currencySymbols, unitWidth, resolvePluralRules(macros.rules, macros.locale, status)); } else { - patternModifier->setSymbols(fMicros.symbols, currency, unitWidth, nullptr); + patternModifier->setSymbols(fMicros.symbols, currencySymbols, unitWidth, nullptr); } if (safe) { fImmutablePatternModifier.adoptInstead(patternModifier->createImmutableAndChain(chain, status)); @@ -407,9 +391,9 @@ NumberFormatterImpl::macrosToMicroGenerator(const MacroProps ¯os, bool safe, return chain; } -const PluralRules * -NumberFormatterImpl::resolvePluralRules(const PluralRules *rulesPtr, const Locale &locale, - UErrorCode &status) { +const PluralRules* +NumberFormatterImpl::resolvePluralRules(const PluralRules* rulesPtr, const Locale& locale, + UErrorCode& status) { if (rulesPtr != nullptr) { return rulesPtr; } @@ -420,9 +404,9 @@ NumberFormatterImpl::resolvePluralRules(const PluralRules *rulesPtr, const Local return fRules.getAlias(); } -int32_t NumberFormatterImpl::microsToString(const MicroProps µs, DecimalQuantity &quantity, - NumberStringBuilder &string, UErrorCode &status) { - micros.rounding.apply(quantity, status); +int32_t NumberFormatterImpl::microsToString(const MicroProps& micros, DecimalQuantity& quantity, + NumberStringBuilder& string, UErrorCode& status) { + micros.rounder.apply(quantity, status); micros.integerWidth.apply(quantity, status); int32_t length = writeNumber(micros, quantity, string, status); // NOTE: When range formatting is added, these modifiers can bubble up. @@ -439,8 +423,8 @@ int32_t NumberFormatterImpl::microsToString(const MicroProps µs, DecimalQua return length; } -int32_t NumberFormatterImpl::writeNumber(const MicroProps µs, DecimalQuantity &quantity, - NumberStringBuilder &string, UErrorCode &status) { +int32_t NumberFormatterImpl::writeNumber(const MicroProps& micros, DecimalQuantity& quantity, + NumberStringBuilder& string, UErrorCode& status) { int32_t length = 0; if (quantity.isInfinite()) { length += string.insert( @@ -480,8 +464,8 @@ int32_t NumberFormatterImpl::writeNumber(const MicroProps µs, DecimalQuanti return length; } -int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps µs, DecimalQuantity &quantity, - NumberStringBuilder &string, UErrorCode &status) { +int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps& micros, DecimalQuantity& quantity, + NumberStringBuilder& string, UErrorCode& status) { int length = 0; int integerCount = quantity.getUpperDisplayMagnitude() + 1; for (int i = 0; i < integerCount; i++) { @@ -499,21 +483,21 @@ int32_t NumberFormatterImpl::writeIntegerDigits(const MicroProps µs, Decima // Get and append the next digit value int8_t nextDigit = quantity.getDigit(i); - length += string.insert( - 0, getDigitFromSymbols(nextDigit, *micros.symbols), UNUM_INTEGER_FIELD, status); + length += utils::insertDigitFromSymbols( + string, 0, nextDigit, *micros.symbols, UNUM_INTEGER_FIELD, status); } return length; } -int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps µs, DecimalQuantity &quantity, - NumberStringBuilder &string, UErrorCode &status) { +int32_t NumberFormatterImpl::writeFractionDigits(const MicroProps& micros, DecimalQuantity& quantity, + NumberStringBuilder& string, UErrorCode& status) { int length = 0; int fractionCount = -quantity.getLowerDisplayMagnitude(); for (int i = 0; i < fractionCount; i++) { // Get and append the next digit value int8_t nextDigit = quantity.getDigit(-i - 1); - length += string.append( - getDigitFromSymbols(nextDigit, *micros.symbols), UNUM_FRACTION_FIELD, status); + length += utils::insertDigitFromSymbols( + string, string.length(), nextDigit, *micros.symbols, UNUM_FRACTION_FIELD, status); } return length; } diff --git a/deps/icu-small/source/i18n/number_formatimpl.h b/deps/icu-small/source/i18n/number_formatimpl.h index cbc04ba30df4c4..744fecec13f984 100644 --- a/deps/icu-small/source/i18n/number_formatimpl.h +++ b/deps/icu-small/source/i18n/number_formatimpl.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_FORMATIMPL_H__ #define __NUMBER_FORMATIMPL_H__ @@ -14,6 +14,7 @@ #include "number_patternmodifier.h" #include "number_longnames.h" #include "number_compact.h" +#include "number_microprops.h" U_NAMESPACE_BEGIN namespace number { namespace impl { @@ -37,10 +38,26 @@ class NumberFormatterImpl : public UMemory { applyStatic(const MacroProps ¯os, DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status); + /** + * Prints only the prefix and suffix; used for DecimalFormat getters. + * + * @return The index into the output at which the prefix ends and the suffix starts; in other words, + * the prefix length. + */ + static int32_t getPrefixSuffixStatic(const MacroProps& macros, int8_t signum, + StandardPlural::Form plural, NumberStringBuilder& outString, + UErrorCode& status); + /** * Evaluates the "safe" MicroPropsGenerator created by "fromMacros". */ - void apply(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status) const; + void apply(DecimalQuantity& inValue, NumberStringBuilder& outString, UErrorCode& status) const; + + /** + * Like getPrefixSuffixStatic() but uses the safe compiled object. + */ + int32_t getPrefixSuffix(int8_t signum, StandardPlural::Form plural, NumberStringBuilder& outString, + UErrorCode& status) const; private: // Head of the MicroPropsGenerator linked list: @@ -50,21 +67,29 @@ class NumberFormatterImpl : public UMemory { MicroProps fMicros; // Other fields possibly used by the number formatting pipeline: - // TODO: Convert some of these LocalPointers to value objects to reduce the number of news? + // TODO: Convert more of these LocalPointers to value objects to reduce the number of news? LocalPointer fSymbols; LocalPointer fRules; LocalPointer fPatternInfo; LocalPointer fScientificHandler; - LocalPointer fPatternModifier; + LocalPointer fPatternModifier; LocalPointer fImmutablePatternModifier; LocalPointer fLongNameHandler; LocalPointer fCompactHandler; + // Value objects possibly used by the number formatting pipeline: + struct Warehouse { + CurrencySymbols fCurrencySymbols; + } fWarehouse; + NumberFormatterImpl(const MacroProps ¯os, bool safe, UErrorCode &status); void applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status); + int32_t getPrefixSuffixUnsafe(int8_t signum, StandardPlural::Form plural, + NumberStringBuilder& outString, UErrorCode& status); + /** * If rulesPtr is non-null, return it. Otherwise, return a PluralRules owned by this object for the * specified locale, creating it if necessary. diff --git a/deps/icu-small/source/i18n/number_grouping.cpp b/deps/icu-small/source/i18n/number_grouping.cpp index a2b1bbd6b3388e..4a1cceb49948b9 100644 --- a/deps/icu-small/source/i18n/number_grouping.cpp +++ b/deps/icu-small/source/i18n/number_grouping.cpp @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "unicode/numberformatter.h" #include "number_patternstring.h" @@ -37,20 +37,33 @@ int16_t getMinGroupingForLocale(const Locale& locale) { Grouper Grouper::forStrategy(UGroupingStrategy grouping) { switch (grouping) { case UNUM_GROUPING_OFF: - return {-1, -1, -2}; + return {-1, -1, -2, grouping}; case UNUM_GROUPING_AUTO: - return {-2, -2, -2}; + return {-2, -2, -2, grouping}; case UNUM_GROUPING_MIN2: - return {-2, -2, -3}; + return {-2, -2, -3, grouping}; case UNUM_GROUPING_ON_ALIGNED: - return {-4, -4, 1}; + return {-4, -4, 1, grouping}; case UNUM_GROUPING_THOUSANDS: - return {3, 3, 1}; + return {3, 3, 1, grouping}; default: U_ASSERT(FALSE); + return {}; // return a value: silence compiler warning } } +Grouper Grouper::forProperties(const DecimalFormatProperties& properties) { + if (!properties.groupingUsed) { + return forStrategy(UNUM_GROUPING_OFF); + } + auto grouping1 = static_cast(properties.groupingSize); + auto grouping2 = static_cast(properties.secondaryGroupingSize); + auto minGrouping = static_cast(properties.minimumGroupingDigits); + grouping1 = grouping1 > 0 ? grouping1 : grouping2 > 0 ? grouping2 : grouping1; + grouping2 = grouping2 > 0 ? grouping2 : grouping1; + return {grouping1, grouping2, minGrouping, UNUM_GROUPING_COUNT}; +} + void Grouper::setLocaleData(const impl::ParsedPatternInfo &patternInfo, const Locale& locale) { if (fGrouping1 != -2 && fGrouping2 != -4) { return; @@ -86,4 +99,12 @@ bool Grouper::groupAtPosition(int32_t position, const impl::DecimalQuantity &val && value.getUpperDisplayMagnitude() - fGrouping1 + 1 >= fMinGrouping; } +int16_t Grouper::getPrimary() const { + return fGrouping1; +} + +int16_t Grouper::getSecondary() const { + return fGrouping2; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_integerwidth.cpp b/deps/icu-small/source/i18n/number_integerwidth.cpp index 4a612273f5e530..6416b292982e56 100644 --- a/deps/icu-small/source/i18n/number_integerwidth.cpp +++ b/deps/icu-small/source/i18n/number_integerwidth.cpp @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "unicode/numberformatter.h" #include "number_types.h" @@ -13,14 +13,15 @@ using namespace icu; using namespace icu::number; using namespace icu::number::impl; -IntegerWidth::IntegerWidth(digits_t minInt, digits_t maxInt) { +IntegerWidth::IntegerWidth(digits_t minInt, digits_t maxInt, bool formatFailIfMoreThanMaxDigits) { fUnion.minMaxInt.fMinInt = minInt; fUnion.minMaxInt.fMaxInt = maxInt; + fUnion.minMaxInt.fFormatFailIfMoreThanMaxDigits = formatFailIfMoreThanMaxDigits; } IntegerWidth IntegerWidth::zeroFillTo(int32_t minInt) { if (minInt >= 0 && minInt <= kMaxIntFracSig) { - return {static_cast(minInt), -1}; + return {static_cast(minInt), -1, false}; } else { return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; } @@ -30,22 +31,37 @@ IntegerWidth IntegerWidth::truncateAt(int32_t maxInt) { if (fHasError) { return *this; } // No-op on error digits_t minInt = fUnion.minMaxInt.fMinInt; if (maxInt >= 0 && maxInt <= kMaxIntFracSig && minInt <= maxInt) { - return {minInt, static_cast(maxInt)}; + return {minInt, static_cast(maxInt), false}; } else if (maxInt == -1) { - return {minInt, -1}; + return {minInt, -1, false}; } else { return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR}; } } -void IntegerWidth::apply(impl::DecimalQuantity &quantity, UErrorCode &status) const { +void IntegerWidth::apply(impl::DecimalQuantity& quantity, UErrorCode& status) const { if (fHasError) { status = U_ILLEGAL_ARGUMENT_ERROR; } else if (fUnion.minMaxInt.fMaxInt == -1) { quantity.setIntegerLength(fUnion.minMaxInt.fMinInt, INT32_MAX); } else { + // Enforce the backwards-compatibility feature "FormatFailIfMoreThanMaxDigits" + if (fUnion.minMaxInt.fFormatFailIfMoreThanMaxDigits && + fUnion.minMaxInt.fMaxInt < quantity.getMagnitude()) { + status = U_ILLEGAL_ARGUMENT_ERROR; + } quantity.setIntegerLength(fUnion.minMaxInt.fMinInt, fUnion.minMaxInt.fMaxInt); } } +bool IntegerWidth::operator==(const IntegerWidth& other) const { + // Private operator==; do error and bogus checking first! + U_ASSERT(!fHasError); + U_ASSERT(!other.fHasError); + U_ASSERT(!isBogus()); + U_ASSERT(!other.isBogus()); + return fUnion.minMaxInt.fMinInt == other.fUnion.minMaxInt.fMinInt && + fUnion.minMaxInt.fMaxInt == other.fUnion.minMaxInt.fMaxInt; +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_longnames.cpp b/deps/icu-small/source/i18n/number_longnames.cpp index 5c363442e7c033..26f9af4c9bdbfe 100644 --- a/deps/icu-small/source/i18n/number_longnames.cpp +++ b/deps/icu-small/source/i18n/number_longnames.cpp @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "unicode/simpleformatter.h" #include "unicode/ures.h" @@ -11,6 +11,7 @@ #include "charstr.h" #include "uresimp.h" #include "number_longnames.h" +#include "number_microprops.h" #include #include "cstring.h" @@ -260,8 +261,8 @@ void LongNameHandler::processQuantity(DecimalQuantity &quantity, MicroProps &mic parent->processQuantity(quantity, micros, status); // TODO: Avoid the copy here? DecimalQuantity copy(quantity); - micros.rounding.apply(copy, status); - micros.modOuter = &fModifiers[copy.getStandardPlural(rules)]; + micros.rounder.apply(copy, status); + micros.modOuter = &fModifiers[utils::getStandardPlural(rules, copy)]; } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_longnames.h b/deps/icu-small/source/i18n/number_longnames.h index 8738bb99e7d2e6..1d1e7dd3e864a6 100644 --- a/deps/icu-small/source/i18n/number_longnames.h +++ b/deps/icu-small/source/i18n/number_longnames.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_LONGNAMES_H__ #define __NUMBER_LONGNAMES_H__ diff --git a/deps/icu-small/source/i18n/number_mapper.cpp b/deps/icu-small/source/i18n/number_mapper.cpp new file mode 100644 index 00000000000000..d260632f93b0db --- /dev/null +++ b/deps/icu-small/source/i18n/number_mapper.cpp @@ -0,0 +1,502 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "number_mapper.h" +#include "number_patternstring.h" +#include "unicode/errorcode.h" +#include "number_utils.h" +#include "number_currencysymbols.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + + +UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, + UErrorCode& status) { + return NumberFormatter::with().macros(oldToNew(properties, symbols, warehouse, nullptr, status)); +} + +UnlocalizedNumberFormatter NumberPropertyMapper::create(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, + DecimalFormatProperties& exportedProperties, + UErrorCode& status) { + return NumberFormatter::with().macros( + oldToNew( + properties, symbols, warehouse, &exportedProperties, status)); +} + +MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, + DecimalFormatProperties* exportedProperties, + UErrorCode& status) { + MacroProps macros; + Locale locale = symbols.getLocale(); + + ///////////// + // SYMBOLS // + ///////////// + + macros.symbols.setTo(symbols); + + ////////////////// + // PLURAL RULES // + ////////////////// + + if (!properties.currencyPluralInfo.fPtr.isNull()) { + macros.rules = properties.currencyPluralInfo.fPtr->getPluralRules(); + } + + ///////////// + // AFFIXES // + ///////////// + + AffixPatternProvider* affixProvider; + if (properties.currencyPluralInfo.fPtr.isNull()) { + warehouse.currencyPluralInfoAPP.setToBogus(); + warehouse.propertiesAPP.setTo(properties, status); + affixProvider = &warehouse.propertiesAPP; + } else { + warehouse.currencyPluralInfoAPP.setTo(*properties.currencyPluralInfo.fPtr, properties, status); + warehouse.propertiesAPP.setToBogus(); + affixProvider = &warehouse.currencyPluralInfoAPP; + } + macros.affixProvider = affixProvider; + + /////////// + // UNITS // + /////////// + + bool useCurrency = ( + !properties.currency.isNull() || !properties.currencyPluralInfo.fPtr.isNull() || + !properties.currencyUsage.isNull() || affixProvider->hasCurrencySign()); + CurrencyUnit currency = resolveCurrency(properties, locale, status); + UCurrencyUsage currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD); + if (useCurrency) { + // NOTE: Slicing is OK. + macros.unit = currency; // NOLINT + } + warehouse.currencySymbols = {currency, locale, symbols, status}; + macros.currencySymbols = &warehouse.currencySymbols; + + /////////////////////// + // ROUNDING STRATEGY // + /////////////////////// + + int32_t maxInt = properties.maximumIntegerDigits; + int32_t minInt = properties.minimumIntegerDigits; + int32_t maxFrac = properties.maximumFractionDigits; + int32_t minFrac = properties.minimumFractionDigits; + int32_t minSig = properties.minimumSignificantDigits; + int32_t maxSig = properties.maximumSignificantDigits; + double roundingIncrement = properties.roundingIncrement; + RoundingMode roundingMode = properties.roundingMode.getOrDefault(UNUM_ROUND_HALFEVEN); + bool explicitMinMaxFrac = minFrac != -1 || maxFrac != -1; + bool explicitMinMaxSig = minSig != -1 || maxSig != -1; + // Resolve min/max frac for currencies, required for the validation logic and for when minFrac or + // maxFrac was + // set (but not both) on a currency instance. + // NOTE: Increments are handled in "Precision.constructCurrency()". + if (useCurrency && (minFrac == -1 || maxFrac == -1)) { + int32_t digits = ucurr_getDefaultFractionDigitsForUsage( + currency.getISOCurrency(), currencyUsage, &status); + if (minFrac == -1 && maxFrac == -1) { + minFrac = digits; + maxFrac = digits; + } else if (minFrac == -1) { + minFrac = std::min(maxFrac, digits); + } else /* if (maxFrac == -1) */ { + maxFrac = std::max(minFrac, digits); + } + } + // Validate min/max int/frac. + // For backwards compatibility, minimum overrides maximum if the two conflict. + // The following logic ensures that there is always a minimum of at least one digit. + if (minInt == 0 && maxFrac != 0) { + // Force a digit after the decimal point. + minFrac = minFrac <= 0 ? 1 : minFrac; + maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; + minInt = 0; + maxInt = maxInt < 0 ? -1 : maxInt > kMaxIntFracSig ? -1 : maxInt; + } else { + // Force a digit before the decimal point. + minFrac = minFrac < 0 ? 0 : minFrac; + maxFrac = maxFrac < 0 ? -1 : maxFrac < minFrac ? minFrac : maxFrac; + minInt = minInt <= 0 ? 1 : minInt > kMaxIntFracSig ? 1 : minInt; + maxInt = maxInt < 0 ? -1 : maxInt < minInt ? minInt : maxInt > kMaxIntFracSig ? -1 : maxInt; + } + Precision precision; + if (!properties.currencyUsage.isNull()) { + precision = Precision::constructCurrency(currencyUsage).withCurrency(currency); + } else if (roundingIncrement != 0.0) { + precision = Precision::constructIncrement(roundingIncrement, minFrac); + } else if (explicitMinMaxSig) { + minSig = minSig < 1 ? 1 : minSig > kMaxIntFracSig ? kMaxIntFracSig : minSig; + maxSig = maxSig < 0 ? kMaxIntFracSig : maxSig < minSig ? minSig : maxSig > kMaxIntFracSig + ? kMaxIntFracSig : maxSig; + precision = Precision::constructSignificant(minSig, maxSig); + } else if (explicitMinMaxFrac) { + precision = Precision::constructFraction(minFrac, maxFrac); + } else if (useCurrency) { + precision = Precision::constructCurrency(currencyUsage); + } + if (!precision.isBogus()) { + precision = precision.withMode(roundingMode); + macros.precision = precision; + } + + /////////////////// + // INTEGER WIDTH // + /////////////////// + + macros.integerWidth = IntegerWidth( + static_cast(minInt), + static_cast(maxInt), + properties.formatFailIfMoreThanMaxDigits); + + /////////////////////// + // GROUPING STRATEGY // + /////////////////////// + + macros.grouper = Grouper::forProperties(properties); + + ///////////// + // PADDING // + ///////////// + + if (properties.formatWidth != -1) { + macros.padder = Padder::forProperties(properties); + } + + /////////////////////////////// + // DECIMAL MARK ALWAYS SHOWN // + /////////////////////////////// + + macros.decimal = properties.decimalSeparatorAlwaysShown ? UNUM_DECIMAL_SEPARATOR_ALWAYS + : UNUM_DECIMAL_SEPARATOR_AUTO; + + /////////////////////// + // SIGN ALWAYS SHOWN // + /////////////////////// + + macros.sign = properties.signAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO; + + ///////////////////////// + // SCIENTIFIC NOTATION // + ///////////////////////// + + if (properties.minimumExponentDigits != -1) { + // Scientific notation is required. + // This whole section feels like a hack, but it is needed for regression tests. + // The mapping from property bag to scientific notation is nontrivial due to LDML rules. + if (maxInt > 8) { + // But #13110: The maximum of 8 digits has unknown origins and is not in the spec. + // If maxInt is greater than 8, it is set to minInt, even if minInt is greater than 8. + maxInt = minInt; + macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); + } else if (maxInt > minInt && minInt > 1) { + // Bug #13289: if maxInt > minInt > 1, then minInt should be 1. + minInt = 1; + macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); + } + int engineering = maxInt < 0 ? -1 : maxInt; + macros.notation = ScientificNotation( + // Engineering interval: + static_cast(engineering), + // Enforce minimum integer digits (for patterns like "000.00E0"): + (engineering == minInt), + // Minimum exponent digits: + static_cast(properties.minimumExponentDigits), + // Exponent sign always shown: + properties.exponentSignAlwaysShown ? UNUM_SIGN_ALWAYS : UNUM_SIGN_AUTO); + // Scientific notation also involves overriding the rounding mode. + // TODO: Overriding here is a bit of a hack. Should this logic go earlier? + if (macros.precision.fType == Precision::PrecisionType::RND_FRACTION) { + // For the purposes of rounding, get the original min/max int/frac, since the local + // variables + // have been manipulated for display purposes. + int minInt_ = properties.minimumIntegerDigits; + int minFrac_ = properties.minimumFractionDigits; + int maxFrac_ = properties.maximumFractionDigits; + if (minInt_ == 0 && maxFrac_ == 0) { + // Patterns like "#E0" and "##E0", which mean no rounding! + macros.precision = Precision::unlimited().withMode(roundingMode); + } else if (minInt_ == 0 && minFrac_ == 0) { + // Patterns like "#.##E0" (no zeros in the mantissa), which mean round to maxFrac+1 + macros.precision = Precision::constructSignificant(1, maxFrac_ + 1).withMode(roundingMode); + } else { + // All other scientific patterns, which mean round to minInt+maxFrac + macros.precision = Precision::constructSignificant( + minInt_ + minFrac_, minInt_ + maxFrac_).withMode(roundingMode); + } + } + } + + ////////////////////// + // COMPACT NOTATION // + ////////////////////// + + if (!properties.compactStyle.isNull()) { + if (properties.compactStyle.getNoError() == UNumberCompactStyle::UNUM_LONG) { + macros.notation = Notation::compactLong(); + } else { + macros.notation = Notation::compactShort(); + } + // Do not forward the affix provider. + macros.affixProvider = nullptr; + } + + ///////////////// + // MULTIPLIERS // + ///////////////// + + macros.scale = scaleFromProperties(properties); + + ////////////////////// + // PROPERTY EXPORTS // + ////////////////////// + + if (exportedProperties != nullptr) { + + exportedProperties->currency = currency; + exportedProperties->roundingMode = roundingMode; + exportedProperties->minimumIntegerDigits = minInt; + exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt; + + Precision rounding_; + if (precision.fType == Precision::PrecisionType::RND_CURRENCY) { + rounding_ = precision.withCurrency(currency, status); + } else { + rounding_ = precision; + } + int minFrac_ = minFrac; + int maxFrac_ = maxFrac; + int minSig_ = minSig; + int maxSig_ = maxSig; + double increment_ = 0.0; + if (rounding_.fType == Precision::PrecisionType::RND_FRACTION) { + minFrac_ = rounding_.fUnion.fracSig.fMinFrac; + maxFrac_ = rounding_.fUnion.fracSig.fMaxFrac; + } else if (rounding_.fType == Precision::PrecisionType::RND_INCREMENT) { + increment_ = rounding_.fUnion.increment.fIncrement; + minFrac_ = rounding_.fUnion.increment.fMinFrac; + maxFrac_ = rounding_.fUnion.increment.fMinFrac; + } else if (rounding_.fType == Precision::PrecisionType::RND_SIGNIFICANT) { + minSig_ = rounding_.fUnion.fracSig.fMinSig; + maxSig_ = rounding_.fUnion.fracSig.fMaxSig; + } + + exportedProperties->minimumFractionDigits = minFrac_; + exportedProperties->maximumFractionDigits = maxFrac_; + exportedProperties->minimumSignificantDigits = minSig_; + exportedProperties->maximumSignificantDigits = maxSig_; + exportedProperties->roundingIncrement = increment_; + } + + return macros; +} + + +void PropertiesAffixPatternProvider::setTo(const DecimalFormatProperties& properties, UErrorCode&) { + fBogus = false; + + // There are two ways to set affixes in DecimalFormat: via the pattern string (applyPattern), and via the + // explicit setters (setPositivePrefix and friends). The way to resolve the settings is as follows: + // + // 1) If the explicit setting is present for the field, use it. + // 2) Otherwise, follows UTS 35 rules based on the pattern string. + // + // Importantly, the explicit setters affect only the one field they override. If you set the positive + // prefix, that should not affect the negative prefix. Since it is impossible for the user of this class + // to know whether the origin for a string was the override or the pattern, we have to say that we always + // have a negative subpattern and perform all resolution logic here. + + // Convenience: Extract the properties into local variables. + // Variables are named with three chars: [p/n][p/s][o/p] + // [p/n] => p for positive, n for negative + // [p/s] => p for prefix, s for suffix + // [o/p] => o for escaped custom override string, p for pattern string + UnicodeString ppo = AffixUtils::escape(properties.positivePrefix); + UnicodeString pso = AffixUtils::escape(properties.positiveSuffix); + UnicodeString npo = AffixUtils::escape(properties.negativePrefix); + UnicodeString nso = AffixUtils::escape(properties.negativeSuffix); + const UnicodeString& ppp = properties.positivePrefixPattern; + const UnicodeString& psp = properties.positiveSuffixPattern; + const UnicodeString& npp = properties.negativePrefixPattern; + const UnicodeString& nsp = properties.negativeSuffixPattern; + + if (!properties.positivePrefix.isBogus()) { + posPrefix = ppo; + } else if (!ppp.isBogus()) { + posPrefix = ppp; + } else { + // UTS 35: Default positive prefix is empty string. + posPrefix = u""; + } + + if (!properties.positiveSuffix.isBogus()) { + posSuffix = pso; + } else if (!psp.isBogus()) { + posSuffix = psp; + } else { + // UTS 35: Default positive suffix is empty string. + posSuffix = u""; + } + + if (!properties.negativePrefix.isBogus()) { + negPrefix = npo; + } else if (!npp.isBogus()) { + negPrefix = npp; + } else { + // UTS 35: Default negative prefix is "-" with positive prefix. + // Important: We prepend the "-" to the pattern, not the override! + negPrefix = ppp.isBogus() ? u"-" : u"-" + ppp; + } + + if (!properties.negativeSuffix.isBogus()) { + negSuffix = nso; + } else if (!nsp.isBogus()) { + negSuffix = nsp; + } else { + // UTS 35: Default negative prefix is the positive prefix. + negSuffix = psp.isBogus() ? u"" : psp; + } +} + +char16_t PropertiesAffixPatternProvider::charAt(int flags, int i) const { + return getStringInternal(flags).charAt(i); +} + +int PropertiesAffixPatternProvider::length(int flags) const { + return getStringInternal(flags).length(); +} + +UnicodeString PropertiesAffixPatternProvider::getString(int32_t flags) const { + return getStringInternal(flags); +} + +const UnicodeString& PropertiesAffixPatternProvider::getStringInternal(int32_t flags) const { + bool prefix = (flags & AFFIX_PREFIX) != 0; + bool negative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0; + if (prefix && negative) { + return negPrefix; + } else if (prefix) { + return posPrefix; + } else if (negative) { + return negSuffix; + } else { + return posSuffix; + } +} + +bool PropertiesAffixPatternProvider::positiveHasPlusSign() const { + // TODO: Change the internal APIs to propagate out the error? + ErrorCode localStatus; + return AffixUtils::containsType(posPrefix, TYPE_PLUS_SIGN, localStatus) || + AffixUtils::containsType(posSuffix, TYPE_PLUS_SIGN, localStatus); +} + +bool PropertiesAffixPatternProvider::hasNegativeSubpattern() const { + // See comments in the constructor for more information on why this is always true. + return true; +} + +bool PropertiesAffixPatternProvider::negativeHasMinusSign() const { + ErrorCode localStatus; + return AffixUtils::containsType(negPrefix, TYPE_MINUS_SIGN, localStatus) || + AffixUtils::containsType(negSuffix, TYPE_MINUS_SIGN, localStatus); +} + +bool PropertiesAffixPatternProvider::hasCurrencySign() const { + ErrorCode localStatus; + return AffixUtils::hasCurrencySymbols(posPrefix, localStatus) || + AffixUtils::hasCurrencySymbols(posSuffix, localStatus) || + AffixUtils::hasCurrencySymbols(negPrefix, localStatus) || + AffixUtils::hasCurrencySymbols(negSuffix, localStatus); +} + +bool PropertiesAffixPatternProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const { + return AffixUtils::containsType(posPrefix, type, status) || + AffixUtils::containsType(posSuffix, type, status) || + AffixUtils::containsType(negPrefix, type, status) || + AffixUtils::containsType(negSuffix, type, status); +} + +bool PropertiesAffixPatternProvider::hasBody() const { + return true; +} + + +void CurrencyPluralInfoAffixProvider::setTo(const CurrencyPluralInfo& cpi, + const DecimalFormatProperties& properties, + UErrorCode& status) { + // We need to use a PropertiesAffixPatternProvider, not the simpler version ParsedPatternInfo, + // because user-specified affix overrides still need to work. + fBogus = false; + DecimalFormatProperties pluralProperties(properties); + for (int32_t plural = 0; plural < StandardPlural::COUNT; plural++) { + const char* keyword = StandardPlural::getKeyword(static_cast(plural)); + UnicodeString patternString; + patternString = cpi.getCurrencyPluralPattern(keyword, patternString); + PatternParser::parseToExistingProperties( + patternString, + pluralProperties, + IGNORE_ROUNDING_NEVER, + status); + affixesByPlural[plural].setTo(pluralProperties, status); + } +} + +char16_t CurrencyPluralInfoAffixProvider::charAt(int32_t flags, int32_t i) const { + int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); + return affixesByPlural[pluralOrdinal].charAt(flags, i); +} + +int32_t CurrencyPluralInfoAffixProvider::length(int32_t flags) const { + int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); + return affixesByPlural[pluralOrdinal].length(flags); +} + +UnicodeString CurrencyPluralInfoAffixProvider::getString(int32_t flags) const { + int32_t pluralOrdinal = (flags & AFFIX_PLURAL_MASK); + return affixesByPlural[pluralOrdinal].getString(flags); +} + +bool CurrencyPluralInfoAffixProvider::positiveHasPlusSign() const { + return affixesByPlural[StandardPlural::OTHER].positiveHasPlusSign(); +} + +bool CurrencyPluralInfoAffixProvider::hasNegativeSubpattern() const { + return affixesByPlural[StandardPlural::OTHER].hasNegativeSubpattern(); +} + +bool CurrencyPluralInfoAffixProvider::negativeHasMinusSign() const { + return affixesByPlural[StandardPlural::OTHER].negativeHasMinusSign(); +} + +bool CurrencyPluralInfoAffixProvider::hasCurrencySign() const { + return affixesByPlural[StandardPlural::OTHER].hasCurrencySign(); +} + +bool CurrencyPluralInfoAffixProvider::containsSymbolType(AffixPatternType type, UErrorCode& status) const { + return affixesByPlural[StandardPlural::OTHER].containsSymbolType(type, status); +} + +bool CurrencyPluralInfoAffixProvider::hasBody() const { + return affixesByPlural[StandardPlural::OTHER].hasBody(); +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_mapper.h b/deps/icu-small/source/i18n/number_mapper.h new file mode 100644 index 00000000000000..82c5711c8d0ce7 --- /dev/null +++ b/deps/icu-small/source/i18n/number_mapper.h @@ -0,0 +1,206 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMBER_MAPPER_H__ +#define __NUMBER_MAPPER_H__ + +#include +#include "number_types.h" +#include "unicode/currpinf.h" +#include "standardplural.h" +#include "number_patternstring.h" +#include "number_currencysymbols.h" +#include "numparse_impl.h" + +U_NAMESPACE_BEGIN +namespace number { +namespace impl { + + +class PropertiesAffixPatternProvider : public AffixPatternProvider, public UMemory { + public: + bool isBogus() const { + return fBogus; + } + + void setToBogus() { + fBogus = true; + } + + void setTo(const DecimalFormatProperties& properties, UErrorCode& status); + + PropertiesAffixPatternProvider() = default; // puts instance in valid but undefined state + + PropertiesAffixPatternProvider(const DecimalFormatProperties& properties, UErrorCode& status) { + setTo(properties, status); + } + + // AffixPatternProvider Methods: + + char16_t charAt(int32_t flags, int32_t i) const U_OVERRIDE; + + int32_t length(int32_t flags) const U_OVERRIDE; + + UnicodeString getString(int32_t flags) const U_OVERRIDE; + + bool hasCurrencySign() const U_OVERRIDE; + + bool positiveHasPlusSign() const U_OVERRIDE; + + bool hasNegativeSubpattern() const U_OVERRIDE; + + bool negativeHasMinusSign() const U_OVERRIDE; + + bool containsSymbolType(AffixPatternType, UErrorCode&) const U_OVERRIDE; + + bool hasBody() const U_OVERRIDE; + + private: + UnicodeString posPrefix; + UnicodeString posSuffix; + UnicodeString negPrefix; + UnicodeString negSuffix; + + const UnicodeString& getStringInternal(int32_t flags) const; + + bool fBogus{true}; +}; + + +class CurrencyPluralInfoAffixProvider : public AffixPatternProvider, public UMemory { + public: + bool isBogus() const { + return fBogus; + } + + void setToBogus() { + fBogus = true; + } + + void setTo(const CurrencyPluralInfo& cpi, const DecimalFormatProperties& properties, + UErrorCode& status); + + // AffixPatternProvider Methods: + + char16_t charAt(int32_t flags, int32_t i) const U_OVERRIDE; + + int32_t length(int32_t flags) const U_OVERRIDE; + + UnicodeString getString(int32_t flags) const U_OVERRIDE; + + bool hasCurrencySign() const U_OVERRIDE; + + bool positiveHasPlusSign() const U_OVERRIDE; + + bool hasNegativeSubpattern() const U_OVERRIDE; + + bool negativeHasMinusSign() const U_OVERRIDE; + + bool containsSymbolType(AffixPatternType, UErrorCode&) const U_OVERRIDE; + + bool hasBody() const U_OVERRIDE; + + private: + PropertiesAffixPatternProvider affixesByPlural[StandardPlural::COUNT]; + + bool fBogus{true}; +}; + + +/** + * A struct for ownership of a few objects needed for formatting. + */ +struct DecimalFormatWarehouse { + PropertiesAffixPatternProvider propertiesAPP; + CurrencyPluralInfoAffixProvider currencyPluralInfoAPP; + CurrencySymbols currencySymbols; +}; + + +/** +* Internal fields for DecimalFormat. +* TODO: Make some of these fields by value instead of by LocalPointer? +*/ +struct DecimalFormatFields : public UMemory { + /** The property bag corresponding to user-specified settings and settings from the pattern string. */ + LocalPointer properties; + + /** The symbols for the current locale. */ + LocalPointer symbols; + + /** + * The pre-computed formatter object. Setters cause this to be re-computed atomically. The {@link + * #format} method uses the formatter directly without needing to synchronize. + */ + LocalPointer formatter; + + /** The lazy-computed parser for .parse() */ + std::atomic<::icu::numparse::impl::NumberParserImpl*> atomicParser = {}; + + /** The lazy-computed parser for .parseCurrency() */ + std::atomic<::icu::numparse::impl::NumberParserImpl*> atomicCurrencyParser = {}; + + /** Small object ownership warehouse for the formatter and parser */ + DecimalFormatWarehouse warehouse; + + /** The effective properties as exported from the formatter object. Used by some getters. */ + LocalPointer exportedProperties; + + // Data for fastpath + bool canUseFastFormat = false; + struct FastFormatData { + char16_t cpZero; + char16_t cpGroupingSeparator; + char16_t cpMinusSign; + int8_t minInt; + int8_t maxInt; + } fastData; +}; + + +/** + * Utilities for converting between a DecimalFormatProperties and a MacroProps. + */ +class NumberPropertyMapper { + public: + /** Convenience method to create a NumberFormatter directly from Properties. */ + static UnlocalizedNumberFormatter create(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, UErrorCode& status); + + /** Convenience method to create a NumberFormatter directly from Properties. */ + static UnlocalizedNumberFormatter create(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, + DecimalFormatWarehouse& warehouse, + DecimalFormatProperties& exportedProperties, + UErrorCode& status); + + /** + * Creates a new {@link MacroProps} object based on the content of a {@link DecimalFormatProperties} + * object. In other words, maps Properties to MacroProps. This function is used by the + * JDK-compatibility API to call into the ICU 60 fluent number formatting pipeline. + * + * @param properties + * The property bag to be mapped. + * @param symbols + * The symbols associated with the property bag. + * @param exportedProperties + * A property bag in which to store validated properties. Used by some DecimalFormat + * getters. + * @return A new MacroProps containing all of the information in the Properties. + */ + static MacroProps oldToNew(const DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, DecimalFormatWarehouse& warehouse, + DecimalFormatProperties* exportedProperties, UErrorCode& status); +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMBER_MAPPER_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_microprops.h b/deps/icu-small/source/i18n/number_microprops.h new file mode 100644 index 00000000000000..daa887bb0dd861 --- /dev/null +++ b/deps/icu-small/source/i18n/number_microprops.h @@ -0,0 +1,82 @@ +// © 2017 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMBER_MICROPROPS_H__ +#define __NUMBER_MICROPROPS_H__ + +// TODO: minimize includes +#include "unicode/numberformatter.h" +#include "number_types.h" +#include "number_decimalquantity.h" +#include "number_scientific.h" +#include "number_patternstring.h" +#include "number_modifiers.h" +#include "number_multiplier.h" +#include "number_roundingutils.h" +#include "decNumber.h" +#include "charstr.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + +struct MicroProps : public MicroPropsGenerator { + + // NOTE: All of these fields are properly initialized in NumberFormatterImpl. + RoundingImpl rounder; + Grouper grouping; + Padder padding; + IntegerWidth integerWidth; + UNumberSignDisplay sign; + UNumberDecimalSeparatorDisplay decimal; + bool useCurrency; + + // Note: This struct has no direct ownership of the following pointers. + const DecimalFormatSymbols* symbols; + const Modifier* modOuter; + const Modifier* modMiddle; + const Modifier* modInner; + + // The following "helper" fields may optionally be used during the MicroPropsGenerator. + // They live here to retain memory. + struct { + ScientificModifier scientificModifier; + EmptyModifier emptyWeakModifier{false}; + EmptyModifier emptyStrongModifier{true}; + MultiplierFormatHandler multiplier; + } helpers; + + + MicroProps() = default; + + MicroProps(const MicroProps& other) = default; + + MicroProps& operator=(const MicroProps& other) = default; + + void processQuantity(DecimalQuantity&, MicroProps& micros, UErrorCode& status) const U_OVERRIDE { + (void) status; + if (this == µs) { + // Unsafe path: no need to perform a copy. + U_ASSERT(!exhausted); + micros.exhausted = true; + U_ASSERT(exhausted); + } else { + // Safe path: copy self into the output micros. + micros = *this; + } + } + + private: + // Internal fields: + bool exhausted = false; +}; + +} // namespace impl +} // namespace number +U_NAMESPACE_END + +#endif // __NUMBER_MICROPROPS_H__ + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_modifiers.cpp b/deps/icu-small/source/i18n/number_modifiers.cpp index 872b97010d74b7..4385499b54f603 100644 --- a/deps/icu-small/source/i18n/number_modifiers.cpp +++ b/deps/icu-small/source/i18n/number_modifiers.cpp @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "umutex.h" #include "ucln_cmn.h" @@ -32,6 +32,7 @@ UBool U_CALLCONV cleanupDefaultCurrencySpacing() { UNISET_DIGIT = nullptr; delete UNISET_NOTS; UNISET_NOTS = nullptr; + gDefaultCurrencySpacingInitOnce.reset(); return TRUE; } @@ -50,6 +51,9 @@ void U_CALLCONV initDefaultCurrencySpacing(UErrorCode &status) { } // namespace +Modifier::~Modifier() = default; + + int32_t ConstantAffixModifier::apply(NumberStringBuilder &output, int leftIndex, int rightIndex, UErrorCode &status) const { // Insert the suffix first since inserting the prefix will change the rightIndex diff --git a/deps/icu-small/source/i18n/number_modifiers.h b/deps/icu-small/source/i18n/number_modifiers.h index 4762a6f6d37a2d..a553100cd92a9b 100644 --- a/deps/icu-small/source/i18n/number_modifiers.h +++ b/deps/icu-small/source/i18n/number_modifiers.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_MODIFIERS_H__ #define __NUMBER_MODIFIERS_H__ diff --git a/deps/icu-small/source/i18n/number_multiplier.cpp b/deps/icu-small/source/i18n/number_multiplier.cpp new file mode 100644 index 00000000000000..a27142c9bd689b --- /dev/null +++ b/deps/icu-small/source/i18n/number_multiplier.cpp @@ -0,0 +1,156 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "number_decnum.h" +#include "number_types.h" +#include "number_multiplier.h" +#include "numparse_validators.h" +#include "number_utils.h" +#include "decNumber.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; +using namespace icu::numparse::impl; + + +Scale::Scale(int32_t magnitude, DecNum* arbitraryToAdopt) + : fMagnitude(magnitude), fArbitrary(arbitraryToAdopt), fError(U_ZERO_ERROR) { + if (fArbitrary != nullptr) { + // Attempt to convert the DecNum to a magnitude multiplier. + fArbitrary->normalize(); + if (fArbitrary->getRawDecNumber()->digits == 1 && fArbitrary->getRawDecNumber()->lsu[0] == 1 && + !fArbitrary->isNegative()) { + // Success! + fMagnitude += fArbitrary->getRawDecNumber()->exponent; + delete fArbitrary; + fArbitrary = nullptr; + } + } +} + +Scale::Scale(const Scale& other) + : fMagnitude(other.fMagnitude), fArbitrary(nullptr), fError(other.fError) { + if (other.fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + fArbitrary = new DecNum(*other.fArbitrary, localStatus); + } +} + +Scale& Scale::operator=(const Scale& other) { + fMagnitude = other.fMagnitude; + if (other.fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + fArbitrary = new DecNum(*other.fArbitrary, localStatus); + } else { + fArbitrary = nullptr; + } + fError = other.fError; + return *this; +} + +Scale::Scale(Scale&& src) U_NOEXCEPT + : fMagnitude(src.fMagnitude), fArbitrary(src.fArbitrary), fError(src.fError) { + // Take ownership away from src if necessary + src.fArbitrary = nullptr; +} + +Scale& Scale::operator=(Scale&& src) U_NOEXCEPT { + fMagnitude = src.fMagnitude; + fArbitrary = src.fArbitrary; + fError = src.fError; + // Take ownership away from src if necessary + src.fArbitrary = nullptr; + return *this; +} + +Scale::~Scale() { + delete fArbitrary; +} + + +Scale Scale::none() { + return {0, nullptr}; +} + +Scale Scale::powerOfTen(int32_t power) { + return {power, nullptr}; +} + +Scale Scale::byDecimal(StringPiece multiplicand) { + UErrorCode localError = U_ZERO_ERROR; + LocalPointer decnum(new DecNum(), localError); + if (U_FAILURE(localError)) { + return {localError}; + } + decnum->setTo(multiplicand, localError); + if (U_FAILURE(localError)) { + return {localError}; + } + return {0, decnum.orphan()}; +} + +Scale Scale::byDouble(double multiplicand) { + UErrorCode localError = U_ZERO_ERROR; + LocalPointer decnum(new DecNum(), localError); + if (U_FAILURE(localError)) { + return {localError}; + } + decnum->setTo(multiplicand, localError); + if (U_FAILURE(localError)) { + return {localError}; + } + return {0, decnum.orphan()}; +} + +Scale Scale::byDoubleAndPowerOfTen(double multiplicand, int32_t power) { + UErrorCode localError = U_ZERO_ERROR; + LocalPointer decnum(new DecNum(), localError); + if (U_FAILURE(localError)) { + return {localError}; + } + decnum->setTo(multiplicand, localError); + if (U_FAILURE(localError)) { + return {localError}; + } + return {power, decnum.orphan()}; +} + +void Scale::applyTo(impl::DecimalQuantity& quantity) const { + quantity.adjustMagnitude(fMagnitude); + if (fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + quantity.multiplyBy(*fArbitrary, localStatus); + } +} + +void Scale::applyReciprocalTo(impl::DecimalQuantity& quantity) const { + quantity.adjustMagnitude(-fMagnitude); + if (fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + quantity.divideBy(*fArbitrary, localStatus); + } +} + + +void +MultiplierFormatHandler::setAndChain(const Scale& multiplier, const MicroPropsGenerator* parent) { + this->multiplier = multiplier; + this->parent = parent; +} + +void MultiplierFormatHandler::processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const { + parent->processQuantity(quantity, micros, status); + multiplier.applyTo(quantity); +} + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_multiplier.h b/deps/icu-small/source/i18n/number_multiplier.h new file mode 100644 index 00000000000000..82c30c78426cdb --- /dev/null +++ b/deps/icu-small/source/i18n/number_multiplier.h @@ -0,0 +1,57 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __SOURCE_NUMBER_MULTIPLIER_H__ +#define __SOURCE_NUMBER_MULTIPLIER_H__ + +#include "numparse_types.h" +#include "number_decimfmtprops.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + + +/** + * Wraps a {@link Multiplier} for use in the number formatting pipeline. + */ +// Exported as U_I18N_API for tests +class U_I18N_API MultiplierFormatHandler : public MicroPropsGenerator, public UMemory { + public: + MultiplierFormatHandler() = default; // WARNING: Leaves object in an unusable state; call setAndChain() + + void setAndChain(const Scale& multiplier, const MicroPropsGenerator* parent); + + void processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const U_OVERRIDE; + + private: + Scale multiplier; + const MicroPropsGenerator *parent; +}; + + +/** Gets a Scale from a DecimalFormatProperties. In Java, defined in RoundingUtils.java */ +static inline Scale scaleFromProperties(const DecimalFormatProperties& properties) { + int32_t magnitudeMultiplier = properties.magnitudeMultiplier + properties.multiplierScale; + int32_t arbitraryMultiplier = properties.multiplier; + if (magnitudeMultiplier != 0 && arbitraryMultiplier != 1) { + return Scale::byDoubleAndPowerOfTen(arbitraryMultiplier, magnitudeMultiplier); + } else if (magnitudeMultiplier != 0) { + return Scale::powerOfTen(magnitudeMultiplier); + } else if (arbitraryMultiplier != 1) { + return Scale::byDouble(arbitraryMultiplier); + } else { + return Scale::none(); + } +} + + +} // namespace impl +} // namespace number +U_NAMESPACE_END + +#endif //__SOURCE_NUMBER_MULTIPLIER_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_notation.cpp b/deps/icu-small/source/i18n/number_notation.cpp index f4ad333354d0c7..b3cabb57a50ea7 100644 --- a/deps/icu-small/source/i18n/number_notation.cpp +++ b/deps/icu-small/source/i18n/number_notation.cpp @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "unicode/numberformatter.h" #include "number_types.h" @@ -36,6 +36,19 @@ ScientificNotation Notation::engineering() { return {NTN_SCIENTIFIC, union_}; } +ScientificNotation::ScientificNotation(int8_t fEngineeringInterval, bool fRequireMinInt, + impl::digits_t fMinExponentDigits, + UNumberSignDisplay fExponentSignDisplay) { + ScientificSettings settings; + settings.fEngineeringInterval = fEngineeringInterval; + settings.fRequireMinInt = fRequireMinInt; + settings.fMinExponentDigits = fMinExponentDigits; + settings.fExponentSignDisplay = fExponentSignDisplay; + NotationUnion union_; + union_.scientific = settings; + *this = {NTN_SCIENTIFIC, union_}; +} + Notation Notation::compactShort() { NotationUnion union_; union_.compactStyle = CompactStyle::UNUM_SHORT; diff --git a/deps/icu-small/source/i18n/number_padding.cpp b/deps/icu-small/source/i18n/number_padding.cpp index b1db3490cd4489..97e7b6014f9aac 100644 --- a/deps/icu-small/source/i18n/number_padding.cpp +++ b/deps/icu-small/source/i18n/number_padding.cpp @@ -3,11 +3,12 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "unicode/numberformatter.h" #include "number_types.h" #include "number_stringbuilder.h" +#include "number_decimfmtprops.h" using namespace icu; using namespace icu::number; @@ -28,6 +29,7 @@ addPaddingHelper(UChar32 paddingCp, int32_t requiredPadding, NumberStringBuilder } Padder::Padder(UChar32 cp, int32_t width, UNumberFormatPadPosition position) : fWidth(width) { + // TODO(13034): Consider making this a string instead of code point. fUnion.padding.fCp = cp; fUnion.padding.fPosition = position; } @@ -47,6 +49,16 @@ Padder Padder::codePoints(UChar32 cp, int32_t targetWidth, UNumberFormatPadPosit } } +Padder Padder::forProperties(const DecimalFormatProperties& properties) { + UChar32 padCp; + if (properties.padString.length() > 0) { + padCp = properties.padString.char32At(0); + } else { + padCp = kFallbackPaddingString[0]; + } + return {padCp, properties.formatWidth, properties.padPosition.getOrDefault(UNUM_PAD_BEFORE_PREFIX)}; +} + int32_t Padder::padAndApply(const Modifier &mod1, const Modifier &mod2, NumberStringBuilder &string, int32_t leftIndex, int32_t rightIndex, UErrorCode &status) const { diff --git a/deps/icu-small/source/i18n/number_patternmodifier.cpp b/deps/icu-small/source/i18n/number_patternmodifier.cpp index e182104c9116e7..6417e14378bea6 100644 --- a/deps/icu-small/source/i18n/number_patternmodifier.cpp +++ b/deps/icu-small/source/i18n/number_patternmodifier.cpp @@ -3,21 +3,27 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "cstring.h" #include "number_patternmodifier.h" #include "unicode/dcfmtsym.h" #include "unicode/ucurr.h" #include "unicode/unistr.h" +#include "number_microprops.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; -MutablePatternModifier::MutablePatternModifier(bool isStrong) : fStrong(isStrong) {} -void MutablePatternModifier::setPatternInfo(const AffixPatternProvider *patternInfo) { +AffixPatternProvider::~AffixPatternProvider() = default; + + +MutablePatternModifier::MutablePatternModifier(bool isStrong) + : fStrong(isStrong) {} + +void MutablePatternModifier::setPatternInfo(const AffixPatternProvider* patternInfo) { this->patternInfo = patternInfo; } @@ -26,14 +32,12 @@ void MutablePatternModifier::setPatternAttributes(UNumberSignDisplay signDisplay this->perMilleReplacesPercent = perMille; } -void -MutablePatternModifier::setSymbols(const DecimalFormatSymbols *symbols, const CurrencyUnit ¤cy, - const UNumberUnitWidth unitWidth, const PluralRules *rules) { +void MutablePatternModifier::setSymbols(const DecimalFormatSymbols* symbols, + const CurrencySymbols* currencySymbols, + const UNumberUnitWidth unitWidth, const PluralRules* rules) { U_ASSERT((rules != nullptr) == needsPlurals()); this->symbols = symbols; - uprv_memcpy(static_cast(this->currencyCode), - currency.getISOCurrency(), - sizeof(char16_t) * 4); + this->currencySymbols = currencySymbols; this->unitWidth = unitWidth; this->rules = rules; } @@ -49,12 +53,12 @@ bool MutablePatternModifier::needsPlurals() const { // Silently ignore any error codes. } -ImmutablePatternModifier *MutablePatternModifier::createImmutable(UErrorCode &status) { +ImmutablePatternModifier* MutablePatternModifier::createImmutable(UErrorCode& status) { return createImmutableAndChain(nullptr, status); } -ImmutablePatternModifier * -MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *parent, UErrorCode &status) { +ImmutablePatternModifier* +MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator* parent, UErrorCode& status) { // TODO: Move StandardPlural VALUES to standardplural.h static const StandardPlural::Form STANDARD_PLURAL_VALUES[] = { @@ -89,11 +93,11 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *paren } else { // Faster path when plural keyword is not needed. setNumberProperties(1, StandardPlural::Form::COUNT); - Modifier *positive = createConstantModifier(status); + Modifier* positive = createConstantModifier(status); setNumberProperties(0, StandardPlural::Form::COUNT); - Modifier *zero = createConstantModifier(status); + Modifier* zero = createConstantModifier(status); setNumberProperties(-1, StandardPlural::Form::COUNT); - Modifier *negative = createConstantModifier(status); + Modifier* negative = createConstantModifier(status); pm->adoptPositiveNegativeModifiers(positive, zero, negative); if (U_FAILURE(status)) { delete pm; @@ -103,77 +107,91 @@ MutablePatternModifier::createImmutableAndChain(const MicroPropsGenerator *paren } } -ConstantMultiFieldModifier *MutablePatternModifier::createConstantModifier(UErrorCode &status) { +ConstantMultiFieldModifier* MutablePatternModifier::createConstantModifier(UErrorCode& status) { NumberStringBuilder a; NumberStringBuilder b; insertPrefix(a, 0, status); insertSuffix(b, 0, status); if (patternInfo->hasCurrencySign()) { - return new CurrencySpacingEnabledModifier(a, b, !patternInfo->hasBody(), fStrong, *symbols, status); + return new CurrencySpacingEnabledModifier( + a, b, !patternInfo->hasBody(), fStrong, *symbols, status); } else { return new ConstantMultiFieldModifier(a, b, !patternInfo->hasBody(), fStrong); } } -ImmutablePatternModifier::ImmutablePatternModifier(ParameterizedModifier *pm, const PluralRules *rules, - const MicroPropsGenerator *parent) +ImmutablePatternModifier::ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules, + const MicroPropsGenerator* parent) : pm(pm), rules(rules), parent(parent) {} -void ImmutablePatternModifier::processQuantity(DecimalQuantity &quantity, MicroProps µs, - UErrorCode &status) const { +void ImmutablePatternModifier::processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const { parent->processQuantity(quantity, micros, status); applyToMicros(micros, quantity); } -void ImmutablePatternModifier::applyToMicros(MicroProps µs, DecimalQuantity &quantity) const { +void ImmutablePatternModifier::applyToMicros(MicroProps& micros, DecimalQuantity& quantity) const { if (rules == nullptr) { micros.modMiddle = pm->getModifier(quantity.signum()); } else { // TODO: Fix this. Avoid the copy. DecimalQuantity copy(quantity); copy.roundToInfinity(); - StandardPlural::Form plural = copy.getStandardPlural(rules); + StandardPlural::Form plural = utils::getStandardPlural(rules, copy); micros.modMiddle = pm->getModifier(quantity.signum(), plural); } } +const Modifier* ImmutablePatternModifier::getModifier(int8_t signum, StandardPlural::Form plural) const { + if (rules == nullptr) { + return pm->getModifier(signum); + } else { + return pm->getModifier(signum, plural); + } +} + + /** Used by the unsafe code path. */ -MicroPropsGenerator &MutablePatternModifier::addToChain(const MicroPropsGenerator *parent) { +MicroPropsGenerator& MutablePatternModifier::addToChain(const MicroPropsGenerator* parent) { this->parent = parent; return *this; } -void MutablePatternModifier::processQuantity(DecimalQuantity &fq, MicroProps µs, - UErrorCode &status) const { +void MutablePatternModifier::processQuantity(DecimalQuantity& fq, MicroProps& micros, + UErrorCode& status) const { parent->processQuantity(fq, micros, status); // The unsafe code path performs self-mutation, so we need a const_cast. // This method needs to be const because it overrides a const method in the parent class. - auto nonConstThis = const_cast(this); + auto nonConstThis = const_cast(this); if (needsPlurals()) { // TODO: Fix this. Avoid the copy. DecimalQuantity copy(fq); - micros.rounding.apply(copy, status); - nonConstThis->setNumberProperties(fq.signum(), copy.getStandardPlural(rules)); + micros.rounder.apply(copy, status); + nonConstThis->setNumberProperties(fq.signum(), utils::getStandardPlural(rules, copy)); } else { nonConstThis->setNumberProperties(fq.signum(), StandardPlural::Form::COUNT); } micros.modMiddle = this; } -int32_t MutablePatternModifier::apply(NumberStringBuilder &output, int32_t leftIndex, int32_t rightIndex, - UErrorCode &status) const { +int32_t MutablePatternModifier::apply(NumberStringBuilder& output, int32_t leftIndex, int32_t rightIndex, + UErrorCode& status) const { // The unsafe code path performs self-mutation, so we need a const_cast. // This method needs to be const because it overrides a const method in the parent class. - auto nonConstThis = const_cast(this); + auto nonConstThis = const_cast(this); int32_t prefixLen = nonConstThis->insertPrefix(output, leftIndex, status); int32_t suffixLen = nonConstThis->insertSuffix(output, rightIndex + prefixLen, status); // If the pattern had no decimal stem body (like #,##0.00), overwrite the value. int32_t overwriteLen = 0; if (!patternInfo->hasBody()) { overwriteLen = output.splice( - leftIndex + prefixLen, rightIndex + prefixLen, - UnicodeString(), 0, 0, UNUM_FIELD_COUNT, - status); + leftIndex + prefixLen, + rightIndex + prefixLen, + UnicodeString(), + 0, + 0, + UNUM_FIELD_COUNT, + status); } CurrencySpacingEnabledModifier::applyCurrencySpacing( output, @@ -186,30 +204,27 @@ int32_t MutablePatternModifier::apply(NumberStringBuilder &output, int32_t leftI return prefixLen + overwriteLen + suffixLen; } -int32_t MutablePatternModifier::getPrefixLength(UErrorCode &status) const { +int32_t MutablePatternModifier::getPrefixLength(UErrorCode& status) const { // The unsafe code path performs self-mutation, so we need a const_cast. // This method needs to be const because it overrides a const method in the parent class. - auto nonConstThis = const_cast(this); + auto nonConstThis = const_cast(this); // Enter and exit CharSequence Mode to get the length. - nonConstThis->enterCharSequenceMode(true); - int result = AffixUtils::unescapedCodePointCount(*this, *this, status); // prefix length - nonConstThis->exitCharSequenceMode(); + nonConstThis->prepareAffix(true); + int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status); // prefix length return result; } -int32_t MutablePatternModifier::getCodePointCount(UErrorCode &status) const { +int32_t MutablePatternModifier::getCodePointCount(UErrorCode& status) const { // The unsafe code path performs self-mutation, so we need a const_cast. // This method needs to be const because it overrides a const method in the parent class. - auto nonConstThis = const_cast(this); + auto nonConstThis = const_cast(this); - // Enter and exit CharSequence Mode to get the length. - nonConstThis->enterCharSequenceMode(true); - int result = AffixUtils::unescapedCodePointCount(*this, *this, status); // prefix length - nonConstThis->exitCharSequenceMode(); - nonConstThis->enterCharSequenceMode(false); - result += AffixUtils::unescapedCodePointCount(*this, *this, status); // suffix length - nonConstThis->exitCharSequenceMode(); + // Render the affixes to get the length + nonConstThis->prepareAffix(true); + int result = AffixUtils::unescapedCodePointCount(currentAffix, *this, status); // prefix length + nonConstThis->prepareAffix(false); + result += AffixUtils::unescapedCodePointCount(currentAffix, *this, status); // suffix length return result; } @@ -217,21 +232,26 @@ bool MutablePatternModifier::isStrong() const { return fStrong; } -int32_t MutablePatternModifier::insertPrefix(NumberStringBuilder &sb, int position, UErrorCode &status) { - enterCharSequenceMode(true); - int length = AffixUtils::unescape(*this, sb, position, *this, status); - exitCharSequenceMode(); +int32_t MutablePatternModifier::insertPrefix(NumberStringBuilder& sb, int position, UErrorCode& status) { + prepareAffix(true); + int length = AffixUtils::unescape(currentAffix, sb, position, *this, status); return length; } -int32_t MutablePatternModifier::insertSuffix(NumberStringBuilder &sb, int position, UErrorCode &status) { - enterCharSequenceMode(false); - int length = AffixUtils::unescape(*this, sb, position, *this, status); - exitCharSequenceMode(); +int32_t MutablePatternModifier::insertSuffix(NumberStringBuilder& sb, int position, UErrorCode& status) { + prepareAffix(false); + int length = AffixUtils::unescape(currentAffix, sb, position, *this, status); return length; } +/** This method contains the heart of the logic for rendering LDML affix strings. */ +void MutablePatternModifier::prepareAffix(bool isPrefix) { + PatternStringUtils::patternInfoToStringBuilder( + *patternInfo, isPrefix, signum, signDisplay, plural, perMilleReplacesPercent, currentAffix); +} + UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { + UErrorCode localStatus = U_ZERO_ERROR; switch (type) { case AffixPatternType::TYPE_MINUS_SIGN: return symbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kMinusSignSymbol); @@ -244,45 +264,23 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { case AffixPatternType::TYPE_CURRENCY_SINGLE: { // UnitWidth ISO and HIDDEN overrides the singular currency symbol. if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE) { - return UnicodeString(currencyCode, 3); + return currencySymbols->getIntlCurrencySymbol(localStatus); } else if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_HIDDEN) { return UnicodeString(); + } else if (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW) { + return currencySymbols->getNarrowCurrencySymbol(localStatus); } else { - UCurrNameStyle selector = (unitWidth == UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW) - ? UCurrNameStyle::UCURR_NARROW_SYMBOL_NAME - : UCurrNameStyle::UCURR_SYMBOL_NAME; - UErrorCode status = U_ZERO_ERROR; - UBool isChoiceFormat = FALSE; - int32_t symbolLen = 0; - const char16_t *symbol = ucurr_getName( - currencyCode, - symbols->getLocale().getName(), - selector, - &isChoiceFormat, - &symbolLen, - &status); - return UnicodeString(symbol, symbolLen); + return currencySymbols->getCurrencySymbol(localStatus); } } case AffixPatternType::TYPE_CURRENCY_DOUBLE: - return UnicodeString(currencyCode, 3); - case AffixPatternType::TYPE_CURRENCY_TRIPLE: { + return currencySymbols->getIntlCurrencySymbol(localStatus); + case AffixPatternType::TYPE_CURRENCY_TRIPLE: // NOTE: This is the code path only for patterns containing "¤¤¤". // Plural currencies set via the API are formatted in LongNameHandler. // This code path is used by DecimalFormat via CurrencyPluralInfo. U_ASSERT(plural != StandardPlural::Form::COUNT); - UErrorCode status = U_ZERO_ERROR; - UBool isChoiceFormat = FALSE; - int32_t symbolLen = 0; - const char16_t *symbol = ucurr_getPluralName( - currencyCode, - symbols->getLocale().getName(), - &isChoiceFormat, - StandardPlural::getKeyword(plural), - &symbolLen, - &status); - return UnicodeString(symbol, symbolLen); - } + return currencySymbols->getPluralName(plural, localStatus); case AffixPatternType::TYPE_CURRENCY_QUAD: return UnicodeString(u"\uFFFD"); case AffixPatternType::TYPE_CURRENCY_QUINT: @@ -293,79 +291,6 @@ UnicodeString MutablePatternModifier::getSymbol(AffixPatternType type) const { } } -/** This method contains the heart of the logic for rendering LDML affix strings. */ -void MutablePatternModifier::enterCharSequenceMode(bool isPrefix) { - U_ASSERT(!inCharSequenceMode); - inCharSequenceMode = true; - - // Should the output render '+' where '-' would normally appear in the pattern? - plusReplacesMinusSign = signum != -1 - && (signDisplay == UNUM_SIGN_ALWAYS - || signDisplay == UNUM_SIGN_ACCOUNTING_ALWAYS - || (signum == 1 - && (signDisplay == UNUM_SIGN_EXCEPT_ZERO - || signDisplay == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO))) - && patternInfo->positiveHasPlusSign() == false; - - // Should we use the affix from the negative subpattern? (If not, we will use the positive subpattern.) - bool useNegativeAffixPattern = patternInfo->hasNegativeSubpattern() && ( - signum == -1 || (patternInfo->negativeHasMinusSign() && plusReplacesMinusSign)); - - // Resolve the flags for the affix pattern. - fFlags = 0; - if (useNegativeAffixPattern) { - fFlags |= AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN; - } - if (isPrefix) { - fFlags |= AffixPatternProvider::AFFIX_PREFIX; - } - if (plural != StandardPlural::Form::COUNT) { - U_ASSERT(plural == (AffixPatternProvider::AFFIX_PLURAL_MASK & plural)); - fFlags |= plural; - } - - // Should we prepend a sign to the pattern? - if (!isPrefix || useNegativeAffixPattern) { - prependSign = false; - } else if (signum == -1) { - prependSign = signDisplay != UNUM_SIGN_NEVER; - } else { - prependSign = plusReplacesMinusSign; - } - - // Finally, compute the length of the affix pattern. - fLength = patternInfo->length(fFlags) + (prependSign ? 1 : 0); -} - -void MutablePatternModifier::exitCharSequenceMode() { - U_ASSERT(inCharSequenceMode); - inCharSequenceMode = false; -} - -int32_t MutablePatternModifier::length() const { - U_ASSERT(inCharSequenceMode); - return fLength; -} - -char16_t MutablePatternModifier::charAt(int32_t index) const { - U_ASSERT(inCharSequenceMode); - char16_t candidate; - if (prependSign && index == 0) { - candidate = u'-'; - } else if (prependSign) { - candidate = patternInfo->charAt(fFlags, index - 1); - } else { - candidate = patternInfo->charAt(fFlags, index); - } - if (plusReplacesMinusSign && candidate == u'-') { - return u'+'; - } - if (perMilleReplacesPercent && candidate == u'%') { - return u'‰'; - } - return candidate; -} - UnicodeString MutablePatternModifier::toUnicodeString() const { // Never called by AffixUtils U_ASSERT(false); diff --git a/deps/icu-small/source/i18n/number_patternmodifier.h b/deps/icu-small/source/i18n/number_patternmodifier.h index 9c8b95f7764436..f1359bd5747d89 100644 --- a/deps/icu-small/source/i18n/number_patternmodifier.h +++ b/deps/icu-small/source/i18n/number_patternmodifier.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_PATTERNMODIFIER_H__ #define __NUMBER_PATTERNMODIFIER_H__ @@ -13,6 +13,7 @@ #include "number_types.h" #include "number_modifiers.h" #include "number_utils.h" +#include "number_currencysymbols.h" U_NAMESPACE_BEGIN @@ -35,20 +36,23 @@ class MutablePatternModifier; // Exported as U_I18N_API because it is needed for the unit test PatternModifierTest class U_I18N_API ImmutablePatternModifier : public MicroPropsGenerator, public UMemory { public: - ~ImmutablePatternModifier() U_OVERRIDE = default; + ~ImmutablePatternModifier() U_OVERRIDE = default; - void processQuantity(DecimalQuantity &, MicroProps µs, UErrorCode &status) const U_OVERRIDE; + void processQuantity(DecimalQuantity&, MicroProps& micros, UErrorCode& status) const U_OVERRIDE; + + void applyToMicros(MicroProps& micros, DecimalQuantity& quantity) const; - void applyToMicros(MicroProps µs, DecimalQuantity &quantity) const; + const Modifier* getModifier(int8_t signum, StandardPlural::Form plural) const; private: - ImmutablePatternModifier(ParameterizedModifier *pm, const PluralRules *rules, const MicroPropsGenerator *parent); + ImmutablePatternModifier(ParameterizedModifier* pm, const PluralRules* rules, + const MicroPropsGenerator* parent); const LocalPointer pm; - const PluralRules *rules; - const MicroPropsGenerator *parent; + const PluralRules* rules; + const MicroPropsGenerator* parent; - friend class MutablePatternModifier; + friend class MutablePatternModifier; }; /** @@ -74,7 +78,6 @@ class U_I18N_API MutablePatternModifier : public MicroPropsGenerator, public Modifier, public SymbolProvider, - public CharSequence, public UMemory { public: @@ -110,17 +113,16 @@ class U_I18N_API MutablePatternModifier * * @param symbols * The desired instance of DecimalFormatSymbols. - * @param currency - * The currency to be used when substituting currency values into the affixes. + * @param currencySymbols + * The currency symbols to be used when substituting currency values into the affixes. * @param unitWidth * The width used to render currencies. * @param rules * Required if the triple currency sign, "¤¤¤", appears in the pattern, which can be determined from the * convenience method {@link #needsPlurals()}. */ - void - setSymbols(const DecimalFormatSymbols *symbols, const CurrencyUnit ¤cy, UNumberUnitWidth unitWidth, - const PluralRules *rules); + void setSymbols(const DecimalFormatSymbols* symbols, const CurrencySymbols* currencySymbols, + UNumberUnitWidth unitWidth, const PluralRules* rules); /** * Sets attributes of the current number being processed. @@ -187,13 +189,7 @@ class U_I18N_API MutablePatternModifier */ UnicodeString getSymbol(AffixPatternType type) const U_OVERRIDE; - int32_t length() const U_OVERRIDE; - - char16_t charAt(int32_t index) const U_OVERRIDE; - - // Use default implementation of codePointAt - - UnicodeString toUnicodeString() const U_OVERRIDE; + UnicodeString toUnicodeString() const; private: // Modifier details (initialized in constructor) @@ -207,7 +203,7 @@ class U_I18N_API MutablePatternModifier // Symbol details (initialized in setSymbols) const DecimalFormatSymbols *symbols; UNumberUnitWidth unitWidth; - char16_t currencyCode[4]; + const CurrencySymbols *currencySymbols; const PluralRules *rules; // Number details (initialized in setNumberProperties) @@ -217,12 +213,8 @@ class U_I18N_API MutablePatternModifier // QuantityChain details (initialized in addToChain) const MicroPropsGenerator *parent; - // Transient CharSequence fields (initialized in enterCharSequenceMode) - bool inCharSequenceMode = false; - int32_t fFlags; - int32_t fLength; - bool prependSign; - bool plusReplacesMinusSign; + // Transient fields for rendering + UnicodeString currentAffix; /** * Uses the current properties to create a single {@link ConstantMultiFieldModifier} with currency spacing support @@ -244,9 +236,7 @@ class U_I18N_API MutablePatternModifier int32_t insertSuffix(NumberStringBuilder &sb, int position, UErrorCode &status); - void enterCharSequenceMode(bool isPrefix); - - void exitCharSequenceMode(); + void prepareAffix(bool isPrefix); }; diff --git a/deps/icu-small/source/i18n/number_patternstring.cpp b/deps/icu-small/source/i18n/number_patternstring.cpp index 20178824b0e20a..63195eed989e54 100644 --- a/deps/icu-small/source/i18n/number_patternstring.cpp +++ b/deps/icu-small/source/i18n/number_patternstring.cpp @@ -3,36 +3,51 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT +#define UNISTR_FROM_CHAR_EXPLICIT #include "uassert.h" #include "number_patternstring.h" #include "unicode/utf16.h" #include "number_utils.h" +#include "number_roundingutils.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; -void PatternParser::parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, UErrorCode &status) { + +void PatternParser::parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, + UErrorCode& status) { patternInfo.consumePattern(patternString, status); } DecimalFormatProperties PatternParser::parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding, - UErrorCode &status) { + UErrorCode& status) { DecimalFormatProperties properties; parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status); return properties; } -void PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties, - IgnoreRounding ignoreRounding, UErrorCode &status) { +DecimalFormatProperties PatternParser::parseToProperties(const UnicodeString& pattern, + UErrorCode& status) { + return parseToProperties(pattern, IGNORE_ROUNDING_NEVER, status); +} + +void +PatternParser::parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties, + IgnoreRounding ignoreRounding, UErrorCode& status) { parseToExistingPropertiesImpl(pattern, properties, ignoreRounding, status); } + char16_t ParsedPatternInfo::charAt(int32_t flags, int32_t index) const { - const Endpoints &endpoints = getEndpoints(flags); + const Endpoints& endpoints = getEndpoints(flags); if (index < 0 || index >= endpoints.end - endpoints.start) { U_ASSERT(false); } @@ -43,12 +58,12 @@ int32_t ParsedPatternInfo::length(int32_t flags) const { return getLengthFromEndpoints(getEndpoints(flags)); } -int32_t ParsedPatternInfo::getLengthFromEndpoints(const Endpoints &endpoints) { +int32_t ParsedPatternInfo::getLengthFromEndpoints(const Endpoints& endpoints) { return endpoints.end - endpoints.start; } UnicodeString ParsedPatternInfo::getString(int32_t flags) const { - const Endpoints &endpoints = getEndpoints(flags); + const Endpoints& endpoints = getEndpoints(flags); if (endpoints.start == endpoints.end) { return UnicodeString(); } @@ -56,7 +71,7 @@ UnicodeString ParsedPatternInfo::getString(int32_t flags) const { return UnicodeString(pattern, endpoints.start, endpoints.end - endpoints.start); } -const Endpoints &ParsedPatternInfo::getEndpoints(int32_t flags) const { +const Endpoints& ParsedPatternInfo::getEndpoints(int32_t flags) const { bool prefix = (flags & AFFIX_PREFIX) != 0; bool isNegative = (flags & AFFIX_NEGATIVE_SUBPATTERN) != 0; bool padding = (flags & AFFIX_PADDING) != 0; @@ -91,8 +106,8 @@ bool ParsedPatternInfo::hasCurrencySign() const { return positive.hasCurrencySign || (fHasNegativeSubpattern && negative.hasCurrencySign); } -bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode &status) const { - return AffixUtils::containsType(UnicodeStringCharSequence(pattern), type, status); +bool ParsedPatternInfo::containsSymbolType(AffixPatternType type, UErrorCode& status) const { + return AffixUtils::containsType(pattern, type, status); } bool ParsedPatternInfo::hasBody() const { @@ -117,10 +132,14 @@ UChar32 ParsedPatternInfo::ParserState::next() { return codePoint; } -void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErrorCode &status) { +void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErrorCode& status) { if (U_FAILURE(status)) { return; } this->pattern = patternString; + // This class is not intended for writing twice! + // Use move assignment to overwrite instead. + U_ASSERT(state.offset == 0); + // pattern := subpattern (';' subpattern)? currentSubpattern = &positive; consumeSubpattern(status); @@ -141,7 +160,7 @@ void ParsedPatternInfo::consumePattern(const UnicodeString& patternString, UErro } } -void ParsedPatternInfo::consumeSubpattern(UErrorCode &status) { +void ParsedPatternInfo::consumeSubpattern(UErrorCode& status) { // subpattern := literals? number exponent? literals? consumePadding(PadPosition::UNUM_PAD_BEFORE_PREFIX, status); if (U_FAILURE(status)) { return; } @@ -161,23 +180,24 @@ void ParsedPatternInfo::consumeSubpattern(UErrorCode &status) { if (U_FAILURE(status)) { return; } } -void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode &status) { +void ParsedPatternInfo::consumePadding(PadPosition paddingLocation, UErrorCode& status) { if (state.peek() != u'*') { return; } - if (!currentSubpattern->paddingLocation.isNull()) { + if (currentSubpattern->hasPadding) { state.toParseException(u"Cannot have multiple pad specifiers"); status = U_MULTIPLE_PAD_SPECIFIERS; return; } currentSubpattern->paddingLocation = paddingLocation; + currentSubpattern->hasPadding = true; state.next(); // consume the '*' currentSubpattern->paddingEndpoints.start = state.offset; consumeLiteral(status); currentSubpattern->paddingEndpoints.end = state.offset; } -void ParsedPatternInfo::consumeAffix(Endpoints &endpoints, UErrorCode &status) { +void ParsedPatternInfo::consumeAffix(Endpoints& endpoints, UErrorCode& status) { // literals := { literal } endpoints.start = state.offset; while (true) { @@ -233,7 +253,7 @@ void ParsedPatternInfo::consumeAffix(Endpoints &endpoints, UErrorCode &status) { endpoints.end = state.offset; } -void ParsedPatternInfo::consumeLiteral(UErrorCode &status) { +void ParsedPatternInfo::consumeLiteral(UErrorCode& status) { if (state.peek() == -1) { state.toParseException(u"Expected unquoted literal but found EOL"); status = U_PATTERN_SYNTAX_ERROR; @@ -256,7 +276,7 @@ void ParsedPatternInfo::consumeLiteral(UErrorCode &status) { } } -void ParsedPatternInfo::consumeFormat(UErrorCode &status) { +void ParsedPatternInfo::consumeFormat(UErrorCode& status) { consumeIntegerFormat(status); if (U_FAILURE(status)) { return; } if (state.peek() == u'.') { @@ -268,9 +288,9 @@ void ParsedPatternInfo::consumeFormat(UErrorCode &status) { } } -void ParsedPatternInfo::consumeIntegerFormat(UErrorCode &status) { +void ParsedPatternInfo::consumeIntegerFormat(UErrorCode& status) { // Convenience reference: - ParsedSubpatternInfo &result = *currentSubpattern; + ParsedSubpatternInfo& result = *currentSubpattern; while (true) { switch (state.peek()) { @@ -359,9 +379,9 @@ void ParsedPatternInfo::consumeIntegerFormat(UErrorCode &status) { } } -void ParsedPatternInfo::consumeFractionFormat(UErrorCode &status) { +void ParsedPatternInfo::consumeFractionFormat(UErrorCode& status) { // Convenience reference: - ParsedSubpatternInfo &result = *currentSubpattern; + ParsedSubpatternInfo& result = *currentSubpattern; int32_t zeroCounter = 0; while (true) { @@ -407,9 +427,9 @@ void ParsedPatternInfo::consumeFractionFormat(UErrorCode &status) { } } -void ParsedPatternInfo::consumeExponent(UErrorCode &status) { +void ParsedPatternInfo::consumeExponent(UErrorCode& status) { // Convenience reference: - ParsedSubpatternInfo &result = *currentSubpattern; + ParsedSubpatternInfo& result = *currentSubpattern; if (state.peek() != u'E') { return; @@ -437,9 +457,9 @@ void ParsedPatternInfo::consumeExponent(UErrorCode &status) { /// END RECURSIVE DESCENT PARSER IMPLEMENTATION /// /////////////////////////////////////////////////// -void -PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern, DecimalFormatProperties &properties, - IgnoreRounding ignoreRounding, UErrorCode &status) { +void PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern, + DecimalFormatProperties& properties, + IgnoreRounding ignoreRounding, UErrorCode& status) { if (pattern.length() == 0) { // Backwards compatibility requires that we reset to the default values. // TODO: Only overwrite the properties that "saveToProperties" normally touches? @@ -453,13 +473,13 @@ PatternParser::parseToExistingPropertiesImpl(const UnicodeString& pattern, Decim patternInfoToProperties(properties, patternInfo, ignoreRounding, status); } -void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, - ParsedPatternInfo& patternInfo, - IgnoreRounding _ignoreRounding, UErrorCode &status) { +void +PatternParser::patternInfoToProperties(DecimalFormatProperties& properties, ParsedPatternInfo& patternInfo, + IgnoreRounding _ignoreRounding, UErrorCode& status) { // Translate from PatternParseResult to Properties. // Note that most data from "negative" is ignored per the specification of DecimalFormat. - const ParsedSubpatternInfo &positive = patternInfo.positive; + const ParsedSubpatternInfo& positive = patternInfo.positive; bool ignoreRounding; if (_ignoreRounding == IGNORE_ROUNDING_NEVER) { @@ -477,8 +497,10 @@ void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, auto grouping3 = static_cast ((positive.groupingSizes >> 32) & 0xffff); if (grouping2 != -1) { properties.groupingSize = grouping1; + properties.groupingUsed = true; } else { properties.groupingSize = -1; + properties.groupingUsed = false; } if (grouping3 != -1) { properties.secondaryGroupingSize = grouping2; @@ -508,8 +530,7 @@ void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, properties.maximumFractionDigits = -1; properties.roundingIncrement = 0.0; properties.minimumSignificantDigits = positive.integerAtSigns; - properties.maximumSignificantDigits = - positive.integerAtSigns + positive.integerTrailingHashSigns; + properties.maximumSignificantDigits = positive.integerAtSigns + positive.integerTrailingHashSigns; } else if (!positive.rounding.isZero()) { if (!ignoreRounding) { properties.minimumFractionDigits = minFrac; @@ -568,11 +589,11 @@ void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, UnicodeString posSuffix = patternInfo.getString(0); // Padding settings - if (!positive.paddingLocation.isNull()) { + if (positive.hasPadding) { // The width of the positive prefix and suffix templates are included in the padding - int paddingWidth = - positive.widthExceptAffixes + AffixUtils::estimateLength(UnicodeStringCharSequence(posPrefix), status) + - AffixUtils::estimateLength(UnicodeStringCharSequence(posSuffix), status); + int paddingWidth = positive.widthExceptAffixes + + AffixUtils::estimateLength(posPrefix, status) + + AffixUtils::estimateLength(posSuffix, status); properties.formatWidth = paddingWidth; UnicodeString rawPaddingString = patternInfo.getString(AffixPatternProvider::AFFIX_PADDING); if (rawPaddingString.length() == 1) { @@ -622,8 +643,8 @@ void PatternParser::patternInfoToProperties(DecimalFormatProperties &properties, /// End PatternStringParser.java; begin PatternStringUtils.java /// /////////////////////////////////////////////////////////////////// -UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatProperties &properties, - UErrorCode &status) { +UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatProperties& properties, + UErrorCode& status) { UnicodeString sb; // Convenience references @@ -656,7 +677,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP if (!ppp.isBogus()) { sb.append(ppp); } - sb.append(AffixUtils::escape(UnicodeStringCharSequence(pp))); + sb.append(AffixUtils::escape(pp)); int afterPrefixPos = sb.length(); // Figure out the grouping sizes. @@ -695,11 +716,11 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP } } else if (roundingInterval != 0.0) { // Rounding Interval. - digitsStringScale = minFrac; + digitsStringScale = -roundingutils::doubleFractionLength(roundingInterval); // TODO: Check for DoS here? DecimalQuantity incrementQuantity; incrementQuantity.setToDouble(roundingInterval); - incrementQuantity.adjustMagnitude(minFrac); + incrementQuantity.adjustMagnitude(-digitsStringScale); incrementQuantity.roundToMagnitude(0, kDefaultMode, status); UnicodeString str = incrementQuantity.toPlainString(); if (str.charAt(0) == u'-') { @@ -753,7 +774,7 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP if (!psp.isBogus()) { sb.append(psp); } - sb.append(AffixUtils::escape(UnicodeStringCharSequence(ps))); + sb.append(AffixUtils::escape(ps)); // Resolve Padding if (paddingWidth != -1 && !paddingLocation.isNull()) { @@ -795,22 +816,25 @@ UnicodeString PatternStringUtils::propertiesToPatternString(const DecimalFormatP if (!npp.isBogus()) { sb.append(npp); } - sb.append(AffixUtils::escape(UnicodeStringCharSequence(np))); + sb.append(AffixUtils::escape(np)); // Copy the positive digit format into the negative. // This is optional; the pattern is the same as if '#' were appended here instead. - sb.append(sb, afterPrefixPos, beforeSuffixPos); + // NOTE: It is not safe to append the UnicodeString to itself, so we need to copy. + // See http://bugs.icu-project.org/trac/ticket/13707 + UnicodeString copy(sb); + sb.append(copy, afterPrefixPos, beforeSuffixPos - afterPrefixPos); if (!nsp.isBogus()) { sb.append(nsp); } - sb.append(AffixUtils::escape(UnicodeStringCharSequence(ns))); + sb.append(AffixUtils::escape(ns)); } return sb; } int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString& output, int startIndex, - UErrorCode &status) { - (void)status; + UErrorCode& status) { + (void) status; if (input.length() == 0) { input.setTo(kFallbackPaddingString, -1); } @@ -840,4 +864,207 @@ int PatternStringUtils::escapePaddingString(UnicodeString input, UnicodeString& return output.length() - startLength; } +UnicodeString +PatternStringUtils::convertLocalized(const UnicodeString& input, const DecimalFormatSymbols& symbols, + bool toLocalized, UErrorCode& status) { + // Construct a table of strings to be converted between localized and standard. + static constexpr int32_t LEN = 21; + UnicodeString table[LEN][2]; + int standIdx = toLocalized ? 0 : 1; + int localIdx = toLocalized ? 1 : 0; + table[0][standIdx] = u"%"; + table[0][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPercentSymbol); + table[1][standIdx] = u"‰"; + table[1][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol); + table[2][standIdx] = u"."; + table[2][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); + table[3][standIdx] = u","; + table[3][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); + table[4][standIdx] = u"-"; + table[4][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); + table[5][standIdx] = u"+"; + table[5][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); + table[6][standIdx] = u";"; + table[6][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPatternSeparatorSymbol); + table[7][standIdx] = u"@"; + table[7][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kSignificantDigitSymbol); + table[8][standIdx] = u"E"; + table[8][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol); + table[9][standIdx] = u"*"; + table[9][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kPadEscapeSymbol); + table[10][standIdx] = u"#"; + table[10][localIdx] = symbols.getConstSymbol(DecimalFormatSymbols::kDigitSymbol); + for (int i = 0; i < 10; i++) { + table[11 + i][standIdx] = u'0' + i; + table[11 + i][localIdx] = symbols.getConstDigitSymbol(i); + } + + // Special case: quotes are NOT allowed to be in any localIdx strings. + // Substitute them with '’' instead. + for (int32_t i = 0; i < LEN; i++) { + table[i][localIdx].findAndReplace(u'\'', u'’'); + } + + // Iterate through the string and convert. + // State table: + // 0 => base state + // 1 => first char inside a quoted sequence in input and output string + // 2 => inside a quoted sequence in input and output string + // 3 => first char after a close quote in input string; + // close quote still needs to be written to output string + // 4 => base state in input string; inside quoted sequence in output string + // 5 => first char inside a quoted sequence in input string; + // inside quoted sequence in output string + UnicodeString result; + int state = 0; + for (int offset = 0; offset < input.length(); offset++) { + UChar ch = input.charAt(offset); + + // Handle a quote character (state shift) + if (ch == u'\'') { + if (state == 0) { + result.append(u'\''); + state = 1; + continue; + } else if (state == 1) { + result.append(u'\''); + state = 0; + continue; + } else if (state == 2) { + state = 3; + continue; + } else if (state == 3) { + result.append(u'\''); + result.append(u'\''); + state = 1; + continue; + } else if (state == 4) { + state = 5; + continue; + } else { + U_ASSERT(state == 5); + result.append(u'\''); + result.append(u'\''); + state = 4; + continue; + } + } + + if (state == 0 || state == 3 || state == 4) { + for (auto& pair : table) { + // Perform a greedy match on this symbol string + UnicodeString temp = input.tempSubString(offset, pair[0].length()); + if (temp == pair[0]) { + // Skip ahead past this region for the next iteration + offset += pair[0].length() - 1; + if (state == 3 || state == 4) { + result.append(u'\''); + state = 0; + } + result.append(pair[1]); + goto continue_outer; + } + } + // No replacement found. Check if a special quote is necessary + for (auto& pair : table) { + UnicodeString temp = input.tempSubString(offset, pair[1].length()); + if (temp == pair[1]) { + if (state == 0) { + result.append(u'\''); + state = 4; + } + result.append(ch); + goto continue_outer; + } + } + // Still nothing. Copy the char verbatim. (Add a close quote if necessary) + if (state == 3 || state == 4) { + result.append(u'\''); + state = 0; + } + result.append(ch); + } else { + U_ASSERT(state == 1 || state == 2 || state == 5); + result.append(ch); + state = 2; + } + continue_outer:; + } + // Resolve final quotes + if (state == 3 || state == 4) { + result.append(u'\''); + state = 0; + } + if (state != 0) { + // Malformed localized pattern: unterminated quote + status = U_PATTERN_SYNTAX_ERROR; + } + return result; +} + +void PatternStringUtils::patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix, + int8_t signum, UNumberSignDisplay signDisplay, + StandardPlural::Form plural, + bool perMilleReplacesPercent, UnicodeString& output) { + + // Should the output render '+' where '-' would normally appear in the pattern? + bool plusReplacesMinusSign = signum != -1 && ( + signDisplay == UNUM_SIGN_ALWAYS || signDisplay == UNUM_SIGN_ACCOUNTING_ALWAYS || ( + signum == 1 && ( + signDisplay == UNUM_SIGN_EXCEPT_ZERO || + signDisplay == UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO))) && + patternInfo.positiveHasPlusSign() == false; + + // Should we use the affix from the negative subpattern? (If not, we will use the positive + // subpattern.) + bool useNegativeAffixPattern = patternInfo.hasNegativeSubpattern() && ( + signum == -1 || (patternInfo.negativeHasMinusSign() && plusReplacesMinusSign)); + + // Resolve the flags for the affix pattern. + int flags = 0; + if (useNegativeAffixPattern) { + flags |= AffixPatternProvider::AFFIX_NEGATIVE_SUBPATTERN; + } + if (isPrefix) { + flags |= AffixPatternProvider::AFFIX_PREFIX; + } + if (plural != StandardPlural::Form::COUNT) { + U_ASSERT(plural == (AffixPatternProvider::AFFIX_PLURAL_MASK & plural)); + flags |= plural; + } + + // Should we prepend a sign to the pattern? + bool prependSign; + if (!isPrefix || useNegativeAffixPattern) { + prependSign = false; + } else if (signum == -1) { + prependSign = signDisplay != UNUM_SIGN_NEVER; + } else { + prependSign = plusReplacesMinusSign; + } + + // Compute the length of the affix pattern. + int length = patternInfo.length(flags) + (prependSign ? 1 : 0); + + // Finally, set the result into the StringBuilder. + output.remove(); + for (int index = 0; index < length; index++) { + char16_t candidate; + if (prependSign && index == 0) { + candidate = u'-'; + } else if (prependSign) { + candidate = patternInfo.charAt(flags, index - 1); + } else { + candidate = patternInfo.charAt(flags, index); + } + if (plusReplacesMinusSign && candidate == u'-') { + candidate = u'+'; + } + if (perMilleReplacesPercent && candidate == u'%') { + candidate = u'‰'; + } + output.append(candidate); + } +} + #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_patternstring.h b/deps/icu-small/source/i18n/number_patternstring.h index ec44290d66397c..91e120c16a1a84 100644 --- a/deps/icu-small/source/i18n/number_patternstring.h +++ b/deps/icu-small/source/i18n/number_patternstring.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_PATTERNSTRING_H__ #define __NUMBER_PATTERNSTRING_H__ @@ -30,7 +30,7 @@ struct U_I18N_API Endpoints { // Exported as U_I18N_API because it is a public member field of exported ParsedPatternInfo struct U_I18N_API ParsedSubpatternInfo { - int64_t groupingSizes = 0x0000ffffffff0000L; + uint64_t groupingSizes = 0x0000ffffffff0000L; int32_t integerLeadingHashSigns = 0; int32_t integerTrailingHashSigns = 0; int32_t integerNumerals = 0; @@ -41,7 +41,9 @@ struct U_I18N_API ParsedSubpatternInfo { int32_t fractionTotal = 0; // for convenience bool hasDecimal = false; int32_t widthExceptAffixes = 0; - NullableValue paddingLocation; + // Note: NullableValue causes issues here with std::move. + bool hasPadding = false; + UNumberFormatPadPosition paddingLocation = UNUM_PAD_BEFORE_PREFIX; DecimalQuantity rounding; bool exponentHasPlusSign = false; int32_t exponentZeros = 0; @@ -62,17 +64,21 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor ParsedSubpatternInfo positive; ParsedSubpatternInfo negative; - ParsedPatternInfo() : state(this->pattern), currentSubpattern(nullptr) {} + ParsedPatternInfo() + : state(this->pattern), currentSubpattern(nullptr) {} ~ParsedPatternInfo() U_OVERRIDE = default; - static int32_t getLengthFromEndpoints(const Endpoints &endpoints); + // Need to declare this explicitly because of the destructor + ParsedPatternInfo& operator=(ParsedPatternInfo&& src) U_NOEXCEPT = default; + + static int32_t getLengthFromEndpoints(const Endpoints& endpoints); char16_t charAt(int32_t flags, int32_t index) const U_OVERRIDE; int32_t length(int32_t flags) const U_OVERRIDE; - UnicodeString getString(int32_t flags) const; + UnicodeString getString(int32_t flags) const U_OVERRIDE; bool positiveHasPlusSign() const U_OVERRIDE; @@ -82,16 +88,24 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor bool hasCurrencySign() const U_OVERRIDE; - bool containsSymbolType(AffixPatternType type, UErrorCode &status) const U_OVERRIDE; + bool containsSymbolType(AffixPatternType type, UErrorCode& status) const U_OVERRIDE; bool hasBody() const U_OVERRIDE; private: struct U_I18N_API ParserState { - const UnicodeString &pattern; // reference to the parent + const UnicodeString& pattern; // reference to the parent int32_t offset = 0; - explicit ParserState(const UnicodeString &_pattern) : pattern(_pattern) {}; + explicit ParserState(const UnicodeString& _pattern) + : pattern(_pattern) {}; + + ParserState& operator=(ParserState&& src) U_NOEXCEPT { + // Leave pattern reference alone; it will continue to point to the same place in memory, + // which gets overwritten by ParsedPatternInfo's implicit move assignment. + offset = src.offset; + return *this; + } UChar32 peek(); @@ -99,45 +113,48 @@ struct U_I18N_API ParsedPatternInfo : public AffixPatternProvider, public UMemor // TODO: We don't currently do anything with the message string. // This method is here as a shell for Java compatibility. - inline void toParseException(const char16_t *message) { (void)message; } - } - state; + inline void toParseException(const char16_t* message) { (void) message; } + } state; // NOTE: In Java, these are written as pure functions. // In C++, they're written as methods. // The behavior is the same. // Mutable transient pointer: - ParsedSubpatternInfo *currentSubpattern; + ParsedSubpatternInfo* currentSubpattern; // In Java, "negative == null" tells us whether or not we had a negative subpattern. // In C++, we need to remember in another boolean. bool fHasNegativeSubpattern = false; - const Endpoints &getEndpoints(int32_t flags) const; + const Endpoints& getEndpoints(int32_t flags) const; /** Run the recursive descent parser. */ - void consumePattern(const UnicodeString &patternString, UErrorCode &status); + void consumePattern(const UnicodeString& patternString, UErrorCode& status); - void consumeSubpattern(UErrorCode &status); + void consumeSubpattern(UErrorCode& status); - void consumePadding(PadPosition paddingLocation, UErrorCode &status); + void consumePadding(PadPosition paddingLocation, UErrorCode& status); - void consumeAffix(Endpoints &endpoints, UErrorCode &status); + void consumeAffix(Endpoints& endpoints, UErrorCode& status); - void consumeLiteral(UErrorCode &status); + void consumeLiteral(UErrorCode& status); - void consumeFormat(UErrorCode &status); + void consumeFormat(UErrorCode& status); - void consumeIntegerFormat(UErrorCode &status); + void consumeIntegerFormat(UErrorCode& status); - void consumeFractionFormat(UErrorCode &status); + void consumeFractionFormat(UErrorCode& status); - void consumeExponent(UErrorCode &status); + void consumeExponent(UErrorCode& status); friend class PatternParser; }; +enum IgnoreRounding { + IGNORE_ROUNDING_NEVER = 0, IGNORE_ROUNDING_IF_CURRENCY = 1, IGNORE_ROUNDING_ALWAYS = 2 +}; + class U_I18N_API PatternParser { public: /** @@ -153,12 +170,8 @@ class U_I18N_API PatternParser { * The LDML decimal format pattern (Excel-style pattern) to parse. * @return The results of the parse. */ - static void - parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo &patternInfo, UErrorCode &status); - - enum IgnoreRounding { - IGNORE_ROUNDING_NEVER = 0, IGNORE_ROUNDING_IF_CURRENCY = 1, IGNORE_ROUNDING_ALWAYS = 2 - }; + static void parseToPatternInfo(const UnicodeString& patternString, ParsedPatternInfo& patternInfo, + UErrorCode& status); /** * Parses a pattern string into a new property bag. @@ -173,8 +186,10 @@ class U_I18N_API PatternParser { * @throws IllegalArgumentException * If there is a syntax error in the pattern string. */ - static DecimalFormatProperties - parseToProperties(const UnicodeString& pattern, IgnoreRounding ignoreRounding, UErrorCode &status); + static DecimalFormatProperties parseToProperties(const UnicodeString& pattern, + IgnoreRounding ignoreRounding, UErrorCode& status); + + static DecimalFormatProperties parseToProperties(const UnicodeString& pattern, UErrorCode& status); /** * Parses a pattern string into an existing property bag. All properties that can be encoded into a pattern string @@ -190,18 +205,19 @@ class U_I18N_API PatternParser { * @throws IllegalArgumentException * If there was a syntax error in the pattern string. */ - static void parseToExistingProperties(const UnicodeString& pattern, DecimalFormatProperties& properties, - IgnoreRounding ignoreRounding, UErrorCode &status); + static void parseToExistingProperties(const UnicodeString& pattern, + DecimalFormatProperties& properties, + IgnoreRounding ignoreRounding, UErrorCode& status); private: - static void - parseToExistingPropertiesImpl(const UnicodeString& pattern, DecimalFormatProperties &properties, - IgnoreRounding ignoreRounding, UErrorCode &status); + static void parseToExistingPropertiesImpl(const UnicodeString& pattern, + DecimalFormatProperties& properties, + IgnoreRounding ignoreRounding, UErrorCode& status); /** Finalizes the temporary data stored in the ParsedPatternInfo to the Properties. */ - static void - patternInfoToProperties(DecimalFormatProperties &properties, ParsedPatternInfo& patternInfo, - IgnoreRounding _ignoreRounding, UErrorCode &status); + static void patternInfoToProperties(DecimalFormatProperties& properties, + ParsedPatternInfo& patternInfo, IgnoreRounding _ignoreRounding, + UErrorCode& status); }; class U_I18N_API PatternStringUtils { @@ -217,8 +233,8 @@ class U_I18N_API PatternStringUtils { * The property bag to serialize. * @return A pattern string approximately serializing the property bag. */ - static UnicodeString - propertiesToPatternString(const DecimalFormatProperties &properties, UErrorCode &status); + static UnicodeString propertiesToPatternString(const DecimalFormatProperties& properties, + UErrorCode& status); /** @@ -248,14 +264,23 @@ class U_I18N_API PatternStringUtils { * notation. * @return The pattern expressed in the other notation. */ - static UnicodeString - convertLocalized(UnicodeString input, DecimalFormatSymbols symbols, bool toLocalized, - UErrorCode &status); + static UnicodeString convertLocalized(const UnicodeString& input, const DecimalFormatSymbols& symbols, + bool toLocalized, UErrorCode& status); + + /** + * This method contains the heart of the logic for rendering LDML affix strings. It handles + * sign-always-shown resolution, whether to use the positive or negative subpattern, permille + * substitution, and plural forms for CurrencyPluralInfo. + */ + static void patternInfoToStringBuilder(const AffixPatternProvider& patternInfo, bool isPrefix, + int8_t signum, UNumberSignDisplay signDisplay, + StandardPlural::Form plural, bool perMilleReplacesPercent, + UnicodeString& output); private: /** @return The number of chars inserted. */ - static int - escapePaddingString(UnicodeString input, UnicodeString &output, int startIndex, UErrorCode &status); + static int escapePaddingString(UnicodeString input, UnicodeString& output, int startIndex, + UErrorCode& status); }; } // namespace impl diff --git a/deps/icu-small/source/i18n/number_rounding.cpp b/deps/icu-small/source/i18n/number_rounding.cpp index fd4dafdf983b61..ae4b8849fbe956 100644 --- a/deps/icu-small/source/i18n/number_rounding.cpp +++ b/deps/icu-small/source/i18n/number_rounding.cpp @@ -3,17 +3,23 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "uassert.h" #include "unicode/numberformatter.h" #include "number_types.h" #include "number_decimalquantity.h" +#include "double-conversion.h" +#include "number_roundingutils.h" +#include "putilimp.h" using namespace icu; using namespace icu::number; using namespace icu::number::impl; + +using double_conversion::DoubleToStringConverter; + namespace { int32_t getRoundingMagnitudeFraction(int maxFrac) { @@ -46,15 +52,38 @@ int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) } -Rounder Rounder::unlimited() { - return Rounder(RND_NONE, {}, kDefaultMode); +MultiplierProducer::~MultiplierProducer() = default; + + +digits_t roundingutils::doubleFractionLength(double input) { + char buffer[DoubleToStringConverter::kBase10MaximalLength + 1]; + bool sign; // unused; always positive + int32_t length; + int32_t point; + DoubleToStringConverter::DoubleToAscii( + input, + DoubleToStringConverter::DtoaMode::SHORTEST, + 0, + buffer, + sizeof(buffer), + &sign, + &length, + &point + ); + + return static_cast(length - point); } -FractionRounder Rounder::integer() { + +Precision Precision::unlimited() { + return Precision(RND_NONE, {}, kDefaultMode); +} + +FractionPrecision Precision::integer() { return constructFraction(0, 0); } -FractionRounder Rounder::fixedFraction(int32_t minMaxFractionPlaces) { +FractionPrecision Precision::fixedFraction(int32_t minMaxFractionPlaces) { if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= kMaxIntFracSig) { return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces); } else { @@ -62,7 +91,7 @@ FractionRounder Rounder::fixedFraction(int32_t minMaxFractionPlaces) { } } -FractionRounder Rounder::minFraction(int32_t minFractionPlaces) { +FractionPrecision Precision::minFraction(int32_t minFractionPlaces) { if (minFractionPlaces >= 0 && minFractionPlaces <= kMaxIntFracSig) { return constructFraction(minFractionPlaces, -1); } else { @@ -70,7 +99,7 @@ FractionRounder Rounder::minFraction(int32_t minFractionPlaces) { } } -FractionRounder Rounder::maxFraction(int32_t maxFractionPlaces) { +FractionPrecision Precision::maxFraction(int32_t maxFractionPlaces) { if (maxFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig) { return constructFraction(0, maxFractionPlaces); } else { @@ -78,7 +107,7 @@ FractionRounder Rounder::maxFraction(int32_t maxFractionPlaces) { } } -FractionRounder Rounder::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) { +FractionPrecision Precision::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) { if (minFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig && minFractionPlaces <= maxFractionPlaces) { return constructFraction(minFractionPlaces, maxFractionPlaces); @@ -87,7 +116,7 @@ FractionRounder Rounder::minMaxFraction(int32_t minFractionPlaces, int32_t maxFr } } -Rounder Rounder::fixedDigits(int32_t minMaxSignificantDigits) { +Precision Precision::fixedSignificantDigits(int32_t minMaxSignificantDigits) { if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= kMaxIntFracSig) { return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits); } else { @@ -95,7 +124,7 @@ Rounder Rounder::fixedDigits(int32_t minMaxSignificantDigits) { } } -Rounder Rounder::minDigits(int32_t minSignificantDigits) { +Precision Precision::minSignificantDigits(int32_t minSignificantDigits) { if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) { return constructSignificant(minSignificantDigits, -1); } else { @@ -103,7 +132,7 @@ Rounder Rounder::minDigits(int32_t minSignificantDigits) { } } -Rounder Rounder::maxDigits(int32_t maxSignificantDigits) { +Precision Precision::maxSignificantDigits(int32_t maxSignificantDigits) { if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) { return constructSignificant(1, maxSignificantDigits); } else { @@ -111,7 +140,7 @@ Rounder Rounder::maxDigits(int32_t maxSignificantDigits) { } } -Rounder Rounder::minMaxDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) { +Precision Precision::minMaxSignificantDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) { if (minSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig && minSignificantDigits <= maxSignificantDigits) { return constructSignificant(minSignificantDigits, maxSignificantDigits); @@ -120,7 +149,7 @@ Rounder Rounder::minMaxDigits(int32_t minSignificantDigits, int32_t maxSignifica } } -IncrementRounder Rounder::increment(double roundingIncrement) { +IncrementPrecision Precision::increment(double roundingIncrement) { if (roundingIncrement > 0.0) { return constructIncrement(roundingIncrement, 0); } else { @@ -128,16 +157,18 @@ IncrementRounder Rounder::increment(double roundingIncrement) { } } -CurrencyRounder Rounder::currency(UCurrencyUsage currencyUsage) { +CurrencyPrecision Precision::currency(UCurrencyUsage currencyUsage) { return constructCurrency(currencyUsage); } -Rounder Rounder::withMode(RoundingMode roundingMode) const { +Precision Precision::withMode(RoundingMode roundingMode) const { if (fType == RND_ERROR) { return *this; } // no-op in error state - return {fType, fUnion, roundingMode}; + Precision retval = *this; + retval.fRoundingMode = roundingMode; + return retval; } -Rounder FractionRounder::withMinDigits(int32_t minSignificantDigits) const { +Precision FractionPrecision::withMinDigits(int32_t minSignificantDigits) const { if (fType == RND_ERROR) { return *this; } // no-op in error state if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) { return constructFractionSignificant(*this, minSignificantDigits, -1); @@ -146,7 +177,7 @@ Rounder FractionRounder::withMinDigits(int32_t minSignificantDigits) const { } } -Rounder FractionRounder::withMaxDigits(int32_t maxSignificantDigits) const { +Precision FractionPrecision::withMaxDigits(int32_t maxSignificantDigits) const { if (fType == RND_ERROR) { return *this; } // no-op in error state if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) { return constructFractionSignificant(*this, -1, maxSignificantDigits); @@ -156,7 +187,7 @@ Rounder FractionRounder::withMaxDigits(int32_t maxSignificantDigits) const { } // Private method on base class -Rounder Rounder::withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const { +Precision Precision::withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const { if (fType == RND_ERROR) { return *this; } // no-op in error state U_ASSERT(fType == RND_CURRENCY); const char16_t *isoCode = currency.getISOCurrency(); @@ -170,17 +201,17 @@ Rounder Rounder::withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) } } -// Public method on CurrencyRounder subclass -Rounder CurrencyRounder::withCurrency(const CurrencyUnit ¤cy) const { +// Public method on CurrencyPrecision subclass +Precision CurrencyPrecision::withCurrency(const CurrencyUnit ¤cy) const { UErrorCode localStatus = U_ZERO_ERROR; - Rounder result = Rounder::withCurrency(currency, localStatus); + Precision result = Precision::withCurrency(currency, localStatus); if (U_FAILURE(localStatus)) { return {localStatus}; } return result; } -Rounder IncrementRounder::withMinFraction(int32_t minFrac) const { +Precision IncrementPrecision::withMinFraction(int32_t minFrac) const { if (fType == RND_ERROR) { return *this; } // no-op in error state if (minFrac >= 0 && minFrac <= kMaxIntFracSig) { return constructIncrement(fUnion.increment.fIncrement, minFrac); @@ -189,67 +220,77 @@ Rounder IncrementRounder::withMinFraction(int32_t minFrac) const { } } -FractionRounder Rounder::constructFraction(int32_t minFrac, int32_t maxFrac) { +FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac) { FractionSignificantSettings settings; settings.fMinFrac = static_cast(minFrac); settings.fMaxFrac = static_cast(maxFrac); settings.fMinSig = -1; settings.fMaxSig = -1; - RounderUnion union_; + PrecisionUnion union_; union_.fracSig = settings; return {RND_FRACTION, union_, kDefaultMode}; } -Rounder Rounder::constructSignificant(int32_t minSig, int32_t maxSig) { +Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) { FractionSignificantSettings settings; settings.fMinFrac = -1; settings.fMaxFrac = -1; settings.fMinSig = static_cast(minSig); settings.fMaxSig = static_cast(maxSig); - RounderUnion union_; + PrecisionUnion union_; union_.fracSig = settings; return {RND_SIGNIFICANT, union_, kDefaultMode}; } -Rounder -Rounder::constructFractionSignificant(const FractionRounder &base, int32_t minSig, int32_t maxSig) { +Precision +Precision::constructFractionSignificant(const FractionPrecision &base, int32_t minSig, int32_t maxSig) { FractionSignificantSettings settings = base.fUnion.fracSig; settings.fMinSig = static_cast(minSig); settings.fMaxSig = static_cast(maxSig); - RounderUnion union_; + PrecisionUnion union_; union_.fracSig = settings; return {RND_FRACTION_SIGNIFICANT, union_, kDefaultMode}; } -IncrementRounder Rounder::constructIncrement(double increment, int32_t minFrac) { +IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) { IncrementSettings settings; settings.fIncrement = increment; settings.fMinFrac = static_cast(minFrac); - RounderUnion union_; + // One of the few pre-computed quantities: + // Note: it is possible for minFrac to be more than maxFrac... (misleading) + settings.fMaxFrac = roundingutils::doubleFractionLength(increment); + PrecisionUnion union_; union_.increment = settings; return {RND_INCREMENT, union_, kDefaultMode}; } -CurrencyRounder Rounder::constructCurrency(UCurrencyUsage usage) { - RounderUnion union_; +CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) { + PrecisionUnion union_; union_.currencyUsage = usage; return {RND_CURRENCY, union_, kDefaultMode}; } -Rounder Rounder::constructPassThrough() { - RounderUnion union_; - union_.errorCode = U_ZERO_ERROR; // initialize the variable - return {RND_PASS_THROUGH, union_, kDefaultMode}; -} -void Rounder::setLocaleData(const CurrencyUnit ¤cy, UErrorCode &status) { - if (fType == RND_CURRENCY) { - *this = withCurrency(currency, status); +RoundingImpl::RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode, + const CurrencyUnit& currency, UErrorCode& status) + : fPrecision(precision), fRoundingMode(roundingMode), fPassThrough(false) { + if (precision.fType == Precision::RND_CURRENCY) { + fPrecision = precision.withCurrency(currency, status); } } +RoundingImpl RoundingImpl::passThrough() { + RoundingImpl retval; + retval.fPassThrough = true; + return retval; +} + +bool RoundingImpl::isSignificantDigits() const { + return fPrecision.fType == Precision::RND_SIGNIFICANT; +} + int32_t -Rounder::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, +RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, UErrorCode &status) { // Do not call this method with zero. U_ASSERT(!input.isZero()); @@ -287,45 +328,59 @@ Rounder::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::Mult } /** This is the method that contains the actual rounding logic. */ -void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const { - switch (fType) { - case RND_BOGUS: - case RND_ERROR: +void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const { + if (fPassThrough) { + return; + } + switch (fPrecision.fType) { + case Precision::RND_BOGUS: + case Precision::RND_ERROR: // Errors should be caught before the apply() method is called status = U_INTERNAL_PROGRAM_ERROR; break; - case RND_NONE: + case Precision::RND_NONE: value.roundToInfinity(); break; - case RND_FRACTION: + case Precision::RND_FRACTION: value.roundToMagnitude( - getRoundingMagnitudeFraction(fUnion.fracSig.fMaxFrac), fRoundingMode, status); + getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac), + fRoundingMode, + status); value.setFractionLength( - uprv_max(0, -getDisplayMagnitudeFraction(fUnion.fracSig.fMinFrac)), INT32_MAX); + uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)), + INT32_MAX); break; - case RND_SIGNIFICANT: + case Precision::RND_SIGNIFICANT: value.roundToMagnitude( - getRoundingMagnitudeSignificant(value, fUnion.fracSig.fMaxSig), + getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig), fRoundingMode, status); value.setFractionLength( - uprv_max(0, -getDisplayMagnitudeSignificant(value, fUnion.fracSig.fMinSig)), + uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)), INT32_MAX); + // Make sure that digits are displayed on zero. + if (value.isZero() && fPrecision.fUnion.fracSig.fMinSig > 0) { + value.setIntegerLength(1, INT32_MAX); + } break; - case RND_FRACTION_SIGNIFICANT: { - int32_t displayMag = getDisplayMagnitudeFraction(fUnion.fracSig.fMinFrac); - int32_t roundingMag = getRoundingMagnitudeFraction(fUnion.fracSig.fMaxFrac); - if (fUnion.fracSig.fMinSig == -1) { + case Precision::RND_FRACTION_SIGNIFICANT: { + int32_t displayMag = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac); + int32_t roundingMag = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac); + if (fPrecision.fUnion.fracSig.fMinSig == -1) { // Max Sig override - int32_t candidate = getRoundingMagnitudeSignificant(value, fUnion.fracSig.fMaxSig); + int32_t candidate = getRoundingMagnitudeSignificant( + value, + fPrecision.fUnion.fracSig.fMaxSig); roundingMag = uprv_max(roundingMag, candidate); } else { // Min Sig override - int32_t candidate = getDisplayMagnitudeSignificant(value, fUnion.fracSig.fMinSig); + int32_t candidate = getDisplayMagnitudeSignificant( + value, + fPrecision.fUnion.fracSig.fMinSig); roundingMag = uprv_min(roundingMag, candidate); } value.roundToMagnitude(roundingMag, fRoundingMode, status); @@ -333,27 +388,27 @@ void Rounder::apply(impl::DecimalQuantity &value, UErrorCode& status) const { break; } - case RND_INCREMENT: + case Precision::RND_INCREMENT: value.roundToIncrement( - fUnion.increment.fIncrement, fRoundingMode, fUnion.increment.fMinFrac, status); - value.setFractionLength(fUnion.increment.fMinFrac, fUnion.increment.fMinFrac); + fPrecision.fUnion.increment.fIncrement, + fRoundingMode, + fPrecision.fUnion.increment.fMaxFrac, + status); + value.setFractionLength(fPrecision.fUnion.increment.fMinFrac, INT32_MAX); break; - case RND_CURRENCY: + case Precision::RND_CURRENCY: // Call .withCurrency() before .apply()! U_ASSERT(false); break; - - case RND_PASS_THROUGH: - break; } } -void Rounder::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) { +void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) { // This method is intended for the one specific purpose of helping print "00.000E0". - U_ASSERT(fType == RND_SIGNIFICANT); + U_ASSERT(isSignificantDigits()); U_ASSERT(value.isZero()); - value.setFractionLength(fUnion.fracSig.fMinSig - minInt, INT32_MAX); + value.setFractionLength(fPrecision.fUnion.fracSig.fMinSig - minInt, INT32_MAX); } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_roundingutils.h b/deps/icu-small/source/i18n/number_roundingutils.h index 6868ee0b86817e..66d58bb775bbee 100644 --- a/deps/icu-small/source/i18n/number_roundingutils.h +++ b/deps/icu-small/source/i18n/number_roundingutils.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_ROUNDINGUTILS_H__ #define __NUMBER_ROUNDINGUTILS_H__ @@ -131,7 +131,62 @@ inline bool roundsAtMidpoint(int roundingMode) { } } +/** + * Computes the number of fraction digits in a double. Used for computing maxFrac for an increment. + * Calls into the DoubleToStringConverter library to do so. + */ +digits_t doubleFractionLength(double input); + } // namespace roundingutils + + +/** + * Encapsulates a Precision and a RoundingMode and performs rounding on a DecimalQuantity. + * + * This class does not exist in Java: instead, the base Precision class is used. + */ +class RoundingImpl { + public: + RoundingImpl() = default; // default constructor: leaves object in undefined state + + RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode, + const CurrencyUnit& currency, UErrorCode& status); + + static RoundingImpl passThrough(); + + /** Required for ScientificFormatter */ + bool isSignificantDigits() const; + + /** + * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude + * adjustment), applies the adjustment, rounds, and returns the chosen multiplier. + * + *

+ * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we + * need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you + * guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then + * change your multiplier to be -6, and you get 1.0E6, which is correct. + * + * @param input The quantity to process. + * @param producer Function to call to return a multiplier based on a magnitude. + * @return The number of orders of magnitude the input was adjusted by this method. + */ + int32_t + chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, + UErrorCode &status); + + void apply(impl::DecimalQuantity &value, UErrorCode &status) const; + + /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */ + void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status); + + private: + Precision fPrecision; + UNumberFormatRoundingMode fRoundingMode; + bool fPassThrough; +}; + + } // namespace impl } // namespace number U_NAMESPACE_END diff --git a/deps/icu-small/source/i18n/number_scientific.cpp b/deps/icu-small/source/i18n/number_scientific.cpp index a2f2bf85a1fc9d..40952024e995cc 100644 --- a/deps/icu-small/source/i18n/number_scientific.cpp +++ b/deps/icu-small/source/i18n/number_scientific.cpp @@ -3,13 +3,14 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include #include "number_scientific.h" #include "number_utils.h" #include "number_stringbuilder.h" #include "unicode/unum.h" +#include "number_microprops.h" using namespace icu; using namespace icu::number; @@ -64,8 +65,13 @@ int32_t ScientificModifier::apply(NumberStringBuilder &output, int32_t /*leftInd int32_t disp = std::abs(fExponent); for (int j = 0; j < fHandler->fSettings.fMinExponentDigits || disp > 0; j++, disp /= 10) { auto d = static_cast(disp % 10); - const UnicodeString &digitString = getDigitFromSymbols(d, *fHandler->fSymbols); - i += output.insert(i - j, digitString, UNUM_EXPONENT_FIELD, status); + i += utils::insertDigitFromSymbols( + output, + i - j, + d, + *fHandler->fSymbols, + UNUM_EXPONENT_FIELD, + status); } return i - rightIndex; } @@ -101,22 +107,25 @@ void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m // Treat zero as if it had magnitude 0 int32_t exponent; if (quantity.isZero()) { - if (fSettings.fRequireMinInt && micros.rounding.fType == Rounder::RND_SIGNIFICANT) { + if (fSettings.fRequireMinInt && micros.rounder.isSignificantDigits()) { // Show "00.000E0" on pattern "00.000E0" - micros.rounding.apply(quantity, fSettings.fEngineeringInterval, status); + micros.rounder.apply(quantity, fSettings.fEngineeringInterval, status); exponent = 0; } else { - micros.rounding.apply(quantity, status); + micros.rounder.apply(quantity, status); exponent = 0; } } else { - exponent = -micros.rounding.chooseMultiplierAndApply(quantity, *this, status); + exponent = -micros.rounder.chooseMultiplierAndApply(quantity, *this, status); } // Use MicroProps's helper ScientificModifier and save it as the modInner. ScientificModifier &mod = micros.helpers.scientificModifier; mod.set(exponent, this); micros.modInner = &mod; + + // We already performed rounding. Do not perform it again. + micros.rounder = RoundingImpl::passThrough(); } int32_t ScientificHandler::getMultiplier(int32_t magnitude) const { diff --git a/deps/icu-small/source/i18n/number_scientific.h b/deps/icu-small/source/i18n/number_scientific.h index f5e4d30e6a9737..974ab3adb614ca 100644 --- a/deps/icu-small/source/i18n/number_scientific.h +++ b/deps/icu-small/source/i18n/number_scientific.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_SCIENTIFIC_H__ #define __NUMBER_SCIENTIFIC_H__ diff --git a/deps/icu-small/source/i18n/number_skeletons.cpp b/deps/icu-small/source/i18n/number_skeletons.cpp new file mode 100644 index 00000000000000..c7bb18b5f3d2b5 --- /dev/null +++ b/deps/icu-small/source/i18n/number_skeletons.cpp @@ -0,0 +1,1510 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "number_decnum.h" +#include "number_skeletons.h" +#include "umutex.h" +#include "ucln_in.h" +#include "patternprops.h" +#include "unicode/ucharstriebuilder.h" +#include "number_utils.h" +#include "number_decimalquantity.h" +#include "unicode/numberformatter.h" +#include "uinvchar.h" +#include "charstr.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; +using namespace icu::number::impl::skeleton; + +namespace { + +icu::UInitOnce gNumberSkeletonsInitOnce = U_INITONCE_INITIALIZER; + +char16_t* kSerializedStemTrie = nullptr; + +UBool U_CALLCONV cleanupNumberSkeletons() { + uprv_free(kSerializedStemTrie); + kSerializedStemTrie = nullptr; + gNumberSkeletonsInitOnce.reset(); + return TRUE; +} + +void U_CALLCONV initNumberSkeletons(UErrorCode& status) { + ucln_i18n_registerCleanup(UCLN_I18N_NUMBER_SKELETONS, cleanupNumberSkeletons); + + UCharsTrieBuilder b(status); + if (U_FAILURE(status)) { return; } + + // Section 1: + b.add(u"compact-short", STEM_COMPACT_SHORT, status); + b.add(u"compact-long", STEM_COMPACT_LONG, status); + b.add(u"scientific", STEM_SCIENTIFIC, status); + b.add(u"engineering", STEM_ENGINEERING, status); + b.add(u"notation-simple", STEM_NOTATION_SIMPLE, status); + b.add(u"base-unit", STEM_BASE_UNIT, status); + b.add(u"percent", STEM_PERCENT, status); + b.add(u"permille", STEM_PERMILLE, status); + b.add(u"precision-integer", STEM_PRECISION_INTEGER, status); + b.add(u"precision-unlimited", STEM_PRECISION_UNLIMITED, status); + b.add(u"precision-currency-standard", STEM_PRECISION_CURRENCY_STANDARD, status); + b.add(u"precision-currency-cash", STEM_PRECISION_CURRENCY_CASH, status); + b.add(u"rounding-mode-ceiling", STEM_ROUNDING_MODE_CEILING, status); + b.add(u"rounding-mode-floor", STEM_ROUNDING_MODE_FLOOR, status); + b.add(u"rounding-mode-down", STEM_ROUNDING_MODE_DOWN, status); + b.add(u"rounding-mode-up", STEM_ROUNDING_MODE_UP, status); + b.add(u"rounding-mode-half-even", STEM_ROUNDING_MODE_HALF_EVEN, status); + b.add(u"rounding-mode-half-down", STEM_ROUNDING_MODE_HALF_DOWN, status); + b.add(u"rounding-mode-half-up", STEM_ROUNDING_MODE_HALF_UP, status); + b.add(u"rounding-mode-unnecessary", STEM_ROUNDING_MODE_UNNECESSARY, status); + b.add(u"group-off", STEM_GROUP_OFF, status); + b.add(u"group-min2", STEM_GROUP_MIN2, status); + b.add(u"group-auto", STEM_GROUP_AUTO, status); + b.add(u"group-on-aligned", STEM_GROUP_ON_ALIGNED, status); + b.add(u"group-thousands", STEM_GROUP_THOUSANDS, status); + b.add(u"latin", STEM_LATIN, status); + b.add(u"unit-width-narrow", STEM_UNIT_WIDTH_NARROW, status); + b.add(u"unit-width-short", STEM_UNIT_WIDTH_SHORT, status); + b.add(u"unit-width-full-name", STEM_UNIT_WIDTH_FULL_NAME, status); + b.add(u"unit-width-iso-code", STEM_UNIT_WIDTH_ISO_CODE, status); + b.add(u"unit-width-hidden", STEM_UNIT_WIDTH_HIDDEN, status); + b.add(u"sign-auto", STEM_SIGN_AUTO, status); + b.add(u"sign-always", STEM_SIGN_ALWAYS, status); + b.add(u"sign-never", STEM_SIGN_NEVER, status); + b.add(u"sign-accounting", STEM_SIGN_ACCOUNTING, status); + b.add(u"sign-accounting-always", STEM_SIGN_ACCOUNTING_ALWAYS, status); + b.add(u"sign-except-zero", STEM_SIGN_EXCEPT_ZERO, status); + b.add(u"sign-accounting-except-zero", STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, status); + b.add(u"decimal-auto", STEM_DECIMAL_AUTO, status); + b.add(u"decimal-always", STEM_DECIMAL_ALWAYS, status); + if (U_FAILURE(status)) { return; } + + // Section 2: + b.add(u"precision-increment", STEM_PRECISION_INCREMENT, status); + b.add(u"measure-unit", STEM_MEASURE_UNIT, status); + b.add(u"per-measure-unit", STEM_PER_MEASURE_UNIT, status); + b.add(u"currency", STEM_CURRENCY, status); + b.add(u"integer-width", STEM_INTEGER_WIDTH, status); + b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status); + b.add(u"scale", STEM_SCALE, status); + if (U_FAILURE(status)) { return; } + + // Build the CharsTrie + // TODO: Use SLOW or FAST here? + UnicodeString result; + b.buildUnicodeString(USTRINGTRIE_BUILD_FAST, result, status); + if (U_FAILURE(status)) { return; } + + // Copy the result into the global constant pointer + size_t numBytes = result.length() * sizeof(char16_t); + kSerializedStemTrie = static_cast(uprv_malloc(numBytes)); + uprv_memcpy(kSerializedStemTrie, result.getBuffer(), numBytes); +} + + +inline void appendMultiple(UnicodeString& sb, UChar32 cp, int32_t count) { + for (int i = 0; i < count; i++) { + sb.append(cp); + } +} + + +#define CHECK_NULL(seen, field, status) (void)(seen); /* for auto-format line wrapping */ \ +{ \ + if ((seen).field) { \ + (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ + return STATE_NULL; \ + } \ + (seen).field = true; \ +} + + +#define SKELETON_UCHAR_TO_CHAR(dest, src, start, end, status) (void)(dest); \ +{ \ + UErrorCode conversionStatus = U_ZERO_ERROR; \ + (dest).appendInvariantChars({FALSE, (src).getBuffer() + (start), (end) - (start)}, conversionStatus); \ + if (conversionStatus == U_INVARIANT_CONVERSION_ERROR) { \ + /* Don't propagate the invariant conversion error; it is a skeleton syntax error */ \ + (status) = U_NUMBER_SKELETON_SYNTAX_ERROR; \ + return; \ + } else if (U_FAILURE(conversionStatus)) { \ + (status) = conversionStatus; \ + return; \ + } \ +} + + +} // anonymous namespace + + +Notation stem_to_object::notation(skeleton::StemEnum stem) { + switch (stem) { + case STEM_COMPACT_SHORT: + return Notation::compactShort(); + case STEM_COMPACT_LONG: + return Notation::compactLong(); + case STEM_SCIENTIFIC: + return Notation::scientific(); + case STEM_ENGINEERING: + return Notation::engineering(); + case STEM_NOTATION_SIMPLE: + return Notation::simple(); + default: + U_ASSERT(false); + return Notation::simple(); // return a value: silence compiler warning + } +} + +MeasureUnit stem_to_object::unit(skeleton::StemEnum stem) { + switch (stem) { + case STEM_BASE_UNIT: + // Slicing is okay + return NoUnit::base(); // NOLINT + case STEM_PERCENT: + // Slicing is okay + return NoUnit::percent(); // NOLINT + case STEM_PERMILLE: + // Slicing is okay + return NoUnit::permille(); // NOLINT + default: + U_ASSERT(false); + return {}; // return a value: silence compiler warning + } +} + +Precision stem_to_object::precision(skeleton::StemEnum stem) { + switch (stem) { + case STEM_PRECISION_INTEGER: + return Precision::integer(); + case STEM_PRECISION_UNLIMITED: + return Precision::unlimited(); + case STEM_PRECISION_CURRENCY_STANDARD: + return Precision::currency(UCURR_USAGE_STANDARD); + case STEM_PRECISION_CURRENCY_CASH: + return Precision::currency(UCURR_USAGE_CASH); + default: + U_ASSERT(false); + return Precision::integer(); // return a value: silence compiler warning + } +} + +UNumberFormatRoundingMode stem_to_object::roundingMode(skeleton::StemEnum stem) { + switch (stem) { + case STEM_ROUNDING_MODE_CEILING: + return UNUM_ROUND_CEILING; + case STEM_ROUNDING_MODE_FLOOR: + return UNUM_ROUND_FLOOR; + case STEM_ROUNDING_MODE_DOWN: + return UNUM_ROUND_DOWN; + case STEM_ROUNDING_MODE_UP: + return UNUM_ROUND_UP; + case STEM_ROUNDING_MODE_HALF_EVEN: + return UNUM_ROUND_HALFEVEN; + case STEM_ROUNDING_MODE_HALF_DOWN: + return UNUM_ROUND_HALFDOWN; + case STEM_ROUNDING_MODE_HALF_UP: + return UNUM_ROUND_HALFUP; + case STEM_ROUNDING_MODE_UNNECESSARY: + return UNUM_ROUND_UNNECESSARY; + default: + U_ASSERT(false); + return UNUM_ROUND_UNNECESSARY; + } +} + +UGroupingStrategy stem_to_object::groupingStrategy(skeleton::StemEnum stem) { + switch (stem) { + case STEM_GROUP_OFF: + return UNUM_GROUPING_OFF; + case STEM_GROUP_MIN2: + return UNUM_GROUPING_MIN2; + case STEM_GROUP_AUTO: + return UNUM_GROUPING_AUTO; + case STEM_GROUP_ON_ALIGNED: + return UNUM_GROUPING_ON_ALIGNED; + case STEM_GROUP_THOUSANDS: + return UNUM_GROUPING_THOUSANDS; + default: + return UNUM_GROUPING_COUNT; // for objects, throw; for enums, return COUNT + } +} + +UNumberUnitWidth stem_to_object::unitWidth(skeleton::StemEnum stem) { + switch (stem) { + case STEM_UNIT_WIDTH_NARROW: + return UNUM_UNIT_WIDTH_NARROW; + case STEM_UNIT_WIDTH_SHORT: + return UNUM_UNIT_WIDTH_SHORT; + case STEM_UNIT_WIDTH_FULL_NAME: + return UNUM_UNIT_WIDTH_FULL_NAME; + case STEM_UNIT_WIDTH_ISO_CODE: + return UNUM_UNIT_WIDTH_ISO_CODE; + case STEM_UNIT_WIDTH_HIDDEN: + return UNUM_UNIT_WIDTH_HIDDEN; + default: + return UNUM_UNIT_WIDTH_COUNT; // for objects, throw; for enums, return COUNT + } +} + +UNumberSignDisplay stem_to_object::signDisplay(skeleton::StemEnum stem) { + switch (stem) { + case STEM_SIGN_AUTO: + return UNUM_SIGN_AUTO; + case STEM_SIGN_ALWAYS: + return UNUM_SIGN_ALWAYS; + case STEM_SIGN_NEVER: + return UNUM_SIGN_NEVER; + case STEM_SIGN_ACCOUNTING: + return UNUM_SIGN_ACCOUNTING; + case STEM_SIGN_ACCOUNTING_ALWAYS: + return UNUM_SIGN_ACCOUNTING_ALWAYS; + case STEM_SIGN_EXCEPT_ZERO: + return UNUM_SIGN_EXCEPT_ZERO; + case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + return UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO; + default: + return UNUM_SIGN_COUNT; // for objects, throw; for enums, return COUNT + } +} + +UNumberDecimalSeparatorDisplay stem_to_object::decimalSeparatorDisplay(skeleton::StemEnum stem) { + switch (stem) { + case STEM_DECIMAL_AUTO: + return UNUM_DECIMAL_SEPARATOR_AUTO; + case STEM_DECIMAL_ALWAYS: + return UNUM_DECIMAL_SEPARATOR_ALWAYS; + default: + return UNUM_DECIMAL_SEPARATOR_COUNT; // for objects, throw; for enums, return COUNT + } +} + + +void enum_to_stem_string::roundingMode(UNumberFormatRoundingMode value, UnicodeString& sb) { + switch (value) { + case UNUM_ROUND_CEILING: + sb.append(u"rounding-mode-ceiling", -1); + break; + case UNUM_ROUND_FLOOR: + sb.append(u"rounding-mode-floor", -1); + break; + case UNUM_ROUND_DOWN: + sb.append(u"rounding-mode-down", -1); + break; + case UNUM_ROUND_UP: + sb.append(u"rounding-mode-up", -1); + break; + case UNUM_ROUND_HALFEVEN: + sb.append(u"rounding-mode-half-even", -1); + break; + case UNUM_ROUND_HALFDOWN: + sb.append(u"rounding-mode-half-down", -1); + break; + case UNUM_ROUND_HALFUP: + sb.append(u"rounding-mode-half-up", -1); + break; + case UNUM_ROUND_UNNECESSARY: + sb.append(u"rounding-mode-unnecessary", -1); + break; + default: + U_ASSERT(false); + } +} + +void enum_to_stem_string::groupingStrategy(UGroupingStrategy value, UnicodeString& sb) { + switch (value) { + case UNUM_GROUPING_OFF: + sb.append(u"group-off", -1); + break; + case UNUM_GROUPING_MIN2: + sb.append(u"group-min2", -1); + break; + case UNUM_GROUPING_AUTO: + sb.append(u"group-auto", -1); + break; + case UNUM_GROUPING_ON_ALIGNED: + sb.append(u"group-on-aligned", -1); + break; + case UNUM_GROUPING_THOUSANDS: + sb.append(u"group-thousands", -1); + break; + default: + U_ASSERT(false); + } +} + +void enum_to_stem_string::unitWidth(UNumberUnitWidth value, UnicodeString& sb) { + switch (value) { + case UNUM_UNIT_WIDTH_NARROW: + sb.append(u"unit-width-narrow", -1); + break; + case UNUM_UNIT_WIDTH_SHORT: + sb.append(u"unit-width-short", -1); + break; + case UNUM_UNIT_WIDTH_FULL_NAME: + sb.append(u"unit-width-full-name", -1); + break; + case UNUM_UNIT_WIDTH_ISO_CODE: + sb.append(u"unit-width-iso-code", -1); + break; + case UNUM_UNIT_WIDTH_HIDDEN: + sb.append(u"unit-width-hidden", -1); + break; + default: + U_ASSERT(false); + } +} + +void enum_to_stem_string::signDisplay(UNumberSignDisplay value, UnicodeString& sb) { + switch (value) { + case UNUM_SIGN_AUTO: + sb.append(u"sign-auto", -1); + break; + case UNUM_SIGN_ALWAYS: + sb.append(u"sign-always", -1); + break; + case UNUM_SIGN_NEVER: + sb.append(u"sign-never", -1); + break; + case UNUM_SIGN_ACCOUNTING: + sb.append(u"sign-accounting", -1); + break; + case UNUM_SIGN_ACCOUNTING_ALWAYS: + sb.append(u"sign-accounting-always", -1); + break; + case UNUM_SIGN_EXCEPT_ZERO: + sb.append(u"sign-except-zero", -1); + break; + case UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO: + sb.append(u"sign-accounting-except-zero", -1); + break; + default: + U_ASSERT(false); + } +} + +void +enum_to_stem_string::decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb) { + switch (value) { + case UNUM_DECIMAL_SEPARATOR_AUTO: + sb.append(u"decimal-auto", -1); + break; + case UNUM_DECIMAL_SEPARATOR_ALWAYS: + sb.append(u"decimal-always", -1); + break; + default: + U_ASSERT(false); + } +} + + +UnlocalizedNumberFormatter skeleton::create(const UnicodeString& skeletonString, UErrorCode& status) { + umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status); + MacroProps macros = parseSkeleton(skeletonString, status); + return NumberFormatter::with().macros(macros); +} + +UnicodeString skeleton::generate(const MacroProps& macros, UErrorCode& status) { + umtx_initOnce(gNumberSkeletonsInitOnce, &initNumberSkeletons, status); + UnicodeString sb; + GeneratorHelpers::generateSkeleton(macros, sb, status); + return sb; +} + +MacroProps skeleton::parseSkeleton(const UnicodeString& skeletonString, UErrorCode& status) { + if (U_FAILURE(status)) { return MacroProps(); } + + // Add a trailing whitespace to the end of the skeleton string to make code cleaner. + UnicodeString tempSkeletonString(skeletonString); + tempSkeletonString.append(u' '); + + SeenMacroProps seen; + MacroProps macros; + StringSegment segment(tempSkeletonString, false); + UCharsTrie stemTrie(kSerializedStemTrie); + ParseState stem = STATE_NULL; + int32_t offset = 0; + + // Primary skeleton parse loop: + while (offset < segment.length()) { + UChar32 cp = segment.codePointAt(offset); + bool isTokenSeparator = PatternProps::isWhiteSpace(cp); + bool isOptionSeparator = (cp == u'/'); + + if (!isTokenSeparator && !isOptionSeparator) { + // Non-separator token; consume it. + offset += U16_LENGTH(cp); + if (stem == STATE_NULL) { + // We are currently consuming a stem. + // Go to the next state in the stem trie. + stemTrie.nextForCodePoint(cp); + } + continue; + } + + // We are looking at a token or option separator. + // If the segment is nonempty, parse it and reset the segment. + // Otherwise, make sure it is a valid repeating separator. + if (offset != 0) { + segment.setLength(offset); + if (stem == STATE_NULL) { + // The first separator after the start of a token. Parse it as a stem. + stem = parseStem(segment, stemTrie, seen, macros, status); + stemTrie.reset(); + } else { + // A separator after the first separator of a token. Parse it as an option. + stem = parseOption(stem, segment, macros, status); + } + segment.resetLength(); + if (U_FAILURE(status)) { return macros; } + + // Consume the segment: + segment.adjustOffset(offset); + offset = 0; + + } else if (stem != STATE_NULL) { + // A separator ('/' or whitespace) following an option separator ('/') + // segment.setLength(U16_LENGTH(cp)); // for error message + // throw new SkeletonSyntaxException("Unexpected separator character", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return macros; + + } else { + // Two spaces in a row; this is OK. + } + + // Does the current stem forbid options? + if (isOptionSeparator && stem == STATE_NULL) { + // segment.setLength(U16_LENGTH(cp)); // for error message + // throw new SkeletonSyntaxException("Unexpected option separator", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return macros; + } + + // Does the current stem require an option? + if (isTokenSeparator && stem != STATE_NULL) { + switch (stem) { + case STATE_INCREMENT_PRECISION: + case STATE_MEASURE_UNIT: + case STATE_PER_MEASURE_UNIT: + case STATE_CURRENCY_UNIT: + case STATE_INTEGER_WIDTH: + case STATE_NUMBERING_SYSTEM: + case STATE_SCALE: + // segment.setLength(U16_LENGTH(cp)); // for error message + // throw new SkeletonSyntaxException("Stem requires an option", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return macros; + default: + break; + } + stem = STATE_NULL; + } + + // Consume the separator: + segment.adjustOffset(U16_LENGTH(cp)); + } + U_ASSERT(stem == STATE_NULL); + return macros; +} + +ParseState +skeleton::parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen, + MacroProps& macros, UErrorCode& status) { + // First check for "blueprint" stems, which start with a "signal char" + switch (segment.charAt(0)) { + case u'.': + CHECK_NULL(seen, precision, status); + blueprint_helpers::parseFractionStem(segment, macros, status); + return STATE_FRACTION_PRECISION; + case u'@': + CHECK_NULL(seen, precision, status); + blueprint_helpers::parseDigitsStem(segment, macros, status); + return STATE_NULL; + default: + break; + } + + // Now look at the stemsTrie, which is already be pointing at our stem. + UStringTrieResult stemResult = stemTrie.current(); + + if (stemResult != USTRINGTRIE_INTERMEDIATE_VALUE && stemResult != USTRINGTRIE_FINAL_VALUE) { + // throw new SkeletonSyntaxException("Unknown stem", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return STATE_NULL; + } + + auto stem = static_cast(stemTrie.getValue()); + switch (stem) { + + // Stems with meaning on their own, not requiring an option: + + case STEM_COMPACT_SHORT: + case STEM_COMPACT_LONG: + case STEM_SCIENTIFIC: + case STEM_ENGINEERING: + case STEM_NOTATION_SIMPLE: + CHECK_NULL(seen, notation, status); + macros.notation = stem_to_object::notation(stem); + switch (stem) { + case STEM_SCIENTIFIC: + case STEM_ENGINEERING: + return STATE_SCIENTIFIC; // allows for scientific options + default: + return STATE_NULL; + } + + case STEM_BASE_UNIT: + case STEM_PERCENT: + case STEM_PERMILLE: + CHECK_NULL(seen, unit, status); + macros.unit = stem_to_object::unit(stem); + return STATE_NULL; + + case STEM_PRECISION_INTEGER: + case STEM_PRECISION_UNLIMITED: + case STEM_PRECISION_CURRENCY_STANDARD: + case STEM_PRECISION_CURRENCY_CASH: + CHECK_NULL(seen, precision, status); + macros.precision = stem_to_object::precision(stem); + switch (stem) { + case STEM_PRECISION_INTEGER: + return STATE_FRACTION_PRECISION; // allows for "precision-integer/@##" + default: + return STATE_NULL; + } + + case STEM_ROUNDING_MODE_CEILING: + case STEM_ROUNDING_MODE_FLOOR: + case STEM_ROUNDING_MODE_DOWN: + case STEM_ROUNDING_MODE_UP: + case STEM_ROUNDING_MODE_HALF_EVEN: + case STEM_ROUNDING_MODE_HALF_DOWN: + case STEM_ROUNDING_MODE_HALF_UP: + case STEM_ROUNDING_MODE_UNNECESSARY: + CHECK_NULL(seen, roundingMode, status); + macros.roundingMode = stem_to_object::roundingMode(stem); + return STATE_NULL; + + case STEM_GROUP_OFF: + case STEM_GROUP_MIN2: + case STEM_GROUP_AUTO: + case STEM_GROUP_ON_ALIGNED: + case STEM_GROUP_THOUSANDS: + CHECK_NULL(seen, grouper, status); + macros.grouper = Grouper::forStrategy(stem_to_object::groupingStrategy(stem)); + return STATE_NULL; + + case STEM_LATIN: + CHECK_NULL(seen, symbols, status); + macros.symbols.setTo(NumberingSystem::createInstanceByName("latn", status)); + return STATE_NULL; + + case STEM_UNIT_WIDTH_NARROW: + case STEM_UNIT_WIDTH_SHORT: + case STEM_UNIT_WIDTH_FULL_NAME: + case STEM_UNIT_WIDTH_ISO_CODE: + case STEM_UNIT_WIDTH_HIDDEN: + CHECK_NULL(seen, unitWidth, status); + macros.unitWidth = stem_to_object::unitWidth(stem); + return STATE_NULL; + + case STEM_SIGN_AUTO: + case STEM_SIGN_ALWAYS: + case STEM_SIGN_NEVER: + case STEM_SIGN_ACCOUNTING: + case STEM_SIGN_ACCOUNTING_ALWAYS: + case STEM_SIGN_EXCEPT_ZERO: + case STEM_SIGN_ACCOUNTING_EXCEPT_ZERO: + CHECK_NULL(seen, sign, status); + macros.sign = stem_to_object::signDisplay(stem); + return STATE_NULL; + + case STEM_DECIMAL_AUTO: + case STEM_DECIMAL_ALWAYS: + CHECK_NULL(seen, decimal, status); + macros.decimal = stem_to_object::decimalSeparatorDisplay(stem); + return STATE_NULL; + + // Stems requiring an option: + + case STEM_PRECISION_INCREMENT: + CHECK_NULL(seen, precision, status); + return STATE_INCREMENT_PRECISION; + + case STEM_MEASURE_UNIT: + CHECK_NULL(seen, unit, status); + return STATE_MEASURE_UNIT; + + case STEM_PER_MEASURE_UNIT: + CHECK_NULL(seen, perUnit, status); + return STATE_PER_MEASURE_UNIT; + + case STEM_CURRENCY: + CHECK_NULL(seen, unit, status); + return STATE_CURRENCY_UNIT; + + case STEM_INTEGER_WIDTH: + CHECK_NULL(seen, integerWidth, status); + return STATE_INTEGER_WIDTH; + + case STEM_NUMBERING_SYSTEM: + CHECK_NULL(seen, symbols, status); + return STATE_NUMBERING_SYSTEM; + + case STEM_SCALE: + CHECK_NULL(seen, scale, status); + return STATE_SCALE; + + default: + U_ASSERT(false); + return STATE_NULL; // return a value: silence compiler warning + } +} + +ParseState skeleton::parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + + ///// Required options: ///// + + switch (stem) { + case STATE_CURRENCY_UNIT: + blueprint_helpers::parseCurrencyOption(segment, macros, status); + return STATE_NULL; + case STATE_MEASURE_UNIT: + blueprint_helpers::parseMeasureUnitOption(segment, macros, status); + return STATE_NULL; + case STATE_PER_MEASURE_UNIT: + blueprint_helpers::parseMeasurePerUnitOption(segment, macros, status); + return STATE_NULL; + case STATE_INCREMENT_PRECISION: + blueprint_helpers::parseIncrementOption(segment, macros, status); + return STATE_NULL; + case STATE_INTEGER_WIDTH: + blueprint_helpers::parseIntegerWidthOption(segment, macros, status); + return STATE_NULL; + case STATE_NUMBERING_SYSTEM: + blueprint_helpers::parseNumberingSystemOption(segment, macros, status); + return STATE_NULL; + case STATE_SCALE: + blueprint_helpers::parseScaleOption(segment, macros, status); + return STATE_NULL; + default: + break; + } + + ///// Non-required options: ///// + + // Scientific options + switch (stem) { + case STATE_SCIENTIFIC: + if (blueprint_helpers::parseExponentWidthOption(segment, macros, status)) { + return STATE_SCIENTIFIC; + } + if (U_FAILURE(status)) { + return {}; + } + if (blueprint_helpers::parseExponentSignOption(segment, macros, status)) { + return STATE_SCIENTIFIC; + } + if (U_FAILURE(status)) { + return {}; + } + break; + default: + break; + } + + // Frac-sig option + switch (stem) { + case STATE_FRACTION_PRECISION: + if (blueprint_helpers::parseFracSigOption(segment, macros, status)) { + return STATE_NULL; + } + if (U_FAILURE(status)) { + return {}; + } + break; + default: + break; + } + + // Unknown option + // throw new SkeletonSyntaxException("Invalid option", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return STATE_NULL; +} + +void GeneratorHelpers::generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (U_FAILURE(status)) { return; } + + // Supported options + if (GeneratorHelpers::notation(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::unit(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::perUnit(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::precision(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::roundingMode(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::grouping(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::integerWidth(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::symbols(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::unitWidth(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::sign(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::decimal(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + if (GeneratorHelpers::scale(macros, sb, status)) { + sb.append(u' '); + } + if (U_FAILURE(status)) { return; } + + // Unsupported options + if (!macros.padder.isBogus()) { + status = U_UNSUPPORTED_ERROR; + return; + } + if (macros.affixProvider != nullptr) { + status = U_UNSUPPORTED_ERROR; + return; + } + if (macros.rules != nullptr) { + status = U_UNSUPPORTED_ERROR; + return; + } + if (macros.currencySymbols != nullptr) { + status = U_UNSUPPORTED_ERROR; + return; + } + + // Remove the trailing space + if (sb.length() > 0) { + sb.truncate(sb.length() - 1); + } +} + + +bool blueprint_helpers::parseExponentWidthOption(const StringSegment& segment, MacroProps& macros, + UErrorCode&) { + if (segment.charAt(0) != u'+') { + return false; + } + int32_t offset = 1; + int32_t minExp = 0; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'e') { + minExp++; + } else { + break; + } + } + if (offset < segment.length()) { + return false; + } + // Use the public APIs to enforce bounds checking + macros.notation = static_cast(macros.notation).withMinExponentDigits(minExp); + return true; +} + +void +blueprint_helpers::generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode&) { + sb.append(u'+'); + appendMultiple(sb, u'e', minExponentDigits); +} + +bool +blueprint_helpers::parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode&) { + // Get the sign display type out of the CharsTrie data structure. + UCharsTrie tempStemTrie(kSerializedStemTrie); + UStringTrieResult result = tempStemTrie.next( + segment.toTempUnicodeString().getBuffer(), + segment.length()); + if (result != USTRINGTRIE_INTERMEDIATE_VALUE && result != USTRINGTRIE_FINAL_VALUE) { + return false; + } + auto sign = stem_to_object::signDisplay(static_cast(tempStemTrie.getValue())); + if (sign == UNUM_SIGN_COUNT) { + return false; + } + macros.notation = static_cast(macros.notation).withExponentSignDisplay(sign); + return true; +} + +void blueprint_helpers::parseCurrencyOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // Unlike ICU4J, have to check length manually because ICU4C CurrencyUnit does not check it for us + if (segment.length() != 3) { + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + const UChar* currencyCode = segment.toTempUnicodeString().getBuffer(); + UErrorCode localStatus = U_ZERO_ERROR; + CurrencyUnit currency(currencyCode, localStatus); + if (U_FAILURE(localStatus)) { + // Not 3 ascii chars + // throw new SkeletonSyntaxException("Invalid currency", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + // Slicing is OK + macros.unit = currency; // NOLINT +} + +void +blueprint_helpers::generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode&) { + sb.append(currency.getISOCurrency(), -1); +} + +void blueprint_helpers::parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + const UnicodeString stemString = segment.toTempUnicodeString(); + + // NOTE: The category (type) of the unit is guaranteed to be a valid subtag (alphanumeric) + // http://unicode.org/reports/tr35/#Validity_Data + int firstHyphen = 0; + while (firstHyphen < stemString.length() && stemString.charAt(firstHyphen) != '-') { + firstHyphen++; + } + if (firstHyphen == stemString.length()) { + // throw new SkeletonSyntaxException("Invalid measure unit option", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + + // Need to do char <-> UChar conversion... + U_ASSERT(U_SUCCESS(status)); + CharString type; + SKELETON_UCHAR_TO_CHAR(type, stemString, 0, firstHyphen, status); + CharString subType; + SKELETON_UCHAR_TO_CHAR(subType, stemString, firstHyphen + 1, stemString.length(), status); + + // Note: the largest type as of this writing (March 2018) is "volume", which has 24 units. + static constexpr int32_t CAPACITY = 30; + MeasureUnit units[CAPACITY]; + UErrorCode localStatus = U_ZERO_ERROR; + int32_t numUnits = MeasureUnit::getAvailable(type.data(), units, CAPACITY, localStatus); + if (U_FAILURE(localStatus)) { + // More than 30 units in this type? + status = U_INTERNAL_PROGRAM_ERROR; + return; + } + for (int32_t i = 0; i < numUnits; i++) { + auto& unit = units[i]; + if (uprv_strcmp(subType.data(), unit.getSubtype()) == 0) { + macros.unit = unit; + return; + } + } + + // throw new SkeletonSyntaxException("Unknown measure unit", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; +} + +void blueprint_helpers::generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb, + UErrorCode&) { + // Need to do char <-> UChar conversion... + sb.append(UnicodeString(measureUnit.getType(), -1, US_INV)); + sb.append(u'-'); + sb.append(UnicodeString(measureUnit.getSubtype(), -1, US_INV)); +} + +void blueprint_helpers::parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // A little bit of a hack: safe the current unit (numerator), call the main measure unit + // parsing code, put back the numerator unit, and put the new unit into per-unit. + MeasureUnit numerator = macros.unit; + parseMeasureUnitOption(segment, macros, status); + if (U_FAILURE(status)) { return; } + macros.perUnit = macros.unit; + macros.unit = numerator; +} + +void blueprint_helpers::parseFractionStem(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + U_ASSERT(segment.charAt(0) == u'.'); + int32_t offset = 1; + int32_t minFrac = 0; + int32_t maxFrac; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'0') { + minFrac++; + } else { + break; + } + } + if (offset < segment.length()) { + if (segment.charAt(offset) == u'+') { + maxFrac = -1; + offset++; + } else { + maxFrac = minFrac; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'#') { + maxFrac++; + } else { + break; + } + } + } + } else { + maxFrac = minFrac; + } + if (offset < segment.length()) { + // throw new SkeletonSyntaxException("Invalid fraction stem", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + // Use the public APIs to enforce bounds checking + if (maxFrac == -1) { + macros.precision = Precision::minFraction(minFrac); + } else { + macros.precision = Precision::minMaxFraction(minFrac, maxFrac); + } +} + +void +blueprint_helpers::generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode&) { + if (minFrac == 0 && maxFrac == 0) { + sb.append(u"precision-integer", -1); + return; + } + sb.append(u'.'); + appendMultiple(sb, u'0', minFrac); + if (maxFrac == -1) { + sb.append(u'+'); + } else { + appendMultiple(sb, u'#', maxFrac - minFrac); + } +} + +void +blueprint_helpers::parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status) { + U_ASSERT(segment.charAt(0) == u'@'); + int offset = 0; + int minSig = 0; + int maxSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'@') { + minSig++; + } else { + break; + } + } + if (offset < segment.length()) { + if (segment.charAt(offset) == u'+') { + maxSig = -1; + offset++; + } else { + maxSig = minSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'#') { + maxSig++; + } else { + break; + } + } + } + } else { + maxSig = minSig; + } + if (offset < segment.length()) { + // throw new SkeletonSyntaxException("Invalid significant digits stem", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + // Use the public APIs to enforce bounds checking + if (maxSig == -1) { + macros.precision = Precision::minSignificantDigits(minSig); + } else { + macros.precision = Precision::minMaxSignificantDigits(minSig, maxSig); + } +} + +void +blueprint_helpers::generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode&) { + appendMultiple(sb, u'@', minSig); + if (maxSig == -1) { + sb.append(u'+'); + } else { + appendMultiple(sb, u'#', maxSig - minSig); + } +} + +bool blueprint_helpers::parseFracSigOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + if (segment.charAt(0) != u'@') { + return false; + } + int offset = 0; + int minSig = 0; + int maxSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'@') { + minSig++; + } else { + break; + } + } + // For the frac-sig option, there must be minSig or maxSig but not both. + // Valid: @+, @@+, @@@+ + // Valid: @#, @##, @### + // Invalid: @, @@, @@@ + // Invalid: @@#, @@##, @@@# + if (offset < segment.length()) { + if (segment.charAt(offset) == u'+') { + maxSig = -1; + offset++; + } else if (minSig > 1) { + // @@#, @@##, @@@# + // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return false; + } else { + maxSig = minSig; + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'#') { + maxSig++; + } else { + break; + } + } + } + } else { + // @, @@, @@@ + // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return false; + } + if (offset < segment.length()) { + // throw new SkeletonSyntaxException("Invalid digits option for fraction rounder", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return false; + } + + auto& oldPrecision = static_cast(macros.precision); + if (maxSig == -1) { + macros.precision = oldPrecision.withMinDigits(minSig); + } else { + macros.precision = oldPrecision.withMaxDigits(maxSig); + } + return true; +} + +void blueprint_helpers::parseIncrementOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // Need to do char <-> UChar conversion... + U_ASSERT(U_SUCCESS(status)); + CharString buffer; + SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); + + // Utilize DecimalQuantity/decNumber to parse this for us. + DecimalQuantity dq; + UErrorCode localStatus = U_ZERO_ERROR; + dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus); + if (U_FAILURE(localStatus)) { + // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + double increment = dq.toDouble(); + + // We also need to figure out how many digits. Do a brute force string operation. + int decimalOffset = 0; + while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') { + decimalOffset++; + } + if (decimalOffset == segment.length()) { + macros.precision = Precision::increment(increment); + } else { + int32_t fractionLength = segment.length() - decimalOffset - 1; + macros.precision = Precision::increment(increment).withMinFraction(fractionLength); + } +} + +void blueprint_helpers::generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb, + UErrorCode&) { + // Utilize DecimalQuantity/double_conversion to format this for us. + DecimalQuantity dq; + dq.setToDouble(increment); + dq.roundToInfinity(); + sb.append(dq.toPlainString()); + + // We might need to append extra trailing zeros for min fraction... + if (trailingZeros > 0) { + appendMultiple(sb, u'0', trailingZeros); + } +} + +void blueprint_helpers::parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + int32_t offset = 0; + int32_t minInt = 0; + int32_t maxInt; + if (segment.charAt(0) == u'+') { + maxInt = -1; + offset++; + } else { + maxInt = 0; + } + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'#') { + maxInt++; + } else { + break; + } + } + if (offset < segment.length()) { + for (; offset < segment.length(); offset++) { + if (segment.charAt(offset) == u'0') { + minInt++; + } else { + break; + } + } + } + if (maxInt != -1) { + maxInt += minInt; + } + if (offset < segment.length()) { + // throw new SkeletonSyntaxException("Invalid integer width stem", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + // Use the public APIs to enforce bounds checking + if (maxInt == -1) { + macros.integerWidth = IntegerWidth::zeroFillTo(minInt); + } else { + macros.integerWidth = IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt); + } +} + +void blueprint_helpers::generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb, + UErrorCode&) { + if (maxInt == -1) { + sb.append(u'+'); + } else { + appendMultiple(sb, u'#', maxInt - minInt); + } + appendMultiple(sb, u'0', minInt); +} + +void blueprint_helpers::parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // Need to do char <-> UChar conversion... + U_ASSERT(U_SUCCESS(status)); + CharString buffer; + SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); + + NumberingSystem* ns = NumberingSystem::createInstanceByName(buffer.data(), status); + if (ns == nullptr || U_FAILURE(status)) { + // This is a skeleton syntax error; don't bubble up the low-level NumberingSystem error + // throw new SkeletonSyntaxException("Unknown numbering system", segment); + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + macros.symbols.setTo(ns); +} + +void blueprint_helpers::generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, + UErrorCode&) { + // Need to do char <-> UChar conversion... + sb.append(UnicodeString(ns.getName(), -1, US_INV)); +} + +void blueprint_helpers::parseScaleOption(const StringSegment& segment, MacroProps& macros, + UErrorCode& status) { + // Need to do char <-> UChar conversion... + U_ASSERT(U_SUCCESS(status)); + CharString buffer; + SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status); + + LocalPointer decnum(new DecNum(), status); + if (U_FAILURE(status)) { return; } + decnum->setTo({buffer.data(), buffer.length()}, status); + if (U_FAILURE(status)) { + // This is a skeleton syntax error; don't let the low-level decnum error bubble up + status = U_NUMBER_SKELETON_SYNTAX_ERROR; + return; + } + + // NOTE: The constructor will optimize the decnum for us if possible. + macros.scale = {0, decnum.orphan()}; +} + +void blueprint_helpers::generateScaleOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb, + UErrorCode& status) { + // Utilize DecimalQuantity/double_conversion to format this for us. + DecimalQuantity dq; + if (arbitrary != nullptr) { + dq.setToDecNum(*arbitrary, status); + if (U_FAILURE(status)) { return; } + } else { + dq.setToInt(1); + } + dq.adjustMagnitude(magnitude); + dq.roundToInfinity(); + sb.append(dq.toPlainString()); +} + + +bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.notation.fType == Notation::NTN_COMPACT) { + UNumberCompactStyle style = macros.notation.fUnion.compactStyle; + if (style == UNumberCompactStyle::UNUM_LONG) { + sb.append(u"compact-long", -1); + return true; + } else if (style == UNumberCompactStyle::UNUM_SHORT) { + sb.append(u"compact-short", -1); + return true; + } else { + // Compact notation generated from custom data (not supported in skeleton) + // The other compact notations are literals + status = U_UNSUPPORTED_ERROR; + return false; + } + } else if (macros.notation.fType == Notation::NTN_SCIENTIFIC) { + const Notation::ScientificSettings& impl = macros.notation.fUnion.scientific; + if (impl.fEngineeringInterval == 3) { + sb.append(u"engineering", -1); + } else { + sb.append(u"scientific", -1); + } + if (impl.fMinExponentDigits > 1) { + sb.append(u'/'); + blueprint_helpers::generateExponentWidthOption(impl.fMinExponentDigits, sb, status); + if (U_FAILURE(status)) { + return false; + } + } + if (impl.fExponentSignDisplay != UNUM_SIGN_AUTO) { + sb.append(u'/'); + enum_to_stem_string::signDisplay(impl.fExponentSignDisplay, sb); + } + return true; + } else { + // Default value is not shown in normalized form + return false; + } +} + +bool GeneratorHelpers::unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (utils::unitIsCurrency(macros.unit)) { + sb.append(u"currency/", -1); + CurrencyUnit currency(macros.unit, status); + if (U_FAILURE(status)) { + return false; + } + blueprint_helpers::generateCurrencyOption(currency, sb, status); + return true; + } else if (utils::unitIsNoUnit(macros.unit)) { + if (utils::unitIsPercent(macros.unit)) { + sb.append(u"percent", -1); + return true; + } else if (utils::unitIsPermille(macros.unit)) { + sb.append(u"permille", -1); + return true; + } else { + // Default value is not shown in normalized form + return false; + } + } else { + sb.append(u"measure-unit/", -1); + blueprint_helpers::generateMeasureUnitOption(macros.unit, sb, status); + return true; + } +} + +bool GeneratorHelpers::perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + // Per-units are currently expected to be only MeasureUnits. + if (utils::unitIsNoUnit(macros.perUnit)) { + if (utils::unitIsPercent(macros.perUnit) || utils::unitIsPermille(macros.perUnit)) { + status = U_UNSUPPORTED_ERROR; + return false; + } else { + // Default value: ok to ignore + return false; + } + } else if (utils::unitIsCurrency(macros.perUnit)) { + status = U_UNSUPPORTED_ERROR; + return false; + } else { + sb.append(u"per-measure-unit/", -1); + blueprint_helpers::generateMeasureUnitOption(macros.perUnit, sb, status); + return true; + } +} + +bool GeneratorHelpers::precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.precision.fType == Precision::RND_NONE) { + sb.append(u"precision-unlimited", -1); + } else if (macros.precision.fType == Precision::RND_FRACTION) { + const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; + blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); + } else if (macros.precision.fType == Precision::RND_SIGNIFICANT) { + const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; + blueprint_helpers::generateDigitsStem(impl.fMinSig, impl.fMaxSig, sb, status); + } else if (macros.precision.fType == Precision::RND_FRACTION_SIGNIFICANT) { + const Precision::FractionSignificantSettings& impl = macros.precision.fUnion.fracSig; + blueprint_helpers::generateFractionStem(impl.fMinFrac, impl.fMaxFrac, sb, status); + sb.append(u'/'); + if (impl.fMinSig == -1) { + blueprint_helpers::generateDigitsStem(1, impl.fMaxSig, sb, status); + } else { + blueprint_helpers::generateDigitsStem(impl.fMinSig, -1, sb, status); + } + } else if (macros.precision.fType == Precision::RND_INCREMENT) { + const Precision::IncrementSettings& impl = macros.precision.fUnion.increment; + sb.append(u"precision-increment/", -1); + blueprint_helpers::generateIncrementOption( + impl.fIncrement, + impl.fMinFrac - impl.fMaxFrac, + sb, + status); + } else if (macros.precision.fType == Precision::RND_CURRENCY) { + UCurrencyUsage usage = macros.precision.fUnion.currencyUsage; + if (usage == UCURR_USAGE_STANDARD) { + sb.append(u"precision-currency-standard", -1); + } else { + sb.append(u"precision-currency-cash", -1); + } + } else { + // Bogus or Error + return false; + } + + // NOTE: Always return true for rounding because the default value depends on other options. + return true; +} + +bool GeneratorHelpers::roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { + if (macros.roundingMode == kDefaultMode) { + return false; // Default + } + enum_to_stem_string::roundingMode(macros.roundingMode, sb); + return true; +} + +bool GeneratorHelpers::grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.grouper.isBogus()) { + return false; // No value + } else if (macros.grouper.fStrategy == UNUM_GROUPING_COUNT) { + status = U_UNSUPPORTED_ERROR; + return false; + } else if (macros.grouper.fStrategy == UNUM_GROUPING_AUTO) { + return false; // Default value + } else { + enum_to_stem_string::groupingStrategy(macros.grouper.fStrategy, sb); + return true; + } +} + +bool GeneratorHelpers::integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.integerWidth.fHasError || macros.integerWidth.isBogus() || + macros.integerWidth == IntegerWidth::standard()) { + // Error or Default + return false; + } + sb.append(u"integer-width/", -1); + blueprint_helpers::generateIntegerWidthOption( + macros.integerWidth.fUnion.minMaxInt.fMinInt, + macros.integerWidth.fUnion.minMaxInt.fMaxInt, + sb, + status); + return true; +} + +bool GeneratorHelpers::symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (macros.symbols.isNumberingSystem()) { + const NumberingSystem& ns = *macros.symbols.getNumberingSystem(); + if (uprv_strcmp(ns.getName(), "latn") == 0) { + sb.append(u"latin", -1); + } else { + sb.append(u"numbering-system/", -1); + blueprint_helpers::generateNumberingSystemOption(ns, sb, status); + } + return true; + } else if (macros.symbols.isDecimalFormatSymbols()) { + status = U_UNSUPPORTED_ERROR; + return false; + } else { + // No custom symbols + return false; + } +} + +bool GeneratorHelpers::unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { + if (macros.unitWidth == UNUM_UNIT_WIDTH_SHORT || macros.unitWidth == UNUM_UNIT_WIDTH_COUNT) { + return false; // Default or Bogus + } + enum_to_stem_string::unitWidth(macros.unitWidth, sb); + return true; +} + +bool GeneratorHelpers::sign(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { + if (macros.sign == UNUM_SIGN_AUTO || macros.sign == UNUM_SIGN_COUNT) { + return false; // Default or Bogus + } + enum_to_stem_string::signDisplay(macros.sign, sb); + return true; +} + +bool GeneratorHelpers::decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode&) { + if (macros.decimal == UNUM_DECIMAL_SEPARATOR_AUTO || macros.decimal == UNUM_DECIMAL_SEPARATOR_COUNT) { + return false; // Default or Bogus + } + enum_to_stem_string::decimalSeparatorDisplay(macros.decimal, sb); + return true; +} + +bool GeneratorHelpers::scale(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) { + if (!macros.scale.isValid()) { + return false; // Default or Bogus + } + sb.append(u"scale/", -1); + blueprint_helpers::generateScaleOption( + macros.scale.fMagnitude, + macros.scale.fArbitrary, + sb, + status); + return true; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_skeletons.h b/deps/icu-small/source/i18n/number_skeletons.h new file mode 100644 index 00000000000000..0161f5f0ba8c1a --- /dev/null +++ b/deps/icu-small/source/i18n/number_skeletons.h @@ -0,0 +1,327 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __SOURCE_NUMBER_SKELETONS_H__ +#define __SOURCE_NUMBER_SKELETONS_H__ + +#include "number_types.h" +#include "numparse_types.h" +#include "unicode/ucharstrie.h" + +using icu::numparse::impl::StringSegment; + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + +// Forward-declaration +struct SeenMacroProps; + +// namespace for enums and entrypoint functions +namespace skeleton { + +/////////////////////////////////////////////////////////////////////////////////////// +// NOTE: For an example of how to add a new stem to the number skeleton parser, see: // +// http://bugs.icu-project.org/trac/changeset/41193 // +/////////////////////////////////////////////////////////////////////////////////////// + +/** + * While parsing a skeleton, this enum records what type of option we expect to find next. + */ +enum ParseState { + + // Section 0: We expect whitespace or a stem, but not an option: + + STATE_NULL, + + // Section 1: We might accept an option, but it is not required: + + STATE_SCIENTIFIC, + STATE_FRACTION_PRECISION, + + // Section 2: An option is required: + + STATE_INCREMENT_PRECISION, + STATE_MEASURE_UNIT, + STATE_PER_MEASURE_UNIT, + STATE_CURRENCY_UNIT, + STATE_INTEGER_WIDTH, + STATE_NUMBERING_SYSTEM, + STATE_SCALE, +}; + +/** + * All possible stem literals have an entry in the StemEnum. The enum name is the kebab case stem + * string literal written in upper snake case. + * + * @see StemToObject + * @see #SERIALIZED_STEM_TRIE + */ +enum StemEnum { + + // Section 1: Stems that do not require an option: + + STEM_COMPACT_SHORT, + STEM_COMPACT_LONG, + STEM_SCIENTIFIC, + STEM_ENGINEERING, + STEM_NOTATION_SIMPLE, + STEM_BASE_UNIT, + STEM_PERCENT, + STEM_PERMILLE, + STEM_PRECISION_INTEGER, + STEM_PRECISION_UNLIMITED, + STEM_PRECISION_CURRENCY_STANDARD, + STEM_PRECISION_CURRENCY_CASH, + STEM_ROUNDING_MODE_CEILING, + STEM_ROUNDING_MODE_FLOOR, + STEM_ROUNDING_MODE_DOWN, + STEM_ROUNDING_MODE_UP, + STEM_ROUNDING_MODE_HALF_EVEN, + STEM_ROUNDING_MODE_HALF_DOWN, + STEM_ROUNDING_MODE_HALF_UP, + STEM_ROUNDING_MODE_UNNECESSARY, + STEM_GROUP_OFF, + STEM_GROUP_MIN2, + STEM_GROUP_AUTO, + STEM_GROUP_ON_ALIGNED, + STEM_GROUP_THOUSANDS, + STEM_LATIN, + STEM_UNIT_WIDTH_NARROW, + STEM_UNIT_WIDTH_SHORT, + STEM_UNIT_WIDTH_FULL_NAME, + STEM_UNIT_WIDTH_ISO_CODE, + STEM_UNIT_WIDTH_HIDDEN, + STEM_SIGN_AUTO, + STEM_SIGN_ALWAYS, + STEM_SIGN_NEVER, + STEM_SIGN_ACCOUNTING, + STEM_SIGN_ACCOUNTING_ALWAYS, + STEM_SIGN_EXCEPT_ZERO, + STEM_SIGN_ACCOUNTING_EXCEPT_ZERO, + STEM_DECIMAL_AUTO, + STEM_DECIMAL_ALWAYS, + + // Section 2: Stems that DO require an option: + + STEM_PRECISION_INCREMENT, + STEM_MEASURE_UNIT, + STEM_PER_MEASURE_UNIT, + STEM_CURRENCY, + STEM_INTEGER_WIDTH, + STEM_NUMBERING_SYSTEM, + STEM_SCALE, +}; + +/** + * Creates a NumberFormatter corresponding to the given skeleton string. + * + * @param skeletonString + * A number skeleton string, possibly not in its shortest form. + * @return An UnlocalizedNumberFormatter with behavior defined by the given skeleton string. + */ +UnlocalizedNumberFormatter create(const UnicodeString& skeletonString, UErrorCode& status); + +/** + * Create a skeleton string corresponding to the given NumberFormatter. + * + * @param macros + * The NumberFormatter options object. + * @return A skeleton string in normalized form. + */ +UnicodeString generate(const MacroProps& macros, UErrorCode& status); + +/** + * Converts from a skeleton string to a MacroProps. This method contains the primary parse loop. + * + * Internal: use the create() endpoint instead of this function. + */ +MacroProps parseSkeleton(const UnicodeString& skeletonString, UErrorCode& status); + +/** + * Given that the current segment represents a stem, parse it and save the result. + * + * @return The next state after parsing this stem, corresponding to what subset of options to expect. + */ +ParseState parseStem(const StringSegment& segment, const UCharsTrie& stemTrie, SeenMacroProps& seen, + MacroProps& macros, UErrorCode& status); + +/** + * Given that the current segment represents an option, parse it and save the result. + * + * @return The next state after parsing this option, corresponding to what subset of options to + * expect next. + */ +ParseState +parseOption(ParseState stem, const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +} // namespace skeleton + + +/** + * Namespace for utility methods that convert from StemEnum to corresponding objects or enums. This + * applies to only the "Section 1" stems, those that are well-defined without an option. + */ +namespace stem_to_object { + +Notation notation(skeleton::StemEnum stem); + +MeasureUnit unit(skeleton::StemEnum stem); + +Precision precision(skeleton::StemEnum stem); + +UNumberFormatRoundingMode roundingMode(skeleton::StemEnum stem); + +UGroupingStrategy groupingStrategy(skeleton::StemEnum stem); + +UNumberUnitWidth unitWidth(skeleton::StemEnum stem); + +UNumberSignDisplay signDisplay(skeleton::StemEnum stem); + +UNumberDecimalSeparatorDisplay decimalSeparatorDisplay(skeleton::StemEnum stem); + +} // namespace stem_to_object + +/** + * Namespace for utility methods that convert from enums to stem strings. More complex object conversions + * take place in the object_to_stem_string namespace. + */ +namespace enum_to_stem_string { + +void roundingMode(UNumberFormatRoundingMode value, UnicodeString& sb); + +void groupingStrategy(UGroupingStrategy value, UnicodeString& sb); + +void unitWidth(UNumberUnitWidth value, UnicodeString& sb); + +void signDisplay(UNumberSignDisplay value, UnicodeString& sb); + +void decimalSeparatorDisplay(UNumberDecimalSeparatorDisplay value, UnicodeString& sb); + +} // namespace enum_to_stem_string + +/** + * Namespace for utility methods for processing stems and options that cannot be interpreted literally. + */ +namespace blueprint_helpers { + +/** @return Whether we successfully found and parsed an exponent width option. */ +bool parseExponentWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateExponentWidthOption(int32_t minExponentDigits, UnicodeString& sb, UErrorCode& status); + +/** @return Whether we successfully found and parsed an exponent sign option. */ +bool parseExponentSignOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void parseCurrencyOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateCurrencyOption(const CurrencyUnit& currency, UnicodeString& sb, UErrorCode& status); + +void parseMeasureUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateMeasureUnitOption(const MeasureUnit& measureUnit, UnicodeString& sb, UErrorCode& status); + +void parseMeasurePerUnitOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void parseFractionStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateFractionStem(int32_t minFrac, int32_t maxFrac, UnicodeString& sb, UErrorCode& status); + +void parseDigitsStem(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateDigitsStem(int32_t minSig, int32_t maxSig, UnicodeString& sb, UErrorCode& status); + +/** @return Whether we successfully found and parsed a frac-sig option. */ +bool parseFracSigOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void parseIncrementOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void +generateIncrementOption(double increment, int32_t trailingZeros, UnicodeString& sb, UErrorCode& status); + +void parseIntegerWidthOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateIntegerWidthOption(int32_t minInt, int32_t maxInt, UnicodeString& sb, UErrorCode& status); + +void parseNumberingSystemOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, UErrorCode& status); + +void parseScaleOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); + +void generateScaleOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb, + UErrorCode& status); + +} // namespace blueprint_helpers + +/** + * Class for utility methods for generating a token corresponding to each macro-prop. Each method + * returns whether or not a token was written to the string builder. + * + * This needs to be a class, not a namespace, so it can be friended. + */ +class GeneratorHelpers { + public: + /** + * Main skeleton generator function. Appends the normalized skeleton for the MacroProps to the given + * StringBuilder. + * + * Internal: use the create() endpoint instead of this function. + */ + static void generateSkeleton(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + private: + static bool notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool unit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool perUnit(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool precision(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool roundingMode(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool grouping(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool integerWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool symbols(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool unitWidth(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool sign(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + + static bool scale(const MacroProps& macros, UnicodeString& sb, UErrorCode& status); + +}; + +/** + * Struct for null-checking. + * In Java, we can just check the object reference. In C++, we need a different method. + */ +struct SeenMacroProps { + bool notation = false; + bool unit = false; + bool perUnit = false; + bool precision = false; + bool roundingMode = false; + bool grouper = false; + bool padder = false; + bool integerWidth = false; + bool symbols = false; + bool unitWidth = false; + bool sign = false; + bool decimal = false; + bool scale = false; +}; + +} // namespace impl +} // namespace number +U_NAMESPACE_END + +#endif //__SOURCE_NUMBER_SKELETONS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_stringbuilder.cpp b/deps/icu-small/source/i18n/number_stringbuilder.cpp index 37159d7e53a60a..37770d11d51dfb 100644 --- a/deps/icu-small/source/i18n/number_stringbuilder.cpp +++ b/deps/icu-small/source/i18n/number_stringbuilder.cpp @@ -3,11 +3,10 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #include "number_stringbuilder.h" #include "unicode/utf16.h" -#include "uvectr32.h" using namespace icu; using namespace icu::number; @@ -337,6 +336,11 @@ UnicodeString NumberStringBuilder::toUnicodeString() const { return UnicodeString(getCharPtr() + fZero, fLength); } +const UnicodeString NumberStringBuilder::toTempUnicodeString() const { + // Readonly-alias constructor: + return UnicodeString(FALSE, getCharPtr() + fZero, fLength); +} + UnicodeString NumberStringBuilder::toDebugString() const { UnicodeString sb; sb.append(u"= UNUM_FIELD_COUNT) { status = U_ILLEGAL_ARGUMENT_ERROR; - return; + return FALSE; } auto field = static_cast(rawField); bool seenStart = false; int32_t fractionStart = -1; - for (int i = fZero; i <= fZero + fLength; i++) { + int32_t startIndex = fp.getEndIndex(); + for (int i = fZero + startIndex; i <= fZero + fLength; i++) { Field _field = UNUM_FIELD_COUNT; if (i < fZero + fLength) { _field = getFieldPtr()[i]; @@ -434,10 +439,10 @@ void NumberStringBuilder::populateFieldPosition(FieldPosition &fp, int32_t offse if (field == UNUM_INTEGER_FIELD && _field == UNUM_GROUPING_SEPARATOR_FIELD) { continue; } - fp.setEndIndex(i - fZero + offset); + fp.setEndIndex(i - fZero); break; } else if (!seenStart && field == _field) { - fp.setBeginIndex(i - fZero + offset); + fp.setBeginIndex(i - fZero); seenStart = true; } if (_field == UNUM_INTEGER_FIELD || _field == UNUM_DECIMAL_SEPARATOR_FIELD) { @@ -445,36 +450,28 @@ void NumberStringBuilder::populateFieldPosition(FieldPosition &fp, int32_t offse } } - // Backwards compatibility: FRACTION needs to start after INTEGER if empty - if (field == UNUM_FRACTION_FIELD && !seenStart) { - fp.setBeginIndex(fractionStart + offset); - fp.setEndIndex(fractionStart + offset); + // Backwards compatibility: FRACTION needs to start after INTEGER if empty. + // Do not return that a field was found, though, since there is not actually a fraction part. + if (field == UNUM_FRACTION_FIELD && !seenStart && fractionStart != -1) { + fp.setBeginIndex(fractionStart); + fp.setEndIndex(fractionStart); } -} -void NumberStringBuilder::populateFieldPositionIterator(FieldPositionIterator &fpi, UErrorCode &status) const { - // TODO: Set an initial capacity on uvec? - LocalPointer uvec(new UVector32(status)); - if (U_FAILURE(status)) { - return; - } + return seenStart; +} +void NumberStringBuilder::getAllFieldPositions(FieldPositionIteratorHandler& fpih, + UErrorCode& status) const { Field current = UNUM_FIELD_COUNT; int32_t currentStart = -1; for (int32_t i = 0; i < fLength; i++) { Field field = fieldAt(i); if (current == UNUM_INTEGER_FIELD && field == UNUM_GROUPING_SEPARATOR_FIELD) { // Special case: GROUPING_SEPARATOR counts as an INTEGER. - // Add the field, followed by the start index, followed by the end index to uvec. - uvec->addElement(UNUM_GROUPING_SEPARATOR_FIELD, status); - uvec->addElement(i, status); - uvec->addElement(i + 1, status); + fpih.addAttribute(UNUM_GROUPING_SEPARATOR_FIELD, i, i + 1); } else if (current != field) { if (current != UNUM_FIELD_COUNT) { - // Add the field, followed by the start index, followed by the end index to uvec. - uvec->addElement(current, status); - uvec->addElement(currentStart, status); - uvec->addElement(i, status); + fpih.addAttribute(current, currentStart, i); } current = field; currentStart = i; @@ -484,14 +481,8 @@ void NumberStringBuilder::populateFieldPositionIterator(FieldPositionIterator &f } } if (current != UNUM_FIELD_COUNT) { - // Add the field, followed by the start index, followed by the end index to uvec. - uvec->addElement(current, status); - uvec->addElement(currentStart, status); - uvec->addElement(fLength, status); + fpih.addAttribute(current, currentStart, fLength); } - - // Give uvec to the FieldPositionIterator, which adopts it. - fpi.setData(uvec.orphan(), status); } #endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_stringbuilder.h b/deps/icu-small/source/i18n/number_stringbuilder.h index a97cc9ca026ad0..cd8ce2f805e994 100644 --- a/deps/icu-small/source/i18n/number_stringbuilder.h +++ b/deps/icu-small/source/i18n/number_stringbuilder.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_STRINGBUILDER_H__ #define __NUMBER_STRINGBUILDER_H__ @@ -14,6 +14,7 @@ #include "cstring.h" #include "uassert.h" #include "number_types.h" +#include "fphdlimp.h" U_NAMESPACE_BEGIN namespace number { namespace impl { @@ -84,17 +85,26 @@ class U_I18N_API NumberStringBuilder : public UMemory { int32_t insert(int32_t index, const NumberStringBuilder &other, UErrorCode &status); + /** + * Gets a "safe" UnicodeString that can be used even after the NumberStringBuilder is destructed. + * */ UnicodeString toUnicodeString() const; + /** + * Gets an "unsafe" UnicodeString that is valid only as long as the NumberStringBuilder is alive and + * unchanged. Slightly faster than toUnicodeString(). + */ + const UnicodeString toTempUnicodeString() const; + UnicodeString toDebugString() const; const char16_t *chars() const; bool contentEquals(const NumberStringBuilder &other) const; - void populateFieldPosition(FieldPosition &fp, int32_t offset, UErrorCode &status) const; + bool nextFieldPosition(FieldPosition& fp, UErrorCode& status) const; - void populateFieldPositionIterator(FieldPositionIterator &fpi, UErrorCode &status) const; + void getAllFieldPositions(FieldPositionIteratorHandler& fpih, UErrorCode& status) const; private: bool fUsingHeap = false; diff --git a/deps/icu-small/source/i18n/number_types.h b/deps/icu-small/source/i18n/number_types.h index c01765e2cea6c6..57da72f8aa0ac1 100644 --- a/deps/icu-small/source/i18n/number_types.h +++ b/deps/icu-small/source/i18n/number_types.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_TYPES_H__ #define __NUMBER_TYPES_H__ @@ -15,9 +15,9 @@ #include "unicode/utf16.h" #include "uassert.h" #include "unicode/platform.h" +#include "unicode/uniset.h" -U_NAMESPACE_BEGIN -namespace number { +U_NAMESPACE_BEGIN namespace number { namespace impl { // Typedef several enums for brevity and for easier comparison to Java. @@ -39,9 +39,6 @@ static constexpr RoundingMode kDefaultMode = RoundingMode::UNUM_FOUND_HALFEVEN; // ICU4J Equivalent: Padder.FALLBACK_PADDING_STRING static constexpr char16_t kFallbackPaddingString[] = u" "; -// ICU4J Equivalent: NumberFormatterImpl.DEFAULT_CURRENCY -static constexpr char16_t kDefaultCurrency[] = u"XXX"; - // Forward declarations: class Modifier; @@ -87,35 +84,10 @@ enum AffixPatternType { }; enum CompactType { - TYPE_DECIMAL, - TYPE_CURRENCY + TYPE_DECIMAL, TYPE_CURRENCY }; -// TODO: Should this be moved somewhere else, maybe where other ICU classes can use it? -// Exported as U_I18N_API because it is a base class for other exported types -class U_I18N_API CharSequence { -public: - virtual ~CharSequence() = default; - - virtual int32_t length() const = 0; - - virtual char16_t charAt(int32_t index) const = 0; - - virtual UChar32 codePointAt(int32_t index) const { - // Default implementation; can be overridden with a more efficient version - char16_t leading = charAt(index); - if (U16_IS_LEAD(leading) && length() > index + 1) { - char16_t trailing = charAt(index + 1); - return U16_GET_SUPPLEMENTARY(leading, trailing); - } else { - return leading; - } - } - - virtual UnicodeString toUnicodeString() const = 0; -}; - class U_I18N_API AffixPatternProvider { public: static const int32_t AFFIX_PLURAL_MASK = 0xff; @@ -123,12 +95,20 @@ class U_I18N_API AffixPatternProvider { static const int32_t AFFIX_NEGATIVE_SUBPATTERN = 0x200; static const int32_t AFFIX_PADDING = 0x400; - virtual ~AffixPatternProvider() = default; + // Convenience compound flags + static const int32_t AFFIX_POS_PREFIX = AFFIX_PREFIX; + static const int32_t AFFIX_POS_SUFFIX = 0; + static const int32_t AFFIX_NEG_PREFIX = AFFIX_PREFIX | AFFIX_NEGATIVE_SUBPATTERN; + static const int32_t AFFIX_NEG_SUFFIX = AFFIX_NEGATIVE_SUBPATTERN; + + virtual ~AffixPatternProvider(); virtual char16_t charAt(int flags, int i) const = 0; virtual int length(int flags) const = 0; + virtual UnicodeString getString(int flags) const = 0; + virtual bool hasCurrencySign() const = 0; virtual bool positiveHasPlusSign() const = 0; @@ -137,7 +117,7 @@ class U_I18N_API AffixPatternProvider { virtual bool negativeHasMinusSign() const = 0; - virtual bool containsSymbolType(AffixPatternType, UErrorCode &) const = 0; + virtual bool containsSymbolType(AffixPatternType, UErrorCode&) const = 0; /** * True if the pattern has a number placeholder like "0" or "#,##0.00"; false if the pattern does not @@ -159,7 +139,7 @@ class U_I18N_API AffixPatternProvider { */ class U_I18N_API Modifier { public: - virtual ~Modifier() = default; + virtual ~Modifier(); /** * Apply this Modifier to the string builder. @@ -173,8 +153,8 @@ class U_I18N_API Modifier { * formatted. * @return The number of characters (UTF-16 code units) that were added to the string builder. */ - virtual int32_t - apply(NumberStringBuilder &output, int leftIndex, int rightIndex, UErrorCode &status) const = 0; + virtual int32_t apply(NumberStringBuilder& output, int leftIndex, int rightIndex, + UErrorCode& status) const = 0; /** * Gets the length of the prefix. This information can be used in combination with {@link #apply} to extract the @@ -187,7 +167,7 @@ class U_I18N_API Modifier { /** * Returns the number of code points in the modifier, prefix plus suffix. */ - virtual int32_t getCodePointCount(UErrorCode &status) const = 0; + virtual int32_t getCodePointCount(UErrorCode& status) const = 0; /** * Whether this modifier is strong. If a modifier is strong, it should always be applied immediately and not allowed @@ -219,7 +199,7 @@ class U_I18N_API Modifier { */ class U_I18N_API MicroPropsGenerator { public: - virtual ~MicroPropsGenerator() = default; + virtual ~MicroPropsGenerator(); /** * Considers the given {@link DecimalQuantity}, optionally mutates it, and returns a {@link MicroProps}. @@ -230,7 +210,8 @@ class U_I18N_API MicroPropsGenerator { * The MicroProps instance to populate. * @return A MicroProps instance resolved for the quantity. */ - virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros, UErrorCode& status) const = 0; + virtual void processQuantity(DecimalQuantity& quantity, MicroProps& micros, + UErrorCode& status) const = 0; }; /** @@ -238,7 +219,7 @@ class U_I18N_API MicroPropsGenerator { */ class MultiplierProducer { public: - virtual ~MultiplierProducer() = default; + virtual ~MultiplierProducer(); /** * Maps a magnitude to a multiplier in powers of ten. For example, in compact notation in English, a magnitude of 5 @@ -255,24 +236,31 @@ class MultiplierProducer { template class U_I18N_API NullableValue { public: - NullableValue() : fNull(true) {} + NullableValue() + : fNull(true) {} - NullableValue(const NullableValue &other) = default; + NullableValue(const NullableValue& other) = default; - explicit NullableValue(const T &other) { + explicit NullableValue(const T& other) { fValue = other; fNull = false; } - NullableValue &operator=(const NullableValue &other) = default; + NullableValue& operator=(const NullableValue& other) { + fNull = other.fNull; + if (!fNull) { + fValue = other.fValue; + } + return *this; + } - NullableValue &operator=(const T &other) { + NullableValue& operator=(const T& other) { fValue = other; fNull = false; return *this; } - bool operator==(const NullableValue &other) const { + bool operator==(const NullableValue& other) const { // "fValue == other.fValue" returns UBool, not bool (causes compiler warnings) return fNull ? other.fNull : (other.fNull ? false : static_cast(fValue == other.fValue)); } @@ -286,13 +274,21 @@ class U_I18N_API NullableValue { return fNull; } - T get(UErrorCode &status) const { + T get(UErrorCode& status) const { if (fNull) { status = U_UNDEFINED_VARIABLE; } return fValue; } + T getNoError() const { + return fValue; + } + + T getOrDefault(T defaultValue) const { + return fNull ? defaultValue : fValue; + } + private: bool fNull; T fValue; diff --git a/deps/icu-small/source/i18n/number_utils.cpp b/deps/icu-small/source/i18n/number_utils.cpp new file mode 100644 index 00000000000000..c79d2de9fa3f55 --- /dev/null +++ b/deps/icu-small/source/i18n/number_utils.cpp @@ -0,0 +1,253 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include +#include +#include "number_decnum.h" +#include "number_types.h" +#include "number_utils.h" +#include "charstr.h" +#include "decContext.h" +#include "decNumber.h" +#include "double-conversion.h" +#include "fphdlimp.h" +#include "uresimp.h" +#include "ureslocs.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + +using icu::double_conversion::DoubleToStringConverter; + + +namespace { + +const char16_t* +doGetPattern(UResourceBundle* res, const char* nsName, const char* patternKey, UErrorCode& publicStatus, + UErrorCode& localStatus) { + // Construct the path into the resource bundle + CharString key; + key.append("NumberElements/", publicStatus); + key.append(nsName, publicStatus); + key.append("/patterns/", publicStatus); + key.append(patternKey, publicStatus); + if (U_FAILURE(publicStatus)) { + return u""; + } + return ures_getStringByKeyWithFallback(res, key.data(), nullptr, &localStatus); +} + +} + + +const char16_t* utils::getPatternForStyle(const Locale& locale, const char* nsName, CldrPatternStyle style, + UErrorCode& status) { + const char* patternKey; + switch (style) { + case CLDR_PATTERN_STYLE_DECIMAL: + patternKey = "decimalFormat"; + break; + case CLDR_PATTERN_STYLE_CURRENCY: + patternKey = "currencyFormat"; + break; + case CLDR_PATTERN_STYLE_ACCOUNTING: + patternKey = "accountingFormat"; + break; + case CLDR_PATTERN_STYLE_PERCENT: + patternKey = "percentFormat"; + break; + case CLDR_PATTERN_STYLE_SCIENTIFIC: + patternKey = "scientificFormat"; + break; + default: + patternKey = "decimalFormat"; // silence compiler error + U_ASSERT(false); + } + LocalUResourceBundlePointer res(ures_open(nullptr, locale.getName(), &status)); + if (U_FAILURE(status)) { return u""; } + + // Attempt to get the pattern with the native numbering system. + UErrorCode localStatus = U_ZERO_ERROR; + const char16_t* pattern; + pattern = doGetPattern(res.getAlias(), nsName, patternKey, status, localStatus); + if (U_FAILURE(status)) { return u""; } + + // Fall back to latn if native numbering system does not have the right pattern + if (U_FAILURE(localStatus) && uprv_strcmp("latn", nsName) != 0) { + localStatus = U_ZERO_ERROR; + pattern = doGetPattern(res.getAlias(), "latn", patternKey, status, localStatus); + if (U_FAILURE(status)) { return u""; } + } + + return pattern; +} + + +DecNum::DecNum() { + uprv_decContextDefault(&fContext, DEC_INIT_BASE); + uprv_decContextSetRounding(&fContext, DEC_ROUND_HALF_EVEN); + fContext.traps = 0; // no traps, thank you (what does this even mean?) +} + +DecNum::DecNum(const DecNum& other, UErrorCode& status) + : fContext(other.fContext) { + // Allocate memory for the new DecNum. + U_ASSERT(fContext.digits == other.fData.getCapacity()); + if (fContext.digits > kDefaultDigits) { + void* p = fData.resize(fContext.digits, 0); + if (p == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + } + + // Copy the data from the old DecNum to the new one. + uprv_memcpy(fData.getAlias(), other.fData.getAlias(), sizeof(decNumber)); + uprv_memcpy(fData.getArrayStart(), + other.fData.getArrayStart(), + other.fData.getArrayLimit() - other.fData.getArrayStart()); +} + +void DecNum::setTo(StringPiece str, UErrorCode& status) { + // We need NUL-terminated for decNumber; CharString guarantees this, but not StringPiece. + CharString cstr(str, status); + if (U_FAILURE(status)) { return; } + _setTo(cstr.data(), str.length(), status); +} + +void DecNum::setTo(const char* str, UErrorCode& status) { + _setTo(str, static_cast(uprv_strlen(str)), status); +} + +void DecNum::setTo(double d, UErrorCode& status) { + // Need to check for NaN and Infinity before going into DoubleToStringConverter + if (std::isnan(d) != 0 || std::isfinite(d) == 0) { + status = U_UNSUPPORTED_ERROR; + return; + } + + // First convert from double to string, then string to DecNum. + // Allocate enough room for: all digits, "E-324", and NUL-terminator. + char buffer[DoubleToStringConverter::kBase10MaximalLength + 6]; + bool sign; // unused; always positive + int32_t length; + int32_t point; + DoubleToStringConverter::DoubleToAscii( + d, + DoubleToStringConverter::DtoaMode::SHORTEST, + 0, + buffer, + sizeof(buffer), + &sign, + &length, + &point + ); + + // Read initial result as a string. + _setTo(buffer, length, status); + + // Set exponent and bitmask. Note that DoubleToStringConverter does not do negatives. + fData.getAlias()->exponent += point - length; + fData.getAlias()->bits |= static_cast(std::signbit(d) ? DECNEG : 0); +} + +void DecNum::_setTo(const char* str, int32_t maxDigits, UErrorCode& status) { + if (maxDigits > kDefaultDigits) { + fData.resize(maxDigits, 0); + fContext.digits = maxDigits; + } else { + fContext.digits = kDefaultDigits; + } + + static_assert(DECDPUN == 1, "Assumes that DECDPUN is set to 1"); + uprv_decNumberFromString(fData.getAlias(), str, &fContext); + + // Check for invalid syntax and set the corresponding error code. + if ((fContext.status & DEC_Conversion_syntax) != 0) { + status = U_DECIMAL_NUMBER_SYNTAX_ERROR; + return; + } else if (fContext.status != 0) { + // Not a syntax error, but some other error, like an exponent that is too large. + status = U_UNSUPPORTED_ERROR; + return; + } + + // For consistency with Java BigDecimal, no support for DecNum that is NaN or Infinity! + if (decNumberIsSpecial(fData.getAlias())) { + status = U_UNSUPPORTED_ERROR; + return; + } +} + +void +DecNum::setTo(const uint8_t* bcd, int32_t length, int32_t scale, bool isNegative, UErrorCode& status) { + if (length > kDefaultDigits) { + fData.resize(length, 0); + fContext.digits = length; + } else { + fContext.digits = kDefaultDigits; + } + + // "digits is of type int32_t, and must have a value in the range 1 through 999,999,999." + if (length < 1 || length > 999999999) { + // Too large for decNumber + status = U_UNSUPPORTED_ERROR; + return; + } + // "The exponent field holds the exponent of the number. Its range is limited by the requirement that + // "the range of the adjusted exponent of the number be balanced and fit within a whole number of + // "decimal digits (in this implementation, be –999,999,999 through +999,999,999). The adjusted + // "exponent is the exponent that would result if the number were expressed with a single digit before + // "the decimal point, and is therefore given by exponent+digits-1." + if (scale > 999999999 - length + 1 || scale < -999999999 - length + 1) { + // Too large for decNumber + status = U_UNSUPPORTED_ERROR; + return; + } + + fData.getAlias()->digits = length; + fData.getAlias()->exponent = scale; + fData.getAlias()->bits = static_cast(isNegative ? DECNEG : 0); + uprv_decNumberSetBCD(fData, bcd, static_cast(length)); + if (fContext.status != 0) { + // Some error occurred while constructing the decNumber. + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +void DecNum::normalize() { + uprv_decNumberReduce(fData, fData, &fContext); +} + +void DecNum::multiplyBy(const DecNum& rhs, UErrorCode& status) { + uprv_decNumberMultiply(fData, fData, rhs.fData, &fContext); + if (fContext.status != 0) { + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +void DecNum::divideBy(const DecNum& rhs, UErrorCode& status) { + uprv_decNumberDivide(fData, fData, rhs.fData, &fContext); + if (fContext.status != 0) { + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +bool DecNum::isNegative() const { + return decNumberIsNegative(fData.getAlias()); +} + +bool DecNum::isZero() const { + return decNumberIsZero(fData.getAlias()); +} + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/number_utils.h b/deps/icu-small/source/i18n/number_utils.h index 3a408d6007a2cd..c367166009c0dc 100644 --- a/deps/icu-small/source/i18n/number_utils.h +++ b/deps/icu-small/source/i18n/number_utils.h @@ -3,7 +3,7 @@ #include "unicode/utypes.h" -#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT +#if !UCONFIG_NO_FORMATTING #ifndef __NUMBER_UTILS_H__ #define __NUMBER_UTILS_H__ @@ -13,116 +13,80 @@ #include "number_scientific.h" #include "number_patternstring.h" #include "number_modifiers.h" +#include "number_multiplier.h" +#include "number_roundingutils.h" +#include "decNumber.h" +#include "charstr.h" -U_NAMESPACE_BEGIN namespace number { -namespace impl { +U_NAMESPACE_BEGIN -class UnicodeStringCharSequence : public CharSequence { - public: - explicit UnicodeStringCharSequence(const UnicodeString &other) { - fStr = other; - } +namespace number { +namespace impl { - ~UnicodeStringCharSequence() U_OVERRIDE = default; +enum CldrPatternStyle { + CLDR_PATTERN_STYLE_DECIMAL, + CLDR_PATTERN_STYLE_CURRENCY, + CLDR_PATTERN_STYLE_ACCOUNTING, + CLDR_PATTERN_STYLE_PERCENT, + CLDR_PATTERN_STYLE_SCIENTIFIC, + CLDR_PATTERN_STYLE_COUNT, +}; - int32_t length() const U_OVERRIDE { - return fStr.length(); - } +// Namespace for naked functions +namespace utils { - char16_t charAt(int32_t index) const U_OVERRIDE { - return fStr.charAt(index); +inline int32_t insertDigitFromSymbols(NumberStringBuilder& output, int32_t index, int8_t digit, + const DecimalFormatSymbols& symbols, Field field, + UErrorCode& status) { + if (symbols.getCodePointZero() != -1) { + return output.insertCodePoint(index, symbols.getCodePointZero() + digit, field, status); } + return output.insert(index, symbols.getConstDigitSymbol(digit), field, status); +} - UChar32 codePointAt(int32_t index) const U_OVERRIDE { - return fStr.char32At(index); - } +inline bool unitIsCurrency(const MeasureUnit& unit) { + return uprv_strcmp("currency", unit.getType()) == 0; +} - UnicodeString toUnicodeString() const U_OVERRIDE { - // Allocate a UnicodeString of the correct length - UnicodeString output(length(), 0, -1); - for (int32_t i = 0; i < length(); i++) { - output.append(charAt(i)); - } - return output; - } +inline bool unitIsNoUnit(const MeasureUnit& unit) { + return uprv_strcmp("none", unit.getType()) == 0; +} - private: - UnicodeString fStr; -}; +inline bool unitIsPercent(const MeasureUnit& unit) { + return uprv_strcmp("percent", unit.getSubtype()) == 0; +} -struct MicroProps : public MicroPropsGenerator { - - // NOTE: All of these fields are properly initialized in NumberFormatterImpl. - Rounder rounding; - Grouper grouping; - Padder padding; - IntegerWidth integerWidth; - UNumberSignDisplay sign; - UNumberDecimalSeparatorDisplay decimal; - bool useCurrency; - - // Note: This struct has no direct ownership of the following pointers. - const DecimalFormatSymbols *symbols; - const Modifier *modOuter; - const Modifier *modMiddle; - const Modifier *modInner; - - // The following "helper" fields may optionally be used during the MicroPropsGenerator. - // They live here to retain memory. - struct { - ScientificModifier scientificModifier; - EmptyModifier emptyWeakModifier{false}; - EmptyModifier emptyStrongModifier{true}; - } helpers; - - - MicroProps() = default; - - MicroProps(const MicroProps &other) = default; - - MicroProps &operator=(const MicroProps &other) = default; - - void processQuantity(DecimalQuantity &, MicroProps µs, UErrorCode &status) const U_OVERRIDE { - (void)status; - if (this == µs) { - // Unsafe path: no need to perform a copy. - U_ASSERT(!exhausted); - micros.exhausted = true; - U_ASSERT(exhausted); - } else { - // Safe path: copy self into the output micros. - micros = *this; - } - } +inline bool unitIsPermille(const MeasureUnit& unit) { + return uprv_strcmp("permille", unit.getSubtype()) == 0; +} - private: - // Internal fields: - bool exhausted = false; -}; +// NOTE: In Java, this method is in NumberFormat.java +const char16_t* +getPatternForStyle(const Locale& locale, const char* nsName, CldrPatternStyle style, UErrorCode& status); /** - * This struct provides the result of the number formatting pipeline to FormattedNumber. + * Computes the plural form for this number based on the specified set of rules. * - * The DecimalQuantity is not currently being used by FormattedNumber, but at some point it could be used - * to add a toDecNumber() or similar method. + * @param rules A {@link PluralRules} object representing the set of rules. + * @return The {@link StandardPlural} according to the PluralRules. If the plural form is not in + * the set of standard plurals, {@link StandardPlural#OTHER} is returned instead. */ -struct NumberFormatterResults : public UMemory { - DecimalQuantity quantity; - NumberStringBuilder string; -}; - -inline const UnicodeString getDigitFromSymbols(int8_t digit, const DecimalFormatSymbols &symbols) { - // TODO: Implement DecimalFormatSymbols.getCodePointZero()? - if (digit == 0) { - return symbols.getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kZeroDigitSymbol); +inline StandardPlural::Form getStandardPlural(const PluralRules *rules, + const IFixedDecimal &fdec) { + if (rules == nullptr) { + // Fail gracefully if the user didn't provide a PluralRules + return StandardPlural::Form::OTHER; } else { - return symbols.getSymbol(static_cast( - DecimalFormatSymbols::ENumberFormatSymbol::kOneDigitSymbol + digit - 1)); + UnicodeString ruleString = rules->select(fdec); + return StandardPlural::orOtherFromString(ruleString); } } +} // namespace utils + } // namespace impl } // namespace number + U_NAMESPACE_END #endif //__NUMBER_UTILS_H__ diff --git a/deps/icu-small/source/i18n/number_utypes.h b/deps/icu-small/source/i18n/number_utypes.h new file mode 100644 index 00000000000000..48bfce1969754a --- /dev/null +++ b/deps/icu-small/source/i18n/number_utypes.h @@ -0,0 +1,79 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __SOURCE_NUMBER_UTYPES_H__ +#define __SOURCE_NUMBER_UTYPES_H__ + +#include "unicode/numberformatter.h" +#include "number_types.h" +#include "number_decimalquantity.h" +#include "number_stringbuilder.h" + +U_NAMESPACE_BEGIN namespace number { +namespace impl { + + +/** + * Implementation class for UNumberFormatter with a magic number for safety. + * + * Wraps a LocalizedNumberFormatter by value. + */ +struct UNumberFormatterData : public UMemory { + // The magic number to identify incoming objects. + // Reads in ASCII as "NFR" (NumberFormatteR with room at the end) + static constexpr int32_t kMagic = 0x4E465200; + + // Data members: + int32_t fMagic = kMagic; + LocalizedNumberFormatter fFormatter; + + /** Convert from UNumberFormatter -> UNumberFormatterData. */ + static UNumberFormatterData* validate(UNumberFormatter* input, UErrorCode& status); + + /** Convert from UNumberFormatter -> UNumberFormatterData (const version). */ + static const UNumberFormatterData* validate(const UNumberFormatter* input, UErrorCode& status); + + /** Convert from UNumberFormatterData -> UNumberFormatter. */ + UNumberFormatter* exportForC(); +}; + + +/** + * Implementation class for UFormattedNumber with magic number for safety. + * + * This struct is also held internally by the C++ version FormattedNumber since the member types are not + * declared in the public header file. + * + * The DecimalQuantity is not currently being used by FormattedNumber, but at some point it could be used + * to add a toDecNumber() or similar method. + */ +struct UFormattedNumberData : public UMemory { + // The magic number to identify incoming objects. + // Reads in ASCII as "FDN" (FormatteDNumber with room at the end) + static constexpr int32_t kMagic = 0x46444E00; + + // Data members: + int32_t fMagic = kMagic; + DecimalQuantity quantity; + NumberStringBuilder string; + + /** Convert from UFormattedNumber -> UFormattedNumberData. */ + static UFormattedNumberData* validate(UFormattedNumber* input, UErrorCode& status); + + /** Convert from UFormattedNumber -> UFormattedNumberData (const version). */ + static const UFormattedNumberData* validate(const UFormattedNumber* input, UErrorCode& status); + + /** Convert from UFormattedNumberData -> UFormattedNumber. */ + UFormattedNumber* exportForC(); +}; + + +} // namespace impl +} // namespace number +U_NAMESPACE_END + +#endif //__SOURCE_NUMBER_UTYPES_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numfmt.cpp b/deps/icu-small/source/i18n/numfmt.cpp index fee3bed8bfb352..13f23131b1851b 100644 --- a/deps/icu-small/source/i18n/numfmt.cpp +++ b/deps/icu-small/source/i18n/numfmt.cpp @@ -51,10 +51,11 @@ #include "uassert.h" #include "umutex.h" #include "mutex.h" -#include "digitlst.h" #include #include "sharednumberformat.h" #include "unifiedcache.h" +#include "number_decimalquantity.h" +#include "number_utils.h" //#define FMT_DEBUG @@ -129,31 +130,28 @@ static const UChar * const gLastResortNumberPatterns[UNUM_FORMAT_STYLE_COUNT] = // Keys used for accessing resource bundles -static const char *gNumberElements = "NumberElements"; -static const char *gLatn = "latn"; -static const char *gPatterns = "patterns"; -static const char *gFormatKeys[UNUM_FORMAT_STYLE_COUNT] = { - NULL, // UNUM_PATTERN_DECIMAL - "decimalFormat", // UNUM_DECIMAL - "currencyFormat", // UNUM_CURRENCY - "percentFormat", // UNUM_PERCENT - "scientificFormat", // UNUM_SCIENTIFIC - NULL, // UNUM_SPELLOUT - NULL, // UNUM_ORDINAL - NULL, // UNUM_DURATION - NULL, // UNUM_NUMBERING_SYSTEM - NULL, // UNUM_PATTERN_RULEBASED +static const icu::number::impl::CldrPatternStyle gFormatCldrStyles[UNUM_FORMAT_STYLE_COUNT] = { + /* NULL */ icu::number::impl::CLDR_PATTERN_STYLE_COUNT, // UNUM_PATTERN_DECIMAL + icu::number::impl::CLDR_PATTERN_STYLE_DECIMAL, // UNUM_DECIMAL + icu::number::impl::CLDR_PATTERN_STYLE_CURRENCY, // UNUM_CURRENCY + icu::number::impl::CLDR_PATTERN_STYLE_PERCENT, // UNUM_PERCENT + icu::number::impl::CLDR_PATTERN_STYLE_SCIENTIFIC, // UNUM_SCIENTIFIC + /* NULL */ icu::number::impl::CLDR_PATTERN_STYLE_COUNT, // UNUM_SPELLOUT + /* NULL */ icu::number::impl::CLDR_PATTERN_STYLE_COUNT, // UNUM_ORDINAL + /* NULL */ icu::number::impl::CLDR_PATTERN_STYLE_COUNT, // UNUM_DURATION + /* NULL */ icu::number::impl::CLDR_PATTERN_STYLE_COUNT, // UNUM_NUMBERING_SYSTEM + /* NULL */ icu::number::impl::CLDR_PATTERN_STYLE_COUNT, // UNUM_PATTERN_RULEBASED // For UNUM_CURRENCY_ISO and UNUM_CURRENCY_PLURAL, // the pattern is the same as the pattern of UNUM_CURRENCY // except for replacing the single currency sign with // double currency sign or triple currency sign. - "currencyFormat", // UNUM_CURRENCY_ISO - "currencyFormat", // UNUM_CURRENCY_PLURAL - "accountingFormat", // UNUM_CURRENCY_ACCOUNTING - "currencyFormat", // UNUM_CASH_CURRENCY - NULL, // UNUM_DECIMAL_COMPACT_SHORT - NULL, // UNUM_DECIMAL_COMPACT_LONG - "currencyFormat", // UNUM_CURRENCY_STANDARD + icu::number::impl::CLDR_PATTERN_STYLE_CURRENCY, // UNUM_CURRENCY_ISO + icu::number::impl::CLDR_PATTERN_STYLE_CURRENCY, // UNUM_CURRENCY_PLURAL + icu::number::impl::CLDR_PATTERN_STYLE_ACCOUNTING, // UNUM_CURRENCY_ACCOUNTING + icu::number::impl::CLDR_PATTERN_STYLE_CURRENCY, // UNUM_CASH_CURRENCY + /* NULL */ icu::number::impl::CLDR_PATTERN_STYLE_COUNT, // UNUM_DECIMAL_COMPACT_SHORT + /* NULL */ icu::number::impl::CLDR_PATTERN_STYLE_COUNT, // UNUM_DECIMAL_COMPACT_LONG + icu::number::impl::CLDR_PATTERN_STYLE_CURRENCY, // UNUM_CURRENCY_STANDARD }; // Static hashtable cache of NumberingSystem objects used by NumberFormat @@ -524,7 +522,7 @@ ArgExtractor::ArgExtractor(const NumberFormat& /*nf*/, const Formattable& obj, U ArgExtractor::~ArgExtractor() { } -UnicodeString& NumberFormat::format(const DigitList &number, +UnicodeString& NumberFormat::format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const { @@ -534,7 +532,7 @@ UnicodeString& NumberFormat::format(const DigitList &number, if (U_FAILURE(status)) { return appendTo; } - double dnum = number.getDouble(); + double dnum = number.toDouble(); format(dnum, appendTo, posIter, status); return appendTo; } @@ -542,7 +540,7 @@ UnicodeString& NumberFormat::format(const DigitList &number, UnicodeString& -NumberFormat::format(const DigitList &number, +NumberFormat::format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode &status) const { @@ -552,7 +550,7 @@ NumberFormat::format(const DigitList &number, if (U_FAILURE(status)) { return appendTo; } - double dnum = number.getDouble(); + double dnum = number.toDouble(); format(dnum, appendTo, pos, status); return appendTo; } @@ -578,7 +576,7 @@ NumberFormat::format(const Formattable& obj, return cloneFmt->format(*n, appendTo, pos, status); } - if (n->isNumeric() && n->getDigitList() != NULL) { + if (n->isNumeric() && n->getDecimalQuantity() != NULL) { // Decimal Number. We will have a DigitList available if the value was // set to a decimal number, or if the value originated with a parse. // @@ -587,17 +585,17 @@ NumberFormat::format(const Formattable& obj, // know about DigitList to continue to operate as they had. // // DecimalFormat overrides the DigitList formatting functions. - format(*n->getDigitList(), appendTo, pos, status); + format(*n->getDecimalQuantity(), appendTo, pos, status); } else { switch (n->getType()) { case Formattable::kDouble: - format(n->getDouble(), appendTo, pos); + format(n->getDouble(), appendTo, pos, status); break; case Formattable::kLong: - format(n->getLong(), appendTo, pos); + format(n->getLong(), appendTo, pos, status); break; case Formattable::kInt64: - format(n->getInt64(), appendTo, pos); + format(n->getInt64(), appendTo, pos, status); break; default: status = U_INVALID_FORMAT_ERROR; @@ -633,9 +631,9 @@ NumberFormat::format(const Formattable& obj, return cloneFmt->format(*n, appendTo, posIter, status); } - if (n->isNumeric() && n->getDigitList() != NULL) { + if (n->isNumeric() && n->getDecimalQuantity() != NULL) { // Decimal Number - format(*n->getDigitList(), appendTo, posIter, status); + format(*n->getDecimalQuantity(), appendTo, posIter, status); } else { switch (n->getType()) { case Formattable::kDouble: @@ -1400,27 +1398,13 @@ NumberFormat::makeInstance(const Locale& desiredLocale, return NULL; } - UResourceBundle *resource = ownedResource.orphan(); - UResourceBundle *numElements = ures_getByKeyWithFallback(resource, gNumberElements, NULL, &status); - resource = ures_getByKeyWithFallback(numElements, ns->getName(), resource, &status); - resource = ures_getByKeyWithFallback(resource, gPatterns, resource, &status); - ownedResource.adoptInstead(resource); - - int32_t patLen = 0; - const UChar *patResStr = ures_getStringByKeyWithFallback(resource, gFormatKeys[style], &patLen, &status); - - // Didn't find a pattern specific to the numbering system, so fall back to "latn" - if ( status == U_MISSING_RESOURCE_ERROR && uprv_strcmp(gLatn,ns->getName())) { - status = U_ZERO_ERROR; - resource = ures_getByKeyWithFallback(numElements, gLatn, resource, &status); - resource = ures_getByKeyWithFallback(resource, gPatterns, resource, &status); - patResStr = ures_getStringByKeyWithFallback(resource, gFormatKeys[style], &patLen, &status); - } - - ures_close(numElements); - - // Creates the specified decimal format style of the desired locale. - pattern.setTo(TRUE, patResStr, patLen); + // Load the pattern from data using the common library function + const char16_t* patternPtr = number::impl::utils::getPatternForStyle( + desiredLocale, + ns->getName(), + gFormatCldrStyles[style], + status); + pattern = UnicodeString(TRUE, patternPtr, -1); } if (U_FAILURE(status)) { return NULL; diff --git a/deps/icu-small/source/i18n/numparse_affixes.cpp b/deps/icu-small/source/i18n/numparse_affixes.cpp new file mode 100644 index 00000000000000..c30d241693c8bf --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_affixes.cpp @@ -0,0 +1,470 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_affixes.h" +#include "numparse_utils.h" +#include "number_utils.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; +using namespace icu::number; +using namespace icu::number::impl; + + +namespace { + +/** + * Helper method to return whether the given AffixPatternMatcher equals the given pattern string. + * Either both arguments must be null or the pattern string inside the AffixPatternMatcher must equal + * the given pattern string. + */ +static bool matched(const AffixPatternMatcher* affix, const UnicodeString& patternString) { + return (affix == nullptr && patternString.isBogus()) || + (affix != nullptr && affix->getPattern() == patternString); +} + +/** + * Helper method to return the length of the given AffixPatternMatcher. Returns 0 for null. + */ +static int32_t length(const AffixPatternMatcher* matcher) { + return matcher == nullptr ? 0 : matcher->getPattern().length(); +} + +/** + * Helper method to return whether (1) both lhs and rhs are null/invalid, or (2) if they are both + * valid, whether they are equal according to operator==. Similar to Java Objects.equals() + */ +static bool equals(const AffixPatternMatcher* lhs, const AffixPatternMatcher* rhs) { + if (lhs == nullptr && rhs == nullptr) { + return true; + } + if (lhs == nullptr || rhs == nullptr) { + return false; + } + return *lhs == *rhs; +} + +} + + +AffixPatternMatcherBuilder::AffixPatternMatcherBuilder(const UnicodeString& pattern, + AffixTokenMatcherWarehouse& warehouse, + IgnorablesMatcher* ignorables) + : fMatchersLen(0), + fLastTypeOrCp(0), + fPattern(pattern), + fWarehouse(warehouse), + fIgnorables(ignorables) {} + +void AffixPatternMatcherBuilder::consumeToken(AffixPatternType type, UChar32 cp, UErrorCode& status) { + // This is called by AffixUtils.iterateWithConsumer() for each token. + + // Add an ignorables matcher between tokens except between two literals, and don't put two + // ignorables matchers in a row. + if (fIgnorables != nullptr && fMatchersLen > 0 && + (fLastTypeOrCp < 0 || !fIgnorables->getSet()->contains(fLastTypeOrCp))) { + addMatcher(*fIgnorables); + } + + if (type != TYPE_CODEPOINT) { + // Case 1: the token is a symbol. + switch (type) { + case TYPE_MINUS_SIGN: + addMatcher(fWarehouse.minusSign()); + break; + case TYPE_PLUS_SIGN: + addMatcher(fWarehouse.plusSign()); + break; + case TYPE_PERCENT: + addMatcher(fWarehouse.percent()); + break; + case TYPE_PERMILLE: + addMatcher(fWarehouse.permille()); + break; + case TYPE_CURRENCY_SINGLE: + case TYPE_CURRENCY_DOUBLE: + case TYPE_CURRENCY_TRIPLE: + case TYPE_CURRENCY_QUAD: + case TYPE_CURRENCY_QUINT: + // All currency symbols use the same matcher + addMatcher(fWarehouse.currency(status)); + break; + default: + U_ASSERT(FALSE); + } + + } else if (fIgnorables != nullptr && fIgnorables->getSet()->contains(cp)) { + // Case 2: the token is an ignorable literal. + // No action necessary: the ignorables matcher has already been added. + + } else { + // Case 3: the token is a non-ignorable literal. + addMatcher(fWarehouse.nextCodePointMatcher(cp)); + } + fLastTypeOrCp = type != TYPE_CODEPOINT ? type : cp; +} + +void AffixPatternMatcherBuilder::addMatcher(NumberParseMatcher& matcher) { + if (fMatchersLen >= fMatchers.getCapacity()) { + fMatchers.resize(fMatchersLen * 2, fMatchersLen); + } + fMatchers[fMatchersLen++] = &matcher; +} + +AffixPatternMatcher AffixPatternMatcherBuilder::build() { + return AffixPatternMatcher(fMatchers, fMatchersLen, fPattern); +} + + +CodePointMatcherWarehouse::CodePointMatcherWarehouse() + : codePointCount(0), codePointNumBatches(0) {} + +CodePointMatcherWarehouse::~CodePointMatcherWarehouse() { + // Delete the variable number of batches of code point matchers + for (int32_t i = 0; i < codePointNumBatches; i++) { + delete[] codePointsOverflow[i]; + } +} + +CodePointMatcherWarehouse::CodePointMatcherWarehouse(CodePointMatcherWarehouse&& src) U_NOEXCEPT + : codePoints(std::move(src.codePoints)), + codePointsOverflow(std::move(src.codePointsOverflow)), + codePointCount(src.codePointCount), + codePointNumBatches(src.codePointNumBatches) {} + +CodePointMatcherWarehouse& +CodePointMatcherWarehouse::operator=(CodePointMatcherWarehouse&& src) U_NOEXCEPT { + codePoints = std::move(src.codePoints); + codePointsOverflow = std::move(src.codePointsOverflow); + codePointCount = src.codePointCount; + codePointNumBatches = src.codePointNumBatches; + return *this; +} + +NumberParseMatcher& CodePointMatcherWarehouse::nextCodePointMatcher(UChar32 cp) { + if (codePointCount < CODE_POINT_STACK_CAPACITY) { + return codePoints[codePointCount++] = {cp}; + } + int32_t totalCapacity = CODE_POINT_STACK_CAPACITY + codePointNumBatches * CODE_POINT_BATCH_SIZE; + if (codePointCount >= totalCapacity) { + // Need a new batch + auto* nextBatch = new CodePointMatcher[CODE_POINT_BATCH_SIZE]; + if (codePointNumBatches >= codePointsOverflow.getCapacity()) { + // Need more room for storing pointers to batches + codePointsOverflow.resize(codePointNumBatches * 2, codePointNumBatches); + } + codePointsOverflow[codePointNumBatches++] = nextBatch; + } + return codePointsOverflow[codePointNumBatches - 1][(codePointCount++ - CODE_POINT_STACK_CAPACITY) % + CODE_POINT_BATCH_SIZE] = {cp}; +} + + +AffixTokenMatcherWarehouse::AffixTokenMatcherWarehouse(const AffixTokenMatcherSetupData* setupData) + : fSetupData(setupData) {} + +NumberParseMatcher& AffixTokenMatcherWarehouse::minusSign() { + return fMinusSign = {fSetupData->dfs, true}; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::plusSign() { + return fPlusSign = {fSetupData->dfs, true}; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::percent() { + return fPercent = {fSetupData->dfs}; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::permille() { + return fPermille = {fSetupData->dfs}; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::currency(UErrorCode& status) { + return fCurrency = {fSetupData->currencySymbols, fSetupData->dfs, fSetupData->parseFlags, status}; +} + +IgnorablesMatcher& AffixTokenMatcherWarehouse::ignorables() { + return fSetupData->ignorables; +} + +NumberParseMatcher& AffixTokenMatcherWarehouse::nextCodePointMatcher(UChar32 cp) { + return fCodePoints.nextCodePointMatcher(cp); +} + + +CodePointMatcher::CodePointMatcher(UChar32 cp) + : fCp(cp) {} + +bool CodePointMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { + if (segment.startsWith(fCp)) { + segment.adjustOffsetByCodePoint(); + result.setCharsConsumed(segment); + } + return false; +} + +bool CodePointMatcher::smokeTest(const StringSegment& segment) const { + return segment.startsWith(fCp); +} + +UnicodeString CodePointMatcher::toString() const { + return u""; +} + + +AffixPatternMatcher AffixPatternMatcher::fromAffixPattern(const UnicodeString& affixPattern, + AffixTokenMatcherWarehouse& tokenWarehouse, + parse_flags_t parseFlags, bool* success, + UErrorCode& status) { + if (affixPattern.isEmpty()) { + *success = false; + return {}; + } + *success = true; + + IgnorablesMatcher* ignorables; + if (0 != (parseFlags & PARSE_FLAG_EXACT_AFFIX)) { + ignorables = nullptr; + } else { + ignorables = &tokenWarehouse.ignorables(); + } + + AffixPatternMatcherBuilder builder(affixPattern, tokenWarehouse, ignorables); + AffixUtils::iterateWithConsumer(affixPattern, builder, status); + return builder.build(); +} + +AffixPatternMatcher::AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, + const UnicodeString& pattern) + : ArraySeriesMatcher(matchers, matchersLen), fPattern(pattern) {} + +UnicodeString AffixPatternMatcher::getPattern() const { + return fPattern.toAliasedUnicodeString(); +} + +bool AffixPatternMatcher::operator==(const AffixPatternMatcher& other) const { + return fPattern == other.fPattern; +} + + +AffixMatcherWarehouse::AffixMatcherWarehouse(AffixTokenMatcherWarehouse* tokenWarehouse) + : fTokenWarehouse(tokenWarehouse) { +} + +bool AffixMatcherWarehouse::isInteresting(const AffixPatternProvider& patternInfo, + const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, + UErrorCode& status) { + UnicodeString posPrefixString = patternInfo.getString(AffixPatternProvider::AFFIX_POS_PREFIX); + UnicodeString posSuffixString = patternInfo.getString(AffixPatternProvider::AFFIX_POS_SUFFIX); + UnicodeString negPrefixString; + UnicodeString negSuffixString; + if (patternInfo.hasNegativeSubpattern()) { + negPrefixString = patternInfo.getString(AffixPatternProvider::AFFIX_NEG_PREFIX); + negSuffixString = patternInfo.getString(AffixPatternProvider::AFFIX_NEG_SUFFIX); + } + + if (0 == (parseFlags & PARSE_FLAG_USE_FULL_AFFIXES) && + AffixUtils::containsOnlySymbolsAndIgnorables(posPrefixString, *ignorables.getSet(), status) && + AffixUtils::containsOnlySymbolsAndIgnorables(posSuffixString, *ignorables.getSet(), status) && + AffixUtils::containsOnlySymbolsAndIgnorables(negPrefixString, *ignorables.getSet(), status) && + AffixUtils::containsOnlySymbolsAndIgnorables(negSuffixString, *ignorables.getSet(), status) + // HACK: Plus and minus sign are a special case: we accept them trailing only if they are + // trailing in the pattern string. + && !AffixUtils::containsType(posSuffixString, TYPE_PLUS_SIGN, status) && + !AffixUtils::containsType(posSuffixString, TYPE_MINUS_SIGN, status) && + !AffixUtils::containsType(negSuffixString, TYPE_PLUS_SIGN, status) && + !AffixUtils::containsType(negSuffixString, TYPE_MINUS_SIGN, status)) { + // The affixes contain only symbols and ignorables. + // No need to generate affix matchers. + return false; + } + return true; +} + +void AffixMatcherWarehouse::createAffixMatchers(const AffixPatternProvider& patternInfo, + MutableMatcherCollection& output, + const IgnorablesMatcher& ignorables, + parse_flags_t parseFlags, UErrorCode& status) { + if (!isInteresting(patternInfo, ignorables, parseFlags, status)) { + return; + } + + // The affixes have interesting characters, or we are in strict mode. + // Use initial capacity of 6, the highest possible number of AffixMatchers. + UnicodeString sb; + bool includeUnpaired = 0 != (parseFlags & PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES); + UNumberSignDisplay signDisplay = (0 != (parseFlags & PARSE_FLAG_PLUS_SIGN_ALLOWED)) ? UNUM_SIGN_ALWAYS + : UNUM_SIGN_AUTO; + + int32_t numAffixMatchers = 0; + int32_t numAffixPatternMatchers = 0; + + AffixPatternMatcher* posPrefix = nullptr; + AffixPatternMatcher* posSuffix = nullptr; + + // Pre-process the affix strings to resolve LDML rules like sign display. + for (int8_t signum = 1; signum >= -1; signum--) { + // Generate Prefix + bool hasPrefix = false; + PatternStringUtils::patternInfoToStringBuilder( + patternInfo, true, signum, signDisplay, StandardPlural::OTHER, false, sb); + fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( + sb, *fTokenWarehouse, parseFlags, &hasPrefix, status); + AffixPatternMatcher* prefix = hasPrefix ? &fAffixPatternMatchers[numAffixPatternMatchers++] + : nullptr; + + // Generate Suffix + bool hasSuffix = false; + PatternStringUtils::patternInfoToStringBuilder( + patternInfo, false, signum, signDisplay, StandardPlural::OTHER, false, sb); + fAffixPatternMatchers[numAffixPatternMatchers] = AffixPatternMatcher::fromAffixPattern( + sb, *fTokenWarehouse, parseFlags, &hasSuffix, status); + AffixPatternMatcher* suffix = hasSuffix ? &fAffixPatternMatchers[numAffixPatternMatchers++] + : nullptr; + + if (signum == 1) { + posPrefix = prefix; + posSuffix = suffix; + } else if (equals(prefix, posPrefix) && equals(suffix, posSuffix)) { + // Skip adding these matchers (we already have equivalents) + continue; + } + + // Flags for setting in the ParsedNumber; the token matchers may add more. + int flags = (signum == -1) ? FLAG_NEGATIVE : 0; + + // Note: it is indeed possible for posPrefix and posSuffix to both be null. + // We still need to add that matcher for strict mode to work. + fAffixMatchers[numAffixMatchers++] = {prefix, suffix, flags}; + if (includeUnpaired && prefix != nullptr && suffix != nullptr) { + // The following if statements are designed to prevent adding two identical matchers. + if (signum == 1 || !equals(prefix, posPrefix)) { + fAffixMatchers[numAffixMatchers++] = {prefix, nullptr, flags}; + } + if (signum == 1 || !equals(suffix, posSuffix)) { + fAffixMatchers[numAffixMatchers++] = {nullptr, suffix, flags}; + } + } + } + + // Put the AffixMatchers in order, and then add them to the output. + // Since there are at most 9 elements, do a simple-to-implement bubble sort. + bool madeChanges; + do { + madeChanges = false; + for (int32_t i = 1; i < numAffixMatchers; i++) { + if (fAffixMatchers[i - 1].compareTo(fAffixMatchers[i]) > 0) { + madeChanges = true; + AffixMatcher temp = std::move(fAffixMatchers[i - 1]); + fAffixMatchers[i - 1] = std::move(fAffixMatchers[i]); + fAffixMatchers[i] = std::move(temp); + } + } + } while (madeChanges); + + for (int32_t i = 0; i < numAffixMatchers; i++) { + // Enable the following line to debug affixes + //std::cout << "Adding affix matcher: " << CStr(fAffixMatchers[i].toString())() << std::endl; + output.addMatcher(fAffixMatchers[i]); + } +} + + +AffixMatcher::AffixMatcher(AffixPatternMatcher* prefix, AffixPatternMatcher* suffix, result_flags_t flags) + : fPrefix(prefix), fSuffix(suffix), fFlags(flags) {} + +bool AffixMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + if (!result.seenNumber()) { + // Prefix + // Do not match if: + // 1. We have already seen a prefix (result.prefix != null) + // 2. The prefix in this AffixMatcher is empty (prefix == null) + if (!result.prefix.isBogus() || fPrefix == nullptr) { + return false; + } + + // Attempt to match the prefix. + int initialOffset = segment.getOffset(); + bool maybeMore = fPrefix->match(segment, result, status); + if (initialOffset != segment.getOffset()) { + result.prefix = fPrefix->getPattern(); + } + return maybeMore; + + } else { + // Suffix + // Do not match if: + // 1. We have already seen a suffix (result.suffix != null) + // 2. The suffix in this AffixMatcher is empty (suffix == null) + // 3. The matched prefix does not equal this AffixMatcher's prefix + if (!result.suffix.isBogus() || fSuffix == nullptr || !matched(fPrefix, result.prefix)) { + return false; + } + + // Attempt to match the suffix. + int initialOffset = segment.getOffset(); + bool maybeMore = fSuffix->match(segment, result, status); + if (initialOffset != segment.getOffset()) { + result.suffix = fSuffix->getPattern(); + } + return maybeMore; + } +} + +bool AffixMatcher::smokeTest(const StringSegment& segment) const { + return (fPrefix != nullptr && fPrefix->smokeTest(segment)) || + (fSuffix != nullptr && fSuffix->smokeTest(segment)); +} + +void AffixMatcher::postProcess(ParsedNumber& result) const { + // Check to see if our affix is the one that was matched. If so, set the flags in the result. + if (matched(fPrefix, result.prefix) && matched(fSuffix, result.suffix)) { + // Fill in the result prefix and suffix with non-null values (empty string). + // Used by strict mode to determine whether an entire affix pair was matched. + if (result.prefix.isBogus()) { + result.prefix = UnicodeString(); + } + if (result.suffix.isBogus()) { + result.suffix = UnicodeString(); + } + result.flags |= fFlags; + if (fPrefix != nullptr) { + fPrefix->postProcess(result); + } + if (fSuffix != nullptr) { + fSuffix->postProcess(result); + } + } +} + +int8_t AffixMatcher::compareTo(const AffixMatcher& rhs) const { + const AffixMatcher& lhs = *this; + if (length(lhs.fPrefix) != length(rhs.fPrefix)) { + return length(lhs.fPrefix) > length(rhs.fPrefix) ? -1 : 1; + } else if (length(lhs.fSuffix) != length(rhs.fSuffix)) { + return length(lhs.fSuffix) > length(rhs.fSuffix) ? -1 : 1; + } else { + return 0; + } +} + +UnicodeString AffixMatcher::toString() const { + bool isNegative = 0 != (fFlags & FLAG_NEGATIVE); + return UnicodeString(u"getPattern() : u"null") + u"#" + + (fSuffix ? fSuffix->getPattern() : u"null") + u">"; + +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_affixes.h b/deps/icu-small/source/i18n/numparse_affixes.h new file mode 100644 index 00000000000000..be8c4fb56473d1 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_affixes.h @@ -0,0 +1,255 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_AFFIXES_H__ +#define __NUMPARSE_AFFIXES_H__ + +#include "numparse_types.h" +#include "numparse_symbols.h" +#include "numparse_currency.h" +#include "number_affixutils.h" +#include "number_currencysymbols.h" + +#include + +U_NAMESPACE_BEGIN +namespace numparse { +namespace impl { + +// Forward-declaration of implementation classes for friending +class AffixPatternMatcherBuilder; +class AffixPatternMatcher; + +using ::icu::number::impl::AffixPatternProvider; +using ::icu::number::impl::TokenConsumer; +using ::icu::number::impl::CurrencySymbols; + + +class CodePointMatcher : public NumberParseMatcher, public UMemory { + public: + CodePointMatcher() = default; // WARNING: Leaves the object in an unusable state + + CodePointMatcher(UChar32 cp); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + bool smokeTest(const StringSegment& segment) const override; + + UnicodeString toString() const override; + + private: + UChar32 fCp; +}; + +} // namespace impl +} // namespace numparse + +// Export a explicit template instantiations of MaybeStackArray and CompactUnicodeString. +// When building DLLs for Windows this is required even though no direct access leaks out of the i18n library. +// (See digitlst.h, pluralaffix.h, datefmt.h, and others for similar examples.) +// Note: These need to be outside of the impl::numparse namespace, or Clang will generate a compile error. +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +template class U_I18N_API MaybeStackArray; +template class U_I18N_API MaybeStackArray; +template class U_I18N_API numparse::impl::CompactUnicodeString<4>; +#endif + +namespace numparse { +namespace impl { + +/** + * A warehouse to retain ownership of CodePointMatchers. + */ +// Exported as U_I18N_API for tests +class U_I18N_API CodePointMatcherWarehouse : public UMemory { + private: + static constexpr int32_t CODE_POINT_STACK_CAPACITY = 5; // Number of entries directly on the stack + static constexpr int32_t CODE_POINT_BATCH_SIZE = 10; // Number of entries per heap allocation + + public: + CodePointMatcherWarehouse(); + + // A custom destructor is needed to free the memory from MaybeStackArray. + // A custom move constructor and move assignment seem to be needed because of the custom destructor. + + ~CodePointMatcherWarehouse(); + + CodePointMatcherWarehouse(CodePointMatcherWarehouse&& src) U_NOEXCEPT; + + CodePointMatcherWarehouse& operator=(CodePointMatcherWarehouse&& src) U_NOEXCEPT; + + NumberParseMatcher& nextCodePointMatcher(UChar32 cp); + + private: + std::array codePoints; // By value + MaybeStackArray codePointsOverflow; // On heap in "batches" + int32_t codePointCount; // Total for both the ones by value and on heap + int32_t codePointNumBatches; // Number of batches in codePointsOverflow +}; + + +struct AffixTokenMatcherSetupData { + const CurrencySymbols& currencySymbols; + const DecimalFormatSymbols& dfs; + IgnorablesMatcher& ignorables; + const Locale& locale; + parse_flags_t parseFlags; +}; + + +/** + * Small helper class that generates matchers for individual tokens for AffixPatternMatcher. + * + * In Java, this is called AffixTokenMatcherFactory (a "factory"). However, in C++, it is called a + * "warehouse", because in addition to generating the matchers, it also retains ownership of them. The + * warehouse must stay in scope for the whole lifespan of the AffixPatternMatcher that uses matchers from + * the warehouse. + * + * @author sffc + */ +// Exported as U_I18N_API for tests +class U_I18N_API AffixTokenMatcherWarehouse : public UMemory { + public: + AffixTokenMatcherWarehouse() = default; // WARNING: Leaves the object in an unusable state + + AffixTokenMatcherWarehouse(const AffixTokenMatcherSetupData* setupData); + + NumberParseMatcher& minusSign(); + + NumberParseMatcher& plusSign(); + + NumberParseMatcher& percent(); + + NumberParseMatcher& permille(); + + NumberParseMatcher& currency(UErrorCode& status); + + IgnorablesMatcher& ignorables(); + + NumberParseMatcher& nextCodePointMatcher(UChar32 cp); + + private: + // NOTE: The following field may be unsafe to access after construction is done! + const AffixTokenMatcherSetupData* fSetupData; + + // NOTE: These are default-constructed and should not be used until initialized. + MinusSignMatcher fMinusSign; + PlusSignMatcher fPlusSign; + PercentMatcher fPercent; + PermilleMatcher fPermille; + CombinedCurrencyMatcher fCurrency; + + // Use a child class for code point matchers, since it requires non-default operators. + CodePointMatcherWarehouse fCodePoints; + + friend class AffixPatternMatcherBuilder; + friend class AffixPatternMatcher; +}; + + +class AffixPatternMatcherBuilder : public TokenConsumer, public MutableMatcherCollection { + public: + AffixPatternMatcherBuilder(const UnicodeString& pattern, AffixTokenMatcherWarehouse& warehouse, + IgnorablesMatcher* ignorables); + + void consumeToken(::icu::number::impl::AffixPatternType type, UChar32 cp, UErrorCode& status) override; + + /** NOTE: You can build only once! */ + AffixPatternMatcher build(); + + private: + ArraySeriesMatcher::MatcherArray fMatchers; + int32_t fMatchersLen; + int32_t fLastTypeOrCp; + + const UnicodeString& fPattern; + AffixTokenMatcherWarehouse& fWarehouse; + IgnorablesMatcher* fIgnorables; + + void addMatcher(NumberParseMatcher& matcher) override; +}; + + +// Exported as U_I18N_API for tests +class U_I18N_API AffixPatternMatcher : public ArraySeriesMatcher { + public: + AffixPatternMatcher() = default; // WARNING: Leaves the object in an unusable state + + static AffixPatternMatcher fromAffixPattern(const UnicodeString& affixPattern, + AffixTokenMatcherWarehouse& warehouse, + parse_flags_t parseFlags, bool* success, + UErrorCode& status); + + UnicodeString getPattern() const; + + bool operator==(const AffixPatternMatcher& other) const; + + private: + CompactUnicodeString<4> fPattern; + + AffixPatternMatcher(MatcherArray& matchers, int32_t matchersLen, const UnicodeString& pattern); + + friend class AffixPatternMatcherBuilder; +}; + + +class AffixMatcher : public NumberParseMatcher, public UMemory { + public: + AffixMatcher() = default; // WARNING: Leaves the object in an unusable state + + AffixMatcher(AffixPatternMatcher* prefix, AffixPatternMatcher* suffix, result_flags_t flags); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + void postProcess(ParsedNumber& result) const override; + + bool smokeTest(const StringSegment& segment) const override; + + int8_t compareTo(const AffixMatcher& rhs) const; + + UnicodeString toString() const override; + + private: + AffixPatternMatcher* fPrefix; + AffixPatternMatcher* fSuffix; + result_flags_t fFlags; +}; + + +/** + * A C++-only class to retain ownership of the AffixMatchers needed for parsing. + */ +class AffixMatcherWarehouse { + public: + AffixMatcherWarehouse() = default; // WARNING: Leaves the object in an unusable state + + AffixMatcherWarehouse(AffixTokenMatcherWarehouse* tokenWarehouse); + + void createAffixMatchers(const AffixPatternProvider& patternInfo, MutableMatcherCollection& output, + const IgnorablesMatcher& ignorables, parse_flags_t parseFlags, + UErrorCode& status); + + private: + // 9 is the limit: positive, zero, and negative, each with prefix, suffix, and prefix+suffix + AffixMatcher fAffixMatchers[9]; + // 6 is the limit: positive, zero, and negative, a prefix and a suffix for each + AffixPatternMatcher fAffixPatternMatchers[6]; + // Reference to the warehouse for tokens used by the AffixPatternMatchers + AffixTokenMatcherWarehouse* fTokenWarehouse; + + friend class AffixMatcher; + + static bool isInteresting(const AffixPatternProvider& patternInfo, const IgnorablesMatcher& ignorables, + parse_flags_t parseFlags, UErrorCode& status); +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_AFFIXES_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_compositions.cpp b/deps/icu-small/source/i18n/numparse_compositions.cpp new file mode 100644 index 00000000000000..19253da805f0bf --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_compositions.cpp @@ -0,0 +1,107 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_compositions.h" +#include "unicode/uniset.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +bool SeriesMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + ParsedNumber backup(result); + + int32_t initialOffset = segment.getOffset(); + bool maybeMore = true; + for (auto* it = begin(); it < end();) { + const NumberParseMatcher* matcher = *it; + int matcherOffset = segment.getOffset(); + if (segment.length() != 0) { + maybeMore = matcher->match(segment, result, status); + } else { + // Nothing for this matcher to match; ask for more. + maybeMore = true; + } + + bool success = (segment.getOffset() != matcherOffset); + bool isFlexible = matcher->isFlexible(); + if (success && isFlexible) { + // Match succeeded, and this is a flexible matcher. Re-run it. + } else if (success) { + // Match succeeded, and this is NOT a flexible matcher. Proceed to the next matcher. + it++; + // Small hack: if there is another matcher coming, do not accept trailing weak chars. + // Needed for proper handling of currency spacing. + if (it < end() && segment.getOffset() != result.charEnd && result.charEnd > matcherOffset) { + segment.setOffset(result.charEnd); + } + } else if (isFlexible) { + // Match failed, and this is a flexible matcher. Try again with the next matcher. + it++; + } else { + // Match failed, and this is NOT a flexible matcher. Exit. + segment.setOffset(initialOffset); + result = backup; + return maybeMore; + } + } + + // All matchers in the series succeeded. + return maybeMore; +} + +bool SeriesMatcher::smokeTest(const StringSegment& segment) const { + // NOTE: The range-based for loop calls the virtual begin() and end() methods. + // NOTE: We only want the first element. Use the for loop for boundary checking. + for (auto& matcher : *this) { + // SeriesMatchers are never allowed to start with a Flexible matcher. + U_ASSERT(!matcher->isFlexible()); + return matcher->smokeTest(segment); + } + return false; +} + +void SeriesMatcher::postProcess(ParsedNumber& result) const { + // NOTE: The range-based for loop calls the virtual begin() and end() methods. + for (auto* matcher : *this) { + matcher->postProcess(result); + } +} + + +ArraySeriesMatcher::ArraySeriesMatcher() + : fMatchersLen(0) { +} + +ArraySeriesMatcher::ArraySeriesMatcher(MatcherArray& matchers, int32_t matchersLen) + : fMatchers(std::move(matchers)), fMatchersLen(matchersLen) { +} + +int32_t ArraySeriesMatcher::length() const { + return fMatchersLen; +} + +const NumberParseMatcher* const* ArraySeriesMatcher::begin() const { + return fMatchers.getAlias(); +} + +const NumberParseMatcher* const* ArraySeriesMatcher::end() const { + return fMatchers.getAlias() + fMatchersLen; +} + +UnicodeString ArraySeriesMatcher::toString() const { + return u""; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_compositions.h b/deps/icu-small/source/i18n/numparse_compositions.h new file mode 100644 index 00000000000000..f085912def1533 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_compositions.h @@ -0,0 +1,124 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __SOURCE_NUMPARSE_COMPOSITIONS__ +#define __SOURCE_NUMPARSE_COMPOSITIONS__ + +#include "numparse_types.h" + +U_NAMESPACE_BEGIN + +// Export an explicit template instantiation of the MaybeStackArray that is used as a data member of ArraySeriesMatcher. +// When building DLLs for Windows this is required even though no direct access to the MaybeStackArray leaks out of the i18n library. +// (See digitlst.h, pluralaffix.h, datefmt.h, and others for similar examples.) +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +template class U_I18N_API MaybeStackArray; +#endif + +namespace numparse { +namespace impl { + +/** + * Base class for AnyMatcher and SeriesMatcher. + */ +// Exported as U_I18N_API for tests +class U_I18N_API CompositionMatcher : public NumberParseMatcher { + protected: + // No construction except by subclasses! + CompositionMatcher() = default; + + // To be overridden by subclasses (used for iteration): + virtual const NumberParseMatcher* const* begin() const = 0; + + // To be overridden by subclasses (used for iteration): + virtual const NumberParseMatcher* const* end() const = 0; +}; + + +// NOTE: AnyMatcher is no longer being used. The previous definition is shown below. +// The implementation can be found in SVN source control, deleted around March 30, 2018. +///** +// * Composes a number of matchers, and succeeds if any of the matchers succeed. Always greedily chooses +// * the first matcher in the list to succeed. +// * +// * NOTE: In C++, this is a base class, unlike ICU4J, which uses a factory-style interface. +// * +// * @author sffc +// * @see SeriesMatcher +// */ +//class AnyMatcher : public CompositionMatcher { +// public: +// bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; +// +// bool smokeTest(const StringSegment& segment) const override; +// +// void postProcess(ParsedNumber& result) const override; +// +// protected: +// // No construction except by subclasses! +// AnyMatcher() = default; +//}; + + +/** + * Composes a number of matchers, running one after another. Matches the input string only if all of the + * matchers in the series succeed. Performs greedy matches within the context of the series. + * + * @author sffc + * @see AnyMatcher + */ +// Exported as U_I18N_API for tests +class U_I18N_API SeriesMatcher : public CompositionMatcher { + public: + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + bool smokeTest(const StringSegment& segment) const override; + + void postProcess(ParsedNumber& result) const override; + + virtual int32_t length() const = 0; + + protected: + // No construction except by subclasses! + SeriesMatcher() = default; +}; + +/** + * An implementation of SeriesMatcher that references an array of matchers. + * + * The object adopts the array, but NOT the matchers contained inside the array. + */ +// Exported as U_I18N_API for tests +class U_I18N_API ArraySeriesMatcher : public SeriesMatcher { + public: + ArraySeriesMatcher(); // WARNING: Leaves the object in an unusable state + + typedef MaybeStackArray MatcherArray; + + /** The array is std::move'd */ + ArraySeriesMatcher(MatcherArray& matchers, int32_t matchersLen); + + UnicodeString toString() const override; + + int32_t length() const override; + + protected: + const NumberParseMatcher* const* begin() const override; + + const NumberParseMatcher* const* end() const override; + + private: + MatcherArray fMatchers; + int32_t fMatchersLen; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__SOURCE_NUMPARSE_COMPOSITIONS__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_currency.cpp b/deps/icu-small/source/i18n/numparse_currency.cpp new file mode 100644 index 00000000000000..ae8196ec483799 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_currency.cpp @@ -0,0 +1,186 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_currency.h" +#include "ucurrimp.h" +#include "unicode/errorcode.h" +#include "numparse_utils.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +CombinedCurrencyMatcher::CombinedCurrencyMatcher(const CurrencySymbols& currencySymbols, const DecimalFormatSymbols& dfs, + parse_flags_t parseFlags, UErrorCode& status) + : fCurrency1(currencySymbols.getCurrencySymbol(status)), + fCurrency2(currencySymbols.getIntlCurrencySymbol(status)), + fUseFullCurrencyData(0 == (parseFlags & PARSE_FLAG_NO_FOREIGN_CURRENCY)), + afterPrefixInsert(dfs.getPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, false, status)), + beforeSuffixInsert(dfs.getPatternForCurrencySpacing(UNUM_CURRENCY_INSERT, true, status)), + fLocaleName(dfs.getLocale().getName(), -1, status) { + utils::copyCurrencyCode(fCurrencyCode, currencySymbols.getIsoCode()); + + // Pre-load the long names for the current locale and currency + // if we are parsing without the full currency data. + if (!fUseFullCurrencyData) { + for (int32_t i=0; i(i); + fLocalLongNames[i] = currencySymbols.getPluralName(plural, status); + } + } + + // TODO: Figure out how to make this faster and re-enable. + // Computing the "lead code points" set for fastpathing is too slow to use in production. + // See http://bugs.icu-project.org/trac/ticket/13584 +// // Compute the full set of characters that could be the first in a currency to allow for +// // efficient smoke test. +// fLeadCodePoints.add(fCurrency1.char32At(0)); +// fLeadCodePoints.add(fCurrency2.char32At(0)); +// fLeadCodePoints.add(beforeSuffixInsert.char32At(0)); +// uprv_currencyLeads(fLocaleName.data(), fLeadCodePoints, status); +// // Always apply case mapping closure for currencies +// fLeadCodePoints.closeOver(USET_ADD_CASE_MAPPINGS); +// fLeadCodePoints.freeze(); +} + +bool +CombinedCurrencyMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + if (result.currencyCode[0] != 0) { + return false; + } + + // Try to match a currency spacing separator. + int32_t initialOffset = segment.getOffset(); + bool maybeMore = false; + if (result.seenNumber() && !beforeSuffixInsert.isEmpty()) { + int32_t overlap = segment.getCommonPrefixLength(beforeSuffixInsert); + if (overlap == beforeSuffixInsert.length()) { + segment.adjustOffset(overlap); + // Note: let currency spacing be a weak match. Don't update chars consumed. + } + maybeMore = maybeMore || overlap == segment.length(); + } + + // Match the currency string, and reset if we didn't find one. + maybeMore = maybeMore || matchCurrency(segment, result, status); + if (result.currencyCode[0] == 0) { + segment.setOffset(initialOffset); + return maybeMore; + } + + // Try to match a currency spacing separator. + if (!result.seenNumber() && !afterPrefixInsert.isEmpty()) { + int32_t overlap = segment.getCommonPrefixLength(afterPrefixInsert); + if (overlap == afterPrefixInsert.length()) { + segment.adjustOffset(overlap); + // Note: let currency spacing be a weak match. Don't update chars consumed. + } + maybeMore = maybeMore || overlap == segment.length(); + } + + return maybeMore; +} + +bool CombinedCurrencyMatcher::matchCurrency(StringSegment& segment, ParsedNumber& result, + UErrorCode& status) const { + bool maybeMore = false; + + int32_t overlap1; + if (!fCurrency1.isEmpty()) { + overlap1 = segment.getCaseSensitivePrefixLength(fCurrency1); + } else { + overlap1 = -1; + } + maybeMore = maybeMore || overlap1 == segment.length(); + if (overlap1 == fCurrency1.length()) { + utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); + segment.adjustOffset(overlap1); + result.setCharsConsumed(segment); + return maybeMore; + } + + int32_t overlap2; + if (!fCurrency2.isEmpty()) { + overlap2 = segment.getCaseSensitivePrefixLength(fCurrency2); + } else { + overlap2 = -1; + } + maybeMore = maybeMore || overlap2 == segment.length(); + if (overlap2 == fCurrency2.length()) { + utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); + segment.adjustOffset(overlap2); + result.setCharsConsumed(segment); + return maybeMore; + } + + if (fUseFullCurrencyData) { + // Use the full currency data. + // NOTE: This call site should be improved with #13584. + const UnicodeString segmentString = segment.toTempUnicodeString(); + + // Try to parse the currency + ParsePosition ppos(0); + int32_t partialMatchLen = 0; + uprv_parseCurrency( + fLocaleName.data(), + segmentString, + ppos, + UCURR_SYMBOL_NAME, // checks for both UCURR_SYMBOL_NAME and UCURR_LONG_NAME + &partialMatchLen, + result.currencyCode, + status); + maybeMore = maybeMore || partialMatchLen == segment.length(); + + if (U_SUCCESS(status) && ppos.getIndex() != 0) { + // Complete match. + // NOTE: The currency code should already be saved in the ParsedNumber. + segment.adjustOffset(ppos.getIndex()); + result.setCharsConsumed(segment); + return maybeMore; + } + + } else { + // Use the locale long names. + int32_t longestFullMatch = 0; + for (int32_t i=0; i longestFullMatch) { + longestFullMatch = name.length(); + } + maybeMore = maybeMore || overlap > 0; + } + if (longestFullMatch > 0) { + utils::copyCurrencyCode(result.currencyCode, fCurrencyCode); + segment.adjustOffset(longestFullMatch); + result.setCharsConsumed(segment); + return maybeMore; + } + } + + // No match found. + return maybeMore; +} + +bool CombinedCurrencyMatcher::smokeTest(const StringSegment&) const { + // TODO: See constructor + return true; + //return segment.startsWith(fLeadCodePoints); +} + +UnicodeString CombinedCurrencyMatcher::toString() const { + return u""; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_currency.h b/deps/icu-small/source/i18n/numparse_currency.h new file mode 100644 index 00000000000000..a94943312fde9d --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_currency.h @@ -0,0 +1,74 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_CURRENCY_H__ +#define __NUMPARSE_CURRENCY_H__ + +#include "numparse_types.h" +#include "numparse_compositions.h" +#include "charstr.h" +#include "number_currencysymbols.h" +#include "unicode/uniset.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + +using ::icu::number::impl::CurrencySymbols; + +/** + * Matches a currency, either a custom currency or one from the data bundle. The class is called + * "combined" to emphasize that the currency string may come from one of multiple sources. + * + * Will match currency spacing either before or after the number depending on whether we are currently in + * the prefix or suffix. + * + * The implementation of this class is slightly different between J and C. See #13584 for a follow-up. + * + * @author sffc + */ +// Exported as U_I18N_API for tests +class U_I18N_API CombinedCurrencyMatcher : public NumberParseMatcher, public UMemory { + public: + CombinedCurrencyMatcher() = default; // WARNING: Leaves the object in an unusable state + + CombinedCurrencyMatcher(const CurrencySymbols& currencySymbols, const DecimalFormatSymbols& dfs, + parse_flags_t parseFlags, UErrorCode& status); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + bool smokeTest(const StringSegment& segment) const override; + + UnicodeString toString() const override; + + private: + UChar fCurrencyCode[4]; + UnicodeString fCurrency1; + UnicodeString fCurrency2; + + bool fUseFullCurrencyData; + UnicodeString fLocalLongNames[StandardPlural::COUNT]; + + UnicodeString afterPrefixInsert; + UnicodeString beforeSuffixInsert; + + // We could use Locale instead of CharString here, but + // Locale has a non-trivial default constructor. + CharString fLocaleName; + + // TODO: See comments in constructor in numparse_currency.cpp + // UnicodeSet fLeadCodePoints; + + /** Matches the currency string without concern for currency spacing. */ + bool matchCurrency(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_CURRENCY_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_decimal.cpp b/deps/icu-small/source/i18n/numparse_decimal.cpp new file mode 100644 index 00000000000000..b120c5c6ad2f91 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_decimal.cpp @@ -0,0 +1,458 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_decimal.h" +#include "static_unicode_sets.h" +#include "numparse_utils.h" +#include "unicode/uchar.h" +#include "putilimp.h" +#include "number_decimalquantity.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +DecimalMatcher::DecimalMatcher(const DecimalFormatSymbols& symbols, const Grouper& grouper, + parse_flags_t parseFlags) { + if (0 != (parseFlags & PARSE_FLAG_MONETARY_SEPARATORS)) { + groupingSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kMonetaryGroupingSeparatorSymbol); + decimalSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kMonetarySeparatorSymbol); + } else { + groupingSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol); + decimalSeparator = symbols.getConstSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol); + } + bool strictSeparators = 0 != (parseFlags & PARSE_FLAG_STRICT_SEPARATORS); + unisets::Key groupingKey = strictSeparators ? unisets::STRICT_ALL_SEPARATORS + : unisets::ALL_SEPARATORS; + + // Attempt to find separators in the static cache + + groupingUniSet = unisets::get(groupingKey); + unisets::Key decimalKey = unisets::chooseFrom( + decimalSeparator, + strictSeparators ? unisets::STRICT_COMMA : unisets::COMMA, + strictSeparators ? unisets::STRICT_PERIOD : unisets::PERIOD); + if (decimalKey >= 0) { + decimalUniSet = unisets::get(decimalKey); + } else if (!decimalSeparator.isEmpty()) { + auto* set = new UnicodeSet(); + set->add(decimalSeparator.char32At(0)); + set->freeze(); + decimalUniSet = set; + fLocalDecimalUniSet.adoptInstead(set); + } else { + decimalUniSet = unisets::get(unisets::EMPTY); + } + + if (groupingKey >= 0 && decimalKey >= 0) { + // Everything is available in the static cache + separatorSet = groupingUniSet; + leadSet = unisets::get( + strictSeparators ? unisets::DIGITS_OR_ALL_SEPARATORS + : unisets::DIGITS_OR_STRICT_ALL_SEPARATORS); + } else { + auto* set = new UnicodeSet(); + set->addAll(*groupingUniSet); + set->addAll(*decimalUniSet); + set->freeze(); + separatorSet = set; + fLocalSeparatorSet.adoptInstead(set); + leadSet = nullptr; + } + + UChar32 cpZero = symbols.getCodePointZero(); + if (cpZero == -1 || !u_isdigit(cpZero) || u_digit(cpZero, 10) != 0) { + // Uncommon case: okay to allocate. + auto digitStrings = new UnicodeString[10]; + fLocalDigitStrings.adoptInstead(digitStrings); + for (int32_t i = 0; i <= 9; i++) { + digitStrings[i] = symbols.getConstDigitSymbol(i); + } + } + + requireGroupingMatch = 0 != (parseFlags & PARSE_FLAG_STRICT_GROUPING_SIZE); + groupingDisabled = 0 != (parseFlags & PARSE_FLAG_GROUPING_DISABLED); + integerOnly = 0 != (parseFlags & PARSE_FLAG_INTEGER_ONLY); + grouping1 = grouper.getPrimary(); + grouping2 = grouper.getSecondary(); + + // Fraction grouping parsing is disabled for now but could be enabled later. + // See http://bugs.icu-project.org/trac/ticket/10794 + // fractionGrouping = 0 != (parseFlags & PARSE_FLAG_FRACTION_GROUPING_ENABLED); +} + +bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + return match(segment, result, 0, status); +} + +bool DecimalMatcher::match(StringSegment& segment, ParsedNumber& result, int8_t exponentSign, + UErrorCode&) const { + if (result.seenNumber() && exponentSign == 0) { + // A number has already been consumed. + return false; + } else if (exponentSign != 0) { + // scientific notation always comes after the number + U_ASSERT(!result.quantity.bogus); + } + + // Initial offset before any character consumption. + int32_t initialOffset = segment.getOffset(); + + // Return value: whether to ask for more characters. + bool maybeMore = false; + + // All digits consumed so far. + number::impl::DecimalQuantity digitsConsumed; + digitsConsumed.bogus = true; + + // The total number of digits after the decimal place, used for scaling the result. + int32_t digitsAfterDecimalPlace = 0; + + // The actual grouping and decimal separators used in the string. + // If non-null, we have seen that token. + UnicodeString actualGroupingString; + UnicodeString actualDecimalString; + actualGroupingString.setToBogus(); + actualDecimalString.setToBogus(); + + // Information for two groups: the previous group and the current group. + // + // Each group has three pieces of information: + // + // Offset: the string position of the beginning of the group, including a leading separator + // if there was a leading separator. This is needed in case we need to rewind the parse to + // that position. + // + // Separator type: + // 0 => beginning of string + // 1 => lead separator is a grouping separator + // 2 => lead separator is a decimal separator + // + // Count: the number of digits in the group. If -1, the group has been validated. + int32_t currGroupOffset = 0; + int32_t currGroupSepType = 0; + int32_t currGroupCount = 0; + int32_t prevGroupOffset = -1; + int32_t prevGroupSepType = -1; + int32_t prevGroupCount = -1; + + while (segment.length() > 0) { + maybeMore = false; + + // Attempt to match a digit. + int8_t digit = -1; + + // Try by code point digit value. + UChar32 cp = segment.getCodePoint(); + if (u_isdigit(cp)) { + segment.adjustOffset(U16_LENGTH(cp)); + digit = static_cast(u_digit(cp, 10)); + } + + // Try by digit string. + if (digit == -1 && !fLocalDigitStrings.isNull()) { + for (int32_t i = 0; i < 10; i++) { + const UnicodeString& str = fLocalDigitStrings[i]; + if (str.isEmpty()) { + continue; + } + int32_t overlap = segment.getCommonPrefixLength(str); + if (overlap == str.length()) { + segment.adjustOffset(overlap); + digit = static_cast(i); + break; + } + maybeMore = maybeMore || (overlap == segment.length()); + } + } + + if (digit >= 0) { + // Digit was found. + if (digitsConsumed.bogus) { + digitsConsumed.bogus = false; + digitsConsumed.clear(); + } + digitsConsumed.appendDigit(digit, 0, true); + currGroupCount++; + if (!actualDecimalString.isBogus()) { + digitsAfterDecimalPlace++; + } + continue; + } + + // Attempt to match a literal grouping or decimal separator. + bool isDecimal = false; + bool isGrouping = false; + + // 1) Attempt the decimal separator string literal. + // if (we have not seen a decimal separator yet) { ... } + if (actualDecimalString.isBogus() && !decimalSeparator.isEmpty()) { + int32_t overlap = segment.getCommonPrefixLength(decimalSeparator); + maybeMore = maybeMore || (overlap == segment.length()); + if (overlap == decimalSeparator.length()) { + isDecimal = true; + actualDecimalString = decimalSeparator; + } + } + + // 2) Attempt to match the actual grouping string literal. + if (!actualGroupingString.isBogus()) { + int32_t overlap = segment.getCommonPrefixLength(actualGroupingString); + maybeMore = maybeMore || (overlap == segment.length()); + if (overlap == actualGroupingString.length()) { + isGrouping = true; + } + } + + // 2.5) Attempt to match a new the grouping separator string literal. + // if (we have not seen a grouping or decimal separator yet) { ... } + if (!groupingDisabled && actualGroupingString.isBogus() && actualDecimalString.isBogus() && + !groupingSeparator.isEmpty()) { + int32_t overlap = segment.getCommonPrefixLength(groupingSeparator); + maybeMore = maybeMore || (overlap == segment.length()); + if (overlap == groupingSeparator.length()) { + isGrouping = true; + actualGroupingString = groupingSeparator; + } + } + + // 3) Attempt to match a decimal separator from the equivalence set. + // if (we have not seen a decimal separator yet) { ... } + // The !isGrouping is to confirm that we haven't yet matched the current character. + if (!isGrouping && actualDecimalString.isBogus()) { + if (decimalUniSet->contains(cp)) { + isDecimal = true; + actualDecimalString = UnicodeString(cp); + } + } + + // 4) Attempt to match a grouping separator from the equivalence set. + // if (we have not seen a grouping or decimal separator yet) { ... } + if (!groupingDisabled && actualGroupingString.isBogus() && actualDecimalString.isBogus()) { + if (groupingUniSet->contains(cp)) { + isGrouping = true; + actualGroupingString = UnicodeString(cp); + } + } + + // Leave if we failed to match this as a separator. + if (!isDecimal && !isGrouping) { + break; + } + + // Check for conditions when we don't want to accept the separator. + if (isDecimal && integerOnly) { + break; + } else if (currGroupSepType == 2 && isGrouping) { + // Fraction grouping + break; + } + + // Validate intermediate grouping sizes. + bool prevValidSecondary = validateGroup(prevGroupSepType, prevGroupCount, false); + bool currValidPrimary = validateGroup(currGroupSepType, currGroupCount, true); + if (!prevValidSecondary || (isDecimal && !currValidPrimary)) { + // Invalid grouping sizes. + if (isGrouping && currGroupCount == 0) { + // Trailing grouping separators: these are taken care of below + U_ASSERT(currGroupSepType == 1); + } else if (requireGroupingMatch) { + // Strict mode: reject the parse + digitsConsumed.clear(); + digitsConsumed.bogus = true; + } + break; + } else if (requireGroupingMatch && currGroupCount == 0 && currGroupSepType == 1) { + break; + } else { + // Grouping sizes OK so far. + prevGroupOffset = currGroupOffset; + prevGroupCount = currGroupCount; + if (isDecimal) { + // Do not validate this group any more. + prevGroupSepType = -1; + } else { + prevGroupSepType = currGroupSepType; + } + } + + // OK to accept the separator. + // Special case: don't update currGroup if it is empty; this allows two grouping + // separators in a row in lenient mode. + if (currGroupCount != 0) { + currGroupOffset = segment.getOffset(); + } + currGroupSepType = isGrouping ? 1 : 2; + currGroupCount = 0; + if (isGrouping) { + segment.adjustOffset(actualGroupingString.length()); + } else { + segment.adjustOffset(actualDecimalString.length()); + } + } + + // End of main loop. + // Back up if there was a trailing grouping separator. + // Shift prev -> curr so we can check it as a final group. + if (currGroupSepType != 2 && currGroupCount == 0) { + maybeMore = true; + segment.setOffset(currGroupOffset); + currGroupOffset = prevGroupOffset; + currGroupSepType = prevGroupSepType; + currGroupCount = prevGroupCount; + prevGroupOffset = -1; + prevGroupSepType = 0; + prevGroupCount = 1; + } + + // Validate final grouping sizes. + bool prevValidSecondary = validateGroup(prevGroupSepType, prevGroupCount, false); + bool currValidPrimary = validateGroup(currGroupSepType, currGroupCount, true); + if (!requireGroupingMatch) { + // The cases we need to handle here are lone digits. + // Examples: "1,1" "1,1," "1,1,1" "1,1,1," ",1" (all parse as 1) + // See more examples in numberformattestspecification.txt + int32_t digitsToRemove = 0; + if (!prevValidSecondary) { + segment.setOffset(prevGroupOffset); + digitsToRemove += prevGroupCount; + digitsToRemove += currGroupCount; + } else if (!currValidPrimary && (prevGroupSepType != 0 || prevGroupCount != 0)) { + maybeMore = true; + segment.setOffset(currGroupOffset); + digitsToRemove += currGroupCount; + } + if (digitsToRemove != 0) { + digitsConsumed.adjustMagnitude(-digitsToRemove); + digitsConsumed.truncate(); + } + prevValidSecondary = true; + currValidPrimary = true; + } + if (currGroupSepType != 2 && (!prevValidSecondary || !currValidPrimary)) { + // Grouping failure. + digitsConsumed.bogus = true; + } + + // Strings that start with a separator but have no digits, + // or strings that failed a grouping size check. + if (digitsConsumed.bogus) { + maybeMore = maybeMore || (segment.length() == 0); + segment.setOffset(initialOffset); + return maybeMore; + } + + // We passed all inspections. Start post-processing. + + // Adjust for fraction part. + digitsConsumed.adjustMagnitude(-digitsAfterDecimalPlace); + + // Set the digits, either normal or exponent. + if (exponentSign != 0 && segment.getOffset() != initialOffset) { + bool overflow = false; + if (digitsConsumed.fitsInLong()) { + int64_t exponentLong = digitsConsumed.toLong(false); + U_ASSERT(exponentLong >= 0); + if (exponentLong <= INT32_MAX) { + auto exponentInt = static_cast(exponentLong); + if (result.quantity.adjustMagnitude(exponentSign * exponentInt)) { + overflow = true; + } + } else { + overflow = true; + } + } else { + overflow = true; + } + if (overflow) { + if (exponentSign == -1) { + // Set to zero + result.quantity.clear(); + } else { + // Set to infinity + result.quantity.bogus = true; + result.flags |= FLAG_INFINITY; + } + } + } else { + result.quantity = digitsConsumed; + } + + // Set other information into the result and return. + if (!actualDecimalString.isBogus()) { + result.flags |= FLAG_HAS_DECIMAL_SEPARATOR; + } + result.setCharsConsumed(segment); + return segment.length() == 0 || maybeMore; +} + +bool DecimalMatcher::validateGroup(int32_t sepType, int32_t count, bool isPrimary) const { + if (requireGroupingMatch) { + if (sepType == -1) { + // No such group (prevGroup before first shift). + return true; + } else if (sepType == 0) { + // First group. + if (isPrimary) { + // No grouping separators is OK. + return true; + } else { + return count != 0 && count <= grouping2; + } + } else if (sepType == 1) { + // Middle group. + if (isPrimary) { + return count == grouping1; + } else { + return count == grouping2; + } + } else { + U_ASSERT(sepType == 2); + // After the decimal separator. + return true; + } + } else { + if (sepType == 1) { + // #11230: don't accept middle groups with only 1 digit. + return count != 1; + } else { + return true; + } + } +} + +bool DecimalMatcher::smokeTest(const StringSegment& segment) const { + // The common case uses a static leadSet for efficiency. + if (fLocalDigitStrings.isNull() && leadSet != nullptr) { + return segment.startsWith(*leadSet); + } + if (segment.startsWith(*separatorSet) || u_isdigit(segment.getCodePoint())) { + return true; + } + if (fLocalDigitStrings.isNull()) { + return false; + } + for (int32_t i = 0; i < 10; i++) { + if (segment.startsWith(fLocalDigitStrings[i])) { + return true; + } + } + return false; +} + +UnicodeString DecimalMatcher::toString() const { + return u""; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_decimal.h b/deps/icu-small/source/i18n/numparse_decimal.h new file mode 100644 index 00000000000000..ec6c76487e44fc --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_decimal.h @@ -0,0 +1,76 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_DECIMAL_H__ +#define __NUMPARSE_DECIMAL_H__ + +#include "unicode/uniset.h" +#include "numparse_types.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + +using ::icu::number::impl::Grouper; + +class DecimalMatcher : public NumberParseMatcher, public UMemory { + public: + DecimalMatcher() = default; // WARNING: Leaves the object in an unusable state + + DecimalMatcher(const DecimalFormatSymbols& symbols, const Grouper& grouper, + parse_flags_t parseFlags); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + bool + match(StringSegment& segment, ParsedNumber& result, int8_t exponentSign, UErrorCode& status) const; + + bool smokeTest(const StringSegment& segment) const override; + + UnicodeString toString() const override; + + private: + /** If true, only accept strings whose grouping sizes match the locale */ + bool requireGroupingMatch; + + /** If true, do not accept grouping separators at all */ + bool groupingDisabled; + + // Fraction grouping parsing is disabled for now but could be enabled later. + // See http://bugs.icu-project.org/trac/ticket/10794 + // bool fractionGrouping; + + /** If true, do not accept numbers in the fraction */ + bool integerOnly; + + int16_t grouping1; + int16_t grouping2; + + UnicodeString groupingSeparator; + UnicodeString decimalSeparator; + + // Assumption: these sets all consist of single code points. If this assumption needs to be broken, + // fix getLeadCodePoints() as well as matching logic. Be careful of the performance impact. + const UnicodeSet* groupingUniSet; + const UnicodeSet* decimalUniSet; + const UnicodeSet* separatorSet; + const UnicodeSet* leadSet; + + // Make this class the owner of a few objects that could be allocated. + // The first three LocalPointers are used for assigning ownership only. + LocalPointer fLocalDecimalUniSet; + LocalPointer fLocalSeparatorSet; + LocalArray fLocalDigitStrings; + + bool validateGroup(int32_t sepType, int32_t count, bool isPrimary) const; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_DECIMAL_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_impl.cpp b/deps/icu-small/source/i18n/numparse_impl.cpp new file mode 100644 index 00000000000000..5fa52f63351c31 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_impl.cpp @@ -0,0 +1,361 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include +#include +#include "number_types.h" +#include "number_patternstring.h" +#include "numparse_types.h" +#include "numparse_impl.h" +#include "numparse_symbols.h" +#include "numparse_decimal.h" +#include "unicode/numberformatter.h" +#include "cstr.h" +#include "number_mapper.h" +#include "static_unicode_sets.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +NumberParseMatcher::~NumberParseMatcher() = default; + + +NumberParserImpl* +NumberParserImpl::createSimpleParser(const Locale& locale, const UnicodeString& patternString, + parse_flags_t parseFlags, UErrorCode& status) { + + LocalPointer parser(new NumberParserImpl(parseFlags)); + DecimalFormatSymbols symbols(locale, status); + + parser->fLocalMatchers.ignorables = {unisets::DEFAULT_IGNORABLES}; + IgnorablesMatcher& ignorables = parser->fLocalMatchers.ignorables; + + DecimalFormatSymbols dfs(locale, status); + dfs.setSymbol(DecimalFormatSymbols::kCurrencySymbol, u"IU$"); + dfs.setSymbol(DecimalFormatSymbols::kIntlCurrencySymbol, u"ICU"); + CurrencySymbols currencySymbols({u"ICU", status}, locale, dfs, status); + + ParsedPatternInfo patternInfo; + PatternParser::parseToPatternInfo(patternString, patternInfo, status); + + // The following statements set up the affix matchers. + AffixTokenMatcherSetupData affixSetupData = { + currencySymbols, symbols, ignorables, locale, parseFlags}; + parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; + parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; + parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( + patternInfo, *parser, ignorables, parseFlags, status); + + Grouper grouper = Grouper::forStrategy(UNUM_GROUPING_AUTO); + grouper.setLocaleData(patternInfo, locale); + + parser->addMatcher(parser->fLocalMatchers.ignorables); + parser->addMatcher(parser->fLocalMatchers.decimal = {symbols, grouper, parseFlags}); + parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false}); + parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false}); + parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); + parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); + parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); + parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); + parser->addMatcher(parser->fLocalMatchers.padding = {u"@"}); + parser->addMatcher(parser->fLocalMatchers.scientific = {symbols, grouper}); + parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, parseFlags, status}); +// parser.addMatcher(new RequireNumberMatcher()); + + parser->freeze(); + return parser.orphan(); +} + +NumberParserImpl* +NumberParserImpl::createParserFromProperties(const number::impl::DecimalFormatProperties& properties, + const DecimalFormatSymbols& symbols, bool parseCurrency, + UErrorCode& status) { + Locale locale = symbols.getLocale(); + PropertiesAffixPatternProvider localPAPP; + CurrencyPluralInfoAffixProvider localCPIAP; + AffixPatternProvider* affixProvider; + if (properties.currencyPluralInfo.fPtr.isNull()) { + localPAPP.setTo(properties, status); + affixProvider = &localPAPP; + } else { + localCPIAP.setTo(*properties.currencyPluralInfo.fPtr, properties, status); + affixProvider = &localCPIAP; + } + if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; } + CurrencyUnit currency = resolveCurrency(properties, locale, status); + CurrencySymbols currencySymbols(currency, locale, symbols, status); + bool isStrict = properties.parseMode.getOrDefault(PARSE_MODE_STRICT) == PARSE_MODE_STRICT; + Grouper grouper = Grouper::forProperties(properties); + int parseFlags = 0; + if (affixProvider == nullptr || U_FAILURE(status)) { return nullptr; } + if (!properties.parseCaseSensitive) { + parseFlags |= PARSE_FLAG_IGNORE_CASE; + } + if (properties.parseIntegerOnly) { + parseFlags |= PARSE_FLAG_INTEGER_ONLY; + } + if (properties.signAlwaysShown) { + parseFlags |= PARSE_FLAG_PLUS_SIGN_ALLOWED; + } + if (isStrict) { + parseFlags |= PARSE_FLAG_STRICT_GROUPING_SIZE; + parseFlags |= PARSE_FLAG_STRICT_SEPARATORS; + parseFlags |= PARSE_FLAG_USE_FULL_AFFIXES; + parseFlags |= PARSE_FLAG_EXACT_AFFIX; + } else { + parseFlags |= PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES; + } + if (grouper.getPrimary() <= 0) { + parseFlags |= PARSE_FLAG_GROUPING_DISABLED; + } + if (parseCurrency || affixProvider->hasCurrencySign()) { + parseFlags |= PARSE_FLAG_MONETARY_SEPARATORS; + } + if (!parseCurrency) { + parseFlags |= PARSE_FLAG_NO_FOREIGN_CURRENCY; + } + + LocalPointer parser(new NumberParserImpl(parseFlags)); + + parser->fLocalMatchers.ignorables = { + isStrict ? unisets::STRICT_IGNORABLES : unisets::DEFAULT_IGNORABLES}; + IgnorablesMatcher& ignorables = parser->fLocalMatchers.ignorables; + + ////////////////////// + /// AFFIX MATCHERS /// + ////////////////////// + + // The following statements set up the affix matchers. + AffixTokenMatcherSetupData affixSetupData = { + currencySymbols, symbols, ignorables, locale, parseFlags}; + parser->fLocalMatchers.affixTokenMatcherWarehouse = {&affixSetupData}; + parser->fLocalMatchers.affixMatcherWarehouse = {&parser->fLocalMatchers.affixTokenMatcherWarehouse}; + parser->fLocalMatchers.affixMatcherWarehouse.createAffixMatchers( + *affixProvider, *parser, ignorables, parseFlags, status); + + //////////////////////// + /// CURRENCY MATCHER /// + //////////////////////// + + if (parseCurrency || affixProvider->hasCurrencySign()) { + parser->addMatcher(parser->fLocalMatchers.currency = {currencySymbols, symbols, parseFlags, status}); + } + + /////////////// + /// PERCENT /// + /////////////// + + // ICU-TC meeting, April 11, 2018: accept percent/permille only if it is in the pattern, + // and to maintain regressive behavior, divide by 100 even if no percent sign is present. + if (affixProvider->containsSymbolType(AffixPatternType::TYPE_PERCENT, status)) { + parser->addMatcher(parser->fLocalMatchers.percent = {symbols}); + } + if (affixProvider->containsSymbolType(AffixPatternType::TYPE_PERMILLE, status)) { + parser->addMatcher(parser->fLocalMatchers.permille = {symbols}); + } + + /////////////////////////////// + /// OTHER STANDARD MATCHERS /// + /////////////////////////////// + + if (!isStrict) { + parser->addMatcher(parser->fLocalMatchers.plusSign = {symbols, false}); + parser->addMatcher(parser->fLocalMatchers.minusSign = {symbols, false}); + } + parser->addMatcher(parser->fLocalMatchers.nan = {symbols}); + parser->addMatcher(parser->fLocalMatchers.infinity = {symbols}); + UnicodeString padString = properties.padString; + if (!padString.isBogus() && !ignorables.getSet()->contains(padString)) { + parser->addMatcher(parser->fLocalMatchers.padding = {padString}); + } + parser->addMatcher(parser->fLocalMatchers.ignorables); + parser->addMatcher(parser->fLocalMatchers.decimal = {symbols, grouper, parseFlags}); + // NOTE: parseNoExponent doesn't disable scientific parsing if we have a scientific formatter + if (!properties.parseNoExponent || properties.minimumExponentDigits > 0) { + parser->addMatcher(parser->fLocalMatchers.scientific = {symbols, grouper}); + } + + ////////////////// + /// VALIDATORS /// + ////////////////// + + parser->addMatcher(parser->fLocalValidators.number = {}); + if (isStrict) { + parser->addMatcher(parser->fLocalValidators.affix = {}); + } + if (parseCurrency) { + parser->addMatcher(parser->fLocalValidators.currency = {}); + } + if (properties.decimalPatternMatchRequired) { + bool patternHasDecimalSeparator = + properties.decimalSeparatorAlwaysShown || properties.maximumFractionDigits != 0; + parser->addMatcher(parser->fLocalValidators.decimalSeparator = {patternHasDecimalSeparator}); + } + // The multiplier takes care of scaling percentages. + Scale multiplier = scaleFromProperties(properties); + if (multiplier.isValid()) { + parser->addMatcher(parser->fLocalValidators.multiplier = {multiplier}); + } + + parser->freeze(); + return parser.orphan(); +} + +NumberParserImpl::NumberParserImpl(parse_flags_t parseFlags) + : fParseFlags(parseFlags) { +} + +NumberParserImpl::~NumberParserImpl() { + fNumMatchers = 0; +} + +void NumberParserImpl::addMatcher(NumberParseMatcher& matcher) { + if (fNumMatchers + 1 > fMatchers.getCapacity()) { + fMatchers.resize(fNumMatchers * 2, fNumMatchers); + } + fMatchers[fNumMatchers] = &matcher; + fNumMatchers++; +} + +void NumberParserImpl::freeze() { + fFrozen = true; +} + +parse_flags_t NumberParserImpl::getParseFlags() const { + return fParseFlags; +} + +void NumberParserImpl::parse(const UnicodeString& input, bool greedy, ParsedNumber& result, + UErrorCode& status) const { + return parse(input, 0, greedy, result, status); +} + +void NumberParserImpl::parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, + UErrorCode& status) const { + if (U_FAILURE(status)) { + return; + } + U_ASSERT(fFrozen); + // TODO: Check start >= 0 and start < input.length() + StringSegment segment(input, 0 != (fParseFlags & PARSE_FLAG_IGNORE_CASE)); + segment.adjustOffset(start); + if (greedy) { + parseGreedyRecursive(segment, result, status); + } else { + parseLongestRecursive(segment, result, status); + } + for (int32_t i = 0; i < fNumMatchers; i++) { + fMatchers[i]->postProcess(result); + } + result.postProcess(); +} + +void NumberParserImpl::parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, + UErrorCode& status) const { + // Base Case + if (segment.length() == 0) { + return; + } + + int initialOffset = segment.getOffset(); + for (int32_t i = 0; i < fNumMatchers; i++) { + const NumberParseMatcher* matcher = fMatchers[i]; + if (!matcher->smokeTest(segment)) { + continue; + } + matcher->match(segment, result, status); + if (U_FAILURE(status)) { + return; + } + if (segment.getOffset() != initialOffset) { + // In a greedy parse, recurse on only the first match. + parseGreedyRecursive(segment, result, status); + // The following line resets the offset so that the StringSegment says the same across + // the function + // call boundary. Since we recurse only once, this line is not strictly necessary. + segment.setOffset(initialOffset); + return; + } + } + + // NOTE: If we get here, the greedy parse completed without consuming the entire string. +} + +void NumberParserImpl::parseLongestRecursive(StringSegment& segment, ParsedNumber& result, + UErrorCode& status) const { + // Base Case + if (segment.length() == 0) { + return; + } + + // TODO: Give a nice way for the matcher to reset the ParsedNumber? + ParsedNumber initial(result); + ParsedNumber candidate; + + int initialOffset = segment.getOffset(); + for (int32_t i = 0; i < fNumMatchers; i++) { + const NumberParseMatcher* matcher = fMatchers[i]; + if (!matcher->smokeTest(segment)) { + continue; + } + + // In a non-greedy parse, we attempt all possible matches and pick the best. + for (int32_t charsToConsume = 0; charsToConsume < segment.length();) { + charsToConsume += U16_LENGTH(segment.codePointAt(charsToConsume)); + + // Run the matcher on a segment of the current length. + candidate = initial; + segment.setLength(charsToConsume); + bool maybeMore = matcher->match(segment, candidate, status); + segment.resetLength(); + if (U_FAILURE(status)) { + return; + } + + // If the entire segment was consumed, recurse. + if (segment.getOffset() - initialOffset == charsToConsume) { + parseLongestRecursive(segment, candidate, status); + if (U_FAILURE(status)) { + return; + } + if (candidate.isBetterThan(result)) { + result = candidate; + } + } + + // Since the segment can be re-used, reset the offset. + // This does not have an effect if the matcher did not consume any chars. + segment.setOffset(initialOffset); + + // Unless the matcher wants to see the next char, continue to the next matcher. + if (!maybeMore) { + break; + } + } + } +} + +UnicodeString NumberParserImpl::toString() const { + UnicodeString result(u"toString()); + } + result.append(u" ]>", -1); + return result; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_impl.h b/deps/icu-small/source/i18n/numparse_impl.h new file mode 100644 index 00000000000000..992114c7edee18 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_impl.h @@ -0,0 +1,109 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_IMPL_H__ +#define __NUMPARSE_IMPL_H__ + +#include "numparse_types.h" +#include "numparse_decimal.h" +#include "numparse_symbols.h" +#include "numparse_scientific.h" +#include "unicode/uniset.h" +#include "numparse_currency.h" +#include "numparse_affixes.h" +#include "number_decimfmtprops.h" +#include "unicode/localpointer.h" +#include "numparse_validators.h" +#include "number_multiplier.h" + +U_NAMESPACE_BEGIN + +// Export an explicit template instantiation of the MaybeStackArray that is used as a data member of NumberParserImpl. +// When building DLLs for Windows this is required even though no direct access to the MaybeStackArray leaks out of the i18n library. +// (See numparse_compositions.h, numparse_affixes.h, datefmt.h, and others for similar examples.) +#if U_PF_WINDOWS <= U_PLATFORM && U_PLATFORM <= U_PF_CYGWIN +template class U_I18N_API MaybeStackArray; +#endif + +namespace numparse { +namespace impl { + +// Exported as U_I18N_API for tests +class U_I18N_API NumberParserImpl : public MutableMatcherCollection, public UMemory { + public: + virtual ~NumberParserImpl(); + + static NumberParserImpl* createSimpleParser(const Locale& locale, const UnicodeString& patternString, + parse_flags_t parseFlags, UErrorCode& status); + + static NumberParserImpl* createParserFromProperties( + const number::impl::DecimalFormatProperties& properties, const DecimalFormatSymbols& symbols, + bool parseCurrency, UErrorCode& status); + + /** + * Does NOT take ownership of the matcher. The matcher MUST remain valid for the lifespan of the + * NumberParserImpl. + * @param matcher The matcher to reference. + */ + void addMatcher(NumberParseMatcher& matcher) override; + + void freeze(); + + parse_flags_t getParseFlags() const; + + void parse(const UnicodeString& input, bool greedy, ParsedNumber& result, UErrorCode& status) const; + + void parse(const UnicodeString& input, int32_t start, bool greedy, ParsedNumber& result, + UErrorCode& status) const; + + UnicodeString toString() const; + + private: + parse_flags_t fParseFlags; + int32_t fNumMatchers = 0; + // NOTE: The stack capacity for fMatchers and fLeads should be the same + MaybeStackArray fMatchers; + bool fFrozen = false; + + // WARNING: All of these matchers start in an undefined state (default-constructed). + // You must use an assignment operator on them before using. + struct { + IgnorablesMatcher ignorables; + InfinityMatcher infinity; + MinusSignMatcher minusSign; + NanMatcher nan; + PaddingMatcher padding; + PercentMatcher percent; + PermilleMatcher permille; + PlusSignMatcher plusSign; + DecimalMatcher decimal; + ScientificMatcher scientific; + CombinedCurrencyMatcher currency; + AffixMatcherWarehouse affixMatcherWarehouse; + AffixTokenMatcherWarehouse affixTokenMatcherWarehouse; + } fLocalMatchers; + struct { + RequireAffixValidator affix; + RequireCurrencyValidator currency; + RequireDecimalSeparatorValidator decimalSeparator; + RequireNumberValidator number; + MultiplierParseHandler multiplier; + } fLocalValidators; + + explicit NumberParserImpl(parse_flags_t parseFlags); + + void parseGreedyRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; + + void parseLongestRecursive(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_IMPL_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_parsednumber.cpp b/deps/icu-small/source/i18n/numparse_parsednumber.cpp new file mode 100644 index 00000000000000..98da4e8319227a --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_parsednumber.cpp @@ -0,0 +1,122 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "number_decimalquantity.h" +#include "putilimp.h" +#include + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +ParsedNumber::ParsedNumber() { + clear(); +} + +void ParsedNumber::clear() { + quantity.bogus = true; + charEnd = 0; + flags = 0; + prefix.setToBogus(); + suffix.setToBogus(); + currencyCode[0] = 0; +} + +void ParsedNumber::setCharsConsumed(const StringSegment& segment) { + charEnd = segment.getOffset(); +} + +void ParsedNumber::postProcess() { + if (!quantity.bogus && 0 != (flags & FLAG_NEGATIVE)) { + quantity.negate(); + } +} + +bool ParsedNumber::success() const { + return charEnd > 0 && 0 == (flags & FLAG_FAIL); +} + +bool ParsedNumber::seenNumber() const { + return !quantity.bogus || 0 != (flags & FLAG_NAN) || 0 != (flags & FLAG_INFINITY); +} + +double ParsedNumber::getDouble() const { + bool sawNaN = 0 != (flags & FLAG_NAN); + bool sawInfinity = 0 != (flags & FLAG_INFINITY); + + // Check for NaN, infinity, and -0.0 + if (sawNaN) { + // Can't use NAN or std::nan because the byte pattern is platform-dependent; + // MSVC sets the sign bit, but Clang and GCC do not + return uprv_getNaN(); + } + if (sawInfinity) { + if (0 != (flags & FLAG_NEGATIVE)) { + return -INFINITY; + } else { + return INFINITY; + } + } + U_ASSERT(!quantity.bogus); + if (quantity.isZero() && quantity.isNegative()) { + return -0.0; + } + + if (quantity.fitsInLong()) { + return static_cast(quantity.toLong()); + } else { + return quantity.toDouble(); + } +} + +void ParsedNumber::populateFormattable(Formattable& output, parse_flags_t parseFlags) const { + bool sawNaN = 0 != (flags & FLAG_NAN); + bool sawInfinity = 0 != (flags & FLAG_INFINITY); + bool integerOnly = 0 != (parseFlags & PARSE_FLAG_INTEGER_ONLY); + + // Check for NaN, infinity, and -0.0 + if (sawNaN) { + // Can't use NAN or std::nan because the byte pattern is platform-dependent; + // MSVC sets the sign bit, but Clang and GCC do not + output.setDouble(uprv_getNaN()); + return; + } + if (sawInfinity) { + if (0 != (flags & FLAG_NEGATIVE)) { + output.setDouble(-INFINITY); + return; + } else { + output.setDouble(INFINITY); + return; + } + } + U_ASSERT(!quantity.bogus); + if (quantity.isZero() && quantity.isNegative() && !integerOnly) { + output.setDouble(-0.0); + return; + } + + // All other numbers + output.adoptDecimalQuantity(new DecimalQuantity(quantity)); +} + +bool ParsedNumber::isBetterThan(const ParsedNumber& other) { + // Favor results with strictly more characters consumed. + return charEnd > other.charEnd; +} + + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_scientific.cpp b/deps/icu-small/source/i18n/numparse_scientific.cpp new file mode 100644 index 00000000000000..611695e57d4743 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_scientific.cpp @@ -0,0 +1,133 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_scientific.h" +#include "static_unicode_sets.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +namespace { + +inline const UnicodeSet& minusSignSet() { + return *unisets::get(unisets::MINUS_SIGN); +} + +inline const UnicodeSet& plusSignSet() { + return *unisets::get(unisets::PLUS_SIGN); +} + +} // namespace + + +ScientificMatcher::ScientificMatcher(const DecimalFormatSymbols& dfs, const Grouper& grouper) + : fExponentSeparatorString(dfs.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol)), + fExponentMatcher(dfs, grouper, PARSE_FLAG_INTEGER_ONLY | PARSE_FLAG_GROUPING_DISABLED) { + + const UnicodeString& minusSign = dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol); + if (minusSignSet().contains(minusSign)) { + fCustomMinusSign.setToBogus(); + } else { + fCustomMinusSign = minusSign; + } + + const UnicodeString& plusSign = dfs.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol); + if (plusSignSet().contains(plusSign)) { + fCustomPlusSign.setToBogus(); + } else { + fCustomPlusSign = plusSign; + } +} + +bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const { + // Only accept scientific notation after the mantissa. + if (!result.seenNumber()) { + return false; + } + + // First match the scientific separator, and then match another number after it. + // NOTE: This is guarded by the smoke test; no need to check fExponentSeparatorString length again. + int overlap1 = segment.getCommonPrefixLength(fExponentSeparatorString); + if (overlap1 == fExponentSeparatorString.length()) { + // Full exponent separator match. + + // First attempt to get a code point, returning true if we can't get one. + if (segment.length() == overlap1) { + return true; + } + segment.adjustOffset(overlap1); + + // Allow a sign, and then try to match digits. + int8_t exponentSign = 1; + if (segment.startsWith(minusSignSet())) { + exponentSign = -1; + segment.adjustOffsetByCodePoint(); + } else if (segment.startsWith(plusSignSet())) { + segment.adjustOffsetByCodePoint(); + } else if (segment.startsWith(fCustomMinusSign)) { + // Note: call site is guarded with startsWith, which returns false on empty string + int32_t overlap2 = segment.getCommonPrefixLength(fCustomMinusSign); + if (overlap2 != fCustomMinusSign.length()) { + // Partial custom sign match; un-match the exponent separator. + segment.adjustOffset(-overlap1); + return true; + } + exponentSign = -1; + segment.adjustOffset(overlap2); + } else if (segment.startsWith(fCustomPlusSign)) { + // Note: call site is guarded with startsWith, which returns false on empty string + int32_t overlap2 = segment.getCommonPrefixLength(fCustomPlusSign); + if (overlap2 != fCustomPlusSign.length()) { + // Partial custom sign match; un-match the exponent separator. + segment.adjustOffset(-overlap1); + return true; + } + segment.adjustOffset(overlap2); + } + + // We are supposed to accept E0 after NaN, so we need to make sure result.quantity is available. + bool wasBogus = result.quantity.bogus; + result.quantity.bogus = false; + int digitsOffset = segment.getOffset(); + bool digitsReturnValue = fExponentMatcher.match(segment, result, exponentSign, status); + result.quantity.bogus = wasBogus; + + if (segment.getOffset() != digitsOffset) { + // At least one exponent digit was matched. + result.flags |= FLAG_HAS_EXPONENT; + } else { + // No exponent digits were matched; un-match the exponent separator. + segment.adjustOffset(-overlap1); + } + return digitsReturnValue; + + } else if (overlap1 == segment.length()) { + // Partial exponent separator match + return true; + } + + // No match + return false; +} + +bool ScientificMatcher::smokeTest(const StringSegment& segment) const { + return segment.startsWith(fExponentSeparatorString); +} + +UnicodeString ScientificMatcher::toString() const { + return u""; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_scientific.h b/deps/icu-small/source/i18n/numparse_scientific.h new file mode 100644 index 00000000000000..ddecf858af3584 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_scientific.h @@ -0,0 +1,45 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_SCIENTIFIC_H__ +#define __NUMPARSE_SCIENTIFIC_H__ + +#include "numparse_types.h" +#include "numparse_decimal.h" +#include "unicode/numberformatter.h" + +using icu::number::impl::Grouper; + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + + +class ScientificMatcher : public NumberParseMatcher, public UMemory { + public: + ScientificMatcher() = default; // WARNING: Leaves the object in an unusable state + + ScientificMatcher(const DecimalFormatSymbols& dfs, const Grouper& grouper); + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + bool smokeTest(const StringSegment& segment) const override; + + UnicodeString toString() const override; + + private: + UnicodeString fExponentSeparatorString; + DecimalMatcher fExponentMatcher; + UnicodeString fCustomMinusSign; + UnicodeString fCustomPlusSign; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_SCIENTIFIC_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_stringsegment.cpp b/deps/icu-small/source/i18n/numparse_stringsegment.cpp new file mode 100644 index 00000000000000..3db4fe618a603a --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_stringsegment.cpp @@ -0,0 +1,146 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_stringsegment.h" +#include "putilimp.h" +#include "unicode/utf16.h" +#include "unicode/uniset.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +StringSegment::StringSegment(const UnicodeString& str, bool ignoreCase) + : fStr(str), fStart(0), fEnd(str.length()), + fFoldCase(ignoreCase) {} + +int32_t StringSegment::getOffset() const { + return fStart; +} + +void StringSegment::setOffset(int32_t start) { + fStart = start; +} + +void StringSegment::adjustOffset(int32_t delta) { + fStart += delta; +} + +void StringSegment::adjustOffsetByCodePoint() { + fStart += U16_LENGTH(getCodePoint()); +} + +void StringSegment::setLength(int32_t length) { + fEnd = fStart + length; +} + +void StringSegment::resetLength() { + fEnd = fStr.length(); +} + +int32_t StringSegment::length() const { + return fEnd - fStart; +} + +char16_t StringSegment::charAt(int32_t index) const { + return fStr.charAt(index + fStart); +} + +UChar32 StringSegment::codePointAt(int32_t index) const { + return fStr.char32At(index + fStart); +} + +UnicodeString StringSegment::toUnicodeString() const { + return UnicodeString(fStr.getBuffer() + fStart, fEnd - fStart); +} + +const UnicodeString StringSegment::toTempUnicodeString() const { + // Use the readonly-aliasing constructor for efficiency. + return UnicodeString(FALSE, fStr.getBuffer() + fStart, fEnd - fStart); +} + +UChar32 StringSegment::getCodePoint() const { + char16_t lead = fStr.charAt(fStart); + if (U16_IS_LEAD(lead) && fStart + 1 < fEnd) { + return fStr.char32At(fStart); + } else if (U16_IS_SURROGATE(lead)) { + return -1; + } else { + return lead; + } +} + +bool StringSegment::startsWith(UChar32 otherCp) const { + return codePointsEqual(getCodePoint(), otherCp, fFoldCase); +} + +bool StringSegment::startsWith(const UnicodeSet& uniset) const { + // TODO: Move UnicodeSet case-folding logic here. + // TODO: Handle string matches here instead of separately. + UChar32 cp = getCodePoint(); + if (cp == -1) { + return false; + } + return uniset.contains(cp); +} + +bool StringSegment::startsWith(const UnicodeString& other) const { + if (other.isBogus() || other.length() == 0 || length() == 0) { + return false; + } + int cp1 = getCodePoint(); + int cp2 = other.char32At(0); + return codePointsEqual(cp1, cp2, fFoldCase); +} + +int32_t StringSegment::getCommonPrefixLength(const UnicodeString& other) { + return getPrefixLengthInternal(other, fFoldCase); +} + +int32_t StringSegment::getCaseSensitivePrefixLength(const UnicodeString& other) { + return getPrefixLengthInternal(other, false); +} + +int32_t StringSegment::getPrefixLengthInternal(const UnicodeString& other, bool foldCase) { + U_ASSERT(other.length() > 0); + int32_t offset = 0; + for (; offset < uprv_min(length(), other.length());) { + // TODO: case-fold code points, not chars + char16_t c1 = charAt(offset); + char16_t c2 = other.charAt(offset); + if (!codePointsEqual(c1, c2, foldCase)) { + break; + } + offset++; + } + return offset; +} + +bool StringSegment::codePointsEqual(UChar32 cp1, UChar32 cp2, bool foldCase) { + if (cp1 == cp2) { + return true; + } + if (!foldCase) { + return false; + } + cp1 = u_foldCase(cp1, TRUE); + cp2 = u_foldCase(cp2, TRUE); + return cp1 == cp2; +} + +bool StringSegment::operator==(const UnicodeString& other) const { + return toTempUnicodeString() == other; +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_stringsegment.h b/deps/icu-small/source/i18n/numparse_stringsegment.h new file mode 100644 index 00000000000000..7a84444d414889 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_stringsegment.h @@ -0,0 +1,24 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_STRINGSEGMENT_H__ +#define __NUMPARSE_STRINGSEGMENT_H__ + +#include "numparse_types.h" +#include "number_types.h" +#include "unicode/unistr.h" + +U_NAMESPACE_BEGIN +namespace numparse { +namespace impl { + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_STRINGSEGMENT_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_symbols.cpp b/deps/icu-small/source/i18n/numparse_symbols.cpp new file mode 100644 index 00000000000000..9ccceec8475d01 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_symbols.cpp @@ -0,0 +1,193 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_symbols.h" +#include "numparse_utils.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +SymbolMatcher::SymbolMatcher(const UnicodeString& symbolString, unisets::Key key) { + fUniSet = unisets::get(key); + if (fUniSet->contains(symbolString)) { + fString.setToBogus(); + } else { + fString = symbolString; + } +} + +const UnicodeSet* SymbolMatcher::getSet() const { + return fUniSet; +} + +bool SymbolMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode&) const { + // Smoke test first; this matcher might be disabled. + if (isDisabled(result)) { + return false; + } + + // Test the string first in order to consume trailing chars greedily. + int overlap = 0; + if (!fString.isEmpty()) { + overlap = segment.getCommonPrefixLength(fString); + if (overlap == fString.length()) { + segment.adjustOffset(fString.length()); + accept(segment, result); + return false; + } + } + + int cp = segment.getCodePoint(); + if (cp != -1 && fUniSet->contains(cp)) { + segment.adjustOffset(U16_LENGTH(cp)); + accept(segment, result); + return false; + } + + return overlap == segment.length(); +} + +bool SymbolMatcher::smokeTest(const StringSegment& segment) const { + return segment.startsWith(*fUniSet) || segment.startsWith(fString); +} + +UnicodeString SymbolMatcher::toString() const { + // TODO: Customize output for each symbol + return u""; +} + + +IgnorablesMatcher::IgnorablesMatcher(unisets::Key key) + : SymbolMatcher({}, key) { +} + +bool IgnorablesMatcher::isFlexible() const { + return true; +} + +UnicodeString IgnorablesMatcher::toString() const { + return u""; +} + +bool IgnorablesMatcher::isDisabled(const ParsedNumber&) const { + return false; +} + +void IgnorablesMatcher::accept(StringSegment&, ParsedNumber&) const { + // No-op +} + + +InfinityMatcher::InfinityMatcher(const DecimalFormatSymbols& dfs) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kInfinitySymbol), unisets::INFINITY_KEY) { +} + +bool InfinityMatcher::isDisabled(const ParsedNumber& result) const { + return 0 != (result.flags & FLAG_INFINITY); +} + +void InfinityMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_INFINITY; + result.setCharsConsumed(segment); +} + + +MinusSignMatcher::MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol), unisets::MINUS_SIGN), + fAllowTrailing(allowTrailing) { +} + +bool MinusSignMatcher::isDisabled(const ParsedNumber& result) const { + return !fAllowTrailing && result.seenNumber(); +} + +void MinusSignMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_NEGATIVE; + result.setCharsConsumed(segment); +} + + +NanMatcher::NanMatcher(const DecimalFormatSymbols& dfs) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kNaNSymbol), unisets::EMPTY) { +} + +bool NanMatcher::isDisabled(const ParsedNumber& result) const { + return result.seenNumber(); +} + +void NanMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_NAN; + result.setCharsConsumed(segment); +} + + +PaddingMatcher::PaddingMatcher(const UnicodeString& padString) + : SymbolMatcher(padString, unisets::EMPTY) {} + +bool PaddingMatcher::isFlexible() const { + return true; +} + +bool PaddingMatcher::isDisabled(const ParsedNumber&) const { + return false; +} + +void PaddingMatcher::accept(StringSegment&, ParsedNumber&) const { + // No-op +} + + +PercentMatcher::PercentMatcher(const DecimalFormatSymbols& dfs) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPercentSymbol), unisets::PERCENT_SIGN) { +} + +bool PercentMatcher::isDisabled(const ParsedNumber& result) const { + return 0 != (result.flags & FLAG_PERCENT); +} + +void PercentMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_PERCENT; + result.setCharsConsumed(segment); +} + + +PermilleMatcher::PermilleMatcher(const DecimalFormatSymbols& dfs) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPerMillSymbol), unisets::PERMILLE_SIGN) { +} + +bool PermilleMatcher::isDisabled(const ParsedNumber& result) const { + return 0 != (result.flags & FLAG_PERMILLE); +} + +void PermilleMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.flags |= FLAG_PERMILLE; + result.setCharsConsumed(segment); +} + + +PlusSignMatcher::PlusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing) + : SymbolMatcher(dfs.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol), unisets::PLUS_SIGN), + fAllowTrailing(allowTrailing) { +} + +bool PlusSignMatcher::isDisabled(const ParsedNumber& result) const { + return !fAllowTrailing && result.seenNumber(); +} + +void PlusSignMatcher::accept(StringSegment& segment, ParsedNumber& result) const { + result.setCharsConsumed(segment); +} + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_symbols.h b/deps/icu-small/source/i18n/numparse_symbols.h new file mode 100644 index 00000000000000..8912ee95b0d009 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_symbols.h @@ -0,0 +1,173 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_SYMBOLS_H__ +#define __NUMPARSE_SYMBOLS_H__ + +#include "numparse_types.h" +#include "unicode/uniset.h" +#include "static_unicode_sets.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + + +/** + * A base class for many matchers that performs a simple match against a UnicodeString and/or UnicodeSet. + * + * @author sffc + */ +// Exported as U_I18N_API for tests +class U_I18N_API SymbolMatcher : public NumberParseMatcher, public UMemory { + public: + SymbolMatcher() = default; // WARNING: Leaves the object in an unusable state + + const UnicodeSet* getSet() const; + + bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const override; + + bool smokeTest(const StringSegment& segment) const override; + + UnicodeString toString() const override; + + virtual bool isDisabled(const ParsedNumber& result) const = 0; + + virtual void accept(StringSegment& segment, ParsedNumber& result) const = 0; + + protected: + UnicodeString fString; + const UnicodeSet* fUniSet; // a reference from numparse_unisets.h; never owned + + SymbolMatcher(const UnicodeString& symbolString, unisets::Key key); +}; + + +// Exported as U_I18N_API for tests +class U_I18N_API IgnorablesMatcher : public SymbolMatcher { + public: + IgnorablesMatcher() = default; // WARNING: Leaves the object in an unusable state + + IgnorablesMatcher(unisets::Key key); + + bool isFlexible() const override; + + UnicodeString toString() const override; + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + +class InfinityMatcher : public SymbolMatcher { + public: + InfinityMatcher() = default; // WARNING: Leaves the object in an unusable state + + InfinityMatcher(const DecimalFormatSymbols& dfs); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + +// Exported as U_I18N_API for tests +class U_I18N_API MinusSignMatcher : public SymbolMatcher { + public: + MinusSignMatcher() = default; // WARNING: Leaves the object in an unusable state + + MinusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; + + private: + bool fAllowTrailing; +}; + + +class NanMatcher : public SymbolMatcher { + public: + NanMatcher() = default; // WARNING: Leaves the object in an unusable state + + NanMatcher(const DecimalFormatSymbols& dfs); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + +class PaddingMatcher : public SymbolMatcher { + public: + PaddingMatcher() = default; // WARNING: Leaves the object in an unusable state + + PaddingMatcher(const UnicodeString& padString); + + bool isFlexible() const override; + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + +// Exported as U_I18N_API for tests +class U_I18N_API PercentMatcher : public SymbolMatcher { + public: + PercentMatcher() = default; // WARNING: Leaves the object in an unusable state + + PercentMatcher(const DecimalFormatSymbols& dfs); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + +// Exported as U_I18N_API for tests +class U_I18N_API PermilleMatcher : public SymbolMatcher { + public: + PermilleMatcher() = default; // WARNING: Leaves the object in an unusable state + + PermilleMatcher(const DecimalFormatSymbols& dfs); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; +}; + + +// Exported as U_I18N_API for tests +class U_I18N_API PlusSignMatcher : public SymbolMatcher { + public: + PlusSignMatcher() = default; // WARNING: Leaves the object in an unusable state + + PlusSignMatcher(const DecimalFormatSymbols& dfs, bool allowTrailing); + + protected: + bool isDisabled(const ParsedNumber& result) const override; + + void accept(StringSegment& segment, ParsedNumber& result) const override; + + private: + bool fAllowTrailing; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_SYMBOLS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_types.h b/deps/icu-small/source/i18n/numparse_types.h new file mode 100644 index 00000000000000..ab591eaba83af5 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_types.h @@ -0,0 +1,377 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_TYPES_H__ +#define __NUMPARSE_TYPES_H__ + +#include "unicode/uobject.h" +#include "number_decimalquantity.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + +// Forward-declarations +class StringSegment; +class ParsedNumber; + +typedef int32_t result_flags_t; +typedef int32_t parse_flags_t; + +/** Flags for the type result_flags_t */ +enum ResultFlags { + FLAG_NEGATIVE = 0x0001, + FLAG_PERCENT = 0x0002, + FLAG_PERMILLE = 0x0004, + FLAG_HAS_EXPONENT = 0x0008, + // FLAG_HAS_DEFAULT_CURRENCY = 0x0010, // no longer used + FLAG_HAS_DECIMAL_SEPARATOR = 0x0020, + FLAG_NAN = 0x0040, + FLAG_INFINITY = 0x0080, + FLAG_FAIL = 0x0100, +}; + +/** Flags for the type parse_flags_t */ +enum ParseFlags { + PARSE_FLAG_IGNORE_CASE = 0x0001, + PARSE_FLAG_MONETARY_SEPARATORS = 0x0002, + PARSE_FLAG_STRICT_SEPARATORS = 0x0004, + PARSE_FLAG_STRICT_GROUPING_SIZE = 0x0008, + PARSE_FLAG_INTEGER_ONLY = 0x0010, + PARSE_FLAG_GROUPING_DISABLED = 0x0020, + // PARSE_FLAG_FRACTION_GROUPING_ENABLED = 0x0040, // see #10794 + PARSE_FLAG_INCLUDE_UNPAIRED_AFFIXES = 0x0080, + PARSE_FLAG_USE_FULL_AFFIXES = 0x0100, + PARSE_FLAG_EXACT_AFFIX = 0x0200, + PARSE_FLAG_PLUS_SIGN_ALLOWED = 0x0400, + // PARSE_FLAG_OPTIMIZE = 0x0800, // no longer used + // PARSE_FLAG_FORCE_BIG_DECIMAL = 0x1000, // not used in ICU4C + PARSE_FLAG_NO_FOREIGN_CURRENCY = 0x2000, +}; + + +// TODO: Is this class worthwhile? +template +class CompactUnicodeString { + public: + CompactUnicodeString() { + static_assert(stackCapacity > 0, "cannot have zero space on stack"); + fBuffer[0] = 0; + } + + CompactUnicodeString(const UnicodeString& text) + : fBuffer(text.length() + 1) { + memcpy(fBuffer.getAlias(), text.getBuffer(), sizeof(UChar) * text.length()); + fBuffer[text.length()] = 0; + } + + inline UnicodeString toAliasedUnicodeString() const { + return UnicodeString(TRUE, fBuffer.getAlias(), -1); + } + + bool operator==(const CompactUnicodeString& other) const { + // Use the alias-only constructor and then call UnicodeString operator== + return toAliasedUnicodeString() == other.toAliasedUnicodeString(); + } + + private: + MaybeStackArray fBuffer; +}; + + +/** + * Struct-like class to hold the results of a parsing routine. + * + * @author sffc + */ +// Exported as U_I18N_API for tests +class U_I18N_API ParsedNumber { + public: + + /** + * The numerical value that was parsed. + */ + ::icu::number::impl::DecimalQuantity quantity; + + /** + * The index of the last char consumed during parsing. If parsing started at index 0, this is equal + * to the number of chars consumed. This is NOT necessarily the same as the StringSegment offset; + * "weak" chars, like whitespace, change the offset, but the charsConsumed is not touched until a + * "strong" char is encountered. + */ + int32_t charEnd; + + /** + * Boolean flags (see constants above). + */ + result_flags_t flags; + + /** + * The pattern string corresponding to the prefix that got consumed. + */ + UnicodeString prefix; + + /** + * The pattern string corresponding to the suffix that got consumed. + */ + UnicodeString suffix; + + /** + * The currency that got consumed. + */ + UChar currencyCode[4]; + + ParsedNumber(); + + ParsedNumber(const ParsedNumber& other) = default; + + ParsedNumber& operator=(const ParsedNumber& other) = default; + + void clear(); + + /** + * Call this method to register that a "strong" char was consumed. This should be done after calling + * {@link StringSegment#setOffset} or {@link StringSegment#adjustOffset} except when the char is + * "weak", like whitespace. + * + *

+ * What is a strong versus weak char? The behavior of number parsing is to "stop" + * after reading the number, even if there is other content following the number. For example, after + * parsing the string "123 " (123 followed by a space), the cursor should be set to 3, not 4, even + * though there are matchers that accept whitespace. In this example, the digits are strong, whereas + * the whitespace is weak. Grouping separators are weak, whereas decimal separators are strong. Most + * other chars are strong. + * + * @param segment + * The current StringSegment, usually immediately following a call to setOffset. + */ + void setCharsConsumed(const StringSegment& segment); + + /** Apply certain number-related flags to the DecimalQuantity. */ + void postProcess(); + + /** + * Returns whether this the parse was successful. To be successful, at least one char must have been + * consumed, and the failure flag must not be set. + */ + bool success() const; + + bool seenNumber() const; + + double getDouble() const; + + void populateFormattable(Formattable& output, parse_flags_t parseFlags) const; + + bool isBetterThan(const ParsedNumber& other); +}; + + +/** + * A mutable class allowing for a String with a variable offset and length. The charAt, length, and + * subSequence methods all operate relative to the fixed offset into the String. + * + * @author sffc + */ +// Exported as U_I18N_API for tests +class U_I18N_API StringSegment : public UMemory { + public: + StringSegment(const UnicodeString& str, bool ignoreCase); + + int32_t getOffset() const; + + void setOffset(int32_t start); + + /** + * Equivalent to setOffset(getOffset()+delta). + * + *

+ * This method is usually called by a Matcher to register that a char was consumed. If the char is + * strong (it usually is, except for things like whitespace), follow this with a call to + * {@link ParsedNumber#setCharsConsumed}. For more information on strong chars, see that method. + */ + void adjustOffset(int32_t delta); + + /** + * Adjusts the offset by the width of the current code point, either 1 or 2 chars. + */ + void adjustOffsetByCodePoint(); + + void setLength(int32_t length); + + void resetLength(); + + int32_t length() const; + + char16_t charAt(int32_t index) const; + + UChar32 codePointAt(int32_t index) const; + + UnicodeString toUnicodeString() const; + + const UnicodeString toTempUnicodeString() const; + + /** + * Returns the first code point in the string segment, or -1 if the string starts with an invalid + * code point. + * + *

+ * Important: Most of the time, you should use {@link #matches}, which handles case + * folding logic, instead of this method. + */ + UChar32 getCodePoint() const; + + /** + * Returns true if the first code point of this StringSegment equals the given code point. + * + *

+ * This method will perform case folding if case folding is enabled for the parser. + */ + bool startsWith(UChar32 otherCp) const; + + /** + * Returns true if the first code point of this StringSegment is in the given UnicodeSet. + */ + bool startsWith(const UnicodeSet& uniset) const; + + /** + * Returns true if there is at least one code point of overlap between this StringSegment and the + * given UnicodeString. + */ + bool startsWith(const UnicodeString& other) const; + + /** + * Returns the length of the prefix shared by this StringSegment and the given CharSequence. For + * example, if this string segment is "aab", and the char sequence is "aac", this method returns 2, + * since the first 2 characters are the same. + * + *

+ * This method only returns offsets along code point boundaries. + * + *

+ * This method will perform case folding if case folding was enabled in the constructor. + * + *

+ * IMPORTANT: The given UnicodeString must not be empty! It is the caller's responsibility to check. + */ + int32_t getCommonPrefixLength(const UnicodeString& other); + + /** + * Like {@link #getCommonPrefixLength}, but never performs case folding, even if case folding is + * enabled for the parser. + */ + int32_t getCaseSensitivePrefixLength(const UnicodeString& other); + + bool operator==(const UnicodeString& other) const; + + private: + const UnicodeString fStr; + int32_t fStart; + int32_t fEnd; + bool fFoldCase; + + int32_t getPrefixLengthInternal(const UnicodeString& other, bool foldCase); + + static bool codePointsEqual(UChar32 cp1, UChar32 cp2, bool foldCase); +}; + + +/** + * The core interface implemented by all matchers used for number parsing. + * + * Given a string, there should NOT be more than one way to consume the string with the same matcher + * applied multiple times. If there is, the non-greedy parsing algorithm will be unhappy and may enter an + * exponential-time loop. For example, consider the "A Matcher" that accepts "any number of As". Given + * the string "AAAA", there are 2^N = 8 ways to apply the A Matcher to this string: you could have the A + * Matcher apply 4 times to each character; you could have it apply just once to all the characters; you + * could have it apply to the first 2 characters and the second 2 characters; and so on. A better version + * of the "A Matcher" would be for it to accept exactly one A, and allow the algorithm to run it + * repeatedly to consume a string of multiple As. The A Matcher can implement the Flexible interface + * below to signal that it can be applied multiple times in a row. + * + * @author sffc + */ +// Exported as U_I18N_API for tests +class U_I18N_API NumberParseMatcher { + public: + virtual ~NumberParseMatcher(); + + /** + * Matchers can override this method to return true to indicate that they are optional and can be run + * repeatedly. Used by SeriesMatcher, primarily in the context of IgnorablesMatcher. + */ + virtual bool isFlexible() const { + return false; + } + + /** + * Runs this matcher starting at the beginning of the given StringSegment. If this matcher finds + * something interesting in the StringSegment, it should update the offset of the StringSegment + * corresponding to how many chars were matched. + * + * This method is thread-safe. + * + * @param segment + * The StringSegment to match against. Matches always start at the beginning of the + * segment. The segment is guaranteed to contain at least one char. + * @param result + * The data structure to store results if the match succeeds. + * @return Whether this matcher thinks there may be more interesting chars beyond the end of the + * string segment. + */ + virtual bool match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const = 0; + + /** + * Performs a fast "smoke check" for whether or not this matcher could possibly match against the + * given string segment. The test should be as fast as possible but also as restrictive as possible. + * For example, matchers can maintain a UnicodeSet of all code points that count possibly start a + * match. Matchers should use the {@link StringSegment#startsWith} method in order to correctly + * handle case folding. + * + * @param segment + * The segment to check against. + * @return true if the matcher might be able to match against this segment; false if it definitely + * will not be able to match. + */ + virtual bool smokeTest(const StringSegment& segment) const = 0; + + /** + * Method called at the end of a parse, after all matchers have failed to consume any more chars. + * Allows a matcher to make final modifications to the result given the knowledge that no more + * matches are possible. + * + * @param result + * The data structure to store results. + */ + virtual void postProcess(ParsedNumber&) const { + // Default implementation: no-op + }; + + // String for debugging + virtual UnicodeString toString() const = 0; + + protected: + // No construction except by subclasses! + NumberParseMatcher() = default; +}; + + +/** + * Interface for use in arguments. + */ +// Exported as U_I18N_API for tests +class U_I18N_API MutableMatcherCollection { + public: + virtual ~MutableMatcherCollection() = default; + + virtual void addMatcher(NumberParseMatcher& matcher) = 0; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_TYPES_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_utils.h b/deps/icu-small/source/i18n/numparse_utils.h new file mode 100644 index 00000000000000..162954bae09444 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_utils.h @@ -0,0 +1,43 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __NUMPARSE_UTILS_H__ +#define __NUMPARSE_UTILS_H__ + +#include "numparse_types.h" +#include "unicode/uniset.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { +namespace utils { + + +inline static void putLeadCodePoints(const UnicodeSet* input, UnicodeSet* output) { + for (int32_t i = 0; i < input->getRangeCount(); i++) { + output->add(input->getRangeStart(i), input->getRangeEnd(i)); + } + // TODO: ANDY: How to iterate over the strings in ICU4C UnicodeSet? +} + +inline static void putLeadCodePoint(const UnicodeString& input, UnicodeSet* output) { + if (!input.isEmpty()) { + output->add(input.char32At(0)); + } +} + +inline static void copyCurrencyCode(UChar* dest, const UChar* src) { + uprv_memcpy(dest, src, sizeof(UChar) * 3); + dest[3] = 0; +} + + +} // namespace utils +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__NUMPARSE_UTILS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_validators.cpp b/deps/icu-small/source/i18n/numparse_validators.cpp new file mode 100644 index 00000000000000..12d3465c4ef3a4 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_validators.cpp @@ -0,0 +1,85 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "numparse_types.h" +#include "numparse_validators.h" +#include "static_unicode_sets.h" + +using namespace icu; +using namespace icu::numparse; +using namespace icu::numparse::impl; + + +void RequireAffixValidator::postProcess(ParsedNumber& result) const { + if (result.prefix.isBogus() || result.suffix.isBogus()) { + // We saw a prefix or a suffix but not both. Fail the parse. + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireAffixValidator::toString() const { + return u""; +} + + +void RequireCurrencyValidator::postProcess(ParsedNumber& result) const { + if (result.currencyCode[0] == 0) { + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireCurrencyValidator::toString() const { + return u""; +} + + +RequireDecimalSeparatorValidator::RequireDecimalSeparatorValidator(bool patternHasDecimalSeparator) + : fPatternHasDecimalSeparator(patternHasDecimalSeparator) { +} + +void RequireDecimalSeparatorValidator::postProcess(ParsedNumber& result) const { + bool parseHasDecimalSeparator = 0 != (result.flags & FLAG_HAS_DECIMAL_SEPARATOR); + if (parseHasDecimalSeparator != fPatternHasDecimalSeparator) { + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireDecimalSeparatorValidator::toString() const { + return u""; +} + + +void RequireNumberValidator::postProcess(ParsedNumber& result) const { + // Require that a number is matched. + if (!result.seenNumber()) { + result.flags |= FLAG_FAIL; + } +} + +UnicodeString RequireNumberValidator::toString() const { + return u""; +} + +MultiplierParseHandler::MultiplierParseHandler(::icu::number::Scale multiplier) + : fMultiplier(std::move(multiplier)) {} + +void MultiplierParseHandler::postProcess(ParsedNumber& result) const { + if (!result.quantity.bogus) { + fMultiplier.applyReciprocalTo(result.quantity); + // NOTE: It is okay if the multiplier was negative. + } +} + +UnicodeString MultiplierParseHandler::toString() const { + return u""; +} + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/numparse_validators.h b/deps/icu-small/source/i18n/numparse_validators.h new file mode 100644 index 00000000000000..5d43b779d0bb67 --- /dev/null +++ b/deps/icu-small/source/i18n/numparse_validators.h @@ -0,0 +1,95 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __SOURCE_NUMPARSE_VALIDATORS_H__ +#define __SOURCE_NUMPARSE_VALIDATORS_H__ + +#include "numparse_types.h" +#include "static_unicode_sets.h" + +U_NAMESPACE_BEGIN namespace numparse { +namespace impl { + + +class ValidationMatcher : public NumberParseMatcher { + public: + bool match(StringSegment&, ParsedNumber&, UErrorCode&) const U_OVERRIDE { + // No-op + return false; + } + + bool smokeTest(const StringSegment&) const U_OVERRIDE { + // No-op + return false; + } + + void postProcess(ParsedNumber& result) const U_OVERRIDE = 0; +}; + + +class RequireAffixValidator : public ValidationMatcher, public UMemory { + public: + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; +}; + + +class RequireCurrencyValidator : public ValidationMatcher, public UMemory { + public: + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; +}; + + +class RequireDecimalSeparatorValidator : public ValidationMatcher, public UMemory { + public: + RequireDecimalSeparatorValidator() = default; // leaves instance in valid but undefined state + + RequireDecimalSeparatorValidator(bool patternHasDecimalSeparator); + + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; + + private: + bool fPatternHasDecimalSeparator; +}; + + +class RequireNumberValidator : public ValidationMatcher, public UMemory { + public: + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; +}; + + +/** + * Wraps a {@link Multiplier} for use in the number parsing pipeline. + */ +class MultiplierParseHandler : public ValidationMatcher, public UMemory { + public: + MultiplierParseHandler() = default; // leaves instance in valid but undefined state + + MultiplierParseHandler(::icu::number::Scale multiplier); + + void postProcess(ParsedNumber& result) const U_OVERRIDE; + + UnicodeString toString() const U_OVERRIDE; + + private: + ::icu::number::Scale fMultiplier; +}; + + +} // namespace impl +} // namespace numparse +U_NAMESPACE_END + +#endif //__SOURCE_NUMPARSE_VALIDATORS_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/pluralaffix.cpp b/deps/icu-small/source/i18n/pluralaffix.cpp deleted file mode 100644 index ea400206b38b81..00000000000000 --- a/deps/icu-small/source/i18n/pluralaffix.cpp +++ /dev/null @@ -1,104 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: pluralaffix.cpp - */ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "cstring.h" -#include "digitaffix.h" -#include "pluralaffix.h" - -U_NAMESPACE_BEGIN - -UBool -PluralAffix::setVariant( - const char *variant, const UnicodeString &value, UErrorCode &status) { - DigitAffix *current = affixes.getMutable(variant, status); - if (U_FAILURE(status)) { - return FALSE; - } - current->remove(); - current->append(value); - return TRUE; -} - -void -PluralAffix::remove() { - affixes.clear(); -} - -void -PluralAffix::appendUChar( - const UChar value, int32_t fieldId) { - PluralMapBase::Category index = PluralMapBase::NONE; - for (DigitAffix *current = affixes.nextMutable(index); - current != NULL; current = affixes.nextMutable(index)) { - current->appendUChar(value, fieldId); - } -} - -void -PluralAffix::append( - const UnicodeString &value, int32_t fieldId) { - PluralMapBase::Category index = PluralMapBase::NONE; - for (DigitAffix *current = affixes.nextMutable(index); - current != NULL; current = affixes.nextMutable(index)) { - current->append(value, fieldId); - } -} - -void -PluralAffix::append( - const UChar *value, int32_t charCount, int32_t fieldId) { - PluralMapBase::Category index = PluralMapBase::NONE; - for (DigitAffix *current = affixes.nextMutable(index); - current != NULL; current = affixes.nextMutable(index)) { - current->append(value, charCount, fieldId); - } -} - -UBool -PluralAffix::append( - const PluralAffix &rhs, int32_t fieldId, UErrorCode &status) { - if (U_FAILURE(status)) { - return FALSE; - } - PluralMapBase::Category index = PluralMapBase::NONE; - while(rhs.affixes.next(index) != NULL) { - affixes.getMutableWithDefault(index, affixes.getOther(), status); - } - index = PluralMapBase::NONE; - for (DigitAffix *current = affixes.nextMutable(index); - current != NULL; current = affixes.nextMutable(index)) { - current->append(rhs.affixes.get(index).toString(), fieldId); - } - return TRUE; -} - -const DigitAffix & -PluralAffix::getByCategory(const char *category) const { - return affixes.get(category); -} - -const DigitAffix & -PluralAffix::getByCategory(const UnicodeString &category) const { - return affixes.get(category); -} - -UBool -PluralAffix::hasMultipleVariants() const { - // This works because OTHER is guaranteed to be the first enum value - PluralMapBase::Category index = PluralMapBase::OTHER; - return (affixes.next(index) != NULL); -} - -U_NAMESPACE_END - -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/pluralaffix.h b/deps/icu-small/source/i18n/pluralaffix.h deleted file mode 100644 index 94366ce4cf81ec..00000000000000 --- a/deps/icu-small/source/i18n/pluralaffix.h +++ /dev/null @@ -1,177 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* pluralaffix.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __PLURALAFFIX_H__ -#define __PLURALAFFIX_H__ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/unum.h" -#include "unicode/uobject.h" - -#include "digitaffix.h" -#include "pluralmap.h" - -U_NAMESPACE_BEGIN - -class FieldPositionHandler; - -// Export an explicit template instantiation. -// -// MSVC requires this, even though it should not be necessary. -// No direct access leaks out of the i18n library. -// -// Macintosh produces duplicate definition linker errors with the explicit template -// instantiation. -// -#if !U_PLATFORM_IS_DARWIN_BASED -template class U_I18N_API PluralMap; -#endif - - -/** - * A plural aware prefix or suffix of a formatted number. - * - * PluralAffix is essentially a map of DigitAffix objects keyed by plural - * category. The 'other' category is the default and always has some - * value. The rest of the categories are optional. Querying for a category that - * is not set always returns the DigitAffix stored in the 'other' category. - * - * To use one of these objects, build it up first using append() and - * setVariant() methods. Once built, leave unchanged and let multiple threads - * safely access. - * - * The following code is sample code for building up: - * one: US Dollar - - * other: US Dollars - - * - * and storing it in "negativeCurrencyPrefix" - * - * UErrorCode status = U_ZERO_ERROR; - * - * PluralAffix negativeCurrencyPrefix; - * - * PluralAffix currencyName; - * currencyName.setVariant("one", "US Dollar", status); - * currencyName.setVariant("other", "US Dollars", status); - * - * negativeCurrencyPrefix.append(currencyName, UNUM_CURRENCY_FIELD, status); - * negativeCurrencyPrefix.append(" "); - * negativeCurrencyPrefix.append("-", UNUM_SIGN_FIELD, status); - */ -class U_I18N_API PluralAffix : public UMemory { -public: - - /** - * Create empty PluralAffix. - */ - PluralAffix() : affixes() { } - - /** - * Create a PluralAffix where the 'other' variant is otherVariant. - */ - PluralAffix(const DigitAffix &otherVariant) : affixes(otherVariant) { } - - /** - * Sets a particular variant for a plural category while overwriting - * anything that may have been previously stored for that plural - * category. The set value has no field annotations. - * @param category "one", "two", "few", ... - * @param variant the variant to store under the particular category - * @param status Any error returned here. - */ - UBool setVariant( - const char *category, - const UnicodeString &variant, - UErrorCode &status); - /** - * Make the 'other' variant be the empty string with no field annotations - * and remove the variants for the rest of the plural categories. - */ - void remove(); - - /** - * Append value to all set plural categories. If fieldId present, value - * is that field type. - */ - void appendUChar(UChar value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append value to all set plural categories. If fieldId present, value - * is that field type. - */ - void append(const UnicodeString &value, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append value to all set plural categories. If fieldId present, value - * is that field type. - */ - void append(const UChar *value, int32_t charCount, int32_t fieldId=UNUM_FIELD_COUNT); - - /** - * Append the value for each plural category in rhs to the corresponding - * plural category in this instance. Each value appended from rhs is - * of type fieldId. - */ - UBool append( - const PluralAffix &rhs, - int32_t fieldId, - UErrorCode &status); - /** - * Get the DigitAffix for a paricular category such as "zero", "one", ... - * If the particular category is not set, returns the 'other' category - * which is always set. - */ - const DigitAffix &getByCategory(const char *category) const; - - /** - * Get the DigitAffix for a paricular category such as "zero", "one", ... - * If the particular category is not set, returns the 'other' category - * which is always set. - */ - const DigitAffix &getByCategory(const UnicodeString &category) const; - - /** - * Get the DigitAffix for the other category which is always set. - */ - const DigitAffix &getOtherVariant() const { - return affixes.getOther(); - } - - /** - * Returns TRUE if this instance has variants stored besides the "other" - * variant. - */ - UBool hasMultipleVariants() const; - - /** - * Returns TRUE if this instance equals rhs. - */ - UBool equals(const PluralAffix &rhs) const { - return affixes.equals(rhs.affixes, &eq); - } - -private: - PluralMap affixes; - - static UBool eq(const DigitAffix &x, const DigitAffix &y) { - return x.equals(y); - } -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __PLURALAFFIX_H__ diff --git a/deps/icu-small/source/i18n/plurfmt.cpp b/deps/icu-small/source/i18n/plurfmt.cpp index e14ef6d831ecce..2775766d32df80 100644 --- a/deps/icu-small/source/i18n/plurfmt.cpp +++ b/deps/icu-small/source/i18n/plurfmt.cpp @@ -21,13 +21,16 @@ #include "plurrule_impl.h" #include "uassert.h" #include "uhash.h" -#include "precision.h" -#include "visibledigits.h" +#include "number_decimalquantity.h" +#include "number_utils.h" +#include "number_utypes.h" #if !UCONFIG_NO_FORMATTING U_NAMESPACE_BEGIN +using number::impl::DecimalQuantity; + static const UChar OTHER_STRING[] = { 0x6F, 0x74, 0x68, 0x65, 0x72, 0 // "other" }; @@ -258,45 +261,33 @@ PluralFormat::format(const Formattable& numberObject, double number, if (msgPattern.countParts() == 0) { return numberFormat->format(numberObject, appendTo, pos, status); } + // Get the appropriate sub-message. // Select it based on the formatted number-offset. double numberMinusOffset = number - offset; - UnicodeString numberString; - FieldPosition ignorePos; - FixedPrecision fp; - VisibleDigitsWithExponent dec; - fp.initVisibleDigitsWithExponent(numberMinusOffset, dec, status); - if (U_FAILURE(status)) { - return appendTo; - } + // Call NumberFormatter to get both the DecimalQuantity and the string. + // This call site needs to use more internal APIs than the Java equivalent. + number::impl::UFormattedNumberData data; if (offset == 0) { - DecimalFormat *decFmt = dynamic_cast(numberFormat); - if(decFmt != NULL) { - decFmt->initVisibleDigitsWithExponent( - numberObject, dec, status); - if (U_FAILURE(status)) { - return appendTo; - } - decFmt->format(dec, numberString, ignorePos, status); - } else { - numberFormat->format( - numberObject, numberString, ignorePos, status); // could be BigDecimal etc. - } + // could be BigDecimal etc. + numberObject.populateDecimalQuantity(data.quantity, status); } else { - DecimalFormat *decFmt = dynamic_cast(numberFormat); - if(decFmt != NULL) { - decFmt->initVisibleDigitsWithExponent( - numberMinusOffset, dec, status); - if (U_FAILURE(status)) { - return appendTo; - } - decFmt->format(dec, numberString, ignorePos, status); + data.quantity.setToDouble(numberMinusOffset); + } + UnicodeString numberString; + auto *decFmt = dynamic_cast(numberFormat); + if(decFmt != nullptr) { + decFmt->toNumberFormatter().formatImpl(&data, status); // mutates &data + numberString = data.string.toUnicodeString(); + } else { + if (offset == 0) { + numberFormat->format(numberObject, numberString, status); } else { - numberFormat->format( - numberMinusOffset, numberString, ignorePos, status); + numberFormat->format(numberMinusOffset, numberString, status); } } - int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, &dec, number, status); + + int32_t partIndex = findSubMessage(msgPattern, 0, pluralRulesWrapper, &data.quantity, number, status); if (U_FAILURE(status)) { return appendTo; } // Replace syntactic # signs in the top level of this sub-message // (not in nested arguments) with the formatted number-offset. @@ -585,7 +576,7 @@ PluralFormat::PluralSelectorAdapter::~PluralSelectorAdapter() { UnicodeString PluralFormat::PluralSelectorAdapter::select(void *context, double number, UErrorCode& /*ec*/) const { (void)number; // unused except in the assertion - VisibleDigitsWithExponent *dec=static_cast(context); + IFixedDecimal *dec=static_cast(context); return pluralRules->select(*dec); } diff --git a/deps/icu-small/source/i18n/plurrule.cpp b/deps/icu-small/source/i18n/plurrule.cpp index 6733a23e00362f..9597e8eb00d023 100644 --- a/deps/icu-small/source/i18n/plurrule.cpp +++ b/deps/icu-small/source/i18n/plurrule.cpp @@ -22,7 +22,6 @@ #include "charstr.h" #include "cmemory.h" #include "cstring.h" -#include "digitlst.h" #include "hash.h" #include "locutil.h" #include "mutex.h" @@ -35,13 +34,15 @@ #include "uvectr32.h" #include "sharedpluralrules.h" #include "unifiedcache.h" -#include "digitinterval.h" -#include "visibledigits.h" +#include "number_decimalquantity.h" #if !UCONFIG_NO_FORMATTING U_NAMESPACE_BEGIN +using namespace icu::pluralimpl; +using icu::number::impl::DecimalQuantity; + static const UChar PLURAL_KEYWORD_OTHER[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,0}; static const UChar PLURAL_DEFAULT_RULE[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,COLON,SPACE,LOW_N,0}; static const UChar PK_IN[]={LOW_I,LOW_N,0}; @@ -247,26 +248,6 @@ PluralRules::select(double number) const { return select(FixedDecimal(number)); } -UnicodeString -PluralRules::select(const Formattable& obj, const NumberFormat& fmt, UErrorCode& status) const { - if (U_SUCCESS(status)) { - const DecimalFormat *decFmt = dynamic_cast(&fmt); - if (decFmt != NULL) { - VisibleDigitsWithExponent digits; - decFmt->initVisibleDigitsWithExponent(obj, digits, status); - if (U_SUCCESS(status)) { - return select(digits); - } - } else { - double number = obj.getDouble(status); - if (U_SUCCESS(status)) { - return select(number); - } - } - } - return UnicodeString(); -} - UnicodeString PluralRules::select(const IFixedDecimal &number) const { if (mRules == NULL) { @@ -277,14 +258,6 @@ PluralRules::select(const IFixedDecimal &number) const { } } -UnicodeString -PluralRules::select(const VisibleDigitsWithExponent &number) const { - if (number.getExponent() != NULL) { - return UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1); - } - return select(FixedDecimal(number.getMantissa())); -} - StringEnumeration* @@ -1425,18 +1398,6 @@ PluralOperand tokenTypeToPluralOperand(tokenType tt) { } } -IFixedDecimal::~IFixedDecimal() = default; - -FixedDecimal::FixedDecimal(const VisibleDigits &digits) { - digits.getFixedDecimal( - source, intValue, decimalDigits, - decimalDigitsWithoutTrailingZeros, - visibleDecimalDigitCount, hasIntegerValue); - isNegative = digits.isNegative(); - _isNaN = digits.isNaN(); - _isInfinite = digits.isInfinite(); -} - FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) { init(n, v, f); // check values. TODO make into unit test. @@ -1474,14 +1435,14 @@ FixedDecimal::FixedDecimal() { FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) { CharString cs; cs.appendInvariantChars(num, status); - DigitList dl; - dl.set(cs.toStringPiece(), status); + DecimalQuantity dl; + dl.setToDecNumber(cs.toStringPiece(), status); if (U_FAILURE(status)) { init(0, 0, 0); return; } int32_t decimalPoint = num.indexOf(DOT); - double n = dl.getDouble(); + double n = dl.toDouble(); if (decimalPoint == -1) { init(n, 0, 0); } else { @@ -1497,7 +1458,7 @@ FixedDecimal::FixedDecimal(const FixedDecimal &other) { decimalDigits = other.decimalDigits; decimalDigitsWithoutTrailingZeros = other.decimalDigitsWithoutTrailingZeros; intValue = other.intValue; - hasIntegerValue = other.hasIntegerValue; + _hasIntegerValue = other._hasIntegerValue; isNegative = other.isNegative; _isNaN = other._isNaN; _isInfinite = other._isInfinite; @@ -1521,10 +1482,10 @@ void FixedDecimal::init(double n, int32_t v, int64_t f) { v = 0; f = 0; intValue = 0; - hasIntegerValue = FALSE; + _hasIntegerValue = FALSE; } else { intValue = (int64_t)source; - hasIntegerValue = (source == intValue); + _hasIntegerValue = (source == intValue); } visibleDecimalDigitCount = v; @@ -1658,6 +1619,10 @@ bool FixedDecimal::isInfinite() const { return _isInfinite; } +bool FixedDecimal::hasIntegerValue() const { + return _hasIntegerValue; +} + bool FixedDecimal::isNanOrInfinity() const { return _isNaN || _isInfinite; } diff --git a/deps/icu-small/source/i18n/plurrule_impl.h b/deps/icu-small/source/i18n/plurrule_impl.h index b93fc501baced2..3ab445d5843c8b 100644 --- a/deps/icu-small/source/i18n/plurrule_impl.h +++ b/deps/icu-small/source/i18n/plurrule_impl.h @@ -40,67 +40,73 @@ class DigitInterval; class PluralRules; class VisibleDigits; -static const UChar DOT = ((UChar)0x002E); -static const UChar SINGLE_QUOTE = ((UChar)0x0027); -static const UChar SLASH = ((UChar)0x002F); -static const UChar BACKSLASH = ((UChar)0x005C); -static const UChar SPACE = ((UChar)0x0020); -static const UChar EXCLAMATION = ((UChar)0x0021); -static const UChar QUOTATION_MARK = ((UChar)0x0022); -static const UChar NUMBER_SIGN = ((UChar)0x0023); -static const UChar PERCENT_SIGN = ((UChar)0x0025); -static const UChar ASTERISK = ((UChar)0x002A); -static const UChar COMMA = ((UChar)0x002C); -static const UChar HYPHEN = ((UChar)0x002D); -static const UChar U_ZERO = ((UChar)0x0030); -static const UChar U_ONE = ((UChar)0x0031); -static const UChar U_TWO = ((UChar)0x0032); -static const UChar U_THREE = ((UChar)0x0033); -static const UChar U_FOUR = ((UChar)0x0034); -static const UChar U_FIVE = ((UChar)0x0035); -static const UChar U_SIX = ((UChar)0x0036); -static const UChar U_SEVEN = ((UChar)0x0037); -static const UChar U_EIGHT = ((UChar)0x0038); -static const UChar U_NINE = ((UChar)0x0039); -static const UChar COLON = ((UChar)0x003A); -static const UChar SEMI_COLON = ((UChar)0x003B); -static const UChar EQUALS = ((UChar)0x003D); -static const UChar AT = ((UChar)0x0040); -static const UChar CAP_A = ((UChar)0x0041); -static const UChar CAP_B = ((UChar)0x0042); -static const UChar CAP_R = ((UChar)0x0052); -static const UChar CAP_Z = ((UChar)0x005A); -static const UChar LOWLINE = ((UChar)0x005F); -static const UChar LEFTBRACE = ((UChar)0x007B); -static const UChar RIGHTBRACE = ((UChar)0x007D); -static const UChar TILDE = ((UChar)0x007E); -static const UChar ELLIPSIS = ((UChar)0x2026); - -static const UChar LOW_A = ((UChar)0x0061); -static const UChar LOW_B = ((UChar)0x0062); -static const UChar LOW_C = ((UChar)0x0063); -static const UChar LOW_D = ((UChar)0x0064); -static const UChar LOW_E = ((UChar)0x0065); -static const UChar LOW_F = ((UChar)0x0066); -static const UChar LOW_G = ((UChar)0x0067); -static const UChar LOW_H = ((UChar)0x0068); -static const UChar LOW_I = ((UChar)0x0069); -static const UChar LOW_J = ((UChar)0x006a); -static const UChar LOW_K = ((UChar)0x006B); -static const UChar LOW_L = ((UChar)0x006C); -static const UChar LOW_M = ((UChar)0x006D); -static const UChar LOW_N = ((UChar)0x006E); -static const UChar LOW_O = ((UChar)0x006F); -static const UChar LOW_P = ((UChar)0x0070); -static const UChar LOW_Q = ((UChar)0x0071); -static const UChar LOW_R = ((UChar)0x0072); -static const UChar LOW_S = ((UChar)0x0073); -static const UChar LOW_T = ((UChar)0x0074); -static const UChar LOW_U = ((UChar)0x0075); -static const UChar LOW_V = ((UChar)0x0076); -static const UChar LOW_W = ((UChar)0x0077); -static const UChar LOW_Y = ((UChar)0x0079); -static const UChar LOW_Z = ((UChar)0x007A); +namespace pluralimpl { + +// TODO: Remove this and replace with u"" literals. Was for EBCDIC compatibility. + +static const UChar DOT = ((UChar) 0x002E); +static const UChar SINGLE_QUOTE = ((UChar) 0x0027); +static const UChar SLASH = ((UChar) 0x002F); +static const UChar BACKSLASH = ((UChar) 0x005C); +static const UChar SPACE = ((UChar) 0x0020); +static const UChar EXCLAMATION = ((UChar) 0x0021); +static const UChar QUOTATION_MARK = ((UChar) 0x0022); +static const UChar NUMBER_SIGN = ((UChar) 0x0023); +static const UChar PERCENT_SIGN = ((UChar) 0x0025); +static const UChar ASTERISK = ((UChar) 0x002A); +static const UChar COMMA = ((UChar) 0x002C); +static const UChar HYPHEN = ((UChar) 0x002D); +static const UChar U_ZERO = ((UChar) 0x0030); +static const UChar U_ONE = ((UChar) 0x0031); +static const UChar U_TWO = ((UChar) 0x0032); +static const UChar U_THREE = ((UChar) 0x0033); +static const UChar U_FOUR = ((UChar) 0x0034); +static const UChar U_FIVE = ((UChar) 0x0035); +static const UChar U_SIX = ((UChar) 0x0036); +static const UChar U_SEVEN = ((UChar) 0x0037); +static const UChar U_EIGHT = ((UChar) 0x0038); +static const UChar U_NINE = ((UChar) 0x0039); +static const UChar COLON = ((UChar) 0x003A); +static const UChar SEMI_COLON = ((UChar) 0x003B); +static const UChar EQUALS = ((UChar) 0x003D); +static const UChar AT = ((UChar) 0x0040); +static const UChar CAP_A = ((UChar) 0x0041); +static const UChar CAP_B = ((UChar) 0x0042); +static const UChar CAP_R = ((UChar) 0x0052); +static const UChar CAP_Z = ((UChar) 0x005A); +static const UChar LOWLINE = ((UChar) 0x005F); +static const UChar LEFTBRACE = ((UChar) 0x007B); +static const UChar RIGHTBRACE = ((UChar) 0x007D); +static const UChar TILDE = ((UChar) 0x007E); +static const UChar ELLIPSIS = ((UChar) 0x2026); + +static const UChar LOW_A = ((UChar) 0x0061); +static const UChar LOW_B = ((UChar) 0x0062); +static const UChar LOW_C = ((UChar) 0x0063); +static const UChar LOW_D = ((UChar) 0x0064); +static const UChar LOW_E = ((UChar) 0x0065); +static const UChar LOW_F = ((UChar) 0x0066); +static const UChar LOW_G = ((UChar) 0x0067); +static const UChar LOW_H = ((UChar) 0x0068); +static const UChar LOW_I = ((UChar) 0x0069); +static const UChar LOW_J = ((UChar) 0x006a); +static const UChar LOW_K = ((UChar) 0x006B); +static const UChar LOW_L = ((UChar) 0x006C); +static const UChar LOW_M = ((UChar) 0x006D); +static const UChar LOW_N = ((UChar) 0x006E); +static const UChar LOW_O = ((UChar) 0x006F); +static const UChar LOW_P = ((UChar) 0x0070); +static const UChar LOW_Q = ((UChar) 0x0071); +static const UChar LOW_R = ((UChar) 0x0072); +static const UChar LOW_S = ((UChar) 0x0073); +static const UChar LOW_T = ((UChar) 0x0074); +static const UChar LOW_U = ((UChar) 0x0075); +static const UChar LOW_V = ((UChar) 0x0076); +static const UChar LOW_W = ((UChar) 0x0077); +static const UChar LOW_Y = ((UChar) 0x0079); +static const UChar LOW_Z = ((UChar) 0x007A); + +} static const int32_t PLURAL_RANGE_HIGH = 0x7fffffff; @@ -244,6 +250,9 @@ class U_I18N_API IFixedDecimal { virtual bool isNaN() const = 0; virtual bool isInfinite() const = 0; + + /** Whether the number has no nonzero fraction digits. */ + virtual bool hasIntegerValue() const = 0; }; /** @@ -263,7 +272,6 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { FixedDecimal(double n, int32_t v, int64_t f); FixedDecimal(double n, int32_t); explicit FixedDecimal(double n); - explicit FixedDecimal(const VisibleDigits &n); FixedDecimal(); ~FixedDecimal() U_OVERRIDE; FixedDecimal(const UnicodeString &s, UErrorCode &ec); @@ -272,6 +280,7 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { double getPluralOperand(PluralOperand operand) const U_OVERRIDE; bool isNaN() const U_OVERRIDE; bool isInfinite() const U_OVERRIDE; + bool hasIntegerValue() const U_OVERRIDE; bool isNanOrInfinity() const; // used in decimfmtimpl.cpp @@ -290,7 +299,7 @@ class U_I18N_API FixedDecimal: public IFixedDecimal, public UObject { int64_t decimalDigits; int64_t decimalDigitsWithoutTrailingZeros; int64_t intValue; - UBool hasIntegerValue; + UBool _hasIntegerValue; UBool isNegative; UBool _isNaN; UBool _isInfinite; diff --git a/deps/icu-small/source/i18n/precision.cpp b/deps/icu-small/source/i18n/precision.cpp deleted file mode 100644 index 97dc13dc385651..00000000000000 --- a/deps/icu-small/source/i18n/precision.cpp +++ /dev/null @@ -1,444 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: precisison.cpp - */ - -#include - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "digitlst.h" -#include "fmtableimp.h" -#include "precision.h" -#include "putilimp.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -static const int32_t gPower10[] = {1, 10, 100, 1000}; - -FixedPrecision::FixedPrecision() - : fExactOnly(FALSE), fFailIfOverMax(FALSE), fRoundingMode(DecimalFormat::kRoundHalfEven) { - fMin.setIntDigitCount(1); - fMin.setFracDigitCount(0); -} - -UBool -FixedPrecision::isRoundingRequired( - int32_t upperExponent, int32_t lowerExponent) const { - int32_t leastSigAllowed = fMax.getLeastSignificantInclusive(); - int32_t maxSignificantDigits = fSignificant.getMax(); - int32_t roundDigit; - if (maxSignificantDigits == INT32_MAX) { - roundDigit = leastSigAllowed; - } else { - int32_t limitDigit = upperExponent - maxSignificantDigits; - roundDigit = - limitDigit > leastSigAllowed ? limitDigit : leastSigAllowed; - } - return (roundDigit > lowerExponent); -} - -DigitList & -FixedPrecision::round( - DigitList &value, int32_t exponent, UErrorCode &status) const { - if (U_FAILURE(status)) { - return value; - } - value .fContext.status &= ~DEC_Inexact; - if (!fRoundingIncrement.isZero()) { - if (exponent == 0) { - value.quantize(fRoundingIncrement, status); - } else { - DigitList adjustedIncrement(fRoundingIncrement); - adjustedIncrement.shiftDecimalRight(exponent); - value.quantize(adjustedIncrement, status); - } - if (U_FAILURE(status)) { - return value; - } - } - int32_t leastSig = fMax.getLeastSignificantInclusive(); - if (leastSig == INT32_MIN) { - value.round(fSignificant.getMax()); - } else { - value.roundAtExponent( - exponent + leastSig, - fSignificant.getMax()); - } - if (fExactOnly && (value.fContext.status & DEC_Inexact)) { - status = U_FORMAT_INEXACT_ERROR; - } else if (fFailIfOverMax) { - // Smallest interval for value stored in interval - DigitInterval interval; - value.getSmallestInterval(interval); - if (fMax.getIntDigitCount() < interval.getIntDigitCount()) { - status = U_ILLEGAL_ARGUMENT_ERROR; - } - } - return value; -} - -DigitInterval & -FixedPrecision::getIntervalForZero(DigitInterval &interval) const { - interval = fMin; - if (fSignificant.getMin() > 0) { - interval.expandToContainDigit(interval.getIntDigitCount() - fSignificant.getMin()); - } - interval.shrinkToFitWithin(fMax); - return interval; -} - -DigitInterval & -FixedPrecision::getInterval( - int32_t upperExponent, DigitInterval &interval) const { - if (fSignificant.getMin() > 0) { - interval.expandToContainDigit( - upperExponent - fSignificant.getMin()); - } - interval.expandToContain(fMin); - interval.shrinkToFitWithin(fMax); - return interval; -} - -DigitInterval & -FixedPrecision::getInterval( - const DigitList &value, DigitInterval &interval) const { - if (value.isZero()) { - interval = fMin; - if (fSignificant.getMin() > 0) { - interval.expandToContainDigit(interval.getIntDigitCount() - fSignificant.getMin()); - } - } else { - value.getSmallestInterval(interval); - if (fSignificant.getMin() > 0) { - interval.expandToContainDigit( - value.getUpperExponent() - fSignificant.getMin()); - } - interval.expandToContain(fMin); - } - interval.shrinkToFitWithin(fMax); - return interval; -} - -UBool -FixedPrecision::isFastFormattable() const { - return (fMin.getFracDigitCount() == 0 && fSignificant.isNoConstraints() && fRoundingIncrement.isZero() && !fFailIfOverMax); -} - -UBool -FixedPrecision::handleNonNumeric(DigitList &value, VisibleDigits &digits) { - if (value.isNaN()) { - digits.setNaN(); - return TRUE; - } - if (value.isInfinite()) { - digits.setInfinite(); - if (!value.isPositive()) { - digits.setNegative(); - } - return TRUE; - } - return FALSE; -} - -VisibleDigits & -FixedPrecision::initVisibleDigits( - DigitList &value, - VisibleDigits &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - digits.clear(); - if (handleNonNumeric(value, digits)) { - return digits; - } - if (!value.isPositive()) { - digits.setNegative(); - } - value.setRoundingMode(fRoundingMode); - round(value, 0, status); - getInterval(value, digits.fInterval); - digits.fExponent = value.getLowerExponent(); - value.appendDigitsTo(digits.fDigits, status); - return digits; -} - -VisibleDigits & -FixedPrecision::initVisibleDigits( - int64_t value, - VisibleDigits &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - if (!fRoundingIncrement.isZero()) { - // If we have round increment, use digit list. - DigitList digitList; - digitList.set(value); - return initVisibleDigits(digitList, digits, status); - } - // Try fast path - if (initVisibleDigits(value, 0, digits, status)) { - digits.fAbsDoubleValue = fabs((double) value); - digits.fAbsDoubleValueSet = U_SUCCESS(status) && !digits.isOverMaxDigits(); - return digits; - } - // Oops have to use digit list - DigitList digitList; - digitList.set(value); - return initVisibleDigits(digitList, digits, status); -} - -VisibleDigits & -FixedPrecision::initVisibleDigits( - double value, - VisibleDigits &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - digits.clear(); - if (uprv_isNaN(value)) { - digits.setNaN(); - return digits; - } - if (uprv_isPositiveInfinity(value)) { - digits.setInfinite(); - return digits; - } - if (uprv_isNegativeInfinity(value)) { - digits.setInfinite(); - digits.setNegative(); - return digits; - } - if (!fRoundingIncrement.isZero()) { - // If we have round increment, use digit list. - DigitList digitList; - digitList.set(value); - return initVisibleDigits(digitList, digits, status); - } - // Try to find n such that value * 10^n is an integer - int32_t n = -1; - double scaled; - for (int32_t i = 0; i < UPRV_LENGTHOF(gPower10); ++i) { - scaled = value * gPower10[i]; - if (scaled > MAX_INT64_IN_DOUBLE || scaled < -MAX_INT64_IN_DOUBLE) { - break; - } - if (scaled == floor(scaled)) { - n = i; - break; - } - } - // Try fast path - if (n >= 0 && initVisibleDigits(static_cast(scaled), -n, digits, status)) { - digits.fAbsDoubleValue = fabs(value); - digits.fAbsDoubleValueSet = U_SUCCESS(status) && !digits.isOverMaxDigits(); - // Adjust for negative 0 because when we cast to an int64, - // negative 0 becomes positive 0. - if (scaled == 0.0 && uprv_isNegative(scaled)) { - digits.setNegative(); - } - return digits; - } - - // Oops have to use digit list - DigitList digitList; - digitList.set(value); - return initVisibleDigits(digitList, digits, status); -} - -UBool -FixedPrecision::initVisibleDigits( - int64_t mantissa, - int32_t exponent, - VisibleDigits &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return TRUE; - } - digits.clear(); - - // Precompute fAbsIntValue if it is small enough, but we don't know yet - // if it will be valid. - UBool absIntValueComputed = FALSE; - if (mantissa > -1000000000000000000LL /* -1e18 */ - && mantissa < 1000000000000000000LL /* 1e18 */) { - digits.fAbsIntValue = mantissa; - if (digits.fAbsIntValue < 0) { - digits.fAbsIntValue = -digits.fAbsIntValue; - } - int32_t i = 0; - int32_t maxPower10Exp = UPRV_LENGTHOF(gPower10) - 1; - for (; i > exponent + maxPower10Exp; i -= maxPower10Exp) { - digits.fAbsIntValue /= gPower10[maxPower10Exp]; - } - digits.fAbsIntValue /= gPower10[i - exponent]; - absIntValueComputed = TRUE; - } - if (mantissa == 0) { - getIntervalForZero(digits.fInterval); - digits.fAbsIntValueSet = absIntValueComputed; - return TRUE; - } - // be sure least significant digit is non zero - while (mantissa % 10 == 0) { - mantissa /= 10; - ++exponent; - } - if (mantissa < 0) { - digits.fDigits.append((char) -(mantissa % -10), status); - mantissa /= -10; - digits.setNegative(); - } - while (mantissa) { - digits.fDigits.append((char) (mantissa % 10), status); - mantissa /= 10; - } - if (U_FAILURE(status)) { - return TRUE; - } - digits.fExponent = exponent; - int32_t upperExponent = exponent + digits.fDigits.length(); - if (fFailIfOverMax && upperExponent > fMax.getIntDigitCount()) { - status = U_ILLEGAL_ARGUMENT_ERROR; - return TRUE; - } - UBool roundingRequired = - isRoundingRequired(upperExponent, exponent); - if (roundingRequired) { - if (fExactOnly) { - status = U_FORMAT_INEXACT_ERROR; - return TRUE; - } - return FALSE; - } - digits.fInterval.setLeastSignificantInclusive(exponent); - digits.fInterval.setMostSignificantExclusive(upperExponent); - getInterval(upperExponent, digits.fInterval); - - // The intValue we computed above is only valid if our visible digits - // doesn't exceed the maximum integer digits allowed. - digits.fAbsIntValueSet = absIntValueComputed && !digits.isOverMaxDigits(); - return TRUE; -} - -VisibleDigitsWithExponent & -FixedPrecision::initVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - digits.clear(); - initVisibleDigits(value, digits.fMantissa, status); - return digits; -} - -VisibleDigitsWithExponent & -FixedPrecision::initVisibleDigitsWithExponent( - double value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - digits.clear(); - initVisibleDigits(value, digits.fMantissa, status); - return digits; -} - -VisibleDigitsWithExponent & -FixedPrecision::initVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - digits.clear(); - initVisibleDigits(value, digits.fMantissa, status); - return digits; -} - -ScientificPrecision::ScientificPrecision() : fMinExponentDigits(1) { -} - -DigitList & -ScientificPrecision::round(DigitList &value, UErrorCode &status) const { - if (U_FAILURE(status)) { - return value; - } - int32_t exponent = value.getScientificExponent( - fMantissa.fMin.getIntDigitCount(), getMultiplier()); - return fMantissa.round(value, exponent, status); -} - -int32_t -ScientificPrecision::toScientific(DigitList &value) const { - return value.toScientific( - fMantissa.fMin.getIntDigitCount(), getMultiplier()); -} - -int32_t -ScientificPrecision::getMultiplier() const { - int32_t maxIntDigitCount = fMantissa.fMax.getIntDigitCount(); - if (maxIntDigitCount == INT32_MAX) { - return 1; - } - int32_t multiplier = - maxIntDigitCount - fMantissa.fMin.getIntDigitCount() + 1; - return (multiplier < 1 ? 1 : multiplier); -} - -VisibleDigitsWithExponent & -ScientificPrecision::initVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - digits.clear(); - if (FixedPrecision::handleNonNumeric(value, digits.fMantissa)) { - return digits; - } - value.setRoundingMode(fMantissa.fRoundingMode); - int64_t exponent = toScientific(round(value, status)); - fMantissa.initVisibleDigits(value, digits.fMantissa, status); - FixedPrecision exponentPrecision; - exponentPrecision.fMin.setIntDigitCount(fMinExponentDigits); - exponentPrecision.initVisibleDigits(exponent, digits.fExponent, status); - digits.fHasExponent = TRUE; - return digits; -} - -VisibleDigitsWithExponent & -ScientificPrecision::initVisibleDigitsWithExponent( - double value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - DigitList digitList; - digitList.set(value); - return initVisibleDigitsWithExponent(digitList, digits, status); -} - -VisibleDigitsWithExponent & -ScientificPrecision::initVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - if (U_FAILURE(status)) { - return digits; - } - DigitList digitList; - digitList.set(value); - return initVisibleDigitsWithExponent(digitList, digits, status); -} - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/precision.h b/deps/icu-small/source/i18n/precision.h deleted file mode 100644 index 0598fa17d62771..00000000000000 --- a/deps/icu-small/source/i18n/precision.h +++ /dev/null @@ -1,323 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* precision.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __PRECISION_H__ -#define __PRECISION_H__ - -#include "unicode/uobject.h" - -#if !UCONFIG_NO_FORMATTING -#include "unicode/utypes.h" - -#include "digitinterval.h" -#include "digitlst.h" -#include "significantdigitinterval.h" - -U_NAMESPACE_BEGIN - -class VisibleDigits; -class VisibleDigitsWithExponent; - - -/** - * A precision manager for values to be formatted as fixed point. - * Handles rounding of number to prepare it for formatting. - */ -class U_I18N_API FixedPrecision : public UMemory { -public: - - /** - * The smallest format interval allowed. Default is 1 integer digit and no - * fraction digits. - */ - DigitInterval fMin; - - /** - * The largest format interval allowed. Must contain fMin. - * Default is all digits. - */ - DigitInterval fMax; - - /** - * Min and max significant digits allowed. The default is no constraints. - */ - SignificantDigitInterval fSignificant; - - /** - * The rounding increment or zero if there is no rounding increment. - * Default is zero. - */ - DigitList fRoundingIncrement; - - /** - * If set, causes round() to set status to U_FORMAT_INEXACT_ERROR if - * any rounding is done. Default is FALSE. - */ - UBool fExactOnly; - - /** - * If set, causes round() to set status to U_ILLEGAL_ARGUMENT_ERROR if - * rounded number has more than maximum integer digits. Default is FALSE. - */ - UBool fFailIfOverMax; - - /** - * Controls the rounding mode that initVisibleDigits uses. - * Default is DecimalFormat::kRoundHalfEven - */ - DecimalFormat::ERoundingMode fRoundingMode; - - FixedPrecision(); - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const FixedPrecision &rhs) const { - return (fMin.equals(rhs.fMin) && - fMax.equals(rhs.fMax) && - fSignificant.equals(rhs.fSignificant) && - (fRoundingIncrement == rhs.fRoundingIncrement) && - fExactOnly == rhs.fExactOnly && - fFailIfOverMax == rhs.fFailIfOverMax && - fRoundingMode == rhs.fRoundingMode); - } - - /** - * Rounds value in place to prepare it for formatting. - * @param value The value to be rounded. It is rounded in place. - * @param exponent Always pass 0 for fixed decimal formatting. scientific - * precision passes the exponent value. Essentially, it divides value by - * 10^exponent, rounds and then multiplies by 10^exponent. - * @param status error returned here. - * @return reference to value. - */ - DigitList &round(DigitList &value, int32_t exponent, UErrorCode &status) const; - - /** - * Returns the interval to use to format the rounded value. - * @param roundedValue the already rounded value to format. - * @param interval modified in place to be the interval to use to format - * the rounded value. - * @return a reference to interval. - */ - DigitInterval &getInterval( - const DigitList &roundedValue, DigitInterval &interval) const; - - /** - * Returns TRUE if this instance allows for fast formatting of integers. - */ - UBool isFastFormattable() const; - - /** - * Initializes a VisibleDigits. - * @param value value for VisibleDigits - * Caller must not assume that the value of this parameter will remain - * unchanged. - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigits &initVisibleDigits( - DigitList &value, - VisibleDigits &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigits. - * @param value value for VisibleDigits - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigits &initVisibleDigits( - double value, - VisibleDigits &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigits. - * @param value value for VisibleDigits - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigits &initVisibleDigits( - int64_t value, - VisibleDigits &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value value for VisibleDigits - * Caller must not assume that the value of this parameter will remain - * unchanged. - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value value for VisibleDigits - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - double value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value value for VisibleDigits - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -private: - /** - * Attempts to initialize 'digits' using simple mod 10 arithmetic. - * Returns FALSE if this is not possible such as when rounding - * would change the value. Otherwise returns TRUE. - * - * If the method returns FALSE, caller should create a DigitList - * and use it to initialize 'digits'. If this method returns TRUE, - * caller should accept the value stored in 'digits'. If this - * method returns TRUE along with a non zero error, caller must accept - * the error and not try again with a DigitList. - * - * Before calling this method, caller must verify that this object - * has no rounding increment set. - * - * The value that 'digits' is initialized to is mantissa * 10^exponent. - * For example mantissa = 54700 and exponent = -3 means 54.7. The - * properties of this object (such as min and max fraction digits), - * not the number of trailing zeros in the mantissa, determine whether or - * not the result contains any trailing 0's after the decimal point. - * - * @param mantissa the digits. May be positive or negative. May contain - * trailing zeros. - * @param exponent must always be zero or negative. An exponent > 0 - * yields undefined results! - * @param digits result stored here. - * @param status any error returned here. - */ - UBool - initVisibleDigits( - int64_t mantissa, - int32_t exponent, - VisibleDigits &digits, - UErrorCode &status) const; - UBool isRoundingRequired( - int32_t upperExponent, int32_t lowerExponent) const; - DigitInterval &getIntervalForZero(DigitInterval &interval) const; - DigitInterval &getInterval( - int32_t upperExponent, DigitInterval &interval) const; - static UBool handleNonNumeric(DigitList &value, VisibleDigits &digits); - - friend class ScientificPrecision; -}; - -/** - * A precision manager for values to be expressed as scientific notation. - */ -class U_I18N_API ScientificPrecision : public UMemory { -public: - FixedPrecision fMantissa; - int32_t fMinExponentDigits; - - ScientificPrecision(); - - /** - * rounds value in place to prepare it for formatting. - * @param value The value to be rounded. It is rounded in place. - * @param status error returned here. - * @return reference to value. - */ - DigitList &round(DigitList &value, UErrorCode &status) const; - - /** - * Converts value to a mantissa and exponent. - * - * @param value modified in place to be the mantissa. Depending on - * the precision settings, the resulting mantissa may not fall - * between 1.0 and 10.0. - * @return the exponent of value. - */ - int32_t toScientific(DigitList &value) const; - - /** - * Returns TRUE if this object equals rhs. - */ - UBool equals(const ScientificPrecision &rhs) const { - return fMantissa.equals(rhs.fMantissa) && fMinExponentDigits == rhs.fMinExponentDigits; - } - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value the value - * Caller must not assume that the value of this parameter will remain - * unchanged. - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value the value - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - double value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Initializes a VisibleDigitsWithExponent. - * @param value the value - * @param digits This is the value that is initialized. - * @param status any error returned here. - * @return digits - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -private: - int32_t getMultiplier() const; - -}; - - - -U_NAMESPACE_END -#endif // #if !UCONFIG_NO_FORMATTING -#endif // __PRECISION_H__ diff --git a/deps/icu-small/source/i18n/quantityformatter.cpp b/deps/icu-small/source/i18n/quantityformatter.cpp index 208e064700ab2b..ba06ba06b97013 100644 --- a/deps/icu-small/source/i18n/quantityformatter.cpp +++ b/deps/icu-small/source/i18n/quantityformatter.cpp @@ -23,8 +23,8 @@ #include "unicode/fmtable.h" #include "unicode/fieldpos.h" #include "standardplural.h" -#include "visibledigits.h" #include "uassert.h" +#include "number_decimalquantity.h" U_NAMESPACE_BEGIN @@ -149,15 +149,15 @@ StandardPlural::Form QuantityFormatter::selectPlural( return StandardPlural::OTHER; } UnicodeString pluralKeyword; - VisibleDigitsWithExponent digits; const DecimalFormat *decFmt = dynamic_cast(&fmt); if (decFmt != NULL) { - decFmt->initVisibleDigitsWithExponent(number, digits, status); + number::impl::DecimalQuantity dq; + decFmt->formatToDecimalQuantity(number, dq, status); if (U_FAILURE(status)) { return StandardPlural::OTHER; } - pluralKeyword = rules.select(digits); - decFmt->format(digits, formattedNumber, pos, status); + pluralKeyword = rules.select(dq); + decFmt->format(number, formattedNumber, pos, status); } else { if (number.getType() == Formattable::kDouble) { pluralKeyword = rules.select(number.getDouble()); diff --git a/deps/icu-small/source/i18n/rbnf.cpp b/deps/icu-small/source/i18n/rbnf.cpp index 3385f300b11afe..ab9ad15e8c720d 100644 --- a/deps/icu-small/source/i18n/rbnf.cpp +++ b/deps/icu-small/source/i18n/rbnf.cpp @@ -34,7 +34,7 @@ #include "patternprops.h" #include "uresimp.h" #include "nfrs.h" -#include "digitlst.h" +#include "number_decimalquantity.h" // debugging // #define RBNF_DEBUG @@ -68,6 +68,8 @@ static const UChar gSemiPercent[] = U_NAMESPACE_BEGIN +using number::impl::DecimalQuantity; + UOBJECT_DEFINE_RTTI_IMPLEMENTATION(RuleBasedNumberFormat) /* @@ -1109,21 +1111,21 @@ RuleBasedNumberFormat::findRuleSet(const UnicodeString& name, UErrorCode& status } UnicodeString& -RuleBasedNumberFormat::format(const DigitList &number, +RuleBasedNumberFormat::format(const DecimalQuantity &number, UnicodeString &appendTo, FieldPositionIterator *posIter, UErrorCode &status) const { if (U_FAILURE(status)) { return appendTo; } - DigitList copy(number); - if (copy.fitsIntoInt64(false)) { - format(((DigitList &)number).getInt64(), appendTo, posIter, status); + DecimalQuantity copy(number); + if (copy.fitsInLong()) { + format(number.toLong(), appendTo, posIter, status); } else { - copy.roundAtExponent(0); - if (copy.fitsIntoInt64(false)) { - format(number.getDouble(), appendTo, posIter, status); + copy.roundToMagnitude(0, number::impl::RoundingMode::UNUM_ROUND_HALFEVEN, status); + if (copy.fitsInLong()) { + format(number.toDouble(), appendTo, posIter, status); } else { // We're outside of our normal range that this framework can handle. @@ -1132,7 +1134,7 @@ RuleBasedNumberFormat::format(const DigitList &number, // TODO this section should probably be optimized. The DecimalFormat is shared in ICU4J. NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); Formattable f; - f.adoptDigitList(new DigitList(number)); + f.adoptDecimalQuantity(new DecimalQuantity(number)); decimalFormat->format(f, appendTo, posIter, status); delete decimalFormat; } @@ -1142,21 +1144,21 @@ RuleBasedNumberFormat::format(const DigitList &number, UnicodeString& -RuleBasedNumberFormat::format(const DigitList &number, +RuleBasedNumberFormat::format(const DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode &status) const { if (U_FAILURE(status)) { return appendTo; } - DigitList copy(number); - if (copy.fitsIntoInt64(false)) { - format(((DigitList &)number).getInt64(), appendTo, pos, status); + DecimalQuantity copy(number); + if (copy.fitsInLong()) { + format(number.toLong(), appendTo, pos, status); } else { - copy.roundAtExponent(0); - if (copy.fitsIntoInt64(false)) { - format(number.getDouble(), appendTo, pos, status); + copy.roundToMagnitude(0, number::impl::RoundingMode::UNUM_ROUND_HALFEVEN, status); + if (copy.fitsInLong()) { + format(number.toDouble(), appendTo, pos, status); } else { // We're outside of our normal range that this framework can handle. @@ -1165,7 +1167,7 @@ RuleBasedNumberFormat::format(const DigitList &number, // TODO this section should probably be optimized. The DecimalFormat is shared in ICU4J. NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); Formattable f; - f.adoptDigitList(new DigitList(number)); + f.adoptDecimalQuantity(new DecimalQuantity(number)); decimalFormat->format(f, appendTo, pos, status); delete decimalFormat; } @@ -1270,11 +1272,13 @@ RuleBasedNumberFormat::format(double number, { int32_t startPos = toAppendTo.length(); if (getRoundingMode() != DecimalFormat::ERoundingMode::kRoundUnnecessary && !uprv_isNaN(number) && !uprv_isInfinite(number)) { - DigitList digitList; - digitList.set(number); - digitList.setRoundingMode(getRoundingMode()); - digitList.roundFixedPoint(getMaximumFractionDigits()); - number = digitList.getDouble(); + DecimalQuantity digitList; + digitList.setToDouble(number); + digitList.roundToMagnitude( + -getMaximumFractionDigits(), + static_cast(getRoundingMode()), + status); + number = digitList.toDouble(); } rs.format(number, toAppendTo, toAppendTo.length(), 0, status); adjustForCapitalizationContext(startPos, toAppendTo, status); @@ -1310,9 +1314,9 @@ RuleBasedNumberFormat::format(int64_t number, NFRuleSet *ruleSet, UnicodeString& NumberFormat *decimalFormat = NumberFormat::createInstance(locale, UNUM_DECIMAL, status); Formattable f; FieldPosition pos(FieldPosition::DONT_CARE); - DigitList *digitList = new DigitList(); - digitList->set(number); - f.adoptDigitList(digitList); + DecimalQuantity *digitList = new DecimalQuantity(); + digitList->setToLong(number); + f.adoptDecimalQuantity(digitList); decimalFormat->format(f, toAppendTo, pos, status); delete decimalFormat; } diff --git a/deps/icu-small/source/i18n/reldatefmt.cpp b/deps/icu-small/source/i18n/reldatefmt.cpp index 5cf053db9a98fb..42ede7ae4d9903 100644 --- a/deps/icu-small/source/i18n/reldatefmt.cpp +++ b/deps/icu-small/source/i18n/reldatefmt.cpp @@ -14,6 +14,7 @@ #if !UCONFIG_NO_FORMATTING && !UCONFIG_NO_BREAK_ITERATION +#include #include "unicode/dtfmtsym.h" #include "unicode/ucasemap.h" #include "unicode/ureldatefmt.h" @@ -849,7 +850,7 @@ UnicodeString& RelativeDateTimeFormatter::formatNumeric( return appendTo; } UDateDirection direction = UDAT_DIRECTION_NEXT; - if (offset < 0) { + if (std::signbit(offset)) { // needed to handle -0.0 direction = UDAT_DIRECTION_LAST; offset = -offset; } diff --git a/deps/icu-small/source/i18n/scientificnumberformatter.cpp b/deps/icu-small/source/i18n/scientificnumberformatter.cpp index adf032d989dd90..03d98dd6e100e2 100644 --- a/deps/icu-small/source/i18n/scientificnumberformatter.cpp +++ b/deps/icu-small/source/i18n/scientificnumberformatter.cpp @@ -15,8 +15,8 @@ #include "unicode/fpositer.h" #include "unicode/utf16.h" #include "unicode/uniset.h" -#include "decfmtst.h" #include "unicode/decimfmt.h" +#include "static_unicode_sets.h" U_NAMESPACE_BEGIN @@ -129,7 +129,6 @@ UnicodeString &ScientificNumberFormatter::SuperscriptStyle::format( const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets &staticSets, UnicodeString &appendTo, UErrorCode &status) const { if (U_FAILURE(status)) { @@ -149,16 +148,17 @@ UnicodeString &ScientificNumberFormatter::SuperscriptStyle::format( break; case UNUM_EXPONENT_SIGN_FIELD: { + using namespace icu::numparse::impl; int32_t beginIndex = fp.getBeginIndex(); int32_t endIndex = fp.getEndIndex(); UChar32 aChar = original.char32At(beginIndex); - if (staticSets.fMinusSigns->contains(aChar)) { + if (unisets::get(unisets::MINUS_SIGN)->contains(aChar)) { appendTo.append( original, copyFromOffset, beginIndex - copyFromOffset); appendTo.append(kSuperscriptMinusSign); - } else if (staticSets.fPlusSigns->contains(aChar)) { + } else if (unisets::get(unisets::PLUS_SIGN)->contains(aChar)) { appendTo.append( original, copyFromOffset, @@ -203,7 +203,6 @@ UnicodeString &ScientificNumberFormatter::MarkupStyle::format( const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets & /*unusedDecimalFormatSets*/, UnicodeString &appendTo, UErrorCode &status) const { if (U_FAILURE(status)) { @@ -243,8 +242,7 @@ ScientificNumberFormatter::ScientificNumberFormatter( DecimalFormat *fmtToAdopt, Style *styleToAdopt, UErrorCode &status) : fPreExponent(), fDecimalFormat(fmtToAdopt), - fStyle(styleToAdopt), - fStaticSets(NULL) { + fStyle(styleToAdopt) { if (U_FAILURE(status)) { return; } @@ -258,7 +256,6 @@ ScientificNumberFormatter::ScientificNumberFormatter( return; } getPreExponent(*sym, fPreExponent); - fStaticSets = DecimalFormatStaticSets::getStaticSets(status); } ScientificNumberFormatter::ScientificNumberFormatter( @@ -266,8 +263,7 @@ ScientificNumberFormatter::ScientificNumberFormatter( : UObject(other), fPreExponent(other.fPreExponent), fDecimalFormat(NULL), - fStyle(NULL), - fStaticSets(other.fStaticSets) { + fStyle(NULL) { fDecimalFormat = static_cast( other.fDecimalFormat->clone()); fStyle = other.fStyle->clone(); @@ -292,7 +288,6 @@ UnicodeString &ScientificNumberFormatter::format( original, fpi, fPreExponent, - *fStaticSets, appendTo, status); } diff --git a/deps/icu-small/source/i18n/significantdigitinterval.h b/deps/icu-small/source/i18n/significantdigitinterval.h deleted file mode 100644 index fc23370de5b6fd..00000000000000 --- a/deps/icu-small/source/i18n/significantdigitinterval.h +++ /dev/null @@ -1,92 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* significantdigitinterval.h -* -* created on: 2015jan6 -* created by: Travis Keep -*/ - -#ifndef __SIGNIFICANTDIGITINTERVAL_H__ -#define __SIGNIFICANTDIGITINTERVAL_H__ - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -/** - * An interval of allowed significant digit counts. - */ -class U_I18N_API SignificantDigitInterval : public UMemory { -public: - - /** - * No limits on significant digits. - */ - SignificantDigitInterval() - : fMax(INT32_MAX), fMin(0) { } - - /** - * Make this instance have no limit on significant digits. - */ - void clear() { - fMin = 0; - fMax = INT32_MAX; - } - - /** - * Returns TRUE if this object is equal to rhs. - */ - UBool equals(const SignificantDigitInterval &rhs) const { - return ((fMax == rhs.fMax) && (fMin == rhs.fMin)); - } - - /** - * Sets maximum significant digits. 0 or negative means no maximum. - */ - void setMax(int32_t count) { - fMax = count <= 0 ? INT32_MAX : count; - } - - /** - * Get maximum significant digits. INT32_MAX means no maximum. - */ - int32_t getMax() const { - return fMax; - } - - /** - * Sets minimum significant digits. 0 or negative means no minimum. - */ - void setMin(int32_t count) { - fMin = count <= 0 ? 0 : count; - } - - /** - * Get maximum significant digits. 0 means no minimum. - */ - int32_t getMin() const { - return fMin; - } - - /** - * Returns TRUE if this instance represents no constraints on significant - * digits. - */ - UBool isNoConstraints() const { - return fMin == 0 && fMax == INT32_MAX; - } - -private: - int32_t fMax; - int32_t fMin; -}; - -U_NAMESPACE_END - -#endif // __SIGNIFICANTDIGITINTERVAL_H__ diff --git a/deps/icu-small/source/i18n/smallintformatter.cpp b/deps/icu-small/source/i18n/smallintformatter.cpp deleted file mode 100644 index 0c56e38bd69560..00000000000000 --- a/deps/icu-small/source/i18n/smallintformatter.cpp +++ /dev/null @@ -1,2622 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2015, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: smallintformatter.cpp - */ - -#include "unicode/unistr.h" - -#include "smallintformatter.h" - -static const int32_t gMaxFastInt = 4096; - -static const UChar gDigits[] = { - 0x30,0x30,0x30,0x30,0x30,0x30,0x30,0x31, - 0x30,0x30,0x30,0x32,0x30,0x30,0x30,0x33, - 0x30,0x30,0x30,0x34,0x30,0x30,0x30,0x35, - 0x30,0x30,0x30,0x36,0x30,0x30,0x30,0x37, - 0x30,0x30,0x30,0x38,0x30,0x30,0x30,0x39, - 0x30,0x30,0x31,0x30,0x30,0x30,0x31,0x31, - 0x30,0x30,0x31,0x32,0x30,0x30,0x31,0x33, - 0x30,0x30,0x31,0x34,0x30,0x30,0x31,0x35, - 0x30,0x30,0x31,0x36,0x30,0x30,0x31,0x37, - 0x30,0x30,0x31,0x38,0x30,0x30,0x31,0x39, - 0x30,0x30,0x32,0x30,0x30,0x30,0x32,0x31, - 0x30,0x30,0x32,0x32,0x30,0x30,0x32,0x33, - 0x30,0x30,0x32,0x34,0x30,0x30,0x32,0x35, - 0x30,0x30,0x32,0x36,0x30,0x30,0x32,0x37, - 0x30,0x30,0x32,0x38,0x30,0x30,0x32,0x39, - 0x30,0x30,0x33,0x30,0x30,0x30,0x33,0x31, - 0x30,0x30,0x33,0x32,0x30,0x30,0x33,0x33, - 0x30,0x30,0x33,0x34,0x30,0x30,0x33,0x35, - 0x30,0x30,0x33,0x36,0x30,0x30,0x33,0x37, - 0x30,0x30,0x33,0x38,0x30,0x30,0x33,0x39, - 0x30,0x30,0x34,0x30,0x30,0x30,0x34,0x31, - 0x30,0x30,0x34,0x32,0x30,0x30,0x34,0x33, - 0x30,0x30,0x34,0x34,0x30,0x30,0x34,0x35, - 0x30,0x30,0x34,0x36,0x30,0x30,0x34,0x37, - 0x30,0x30,0x34,0x38,0x30,0x30,0x34,0x39, - 0x30,0x30,0x35,0x30,0x30,0x30,0x35,0x31, - 0x30,0x30,0x35,0x32,0x30,0x30,0x35,0x33, - 0x30,0x30,0x35,0x34,0x30,0x30,0x35,0x35, - 0x30,0x30,0x35,0x36,0x30,0x30,0x35,0x37, - 0x30,0x30,0x35,0x38,0x30,0x30,0x35,0x39, - 0x30,0x30,0x36,0x30,0x30,0x30,0x36,0x31, - 0x30,0x30,0x36,0x32,0x30,0x30,0x36,0x33, - 0x30,0x30,0x36,0x34,0x30,0x30,0x36,0x35, - 0x30,0x30,0x36,0x36,0x30,0x30,0x36,0x37, - 0x30,0x30,0x36,0x38,0x30,0x30,0x36,0x39, - 0x30,0x30,0x37,0x30,0x30,0x30,0x37,0x31, - 0x30,0x30,0x37,0x32,0x30,0x30,0x37,0x33, - 0x30,0x30,0x37,0x34,0x30,0x30,0x37,0x35, - 0x30,0x30,0x37,0x36,0x30,0x30,0x37,0x37, - 0x30,0x30,0x37,0x38,0x30,0x30,0x37,0x39, - 0x30,0x30,0x38,0x30,0x30,0x30,0x38,0x31, - 0x30,0x30,0x38,0x32,0x30,0x30,0x38,0x33, - 0x30,0x30,0x38,0x34,0x30,0x30,0x38,0x35, - 0x30,0x30,0x38,0x36,0x30,0x30,0x38,0x37, - 0x30,0x30,0x38,0x38,0x30,0x30,0x38,0x39, - 0x30,0x30,0x39,0x30,0x30,0x30,0x39,0x31, - 0x30,0x30,0x39,0x32,0x30,0x30,0x39,0x33, - 0x30,0x30,0x39,0x34,0x30,0x30,0x39,0x35, - 0x30,0x30,0x39,0x36,0x30,0x30,0x39,0x37, - 0x30,0x30,0x39,0x38,0x30,0x30,0x39,0x39, - 0x30,0x31,0x30,0x30,0x30,0x31,0x30,0x31, - 0x30,0x31,0x30,0x32,0x30,0x31,0x30,0x33, - 0x30,0x31,0x30,0x34,0x30,0x31,0x30,0x35, - 0x30,0x31,0x30,0x36,0x30,0x31,0x30,0x37, - 0x30,0x31,0x30,0x38,0x30,0x31,0x30,0x39, - 0x30,0x31,0x31,0x30,0x30,0x31,0x31,0x31, - 0x30,0x31,0x31,0x32,0x30,0x31,0x31,0x33, - 0x30,0x31,0x31,0x34,0x30,0x31,0x31,0x35, - 0x30,0x31,0x31,0x36,0x30,0x31,0x31,0x37, - 0x30,0x31,0x31,0x38,0x30,0x31,0x31,0x39, - 0x30,0x31,0x32,0x30,0x30,0x31,0x32,0x31, - 0x30,0x31,0x32,0x32,0x30,0x31,0x32,0x33, - 0x30,0x31,0x32,0x34,0x30,0x31,0x32,0x35, - 0x30,0x31,0x32,0x36,0x30,0x31,0x32,0x37, - 0x30,0x31,0x32,0x38,0x30,0x31,0x32,0x39, - 0x30,0x31,0x33,0x30,0x30,0x31,0x33,0x31, - 0x30,0x31,0x33,0x32,0x30,0x31,0x33,0x33, - 0x30,0x31,0x33,0x34,0x30,0x31,0x33,0x35, - 0x30,0x31,0x33,0x36,0x30,0x31,0x33,0x37, - 0x30,0x31,0x33,0x38,0x30,0x31,0x33,0x39, - 0x30,0x31,0x34,0x30,0x30,0x31,0x34,0x31, - 0x30,0x31,0x34,0x32,0x30,0x31,0x34,0x33, - 0x30,0x31,0x34,0x34,0x30,0x31,0x34,0x35, - 0x30,0x31,0x34,0x36,0x30,0x31,0x34,0x37, - 0x30,0x31,0x34,0x38,0x30,0x31,0x34,0x39, - 0x30,0x31,0x35,0x30,0x30,0x31,0x35,0x31, - 0x30,0x31,0x35,0x32,0x30,0x31,0x35,0x33, - 0x30,0x31,0x35,0x34,0x30,0x31,0x35,0x35, - 0x30,0x31,0x35,0x36,0x30,0x31,0x35,0x37, - 0x30,0x31,0x35,0x38,0x30,0x31,0x35,0x39, - 0x30,0x31,0x36,0x30,0x30,0x31,0x36,0x31, - 0x30,0x31,0x36,0x32,0x30,0x31,0x36,0x33, - 0x30,0x31,0x36,0x34,0x30,0x31,0x36,0x35, - 0x30,0x31,0x36,0x36,0x30,0x31,0x36,0x37, - 0x30,0x31,0x36,0x38,0x30,0x31,0x36,0x39, - 0x30,0x31,0x37,0x30,0x30,0x31,0x37,0x31, - 0x30,0x31,0x37,0x32,0x30,0x31,0x37,0x33, - 0x30,0x31,0x37,0x34,0x30,0x31,0x37,0x35, - 0x30,0x31,0x37,0x36,0x30,0x31,0x37,0x37, - 0x30,0x31,0x37,0x38,0x30,0x31,0x37,0x39, - 0x30,0x31,0x38,0x30,0x30,0x31,0x38,0x31, - 0x30,0x31,0x38,0x32,0x30,0x31,0x38,0x33, - 0x30,0x31,0x38,0x34,0x30,0x31,0x38,0x35, - 0x30,0x31,0x38,0x36,0x30,0x31,0x38,0x37, - 0x30,0x31,0x38,0x38,0x30,0x31,0x38,0x39, - 0x30,0x31,0x39,0x30,0x30,0x31,0x39,0x31, - 0x30,0x31,0x39,0x32,0x30,0x31,0x39,0x33, - 0x30,0x31,0x39,0x34,0x30,0x31,0x39,0x35, - 0x30,0x31,0x39,0x36,0x30,0x31,0x39,0x37, - 0x30,0x31,0x39,0x38,0x30,0x31,0x39,0x39, - 0x30,0x32,0x30,0x30,0x30,0x32,0x30,0x31, - 0x30,0x32,0x30,0x32,0x30,0x32,0x30,0x33, - 0x30,0x32,0x30,0x34,0x30,0x32,0x30,0x35, - 0x30,0x32,0x30,0x36,0x30,0x32,0x30,0x37, - 0x30,0x32,0x30,0x38,0x30,0x32,0x30,0x39, - 0x30,0x32,0x31,0x30,0x30,0x32,0x31,0x31, - 0x30,0x32,0x31,0x32,0x30,0x32,0x31,0x33, - 0x30,0x32,0x31,0x34,0x30,0x32,0x31,0x35, - 0x30,0x32,0x31,0x36,0x30,0x32,0x31,0x37, - 0x30,0x32,0x31,0x38,0x30,0x32,0x31,0x39, - 0x30,0x32,0x32,0x30,0x30,0x32,0x32,0x31, - 0x30,0x32,0x32,0x32,0x30,0x32,0x32,0x33, - 0x30,0x32,0x32,0x34,0x30,0x32,0x32,0x35, - 0x30,0x32,0x32,0x36,0x30,0x32,0x32,0x37, - 0x30,0x32,0x32,0x38,0x30,0x32,0x32,0x39, - 0x30,0x32,0x33,0x30,0x30,0x32,0x33,0x31, - 0x30,0x32,0x33,0x32,0x30,0x32,0x33,0x33, - 0x30,0x32,0x33,0x34,0x30,0x32,0x33,0x35, - 0x30,0x32,0x33,0x36,0x30,0x32,0x33,0x37, - 0x30,0x32,0x33,0x38,0x30,0x32,0x33,0x39, - 0x30,0x32,0x34,0x30,0x30,0x32,0x34,0x31, - 0x30,0x32,0x34,0x32,0x30,0x32,0x34,0x33, - 0x30,0x32,0x34,0x34,0x30,0x32,0x34,0x35, - 0x30,0x32,0x34,0x36,0x30,0x32,0x34,0x37, - 0x30,0x32,0x34,0x38,0x30,0x32,0x34,0x39, - 0x30,0x32,0x35,0x30,0x30,0x32,0x35,0x31, - 0x30,0x32,0x35,0x32,0x30,0x32,0x35,0x33, - 0x30,0x32,0x35,0x34,0x30,0x32,0x35,0x35, - 0x30,0x32,0x35,0x36,0x30,0x32,0x35,0x37, - 0x30,0x32,0x35,0x38,0x30,0x32,0x35,0x39, - 0x30,0x32,0x36,0x30,0x30,0x32,0x36,0x31, - 0x30,0x32,0x36,0x32,0x30,0x32,0x36,0x33, - 0x30,0x32,0x36,0x34,0x30,0x32,0x36,0x35, - 0x30,0x32,0x36,0x36,0x30,0x32,0x36,0x37, - 0x30,0x32,0x36,0x38,0x30,0x32,0x36,0x39, - 0x30,0x32,0x37,0x30,0x30,0x32,0x37,0x31, - 0x30,0x32,0x37,0x32,0x30,0x32,0x37,0x33, - 0x30,0x32,0x37,0x34,0x30,0x32,0x37,0x35, - 0x30,0x32,0x37,0x36,0x30,0x32,0x37,0x37, - 0x30,0x32,0x37,0x38,0x30,0x32,0x37,0x39, - 0x30,0x32,0x38,0x30,0x30,0x32,0x38,0x31, - 0x30,0x32,0x38,0x32,0x30,0x32,0x38,0x33, - 0x30,0x32,0x38,0x34,0x30,0x32,0x38,0x35, - 0x30,0x32,0x38,0x36,0x30,0x32,0x38,0x37, - 0x30,0x32,0x38,0x38,0x30,0x32,0x38,0x39, - 0x30,0x32,0x39,0x30,0x30,0x32,0x39,0x31, - 0x30,0x32,0x39,0x32,0x30,0x32,0x39,0x33, - 0x30,0x32,0x39,0x34,0x30,0x32,0x39,0x35, - 0x30,0x32,0x39,0x36,0x30,0x32,0x39,0x37, - 0x30,0x32,0x39,0x38,0x30,0x32,0x39,0x39, - 0x30,0x33,0x30,0x30,0x30,0x33,0x30,0x31, - 0x30,0x33,0x30,0x32,0x30,0x33,0x30,0x33, - 0x30,0x33,0x30,0x34,0x30,0x33,0x30,0x35, - 0x30,0x33,0x30,0x36,0x30,0x33,0x30,0x37, - 0x30,0x33,0x30,0x38,0x30,0x33,0x30,0x39, - 0x30,0x33,0x31,0x30,0x30,0x33,0x31,0x31, - 0x30,0x33,0x31,0x32,0x30,0x33,0x31,0x33, - 0x30,0x33,0x31,0x34,0x30,0x33,0x31,0x35, - 0x30,0x33,0x31,0x36,0x30,0x33,0x31,0x37, - 0x30,0x33,0x31,0x38,0x30,0x33,0x31,0x39, - 0x30,0x33,0x32,0x30,0x30,0x33,0x32,0x31, - 0x30,0x33,0x32,0x32,0x30,0x33,0x32,0x33, - 0x30,0x33,0x32,0x34,0x30,0x33,0x32,0x35, - 0x30,0x33,0x32,0x36,0x30,0x33,0x32,0x37, - 0x30,0x33,0x32,0x38,0x30,0x33,0x32,0x39, - 0x30,0x33,0x33,0x30,0x30,0x33,0x33,0x31, - 0x30,0x33,0x33,0x32,0x30,0x33,0x33,0x33, - 0x30,0x33,0x33,0x34,0x30,0x33,0x33,0x35, - 0x30,0x33,0x33,0x36,0x30,0x33,0x33,0x37, - 0x30,0x33,0x33,0x38,0x30,0x33,0x33,0x39, - 0x30,0x33,0x34,0x30,0x30,0x33,0x34,0x31, - 0x30,0x33,0x34,0x32,0x30,0x33,0x34,0x33, - 0x30,0x33,0x34,0x34,0x30,0x33,0x34,0x35, - 0x30,0x33,0x34,0x36,0x30,0x33,0x34,0x37, - 0x30,0x33,0x34,0x38,0x30,0x33,0x34,0x39, - 0x30,0x33,0x35,0x30,0x30,0x33,0x35,0x31, - 0x30,0x33,0x35,0x32,0x30,0x33,0x35,0x33, - 0x30,0x33,0x35,0x34,0x30,0x33,0x35,0x35, - 0x30,0x33,0x35,0x36,0x30,0x33,0x35,0x37, - 0x30,0x33,0x35,0x38,0x30,0x33,0x35,0x39, - 0x30,0x33,0x36,0x30,0x30,0x33,0x36,0x31, - 0x30,0x33,0x36,0x32,0x30,0x33,0x36,0x33, - 0x30,0x33,0x36,0x34,0x30,0x33,0x36,0x35, - 0x30,0x33,0x36,0x36,0x30,0x33,0x36,0x37, - 0x30,0x33,0x36,0x38,0x30,0x33,0x36,0x39, - 0x30,0x33,0x37,0x30,0x30,0x33,0x37,0x31, - 0x30,0x33,0x37,0x32,0x30,0x33,0x37,0x33, - 0x30,0x33,0x37,0x34,0x30,0x33,0x37,0x35, - 0x30,0x33,0x37,0x36,0x30,0x33,0x37,0x37, - 0x30,0x33,0x37,0x38,0x30,0x33,0x37,0x39, - 0x30,0x33,0x38,0x30,0x30,0x33,0x38,0x31, - 0x30,0x33,0x38,0x32,0x30,0x33,0x38,0x33, - 0x30,0x33,0x38,0x34,0x30,0x33,0x38,0x35, - 0x30,0x33,0x38,0x36,0x30,0x33,0x38,0x37, - 0x30,0x33,0x38,0x38,0x30,0x33,0x38,0x39, - 0x30,0x33,0x39,0x30,0x30,0x33,0x39,0x31, - 0x30,0x33,0x39,0x32,0x30,0x33,0x39,0x33, - 0x30,0x33,0x39,0x34,0x30,0x33,0x39,0x35, - 0x30,0x33,0x39,0x36,0x30,0x33,0x39,0x37, - 0x30,0x33,0x39,0x38,0x30,0x33,0x39,0x39, - 0x30,0x34,0x30,0x30,0x30,0x34,0x30,0x31, - 0x30,0x34,0x30,0x32,0x30,0x34,0x30,0x33, - 0x30,0x34,0x30,0x34,0x30,0x34,0x30,0x35, - 0x30,0x34,0x30,0x36,0x30,0x34,0x30,0x37, - 0x30,0x34,0x30,0x38,0x30,0x34,0x30,0x39, - 0x30,0x34,0x31,0x30,0x30,0x34,0x31,0x31, - 0x30,0x34,0x31,0x32,0x30,0x34,0x31,0x33, - 0x30,0x34,0x31,0x34,0x30,0x34,0x31,0x35, - 0x30,0x34,0x31,0x36,0x30,0x34,0x31,0x37, - 0x30,0x34,0x31,0x38,0x30,0x34,0x31,0x39, - 0x30,0x34,0x32,0x30,0x30,0x34,0x32,0x31, - 0x30,0x34,0x32,0x32,0x30,0x34,0x32,0x33, - 0x30,0x34,0x32,0x34,0x30,0x34,0x32,0x35, - 0x30,0x34,0x32,0x36,0x30,0x34,0x32,0x37, - 0x30,0x34,0x32,0x38,0x30,0x34,0x32,0x39, - 0x30,0x34,0x33,0x30,0x30,0x34,0x33,0x31, - 0x30,0x34,0x33,0x32,0x30,0x34,0x33,0x33, - 0x30,0x34,0x33,0x34,0x30,0x34,0x33,0x35, - 0x30,0x34,0x33,0x36,0x30,0x34,0x33,0x37, - 0x30,0x34,0x33,0x38,0x30,0x34,0x33,0x39, - 0x30,0x34,0x34,0x30,0x30,0x34,0x34,0x31, - 0x30,0x34,0x34,0x32,0x30,0x34,0x34,0x33, - 0x30,0x34,0x34,0x34,0x30,0x34,0x34,0x35, - 0x30,0x34,0x34,0x36,0x30,0x34,0x34,0x37, - 0x30,0x34,0x34,0x38,0x30,0x34,0x34,0x39, - 0x30,0x34,0x35,0x30,0x30,0x34,0x35,0x31, - 0x30,0x34,0x35,0x32,0x30,0x34,0x35,0x33, - 0x30,0x34,0x35,0x34,0x30,0x34,0x35,0x35, - 0x30,0x34,0x35,0x36,0x30,0x34,0x35,0x37, - 0x30,0x34,0x35,0x38,0x30,0x34,0x35,0x39, - 0x30,0x34,0x36,0x30,0x30,0x34,0x36,0x31, - 0x30,0x34,0x36,0x32,0x30,0x34,0x36,0x33, - 0x30,0x34,0x36,0x34,0x30,0x34,0x36,0x35, - 0x30,0x34,0x36,0x36,0x30,0x34,0x36,0x37, - 0x30,0x34,0x36,0x38,0x30,0x34,0x36,0x39, - 0x30,0x34,0x37,0x30,0x30,0x34,0x37,0x31, - 0x30,0x34,0x37,0x32,0x30,0x34,0x37,0x33, - 0x30,0x34,0x37,0x34,0x30,0x34,0x37,0x35, - 0x30,0x34,0x37,0x36,0x30,0x34,0x37,0x37, - 0x30,0x34,0x37,0x38,0x30,0x34,0x37,0x39, - 0x30,0x34,0x38,0x30,0x30,0x34,0x38,0x31, - 0x30,0x34,0x38,0x32,0x30,0x34,0x38,0x33, - 0x30,0x34,0x38,0x34,0x30,0x34,0x38,0x35, - 0x30,0x34,0x38,0x36,0x30,0x34,0x38,0x37, - 0x30,0x34,0x38,0x38,0x30,0x34,0x38,0x39, - 0x30,0x34,0x39,0x30,0x30,0x34,0x39,0x31, - 0x30,0x34,0x39,0x32,0x30,0x34,0x39,0x33, - 0x30,0x34,0x39,0x34,0x30,0x34,0x39,0x35, - 0x30,0x34,0x39,0x36,0x30,0x34,0x39,0x37, - 0x30,0x34,0x39,0x38,0x30,0x34,0x39,0x39, - 0x30,0x35,0x30,0x30,0x30,0x35,0x30,0x31, - 0x30,0x35,0x30,0x32,0x30,0x35,0x30,0x33, - 0x30,0x35,0x30,0x34,0x30,0x35,0x30,0x35, - 0x30,0x35,0x30,0x36,0x30,0x35,0x30,0x37, - 0x30,0x35,0x30,0x38,0x30,0x35,0x30,0x39, - 0x30,0x35,0x31,0x30,0x30,0x35,0x31,0x31, - 0x30,0x35,0x31,0x32,0x30,0x35,0x31,0x33, - 0x30,0x35,0x31,0x34,0x30,0x35,0x31,0x35, - 0x30,0x35,0x31,0x36,0x30,0x35,0x31,0x37, - 0x30,0x35,0x31,0x38,0x30,0x35,0x31,0x39, - 0x30,0x35,0x32,0x30,0x30,0x35,0x32,0x31, - 0x30,0x35,0x32,0x32,0x30,0x35,0x32,0x33, - 0x30,0x35,0x32,0x34,0x30,0x35,0x32,0x35, - 0x30,0x35,0x32,0x36,0x30,0x35,0x32,0x37, - 0x30,0x35,0x32,0x38,0x30,0x35,0x32,0x39, - 0x30,0x35,0x33,0x30,0x30,0x35,0x33,0x31, - 0x30,0x35,0x33,0x32,0x30,0x35,0x33,0x33, - 0x30,0x35,0x33,0x34,0x30,0x35,0x33,0x35, - 0x30,0x35,0x33,0x36,0x30,0x35,0x33,0x37, - 0x30,0x35,0x33,0x38,0x30,0x35,0x33,0x39, - 0x30,0x35,0x34,0x30,0x30,0x35,0x34,0x31, - 0x30,0x35,0x34,0x32,0x30,0x35,0x34,0x33, - 0x30,0x35,0x34,0x34,0x30,0x35,0x34,0x35, - 0x30,0x35,0x34,0x36,0x30,0x35,0x34,0x37, - 0x30,0x35,0x34,0x38,0x30,0x35,0x34,0x39, - 0x30,0x35,0x35,0x30,0x30,0x35,0x35,0x31, - 0x30,0x35,0x35,0x32,0x30,0x35,0x35,0x33, - 0x30,0x35,0x35,0x34,0x30,0x35,0x35,0x35, - 0x30,0x35,0x35,0x36,0x30,0x35,0x35,0x37, - 0x30,0x35,0x35,0x38,0x30,0x35,0x35,0x39, - 0x30,0x35,0x36,0x30,0x30,0x35,0x36,0x31, - 0x30,0x35,0x36,0x32,0x30,0x35,0x36,0x33, - 0x30,0x35,0x36,0x34,0x30,0x35,0x36,0x35, - 0x30,0x35,0x36,0x36,0x30,0x35,0x36,0x37, - 0x30,0x35,0x36,0x38,0x30,0x35,0x36,0x39, - 0x30,0x35,0x37,0x30,0x30,0x35,0x37,0x31, - 0x30,0x35,0x37,0x32,0x30,0x35,0x37,0x33, - 0x30,0x35,0x37,0x34,0x30,0x35,0x37,0x35, - 0x30,0x35,0x37,0x36,0x30,0x35,0x37,0x37, - 0x30,0x35,0x37,0x38,0x30,0x35,0x37,0x39, - 0x30,0x35,0x38,0x30,0x30,0x35,0x38,0x31, - 0x30,0x35,0x38,0x32,0x30,0x35,0x38,0x33, - 0x30,0x35,0x38,0x34,0x30,0x35,0x38,0x35, - 0x30,0x35,0x38,0x36,0x30,0x35,0x38,0x37, - 0x30,0x35,0x38,0x38,0x30,0x35,0x38,0x39, - 0x30,0x35,0x39,0x30,0x30,0x35,0x39,0x31, - 0x30,0x35,0x39,0x32,0x30,0x35,0x39,0x33, - 0x30,0x35,0x39,0x34,0x30,0x35,0x39,0x35, - 0x30,0x35,0x39,0x36,0x30,0x35,0x39,0x37, - 0x30,0x35,0x39,0x38,0x30,0x35,0x39,0x39, - 0x30,0x36,0x30,0x30,0x30,0x36,0x30,0x31, - 0x30,0x36,0x30,0x32,0x30,0x36,0x30,0x33, - 0x30,0x36,0x30,0x34,0x30,0x36,0x30,0x35, - 0x30,0x36,0x30,0x36,0x30,0x36,0x30,0x37, - 0x30,0x36,0x30,0x38,0x30,0x36,0x30,0x39, - 0x30,0x36,0x31,0x30,0x30,0x36,0x31,0x31, - 0x30,0x36,0x31,0x32,0x30,0x36,0x31,0x33, - 0x30,0x36,0x31,0x34,0x30,0x36,0x31,0x35, - 0x30,0x36,0x31,0x36,0x30,0x36,0x31,0x37, - 0x30,0x36,0x31,0x38,0x30,0x36,0x31,0x39, - 0x30,0x36,0x32,0x30,0x30,0x36,0x32,0x31, - 0x30,0x36,0x32,0x32,0x30,0x36,0x32,0x33, - 0x30,0x36,0x32,0x34,0x30,0x36,0x32,0x35, - 0x30,0x36,0x32,0x36,0x30,0x36,0x32,0x37, - 0x30,0x36,0x32,0x38,0x30,0x36,0x32,0x39, - 0x30,0x36,0x33,0x30,0x30,0x36,0x33,0x31, - 0x30,0x36,0x33,0x32,0x30,0x36,0x33,0x33, - 0x30,0x36,0x33,0x34,0x30,0x36,0x33,0x35, - 0x30,0x36,0x33,0x36,0x30,0x36,0x33,0x37, - 0x30,0x36,0x33,0x38,0x30,0x36,0x33,0x39, - 0x30,0x36,0x34,0x30,0x30,0x36,0x34,0x31, - 0x30,0x36,0x34,0x32,0x30,0x36,0x34,0x33, - 0x30,0x36,0x34,0x34,0x30,0x36,0x34,0x35, - 0x30,0x36,0x34,0x36,0x30,0x36,0x34,0x37, - 0x30,0x36,0x34,0x38,0x30,0x36,0x34,0x39, - 0x30,0x36,0x35,0x30,0x30,0x36,0x35,0x31, - 0x30,0x36,0x35,0x32,0x30,0x36,0x35,0x33, - 0x30,0x36,0x35,0x34,0x30,0x36,0x35,0x35, - 0x30,0x36,0x35,0x36,0x30,0x36,0x35,0x37, - 0x30,0x36,0x35,0x38,0x30,0x36,0x35,0x39, - 0x30,0x36,0x36,0x30,0x30,0x36,0x36,0x31, - 0x30,0x36,0x36,0x32,0x30,0x36,0x36,0x33, - 0x30,0x36,0x36,0x34,0x30,0x36,0x36,0x35, - 0x30,0x36,0x36,0x36,0x30,0x36,0x36,0x37, - 0x30,0x36,0x36,0x38,0x30,0x36,0x36,0x39, - 0x30,0x36,0x37,0x30,0x30,0x36,0x37,0x31, - 0x30,0x36,0x37,0x32,0x30,0x36,0x37,0x33, - 0x30,0x36,0x37,0x34,0x30,0x36,0x37,0x35, - 0x30,0x36,0x37,0x36,0x30,0x36,0x37,0x37, - 0x30,0x36,0x37,0x38,0x30,0x36,0x37,0x39, - 0x30,0x36,0x38,0x30,0x30,0x36,0x38,0x31, - 0x30,0x36,0x38,0x32,0x30,0x36,0x38,0x33, - 0x30,0x36,0x38,0x34,0x30,0x36,0x38,0x35, - 0x30,0x36,0x38,0x36,0x30,0x36,0x38,0x37, - 0x30,0x36,0x38,0x38,0x30,0x36,0x38,0x39, - 0x30,0x36,0x39,0x30,0x30,0x36,0x39,0x31, - 0x30,0x36,0x39,0x32,0x30,0x36,0x39,0x33, - 0x30,0x36,0x39,0x34,0x30,0x36,0x39,0x35, - 0x30,0x36,0x39,0x36,0x30,0x36,0x39,0x37, - 0x30,0x36,0x39,0x38,0x30,0x36,0x39,0x39, - 0x30,0x37,0x30,0x30,0x30,0x37,0x30,0x31, - 0x30,0x37,0x30,0x32,0x30,0x37,0x30,0x33, - 0x30,0x37,0x30,0x34,0x30,0x37,0x30,0x35, - 0x30,0x37,0x30,0x36,0x30,0x37,0x30,0x37, - 0x30,0x37,0x30,0x38,0x30,0x37,0x30,0x39, - 0x30,0x37,0x31,0x30,0x30,0x37,0x31,0x31, - 0x30,0x37,0x31,0x32,0x30,0x37,0x31,0x33, - 0x30,0x37,0x31,0x34,0x30,0x37,0x31,0x35, - 0x30,0x37,0x31,0x36,0x30,0x37,0x31,0x37, - 0x30,0x37,0x31,0x38,0x30,0x37,0x31,0x39, - 0x30,0x37,0x32,0x30,0x30,0x37,0x32,0x31, - 0x30,0x37,0x32,0x32,0x30,0x37,0x32,0x33, - 0x30,0x37,0x32,0x34,0x30,0x37,0x32,0x35, - 0x30,0x37,0x32,0x36,0x30,0x37,0x32,0x37, - 0x30,0x37,0x32,0x38,0x30,0x37,0x32,0x39, - 0x30,0x37,0x33,0x30,0x30,0x37,0x33,0x31, - 0x30,0x37,0x33,0x32,0x30,0x37,0x33,0x33, - 0x30,0x37,0x33,0x34,0x30,0x37,0x33,0x35, - 0x30,0x37,0x33,0x36,0x30,0x37,0x33,0x37, - 0x30,0x37,0x33,0x38,0x30,0x37,0x33,0x39, - 0x30,0x37,0x34,0x30,0x30,0x37,0x34,0x31, - 0x30,0x37,0x34,0x32,0x30,0x37,0x34,0x33, - 0x30,0x37,0x34,0x34,0x30,0x37,0x34,0x35, - 0x30,0x37,0x34,0x36,0x30,0x37,0x34,0x37, - 0x30,0x37,0x34,0x38,0x30,0x37,0x34,0x39, - 0x30,0x37,0x35,0x30,0x30,0x37,0x35,0x31, - 0x30,0x37,0x35,0x32,0x30,0x37,0x35,0x33, - 0x30,0x37,0x35,0x34,0x30,0x37,0x35,0x35, - 0x30,0x37,0x35,0x36,0x30,0x37,0x35,0x37, - 0x30,0x37,0x35,0x38,0x30,0x37,0x35,0x39, - 0x30,0x37,0x36,0x30,0x30,0x37,0x36,0x31, - 0x30,0x37,0x36,0x32,0x30,0x37,0x36,0x33, - 0x30,0x37,0x36,0x34,0x30,0x37,0x36,0x35, - 0x30,0x37,0x36,0x36,0x30,0x37,0x36,0x37, - 0x30,0x37,0x36,0x38,0x30,0x37,0x36,0x39, - 0x30,0x37,0x37,0x30,0x30,0x37,0x37,0x31, - 0x30,0x37,0x37,0x32,0x30,0x37,0x37,0x33, - 0x30,0x37,0x37,0x34,0x30,0x37,0x37,0x35, - 0x30,0x37,0x37,0x36,0x30,0x37,0x37,0x37, - 0x30,0x37,0x37,0x38,0x30,0x37,0x37,0x39, - 0x30,0x37,0x38,0x30,0x30,0x37,0x38,0x31, - 0x30,0x37,0x38,0x32,0x30,0x37,0x38,0x33, - 0x30,0x37,0x38,0x34,0x30,0x37,0x38,0x35, - 0x30,0x37,0x38,0x36,0x30,0x37,0x38,0x37, - 0x30,0x37,0x38,0x38,0x30,0x37,0x38,0x39, - 0x30,0x37,0x39,0x30,0x30,0x37,0x39,0x31, - 0x30,0x37,0x39,0x32,0x30,0x37,0x39,0x33, - 0x30,0x37,0x39,0x34,0x30,0x37,0x39,0x35, - 0x30,0x37,0x39,0x36,0x30,0x37,0x39,0x37, - 0x30,0x37,0x39,0x38,0x30,0x37,0x39,0x39, - 0x30,0x38,0x30,0x30,0x30,0x38,0x30,0x31, - 0x30,0x38,0x30,0x32,0x30,0x38,0x30,0x33, - 0x30,0x38,0x30,0x34,0x30,0x38,0x30,0x35, - 0x30,0x38,0x30,0x36,0x30,0x38,0x30,0x37, - 0x30,0x38,0x30,0x38,0x30,0x38,0x30,0x39, - 0x30,0x38,0x31,0x30,0x30,0x38,0x31,0x31, - 0x30,0x38,0x31,0x32,0x30,0x38,0x31,0x33, - 0x30,0x38,0x31,0x34,0x30,0x38,0x31,0x35, - 0x30,0x38,0x31,0x36,0x30,0x38,0x31,0x37, - 0x30,0x38,0x31,0x38,0x30,0x38,0x31,0x39, - 0x30,0x38,0x32,0x30,0x30,0x38,0x32,0x31, - 0x30,0x38,0x32,0x32,0x30,0x38,0x32,0x33, - 0x30,0x38,0x32,0x34,0x30,0x38,0x32,0x35, - 0x30,0x38,0x32,0x36,0x30,0x38,0x32,0x37, - 0x30,0x38,0x32,0x38,0x30,0x38,0x32,0x39, - 0x30,0x38,0x33,0x30,0x30,0x38,0x33,0x31, - 0x30,0x38,0x33,0x32,0x30,0x38,0x33,0x33, - 0x30,0x38,0x33,0x34,0x30,0x38,0x33,0x35, - 0x30,0x38,0x33,0x36,0x30,0x38,0x33,0x37, - 0x30,0x38,0x33,0x38,0x30,0x38,0x33,0x39, - 0x30,0x38,0x34,0x30,0x30,0x38,0x34,0x31, - 0x30,0x38,0x34,0x32,0x30,0x38,0x34,0x33, - 0x30,0x38,0x34,0x34,0x30,0x38,0x34,0x35, - 0x30,0x38,0x34,0x36,0x30,0x38,0x34,0x37, - 0x30,0x38,0x34,0x38,0x30,0x38,0x34,0x39, - 0x30,0x38,0x35,0x30,0x30,0x38,0x35,0x31, - 0x30,0x38,0x35,0x32,0x30,0x38,0x35,0x33, - 0x30,0x38,0x35,0x34,0x30,0x38,0x35,0x35, - 0x30,0x38,0x35,0x36,0x30,0x38,0x35,0x37, - 0x30,0x38,0x35,0x38,0x30,0x38,0x35,0x39, - 0x30,0x38,0x36,0x30,0x30,0x38,0x36,0x31, - 0x30,0x38,0x36,0x32,0x30,0x38,0x36,0x33, - 0x30,0x38,0x36,0x34,0x30,0x38,0x36,0x35, - 0x30,0x38,0x36,0x36,0x30,0x38,0x36,0x37, - 0x30,0x38,0x36,0x38,0x30,0x38,0x36,0x39, - 0x30,0x38,0x37,0x30,0x30,0x38,0x37,0x31, - 0x30,0x38,0x37,0x32,0x30,0x38,0x37,0x33, - 0x30,0x38,0x37,0x34,0x30,0x38,0x37,0x35, - 0x30,0x38,0x37,0x36,0x30,0x38,0x37,0x37, - 0x30,0x38,0x37,0x38,0x30,0x38,0x37,0x39, - 0x30,0x38,0x38,0x30,0x30,0x38,0x38,0x31, - 0x30,0x38,0x38,0x32,0x30,0x38,0x38,0x33, - 0x30,0x38,0x38,0x34,0x30,0x38,0x38,0x35, - 0x30,0x38,0x38,0x36,0x30,0x38,0x38,0x37, - 0x30,0x38,0x38,0x38,0x30,0x38,0x38,0x39, - 0x30,0x38,0x39,0x30,0x30,0x38,0x39,0x31, - 0x30,0x38,0x39,0x32,0x30,0x38,0x39,0x33, - 0x30,0x38,0x39,0x34,0x30,0x38,0x39,0x35, - 0x30,0x38,0x39,0x36,0x30,0x38,0x39,0x37, - 0x30,0x38,0x39,0x38,0x30,0x38,0x39,0x39, - 0x30,0x39,0x30,0x30,0x30,0x39,0x30,0x31, - 0x30,0x39,0x30,0x32,0x30,0x39,0x30,0x33, - 0x30,0x39,0x30,0x34,0x30,0x39,0x30,0x35, - 0x30,0x39,0x30,0x36,0x30,0x39,0x30,0x37, - 0x30,0x39,0x30,0x38,0x30,0x39,0x30,0x39, - 0x30,0x39,0x31,0x30,0x30,0x39,0x31,0x31, - 0x30,0x39,0x31,0x32,0x30,0x39,0x31,0x33, - 0x30,0x39,0x31,0x34,0x30,0x39,0x31,0x35, - 0x30,0x39,0x31,0x36,0x30,0x39,0x31,0x37, - 0x30,0x39,0x31,0x38,0x30,0x39,0x31,0x39, - 0x30,0x39,0x32,0x30,0x30,0x39,0x32,0x31, - 0x30,0x39,0x32,0x32,0x30,0x39,0x32,0x33, - 0x30,0x39,0x32,0x34,0x30,0x39,0x32,0x35, - 0x30,0x39,0x32,0x36,0x30,0x39,0x32,0x37, - 0x30,0x39,0x32,0x38,0x30,0x39,0x32,0x39, - 0x30,0x39,0x33,0x30,0x30,0x39,0x33,0x31, - 0x30,0x39,0x33,0x32,0x30,0x39,0x33,0x33, - 0x30,0x39,0x33,0x34,0x30,0x39,0x33,0x35, - 0x30,0x39,0x33,0x36,0x30,0x39,0x33,0x37, - 0x30,0x39,0x33,0x38,0x30,0x39,0x33,0x39, - 0x30,0x39,0x34,0x30,0x30,0x39,0x34,0x31, - 0x30,0x39,0x34,0x32,0x30,0x39,0x34,0x33, - 0x30,0x39,0x34,0x34,0x30,0x39,0x34,0x35, - 0x30,0x39,0x34,0x36,0x30,0x39,0x34,0x37, - 0x30,0x39,0x34,0x38,0x30,0x39,0x34,0x39, - 0x30,0x39,0x35,0x30,0x30,0x39,0x35,0x31, - 0x30,0x39,0x35,0x32,0x30,0x39,0x35,0x33, - 0x30,0x39,0x35,0x34,0x30,0x39,0x35,0x35, - 0x30,0x39,0x35,0x36,0x30,0x39,0x35,0x37, - 0x30,0x39,0x35,0x38,0x30,0x39,0x35,0x39, - 0x30,0x39,0x36,0x30,0x30,0x39,0x36,0x31, - 0x30,0x39,0x36,0x32,0x30,0x39,0x36,0x33, - 0x30,0x39,0x36,0x34,0x30,0x39,0x36,0x35, - 0x30,0x39,0x36,0x36,0x30,0x39,0x36,0x37, - 0x30,0x39,0x36,0x38,0x30,0x39,0x36,0x39, - 0x30,0x39,0x37,0x30,0x30,0x39,0x37,0x31, - 0x30,0x39,0x37,0x32,0x30,0x39,0x37,0x33, - 0x30,0x39,0x37,0x34,0x30,0x39,0x37,0x35, - 0x30,0x39,0x37,0x36,0x30,0x39,0x37,0x37, - 0x30,0x39,0x37,0x38,0x30,0x39,0x37,0x39, - 0x30,0x39,0x38,0x30,0x30,0x39,0x38,0x31, - 0x30,0x39,0x38,0x32,0x30,0x39,0x38,0x33, - 0x30,0x39,0x38,0x34,0x30,0x39,0x38,0x35, - 0x30,0x39,0x38,0x36,0x30,0x39,0x38,0x37, - 0x30,0x39,0x38,0x38,0x30,0x39,0x38,0x39, - 0x30,0x39,0x39,0x30,0x30,0x39,0x39,0x31, - 0x30,0x39,0x39,0x32,0x30,0x39,0x39,0x33, - 0x30,0x39,0x39,0x34,0x30,0x39,0x39,0x35, - 0x30,0x39,0x39,0x36,0x30,0x39,0x39,0x37, - 0x30,0x39,0x39,0x38,0x30,0x39,0x39,0x39, - 0x31,0x30,0x30,0x30,0x31,0x30,0x30,0x31, - 0x31,0x30,0x30,0x32,0x31,0x30,0x30,0x33, - 0x31,0x30,0x30,0x34,0x31,0x30,0x30,0x35, - 0x31,0x30,0x30,0x36,0x31,0x30,0x30,0x37, - 0x31,0x30,0x30,0x38,0x31,0x30,0x30,0x39, - 0x31,0x30,0x31,0x30,0x31,0x30,0x31,0x31, - 0x31,0x30,0x31,0x32,0x31,0x30,0x31,0x33, - 0x31,0x30,0x31,0x34,0x31,0x30,0x31,0x35, - 0x31,0x30,0x31,0x36,0x31,0x30,0x31,0x37, - 0x31,0x30,0x31,0x38,0x31,0x30,0x31,0x39, - 0x31,0x30,0x32,0x30,0x31,0x30,0x32,0x31, - 0x31,0x30,0x32,0x32,0x31,0x30,0x32,0x33, - 0x31,0x30,0x32,0x34,0x31,0x30,0x32,0x35, - 0x31,0x30,0x32,0x36,0x31,0x30,0x32,0x37, - 0x31,0x30,0x32,0x38,0x31,0x30,0x32,0x39, - 0x31,0x30,0x33,0x30,0x31,0x30,0x33,0x31, - 0x31,0x30,0x33,0x32,0x31,0x30,0x33,0x33, - 0x31,0x30,0x33,0x34,0x31,0x30,0x33,0x35, - 0x31,0x30,0x33,0x36,0x31,0x30,0x33,0x37, - 0x31,0x30,0x33,0x38,0x31,0x30,0x33,0x39, - 0x31,0x30,0x34,0x30,0x31,0x30,0x34,0x31, - 0x31,0x30,0x34,0x32,0x31,0x30,0x34,0x33, - 0x31,0x30,0x34,0x34,0x31,0x30,0x34,0x35, - 0x31,0x30,0x34,0x36,0x31,0x30,0x34,0x37, - 0x31,0x30,0x34,0x38,0x31,0x30,0x34,0x39, - 0x31,0x30,0x35,0x30,0x31,0x30,0x35,0x31, - 0x31,0x30,0x35,0x32,0x31,0x30,0x35,0x33, - 0x31,0x30,0x35,0x34,0x31,0x30,0x35,0x35, - 0x31,0x30,0x35,0x36,0x31,0x30,0x35,0x37, - 0x31,0x30,0x35,0x38,0x31,0x30,0x35,0x39, - 0x31,0x30,0x36,0x30,0x31,0x30,0x36,0x31, - 0x31,0x30,0x36,0x32,0x31,0x30,0x36,0x33, - 0x31,0x30,0x36,0x34,0x31,0x30,0x36,0x35, - 0x31,0x30,0x36,0x36,0x31,0x30,0x36,0x37, - 0x31,0x30,0x36,0x38,0x31,0x30,0x36,0x39, - 0x31,0x30,0x37,0x30,0x31,0x30,0x37,0x31, - 0x31,0x30,0x37,0x32,0x31,0x30,0x37,0x33, - 0x31,0x30,0x37,0x34,0x31,0x30,0x37,0x35, - 0x31,0x30,0x37,0x36,0x31,0x30,0x37,0x37, - 0x31,0x30,0x37,0x38,0x31,0x30,0x37,0x39, - 0x31,0x30,0x38,0x30,0x31,0x30,0x38,0x31, - 0x31,0x30,0x38,0x32,0x31,0x30,0x38,0x33, - 0x31,0x30,0x38,0x34,0x31,0x30,0x38,0x35, - 0x31,0x30,0x38,0x36,0x31,0x30,0x38,0x37, - 0x31,0x30,0x38,0x38,0x31,0x30,0x38,0x39, - 0x31,0x30,0x39,0x30,0x31,0x30,0x39,0x31, - 0x31,0x30,0x39,0x32,0x31,0x30,0x39,0x33, - 0x31,0x30,0x39,0x34,0x31,0x30,0x39,0x35, - 0x31,0x30,0x39,0x36,0x31,0x30,0x39,0x37, - 0x31,0x30,0x39,0x38,0x31,0x30,0x39,0x39, - 0x31,0x31,0x30,0x30,0x31,0x31,0x30,0x31, - 0x31,0x31,0x30,0x32,0x31,0x31,0x30,0x33, - 0x31,0x31,0x30,0x34,0x31,0x31,0x30,0x35, - 0x31,0x31,0x30,0x36,0x31,0x31,0x30,0x37, - 0x31,0x31,0x30,0x38,0x31,0x31,0x30,0x39, - 0x31,0x31,0x31,0x30,0x31,0x31,0x31,0x31, - 0x31,0x31,0x31,0x32,0x31,0x31,0x31,0x33, - 0x31,0x31,0x31,0x34,0x31,0x31,0x31,0x35, - 0x31,0x31,0x31,0x36,0x31,0x31,0x31,0x37, - 0x31,0x31,0x31,0x38,0x31,0x31,0x31,0x39, - 0x31,0x31,0x32,0x30,0x31,0x31,0x32,0x31, - 0x31,0x31,0x32,0x32,0x31,0x31,0x32,0x33, - 0x31,0x31,0x32,0x34,0x31,0x31,0x32,0x35, - 0x31,0x31,0x32,0x36,0x31,0x31,0x32,0x37, - 0x31,0x31,0x32,0x38,0x31,0x31,0x32,0x39, - 0x31,0x31,0x33,0x30,0x31,0x31,0x33,0x31, - 0x31,0x31,0x33,0x32,0x31,0x31,0x33,0x33, - 0x31,0x31,0x33,0x34,0x31,0x31,0x33,0x35, - 0x31,0x31,0x33,0x36,0x31,0x31,0x33,0x37, - 0x31,0x31,0x33,0x38,0x31,0x31,0x33,0x39, - 0x31,0x31,0x34,0x30,0x31,0x31,0x34,0x31, - 0x31,0x31,0x34,0x32,0x31,0x31,0x34,0x33, - 0x31,0x31,0x34,0x34,0x31,0x31,0x34,0x35, - 0x31,0x31,0x34,0x36,0x31,0x31,0x34,0x37, - 0x31,0x31,0x34,0x38,0x31,0x31,0x34,0x39, - 0x31,0x31,0x35,0x30,0x31,0x31,0x35,0x31, - 0x31,0x31,0x35,0x32,0x31,0x31,0x35,0x33, - 0x31,0x31,0x35,0x34,0x31,0x31,0x35,0x35, - 0x31,0x31,0x35,0x36,0x31,0x31,0x35,0x37, - 0x31,0x31,0x35,0x38,0x31,0x31,0x35,0x39, - 0x31,0x31,0x36,0x30,0x31,0x31,0x36,0x31, - 0x31,0x31,0x36,0x32,0x31,0x31,0x36,0x33, - 0x31,0x31,0x36,0x34,0x31,0x31,0x36,0x35, - 0x31,0x31,0x36,0x36,0x31,0x31,0x36,0x37, - 0x31,0x31,0x36,0x38,0x31,0x31,0x36,0x39, - 0x31,0x31,0x37,0x30,0x31,0x31,0x37,0x31, - 0x31,0x31,0x37,0x32,0x31,0x31,0x37,0x33, - 0x31,0x31,0x37,0x34,0x31,0x31,0x37,0x35, - 0x31,0x31,0x37,0x36,0x31,0x31,0x37,0x37, - 0x31,0x31,0x37,0x38,0x31,0x31,0x37,0x39, - 0x31,0x31,0x38,0x30,0x31,0x31,0x38,0x31, - 0x31,0x31,0x38,0x32,0x31,0x31,0x38,0x33, - 0x31,0x31,0x38,0x34,0x31,0x31,0x38,0x35, - 0x31,0x31,0x38,0x36,0x31,0x31,0x38,0x37, - 0x31,0x31,0x38,0x38,0x31,0x31,0x38,0x39, - 0x31,0x31,0x39,0x30,0x31,0x31,0x39,0x31, - 0x31,0x31,0x39,0x32,0x31,0x31,0x39,0x33, - 0x31,0x31,0x39,0x34,0x31,0x31,0x39,0x35, - 0x31,0x31,0x39,0x36,0x31,0x31,0x39,0x37, - 0x31,0x31,0x39,0x38,0x31,0x31,0x39,0x39, - 0x31,0x32,0x30,0x30,0x31,0x32,0x30,0x31, - 0x31,0x32,0x30,0x32,0x31,0x32,0x30,0x33, - 0x31,0x32,0x30,0x34,0x31,0x32,0x30,0x35, - 0x31,0x32,0x30,0x36,0x31,0x32,0x30,0x37, - 0x31,0x32,0x30,0x38,0x31,0x32,0x30,0x39, - 0x31,0x32,0x31,0x30,0x31,0x32,0x31,0x31, - 0x31,0x32,0x31,0x32,0x31,0x32,0x31,0x33, - 0x31,0x32,0x31,0x34,0x31,0x32,0x31,0x35, - 0x31,0x32,0x31,0x36,0x31,0x32,0x31,0x37, - 0x31,0x32,0x31,0x38,0x31,0x32,0x31,0x39, - 0x31,0x32,0x32,0x30,0x31,0x32,0x32,0x31, - 0x31,0x32,0x32,0x32,0x31,0x32,0x32,0x33, - 0x31,0x32,0x32,0x34,0x31,0x32,0x32,0x35, - 0x31,0x32,0x32,0x36,0x31,0x32,0x32,0x37, - 0x31,0x32,0x32,0x38,0x31,0x32,0x32,0x39, - 0x31,0x32,0x33,0x30,0x31,0x32,0x33,0x31, - 0x31,0x32,0x33,0x32,0x31,0x32,0x33,0x33, - 0x31,0x32,0x33,0x34,0x31,0x32,0x33,0x35, - 0x31,0x32,0x33,0x36,0x31,0x32,0x33,0x37, - 0x31,0x32,0x33,0x38,0x31,0x32,0x33,0x39, - 0x31,0x32,0x34,0x30,0x31,0x32,0x34,0x31, - 0x31,0x32,0x34,0x32,0x31,0x32,0x34,0x33, - 0x31,0x32,0x34,0x34,0x31,0x32,0x34,0x35, - 0x31,0x32,0x34,0x36,0x31,0x32,0x34,0x37, - 0x31,0x32,0x34,0x38,0x31,0x32,0x34,0x39, - 0x31,0x32,0x35,0x30,0x31,0x32,0x35,0x31, - 0x31,0x32,0x35,0x32,0x31,0x32,0x35,0x33, - 0x31,0x32,0x35,0x34,0x31,0x32,0x35,0x35, - 0x31,0x32,0x35,0x36,0x31,0x32,0x35,0x37, - 0x31,0x32,0x35,0x38,0x31,0x32,0x35,0x39, - 0x31,0x32,0x36,0x30,0x31,0x32,0x36,0x31, - 0x31,0x32,0x36,0x32,0x31,0x32,0x36,0x33, - 0x31,0x32,0x36,0x34,0x31,0x32,0x36,0x35, - 0x31,0x32,0x36,0x36,0x31,0x32,0x36,0x37, - 0x31,0x32,0x36,0x38,0x31,0x32,0x36,0x39, - 0x31,0x32,0x37,0x30,0x31,0x32,0x37,0x31, - 0x31,0x32,0x37,0x32,0x31,0x32,0x37,0x33, - 0x31,0x32,0x37,0x34,0x31,0x32,0x37,0x35, - 0x31,0x32,0x37,0x36,0x31,0x32,0x37,0x37, - 0x31,0x32,0x37,0x38,0x31,0x32,0x37,0x39, - 0x31,0x32,0x38,0x30,0x31,0x32,0x38,0x31, - 0x31,0x32,0x38,0x32,0x31,0x32,0x38,0x33, - 0x31,0x32,0x38,0x34,0x31,0x32,0x38,0x35, - 0x31,0x32,0x38,0x36,0x31,0x32,0x38,0x37, - 0x31,0x32,0x38,0x38,0x31,0x32,0x38,0x39, - 0x31,0x32,0x39,0x30,0x31,0x32,0x39,0x31, - 0x31,0x32,0x39,0x32,0x31,0x32,0x39,0x33, - 0x31,0x32,0x39,0x34,0x31,0x32,0x39,0x35, - 0x31,0x32,0x39,0x36,0x31,0x32,0x39,0x37, - 0x31,0x32,0x39,0x38,0x31,0x32,0x39,0x39, - 0x31,0x33,0x30,0x30,0x31,0x33,0x30,0x31, - 0x31,0x33,0x30,0x32,0x31,0x33,0x30,0x33, - 0x31,0x33,0x30,0x34,0x31,0x33,0x30,0x35, - 0x31,0x33,0x30,0x36,0x31,0x33,0x30,0x37, - 0x31,0x33,0x30,0x38,0x31,0x33,0x30,0x39, - 0x31,0x33,0x31,0x30,0x31,0x33,0x31,0x31, - 0x31,0x33,0x31,0x32,0x31,0x33,0x31,0x33, - 0x31,0x33,0x31,0x34,0x31,0x33,0x31,0x35, - 0x31,0x33,0x31,0x36,0x31,0x33,0x31,0x37, - 0x31,0x33,0x31,0x38,0x31,0x33,0x31,0x39, - 0x31,0x33,0x32,0x30,0x31,0x33,0x32,0x31, - 0x31,0x33,0x32,0x32,0x31,0x33,0x32,0x33, - 0x31,0x33,0x32,0x34,0x31,0x33,0x32,0x35, - 0x31,0x33,0x32,0x36,0x31,0x33,0x32,0x37, - 0x31,0x33,0x32,0x38,0x31,0x33,0x32,0x39, - 0x31,0x33,0x33,0x30,0x31,0x33,0x33,0x31, - 0x31,0x33,0x33,0x32,0x31,0x33,0x33,0x33, - 0x31,0x33,0x33,0x34,0x31,0x33,0x33,0x35, - 0x31,0x33,0x33,0x36,0x31,0x33,0x33,0x37, - 0x31,0x33,0x33,0x38,0x31,0x33,0x33,0x39, - 0x31,0x33,0x34,0x30,0x31,0x33,0x34,0x31, - 0x31,0x33,0x34,0x32,0x31,0x33,0x34,0x33, - 0x31,0x33,0x34,0x34,0x31,0x33,0x34,0x35, - 0x31,0x33,0x34,0x36,0x31,0x33,0x34,0x37, - 0x31,0x33,0x34,0x38,0x31,0x33,0x34,0x39, - 0x31,0x33,0x35,0x30,0x31,0x33,0x35,0x31, - 0x31,0x33,0x35,0x32,0x31,0x33,0x35,0x33, - 0x31,0x33,0x35,0x34,0x31,0x33,0x35,0x35, - 0x31,0x33,0x35,0x36,0x31,0x33,0x35,0x37, - 0x31,0x33,0x35,0x38,0x31,0x33,0x35,0x39, - 0x31,0x33,0x36,0x30,0x31,0x33,0x36,0x31, - 0x31,0x33,0x36,0x32,0x31,0x33,0x36,0x33, - 0x31,0x33,0x36,0x34,0x31,0x33,0x36,0x35, - 0x31,0x33,0x36,0x36,0x31,0x33,0x36,0x37, - 0x31,0x33,0x36,0x38,0x31,0x33,0x36,0x39, - 0x31,0x33,0x37,0x30,0x31,0x33,0x37,0x31, - 0x31,0x33,0x37,0x32,0x31,0x33,0x37,0x33, - 0x31,0x33,0x37,0x34,0x31,0x33,0x37,0x35, - 0x31,0x33,0x37,0x36,0x31,0x33,0x37,0x37, - 0x31,0x33,0x37,0x38,0x31,0x33,0x37,0x39, - 0x31,0x33,0x38,0x30,0x31,0x33,0x38,0x31, - 0x31,0x33,0x38,0x32,0x31,0x33,0x38,0x33, - 0x31,0x33,0x38,0x34,0x31,0x33,0x38,0x35, - 0x31,0x33,0x38,0x36,0x31,0x33,0x38,0x37, - 0x31,0x33,0x38,0x38,0x31,0x33,0x38,0x39, - 0x31,0x33,0x39,0x30,0x31,0x33,0x39,0x31, - 0x31,0x33,0x39,0x32,0x31,0x33,0x39,0x33, - 0x31,0x33,0x39,0x34,0x31,0x33,0x39,0x35, - 0x31,0x33,0x39,0x36,0x31,0x33,0x39,0x37, - 0x31,0x33,0x39,0x38,0x31,0x33,0x39,0x39, - 0x31,0x34,0x30,0x30,0x31,0x34,0x30,0x31, - 0x31,0x34,0x30,0x32,0x31,0x34,0x30,0x33, - 0x31,0x34,0x30,0x34,0x31,0x34,0x30,0x35, - 0x31,0x34,0x30,0x36,0x31,0x34,0x30,0x37, - 0x31,0x34,0x30,0x38,0x31,0x34,0x30,0x39, - 0x31,0x34,0x31,0x30,0x31,0x34,0x31,0x31, - 0x31,0x34,0x31,0x32,0x31,0x34,0x31,0x33, - 0x31,0x34,0x31,0x34,0x31,0x34,0x31,0x35, - 0x31,0x34,0x31,0x36,0x31,0x34,0x31,0x37, - 0x31,0x34,0x31,0x38,0x31,0x34,0x31,0x39, - 0x31,0x34,0x32,0x30,0x31,0x34,0x32,0x31, - 0x31,0x34,0x32,0x32,0x31,0x34,0x32,0x33, - 0x31,0x34,0x32,0x34,0x31,0x34,0x32,0x35, - 0x31,0x34,0x32,0x36,0x31,0x34,0x32,0x37, - 0x31,0x34,0x32,0x38,0x31,0x34,0x32,0x39, - 0x31,0x34,0x33,0x30,0x31,0x34,0x33,0x31, - 0x31,0x34,0x33,0x32,0x31,0x34,0x33,0x33, - 0x31,0x34,0x33,0x34,0x31,0x34,0x33,0x35, - 0x31,0x34,0x33,0x36,0x31,0x34,0x33,0x37, - 0x31,0x34,0x33,0x38,0x31,0x34,0x33,0x39, - 0x31,0x34,0x34,0x30,0x31,0x34,0x34,0x31, - 0x31,0x34,0x34,0x32,0x31,0x34,0x34,0x33, - 0x31,0x34,0x34,0x34,0x31,0x34,0x34,0x35, - 0x31,0x34,0x34,0x36,0x31,0x34,0x34,0x37, - 0x31,0x34,0x34,0x38,0x31,0x34,0x34,0x39, - 0x31,0x34,0x35,0x30,0x31,0x34,0x35,0x31, - 0x31,0x34,0x35,0x32,0x31,0x34,0x35,0x33, - 0x31,0x34,0x35,0x34,0x31,0x34,0x35,0x35, - 0x31,0x34,0x35,0x36,0x31,0x34,0x35,0x37, - 0x31,0x34,0x35,0x38,0x31,0x34,0x35,0x39, - 0x31,0x34,0x36,0x30,0x31,0x34,0x36,0x31, - 0x31,0x34,0x36,0x32,0x31,0x34,0x36,0x33, - 0x31,0x34,0x36,0x34,0x31,0x34,0x36,0x35, - 0x31,0x34,0x36,0x36,0x31,0x34,0x36,0x37, - 0x31,0x34,0x36,0x38,0x31,0x34,0x36,0x39, - 0x31,0x34,0x37,0x30,0x31,0x34,0x37,0x31, - 0x31,0x34,0x37,0x32,0x31,0x34,0x37,0x33, - 0x31,0x34,0x37,0x34,0x31,0x34,0x37,0x35, - 0x31,0x34,0x37,0x36,0x31,0x34,0x37,0x37, - 0x31,0x34,0x37,0x38,0x31,0x34,0x37,0x39, - 0x31,0x34,0x38,0x30,0x31,0x34,0x38,0x31, - 0x31,0x34,0x38,0x32,0x31,0x34,0x38,0x33, - 0x31,0x34,0x38,0x34,0x31,0x34,0x38,0x35, - 0x31,0x34,0x38,0x36,0x31,0x34,0x38,0x37, - 0x31,0x34,0x38,0x38,0x31,0x34,0x38,0x39, - 0x31,0x34,0x39,0x30,0x31,0x34,0x39,0x31, - 0x31,0x34,0x39,0x32,0x31,0x34,0x39,0x33, - 0x31,0x34,0x39,0x34,0x31,0x34,0x39,0x35, - 0x31,0x34,0x39,0x36,0x31,0x34,0x39,0x37, - 0x31,0x34,0x39,0x38,0x31,0x34,0x39,0x39, - 0x31,0x35,0x30,0x30,0x31,0x35,0x30,0x31, - 0x31,0x35,0x30,0x32,0x31,0x35,0x30,0x33, - 0x31,0x35,0x30,0x34,0x31,0x35,0x30,0x35, - 0x31,0x35,0x30,0x36,0x31,0x35,0x30,0x37, - 0x31,0x35,0x30,0x38,0x31,0x35,0x30,0x39, - 0x31,0x35,0x31,0x30,0x31,0x35,0x31,0x31, - 0x31,0x35,0x31,0x32,0x31,0x35,0x31,0x33, - 0x31,0x35,0x31,0x34,0x31,0x35,0x31,0x35, - 0x31,0x35,0x31,0x36,0x31,0x35,0x31,0x37, - 0x31,0x35,0x31,0x38,0x31,0x35,0x31,0x39, - 0x31,0x35,0x32,0x30,0x31,0x35,0x32,0x31, - 0x31,0x35,0x32,0x32,0x31,0x35,0x32,0x33, - 0x31,0x35,0x32,0x34,0x31,0x35,0x32,0x35, - 0x31,0x35,0x32,0x36,0x31,0x35,0x32,0x37, - 0x31,0x35,0x32,0x38,0x31,0x35,0x32,0x39, - 0x31,0x35,0x33,0x30,0x31,0x35,0x33,0x31, - 0x31,0x35,0x33,0x32,0x31,0x35,0x33,0x33, - 0x31,0x35,0x33,0x34,0x31,0x35,0x33,0x35, - 0x31,0x35,0x33,0x36,0x31,0x35,0x33,0x37, - 0x31,0x35,0x33,0x38,0x31,0x35,0x33,0x39, - 0x31,0x35,0x34,0x30,0x31,0x35,0x34,0x31, - 0x31,0x35,0x34,0x32,0x31,0x35,0x34,0x33, - 0x31,0x35,0x34,0x34,0x31,0x35,0x34,0x35, - 0x31,0x35,0x34,0x36,0x31,0x35,0x34,0x37, - 0x31,0x35,0x34,0x38,0x31,0x35,0x34,0x39, - 0x31,0x35,0x35,0x30,0x31,0x35,0x35,0x31, - 0x31,0x35,0x35,0x32,0x31,0x35,0x35,0x33, - 0x31,0x35,0x35,0x34,0x31,0x35,0x35,0x35, - 0x31,0x35,0x35,0x36,0x31,0x35,0x35,0x37, - 0x31,0x35,0x35,0x38,0x31,0x35,0x35,0x39, - 0x31,0x35,0x36,0x30,0x31,0x35,0x36,0x31, - 0x31,0x35,0x36,0x32,0x31,0x35,0x36,0x33, - 0x31,0x35,0x36,0x34,0x31,0x35,0x36,0x35, - 0x31,0x35,0x36,0x36,0x31,0x35,0x36,0x37, - 0x31,0x35,0x36,0x38,0x31,0x35,0x36,0x39, - 0x31,0x35,0x37,0x30,0x31,0x35,0x37,0x31, - 0x31,0x35,0x37,0x32,0x31,0x35,0x37,0x33, - 0x31,0x35,0x37,0x34,0x31,0x35,0x37,0x35, - 0x31,0x35,0x37,0x36,0x31,0x35,0x37,0x37, - 0x31,0x35,0x37,0x38,0x31,0x35,0x37,0x39, - 0x31,0x35,0x38,0x30,0x31,0x35,0x38,0x31, - 0x31,0x35,0x38,0x32,0x31,0x35,0x38,0x33, - 0x31,0x35,0x38,0x34,0x31,0x35,0x38,0x35, - 0x31,0x35,0x38,0x36,0x31,0x35,0x38,0x37, - 0x31,0x35,0x38,0x38,0x31,0x35,0x38,0x39, - 0x31,0x35,0x39,0x30,0x31,0x35,0x39,0x31, - 0x31,0x35,0x39,0x32,0x31,0x35,0x39,0x33, - 0x31,0x35,0x39,0x34,0x31,0x35,0x39,0x35, - 0x31,0x35,0x39,0x36,0x31,0x35,0x39,0x37, - 0x31,0x35,0x39,0x38,0x31,0x35,0x39,0x39, - 0x31,0x36,0x30,0x30,0x31,0x36,0x30,0x31, - 0x31,0x36,0x30,0x32,0x31,0x36,0x30,0x33, - 0x31,0x36,0x30,0x34,0x31,0x36,0x30,0x35, - 0x31,0x36,0x30,0x36,0x31,0x36,0x30,0x37, - 0x31,0x36,0x30,0x38,0x31,0x36,0x30,0x39, - 0x31,0x36,0x31,0x30,0x31,0x36,0x31,0x31, - 0x31,0x36,0x31,0x32,0x31,0x36,0x31,0x33, - 0x31,0x36,0x31,0x34,0x31,0x36,0x31,0x35, - 0x31,0x36,0x31,0x36,0x31,0x36,0x31,0x37, - 0x31,0x36,0x31,0x38,0x31,0x36,0x31,0x39, - 0x31,0x36,0x32,0x30,0x31,0x36,0x32,0x31, - 0x31,0x36,0x32,0x32,0x31,0x36,0x32,0x33, - 0x31,0x36,0x32,0x34,0x31,0x36,0x32,0x35, - 0x31,0x36,0x32,0x36,0x31,0x36,0x32,0x37, - 0x31,0x36,0x32,0x38,0x31,0x36,0x32,0x39, - 0x31,0x36,0x33,0x30,0x31,0x36,0x33,0x31, - 0x31,0x36,0x33,0x32,0x31,0x36,0x33,0x33, - 0x31,0x36,0x33,0x34,0x31,0x36,0x33,0x35, - 0x31,0x36,0x33,0x36,0x31,0x36,0x33,0x37, - 0x31,0x36,0x33,0x38,0x31,0x36,0x33,0x39, - 0x31,0x36,0x34,0x30,0x31,0x36,0x34,0x31, - 0x31,0x36,0x34,0x32,0x31,0x36,0x34,0x33, - 0x31,0x36,0x34,0x34,0x31,0x36,0x34,0x35, - 0x31,0x36,0x34,0x36,0x31,0x36,0x34,0x37, - 0x31,0x36,0x34,0x38,0x31,0x36,0x34,0x39, - 0x31,0x36,0x35,0x30,0x31,0x36,0x35,0x31, - 0x31,0x36,0x35,0x32,0x31,0x36,0x35,0x33, - 0x31,0x36,0x35,0x34,0x31,0x36,0x35,0x35, - 0x31,0x36,0x35,0x36,0x31,0x36,0x35,0x37, - 0x31,0x36,0x35,0x38,0x31,0x36,0x35,0x39, - 0x31,0x36,0x36,0x30,0x31,0x36,0x36,0x31, - 0x31,0x36,0x36,0x32,0x31,0x36,0x36,0x33, - 0x31,0x36,0x36,0x34,0x31,0x36,0x36,0x35, - 0x31,0x36,0x36,0x36,0x31,0x36,0x36,0x37, - 0x31,0x36,0x36,0x38,0x31,0x36,0x36,0x39, - 0x31,0x36,0x37,0x30,0x31,0x36,0x37,0x31, - 0x31,0x36,0x37,0x32,0x31,0x36,0x37,0x33, - 0x31,0x36,0x37,0x34,0x31,0x36,0x37,0x35, - 0x31,0x36,0x37,0x36,0x31,0x36,0x37,0x37, - 0x31,0x36,0x37,0x38,0x31,0x36,0x37,0x39, - 0x31,0x36,0x38,0x30,0x31,0x36,0x38,0x31, - 0x31,0x36,0x38,0x32,0x31,0x36,0x38,0x33, - 0x31,0x36,0x38,0x34,0x31,0x36,0x38,0x35, - 0x31,0x36,0x38,0x36,0x31,0x36,0x38,0x37, - 0x31,0x36,0x38,0x38,0x31,0x36,0x38,0x39, - 0x31,0x36,0x39,0x30,0x31,0x36,0x39,0x31, - 0x31,0x36,0x39,0x32,0x31,0x36,0x39,0x33, - 0x31,0x36,0x39,0x34,0x31,0x36,0x39,0x35, - 0x31,0x36,0x39,0x36,0x31,0x36,0x39,0x37, - 0x31,0x36,0x39,0x38,0x31,0x36,0x39,0x39, - 0x31,0x37,0x30,0x30,0x31,0x37,0x30,0x31, - 0x31,0x37,0x30,0x32,0x31,0x37,0x30,0x33, - 0x31,0x37,0x30,0x34,0x31,0x37,0x30,0x35, - 0x31,0x37,0x30,0x36,0x31,0x37,0x30,0x37, - 0x31,0x37,0x30,0x38,0x31,0x37,0x30,0x39, - 0x31,0x37,0x31,0x30,0x31,0x37,0x31,0x31, - 0x31,0x37,0x31,0x32,0x31,0x37,0x31,0x33, - 0x31,0x37,0x31,0x34,0x31,0x37,0x31,0x35, - 0x31,0x37,0x31,0x36,0x31,0x37,0x31,0x37, - 0x31,0x37,0x31,0x38,0x31,0x37,0x31,0x39, - 0x31,0x37,0x32,0x30,0x31,0x37,0x32,0x31, - 0x31,0x37,0x32,0x32,0x31,0x37,0x32,0x33, - 0x31,0x37,0x32,0x34,0x31,0x37,0x32,0x35, - 0x31,0x37,0x32,0x36,0x31,0x37,0x32,0x37, - 0x31,0x37,0x32,0x38,0x31,0x37,0x32,0x39, - 0x31,0x37,0x33,0x30,0x31,0x37,0x33,0x31, - 0x31,0x37,0x33,0x32,0x31,0x37,0x33,0x33, - 0x31,0x37,0x33,0x34,0x31,0x37,0x33,0x35, - 0x31,0x37,0x33,0x36,0x31,0x37,0x33,0x37, - 0x31,0x37,0x33,0x38,0x31,0x37,0x33,0x39, - 0x31,0x37,0x34,0x30,0x31,0x37,0x34,0x31, - 0x31,0x37,0x34,0x32,0x31,0x37,0x34,0x33, - 0x31,0x37,0x34,0x34,0x31,0x37,0x34,0x35, - 0x31,0x37,0x34,0x36,0x31,0x37,0x34,0x37, - 0x31,0x37,0x34,0x38,0x31,0x37,0x34,0x39, - 0x31,0x37,0x35,0x30,0x31,0x37,0x35,0x31, - 0x31,0x37,0x35,0x32,0x31,0x37,0x35,0x33, - 0x31,0x37,0x35,0x34,0x31,0x37,0x35,0x35, - 0x31,0x37,0x35,0x36,0x31,0x37,0x35,0x37, - 0x31,0x37,0x35,0x38,0x31,0x37,0x35,0x39, - 0x31,0x37,0x36,0x30,0x31,0x37,0x36,0x31, - 0x31,0x37,0x36,0x32,0x31,0x37,0x36,0x33, - 0x31,0x37,0x36,0x34,0x31,0x37,0x36,0x35, - 0x31,0x37,0x36,0x36,0x31,0x37,0x36,0x37, - 0x31,0x37,0x36,0x38,0x31,0x37,0x36,0x39, - 0x31,0x37,0x37,0x30,0x31,0x37,0x37,0x31, - 0x31,0x37,0x37,0x32,0x31,0x37,0x37,0x33, - 0x31,0x37,0x37,0x34,0x31,0x37,0x37,0x35, - 0x31,0x37,0x37,0x36,0x31,0x37,0x37,0x37, - 0x31,0x37,0x37,0x38,0x31,0x37,0x37,0x39, - 0x31,0x37,0x38,0x30,0x31,0x37,0x38,0x31, - 0x31,0x37,0x38,0x32,0x31,0x37,0x38,0x33, - 0x31,0x37,0x38,0x34,0x31,0x37,0x38,0x35, - 0x31,0x37,0x38,0x36,0x31,0x37,0x38,0x37, - 0x31,0x37,0x38,0x38,0x31,0x37,0x38,0x39, - 0x31,0x37,0x39,0x30,0x31,0x37,0x39,0x31, - 0x31,0x37,0x39,0x32,0x31,0x37,0x39,0x33, - 0x31,0x37,0x39,0x34,0x31,0x37,0x39,0x35, - 0x31,0x37,0x39,0x36,0x31,0x37,0x39,0x37, - 0x31,0x37,0x39,0x38,0x31,0x37,0x39,0x39, - 0x31,0x38,0x30,0x30,0x31,0x38,0x30,0x31, - 0x31,0x38,0x30,0x32,0x31,0x38,0x30,0x33, - 0x31,0x38,0x30,0x34,0x31,0x38,0x30,0x35, - 0x31,0x38,0x30,0x36,0x31,0x38,0x30,0x37, - 0x31,0x38,0x30,0x38,0x31,0x38,0x30,0x39, - 0x31,0x38,0x31,0x30,0x31,0x38,0x31,0x31, - 0x31,0x38,0x31,0x32,0x31,0x38,0x31,0x33, - 0x31,0x38,0x31,0x34,0x31,0x38,0x31,0x35, - 0x31,0x38,0x31,0x36,0x31,0x38,0x31,0x37, - 0x31,0x38,0x31,0x38,0x31,0x38,0x31,0x39, - 0x31,0x38,0x32,0x30,0x31,0x38,0x32,0x31, - 0x31,0x38,0x32,0x32,0x31,0x38,0x32,0x33, - 0x31,0x38,0x32,0x34,0x31,0x38,0x32,0x35, - 0x31,0x38,0x32,0x36,0x31,0x38,0x32,0x37, - 0x31,0x38,0x32,0x38,0x31,0x38,0x32,0x39, - 0x31,0x38,0x33,0x30,0x31,0x38,0x33,0x31, - 0x31,0x38,0x33,0x32,0x31,0x38,0x33,0x33, - 0x31,0x38,0x33,0x34,0x31,0x38,0x33,0x35, - 0x31,0x38,0x33,0x36,0x31,0x38,0x33,0x37, - 0x31,0x38,0x33,0x38,0x31,0x38,0x33,0x39, - 0x31,0x38,0x34,0x30,0x31,0x38,0x34,0x31, - 0x31,0x38,0x34,0x32,0x31,0x38,0x34,0x33, - 0x31,0x38,0x34,0x34,0x31,0x38,0x34,0x35, - 0x31,0x38,0x34,0x36,0x31,0x38,0x34,0x37, - 0x31,0x38,0x34,0x38,0x31,0x38,0x34,0x39, - 0x31,0x38,0x35,0x30,0x31,0x38,0x35,0x31, - 0x31,0x38,0x35,0x32,0x31,0x38,0x35,0x33, - 0x31,0x38,0x35,0x34,0x31,0x38,0x35,0x35, - 0x31,0x38,0x35,0x36,0x31,0x38,0x35,0x37, - 0x31,0x38,0x35,0x38,0x31,0x38,0x35,0x39, - 0x31,0x38,0x36,0x30,0x31,0x38,0x36,0x31, - 0x31,0x38,0x36,0x32,0x31,0x38,0x36,0x33, - 0x31,0x38,0x36,0x34,0x31,0x38,0x36,0x35, - 0x31,0x38,0x36,0x36,0x31,0x38,0x36,0x37, - 0x31,0x38,0x36,0x38,0x31,0x38,0x36,0x39, - 0x31,0x38,0x37,0x30,0x31,0x38,0x37,0x31, - 0x31,0x38,0x37,0x32,0x31,0x38,0x37,0x33, - 0x31,0x38,0x37,0x34,0x31,0x38,0x37,0x35, - 0x31,0x38,0x37,0x36,0x31,0x38,0x37,0x37, - 0x31,0x38,0x37,0x38,0x31,0x38,0x37,0x39, - 0x31,0x38,0x38,0x30,0x31,0x38,0x38,0x31, - 0x31,0x38,0x38,0x32,0x31,0x38,0x38,0x33, - 0x31,0x38,0x38,0x34,0x31,0x38,0x38,0x35, - 0x31,0x38,0x38,0x36,0x31,0x38,0x38,0x37, - 0x31,0x38,0x38,0x38,0x31,0x38,0x38,0x39, - 0x31,0x38,0x39,0x30,0x31,0x38,0x39,0x31, - 0x31,0x38,0x39,0x32,0x31,0x38,0x39,0x33, - 0x31,0x38,0x39,0x34,0x31,0x38,0x39,0x35, - 0x31,0x38,0x39,0x36,0x31,0x38,0x39,0x37, - 0x31,0x38,0x39,0x38,0x31,0x38,0x39,0x39, - 0x31,0x39,0x30,0x30,0x31,0x39,0x30,0x31, - 0x31,0x39,0x30,0x32,0x31,0x39,0x30,0x33, - 0x31,0x39,0x30,0x34,0x31,0x39,0x30,0x35, - 0x31,0x39,0x30,0x36,0x31,0x39,0x30,0x37, - 0x31,0x39,0x30,0x38,0x31,0x39,0x30,0x39, - 0x31,0x39,0x31,0x30,0x31,0x39,0x31,0x31, - 0x31,0x39,0x31,0x32,0x31,0x39,0x31,0x33, - 0x31,0x39,0x31,0x34,0x31,0x39,0x31,0x35, - 0x31,0x39,0x31,0x36,0x31,0x39,0x31,0x37, - 0x31,0x39,0x31,0x38,0x31,0x39,0x31,0x39, - 0x31,0x39,0x32,0x30,0x31,0x39,0x32,0x31, - 0x31,0x39,0x32,0x32,0x31,0x39,0x32,0x33, - 0x31,0x39,0x32,0x34,0x31,0x39,0x32,0x35, - 0x31,0x39,0x32,0x36,0x31,0x39,0x32,0x37, - 0x31,0x39,0x32,0x38,0x31,0x39,0x32,0x39, - 0x31,0x39,0x33,0x30,0x31,0x39,0x33,0x31, - 0x31,0x39,0x33,0x32,0x31,0x39,0x33,0x33, - 0x31,0x39,0x33,0x34,0x31,0x39,0x33,0x35, - 0x31,0x39,0x33,0x36,0x31,0x39,0x33,0x37, - 0x31,0x39,0x33,0x38,0x31,0x39,0x33,0x39, - 0x31,0x39,0x34,0x30,0x31,0x39,0x34,0x31, - 0x31,0x39,0x34,0x32,0x31,0x39,0x34,0x33, - 0x31,0x39,0x34,0x34,0x31,0x39,0x34,0x35, - 0x31,0x39,0x34,0x36,0x31,0x39,0x34,0x37, - 0x31,0x39,0x34,0x38,0x31,0x39,0x34,0x39, - 0x31,0x39,0x35,0x30,0x31,0x39,0x35,0x31, - 0x31,0x39,0x35,0x32,0x31,0x39,0x35,0x33, - 0x31,0x39,0x35,0x34,0x31,0x39,0x35,0x35, - 0x31,0x39,0x35,0x36,0x31,0x39,0x35,0x37, - 0x31,0x39,0x35,0x38,0x31,0x39,0x35,0x39, - 0x31,0x39,0x36,0x30,0x31,0x39,0x36,0x31, - 0x31,0x39,0x36,0x32,0x31,0x39,0x36,0x33, - 0x31,0x39,0x36,0x34,0x31,0x39,0x36,0x35, - 0x31,0x39,0x36,0x36,0x31,0x39,0x36,0x37, - 0x31,0x39,0x36,0x38,0x31,0x39,0x36,0x39, - 0x31,0x39,0x37,0x30,0x31,0x39,0x37,0x31, - 0x31,0x39,0x37,0x32,0x31,0x39,0x37,0x33, - 0x31,0x39,0x37,0x34,0x31,0x39,0x37,0x35, - 0x31,0x39,0x37,0x36,0x31,0x39,0x37,0x37, - 0x31,0x39,0x37,0x38,0x31,0x39,0x37,0x39, - 0x31,0x39,0x38,0x30,0x31,0x39,0x38,0x31, - 0x31,0x39,0x38,0x32,0x31,0x39,0x38,0x33, - 0x31,0x39,0x38,0x34,0x31,0x39,0x38,0x35, - 0x31,0x39,0x38,0x36,0x31,0x39,0x38,0x37, - 0x31,0x39,0x38,0x38,0x31,0x39,0x38,0x39, - 0x31,0x39,0x39,0x30,0x31,0x39,0x39,0x31, - 0x31,0x39,0x39,0x32,0x31,0x39,0x39,0x33, - 0x31,0x39,0x39,0x34,0x31,0x39,0x39,0x35, - 0x31,0x39,0x39,0x36,0x31,0x39,0x39,0x37, - 0x31,0x39,0x39,0x38,0x31,0x39,0x39,0x39, - 0x32,0x30,0x30,0x30,0x32,0x30,0x30,0x31, - 0x32,0x30,0x30,0x32,0x32,0x30,0x30,0x33, - 0x32,0x30,0x30,0x34,0x32,0x30,0x30,0x35, - 0x32,0x30,0x30,0x36,0x32,0x30,0x30,0x37, - 0x32,0x30,0x30,0x38,0x32,0x30,0x30,0x39, - 0x32,0x30,0x31,0x30,0x32,0x30,0x31,0x31, - 0x32,0x30,0x31,0x32,0x32,0x30,0x31,0x33, - 0x32,0x30,0x31,0x34,0x32,0x30,0x31,0x35, - 0x32,0x30,0x31,0x36,0x32,0x30,0x31,0x37, - 0x32,0x30,0x31,0x38,0x32,0x30,0x31,0x39, - 0x32,0x30,0x32,0x30,0x32,0x30,0x32,0x31, - 0x32,0x30,0x32,0x32,0x32,0x30,0x32,0x33, - 0x32,0x30,0x32,0x34,0x32,0x30,0x32,0x35, - 0x32,0x30,0x32,0x36,0x32,0x30,0x32,0x37, - 0x32,0x30,0x32,0x38,0x32,0x30,0x32,0x39, - 0x32,0x30,0x33,0x30,0x32,0x30,0x33,0x31, - 0x32,0x30,0x33,0x32,0x32,0x30,0x33,0x33, - 0x32,0x30,0x33,0x34,0x32,0x30,0x33,0x35, - 0x32,0x30,0x33,0x36,0x32,0x30,0x33,0x37, - 0x32,0x30,0x33,0x38,0x32,0x30,0x33,0x39, - 0x32,0x30,0x34,0x30,0x32,0x30,0x34,0x31, - 0x32,0x30,0x34,0x32,0x32,0x30,0x34,0x33, - 0x32,0x30,0x34,0x34,0x32,0x30,0x34,0x35, - 0x32,0x30,0x34,0x36,0x32,0x30,0x34,0x37, - 0x32,0x30,0x34,0x38,0x32,0x30,0x34,0x39, - 0x32,0x30,0x35,0x30,0x32,0x30,0x35,0x31, - 0x32,0x30,0x35,0x32,0x32,0x30,0x35,0x33, - 0x32,0x30,0x35,0x34,0x32,0x30,0x35,0x35, - 0x32,0x30,0x35,0x36,0x32,0x30,0x35,0x37, - 0x32,0x30,0x35,0x38,0x32,0x30,0x35,0x39, - 0x32,0x30,0x36,0x30,0x32,0x30,0x36,0x31, - 0x32,0x30,0x36,0x32,0x32,0x30,0x36,0x33, - 0x32,0x30,0x36,0x34,0x32,0x30,0x36,0x35, - 0x32,0x30,0x36,0x36,0x32,0x30,0x36,0x37, - 0x32,0x30,0x36,0x38,0x32,0x30,0x36,0x39, - 0x32,0x30,0x37,0x30,0x32,0x30,0x37,0x31, - 0x32,0x30,0x37,0x32,0x32,0x30,0x37,0x33, - 0x32,0x30,0x37,0x34,0x32,0x30,0x37,0x35, - 0x32,0x30,0x37,0x36,0x32,0x30,0x37,0x37, - 0x32,0x30,0x37,0x38,0x32,0x30,0x37,0x39, - 0x32,0x30,0x38,0x30,0x32,0x30,0x38,0x31, - 0x32,0x30,0x38,0x32,0x32,0x30,0x38,0x33, - 0x32,0x30,0x38,0x34,0x32,0x30,0x38,0x35, - 0x32,0x30,0x38,0x36,0x32,0x30,0x38,0x37, - 0x32,0x30,0x38,0x38,0x32,0x30,0x38,0x39, - 0x32,0x30,0x39,0x30,0x32,0x30,0x39,0x31, - 0x32,0x30,0x39,0x32,0x32,0x30,0x39,0x33, - 0x32,0x30,0x39,0x34,0x32,0x30,0x39,0x35, - 0x32,0x30,0x39,0x36,0x32,0x30,0x39,0x37, - 0x32,0x30,0x39,0x38,0x32,0x30,0x39,0x39, - 0x32,0x31,0x30,0x30,0x32,0x31,0x30,0x31, - 0x32,0x31,0x30,0x32,0x32,0x31,0x30,0x33, - 0x32,0x31,0x30,0x34,0x32,0x31,0x30,0x35, - 0x32,0x31,0x30,0x36,0x32,0x31,0x30,0x37, - 0x32,0x31,0x30,0x38,0x32,0x31,0x30,0x39, - 0x32,0x31,0x31,0x30,0x32,0x31,0x31,0x31, - 0x32,0x31,0x31,0x32,0x32,0x31,0x31,0x33, - 0x32,0x31,0x31,0x34,0x32,0x31,0x31,0x35, - 0x32,0x31,0x31,0x36,0x32,0x31,0x31,0x37, - 0x32,0x31,0x31,0x38,0x32,0x31,0x31,0x39, - 0x32,0x31,0x32,0x30,0x32,0x31,0x32,0x31, - 0x32,0x31,0x32,0x32,0x32,0x31,0x32,0x33, - 0x32,0x31,0x32,0x34,0x32,0x31,0x32,0x35, - 0x32,0x31,0x32,0x36,0x32,0x31,0x32,0x37, - 0x32,0x31,0x32,0x38,0x32,0x31,0x32,0x39, - 0x32,0x31,0x33,0x30,0x32,0x31,0x33,0x31, - 0x32,0x31,0x33,0x32,0x32,0x31,0x33,0x33, - 0x32,0x31,0x33,0x34,0x32,0x31,0x33,0x35, - 0x32,0x31,0x33,0x36,0x32,0x31,0x33,0x37, - 0x32,0x31,0x33,0x38,0x32,0x31,0x33,0x39, - 0x32,0x31,0x34,0x30,0x32,0x31,0x34,0x31, - 0x32,0x31,0x34,0x32,0x32,0x31,0x34,0x33, - 0x32,0x31,0x34,0x34,0x32,0x31,0x34,0x35, - 0x32,0x31,0x34,0x36,0x32,0x31,0x34,0x37, - 0x32,0x31,0x34,0x38,0x32,0x31,0x34,0x39, - 0x32,0x31,0x35,0x30,0x32,0x31,0x35,0x31, - 0x32,0x31,0x35,0x32,0x32,0x31,0x35,0x33, - 0x32,0x31,0x35,0x34,0x32,0x31,0x35,0x35, - 0x32,0x31,0x35,0x36,0x32,0x31,0x35,0x37, - 0x32,0x31,0x35,0x38,0x32,0x31,0x35,0x39, - 0x32,0x31,0x36,0x30,0x32,0x31,0x36,0x31, - 0x32,0x31,0x36,0x32,0x32,0x31,0x36,0x33, - 0x32,0x31,0x36,0x34,0x32,0x31,0x36,0x35, - 0x32,0x31,0x36,0x36,0x32,0x31,0x36,0x37, - 0x32,0x31,0x36,0x38,0x32,0x31,0x36,0x39, - 0x32,0x31,0x37,0x30,0x32,0x31,0x37,0x31, - 0x32,0x31,0x37,0x32,0x32,0x31,0x37,0x33, - 0x32,0x31,0x37,0x34,0x32,0x31,0x37,0x35, - 0x32,0x31,0x37,0x36,0x32,0x31,0x37,0x37, - 0x32,0x31,0x37,0x38,0x32,0x31,0x37,0x39, - 0x32,0x31,0x38,0x30,0x32,0x31,0x38,0x31, - 0x32,0x31,0x38,0x32,0x32,0x31,0x38,0x33, - 0x32,0x31,0x38,0x34,0x32,0x31,0x38,0x35, - 0x32,0x31,0x38,0x36,0x32,0x31,0x38,0x37, - 0x32,0x31,0x38,0x38,0x32,0x31,0x38,0x39, - 0x32,0x31,0x39,0x30,0x32,0x31,0x39,0x31, - 0x32,0x31,0x39,0x32,0x32,0x31,0x39,0x33, - 0x32,0x31,0x39,0x34,0x32,0x31,0x39,0x35, - 0x32,0x31,0x39,0x36,0x32,0x31,0x39,0x37, - 0x32,0x31,0x39,0x38,0x32,0x31,0x39,0x39, - 0x32,0x32,0x30,0x30,0x32,0x32,0x30,0x31, - 0x32,0x32,0x30,0x32,0x32,0x32,0x30,0x33, - 0x32,0x32,0x30,0x34,0x32,0x32,0x30,0x35, - 0x32,0x32,0x30,0x36,0x32,0x32,0x30,0x37, - 0x32,0x32,0x30,0x38,0x32,0x32,0x30,0x39, - 0x32,0x32,0x31,0x30,0x32,0x32,0x31,0x31, - 0x32,0x32,0x31,0x32,0x32,0x32,0x31,0x33, - 0x32,0x32,0x31,0x34,0x32,0x32,0x31,0x35, - 0x32,0x32,0x31,0x36,0x32,0x32,0x31,0x37, - 0x32,0x32,0x31,0x38,0x32,0x32,0x31,0x39, - 0x32,0x32,0x32,0x30,0x32,0x32,0x32,0x31, - 0x32,0x32,0x32,0x32,0x32,0x32,0x32,0x33, - 0x32,0x32,0x32,0x34,0x32,0x32,0x32,0x35, - 0x32,0x32,0x32,0x36,0x32,0x32,0x32,0x37, - 0x32,0x32,0x32,0x38,0x32,0x32,0x32,0x39, - 0x32,0x32,0x33,0x30,0x32,0x32,0x33,0x31, - 0x32,0x32,0x33,0x32,0x32,0x32,0x33,0x33, - 0x32,0x32,0x33,0x34,0x32,0x32,0x33,0x35, - 0x32,0x32,0x33,0x36,0x32,0x32,0x33,0x37, - 0x32,0x32,0x33,0x38,0x32,0x32,0x33,0x39, - 0x32,0x32,0x34,0x30,0x32,0x32,0x34,0x31, - 0x32,0x32,0x34,0x32,0x32,0x32,0x34,0x33, - 0x32,0x32,0x34,0x34,0x32,0x32,0x34,0x35, - 0x32,0x32,0x34,0x36,0x32,0x32,0x34,0x37, - 0x32,0x32,0x34,0x38,0x32,0x32,0x34,0x39, - 0x32,0x32,0x35,0x30,0x32,0x32,0x35,0x31, - 0x32,0x32,0x35,0x32,0x32,0x32,0x35,0x33, - 0x32,0x32,0x35,0x34,0x32,0x32,0x35,0x35, - 0x32,0x32,0x35,0x36,0x32,0x32,0x35,0x37, - 0x32,0x32,0x35,0x38,0x32,0x32,0x35,0x39, - 0x32,0x32,0x36,0x30,0x32,0x32,0x36,0x31, - 0x32,0x32,0x36,0x32,0x32,0x32,0x36,0x33, - 0x32,0x32,0x36,0x34,0x32,0x32,0x36,0x35, - 0x32,0x32,0x36,0x36,0x32,0x32,0x36,0x37, - 0x32,0x32,0x36,0x38,0x32,0x32,0x36,0x39, - 0x32,0x32,0x37,0x30,0x32,0x32,0x37,0x31, - 0x32,0x32,0x37,0x32,0x32,0x32,0x37,0x33, - 0x32,0x32,0x37,0x34,0x32,0x32,0x37,0x35, - 0x32,0x32,0x37,0x36,0x32,0x32,0x37,0x37, - 0x32,0x32,0x37,0x38,0x32,0x32,0x37,0x39, - 0x32,0x32,0x38,0x30,0x32,0x32,0x38,0x31, - 0x32,0x32,0x38,0x32,0x32,0x32,0x38,0x33, - 0x32,0x32,0x38,0x34,0x32,0x32,0x38,0x35, - 0x32,0x32,0x38,0x36,0x32,0x32,0x38,0x37, - 0x32,0x32,0x38,0x38,0x32,0x32,0x38,0x39, - 0x32,0x32,0x39,0x30,0x32,0x32,0x39,0x31, - 0x32,0x32,0x39,0x32,0x32,0x32,0x39,0x33, - 0x32,0x32,0x39,0x34,0x32,0x32,0x39,0x35, - 0x32,0x32,0x39,0x36,0x32,0x32,0x39,0x37, - 0x32,0x32,0x39,0x38,0x32,0x32,0x39,0x39, - 0x32,0x33,0x30,0x30,0x32,0x33,0x30,0x31, - 0x32,0x33,0x30,0x32,0x32,0x33,0x30,0x33, - 0x32,0x33,0x30,0x34,0x32,0x33,0x30,0x35, - 0x32,0x33,0x30,0x36,0x32,0x33,0x30,0x37, - 0x32,0x33,0x30,0x38,0x32,0x33,0x30,0x39, - 0x32,0x33,0x31,0x30,0x32,0x33,0x31,0x31, - 0x32,0x33,0x31,0x32,0x32,0x33,0x31,0x33, - 0x32,0x33,0x31,0x34,0x32,0x33,0x31,0x35, - 0x32,0x33,0x31,0x36,0x32,0x33,0x31,0x37, - 0x32,0x33,0x31,0x38,0x32,0x33,0x31,0x39, - 0x32,0x33,0x32,0x30,0x32,0x33,0x32,0x31, - 0x32,0x33,0x32,0x32,0x32,0x33,0x32,0x33, - 0x32,0x33,0x32,0x34,0x32,0x33,0x32,0x35, - 0x32,0x33,0x32,0x36,0x32,0x33,0x32,0x37, - 0x32,0x33,0x32,0x38,0x32,0x33,0x32,0x39, - 0x32,0x33,0x33,0x30,0x32,0x33,0x33,0x31, - 0x32,0x33,0x33,0x32,0x32,0x33,0x33,0x33, - 0x32,0x33,0x33,0x34,0x32,0x33,0x33,0x35, - 0x32,0x33,0x33,0x36,0x32,0x33,0x33,0x37, - 0x32,0x33,0x33,0x38,0x32,0x33,0x33,0x39, - 0x32,0x33,0x34,0x30,0x32,0x33,0x34,0x31, - 0x32,0x33,0x34,0x32,0x32,0x33,0x34,0x33, - 0x32,0x33,0x34,0x34,0x32,0x33,0x34,0x35, - 0x32,0x33,0x34,0x36,0x32,0x33,0x34,0x37, - 0x32,0x33,0x34,0x38,0x32,0x33,0x34,0x39, - 0x32,0x33,0x35,0x30,0x32,0x33,0x35,0x31, - 0x32,0x33,0x35,0x32,0x32,0x33,0x35,0x33, - 0x32,0x33,0x35,0x34,0x32,0x33,0x35,0x35, - 0x32,0x33,0x35,0x36,0x32,0x33,0x35,0x37, - 0x32,0x33,0x35,0x38,0x32,0x33,0x35,0x39, - 0x32,0x33,0x36,0x30,0x32,0x33,0x36,0x31, - 0x32,0x33,0x36,0x32,0x32,0x33,0x36,0x33, - 0x32,0x33,0x36,0x34,0x32,0x33,0x36,0x35, - 0x32,0x33,0x36,0x36,0x32,0x33,0x36,0x37, - 0x32,0x33,0x36,0x38,0x32,0x33,0x36,0x39, - 0x32,0x33,0x37,0x30,0x32,0x33,0x37,0x31, - 0x32,0x33,0x37,0x32,0x32,0x33,0x37,0x33, - 0x32,0x33,0x37,0x34,0x32,0x33,0x37,0x35, - 0x32,0x33,0x37,0x36,0x32,0x33,0x37,0x37, - 0x32,0x33,0x37,0x38,0x32,0x33,0x37,0x39, - 0x32,0x33,0x38,0x30,0x32,0x33,0x38,0x31, - 0x32,0x33,0x38,0x32,0x32,0x33,0x38,0x33, - 0x32,0x33,0x38,0x34,0x32,0x33,0x38,0x35, - 0x32,0x33,0x38,0x36,0x32,0x33,0x38,0x37, - 0x32,0x33,0x38,0x38,0x32,0x33,0x38,0x39, - 0x32,0x33,0x39,0x30,0x32,0x33,0x39,0x31, - 0x32,0x33,0x39,0x32,0x32,0x33,0x39,0x33, - 0x32,0x33,0x39,0x34,0x32,0x33,0x39,0x35, - 0x32,0x33,0x39,0x36,0x32,0x33,0x39,0x37, - 0x32,0x33,0x39,0x38,0x32,0x33,0x39,0x39, - 0x32,0x34,0x30,0x30,0x32,0x34,0x30,0x31, - 0x32,0x34,0x30,0x32,0x32,0x34,0x30,0x33, - 0x32,0x34,0x30,0x34,0x32,0x34,0x30,0x35, - 0x32,0x34,0x30,0x36,0x32,0x34,0x30,0x37, - 0x32,0x34,0x30,0x38,0x32,0x34,0x30,0x39, - 0x32,0x34,0x31,0x30,0x32,0x34,0x31,0x31, - 0x32,0x34,0x31,0x32,0x32,0x34,0x31,0x33, - 0x32,0x34,0x31,0x34,0x32,0x34,0x31,0x35, - 0x32,0x34,0x31,0x36,0x32,0x34,0x31,0x37, - 0x32,0x34,0x31,0x38,0x32,0x34,0x31,0x39, - 0x32,0x34,0x32,0x30,0x32,0x34,0x32,0x31, - 0x32,0x34,0x32,0x32,0x32,0x34,0x32,0x33, - 0x32,0x34,0x32,0x34,0x32,0x34,0x32,0x35, - 0x32,0x34,0x32,0x36,0x32,0x34,0x32,0x37, - 0x32,0x34,0x32,0x38,0x32,0x34,0x32,0x39, - 0x32,0x34,0x33,0x30,0x32,0x34,0x33,0x31, - 0x32,0x34,0x33,0x32,0x32,0x34,0x33,0x33, - 0x32,0x34,0x33,0x34,0x32,0x34,0x33,0x35, - 0x32,0x34,0x33,0x36,0x32,0x34,0x33,0x37, - 0x32,0x34,0x33,0x38,0x32,0x34,0x33,0x39, - 0x32,0x34,0x34,0x30,0x32,0x34,0x34,0x31, - 0x32,0x34,0x34,0x32,0x32,0x34,0x34,0x33, - 0x32,0x34,0x34,0x34,0x32,0x34,0x34,0x35, - 0x32,0x34,0x34,0x36,0x32,0x34,0x34,0x37, - 0x32,0x34,0x34,0x38,0x32,0x34,0x34,0x39, - 0x32,0x34,0x35,0x30,0x32,0x34,0x35,0x31, - 0x32,0x34,0x35,0x32,0x32,0x34,0x35,0x33, - 0x32,0x34,0x35,0x34,0x32,0x34,0x35,0x35, - 0x32,0x34,0x35,0x36,0x32,0x34,0x35,0x37, - 0x32,0x34,0x35,0x38,0x32,0x34,0x35,0x39, - 0x32,0x34,0x36,0x30,0x32,0x34,0x36,0x31, - 0x32,0x34,0x36,0x32,0x32,0x34,0x36,0x33, - 0x32,0x34,0x36,0x34,0x32,0x34,0x36,0x35, - 0x32,0x34,0x36,0x36,0x32,0x34,0x36,0x37, - 0x32,0x34,0x36,0x38,0x32,0x34,0x36,0x39, - 0x32,0x34,0x37,0x30,0x32,0x34,0x37,0x31, - 0x32,0x34,0x37,0x32,0x32,0x34,0x37,0x33, - 0x32,0x34,0x37,0x34,0x32,0x34,0x37,0x35, - 0x32,0x34,0x37,0x36,0x32,0x34,0x37,0x37, - 0x32,0x34,0x37,0x38,0x32,0x34,0x37,0x39, - 0x32,0x34,0x38,0x30,0x32,0x34,0x38,0x31, - 0x32,0x34,0x38,0x32,0x32,0x34,0x38,0x33, - 0x32,0x34,0x38,0x34,0x32,0x34,0x38,0x35, - 0x32,0x34,0x38,0x36,0x32,0x34,0x38,0x37, - 0x32,0x34,0x38,0x38,0x32,0x34,0x38,0x39, - 0x32,0x34,0x39,0x30,0x32,0x34,0x39,0x31, - 0x32,0x34,0x39,0x32,0x32,0x34,0x39,0x33, - 0x32,0x34,0x39,0x34,0x32,0x34,0x39,0x35, - 0x32,0x34,0x39,0x36,0x32,0x34,0x39,0x37, - 0x32,0x34,0x39,0x38,0x32,0x34,0x39,0x39, - 0x32,0x35,0x30,0x30,0x32,0x35,0x30,0x31, - 0x32,0x35,0x30,0x32,0x32,0x35,0x30,0x33, - 0x32,0x35,0x30,0x34,0x32,0x35,0x30,0x35, - 0x32,0x35,0x30,0x36,0x32,0x35,0x30,0x37, - 0x32,0x35,0x30,0x38,0x32,0x35,0x30,0x39, - 0x32,0x35,0x31,0x30,0x32,0x35,0x31,0x31, - 0x32,0x35,0x31,0x32,0x32,0x35,0x31,0x33, - 0x32,0x35,0x31,0x34,0x32,0x35,0x31,0x35, - 0x32,0x35,0x31,0x36,0x32,0x35,0x31,0x37, - 0x32,0x35,0x31,0x38,0x32,0x35,0x31,0x39, - 0x32,0x35,0x32,0x30,0x32,0x35,0x32,0x31, - 0x32,0x35,0x32,0x32,0x32,0x35,0x32,0x33, - 0x32,0x35,0x32,0x34,0x32,0x35,0x32,0x35, - 0x32,0x35,0x32,0x36,0x32,0x35,0x32,0x37, - 0x32,0x35,0x32,0x38,0x32,0x35,0x32,0x39, - 0x32,0x35,0x33,0x30,0x32,0x35,0x33,0x31, - 0x32,0x35,0x33,0x32,0x32,0x35,0x33,0x33, - 0x32,0x35,0x33,0x34,0x32,0x35,0x33,0x35, - 0x32,0x35,0x33,0x36,0x32,0x35,0x33,0x37, - 0x32,0x35,0x33,0x38,0x32,0x35,0x33,0x39, - 0x32,0x35,0x34,0x30,0x32,0x35,0x34,0x31, - 0x32,0x35,0x34,0x32,0x32,0x35,0x34,0x33, - 0x32,0x35,0x34,0x34,0x32,0x35,0x34,0x35, - 0x32,0x35,0x34,0x36,0x32,0x35,0x34,0x37, - 0x32,0x35,0x34,0x38,0x32,0x35,0x34,0x39, - 0x32,0x35,0x35,0x30,0x32,0x35,0x35,0x31, - 0x32,0x35,0x35,0x32,0x32,0x35,0x35,0x33, - 0x32,0x35,0x35,0x34,0x32,0x35,0x35,0x35, - 0x32,0x35,0x35,0x36,0x32,0x35,0x35,0x37, - 0x32,0x35,0x35,0x38,0x32,0x35,0x35,0x39, - 0x32,0x35,0x36,0x30,0x32,0x35,0x36,0x31, - 0x32,0x35,0x36,0x32,0x32,0x35,0x36,0x33, - 0x32,0x35,0x36,0x34,0x32,0x35,0x36,0x35, - 0x32,0x35,0x36,0x36,0x32,0x35,0x36,0x37, - 0x32,0x35,0x36,0x38,0x32,0x35,0x36,0x39, - 0x32,0x35,0x37,0x30,0x32,0x35,0x37,0x31, - 0x32,0x35,0x37,0x32,0x32,0x35,0x37,0x33, - 0x32,0x35,0x37,0x34,0x32,0x35,0x37,0x35, - 0x32,0x35,0x37,0x36,0x32,0x35,0x37,0x37, - 0x32,0x35,0x37,0x38,0x32,0x35,0x37,0x39, - 0x32,0x35,0x38,0x30,0x32,0x35,0x38,0x31, - 0x32,0x35,0x38,0x32,0x32,0x35,0x38,0x33, - 0x32,0x35,0x38,0x34,0x32,0x35,0x38,0x35, - 0x32,0x35,0x38,0x36,0x32,0x35,0x38,0x37, - 0x32,0x35,0x38,0x38,0x32,0x35,0x38,0x39, - 0x32,0x35,0x39,0x30,0x32,0x35,0x39,0x31, - 0x32,0x35,0x39,0x32,0x32,0x35,0x39,0x33, - 0x32,0x35,0x39,0x34,0x32,0x35,0x39,0x35, - 0x32,0x35,0x39,0x36,0x32,0x35,0x39,0x37, - 0x32,0x35,0x39,0x38,0x32,0x35,0x39,0x39, - 0x32,0x36,0x30,0x30,0x32,0x36,0x30,0x31, - 0x32,0x36,0x30,0x32,0x32,0x36,0x30,0x33, - 0x32,0x36,0x30,0x34,0x32,0x36,0x30,0x35, - 0x32,0x36,0x30,0x36,0x32,0x36,0x30,0x37, - 0x32,0x36,0x30,0x38,0x32,0x36,0x30,0x39, - 0x32,0x36,0x31,0x30,0x32,0x36,0x31,0x31, - 0x32,0x36,0x31,0x32,0x32,0x36,0x31,0x33, - 0x32,0x36,0x31,0x34,0x32,0x36,0x31,0x35, - 0x32,0x36,0x31,0x36,0x32,0x36,0x31,0x37, - 0x32,0x36,0x31,0x38,0x32,0x36,0x31,0x39, - 0x32,0x36,0x32,0x30,0x32,0x36,0x32,0x31, - 0x32,0x36,0x32,0x32,0x32,0x36,0x32,0x33, - 0x32,0x36,0x32,0x34,0x32,0x36,0x32,0x35, - 0x32,0x36,0x32,0x36,0x32,0x36,0x32,0x37, - 0x32,0x36,0x32,0x38,0x32,0x36,0x32,0x39, - 0x32,0x36,0x33,0x30,0x32,0x36,0x33,0x31, - 0x32,0x36,0x33,0x32,0x32,0x36,0x33,0x33, - 0x32,0x36,0x33,0x34,0x32,0x36,0x33,0x35, - 0x32,0x36,0x33,0x36,0x32,0x36,0x33,0x37, - 0x32,0x36,0x33,0x38,0x32,0x36,0x33,0x39, - 0x32,0x36,0x34,0x30,0x32,0x36,0x34,0x31, - 0x32,0x36,0x34,0x32,0x32,0x36,0x34,0x33, - 0x32,0x36,0x34,0x34,0x32,0x36,0x34,0x35, - 0x32,0x36,0x34,0x36,0x32,0x36,0x34,0x37, - 0x32,0x36,0x34,0x38,0x32,0x36,0x34,0x39, - 0x32,0x36,0x35,0x30,0x32,0x36,0x35,0x31, - 0x32,0x36,0x35,0x32,0x32,0x36,0x35,0x33, - 0x32,0x36,0x35,0x34,0x32,0x36,0x35,0x35, - 0x32,0x36,0x35,0x36,0x32,0x36,0x35,0x37, - 0x32,0x36,0x35,0x38,0x32,0x36,0x35,0x39, - 0x32,0x36,0x36,0x30,0x32,0x36,0x36,0x31, - 0x32,0x36,0x36,0x32,0x32,0x36,0x36,0x33, - 0x32,0x36,0x36,0x34,0x32,0x36,0x36,0x35, - 0x32,0x36,0x36,0x36,0x32,0x36,0x36,0x37, - 0x32,0x36,0x36,0x38,0x32,0x36,0x36,0x39, - 0x32,0x36,0x37,0x30,0x32,0x36,0x37,0x31, - 0x32,0x36,0x37,0x32,0x32,0x36,0x37,0x33, - 0x32,0x36,0x37,0x34,0x32,0x36,0x37,0x35, - 0x32,0x36,0x37,0x36,0x32,0x36,0x37,0x37, - 0x32,0x36,0x37,0x38,0x32,0x36,0x37,0x39, - 0x32,0x36,0x38,0x30,0x32,0x36,0x38,0x31, - 0x32,0x36,0x38,0x32,0x32,0x36,0x38,0x33, - 0x32,0x36,0x38,0x34,0x32,0x36,0x38,0x35, - 0x32,0x36,0x38,0x36,0x32,0x36,0x38,0x37, - 0x32,0x36,0x38,0x38,0x32,0x36,0x38,0x39, - 0x32,0x36,0x39,0x30,0x32,0x36,0x39,0x31, - 0x32,0x36,0x39,0x32,0x32,0x36,0x39,0x33, - 0x32,0x36,0x39,0x34,0x32,0x36,0x39,0x35, - 0x32,0x36,0x39,0x36,0x32,0x36,0x39,0x37, - 0x32,0x36,0x39,0x38,0x32,0x36,0x39,0x39, - 0x32,0x37,0x30,0x30,0x32,0x37,0x30,0x31, - 0x32,0x37,0x30,0x32,0x32,0x37,0x30,0x33, - 0x32,0x37,0x30,0x34,0x32,0x37,0x30,0x35, - 0x32,0x37,0x30,0x36,0x32,0x37,0x30,0x37, - 0x32,0x37,0x30,0x38,0x32,0x37,0x30,0x39, - 0x32,0x37,0x31,0x30,0x32,0x37,0x31,0x31, - 0x32,0x37,0x31,0x32,0x32,0x37,0x31,0x33, - 0x32,0x37,0x31,0x34,0x32,0x37,0x31,0x35, - 0x32,0x37,0x31,0x36,0x32,0x37,0x31,0x37, - 0x32,0x37,0x31,0x38,0x32,0x37,0x31,0x39, - 0x32,0x37,0x32,0x30,0x32,0x37,0x32,0x31, - 0x32,0x37,0x32,0x32,0x32,0x37,0x32,0x33, - 0x32,0x37,0x32,0x34,0x32,0x37,0x32,0x35, - 0x32,0x37,0x32,0x36,0x32,0x37,0x32,0x37, - 0x32,0x37,0x32,0x38,0x32,0x37,0x32,0x39, - 0x32,0x37,0x33,0x30,0x32,0x37,0x33,0x31, - 0x32,0x37,0x33,0x32,0x32,0x37,0x33,0x33, - 0x32,0x37,0x33,0x34,0x32,0x37,0x33,0x35, - 0x32,0x37,0x33,0x36,0x32,0x37,0x33,0x37, - 0x32,0x37,0x33,0x38,0x32,0x37,0x33,0x39, - 0x32,0x37,0x34,0x30,0x32,0x37,0x34,0x31, - 0x32,0x37,0x34,0x32,0x32,0x37,0x34,0x33, - 0x32,0x37,0x34,0x34,0x32,0x37,0x34,0x35, - 0x32,0x37,0x34,0x36,0x32,0x37,0x34,0x37, - 0x32,0x37,0x34,0x38,0x32,0x37,0x34,0x39, - 0x32,0x37,0x35,0x30,0x32,0x37,0x35,0x31, - 0x32,0x37,0x35,0x32,0x32,0x37,0x35,0x33, - 0x32,0x37,0x35,0x34,0x32,0x37,0x35,0x35, - 0x32,0x37,0x35,0x36,0x32,0x37,0x35,0x37, - 0x32,0x37,0x35,0x38,0x32,0x37,0x35,0x39, - 0x32,0x37,0x36,0x30,0x32,0x37,0x36,0x31, - 0x32,0x37,0x36,0x32,0x32,0x37,0x36,0x33, - 0x32,0x37,0x36,0x34,0x32,0x37,0x36,0x35, - 0x32,0x37,0x36,0x36,0x32,0x37,0x36,0x37, - 0x32,0x37,0x36,0x38,0x32,0x37,0x36,0x39, - 0x32,0x37,0x37,0x30,0x32,0x37,0x37,0x31, - 0x32,0x37,0x37,0x32,0x32,0x37,0x37,0x33, - 0x32,0x37,0x37,0x34,0x32,0x37,0x37,0x35, - 0x32,0x37,0x37,0x36,0x32,0x37,0x37,0x37, - 0x32,0x37,0x37,0x38,0x32,0x37,0x37,0x39, - 0x32,0x37,0x38,0x30,0x32,0x37,0x38,0x31, - 0x32,0x37,0x38,0x32,0x32,0x37,0x38,0x33, - 0x32,0x37,0x38,0x34,0x32,0x37,0x38,0x35, - 0x32,0x37,0x38,0x36,0x32,0x37,0x38,0x37, - 0x32,0x37,0x38,0x38,0x32,0x37,0x38,0x39, - 0x32,0x37,0x39,0x30,0x32,0x37,0x39,0x31, - 0x32,0x37,0x39,0x32,0x32,0x37,0x39,0x33, - 0x32,0x37,0x39,0x34,0x32,0x37,0x39,0x35, - 0x32,0x37,0x39,0x36,0x32,0x37,0x39,0x37, - 0x32,0x37,0x39,0x38,0x32,0x37,0x39,0x39, - 0x32,0x38,0x30,0x30,0x32,0x38,0x30,0x31, - 0x32,0x38,0x30,0x32,0x32,0x38,0x30,0x33, - 0x32,0x38,0x30,0x34,0x32,0x38,0x30,0x35, - 0x32,0x38,0x30,0x36,0x32,0x38,0x30,0x37, - 0x32,0x38,0x30,0x38,0x32,0x38,0x30,0x39, - 0x32,0x38,0x31,0x30,0x32,0x38,0x31,0x31, - 0x32,0x38,0x31,0x32,0x32,0x38,0x31,0x33, - 0x32,0x38,0x31,0x34,0x32,0x38,0x31,0x35, - 0x32,0x38,0x31,0x36,0x32,0x38,0x31,0x37, - 0x32,0x38,0x31,0x38,0x32,0x38,0x31,0x39, - 0x32,0x38,0x32,0x30,0x32,0x38,0x32,0x31, - 0x32,0x38,0x32,0x32,0x32,0x38,0x32,0x33, - 0x32,0x38,0x32,0x34,0x32,0x38,0x32,0x35, - 0x32,0x38,0x32,0x36,0x32,0x38,0x32,0x37, - 0x32,0x38,0x32,0x38,0x32,0x38,0x32,0x39, - 0x32,0x38,0x33,0x30,0x32,0x38,0x33,0x31, - 0x32,0x38,0x33,0x32,0x32,0x38,0x33,0x33, - 0x32,0x38,0x33,0x34,0x32,0x38,0x33,0x35, - 0x32,0x38,0x33,0x36,0x32,0x38,0x33,0x37, - 0x32,0x38,0x33,0x38,0x32,0x38,0x33,0x39, - 0x32,0x38,0x34,0x30,0x32,0x38,0x34,0x31, - 0x32,0x38,0x34,0x32,0x32,0x38,0x34,0x33, - 0x32,0x38,0x34,0x34,0x32,0x38,0x34,0x35, - 0x32,0x38,0x34,0x36,0x32,0x38,0x34,0x37, - 0x32,0x38,0x34,0x38,0x32,0x38,0x34,0x39, - 0x32,0x38,0x35,0x30,0x32,0x38,0x35,0x31, - 0x32,0x38,0x35,0x32,0x32,0x38,0x35,0x33, - 0x32,0x38,0x35,0x34,0x32,0x38,0x35,0x35, - 0x32,0x38,0x35,0x36,0x32,0x38,0x35,0x37, - 0x32,0x38,0x35,0x38,0x32,0x38,0x35,0x39, - 0x32,0x38,0x36,0x30,0x32,0x38,0x36,0x31, - 0x32,0x38,0x36,0x32,0x32,0x38,0x36,0x33, - 0x32,0x38,0x36,0x34,0x32,0x38,0x36,0x35, - 0x32,0x38,0x36,0x36,0x32,0x38,0x36,0x37, - 0x32,0x38,0x36,0x38,0x32,0x38,0x36,0x39, - 0x32,0x38,0x37,0x30,0x32,0x38,0x37,0x31, - 0x32,0x38,0x37,0x32,0x32,0x38,0x37,0x33, - 0x32,0x38,0x37,0x34,0x32,0x38,0x37,0x35, - 0x32,0x38,0x37,0x36,0x32,0x38,0x37,0x37, - 0x32,0x38,0x37,0x38,0x32,0x38,0x37,0x39, - 0x32,0x38,0x38,0x30,0x32,0x38,0x38,0x31, - 0x32,0x38,0x38,0x32,0x32,0x38,0x38,0x33, - 0x32,0x38,0x38,0x34,0x32,0x38,0x38,0x35, - 0x32,0x38,0x38,0x36,0x32,0x38,0x38,0x37, - 0x32,0x38,0x38,0x38,0x32,0x38,0x38,0x39, - 0x32,0x38,0x39,0x30,0x32,0x38,0x39,0x31, - 0x32,0x38,0x39,0x32,0x32,0x38,0x39,0x33, - 0x32,0x38,0x39,0x34,0x32,0x38,0x39,0x35, - 0x32,0x38,0x39,0x36,0x32,0x38,0x39,0x37, - 0x32,0x38,0x39,0x38,0x32,0x38,0x39,0x39, - 0x32,0x39,0x30,0x30,0x32,0x39,0x30,0x31, - 0x32,0x39,0x30,0x32,0x32,0x39,0x30,0x33, - 0x32,0x39,0x30,0x34,0x32,0x39,0x30,0x35, - 0x32,0x39,0x30,0x36,0x32,0x39,0x30,0x37, - 0x32,0x39,0x30,0x38,0x32,0x39,0x30,0x39, - 0x32,0x39,0x31,0x30,0x32,0x39,0x31,0x31, - 0x32,0x39,0x31,0x32,0x32,0x39,0x31,0x33, - 0x32,0x39,0x31,0x34,0x32,0x39,0x31,0x35, - 0x32,0x39,0x31,0x36,0x32,0x39,0x31,0x37, - 0x32,0x39,0x31,0x38,0x32,0x39,0x31,0x39, - 0x32,0x39,0x32,0x30,0x32,0x39,0x32,0x31, - 0x32,0x39,0x32,0x32,0x32,0x39,0x32,0x33, - 0x32,0x39,0x32,0x34,0x32,0x39,0x32,0x35, - 0x32,0x39,0x32,0x36,0x32,0x39,0x32,0x37, - 0x32,0x39,0x32,0x38,0x32,0x39,0x32,0x39, - 0x32,0x39,0x33,0x30,0x32,0x39,0x33,0x31, - 0x32,0x39,0x33,0x32,0x32,0x39,0x33,0x33, - 0x32,0x39,0x33,0x34,0x32,0x39,0x33,0x35, - 0x32,0x39,0x33,0x36,0x32,0x39,0x33,0x37, - 0x32,0x39,0x33,0x38,0x32,0x39,0x33,0x39, - 0x32,0x39,0x34,0x30,0x32,0x39,0x34,0x31, - 0x32,0x39,0x34,0x32,0x32,0x39,0x34,0x33, - 0x32,0x39,0x34,0x34,0x32,0x39,0x34,0x35, - 0x32,0x39,0x34,0x36,0x32,0x39,0x34,0x37, - 0x32,0x39,0x34,0x38,0x32,0x39,0x34,0x39, - 0x32,0x39,0x35,0x30,0x32,0x39,0x35,0x31, - 0x32,0x39,0x35,0x32,0x32,0x39,0x35,0x33, - 0x32,0x39,0x35,0x34,0x32,0x39,0x35,0x35, - 0x32,0x39,0x35,0x36,0x32,0x39,0x35,0x37, - 0x32,0x39,0x35,0x38,0x32,0x39,0x35,0x39, - 0x32,0x39,0x36,0x30,0x32,0x39,0x36,0x31, - 0x32,0x39,0x36,0x32,0x32,0x39,0x36,0x33, - 0x32,0x39,0x36,0x34,0x32,0x39,0x36,0x35, - 0x32,0x39,0x36,0x36,0x32,0x39,0x36,0x37, - 0x32,0x39,0x36,0x38,0x32,0x39,0x36,0x39, - 0x32,0x39,0x37,0x30,0x32,0x39,0x37,0x31, - 0x32,0x39,0x37,0x32,0x32,0x39,0x37,0x33, - 0x32,0x39,0x37,0x34,0x32,0x39,0x37,0x35, - 0x32,0x39,0x37,0x36,0x32,0x39,0x37,0x37, - 0x32,0x39,0x37,0x38,0x32,0x39,0x37,0x39, - 0x32,0x39,0x38,0x30,0x32,0x39,0x38,0x31, - 0x32,0x39,0x38,0x32,0x32,0x39,0x38,0x33, - 0x32,0x39,0x38,0x34,0x32,0x39,0x38,0x35, - 0x32,0x39,0x38,0x36,0x32,0x39,0x38,0x37, - 0x32,0x39,0x38,0x38,0x32,0x39,0x38,0x39, - 0x32,0x39,0x39,0x30,0x32,0x39,0x39,0x31, - 0x32,0x39,0x39,0x32,0x32,0x39,0x39,0x33, - 0x32,0x39,0x39,0x34,0x32,0x39,0x39,0x35, - 0x32,0x39,0x39,0x36,0x32,0x39,0x39,0x37, - 0x32,0x39,0x39,0x38,0x32,0x39,0x39,0x39, - 0x33,0x30,0x30,0x30,0x33,0x30,0x30,0x31, - 0x33,0x30,0x30,0x32,0x33,0x30,0x30,0x33, - 0x33,0x30,0x30,0x34,0x33,0x30,0x30,0x35, - 0x33,0x30,0x30,0x36,0x33,0x30,0x30,0x37, - 0x33,0x30,0x30,0x38,0x33,0x30,0x30,0x39, - 0x33,0x30,0x31,0x30,0x33,0x30,0x31,0x31, - 0x33,0x30,0x31,0x32,0x33,0x30,0x31,0x33, - 0x33,0x30,0x31,0x34,0x33,0x30,0x31,0x35, - 0x33,0x30,0x31,0x36,0x33,0x30,0x31,0x37, - 0x33,0x30,0x31,0x38,0x33,0x30,0x31,0x39, - 0x33,0x30,0x32,0x30,0x33,0x30,0x32,0x31, - 0x33,0x30,0x32,0x32,0x33,0x30,0x32,0x33, - 0x33,0x30,0x32,0x34,0x33,0x30,0x32,0x35, - 0x33,0x30,0x32,0x36,0x33,0x30,0x32,0x37, - 0x33,0x30,0x32,0x38,0x33,0x30,0x32,0x39, - 0x33,0x30,0x33,0x30,0x33,0x30,0x33,0x31, - 0x33,0x30,0x33,0x32,0x33,0x30,0x33,0x33, - 0x33,0x30,0x33,0x34,0x33,0x30,0x33,0x35, - 0x33,0x30,0x33,0x36,0x33,0x30,0x33,0x37, - 0x33,0x30,0x33,0x38,0x33,0x30,0x33,0x39, - 0x33,0x30,0x34,0x30,0x33,0x30,0x34,0x31, - 0x33,0x30,0x34,0x32,0x33,0x30,0x34,0x33, - 0x33,0x30,0x34,0x34,0x33,0x30,0x34,0x35, - 0x33,0x30,0x34,0x36,0x33,0x30,0x34,0x37, - 0x33,0x30,0x34,0x38,0x33,0x30,0x34,0x39, - 0x33,0x30,0x35,0x30,0x33,0x30,0x35,0x31, - 0x33,0x30,0x35,0x32,0x33,0x30,0x35,0x33, - 0x33,0x30,0x35,0x34,0x33,0x30,0x35,0x35, - 0x33,0x30,0x35,0x36,0x33,0x30,0x35,0x37, - 0x33,0x30,0x35,0x38,0x33,0x30,0x35,0x39, - 0x33,0x30,0x36,0x30,0x33,0x30,0x36,0x31, - 0x33,0x30,0x36,0x32,0x33,0x30,0x36,0x33, - 0x33,0x30,0x36,0x34,0x33,0x30,0x36,0x35, - 0x33,0x30,0x36,0x36,0x33,0x30,0x36,0x37, - 0x33,0x30,0x36,0x38,0x33,0x30,0x36,0x39, - 0x33,0x30,0x37,0x30,0x33,0x30,0x37,0x31, - 0x33,0x30,0x37,0x32,0x33,0x30,0x37,0x33, - 0x33,0x30,0x37,0x34,0x33,0x30,0x37,0x35, - 0x33,0x30,0x37,0x36,0x33,0x30,0x37,0x37, - 0x33,0x30,0x37,0x38,0x33,0x30,0x37,0x39, - 0x33,0x30,0x38,0x30,0x33,0x30,0x38,0x31, - 0x33,0x30,0x38,0x32,0x33,0x30,0x38,0x33, - 0x33,0x30,0x38,0x34,0x33,0x30,0x38,0x35, - 0x33,0x30,0x38,0x36,0x33,0x30,0x38,0x37, - 0x33,0x30,0x38,0x38,0x33,0x30,0x38,0x39, - 0x33,0x30,0x39,0x30,0x33,0x30,0x39,0x31, - 0x33,0x30,0x39,0x32,0x33,0x30,0x39,0x33, - 0x33,0x30,0x39,0x34,0x33,0x30,0x39,0x35, - 0x33,0x30,0x39,0x36,0x33,0x30,0x39,0x37, - 0x33,0x30,0x39,0x38,0x33,0x30,0x39,0x39, - 0x33,0x31,0x30,0x30,0x33,0x31,0x30,0x31, - 0x33,0x31,0x30,0x32,0x33,0x31,0x30,0x33, - 0x33,0x31,0x30,0x34,0x33,0x31,0x30,0x35, - 0x33,0x31,0x30,0x36,0x33,0x31,0x30,0x37, - 0x33,0x31,0x30,0x38,0x33,0x31,0x30,0x39, - 0x33,0x31,0x31,0x30,0x33,0x31,0x31,0x31, - 0x33,0x31,0x31,0x32,0x33,0x31,0x31,0x33, - 0x33,0x31,0x31,0x34,0x33,0x31,0x31,0x35, - 0x33,0x31,0x31,0x36,0x33,0x31,0x31,0x37, - 0x33,0x31,0x31,0x38,0x33,0x31,0x31,0x39, - 0x33,0x31,0x32,0x30,0x33,0x31,0x32,0x31, - 0x33,0x31,0x32,0x32,0x33,0x31,0x32,0x33, - 0x33,0x31,0x32,0x34,0x33,0x31,0x32,0x35, - 0x33,0x31,0x32,0x36,0x33,0x31,0x32,0x37, - 0x33,0x31,0x32,0x38,0x33,0x31,0x32,0x39, - 0x33,0x31,0x33,0x30,0x33,0x31,0x33,0x31, - 0x33,0x31,0x33,0x32,0x33,0x31,0x33,0x33, - 0x33,0x31,0x33,0x34,0x33,0x31,0x33,0x35, - 0x33,0x31,0x33,0x36,0x33,0x31,0x33,0x37, - 0x33,0x31,0x33,0x38,0x33,0x31,0x33,0x39, - 0x33,0x31,0x34,0x30,0x33,0x31,0x34,0x31, - 0x33,0x31,0x34,0x32,0x33,0x31,0x34,0x33, - 0x33,0x31,0x34,0x34,0x33,0x31,0x34,0x35, - 0x33,0x31,0x34,0x36,0x33,0x31,0x34,0x37, - 0x33,0x31,0x34,0x38,0x33,0x31,0x34,0x39, - 0x33,0x31,0x35,0x30,0x33,0x31,0x35,0x31, - 0x33,0x31,0x35,0x32,0x33,0x31,0x35,0x33, - 0x33,0x31,0x35,0x34,0x33,0x31,0x35,0x35, - 0x33,0x31,0x35,0x36,0x33,0x31,0x35,0x37, - 0x33,0x31,0x35,0x38,0x33,0x31,0x35,0x39, - 0x33,0x31,0x36,0x30,0x33,0x31,0x36,0x31, - 0x33,0x31,0x36,0x32,0x33,0x31,0x36,0x33, - 0x33,0x31,0x36,0x34,0x33,0x31,0x36,0x35, - 0x33,0x31,0x36,0x36,0x33,0x31,0x36,0x37, - 0x33,0x31,0x36,0x38,0x33,0x31,0x36,0x39, - 0x33,0x31,0x37,0x30,0x33,0x31,0x37,0x31, - 0x33,0x31,0x37,0x32,0x33,0x31,0x37,0x33, - 0x33,0x31,0x37,0x34,0x33,0x31,0x37,0x35, - 0x33,0x31,0x37,0x36,0x33,0x31,0x37,0x37, - 0x33,0x31,0x37,0x38,0x33,0x31,0x37,0x39, - 0x33,0x31,0x38,0x30,0x33,0x31,0x38,0x31, - 0x33,0x31,0x38,0x32,0x33,0x31,0x38,0x33, - 0x33,0x31,0x38,0x34,0x33,0x31,0x38,0x35, - 0x33,0x31,0x38,0x36,0x33,0x31,0x38,0x37, - 0x33,0x31,0x38,0x38,0x33,0x31,0x38,0x39, - 0x33,0x31,0x39,0x30,0x33,0x31,0x39,0x31, - 0x33,0x31,0x39,0x32,0x33,0x31,0x39,0x33, - 0x33,0x31,0x39,0x34,0x33,0x31,0x39,0x35, - 0x33,0x31,0x39,0x36,0x33,0x31,0x39,0x37, - 0x33,0x31,0x39,0x38,0x33,0x31,0x39,0x39, - 0x33,0x32,0x30,0x30,0x33,0x32,0x30,0x31, - 0x33,0x32,0x30,0x32,0x33,0x32,0x30,0x33, - 0x33,0x32,0x30,0x34,0x33,0x32,0x30,0x35, - 0x33,0x32,0x30,0x36,0x33,0x32,0x30,0x37, - 0x33,0x32,0x30,0x38,0x33,0x32,0x30,0x39, - 0x33,0x32,0x31,0x30,0x33,0x32,0x31,0x31, - 0x33,0x32,0x31,0x32,0x33,0x32,0x31,0x33, - 0x33,0x32,0x31,0x34,0x33,0x32,0x31,0x35, - 0x33,0x32,0x31,0x36,0x33,0x32,0x31,0x37, - 0x33,0x32,0x31,0x38,0x33,0x32,0x31,0x39, - 0x33,0x32,0x32,0x30,0x33,0x32,0x32,0x31, - 0x33,0x32,0x32,0x32,0x33,0x32,0x32,0x33, - 0x33,0x32,0x32,0x34,0x33,0x32,0x32,0x35, - 0x33,0x32,0x32,0x36,0x33,0x32,0x32,0x37, - 0x33,0x32,0x32,0x38,0x33,0x32,0x32,0x39, - 0x33,0x32,0x33,0x30,0x33,0x32,0x33,0x31, - 0x33,0x32,0x33,0x32,0x33,0x32,0x33,0x33, - 0x33,0x32,0x33,0x34,0x33,0x32,0x33,0x35, - 0x33,0x32,0x33,0x36,0x33,0x32,0x33,0x37, - 0x33,0x32,0x33,0x38,0x33,0x32,0x33,0x39, - 0x33,0x32,0x34,0x30,0x33,0x32,0x34,0x31, - 0x33,0x32,0x34,0x32,0x33,0x32,0x34,0x33, - 0x33,0x32,0x34,0x34,0x33,0x32,0x34,0x35, - 0x33,0x32,0x34,0x36,0x33,0x32,0x34,0x37, - 0x33,0x32,0x34,0x38,0x33,0x32,0x34,0x39, - 0x33,0x32,0x35,0x30,0x33,0x32,0x35,0x31, - 0x33,0x32,0x35,0x32,0x33,0x32,0x35,0x33, - 0x33,0x32,0x35,0x34,0x33,0x32,0x35,0x35, - 0x33,0x32,0x35,0x36,0x33,0x32,0x35,0x37, - 0x33,0x32,0x35,0x38,0x33,0x32,0x35,0x39, - 0x33,0x32,0x36,0x30,0x33,0x32,0x36,0x31, - 0x33,0x32,0x36,0x32,0x33,0x32,0x36,0x33, - 0x33,0x32,0x36,0x34,0x33,0x32,0x36,0x35, - 0x33,0x32,0x36,0x36,0x33,0x32,0x36,0x37, - 0x33,0x32,0x36,0x38,0x33,0x32,0x36,0x39, - 0x33,0x32,0x37,0x30,0x33,0x32,0x37,0x31, - 0x33,0x32,0x37,0x32,0x33,0x32,0x37,0x33, - 0x33,0x32,0x37,0x34,0x33,0x32,0x37,0x35, - 0x33,0x32,0x37,0x36,0x33,0x32,0x37,0x37, - 0x33,0x32,0x37,0x38,0x33,0x32,0x37,0x39, - 0x33,0x32,0x38,0x30,0x33,0x32,0x38,0x31, - 0x33,0x32,0x38,0x32,0x33,0x32,0x38,0x33, - 0x33,0x32,0x38,0x34,0x33,0x32,0x38,0x35, - 0x33,0x32,0x38,0x36,0x33,0x32,0x38,0x37, - 0x33,0x32,0x38,0x38,0x33,0x32,0x38,0x39, - 0x33,0x32,0x39,0x30,0x33,0x32,0x39,0x31, - 0x33,0x32,0x39,0x32,0x33,0x32,0x39,0x33, - 0x33,0x32,0x39,0x34,0x33,0x32,0x39,0x35, - 0x33,0x32,0x39,0x36,0x33,0x32,0x39,0x37, - 0x33,0x32,0x39,0x38,0x33,0x32,0x39,0x39, - 0x33,0x33,0x30,0x30,0x33,0x33,0x30,0x31, - 0x33,0x33,0x30,0x32,0x33,0x33,0x30,0x33, - 0x33,0x33,0x30,0x34,0x33,0x33,0x30,0x35, - 0x33,0x33,0x30,0x36,0x33,0x33,0x30,0x37, - 0x33,0x33,0x30,0x38,0x33,0x33,0x30,0x39, - 0x33,0x33,0x31,0x30,0x33,0x33,0x31,0x31, - 0x33,0x33,0x31,0x32,0x33,0x33,0x31,0x33, - 0x33,0x33,0x31,0x34,0x33,0x33,0x31,0x35, - 0x33,0x33,0x31,0x36,0x33,0x33,0x31,0x37, - 0x33,0x33,0x31,0x38,0x33,0x33,0x31,0x39, - 0x33,0x33,0x32,0x30,0x33,0x33,0x32,0x31, - 0x33,0x33,0x32,0x32,0x33,0x33,0x32,0x33, - 0x33,0x33,0x32,0x34,0x33,0x33,0x32,0x35, - 0x33,0x33,0x32,0x36,0x33,0x33,0x32,0x37, - 0x33,0x33,0x32,0x38,0x33,0x33,0x32,0x39, - 0x33,0x33,0x33,0x30,0x33,0x33,0x33,0x31, - 0x33,0x33,0x33,0x32,0x33,0x33,0x33,0x33, - 0x33,0x33,0x33,0x34,0x33,0x33,0x33,0x35, - 0x33,0x33,0x33,0x36,0x33,0x33,0x33,0x37, - 0x33,0x33,0x33,0x38,0x33,0x33,0x33,0x39, - 0x33,0x33,0x34,0x30,0x33,0x33,0x34,0x31, - 0x33,0x33,0x34,0x32,0x33,0x33,0x34,0x33, - 0x33,0x33,0x34,0x34,0x33,0x33,0x34,0x35, - 0x33,0x33,0x34,0x36,0x33,0x33,0x34,0x37, - 0x33,0x33,0x34,0x38,0x33,0x33,0x34,0x39, - 0x33,0x33,0x35,0x30,0x33,0x33,0x35,0x31, - 0x33,0x33,0x35,0x32,0x33,0x33,0x35,0x33, - 0x33,0x33,0x35,0x34,0x33,0x33,0x35,0x35, - 0x33,0x33,0x35,0x36,0x33,0x33,0x35,0x37, - 0x33,0x33,0x35,0x38,0x33,0x33,0x35,0x39, - 0x33,0x33,0x36,0x30,0x33,0x33,0x36,0x31, - 0x33,0x33,0x36,0x32,0x33,0x33,0x36,0x33, - 0x33,0x33,0x36,0x34,0x33,0x33,0x36,0x35, - 0x33,0x33,0x36,0x36,0x33,0x33,0x36,0x37, - 0x33,0x33,0x36,0x38,0x33,0x33,0x36,0x39, - 0x33,0x33,0x37,0x30,0x33,0x33,0x37,0x31, - 0x33,0x33,0x37,0x32,0x33,0x33,0x37,0x33, - 0x33,0x33,0x37,0x34,0x33,0x33,0x37,0x35, - 0x33,0x33,0x37,0x36,0x33,0x33,0x37,0x37, - 0x33,0x33,0x37,0x38,0x33,0x33,0x37,0x39, - 0x33,0x33,0x38,0x30,0x33,0x33,0x38,0x31, - 0x33,0x33,0x38,0x32,0x33,0x33,0x38,0x33, - 0x33,0x33,0x38,0x34,0x33,0x33,0x38,0x35, - 0x33,0x33,0x38,0x36,0x33,0x33,0x38,0x37, - 0x33,0x33,0x38,0x38,0x33,0x33,0x38,0x39, - 0x33,0x33,0x39,0x30,0x33,0x33,0x39,0x31, - 0x33,0x33,0x39,0x32,0x33,0x33,0x39,0x33, - 0x33,0x33,0x39,0x34,0x33,0x33,0x39,0x35, - 0x33,0x33,0x39,0x36,0x33,0x33,0x39,0x37, - 0x33,0x33,0x39,0x38,0x33,0x33,0x39,0x39, - 0x33,0x34,0x30,0x30,0x33,0x34,0x30,0x31, - 0x33,0x34,0x30,0x32,0x33,0x34,0x30,0x33, - 0x33,0x34,0x30,0x34,0x33,0x34,0x30,0x35, - 0x33,0x34,0x30,0x36,0x33,0x34,0x30,0x37, - 0x33,0x34,0x30,0x38,0x33,0x34,0x30,0x39, - 0x33,0x34,0x31,0x30,0x33,0x34,0x31,0x31, - 0x33,0x34,0x31,0x32,0x33,0x34,0x31,0x33, - 0x33,0x34,0x31,0x34,0x33,0x34,0x31,0x35, - 0x33,0x34,0x31,0x36,0x33,0x34,0x31,0x37, - 0x33,0x34,0x31,0x38,0x33,0x34,0x31,0x39, - 0x33,0x34,0x32,0x30,0x33,0x34,0x32,0x31, - 0x33,0x34,0x32,0x32,0x33,0x34,0x32,0x33, - 0x33,0x34,0x32,0x34,0x33,0x34,0x32,0x35, - 0x33,0x34,0x32,0x36,0x33,0x34,0x32,0x37, - 0x33,0x34,0x32,0x38,0x33,0x34,0x32,0x39, - 0x33,0x34,0x33,0x30,0x33,0x34,0x33,0x31, - 0x33,0x34,0x33,0x32,0x33,0x34,0x33,0x33, - 0x33,0x34,0x33,0x34,0x33,0x34,0x33,0x35, - 0x33,0x34,0x33,0x36,0x33,0x34,0x33,0x37, - 0x33,0x34,0x33,0x38,0x33,0x34,0x33,0x39, - 0x33,0x34,0x34,0x30,0x33,0x34,0x34,0x31, - 0x33,0x34,0x34,0x32,0x33,0x34,0x34,0x33, - 0x33,0x34,0x34,0x34,0x33,0x34,0x34,0x35, - 0x33,0x34,0x34,0x36,0x33,0x34,0x34,0x37, - 0x33,0x34,0x34,0x38,0x33,0x34,0x34,0x39, - 0x33,0x34,0x35,0x30,0x33,0x34,0x35,0x31, - 0x33,0x34,0x35,0x32,0x33,0x34,0x35,0x33, - 0x33,0x34,0x35,0x34,0x33,0x34,0x35,0x35, - 0x33,0x34,0x35,0x36,0x33,0x34,0x35,0x37, - 0x33,0x34,0x35,0x38,0x33,0x34,0x35,0x39, - 0x33,0x34,0x36,0x30,0x33,0x34,0x36,0x31, - 0x33,0x34,0x36,0x32,0x33,0x34,0x36,0x33, - 0x33,0x34,0x36,0x34,0x33,0x34,0x36,0x35, - 0x33,0x34,0x36,0x36,0x33,0x34,0x36,0x37, - 0x33,0x34,0x36,0x38,0x33,0x34,0x36,0x39, - 0x33,0x34,0x37,0x30,0x33,0x34,0x37,0x31, - 0x33,0x34,0x37,0x32,0x33,0x34,0x37,0x33, - 0x33,0x34,0x37,0x34,0x33,0x34,0x37,0x35, - 0x33,0x34,0x37,0x36,0x33,0x34,0x37,0x37, - 0x33,0x34,0x37,0x38,0x33,0x34,0x37,0x39, - 0x33,0x34,0x38,0x30,0x33,0x34,0x38,0x31, - 0x33,0x34,0x38,0x32,0x33,0x34,0x38,0x33, - 0x33,0x34,0x38,0x34,0x33,0x34,0x38,0x35, - 0x33,0x34,0x38,0x36,0x33,0x34,0x38,0x37, - 0x33,0x34,0x38,0x38,0x33,0x34,0x38,0x39, - 0x33,0x34,0x39,0x30,0x33,0x34,0x39,0x31, - 0x33,0x34,0x39,0x32,0x33,0x34,0x39,0x33, - 0x33,0x34,0x39,0x34,0x33,0x34,0x39,0x35, - 0x33,0x34,0x39,0x36,0x33,0x34,0x39,0x37, - 0x33,0x34,0x39,0x38,0x33,0x34,0x39,0x39, - 0x33,0x35,0x30,0x30,0x33,0x35,0x30,0x31, - 0x33,0x35,0x30,0x32,0x33,0x35,0x30,0x33, - 0x33,0x35,0x30,0x34,0x33,0x35,0x30,0x35, - 0x33,0x35,0x30,0x36,0x33,0x35,0x30,0x37, - 0x33,0x35,0x30,0x38,0x33,0x35,0x30,0x39, - 0x33,0x35,0x31,0x30,0x33,0x35,0x31,0x31, - 0x33,0x35,0x31,0x32,0x33,0x35,0x31,0x33, - 0x33,0x35,0x31,0x34,0x33,0x35,0x31,0x35, - 0x33,0x35,0x31,0x36,0x33,0x35,0x31,0x37, - 0x33,0x35,0x31,0x38,0x33,0x35,0x31,0x39, - 0x33,0x35,0x32,0x30,0x33,0x35,0x32,0x31, - 0x33,0x35,0x32,0x32,0x33,0x35,0x32,0x33, - 0x33,0x35,0x32,0x34,0x33,0x35,0x32,0x35, - 0x33,0x35,0x32,0x36,0x33,0x35,0x32,0x37, - 0x33,0x35,0x32,0x38,0x33,0x35,0x32,0x39, - 0x33,0x35,0x33,0x30,0x33,0x35,0x33,0x31, - 0x33,0x35,0x33,0x32,0x33,0x35,0x33,0x33, - 0x33,0x35,0x33,0x34,0x33,0x35,0x33,0x35, - 0x33,0x35,0x33,0x36,0x33,0x35,0x33,0x37, - 0x33,0x35,0x33,0x38,0x33,0x35,0x33,0x39, - 0x33,0x35,0x34,0x30,0x33,0x35,0x34,0x31, - 0x33,0x35,0x34,0x32,0x33,0x35,0x34,0x33, - 0x33,0x35,0x34,0x34,0x33,0x35,0x34,0x35, - 0x33,0x35,0x34,0x36,0x33,0x35,0x34,0x37, - 0x33,0x35,0x34,0x38,0x33,0x35,0x34,0x39, - 0x33,0x35,0x35,0x30,0x33,0x35,0x35,0x31, - 0x33,0x35,0x35,0x32,0x33,0x35,0x35,0x33, - 0x33,0x35,0x35,0x34,0x33,0x35,0x35,0x35, - 0x33,0x35,0x35,0x36,0x33,0x35,0x35,0x37, - 0x33,0x35,0x35,0x38,0x33,0x35,0x35,0x39, - 0x33,0x35,0x36,0x30,0x33,0x35,0x36,0x31, - 0x33,0x35,0x36,0x32,0x33,0x35,0x36,0x33, - 0x33,0x35,0x36,0x34,0x33,0x35,0x36,0x35, - 0x33,0x35,0x36,0x36,0x33,0x35,0x36,0x37, - 0x33,0x35,0x36,0x38,0x33,0x35,0x36,0x39, - 0x33,0x35,0x37,0x30,0x33,0x35,0x37,0x31, - 0x33,0x35,0x37,0x32,0x33,0x35,0x37,0x33, - 0x33,0x35,0x37,0x34,0x33,0x35,0x37,0x35, - 0x33,0x35,0x37,0x36,0x33,0x35,0x37,0x37, - 0x33,0x35,0x37,0x38,0x33,0x35,0x37,0x39, - 0x33,0x35,0x38,0x30,0x33,0x35,0x38,0x31, - 0x33,0x35,0x38,0x32,0x33,0x35,0x38,0x33, - 0x33,0x35,0x38,0x34,0x33,0x35,0x38,0x35, - 0x33,0x35,0x38,0x36,0x33,0x35,0x38,0x37, - 0x33,0x35,0x38,0x38,0x33,0x35,0x38,0x39, - 0x33,0x35,0x39,0x30,0x33,0x35,0x39,0x31, - 0x33,0x35,0x39,0x32,0x33,0x35,0x39,0x33, - 0x33,0x35,0x39,0x34,0x33,0x35,0x39,0x35, - 0x33,0x35,0x39,0x36,0x33,0x35,0x39,0x37, - 0x33,0x35,0x39,0x38,0x33,0x35,0x39,0x39, - 0x33,0x36,0x30,0x30,0x33,0x36,0x30,0x31, - 0x33,0x36,0x30,0x32,0x33,0x36,0x30,0x33, - 0x33,0x36,0x30,0x34,0x33,0x36,0x30,0x35, - 0x33,0x36,0x30,0x36,0x33,0x36,0x30,0x37, - 0x33,0x36,0x30,0x38,0x33,0x36,0x30,0x39, - 0x33,0x36,0x31,0x30,0x33,0x36,0x31,0x31, - 0x33,0x36,0x31,0x32,0x33,0x36,0x31,0x33, - 0x33,0x36,0x31,0x34,0x33,0x36,0x31,0x35, - 0x33,0x36,0x31,0x36,0x33,0x36,0x31,0x37, - 0x33,0x36,0x31,0x38,0x33,0x36,0x31,0x39, - 0x33,0x36,0x32,0x30,0x33,0x36,0x32,0x31, - 0x33,0x36,0x32,0x32,0x33,0x36,0x32,0x33, - 0x33,0x36,0x32,0x34,0x33,0x36,0x32,0x35, - 0x33,0x36,0x32,0x36,0x33,0x36,0x32,0x37, - 0x33,0x36,0x32,0x38,0x33,0x36,0x32,0x39, - 0x33,0x36,0x33,0x30,0x33,0x36,0x33,0x31, - 0x33,0x36,0x33,0x32,0x33,0x36,0x33,0x33, - 0x33,0x36,0x33,0x34,0x33,0x36,0x33,0x35, - 0x33,0x36,0x33,0x36,0x33,0x36,0x33,0x37, - 0x33,0x36,0x33,0x38,0x33,0x36,0x33,0x39, - 0x33,0x36,0x34,0x30,0x33,0x36,0x34,0x31, - 0x33,0x36,0x34,0x32,0x33,0x36,0x34,0x33, - 0x33,0x36,0x34,0x34,0x33,0x36,0x34,0x35, - 0x33,0x36,0x34,0x36,0x33,0x36,0x34,0x37, - 0x33,0x36,0x34,0x38,0x33,0x36,0x34,0x39, - 0x33,0x36,0x35,0x30,0x33,0x36,0x35,0x31, - 0x33,0x36,0x35,0x32,0x33,0x36,0x35,0x33, - 0x33,0x36,0x35,0x34,0x33,0x36,0x35,0x35, - 0x33,0x36,0x35,0x36,0x33,0x36,0x35,0x37, - 0x33,0x36,0x35,0x38,0x33,0x36,0x35,0x39, - 0x33,0x36,0x36,0x30,0x33,0x36,0x36,0x31, - 0x33,0x36,0x36,0x32,0x33,0x36,0x36,0x33, - 0x33,0x36,0x36,0x34,0x33,0x36,0x36,0x35, - 0x33,0x36,0x36,0x36,0x33,0x36,0x36,0x37, - 0x33,0x36,0x36,0x38,0x33,0x36,0x36,0x39, - 0x33,0x36,0x37,0x30,0x33,0x36,0x37,0x31, - 0x33,0x36,0x37,0x32,0x33,0x36,0x37,0x33, - 0x33,0x36,0x37,0x34,0x33,0x36,0x37,0x35, - 0x33,0x36,0x37,0x36,0x33,0x36,0x37,0x37, - 0x33,0x36,0x37,0x38,0x33,0x36,0x37,0x39, - 0x33,0x36,0x38,0x30,0x33,0x36,0x38,0x31, - 0x33,0x36,0x38,0x32,0x33,0x36,0x38,0x33, - 0x33,0x36,0x38,0x34,0x33,0x36,0x38,0x35, - 0x33,0x36,0x38,0x36,0x33,0x36,0x38,0x37, - 0x33,0x36,0x38,0x38,0x33,0x36,0x38,0x39, - 0x33,0x36,0x39,0x30,0x33,0x36,0x39,0x31, - 0x33,0x36,0x39,0x32,0x33,0x36,0x39,0x33, - 0x33,0x36,0x39,0x34,0x33,0x36,0x39,0x35, - 0x33,0x36,0x39,0x36,0x33,0x36,0x39,0x37, - 0x33,0x36,0x39,0x38,0x33,0x36,0x39,0x39, - 0x33,0x37,0x30,0x30,0x33,0x37,0x30,0x31, - 0x33,0x37,0x30,0x32,0x33,0x37,0x30,0x33, - 0x33,0x37,0x30,0x34,0x33,0x37,0x30,0x35, - 0x33,0x37,0x30,0x36,0x33,0x37,0x30,0x37, - 0x33,0x37,0x30,0x38,0x33,0x37,0x30,0x39, - 0x33,0x37,0x31,0x30,0x33,0x37,0x31,0x31, - 0x33,0x37,0x31,0x32,0x33,0x37,0x31,0x33, - 0x33,0x37,0x31,0x34,0x33,0x37,0x31,0x35, - 0x33,0x37,0x31,0x36,0x33,0x37,0x31,0x37, - 0x33,0x37,0x31,0x38,0x33,0x37,0x31,0x39, - 0x33,0x37,0x32,0x30,0x33,0x37,0x32,0x31, - 0x33,0x37,0x32,0x32,0x33,0x37,0x32,0x33, - 0x33,0x37,0x32,0x34,0x33,0x37,0x32,0x35, - 0x33,0x37,0x32,0x36,0x33,0x37,0x32,0x37, - 0x33,0x37,0x32,0x38,0x33,0x37,0x32,0x39, - 0x33,0x37,0x33,0x30,0x33,0x37,0x33,0x31, - 0x33,0x37,0x33,0x32,0x33,0x37,0x33,0x33, - 0x33,0x37,0x33,0x34,0x33,0x37,0x33,0x35, - 0x33,0x37,0x33,0x36,0x33,0x37,0x33,0x37, - 0x33,0x37,0x33,0x38,0x33,0x37,0x33,0x39, - 0x33,0x37,0x34,0x30,0x33,0x37,0x34,0x31, - 0x33,0x37,0x34,0x32,0x33,0x37,0x34,0x33, - 0x33,0x37,0x34,0x34,0x33,0x37,0x34,0x35, - 0x33,0x37,0x34,0x36,0x33,0x37,0x34,0x37, - 0x33,0x37,0x34,0x38,0x33,0x37,0x34,0x39, - 0x33,0x37,0x35,0x30,0x33,0x37,0x35,0x31, - 0x33,0x37,0x35,0x32,0x33,0x37,0x35,0x33, - 0x33,0x37,0x35,0x34,0x33,0x37,0x35,0x35, - 0x33,0x37,0x35,0x36,0x33,0x37,0x35,0x37, - 0x33,0x37,0x35,0x38,0x33,0x37,0x35,0x39, - 0x33,0x37,0x36,0x30,0x33,0x37,0x36,0x31, - 0x33,0x37,0x36,0x32,0x33,0x37,0x36,0x33, - 0x33,0x37,0x36,0x34,0x33,0x37,0x36,0x35, - 0x33,0x37,0x36,0x36,0x33,0x37,0x36,0x37, - 0x33,0x37,0x36,0x38,0x33,0x37,0x36,0x39, - 0x33,0x37,0x37,0x30,0x33,0x37,0x37,0x31, - 0x33,0x37,0x37,0x32,0x33,0x37,0x37,0x33, - 0x33,0x37,0x37,0x34,0x33,0x37,0x37,0x35, - 0x33,0x37,0x37,0x36,0x33,0x37,0x37,0x37, - 0x33,0x37,0x37,0x38,0x33,0x37,0x37,0x39, - 0x33,0x37,0x38,0x30,0x33,0x37,0x38,0x31, - 0x33,0x37,0x38,0x32,0x33,0x37,0x38,0x33, - 0x33,0x37,0x38,0x34,0x33,0x37,0x38,0x35, - 0x33,0x37,0x38,0x36,0x33,0x37,0x38,0x37, - 0x33,0x37,0x38,0x38,0x33,0x37,0x38,0x39, - 0x33,0x37,0x39,0x30,0x33,0x37,0x39,0x31, - 0x33,0x37,0x39,0x32,0x33,0x37,0x39,0x33, - 0x33,0x37,0x39,0x34,0x33,0x37,0x39,0x35, - 0x33,0x37,0x39,0x36,0x33,0x37,0x39,0x37, - 0x33,0x37,0x39,0x38,0x33,0x37,0x39,0x39, - 0x33,0x38,0x30,0x30,0x33,0x38,0x30,0x31, - 0x33,0x38,0x30,0x32,0x33,0x38,0x30,0x33, - 0x33,0x38,0x30,0x34,0x33,0x38,0x30,0x35, - 0x33,0x38,0x30,0x36,0x33,0x38,0x30,0x37, - 0x33,0x38,0x30,0x38,0x33,0x38,0x30,0x39, - 0x33,0x38,0x31,0x30,0x33,0x38,0x31,0x31, - 0x33,0x38,0x31,0x32,0x33,0x38,0x31,0x33, - 0x33,0x38,0x31,0x34,0x33,0x38,0x31,0x35, - 0x33,0x38,0x31,0x36,0x33,0x38,0x31,0x37, - 0x33,0x38,0x31,0x38,0x33,0x38,0x31,0x39, - 0x33,0x38,0x32,0x30,0x33,0x38,0x32,0x31, - 0x33,0x38,0x32,0x32,0x33,0x38,0x32,0x33, - 0x33,0x38,0x32,0x34,0x33,0x38,0x32,0x35, - 0x33,0x38,0x32,0x36,0x33,0x38,0x32,0x37, - 0x33,0x38,0x32,0x38,0x33,0x38,0x32,0x39, - 0x33,0x38,0x33,0x30,0x33,0x38,0x33,0x31, - 0x33,0x38,0x33,0x32,0x33,0x38,0x33,0x33, - 0x33,0x38,0x33,0x34,0x33,0x38,0x33,0x35, - 0x33,0x38,0x33,0x36,0x33,0x38,0x33,0x37, - 0x33,0x38,0x33,0x38,0x33,0x38,0x33,0x39, - 0x33,0x38,0x34,0x30,0x33,0x38,0x34,0x31, - 0x33,0x38,0x34,0x32,0x33,0x38,0x34,0x33, - 0x33,0x38,0x34,0x34,0x33,0x38,0x34,0x35, - 0x33,0x38,0x34,0x36,0x33,0x38,0x34,0x37, - 0x33,0x38,0x34,0x38,0x33,0x38,0x34,0x39, - 0x33,0x38,0x35,0x30,0x33,0x38,0x35,0x31, - 0x33,0x38,0x35,0x32,0x33,0x38,0x35,0x33, - 0x33,0x38,0x35,0x34,0x33,0x38,0x35,0x35, - 0x33,0x38,0x35,0x36,0x33,0x38,0x35,0x37, - 0x33,0x38,0x35,0x38,0x33,0x38,0x35,0x39, - 0x33,0x38,0x36,0x30,0x33,0x38,0x36,0x31, - 0x33,0x38,0x36,0x32,0x33,0x38,0x36,0x33, - 0x33,0x38,0x36,0x34,0x33,0x38,0x36,0x35, - 0x33,0x38,0x36,0x36,0x33,0x38,0x36,0x37, - 0x33,0x38,0x36,0x38,0x33,0x38,0x36,0x39, - 0x33,0x38,0x37,0x30,0x33,0x38,0x37,0x31, - 0x33,0x38,0x37,0x32,0x33,0x38,0x37,0x33, - 0x33,0x38,0x37,0x34,0x33,0x38,0x37,0x35, - 0x33,0x38,0x37,0x36,0x33,0x38,0x37,0x37, - 0x33,0x38,0x37,0x38,0x33,0x38,0x37,0x39, - 0x33,0x38,0x38,0x30,0x33,0x38,0x38,0x31, - 0x33,0x38,0x38,0x32,0x33,0x38,0x38,0x33, - 0x33,0x38,0x38,0x34,0x33,0x38,0x38,0x35, - 0x33,0x38,0x38,0x36,0x33,0x38,0x38,0x37, - 0x33,0x38,0x38,0x38,0x33,0x38,0x38,0x39, - 0x33,0x38,0x39,0x30,0x33,0x38,0x39,0x31, - 0x33,0x38,0x39,0x32,0x33,0x38,0x39,0x33, - 0x33,0x38,0x39,0x34,0x33,0x38,0x39,0x35, - 0x33,0x38,0x39,0x36,0x33,0x38,0x39,0x37, - 0x33,0x38,0x39,0x38,0x33,0x38,0x39,0x39, - 0x33,0x39,0x30,0x30,0x33,0x39,0x30,0x31, - 0x33,0x39,0x30,0x32,0x33,0x39,0x30,0x33, - 0x33,0x39,0x30,0x34,0x33,0x39,0x30,0x35, - 0x33,0x39,0x30,0x36,0x33,0x39,0x30,0x37, - 0x33,0x39,0x30,0x38,0x33,0x39,0x30,0x39, - 0x33,0x39,0x31,0x30,0x33,0x39,0x31,0x31, - 0x33,0x39,0x31,0x32,0x33,0x39,0x31,0x33, - 0x33,0x39,0x31,0x34,0x33,0x39,0x31,0x35, - 0x33,0x39,0x31,0x36,0x33,0x39,0x31,0x37, - 0x33,0x39,0x31,0x38,0x33,0x39,0x31,0x39, - 0x33,0x39,0x32,0x30,0x33,0x39,0x32,0x31, - 0x33,0x39,0x32,0x32,0x33,0x39,0x32,0x33, - 0x33,0x39,0x32,0x34,0x33,0x39,0x32,0x35, - 0x33,0x39,0x32,0x36,0x33,0x39,0x32,0x37, - 0x33,0x39,0x32,0x38,0x33,0x39,0x32,0x39, - 0x33,0x39,0x33,0x30,0x33,0x39,0x33,0x31, - 0x33,0x39,0x33,0x32,0x33,0x39,0x33,0x33, - 0x33,0x39,0x33,0x34,0x33,0x39,0x33,0x35, - 0x33,0x39,0x33,0x36,0x33,0x39,0x33,0x37, - 0x33,0x39,0x33,0x38,0x33,0x39,0x33,0x39, - 0x33,0x39,0x34,0x30,0x33,0x39,0x34,0x31, - 0x33,0x39,0x34,0x32,0x33,0x39,0x34,0x33, - 0x33,0x39,0x34,0x34,0x33,0x39,0x34,0x35, - 0x33,0x39,0x34,0x36,0x33,0x39,0x34,0x37, - 0x33,0x39,0x34,0x38,0x33,0x39,0x34,0x39, - 0x33,0x39,0x35,0x30,0x33,0x39,0x35,0x31, - 0x33,0x39,0x35,0x32,0x33,0x39,0x35,0x33, - 0x33,0x39,0x35,0x34,0x33,0x39,0x35,0x35, - 0x33,0x39,0x35,0x36,0x33,0x39,0x35,0x37, - 0x33,0x39,0x35,0x38,0x33,0x39,0x35,0x39, - 0x33,0x39,0x36,0x30,0x33,0x39,0x36,0x31, - 0x33,0x39,0x36,0x32,0x33,0x39,0x36,0x33, - 0x33,0x39,0x36,0x34,0x33,0x39,0x36,0x35, - 0x33,0x39,0x36,0x36,0x33,0x39,0x36,0x37, - 0x33,0x39,0x36,0x38,0x33,0x39,0x36,0x39, - 0x33,0x39,0x37,0x30,0x33,0x39,0x37,0x31, - 0x33,0x39,0x37,0x32,0x33,0x39,0x37,0x33, - 0x33,0x39,0x37,0x34,0x33,0x39,0x37,0x35, - 0x33,0x39,0x37,0x36,0x33,0x39,0x37,0x37, - 0x33,0x39,0x37,0x38,0x33,0x39,0x37,0x39, - 0x33,0x39,0x38,0x30,0x33,0x39,0x38,0x31, - 0x33,0x39,0x38,0x32,0x33,0x39,0x38,0x33, - 0x33,0x39,0x38,0x34,0x33,0x39,0x38,0x35, - 0x33,0x39,0x38,0x36,0x33,0x39,0x38,0x37, - 0x33,0x39,0x38,0x38,0x33,0x39,0x38,0x39, - 0x33,0x39,0x39,0x30,0x33,0x39,0x39,0x31, - 0x33,0x39,0x39,0x32,0x33,0x39,0x39,0x33, - 0x33,0x39,0x39,0x34,0x33,0x39,0x39,0x35, - 0x33,0x39,0x39,0x36,0x33,0x39,0x39,0x37, - 0x33,0x39,0x39,0x38,0x33,0x39,0x39,0x39, - 0x34,0x30,0x30,0x30,0x34,0x30,0x30,0x31, - 0x34,0x30,0x30,0x32,0x34,0x30,0x30,0x33, - 0x34,0x30,0x30,0x34,0x34,0x30,0x30,0x35, - 0x34,0x30,0x30,0x36,0x34,0x30,0x30,0x37, - 0x34,0x30,0x30,0x38,0x34,0x30,0x30,0x39, - 0x34,0x30,0x31,0x30,0x34,0x30,0x31,0x31, - 0x34,0x30,0x31,0x32,0x34,0x30,0x31,0x33, - 0x34,0x30,0x31,0x34,0x34,0x30,0x31,0x35, - 0x34,0x30,0x31,0x36,0x34,0x30,0x31,0x37, - 0x34,0x30,0x31,0x38,0x34,0x30,0x31,0x39, - 0x34,0x30,0x32,0x30,0x34,0x30,0x32,0x31, - 0x34,0x30,0x32,0x32,0x34,0x30,0x32,0x33, - 0x34,0x30,0x32,0x34,0x34,0x30,0x32,0x35, - 0x34,0x30,0x32,0x36,0x34,0x30,0x32,0x37, - 0x34,0x30,0x32,0x38,0x34,0x30,0x32,0x39, - 0x34,0x30,0x33,0x30,0x34,0x30,0x33,0x31, - 0x34,0x30,0x33,0x32,0x34,0x30,0x33,0x33, - 0x34,0x30,0x33,0x34,0x34,0x30,0x33,0x35, - 0x34,0x30,0x33,0x36,0x34,0x30,0x33,0x37, - 0x34,0x30,0x33,0x38,0x34,0x30,0x33,0x39, - 0x34,0x30,0x34,0x30,0x34,0x30,0x34,0x31, - 0x34,0x30,0x34,0x32,0x34,0x30,0x34,0x33, - 0x34,0x30,0x34,0x34,0x34,0x30,0x34,0x35, - 0x34,0x30,0x34,0x36,0x34,0x30,0x34,0x37, - 0x34,0x30,0x34,0x38,0x34,0x30,0x34,0x39, - 0x34,0x30,0x35,0x30,0x34,0x30,0x35,0x31, - 0x34,0x30,0x35,0x32,0x34,0x30,0x35,0x33, - 0x34,0x30,0x35,0x34,0x34,0x30,0x35,0x35, - 0x34,0x30,0x35,0x36,0x34,0x30,0x35,0x37, - 0x34,0x30,0x35,0x38,0x34,0x30,0x35,0x39, - 0x34,0x30,0x36,0x30,0x34,0x30,0x36,0x31, - 0x34,0x30,0x36,0x32,0x34,0x30,0x36,0x33, - 0x34,0x30,0x36,0x34,0x34,0x30,0x36,0x35, - 0x34,0x30,0x36,0x36,0x34,0x30,0x36,0x37, - 0x34,0x30,0x36,0x38,0x34,0x30,0x36,0x39, - 0x34,0x30,0x37,0x30,0x34,0x30,0x37,0x31, - 0x34,0x30,0x37,0x32,0x34,0x30,0x37,0x33, - 0x34,0x30,0x37,0x34,0x34,0x30,0x37,0x35, - 0x34,0x30,0x37,0x36,0x34,0x30,0x37,0x37, - 0x34,0x30,0x37,0x38,0x34,0x30,0x37,0x39, - 0x34,0x30,0x38,0x30,0x34,0x30,0x38,0x31, - 0x34,0x30,0x38,0x32,0x34,0x30,0x38,0x33, - 0x34,0x30,0x38,0x34,0x34,0x30,0x38,0x35, - 0x34,0x30,0x38,0x36,0x34,0x30,0x38,0x37, - 0x34,0x30,0x38,0x38,0x34,0x30,0x38,0x39, - 0x34,0x30,0x39,0x30,0x34,0x30,0x39,0x31, - 0x34,0x30,0x39,0x32,0x34,0x30,0x39,0x33, - 0x34,0x30,0x39,0x34,0x34,0x30,0x39,0x35}; - - static const int32_t gDigitCount[] = { - 1,1,1,1,1,1,1,1, - 1,1,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,2,2,2,2, - 2,2,2,2,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 3,3,3,3,3,3,3,3, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4, - 4,4,4,4,4,4,4,4}; - -U_NAMESPACE_BEGIN - - -IntDigitCountRange::IntDigitCountRange(int32_t min, int32_t max) { - fMin = min < 0 ? 0 : min; - fMax = max < fMin ? fMin : max; -} - -int32_t -IntDigitCountRange::pin(int32_t digitCount) const { - return digitCount < fMin ? fMin : (digitCount < fMax ? digitCount : fMax); -} - -int32_t -SmallIntFormatter::estimateDigitCount( - int32_t positiveValue, const IntDigitCountRange &range) { - if (positiveValue >= gMaxFastInt) { - return range.getMax(); - } - return range.pin(gDigitCount[positiveValue]); -} - -UBool -SmallIntFormatter::canFormat( - int32_t positiveValue, const IntDigitCountRange &range) { - return (positiveValue < gMaxFastInt && range.getMin() <= 4); -} - -UnicodeString & -SmallIntFormatter::format( - int32_t smallPositiveValue, - const IntDigitCountRange &range, - UnicodeString &appendTo) { - int32_t digits = range.pin(gDigitCount[smallPositiveValue]); - - // Always emit at least '0' - if (digits == 0) { - return appendTo.append((UChar) 0x30); - } - return appendTo.append(gDigits, ((smallPositiveValue + 1) << 2) - digits, digits); -} - -U_NAMESPACE_END diff --git a/deps/icu-small/source/i18n/smallintformatter.h b/deps/icu-small/source/i18n/smallintformatter.h deleted file mode 100644 index 3373a9c35ffcec..00000000000000 --- a/deps/icu-small/source/i18n/smallintformatter.h +++ /dev/null @@ -1,90 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* smallintformatter.h -* -* created on: 2015jan06 -* created by: Travis Keep -*/ - -#ifndef __SMALLINTFORMATTER_H__ -#define __SMALLINTFORMATTER_H__ - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - -U_NAMESPACE_BEGIN - -class UnicodeString; - -/** - * A representation an acceptable range of digit counts for integers. - */ -class U_I18N_API IntDigitCountRange : public UMemory { -public: - /** - * No constraints: 0 up to INT32_MAX - */ - IntDigitCountRange() : fMin(0), fMax(INT32_MAX) { } - IntDigitCountRange(int32_t min, int32_t max); - int32_t pin(int32_t digitCount) const; - int32_t getMax() const { return fMax; } - int32_t getMin() const { return fMin; } -private: - int32_t fMin; - int32_t fMax; -}; - - -/** - * A formatter for small, positive integers. - */ -class U_I18N_API SmallIntFormatter : public UMemory { -public: - /** - * Estimates the actual digit count needed to format positiveValue - * using the given range of digit counts. - * Returns a value that is at least the actual digit count needed. - * - * @param positiveValue the value to format - * @param range the acceptable range of digit counts. - */ - static int32_t estimateDigitCount( - int32_t positiveValue, const IntDigitCountRange &range); - - /** - * Returns TRUE if this class can format positiveValue using - * the given range of digit counts. - * - * @param positiveValue the value to format - * @param range the acceptable range of digit counts. - */ - static UBool canFormat( - int32_t positiveValue, const IntDigitCountRange &range); - - /** - * Formats positiveValue using the given range of digit counts. - * Always uses standard digits '0' through '9'. Formatted value is - * left padded with '0' as necessary to achieve minimum digit count. - * Does not produce any grouping separators or trailing decimal point. - * Calling format to format a value with a particular digit count range - * when canFormat indicates that the same value and digit count range - * cannot be formatted results in undefined behavior. - * - * @param positiveValue the value to format - * @param range the acceptable range of digit counts. - */ - static UnicodeString &format( - int32_t positiveValue, - const IntDigitCountRange &range, - UnicodeString &appendTo); - -}; - -U_NAMESPACE_END - -#endif // __SMALLINTFORMATTER_H__ diff --git a/deps/icu-small/source/i18n/smpdtfmt.cpp b/deps/icu-small/source/i18n/smpdtfmt.cpp index 27fbbd8f7a9ef5..b1b90882fceead 100644 --- a/deps/icu-small/source/i18n/smpdtfmt.cpp +++ b/deps/icu-small/source/i18n/smpdtfmt.cpp @@ -53,6 +53,7 @@ #include "unicode/vtzone.h" #include "unicode/udisplaycontext.h" #include "unicode/brkiter.h" +#include "unicode/rbnf.h" #include "uresimp.h" #include "olsontz.h" #include "patternprops.h" @@ -72,6 +73,7 @@ #include "cstr.h" #include "dayperiodrules.h" #include "tznames_impl.h" // ZONE_NAME_U16_MAX +#include "number_utypes.h" #if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL) #include @@ -312,57 +314,6 @@ const NumberFormat *SimpleDateFormat::getNumberFormatByIndex( return &(**fSharedNumberFormatters[index]); } -class SimpleDateFormatMutableNFNode { - public: - const NumberFormat *key; - NumberFormat *value; - SimpleDateFormatMutableNFNode() - : key(NULL), value(NULL) { } - ~SimpleDateFormatMutableNFNode() { - delete value; - } - private: - SimpleDateFormatMutableNFNode(const SimpleDateFormatMutableNFNode &); - SimpleDateFormatMutableNFNode &operator=(const SimpleDateFormatMutableNFNode &); -}; - -// Single threaded cache of non const NumberFormats. Designed to be stack -// allocated and used for a single format call. -class SimpleDateFormatMutableNFs : public UMemory { - public: - SimpleDateFormatMutableNFs() { - } - - // Returns a non-const clone of nf which can be safely modified. - // Subsequent calls with same nf will return the same non-const clone. - // This object maintains ownership of all returned non-const - // NumberFormat objects. On memory allocation error returns NULL. - // Caller must check for NULL return value. - NumberFormat *get(const NumberFormat *nf) { - if (nf == NULL) { - return NULL; - } - int32_t idx = 0; - while (nodes[idx].value) { - if (nf == nodes[idx].key) { - return nodes[idx].value; - } - ++idx; - } - U_ASSERT(idx < UDAT_FIELD_COUNT); - nodes[idx].key = nf; - nodes[idx].value = (NumberFormat *) nf->clone(); - return nodes[idx].value; - } - private: - // +1 extra for sentinel. If each field had its own NumberFormat, this - // cache would have to allocate UDAT_FIELD_COUNT mutable versions worst - // case. - SimpleDateFormatMutableNFNode nodes[UDAT_FIELD_COUNT + 1]; - SimpleDateFormatMutableNFs(const SimpleDateFormatMutableNFs &); - SimpleDateFormatMutableNFs &operator=(const SimpleDateFormatMutableNFs &); -}; - //---------------------------------------------------------------------- SimpleDateFormat::~SimpleDateFormat() @@ -374,6 +325,7 @@ SimpleDateFormat::~SimpleDateFormat() if (fTimeZoneFormat) { delete fTimeZoneFormat; } + freeFastNumberFormatters(); #if !UCONFIG_NO_BREAK_ITERATION delete fCapitalizationBrkIter; @@ -659,6 +611,10 @@ SimpleDateFormat& SimpleDateFormat::operator=(const SimpleDateFormat& other) } } + UErrorCode localStatus = U_ZERO_ERROR; + freeFastNumberFormatters(); + initFastNumberFormatters(localStatus); + return *this; } @@ -908,7 +864,8 @@ SimpleDateFormat::initialize(const Locale& locale, fixNumberFormatForDates(*fNumberFormat); //fNumberFormat->setLenient(TRUE); // Java uses a custom DateNumberFormat to format/parse - initNumberFormatters(locale,status); + initNumberFormatters(locale, status); + initFastNumberFormatters(status); } else if (U_SUCCESS(status)) @@ -1023,11 +980,6 @@ SimpleDateFormat::_format(Calendar& cal, UnicodeString& appendTo, int32_t fieldNum = 0; UDisplayContext capitalizationContext = getContext(UDISPCTX_TYPE_CAPITALIZATION, status); - // Create temporary cache of mutable number format objects. This way - // subFormat won't have to clone the const NumberFormat for each field. - // if several fields share the same NumberFormat, which will almost - // always be the case, this is a big save. - SimpleDateFormatMutableNFs mutableNFs; // loop through the pattern string character by character for (int32_t i = 0; i < fPattern.length() && U_SUCCESS(status); ++i) { UChar ch = fPattern[i]; @@ -1035,7 +987,7 @@ SimpleDateFormat::_format(Calendar& cal, UnicodeString& appendTo, // Use subFormat() to format a repeated pattern character // when a different pattern or non-pattern character is seen if (ch != prevCh && count > 0) { - subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, mutableNFs, status); + subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, status); count = 0; } if (ch == QUOTE) { @@ -1062,7 +1014,7 @@ SimpleDateFormat::_format(Calendar& cal, UnicodeString& appendTo, // Format the last item in the pattern, if any if (count > 0) { - subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, mutableNFs, status); + subFormat(appendTo, prevCh, count, capitalizationContext, fieldNum++, handler, *workCal, status); } if (calClone != NULL) { @@ -1257,6 +1209,43 @@ _appendSymbolWithMonthPattern(UnicodeString& dst, int32_t value, const UnicodeSt } //---------------------------------------------------------------------- + +static number::LocalizedNumberFormatter* +createFastFormatter(const DecimalFormat* df, int32_t minInt, int32_t maxInt) { + return new number::LocalizedNumberFormatter( + df->toNumberFormatter() + .integerWidth(number::IntegerWidth::zeroFillTo(minInt).truncateAt(maxInt))); +} + +void SimpleDateFormat::initFastNumberFormatters(UErrorCode& status) { + if (U_FAILURE(status)) { + return; + } + auto* df = dynamic_cast(fNumberFormat); + if (df == nullptr) { + return; + } + fFastNumberFormatters[SMPDTFMT_NF_1x10] = createFastFormatter(df, 1, 10); + fFastNumberFormatters[SMPDTFMT_NF_2x10] = createFastFormatter(df, 2, 10); + fFastNumberFormatters[SMPDTFMT_NF_3x10] = createFastFormatter(df, 3, 10); + fFastNumberFormatters[SMPDTFMT_NF_4x10] = createFastFormatter(df, 4, 10); + fFastNumberFormatters[SMPDTFMT_NF_2x2] = createFastFormatter(df, 2, 2); +} + +void SimpleDateFormat::freeFastNumberFormatters() { + delete fFastNumberFormatters[SMPDTFMT_NF_1x10]; + delete fFastNumberFormatters[SMPDTFMT_NF_2x10]; + delete fFastNumberFormatters[SMPDTFMT_NF_3x10]; + delete fFastNumberFormatters[SMPDTFMT_NF_4x10]; + delete fFastNumberFormatters[SMPDTFMT_NF_2x2]; + fFastNumberFormatters[SMPDTFMT_NF_1x10] = nullptr; + fFastNumberFormatters[SMPDTFMT_NF_2x10] = nullptr; + fFastNumberFormatters[SMPDTFMT_NF_3x10] = nullptr; + fFastNumberFormatters[SMPDTFMT_NF_4x10] = nullptr; + fFastNumberFormatters[SMPDTFMT_NF_2x2] = nullptr; +} + + void SimpleDateFormat::initNumberFormatters(const Locale &locale,UErrorCode &status) { if (U_FAILURE(status)) { @@ -1406,7 +1395,6 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, int32_t fieldNum, FieldPositionHandler& handler, Calendar& cal, - SimpleDateFormatMutableNFs &mutableNFs, UErrorCode& status) const { if (U_FAILURE(status)) { @@ -1419,7 +1407,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, UDateFormatField patternCharIndex = DateFormatSymbols::getPatternCharIndex(ch); const int32_t maxIntCount = 10; int32_t beginOffset = appendTo.length(); - NumberFormat *currentNumberFormat; + const NumberFormat *currentNumberFormat; DateFormatSymbols::ECapitalizationContextUsageType capContextUsageType = DateFormatSymbols::kCapContextUsageOther; UBool isHebrewCalendar = (uprv_strcmp(cal.getType(),"hebrew") == 0); @@ -1444,9 +1432,9 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, return; } - currentNumberFormat = mutableNFs.get(getNumberFormatByIndex(patternCharIndex)); + currentNumberFormat = getNumberFormatByIndex(patternCharIndex); if (currentNumberFormat == NULL) { - status = U_MEMORY_ALLOCATION_ERROR; + status = U_INTERNAL_PROGRAM_ERROR; return; } UnicodeString hebr("hebr", 4, US_INV); @@ -1566,18 +1554,15 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, case UDAT_FRACTIONAL_SECOND_FIELD: // Fractional seconds left-justify { - currentNumberFormat->setMinimumIntegerDigits((count > 3) ? 3 : count); - currentNumberFormat->setMaximumIntegerDigits(maxIntCount); + int32_t minDigits = (count > 3) ? 3 : count; if (count == 1) { value /= 100; } else if (count == 2) { value /= 10; } - FieldPosition p(FieldPosition::DONT_CARE); - currentNumberFormat->format(value, appendTo, p); + zeroPaddingNumber(currentNumberFormat, appendTo, value, minDigits, maxIntCount); if (count > 3) { - currentNumberFormat->setMinimumIntegerDigits(count - 3); - currentNumberFormat->format((int32_t)0, appendTo, p); + zeroPaddingNumber(currentNumberFormat, appendTo, 0, count - 3, maxIntCount); } } break; @@ -1695,100 +1680,101 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, UnicodeString zoneString(zsbuf, 0, UPRV_LENGTHOF(zsbuf)); const TimeZone& tz = cal.getTimeZone(); UDate date = cal.getTime(status); + const TimeZoneFormat *tzfmt = tzFormat(status); if (U_SUCCESS(status)) { if (patternCharIndex == UDAT_TIMEZONE_FIELD) { if (count < 4) { // "z", "zz", "zzz" - tzFormat()->format(UTZFMT_STYLE_SPECIFIC_SHORT, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_SPECIFIC_SHORT, tz, date, zoneString); capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneShort; } else { // "zzzz" or longer - tzFormat()->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_SPECIFIC_LONG, tz, date, zoneString); capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneLong; } } else if (patternCharIndex == UDAT_TIMEZONE_RFC_FIELD) { if (count < 4) { // "Z" - tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString); } else if (count == 5) { // "ZZZZZ" - tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString); } else { // "ZZ", "ZZZ", "ZZZZ" - tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString); } } else if (patternCharIndex == UDAT_TIMEZONE_GENERIC_FIELD) { if (count == 1) { // "v" - tzFormat()->format(UTZFMT_STYLE_GENERIC_SHORT, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_GENERIC_SHORT, tz, date, zoneString); capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneShort; } else if (count == 4) { // "vvvv" - tzFormat()->format(UTZFMT_STYLE_GENERIC_LONG, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_GENERIC_LONG, tz, date, zoneString); capContextUsageType = DateFormatSymbols::kCapContextUsageMetazoneLong; } } else if (patternCharIndex == UDAT_TIMEZONE_SPECIAL_FIELD) { if (count == 1) { // "V" - tzFormat()->format(UTZFMT_STYLE_ZONE_ID_SHORT, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ZONE_ID_SHORT, tz, date, zoneString); } else if (count == 2) { // "VV" - tzFormat()->format(UTZFMT_STYLE_ZONE_ID, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ZONE_ID, tz, date, zoneString); } else if (count == 3) { // "VVV" - tzFormat()->format(UTZFMT_STYLE_EXEMPLAR_LOCATION, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_EXEMPLAR_LOCATION, tz, date, zoneString); } else if (count == 4) { // "VVVV" - tzFormat()->format(UTZFMT_STYLE_GENERIC_LOCATION, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_GENERIC_LOCATION, tz, date, zoneString); capContextUsageType = DateFormatSymbols::kCapContextUsageZoneLong; } } else if (patternCharIndex == UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD) { if (count == 1) { // "O" - tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT_SHORT, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_LOCALIZED_GMT_SHORT, tz, date, zoneString); } else if (count == 4) { // "OOOO" - tzFormat()->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_LOCALIZED_GMT, tz, date, zoneString); } } else if (patternCharIndex == UDAT_TIMEZONE_ISO_FIELD) { if (count == 1) { // "X" - tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_SHORT, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_BASIC_SHORT, tz, date, zoneString); } else if (count == 2) { // "XX" - tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_FIXED, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_BASIC_FIXED, tz, date, zoneString); } else if (count == 3) { // "XXX" - tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FIXED, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_FIXED, tz, date, zoneString); } else if (count == 4) { // "XXXX" - tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_FULL, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_BASIC_FULL, tz, date, zoneString); } else if (count == 5) { // "XXXXX" - tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_FULL, tz, date, zoneString); } } else if (patternCharIndex == UDAT_TIMEZONE_ISO_LOCAL_FIELD) { if (count == 1) { // "x" - tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_SHORT, tz, date, zoneString); } else if (count == 2) { // "xx" - tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FIXED, tz, date, zoneString); } else if (count == 3) { // "xxx" - tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FIXED, tz, date, zoneString); } else if (count == 4) { // "xxxx" - tzFormat()->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL, tz, date, zoneString); } else if (count == 5) { // "xxxxx" - tzFormat()->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL, tz, date, zoneString); + tzfmt->format(UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL, tz, date, zoneString); } } else { @@ -1855,7 +1841,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, if (toAppend == NULL || toAppend->isBogus()) { // Reformat with identical arguments except ch, now changed to 'a'. subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum, - handler, cal, mutableNFs, status); + handler, cal, status); } else { appendTo += *toAppend; } @@ -1876,7 +1862,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, // Data doesn't exist for the locale we're looking for. // Falling back to am/pm. subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum, - handler, cal, mutableNFs, status); + handler, cal, status); break; } @@ -1947,7 +1933,7 @@ SimpleDateFormat::subFormat(UnicodeString &appendTo, periodType == DayPeriodRules::DAYPERIOD_PM || toAppend->isBogus()) { subFormat(appendTo, 0x61, count, capitalizationContext, fieldNum, - handler, cal, mutableNFs, status); + handler, cal, status); } else { appendTo += *toAppend; @@ -2004,6 +1990,11 @@ void SimpleDateFormat::adoptNumberFormat(NumberFormat *formatToAdopt) { freeSharedNumberFormatters(fSharedNumberFormatters); fSharedNumberFormatters = NULL; } + + // Also re-compute the fast formatters. + UErrorCode localStatus = U_ZERO_ERROR; + freeFastNumberFormatters(); + initFastNumberFormatters(localStatus); } void SimpleDateFormat::adoptNumberFormat(const UnicodeString& fields, NumberFormat *formatToAdopt, UErrorCode &status){ @@ -2055,16 +2046,58 @@ SimpleDateFormat::getNumberFormatForField(UChar field) const { //---------------------------------------------------------------------- void SimpleDateFormat::zeroPaddingNumber( - NumberFormat *currentNumberFormat, + const NumberFormat *currentNumberFormat, UnicodeString &appendTo, int32_t value, int32_t minDigits, int32_t maxDigits) const { - if (currentNumberFormat!=NULL) { + const number::LocalizedNumberFormatter* fastFormatter = nullptr; + // NOTE: This uses the heuristic that these five min/max int settings account for the vast majority + // of SimpleDateFormat number formatting cases at the time of writing (ICU 62). + if (currentNumberFormat == fNumberFormat) { + if (maxDigits == 10) { + if (minDigits == 1) { + fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_1x10]; + } else if (minDigits == 2) { + fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_2x10]; + } else if (minDigits == 3) { + fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_3x10]; + } else if (minDigits == 4) { + fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_4x10]; + } + } else if (maxDigits == 2) { + if (minDigits == 2) { + fastFormatter = fFastNumberFormatters[SMPDTFMT_NF_2x2]; + } + } + } + if (fastFormatter != nullptr) { + // Can use fast path + number::impl::UFormattedNumberData result; + result.quantity.setToInt(value); + UErrorCode localStatus = U_ZERO_ERROR; + fastFormatter->formatImpl(&result, localStatus); + if (U_FAILURE(localStatus)) { + return; + } + appendTo.append(result.string.toTempUnicodeString()); + return; + } + + // Check for RBNF (no clone necessary) + auto* rbnf = dynamic_cast(currentNumberFormat); + if (rbnf != nullptr) { FieldPosition pos(FieldPosition::DONT_CARE); + rbnf->format(value, appendTo, pos); // 3rd arg is there to speed up processing + return; + } - currentNumberFormat->setMinimumIntegerDigits(minDigits); - currentNumberFormat->setMaximumIntegerDigits(maxDigits); - currentNumberFormat->format(value, appendTo, pos); // 3rd arg is there to speed up processing + // Fall back to slow path (clone and mutate the NumberFormat) + if (currentNumberFormat != nullptr) { + FieldPosition pos(FieldPosition::DONT_CARE); + LocalPointer nf(dynamic_cast(currentNumberFormat->clone())); + nf->setMinimumIntegerDigits(minDigits); + nf->setMaximumIntegerDigits(maxDigits); + nf->format(value, appendTo, pos); // 3rd arg is there to speed up processing } } @@ -2131,7 +2164,6 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& int32_t saveHebrewMonth = -1; int32_t count = 0; UTimeZoneFormatTimeType tzTimeType = UTZFMT_TIME_TYPE_UNKNOWN; - SimpleDateFormatMutableNFs mutableNFs; // For parsing abutting numeric fields. 'abutPat' is the // offset into 'pattern' of the first of 2 or more abutting @@ -2225,7 +2257,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& } pos = subParse(text, pos, ch, count, - TRUE, FALSE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType, mutableNFs); + TRUE, FALSE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType); // If the parse fails anywhere in the run, back up to the // start of the run and retry. @@ -2240,7 +2272,7 @@ SimpleDateFormat::parse(const UnicodeString& text, Calendar& cal, ParsePosition& // fields. else if (ch != 0x6C) { // pattern char 'l' (SMALL LETTER L) just gets ignored int32_t s = subParse(text, pos, ch, count, - FALSE, TRUE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType, mutableNFs, &dayPeriodInt); + FALSE, TRUE, ambiguousYear, saveHebrewMonth, *workCal, i, numericLeapMonthFormatter, &tzTimeType, &dayPeriodInt); if (s == -pos-1) { // era not present, in special cases allow this to continue @@ -2858,7 +2890,7 @@ SimpleDateFormat::set2DigitYearStart(UDate d, UErrorCode& status) */ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UChar ch, int32_t count, UBool obeyCount, UBool allowNegative, UBool ambiguousYear[], int32_t& saveHebrewMonth, Calendar& cal, - int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, SimpleDateFormatMutableNFs &mutableNFs, + int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, int32_t *dayPeriod) const { Formattable number; @@ -2868,7 +2900,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC UErrorCode status = U_ZERO_ERROR; ParsePosition pos(0); UDateFormatField patternCharIndex = DateFormatSymbols::getPatternCharIndex(ch); - NumberFormat *currentNumberFormat; + const NumberFormat *currentNumberFormat; UnicodeString temp; UBool gotNumber = FALSE; @@ -2880,7 +2912,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC return -start; } - currentNumberFormat = mutableNFs.get(getNumberFormatByIndex(patternCharIndex)); + currentNumberFormat = getNumberFormatByIndex(patternCharIndex); if (currentNumberFormat == NULL) { return -start; } @@ -3393,31 +3425,41 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC case UDAT_TIMEZONE_FIELD: // 'z' { UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_SPECIFIC_SHORT : UTZFMT_STYLE_SPECIFIC_LONG; - TimeZone *tz = tzFormat()->parse(style, text, pos, tzTimeType); - if (tz != NULL) { - cal.adoptTimeZone(tz); - return pos.getIndex(); + const TimeZoneFormat *tzfmt = tzFormat(status); + if (U_SUCCESS(status)) { + TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType); + if (tz != NULL) { + cal.adoptTimeZone(tz); + return pos.getIndex(); + } } - } + return -start; + } break; case UDAT_TIMEZONE_RFC_FIELD: // 'Z' { UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_ISO_BASIC_LOCAL_FULL : ((count == 5) ? UTZFMT_STYLE_ISO_EXTENDED_FULL: UTZFMT_STYLE_LOCALIZED_GMT); - TimeZone *tz = tzFormat()->parse(style, text, pos, tzTimeType); - if (tz != NULL) { - cal.adoptTimeZone(tz); - return pos.getIndex(); + const TimeZoneFormat *tzfmt = tzFormat(status); + if (U_SUCCESS(status)) { + TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType); + if (tz != NULL) { + cal.adoptTimeZone(tz); + return pos.getIndex(); + } } return -start; } case UDAT_TIMEZONE_GENERIC_FIELD: // 'v' { UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_GENERIC_SHORT : UTZFMT_STYLE_GENERIC_LONG; - TimeZone *tz = tzFormat()->parse(style, text, pos, tzTimeType); - if (tz != NULL) { - cal.adoptTimeZone(tz); - return pos.getIndex(); + const TimeZoneFormat *tzfmt = tzFormat(status); + if (U_SUCCESS(status)) { + TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType); + if (tz != NULL) { + cal.adoptTimeZone(tz); + return pos.getIndex(); + } } return -start; } @@ -3438,20 +3480,26 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC style = UTZFMT_STYLE_GENERIC_LOCATION; break; } - TimeZone *tz = tzFormat()->parse(style, text, pos, tzTimeType); - if (tz != NULL) { - cal.adoptTimeZone(tz); - return pos.getIndex(); + const TimeZoneFormat *tzfmt = tzFormat(status); + if (U_SUCCESS(status)) { + TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType); + if (tz != NULL) { + cal.adoptTimeZone(tz); + return pos.getIndex(); + } } return -start; } case UDAT_TIMEZONE_LOCALIZED_GMT_OFFSET_FIELD: // 'O' { UTimeZoneFormatStyle style = (count < 4) ? UTZFMT_STYLE_LOCALIZED_GMT_SHORT : UTZFMT_STYLE_LOCALIZED_GMT; - TimeZone *tz = tzFormat()->parse(style, text, pos, tzTimeType); - if (tz != NULL) { - cal.adoptTimeZone(tz); - return pos.getIndex(); + const TimeZoneFormat *tzfmt = tzFormat(status); + if (U_SUCCESS(status)) { + TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType); + if (tz != NULL) { + cal.adoptTimeZone(tz); + return pos.getIndex(); + } } return -start; } @@ -3475,10 +3523,13 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC style = UTZFMT_STYLE_ISO_EXTENDED_FULL; break; } - TimeZone *tz = tzFormat()->parse(style, text, pos, tzTimeType); - if (tz != NULL) { - cal.adoptTimeZone(tz); - return pos.getIndex(); + const TimeZoneFormat *tzfmt = tzFormat(status); + if (U_SUCCESS(status)) { + TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType); + if (tz != NULL) { + cal.adoptTimeZone(tz); + return pos.getIndex(); + } } return -start; } @@ -3502,10 +3553,13 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC style = UTZFMT_STYLE_ISO_EXTENDED_LOCAL_FULL; break; } - TimeZone *tz = tzFormat()->parse(style, text, pos, tzTimeType); - if (tz != NULL) { - cal.adoptTimeZone(tz); - return pos.getIndex(); + const TimeZoneFormat *tzfmt = tzFormat(status); + if (U_SUCCESS(status)) { + TimeZone *tz = tzfmt->parse(style, text, pos, tzTimeType); + if (tz != NULL) { + cal.adoptTimeZone(tz); + return pos.getIndex(); + } } return -start; } @@ -3539,7 +3593,7 @@ int32_t SimpleDateFormat::subParse(const UnicodeString& text, int32_t& start, UC U_ASSERT(dayPeriod != NULL); int32_t ampmStart = subParse(text, start, 0x61, count, obeyCount, allowNegative, ambiguousYear, saveHebrewMonth, cal, - patLoc, numericLeapMonthFormatter, tzTimeType, mutableNFs); + patLoc, numericLeapMonthFormatter, tzTimeType); if (ampmStart > 0) { return ampmStart; @@ -3686,7 +3740,7 @@ void SimpleDateFormat::parseInt(const UnicodeString& text, Formattable& number, ParsePosition& pos, UBool allowNegative, - NumberFormat *fmt) const { + const NumberFormat *fmt) const { parseInt(text, number, -1, pos, allowNegative,fmt); } @@ -3698,18 +3752,21 @@ void SimpleDateFormat::parseInt(const UnicodeString& text, int32_t maxDigits, ParsePosition& pos, UBool allowNegative, - NumberFormat *fmt) const { + const NumberFormat *fmt) const { UnicodeString oldPrefix; - DecimalFormat* df = NULL; - if (!allowNegative && (df = dynamic_cast(fmt)) != NULL) { - df->getNegativePrefix(oldPrefix); + auto* fmtAsDF = dynamic_cast(fmt); + LocalPointer df; + if (!allowNegative && fmtAsDF != nullptr) { + df.adoptInstead(dynamic_cast(fmtAsDF->clone())); + if (df.isNull()) { + // Memory allocation error + return; + } df->setNegativePrefix(UnicodeString(TRUE, SUPPRESS_NEGATIVE_PREFIX, -1)); + fmt = df.getAlias(); } int32_t oldPos = pos.getIndex(); fmt->parse(text, number, pos); - if (df != NULL) { - df->setNegativePrefix(oldPrefix); - } if (maxDigits > 0) { // adjust the result to fit into @@ -3856,7 +3913,13 @@ SimpleDateFormat::setDateFormatSymbols(const DateFormatSymbols& newFormatSymbols //---------------------------------------------------------------------- const TimeZoneFormat* SimpleDateFormat::getTimeZoneFormat(void) const { - return (const TimeZoneFormat*)tzFormat(); + // TimeZoneFormat initialization might fail when out of memory. + // If we always initialize TimeZoneFormat instance, we can return + // such status there. For now, this implementation lazily instantiates + // a TimeZoneFormat for performance optimization reasons, but cannot + // propagate such error (probably just out of memory case) to the caller. + UErrorCode status = U_ZERO_ERROR; + return (const TimeZoneFormat*)tzFormat(status); } //---------------------------------------------------------------------- @@ -4123,12 +4186,11 @@ SimpleDateFormat::skipUWhiteSpace(const UnicodeString& text, int32_t pos) const // Lazy TimeZoneFormat instantiation, semantically const. TimeZoneFormat * -SimpleDateFormat::tzFormat() const { +SimpleDateFormat::tzFormat(UErrorCode &status) const { if (fTimeZoneFormat == NULL) { umtx_lock(&LOCK); { if (fTimeZoneFormat == NULL) { - UErrorCode status = U_ZERO_ERROR; TimeZoneFormat *tzfmt = TimeZoneFormat::createInstance(fLocale, status); if (U_FAILURE(status)) { return NULL; diff --git a/deps/icu-small/source/i18n/timezone.cpp b/deps/icu-small/source/i18n/timezone.cpp index e662bf7674b9f4..f7f45c7d3eeffd 100644 --- a/deps/icu-small/source/i18n/timezone.cpp +++ b/deps/icu-small/source/i18n/timezone.cpp @@ -739,8 +739,7 @@ class TZEnumeration : public StringEnumeration { len = mapLen; } - UBool getID(int32_t i) { - UErrorCode ec = U_ZERO_ERROR; + UBool getID(int32_t i, UErrorCode& ec) { int32_t idLen = 0; const UChar* id = NULL; UResourceBundle *top = ures_openDirect(0, kZONEINFO, &ec); @@ -930,7 +929,7 @@ class TZEnumeration : public StringEnumeration { virtual const UnicodeString* snext(UErrorCode& status) { if (U_SUCCESS(status) && map != NULL && pos < len) { - getID(map[pos]); + getID(map[pos], status); ++pos; return &unistr; } diff --git a/deps/icu-small/source/i18n/ucln_in.h b/deps/icu-small/source/i18n/ucln_in.h index 40a5c36d87a9f7..318eafc143c968 100644 --- a/deps/icu-small/source/i18n/ucln_in.h +++ b/deps/icu-small/source/i18n/ucln_in.h @@ -26,6 +26,7 @@ as the functions are suppose to be called. It's usually best to have child dependencies called first. */ typedef enum ECleanupI18NType { UCLN_I18N_START = -1, + UCLN_I18N_NUMBER_SKELETONS, UCLN_I18N_CURRENCY_SPACING, UCLN_I18N_SPOOF, UCLN_I18N_SPOOFDATA, diff --git a/deps/icu-small/source/i18n/unicode/compactdecimalformat.h b/deps/icu-small/source/i18n/unicode/compactdecimalformat.h index d682d2d0e74278..7dc92f610062f4 100644 --- a/deps/icu-small/source/i18n/unicode/compactdecimalformat.h +++ b/deps/icu-small/source/i18n/unicode/compactdecimalformat.h @@ -84,7 +84,7 @@ class U_I18N_API CompactDecimalFormat : public DecimalFormat { * Destructor. * @stable ICU 51 */ - virtual ~CompactDecimalFormat(); + ~CompactDecimalFormat() U_OVERRIDE; /** * Assignment operator. @@ -101,245 +101,21 @@ class U_I18N_API CompactDecimalFormat : public DecimalFormat { * @return a polymorphic copy of this CompactDecimalFormat. * @stable ICU 51 */ - virtual Format* clone() const; - - /** - * Return TRUE if the given Format objects are semantically equal. - * Objects of different subclasses are considered unequal. - * - * @param other the object to be compared with. - * @return TRUE if the given Format objects are semantically equal. - * @stable ICU 51 - */ - virtual UBool operator==(const Format& other) const; - + Format* clone() const U_OVERRIDE; using DecimalFormat::format; /** - * Format a double or long number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @stable ICU 51 - */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPosition& pos) const; - - /** - * Format a double or long number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @param status - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; - - /** - * Format a double or long number using base-10 representation. - * Currently sets status to U_UNSUPPORTED_ERROR. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * Can be NULL. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(double number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a long number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @stable ICU 56 - */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPosition& pos) const; - - /** - * Format a long number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; - - /** - * Format a long number using base-10 representation. - * Currently sets status to U_UNSUPPORTED_ERROR - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * Can be NULL. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(int32_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format an int64 number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. + * CompactDecimalFormat does not support parsing. This implementation + * does nothing. + * @param text Unused. + * @param result Does not change. + * @param parsePosition Does not change. + * @see Formattable * @stable ICU 51 */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPosition& pos) const; - - /** - * Format an int64 number using base-10 representation. - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode &status) const; - - /** - * Format an int64 number using base-10 representation. - * Currently sets status to U_UNSUPPORTED_ERROR - * - * @param number The value to be formatted. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * Can be NULL. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(int64_t number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR - * The syntax of the unformatted number is a "numeric string" - * as defined in the Decimal Arithmetic Specification, available at - * http://speleotrove.com/decimal - * - * @param number The unformatted number, as a string. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * Can be NULL. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(StringPiece number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR - * The number is a DigitList wrapper onto a floating point decimal number. - * The default implementation in NumberFormat converts the decimal number - * to a double and formats that. - * - * @param number The number, a DigitList format Decimal Floating Point. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param posIter On return, can be used to iterate over positions - * of fields generated by this format call. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(const DigitList &number, - UnicodeString& appendTo, - FieldPositionIterator* posIter, - UErrorCode& status) const; - - /** - * Format a decimal number. Currently sets status to U_UNSUPPORTED_ERROR. - * The number is a DigitList wrapper onto a floating point decimal number. - * The default implementation in NumberFormat converts the decimal number - * to a double and formats that. - * - * @param number The number, a DigitList format Decimal Floating Point. - * @param appendTo Output parameter to receive result. - * Result is appended to existing contents. - * @param pos On input: an alignment field, if desired. - * On output: the offsets of the alignment field. - * @param status Output param filled with success/failure status. - * @return Reference to 'appendTo' parameter. - * @internal - */ - virtual UnicodeString& format(const DigitList &number, - UnicodeString& appendTo, - FieldPosition& pos, - UErrorCode& status) const; - - /** - * CompactDecimalFormat does not support parsing. This implementation - * does nothing. - * @param text Unused. - * @param result Does not change. - * @param parsePosition Does not change. - * @see Formattable - * @stable ICU 51 - */ - virtual void parse(const UnicodeString& text, - Formattable& result, - ParsePosition& parsePosition) const; + void parse(const UnicodeString& text, Formattable& result, + ParsePosition& parsePosition) const U_OVERRIDE; /** * CompactDecimalFormat does not support parsing. This implementation @@ -350,10 +126,9 @@ class U_I18N_API CompactDecimalFormat : public DecimalFormat { * @param status Always set to U_UNSUPPORTED_ERROR. * @stable ICU 51 */ - virtual void parse(const UnicodeString& text, - Formattable& result, - UErrorCode& status) const; + void parse(const UnicodeString& text, Formattable& result, UErrorCode& status) const U_OVERRIDE; +#ifndef U_HIDE_INTERNAL_API /** * Parses text from the given string as a currency amount. Unlike * the parse() method, this method will attempt to parse a generic @@ -374,8 +149,8 @@ class U_I18N_API CompactDecimalFormat : public DecimalFormat { * the parsed currency; if parse fails, this is NULL. * @internal */ - virtual CurrencyAmount* parseCurrency(const UnicodeString& text, - ParsePosition& pos) const; + CurrencyAmount* parseCurrency(const UnicodeString& text, ParsePosition& pos) const U_OVERRIDE; +#endif /* U_HIDE_INTERNAL_API */ /** * Return the class ID for this class. This is useful only for @@ -401,18 +176,10 @@ class U_I18N_API CompactDecimalFormat : public DecimalFormat { * other classes have different class IDs. * @stable ICU 51 */ - virtual UClassID getDynamicClassID() const; - -private: - - const UHashtable* _unitsByVariant; - const double* _divisors; - PluralRules* _pluralRules; - - // Default constructor not implemented. - CompactDecimalFormat(const DecimalFormat &, const UHashtable* unitsByVariant, const double* divisors, PluralRules* pluralRules); + UClassID getDynamicClassID() const U_OVERRIDE; - UBool eqHelper(const CompactDecimalFormat& that) const; + private: + CompactDecimalFormat(const Locale& inLocale, UNumberCompactStyle style, UErrorCode& status); }; U_NAMESPACE_END diff --git a/deps/icu-small/source/i18n/unicode/currunit.h b/deps/icu-small/source/i18n/unicode/currunit.h index e7e0dc72da8292..d5bc4aa6d6d482 100644 --- a/deps/icu-small/source/i18n/unicode/currunit.h +++ b/deps/icu-small/source/i18n/unicode/currunit.h @@ -44,8 +44,9 @@ class U_I18N_API CurrencyUnit: public MeasureUnit { /** * Construct an object with the given ISO currency code. - * @param isoCode the 3-letter ISO 4217 currency code; must not be - * NULL and must have length 3 + * @param isoCode the 3-letter ISO 4217 currency code; must have + * length 3 and need not be NUL-terminated. If NULL, the currency + * is initialized to the unknown currency XXX. * @param ec input-output error code. If the isoCode is invalid, * then this will be set to a failing value. * @stable ICU 3.0 diff --git a/deps/icu-small/source/i18n/unicode/dcfmtsym.h b/deps/icu-small/source/i18n/unicode/dcfmtsym.h index e58befa31bda88..2f824cec3087f5 100644 --- a/deps/icu-small/source/i18n/unicode/dcfmtsym.h +++ b/deps/icu-small/source/i18n/unicode/dcfmtsym.h @@ -406,9 +406,12 @@ class U_I18N_API DecimalFormatSymbols : public UObject { * returning a const reference to one of the symbol strings. * The returned reference becomes invalid when the symbol is changed * or when the DecimalFormatSymbols are destroyed. - * ### TODO markus 2002oct11: Consider proposing getConstSymbol() to be really public. * Note: moved #ifndef U_HIDE_INTERNAL_API after this, since this is needed for inline in DecimalFormat * + * This is not currently stable API, but if you think it should be stable, + * post a comment on the following ticket and the ICU team will take a look: + * http://bugs.icu-project.org/trac/ticket/13580 + * * @param symbol Constant to indicate a number format symbol. * @return the format symbol by the param 'symbol' * @internal @@ -422,6 +425,10 @@ class U_I18N_API DecimalFormatSymbols : public UObject { * to accessing the symbol from getConstSymbol with the corresponding * key, such as kZeroDigitSymbol or kOneDigitSymbol. * + * This is not currently stable API, but if you think it should be stable, + * post a comment on the following ticket and the ICU team will take a look: + * http://bugs.icu-project.org/trac/ticket/13580 + * * @param digit The digit, an integer between 0 and 9 inclusive. * If outside the range 0 to 9, the zero digit is returned. * @return the format symbol for the given digit. diff --git a/deps/icu-small/source/i18n/unicode/decimfmt.h b/deps/icu-small/source/i18n/unicode/decimfmt.h index b062208d9b1f93..3747f510f79c79 100644 --- a/deps/icu-small/source/i18n/unicode/decimfmt.h +++ b/deps/icu-small/source/i18n/unicode/decimfmt.h @@ -43,28 +43,25 @@ #include "unicode/curramt.h" #include "unicode/enumset.h" -#ifndef U_HIDE_INTERNAL_API -/** - * \def UNUM_DECIMALFORMAT_INTERNAL_SIZE - * @internal - */ -#if UCONFIG_FORMAT_FASTPATHS_49 -#define UNUM_DECIMALFORMAT_INTERNAL_SIZE 16 -#endif -#endif /* U_HIDE_INTERNAL_API */ - U_NAMESPACE_BEGIN -class DigitList; class CurrencyPluralInfo; -class Hashtable; -class UnicodeSet; -class FieldPositionHandler; -class DecimalFormatStaticSets; -class FixedDecimal; -class DecimalFormatImpl; -class PluralRules; -class VisibleDigitsWithExponent; +class CompactDecimalFormat; + +namespace number { +class LocalizedNumberFormatter; +class FormattedNumber; +namespace impl { +class DecimalQuantity; +struct DecimalFormatFields; +} +} + +namespace numparse { +namespace impl { +class NumberParserImpl; +} +} // explicit template instantiation. see digitlst.h // (When building DLLs for Windows this is required.) @@ -672,17 +669,14 @@ template class U_I18N_API EnumSet "123", and "123" -> 1.23 * @stable ICU 2.0 @@ -1362,12 +1320,52 @@ class U_I18N_API DecimalFormat: public NumberFormat { * (For Arabic, use arabic percent symbol). * For a permill, set the suffixes to have "\\u2031" and the multiplier to be 1000. * + * This method only supports integer multipliers. To multiply by a non-integer, pair this + * method with setMultiplierScale(). + * * @param newValue the new value of the multiplier for use in percent, permill, etc. * Examples: with 100, 1.23 -> "123", and "123" -> 1.23 * @stable ICU 2.0 */ virtual void setMultiplier(int32_t newValue); +#ifndef U_HIDE_DRAFT_API + /** + * Gets the power of ten by which number should be multiplied before formatting, which + * can be combined with setMultiplier() to multiply by any arbitrary decimal value. + * + * A multiplier scale of 2 corresponds to multiplication by 100, and a multiplier scale + * of -2 corresponds to multiplication by 0.01. + * + * This method is analogous to UNUM_SCALE in getAttribute. + * + * @return the current value of the power-of-ten multiplier. + * @draft ICU 62 + */ + int32_t getMultiplierScale(void) const; +#endif /* U_HIDE_DRAFT_API */ + + /** + * Sets a power of ten by which number should be multiplied before formatting, which + * can be combined with setMultiplier() to multiply by any arbitrary decimal value. + * + * A multiplier scale of 2 corresponds to multiplication by 100, and a multiplier scale + * of -2 corresponds to multiplication by 0.01. + * + * For example, to multiply numbers by 0.5 before formatting, you can do: + * + *

+     * df.setMultiplier(5);
+     * df.setMultiplierScale(-1);
+     * 
+ * + * This method is analogous to UNUM_SCALE in setAttribute. + * + * @param newValue the new value of the power-of-ten multiplier. + * @draft ICU 62 + */ + virtual void setMultiplierScale(int32_t newValue); + /** * Get the rounding increment. * @return A positive rounding increment, or 0.0 if a custom rounding @@ -1400,7 +1398,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * @see #setRoundingMode * @stable ICU 2.0 */ - virtual ERoundingMode getRoundingMode(void) const; + virtual ERoundingMode getRoundingMode(void) const U_OVERRIDE; /** * Set the rounding mode. @@ -1410,7 +1408,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * @see #getRoundingMode * @stable ICU 2.0 */ - virtual void setRoundingMode(ERoundingMode roundingMode); + virtual void setRoundingMode(ERoundingMode roundingMode) U_OVERRIDE; /** * Get the width to which the output of format() is padded. @@ -1469,7 +1467,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * @see #setPadPosition * @stable ICU 2.0 */ - virtual void setPadCharacter(const UnicodeString &padChar); + virtual void setPadCharacter(const UnicodeString& padChar); /** * Get the position at which padding will take place. This is the location @@ -1676,7 +1674,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { #endif /* U_HIDE_INTERNAL_API */ - /* Cannot use #ifndef U_HIDE_INTERNAL_API for the following draft method since it is virtual. */ + /* Cannot use #ifndef U_HIDE_INTERNAL_API for the following draft method since it is virtual. */ /** * Sets the minimum grouping digits. Setting to a value less than or * equal to 1 turns off minimum grouping digits. @@ -1717,7 +1715,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { UBool isDecimalPatternMatchRequired(void) const; /** - * Allows you to set the behavior of the pattern decimal mark. + * Allows you to set the parse behavior of the pattern decimal mark. * * if TRUE, the input must have a decimal mark if one was specified in the pattern. When * FALSE the decimal mark may be omitted from the input. @@ -1727,6 +1725,60 @@ class U_I18N_API DecimalFormat: public NumberFormat { */ virtual void setDecimalPatternMatchRequired(UBool newValue); + /** + * {@icu} Returns whether to ignore exponents when parsing. + * + * @see #setParseNoExponent + * @internal This API is a technical preview. It may change in an upcoming release. + */ + virtual UBool isParseNoExponent() const; + + /** + * {@icu} Specifies whether to stop parsing when an exponent separator is encountered. For + * example, parses "123E4" to 123 (with parse position 3) instead of 1230000 (with parse position + * 5). + * + * @param value true to prevent exponents from being parsed; false to allow them to be parsed. + * @internal This API is a technical preview. It may change in an upcoming release. + */ + virtual void setParseNoExponent(UBool value); + + /** + * {@icu} Returns whether parsing is sensitive to case (lowercase/uppercase). + * + * @see #setParseCaseSensitive + * @internal This API is a technical preview. It may change in an upcoming release. + */ + virtual UBool isParseCaseSensitive() const; + + /** + * {@icu} Whether to pay attention to case when parsing; default is to ignore case (perform + * case-folding). For example, "A" == "a" in case-insensitive but not case-sensitive mode. + * + * Currency symbols are never case-folded. For example, "us$1.00" will not parse in case-insensitive + * mode, even though "US$1.00" parses. + * + * @internal This API is a technical preview. It may change in an upcoming release. + */ + virtual void setParseCaseSensitive(UBool value); + + /** + * {@icu} Returns whether truncation of high-order integer digits should result in an error. + * By default, setMaximumIntegerDigits truncates high-order digits silently. + * + * @see setFormatFailIfMoreThanMaxDigits + * @internal This API is a technical preview. It may change in an upcoming release. + */ + virtual UBool isFormatFailIfMoreThanMaxDigits() const; + + /** + * {@icu} Sets whether truncation of high-order integer digits should result in an error. + * By default, setMaximumIntegerDigits truncates high-order digits silently. + * + * @internal This API is a technical preview. It may change in an upcoming release. + */ + virtual void setFormatFailIfMoreThanMaxDigits(UBool value); + /** * Synthesizes a pattern string that represents the current state @@ -1781,9 +1833,8 @@ class U_I18N_API DecimalFormat: public NumberFormat { * set to a failure result. * @stable ICU 2.0 */ - virtual void applyPattern(const UnicodeString& pattern, - UParseError& parseError, - UErrorCode& status); + virtual void applyPattern(const UnicodeString& pattern, UParseError& parseError, UErrorCode& status); + /** * Sets the pattern. * @param pattern The pattern to be applied. @@ -1792,8 +1843,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * set to a failure result. * @stable ICU 2.0 */ - virtual void applyPattern(const UnicodeString& pattern, - UErrorCode& status); + virtual void applyPattern(const UnicodeString& pattern, UErrorCode& status); /** * Apply the given pattern to this Format object. The pattern @@ -1825,8 +1875,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * set to a failure result. * @stable ICU 2.0 */ - virtual void applyLocalizedPattern(const UnicodeString& pattern, - UParseError& parseError, + virtual void applyLocalizedPattern(const UnicodeString& pattern, UParseError& parseError, UErrorCode& status); /** @@ -1838,8 +1887,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * set to a failure result. * @stable ICU 2.0 */ - virtual void applyLocalizedPattern(const UnicodeString& pattern, - UErrorCode& status); + virtual void applyLocalizedPattern(const UnicodeString& pattern, UErrorCode& status); /** @@ -1851,7 +1899,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * @see NumberFormat#setMaximumIntegerDigits * @stable ICU 2.0 */ - virtual void setMaximumIntegerDigits(int32_t newValue); + void setMaximumIntegerDigits(int32_t newValue) U_OVERRIDE; /** * Sets the minimum number of digits allowed in the integer portion of a @@ -1862,7 +1910,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * @see NumberFormat#setMinimumIntegerDigits * @stable ICU 2.0 */ - virtual void setMinimumIntegerDigits(int32_t newValue); + void setMinimumIntegerDigits(int32_t newValue) U_OVERRIDE; /** * Sets the maximum number of digits allowed in the fraction portion of a @@ -1873,7 +1921,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * @see NumberFormat#setMaximumFractionDigits * @stable ICU 2.0 */ - virtual void setMaximumFractionDigits(int32_t newValue); + void setMaximumFractionDigits(int32_t newValue) U_OVERRIDE; /** * Sets the minimum number of digits allowed in the fraction portion of a @@ -1884,7 +1932,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * @see NumberFormat#setMinimumFractionDigits * @stable ICU 2.0 */ - virtual void setMinimumFractionDigits(int32_t newValue); + void setMinimumFractionDigits(int32_t newValue) U_OVERRIDE; /** * Returns the minimum number of significant digits that will be @@ -1947,7 +1995,6 @@ class U_I18N_API DecimalFormat: public NumberFormat { */ void setSignificantDigitsUsed(UBool useSignificantDigits); - public: /** * Sets the currency used to display currency * amounts. This takes effect immediately, if this format is a @@ -1960,7 +2007,7 @@ class U_I18N_API DecimalFormat: public NumberFormat { * @param ec input-output error code * @stable ICU 3.0 */ - virtual void setCurrency(const char16_t* theCurrency, UErrorCode& ec); + void setCurrency(const char16_t* theCurrency, UErrorCode& ec) U_OVERRIDE; /** * Sets the currency used to display currency amounts. See @@ -1984,78 +2031,53 @@ class U_I18N_API DecimalFormat: public NumberFormat { */ UCurrencyUsage getCurrencyUsage() const; - -#ifndef U_HIDE_DEPRECATED_API - /** - * The resource tags we use to retrieve decimal format data from - * locale resource bundles. - * @deprecated ICU 3.4. This string has no public purpose. Please don't use it. - */ - static const char fgNumberPatterns[]; -#endif // U_HIDE_DEPRECATED_API - #ifndef U_HIDE_INTERNAL_API - /** - * Get a FixedDecimal corresponding to a double as it would be - * formatted by this DecimalFormat. - * Internal, not intended for public use. - * @internal - */ - FixedDecimal getFixedDecimal(double number, UErrorCode &status) const; - - /** - * Get a FixedDecimal corresponding to a formattable as it would be - * formatted by this DecimalFormat. - * Internal, not intended for public use. - * @internal - */ - FixedDecimal getFixedDecimal(const Formattable &number, UErrorCode &status) const; /** - * Get a FixedDecimal corresponding to a DigitList as it would be - * formatted by this DecimalFormat. Note: the DigitList may be modified. + * Format a number and save it into the given DecimalQuantity. * Internal, not intended for public use. * @internal */ - FixedDecimal getFixedDecimal(DigitList &number, UErrorCode &status) const; + void formatToDecimalQuantity(double number, number::impl::DecimalQuantity& output, + UErrorCode& status) const; /** - * Get a VisibleDigitsWithExponent corresponding to a double - * as it would be formatted by this DecimalFormat. + * Get a DecimalQuantity corresponding to a formattable as it would be + * formatted by this DecimalFormat. * Internal, not intended for public use. * @internal */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - double number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; + void formatToDecimalQuantity(const Formattable& number, number::impl::DecimalQuantity& output, + UErrorCode& status) const; - /** - * Get a VisibleDigitsWithExponent corresponding to a formattable - * as it would be formatted by this DecimalFormat. - * Internal, not intended for public use. - * @internal - */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - const Formattable &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; +#endif +#ifndef U_HIDE_DRAFT_API /** - * Get a VisibleDigitsWithExponent corresponding to a DigitList - * as it would be formatted by this DecimalFormat. - * Note: the DigitList may be modified. - * Internal, not intended for public use. - * @internal + * Converts this DecimalFormat to a NumberFormatter. Starting in ICU 60, + * NumberFormatter is the recommended way to format numbers. + * + * NOTE: The returned LocalizedNumberFormatter is owned by this DecimalFormat. + * If a non-const method is called on the DecimalFormat, or if the DecimalFormat + * is deleted, the object becomes invalid. If you plan to keep the return value + * beyond the lifetime of the DecimalFormat, copy it to a local variable: + * + *
+     * LocalizedNumberFormatter f = df->toNumberFormatter();
+     * 
+ * + * It is, however, safe to use the return value for chaining: + * + *
+     * FormattedNumber result = df->toNumberFormatter().formatDouble(123, status);
+     * 
+ * + * @param output The variable into which to store the LocalizedNumberFormatter. + * @return The output variable, for chaining. + * @draft ICU 62 */ - VisibleDigitsWithExponent &initVisibleDigitsWithExponent( - DigitList &number, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - -#endif /* U_HIDE_INTERNAL_API */ - -public: + const number::LocalizedNumberFormatter& toNumberFormatter() const; +#endif /* U_HIDE_DRAFT_API */ /** * Return the class ID for this class. This is useful only for @@ -2081,217 +2103,57 @@ class U_I18N_API DecimalFormat: public NumberFormat { * other classes have different class IDs. * @stable ICU 2.0 */ - virtual UClassID getDynamicClassID(void) const; - -private: - - DecimalFormat(); // default constructor not implemented - - /** - * Initialize all fields of a new DecimalFormatter to a safe default value. - * Common code for use by constructors. - */ - void init(); - - /** - * Do real work of constructing a new DecimalFormat. - */ - void construct(UErrorCode& status, - UParseError& parseErr, - const UnicodeString* pattern = 0, - DecimalFormatSymbols* symbolsToAdopt = 0 - ); - - void handleCurrencySignInPattern(UErrorCode& status); - - void parse(const UnicodeString& text, - Formattable& result, - ParsePosition& pos, - char16_t* currency) const; - - enum { - fgStatusInfinite, - fgStatusLength // Leave last in list. - } StatusFlags; - - UBool subparse(const UnicodeString& text, - const UnicodeString* negPrefix, - const UnicodeString* negSuffix, - const UnicodeString* posPrefix, - const UnicodeString* posSuffix, - UBool complexCurrencyParsing, - int8_t type, - ParsePosition& parsePosition, - DigitList& digits, UBool* status, - char16_t* currency) const; - - // Mixed style parsing for currency. - // It parses against the current currency pattern - // using complex affix comparison - // parses against the currency plural patterns using complex affix comparison, - // and parses against the current pattern using simple affix comparison. - UBool parseForCurrency(const UnicodeString& text, - ParsePosition& parsePosition, - DigitList& digits, - UBool* status, - char16_t* currency) const; - - int32_t skipPadding(const UnicodeString& text, int32_t position) const; - - int32_t compareAffix(const UnicodeString& input, - int32_t pos, - UBool isNegative, - UBool isPrefix, - const UnicodeString* affixPat, - UBool complexCurrencyParsing, - int8_t type, - char16_t* currency) const; - - static UnicodeString& trimMarksFromAffix(const UnicodeString& affix, UnicodeString& trimmedAffix); - - UBool equalWithSignCompatibility(UChar32 lhs, UChar32 rhs) const; - - int32_t compareSimpleAffix(const UnicodeString& affix, - const UnicodeString& input, - int32_t pos, - UBool lenient) const; - - static int32_t skipPatternWhiteSpace(const UnicodeString& text, int32_t pos); - - static int32_t skipUWhiteSpace(const UnicodeString& text, int32_t pos); - - static int32_t skipUWhiteSpaceAndMarks(const UnicodeString& text, int32_t pos); - - static int32_t skipBidiMarks(const UnicodeString& text, int32_t pos); - - int32_t compareComplexAffix(const UnicodeString& affixPat, - const UnicodeString& input, - int32_t pos, - int8_t type, - char16_t* currency) const; - - static int32_t match(const UnicodeString& text, int32_t pos, UChar32 ch); + UClassID getDynamicClassID(void) const U_OVERRIDE; - static int32_t match(const UnicodeString& text, int32_t pos, const UnicodeString& str); + private: - static UBool matchSymbol(const UnicodeString &text, int32_t position, int32_t length, const UnicodeString &symbol, - UnicodeSet *sset, UChar32 schar); + /** Rebuilds the formatter object from the property bag. */ + void touch(UErrorCode& status); - static UBool matchDecimal(UChar32 symbolChar, - UBool sawDecimal, UChar32 sawDecimalChar, - const UnicodeSet *sset, UChar32 schar); - - static UBool matchGrouping(UChar32 groupingChar, - UBool sawGrouping, UChar32 sawGroupingChar, - const UnicodeSet *sset, - UChar32 decimalChar, const UnicodeSet *decimalSet, - UChar32 schar); - - // set up currency affix patterns for mix parsing. - // The patterns saved here are the affix patterns of default currency - // pattern and the unique affix patterns of the plural currency patterns. - // Those patterns are used by parseForCurrency(). - void setupCurrencyAffixPatterns(UErrorCode& status); - - // get the currency rounding with respect to currency usage - double getCurrencyRounding(const char16_t* currency, - UErrorCode* ec) const; - - // get the currency fraction with respect to currency usage - int getCurrencyFractionDigits(const char16_t* currency, - UErrorCode* ec) const; - - // hashtable operations - Hashtable* initHashForAffixPattern(UErrorCode& status); - - void deleteHashForAffixPattern(); - - void copyHashForAffixPattern(const Hashtable* source, - Hashtable* target, UErrorCode& status); - - DecimalFormatImpl *fImpl; + /** Rebuilds the formatter object, hiding the error code. */ + void touchNoError(); /** - * Constants. + * Updates the property bag with settings from the given pattern. + * + * @param pattern The pattern string to parse. + * @param ignoreRounding Whether to leave out rounding information (minFrac, maxFrac, and rounding + * increment) when parsing the pattern. This may be desirable if a custom rounding mode, such + * as CurrencyUsage, is to be used instead. One of {@link + * PatternStringParser#IGNORE_ROUNDING_ALWAYS}, {@link PatternStringParser#IGNORE_ROUNDING_IF_CURRENCY}, + * or {@link PatternStringParser#IGNORE_ROUNDING_NEVER}. + * @see PatternAndPropertyUtils#parseToExistingProperties */ + void setPropertiesFromPattern(const UnicodeString& pattern, int32_t ignoreRounding, + UErrorCode& status); + const numparse::impl::NumberParserImpl* getParser(UErrorCode& status) const; - EnumSet - fBoolFlags; + const numparse::impl::NumberParserImpl* getCurrencyParser(UErrorCode& status) const; + static void fieldPositionHelper(const number::FormattedNumber& formatted, FieldPosition& fieldPosition, + int32_t offset, UErrorCode& status); - // style is only valid when decimal formatter is constructed by - // DecimalFormat(pattern, decimalFormatSymbol, style) - int fStyle; + static void fieldPositionIteratorHelper(const number::FormattedNumber& formatted, + FieldPositionIterator* fpi, int32_t offset, UErrorCode& status); + void setupFastFormat(); - // Affix pattern set for currency. - // It is a set of AffixPatternsForCurrency, - // each element of the set saves the negative prefix pattern, - // negative suffix pattern, positive prefix pattern, - // and positive suffix pattern of a pattern. - // It is used for currency mixed style parsing. - // It is actually is a set. - // The set contains the default currency pattern from the locale, - // and the currency plural patterns. - // Since it is a set, it does not contain duplicated items. - // For example, if 2 currency plural patterns are the same, only one pattern - // is included in the set. When parsing, we do not check whether the plural - // count match or not. - Hashtable* fAffixPatternsForCurrency; + bool fastFormatDouble(double input, UnicodeString& output) const; - // Information needed for DecimalFormat to format/parse currency plural. - CurrencyPluralInfo* fCurrencyPluralInfo; + bool fastFormatInt64(int64_t input, UnicodeString& output) const; -#if UCONFIG_HAVE_PARSEALLINPUT - UNumberFormatAttributeValue fParseAllInput; -#endif + void doFastFormatInt32(int32_t input, bool isNegative, UnicodeString& output) const; - // Decimal Format Static Sets singleton. - const DecimalFormatStaticSets *fStaticSets; + //=====================================================================================// + // INSTANCE FIELDS // + //=====================================================================================// -protected: + // Only one instance field: keep all fields inside of an implementation class defined in number_mapper.h + number::impl::DecimalFormatFields* fields; -#ifndef U_HIDE_INTERNAL_API - /** - * Rounds a value according to the rules of this object. - * @internal - */ - DigitList& _round(const DigitList& number, DigitList& adjustedNum, UBool& isNegative, UErrorCode& status) const; -#endif /* U_HIDE_INTERNAL_API */ - - /** - * Returns the currency in effect for this formatter. Subclasses - * should override this method as needed. Unlike getCurrency(), - * this method should never return "". - * @result output parameter for null-terminated result, which must - * have a capacity of at least 4 - * @internal - */ - virtual void getEffectiveCurrency(char16_t* result, UErrorCode& ec) const; - - /** number of integer digits - * @stable ICU 2.4 - */ - static const int32_t kDoubleIntegerDigits; - /** number of fraction digits - * @stable ICU 2.4 - */ - static const int32_t kDoubleFractionDigits; - - /** - * When someone turns on scientific mode, we assume that more than this - * number of digits is due to flipping from some other mode that didn't - * restrict the maximum, and so we force 1 integer digit. We don't bother - * to track and see if someone is using exponential notation with more than - * this number, it wouldn't make sense anyway, and this is just to make sure - * that someone turning on scientific mode with default settings doesn't - * end up with lots of zeroes. - * @stable ICU 2.8 - */ - static const int32_t kMaxScientificIntegerDigits; + // Allow child class CompactDecimalFormat to access fProperties: + friend class CompactDecimalFormat; }; diff --git a/deps/icu-small/source/i18n/unicode/fmtable.h b/deps/icu-small/source/i18n/unicode/fmtable.h index 766a71969deadc..2359b61d46186e 100644 --- a/deps/icu-small/source/i18n/unicode/fmtable.h +++ b/deps/icu-small/source/i18n/unicode/fmtable.h @@ -33,17 +33,11 @@ U_NAMESPACE_BEGIN class CharString; -class DigitList; - -/** - * \def UNUM_INTERNAL_STACKARRAY_SIZE - * @internal - */ -#if U_PLATFORM == U_PF_OS400 -#define UNUM_INTERNAL_STACKARRAY_SIZE 144 -#else -#define UNUM_INTERNAL_STACKARRAY_SIZE 128 -#endif +namespace number { +namespace impl { +class DecimalQuantity; +} +} /** * Formattable objects can be passed to the Format class or @@ -649,24 +643,25 @@ class U_I18N_API Formattable : public UObject { * Internal function, do not use. * TODO: figure out how to make this be non-public. * NumberFormat::format(Formattable, ... - * needs to get at the DigitList, if it exists, for + * needs to get at the DecimalQuantity, if it exists, for * big decimal formatting. * @internal */ - DigitList *getDigitList() const { return fDecimalNum;} + number::impl::DecimalQuantity *getDecimalQuantity() const { return fDecimalQuantity;} /** - * @internal + * Export the value of this Formattable to a DecimalQuantity. + * @internal */ - DigitList *getInternalDigitList(); + void populateDecimalQuantity(number::impl::DecimalQuantity& output, UErrorCode& status) const; /** - * Adopt, and set value from, a DigitList + * Adopt, and set value from, a DecimalQuantity * Internal Function, do not use. - * @param dl the Digit List to be adopted + * @param dl the DecimalQuantity to be adopted * @internal */ - void adoptDigitList(DigitList *dl); + void adoptDecimalQuantity(number::impl::DecimalQuantity *dq); /** * Internal function to return the CharString pointer. @@ -706,9 +701,7 @@ class U_I18N_API Formattable : public UObject { CharString *fDecimalStr; - DigitList *fDecimalNum; - - char fStackData[UNUM_INTERNAL_STACKARRAY_SIZE]; // must be big enough for DigitList + number::impl::DecimalQuantity *fDecimalQuantity; Type fType; UnicodeString fBogus; // Bogus string when it's needed. diff --git a/deps/icu-small/source/i18n/unicode/fpositer.h b/deps/icu-small/source/i18n/unicode/fpositer.h index 8e9d69c547f849..81091f0ffad6b6 100644 --- a/deps/icu-small/source/i18n/unicode/fpositer.h +++ b/deps/icu-small/source/i18n/unicode/fpositer.h @@ -47,13 +47,6 @@ U_NAMESPACE_BEGIN class UVector32; -// Forward declaration for number formatting: -namespace number { -namespace impl { -class NumberStringBuilder; -} -} - /** * FieldPositionIterator returns the field ids and their start/limit positions generated * by a call to Format::format. See Format, NumberFormat, DecimalFormat. @@ -114,7 +107,6 @@ class U_I18N_API FieldPositionIterator : public UObject { void setData(UVector32 *adopt, UErrorCode& status); friend class FieldPositionIteratorHandler; - friend class number::impl::NumberStringBuilder; UVector32 *data; int32_t pos; diff --git a/deps/icu-small/source/i18n/unicode/measunit.h b/deps/icu-small/source/i18n/unicode/measunit.h index f7ddb4e20c559f..f552253544f890 100644 --- a/deps/icu-small/source/i18n/unicode/measunit.h +++ b/deps/icu-small/source/i18n/unicode/measunit.h @@ -400,14 +400,6 @@ class U_I18N_API MeasureUnit: public UObject { */ static MeasureUnit *createMilePerGallonImperial(UErrorCode &status); - /* - * The following were draft ICU 58, but have been withdrawn: - * static MeasureUnit *createEast(UErrorCode &status); - * static MeasureUnit *createNorth(UErrorCode &status); - * static MeasureUnit *createSouth(UErrorCode &status); - * static MeasureUnit *createWest(UErrorCode &status); - */ - /** * Returns unit of digital: bit. * Caller owns returned value and must free it. diff --git a/deps/icu-small/source/i18n/unicode/nounit.h b/deps/icu-small/source/i18n/unicode/nounit.h index 290e77e8806040..288f268d66d658 100644 --- a/deps/icu-small/source/i18n/unicode/nounit.h +++ b/deps/icu-small/source/i18n/unicode/nounit.h @@ -61,6 +61,12 @@ class U_I18N_API NoUnit: public MeasureUnit { */ NoUnit(const NoUnit& other); + /** + * Destructor. + * @draft ICU 60 + */ + virtual ~NoUnit(); + /** * Return a polymorphic clone of this object. The result will * have the same class as returned by getDynamicClassID(). @@ -86,12 +92,6 @@ class U_I18N_API NoUnit: public MeasureUnit { */ static UClassID U_EXPORT2 getStaticClassID(); - /** - * Destructor. - * @draft ICU 60 - */ - virtual ~NoUnit(); - private: /** * Constructor diff --git a/deps/icu-small/source/i18n/unicode/numberformatter.h b/deps/icu-small/source/i18n/unicode/numberformatter.h index 3fbb33cceeabf7..3ab08319f73bc7 100644 --- a/deps/icu-small/source/i18n/unicode/numberformatter.h +++ b/deps/icu-small/source/i18n/unicode/numberformatter.h @@ -17,6 +17,8 @@ #include "unicode/plurrule.h" #include "unicode/ucurr.h" #include "unicode/unum.h" +#include "unicode/unumberformatter.h" +#include "unicode/uobject.h" #ifndef U_HIDE_DRAFT_API @@ -31,11 +33,11 @@ * // Most basic usage: * NumberFormatter::withLocale(...).format(123).toString(); // 1,234 in en-US * - * // Custom notation, unit, and rounding strategy: + * // Custom notation, unit, and rounding precision: * NumberFormatter::with() * .notation(Notation::compactShort()) * .unit(CurrencyUnit("EUR", status)) - * .rounding(Rounder::maxDigits(2)) + * .precision(Precision::maxDigits(2)) * .locale(...) * .format(1234) * .toString(); // €1.2K in en-US @@ -43,7 +45,7 @@ * // Create a formatter in a singleton for use later: * static const LocalizedNumberFormatter formatter = NumberFormatter::withLocale(...) * .unit(NoUnit::percent()) - * .rounding(Rounder::fixedFraction(3)); + * .precision(Precision::fixedFraction(3)); * formatter.format(5.9831).toString(); // 5.983% in en-US * * // Create a "template" in a singleton but without setting a locale until the call site: @@ -63,7 +65,7 @@ * *
  * UnlocalizedNumberFormatter formatter = UnlocalizedNumberFormatter::with().notation(Notation::scientific());
- * formatter.rounding(Rounder.maxFraction(2)); // does nothing!
+ * formatter.precision(Precision.maxFraction(2)); // does nothing!
  * formatter.locale(Locale.getEnglish()).format(9.8765).toString(); // prints "9.8765E0", not "9.88E0"
  * 
* @@ -74,321 +76,23 @@ * @author Shane Carr */ -/** - * An enum declaring how to render units, including currencies. Example outputs when formatting 123 USD and 123 - * meters in en-CA: - * - *

- *

    - *
  • NARROW*: "$123.00" and "123 m" - *
  • SHORT: "US$ 123.00" and "123 m" - *
  • FULL_NAME: "123.00 US dollars" and "123 meters" - *
  • ISO_CODE: "USD 123.00" and undefined behavior - *
  • HIDDEN: "123.00" and "123" - *
- * - *

- * This enum is similar to {@link com.ibm.icu.text.MeasureFormat.FormatWidth}. - * - * @draft ICU 60 - */ -typedef enum UNumberUnitWidth { - /** - * Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest available - * abbreviation or symbol. This option can be used when the context hints at the identity of the unit. For more - * information on the difference between NARROW and SHORT, see SHORT. - * - *

- * In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤" placeholder for - * currencies. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_NARROW, - - /** - * Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or - * symbol when there may be ambiguity. This is the default behavior. - * - *

- * For example, in es-US, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form is "{0}°", - * since Fahrenheit is the customary unit for temperature in that locale. - * - *

- * In CLDR, this option corresponds to the "Short" format for measure units and the "¤" placeholder for - * currencies. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_SHORT, - - /** - * Print the full name of the unit, without any abbreviations. - * - *

- * In CLDR, this option corresponds to the default format for measure units and the "¤¤¤" placeholder for - * currencies. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_FULL_NAME, - - /** - * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this - * option is currently undefined for use with measure units. - * - *

- * In CLDR, this option corresponds to the "¤¤" placeholder for currencies. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_ISO_CODE, - - /** - * Format the number according to the specified unit, but do not display the unit. For currencies, apply - * monetary symbols and formats as with SHORT, but omit the currency symbol. For measure units, the behavior is - * equivalent to not specifying the unit at all. - * - * @draft ICU 60 - */ - UNUM_UNIT_WIDTH_HIDDEN - -#ifndef U_HIDE_INTERNAL_API - , - /** - * One more than the highest UNumberUnitWidth value. - * - * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. - */ - UNUM_UNIT_WIDTH_COUNT -#endif // U_HIDE_INTERNAL_API -} UNumberUnitWidth; - -/** - * An enum declaring the strategy for when and how to display grouping separators (i.e., the - * separator, often a comma or period, after every 2-3 powers of ten). The choices are several - * pre-built strategies for different use cases that employ locale data whenever possible. Example - * outputs for 1234 and 1234567 in en-IN: - * - *

    - *
  • OFF: 1234 and 12345 - *
  • MIN2: 1234 and 12,34,567 - *
  • AUTO: 1,234 and 12,34,567 - *
  • ON_ALIGNED: 1,234 and 12,34,567 - *
  • THOUSANDS: 1,234 and 1,234,567 - *
- * - *

- * The default is AUTO, which displays grouping separators unless the locale data says that grouping - * is not customary. To force grouping for all numbers greater than 1000 consistently across locales, - * use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2 - * or OFF. See the docs of each option for details. - * - *

- * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the - * grouping separator, use the "symbols" setter. - * - * @draft ICU 61 - */ -typedef enum UGroupingStrategy { - /** - * Do not display grouping separators in any locale. - * - * @draft ICU 61 - */ - UNUM_GROUPING_OFF, - - /** - * Display grouping using locale defaults, except do not show grouping on values smaller than - * 10000 (such that there is a minimum of two digits before the first separator). - * - *

- * Note that locales may restrict grouping separators to be displayed only on 1 million or - * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). - * - *

- * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @draft ICU 61 - */ - UNUM_GROUPING_MIN2, - - /** - * Display grouping using the default strategy for all locales. This is the default behavior. - * - *

- * Note that locales may restrict grouping separators to be displayed only on 1 million or - * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). - * - *

- * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @draft ICU 61 - */ - UNUM_GROUPING_AUTO, - - /** - * Always display the grouping separator on values of at least 1000. - * - *

- * This option ignores the locale data that restricts or disables grouping, described in MIN2 and - * AUTO. This option may be useful to normalize the alignment of numbers, such as in a - * spreadsheet. - * - *

- * Locale data is used to determine whether to separate larger numbers into groups of 2 - * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). - * - * @draft ICU 61 - */ - UNUM_GROUPING_ON_ALIGNED, - - /** - * Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use - * locale data for determining the grouping strategy. - * - * @draft ICU 61 - */ - UNUM_GROUPING_THOUSANDS - -} UGroupingStrategy; - -/** - * An enum declaring how to denote positive and negative numbers. Example outputs when formatting - * 123, 0, and -123 in en-US: - * - *

    - *
  • AUTO: "123", "0", and "-123" - *
  • ALWAYS: "+123", "+0", and "-123" - *
  • NEVER: "123", "0", and "123" - *
  • ACCOUNTING: "$123", "$0", and "($123)" - *
  • ACCOUNTING_ALWAYS: "+$123", "+$0", and "($123)" - *
  • EXCEPT_ZERO: "+123", "0", and "-123" - *
  • ACCOUNTING_EXCEPT_ZERO: "+$123", "$0", and "($123)" - *
- * - *

- * The exact format, including the position and the code point of the sign, differ by locale. - * - * @draft ICU 60 - */ -typedef enum UNumberSignDisplay { - /** - * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is the default - * behavior. - * - * @draft ICU 60 - */ - UNUM_SIGN_AUTO, - - /** - * Show the minus sign on negative numbers and the plus sign on positive numbers, including zero. - * To hide the sign on zero, see {@link UNUM_SIGN_EXCEPT_ZERO}. - * - * @draft ICU 60 - */ - UNUM_SIGN_ALWAYS, - - /** - * Do not show the sign on positive or negative numbers. - * - * @draft ICU 60 - */ - UNUM_SIGN_NEVER, - - /** - * Use the locale-dependent accounting format on negative numbers, and do not show the sign on positive numbers. - * - *

- * The accounting format is defined in CLDR and varies by locale; in many Western locales, the format is a pair - * of parentheses around the number. - * - *

- * Note: Since CLDR defines the accounting format in the monetary context only, this option falls back to the - * AUTO sign display strategy when formatting without a currency unit. This limitation may be lifted in the - * future. - * - * @draft ICU 60 - */ - UNUM_SIGN_ACCOUNTING, - - /** - * Use the locale-dependent accounting format on negative numbers, and show the plus sign on - * positive numbers, including zero. For more information on the accounting format, see the - * ACCOUNTING sign display strategy. To hide the sign on zero, see - * {@link UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO}. - * - * @draft ICU 60 - */ - UNUM_SIGN_ACCOUNTING_ALWAYS, - - /** - * Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a - * sign on zero. - * - * @draft ICU 61 - */ - UNUM_SIGN_EXCEPT_ZERO, - - /** - * Use the locale-dependent accounting format on negative numbers, and show the plus sign on - * positive numbers. Do not show a sign on zero. For more information on the accounting format, - * see the ACCOUNTING sign display strategy. - * - * @draft ICU 61 - */ - UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO +U_NAMESPACE_BEGIN -#ifndef U_HIDE_INTERNAL_API - , - /** - * One more than the highest UNumberSignDisplay value. - * - * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. - */ - UNUM_SIGN_COUNT -#endif // U_HIDE_INTERNAL_API -} UNumberSignDisplay; +// Forward declarations: +class IFixedDecimal; +class FieldPositionIteratorHandler; -/** - * An enum declaring how to render the decimal separator. - * - *

- *

    - *
  • UNUM_DECIMAL_SEPARATOR_AUTO: "1", "1.1" - *
  • UNUM_DECIMAL_SEPARATOR_ALWAYS: "1.", "1.1" - *
- */ -typedef enum UNumberDecimalSeparatorDisplay { - /** - * Show the decimal separator when there are one or more digits to display after the separator, and do not show - * it otherwise. This is the default behavior. - * - * @draft ICU 60 - */ - UNUM_DECIMAL_SEPARATOR_AUTO, +namespace numparse { +namespace impl { - /** - * Always show the decimal separator, even if there are no digits to display after the separator. - * - * @draft ICU 60 - */ - UNUM_DECIMAL_SEPARATOR_ALWAYS +// Forward declarations: +class NumberParserImpl; +class MultiplierParseHandler; -#ifndef U_HIDE_INTERNAL_API - , - /** - * One more than the highest UNumberDecimalSeparatorDisplay value. - * - * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. - */ - UNUM_DECIMAL_SEPARATOR_COUNT -#endif // U_HIDE_INTERNAL_API -} UNumberDecimalMarkDisplay; +} +} -U_NAMESPACE_BEGIN namespace number { // icu::number +namespace number { // icu::number // Forward declarations: class UnlocalizedNumberFormatter; @@ -396,15 +100,14 @@ class LocalizedNumberFormatter; class FormattedNumber; class Notation; class ScientificNotation; -class Rounder; -class FractionRounder; -class CurrencyRounder; -class IncrementRounder; +class Precision; +class FractionPrecision; +class CurrencyPrecision; +class IncrementPrecision; class IntegerWidth; namespace impl { -#ifndef U_HIDE_INTERNAL_API /** * Datatype for minimum/maximum fraction digits. Must be able to hold kMaxIntFracSig. * @@ -419,24 +122,28 @@ typedef int16_t digits_t; * @internal */ static constexpr int32_t DEFAULT_THRESHOLD = 3; -#endif // U_HIDE_INTERNAL_API // Forward declarations: class Padder; struct MacroProps; struct MicroProps; class DecimalQuantity; -struct NumberFormatterResults; +struct UFormattedNumberData; class NumberFormatterImpl; struct ParsedPatternInfo; class ScientificModifier; class MultiplierProducer; -class MutablePatternModifier; -class LongNameHandler; +class RoundingImpl; class ScientificHandler; -class CompactHandler; class Modifier; class NumberStringBuilder; +class AffixPatternProvider; +class NumberPropertyMapper; +struct DecimalFormatProperties; +class MultiplierFormatHandler; +class CurrencySymbols; +class GeneratorHelpers; +class DecNum; } // namespace impl @@ -530,13 +237,13 @@ class U_I18N_API Notation : public UMemory { * * *

- * When compact notation is specified without an explicit rounding strategy, numbers are rounded off to the closest + * When compact notation is specified without an explicit rounding precision, numbers are rounded off to the closest * integer after scaling the number by the corresponding power of 10, but with a digit shown after the decimal - * separator if there is only one digit before the decimal separator. The default compact notation rounding strategy + * separator if there is only one digit before the decimal separator. The default compact notation rounding precision * is equivalent to: * *

-     * Rounder.integer().withMinDigits(2)
+     * Precision::integer().withMinDigits(2)
      * 
* * @return A CompactNotation for passing to the NumberFormatter notation() setter. @@ -641,6 +348,9 @@ class U_I18N_API Notation : public UMemory { friend class impl::NumberFormatterImpl; friend class impl::ScientificModifier; friend class impl::ScientificHandler; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; /** @@ -687,21 +397,36 @@ class U_I18N_API ScientificNotation : public Notation { // Inherit constructor using Notation::Notation; + // Raw constructor for NumberPropertyMapper + ScientificNotation(int8_t fEngineeringInterval, bool fRequireMinInt, impl::digits_t fMinExponentDigits, + UNumberSignDisplay fExponentSignDisplay); + friend class Notation; + + // So that NumberPropertyMapper can create instances + friend class impl::NumberPropertyMapper; }; // Reserve extra names in case they are added as classes in the future: -typedef Rounder DigitRounder; +typedef Precision SignificantDigitsPrecision; + +// Typedefs for ICU 60/61 compatibility. +// These will be removed in ICU 64. +// See http://bugs.icu-project.org/trac/ticket/13746 +typedef Precision Rounder; +typedef FractionPrecision FractionRounder; +typedef IncrementPrecision IncrementRounder; +typedef CurrencyPrecision CurrencyRounder; /** - * A class that defines the rounding strategy to be used when formatting numbers in NumberFormatter. + * A class that defines the rounding precision to be used when formatting numbers in NumberFormatter. * *

- * To create a Rounder, use one of the factory methods. + * To create a Precision, use one of the factory methods. * * @draft ICU 60 */ -class U_I18N_API Rounder : public UMemory { +class U_I18N_API Precision : public UMemory { public: /** @@ -717,18 +442,18 @@ class U_I18N_API Rounder : public UMemory { *

* http://www.serpentine.com/blog/2011/06/29/here-be-dragons-advances-in-problems-you-didnt-even-know-you-had/ * - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A Precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static Rounder unlimited(); + static Precision unlimited(); /** * Show numbers rounded if necessary to the nearest integer. * - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder integer(); + static FractionPrecision integer(); /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator). @@ -754,10 +479,10 @@ class U_I18N_API Rounder : public UMemory { * @param minMaxFractionPlaces * The minimum and maximum number of numerals to display after the decimal separator (rounding if too * long or padding with zeros if too short). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder fixedFraction(int32_t minMaxFractionPlaces); + static FractionPrecision fixedFraction(int32_t minMaxFractionPlaces); /** * Always show at least a certain number of fraction places after the decimal separator, padding with zeros if @@ -769,10 +494,10 @@ class U_I18N_API Rounder : public UMemory { * @param minFractionPlaces * The minimum number of numerals to display after the decimal separator (padding with zeros if * necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder minFraction(int32_t minFractionPlaces); + static FractionPrecision minFraction(int32_t minFractionPlaces); /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator). @@ -781,10 +506,10 @@ class U_I18N_API Rounder : public UMemory { * * @param maxFractionPlaces * The maximum number of numerals to display after the decimal mark (rounding if necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder maxFraction(int32_t maxFractionPlaces); + static FractionPrecision maxFraction(int32_t maxFractionPlaces); /** * Show numbers rounded if necessary to a certain number of fraction places (numerals after the decimal separator); @@ -796,10 +521,10 @@ class U_I18N_API Rounder : public UMemory { * necessary). * @param maxFractionPlaces * The maximum number of numerals to display after the decimal separator (rounding if necessary). - * @return A FractionRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A FractionPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static FractionRounder minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces); + static FractionPrecision minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces); /** * Show numbers rounded if necessary to a certain number of significant digits or significant figures. Additionally, @@ -811,10 +536,10 @@ class U_I18N_API Rounder : public UMemory { * @param minMaxSignificantDigits * The minimum and maximum number of significant digits to display (rounding if too long or padding with * zeros if too short). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A precision for chaining or passing to the NumberFormatter precision() setter. + * @draft ICU 62 */ - static DigitRounder fixedDigits(int32_t minMaxSignificantDigits); + static SignificantDigitsPrecision fixedSignificantDigits(int32_t minMaxSignificantDigits); /** * Always show at least a certain number of significant digits/figures, padding with zeros if necessary. Do not @@ -825,20 +550,20 @@ class U_I18N_API Rounder : public UMemory { * * @param minSignificantDigits * The minimum number of significant digits to display (padding with zeros if too short). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A precision for chaining or passing to the NumberFormatter precision() setter. + * @draft ICU 62 */ - static DigitRounder minDigits(int32_t minSignificantDigits); + static SignificantDigitsPrecision minSignificantDigits(int32_t minSignificantDigits); /** * Show numbers rounded if necessary to a certain number of significant digits/figures. * * @param maxSignificantDigits * The maximum number of significant digits to display (rounding if too long). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A precision for chaining or passing to the NumberFormatter precision() setter. + * @draft ICU 62 */ - static DigitRounder maxDigits(int32_t maxSignificantDigits); + static SignificantDigitsPrecision maxSignificantDigits(int32_t maxSignificantDigits); /** * Show numbers rounded if necessary to a certain number of significant digits/figures; in addition, always show at @@ -848,10 +573,36 @@ class U_I18N_API Rounder : public UMemory { * The minimum number of significant digits to display (padding with zeros if necessary). * @param maxSignificantDigits * The maximum number of significant digits to display (rounding if necessary). - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A precision for chaining or passing to the NumberFormatter precision() setter. + * @draft ICU 62 */ - static DigitRounder minMaxDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits); + static SignificantDigitsPrecision minMaxSignificantDigits(int32_t minSignificantDigits, + int32_t maxSignificantDigits); + +#ifndef U_HIDE_DEPRECATED_API + // Compatiblity methods that will be removed in ICU 64. + // See http://bugs.icu-project.org/trac/ticket/13746 + + /** @deprecated ICU 62 */ + static inline SignificantDigitsPrecision fixedDigits(int32_t a) { + return fixedSignificantDigits(a); + } + + /** @deprecated ICU 62 */ + static inline SignificantDigitsPrecision minDigits(int32_t a) { + return minSignificantDigits(a); + } + + /** @deprecated ICU 62 */ + static inline SignificantDigitsPrecision maxDigits(int32_t a) { + return maxSignificantDigits(a); + } + + /** @deprecated ICU 62 */ + static inline SignificantDigitsPrecision minMaxDigits(int32_t a, int32_t b) { + return minMaxSignificantDigits(a, b); + } +#endif /* U_HIDE_DEPRECATED_API */ /** * Show numbers rounded if necessary to the closest multiple of a certain rounding increment. For example, if the @@ -864,20 +615,21 @@ class U_I18N_API Rounder : public UMemory { * decimal separator (to display 1.2 as "1.00" and 1.3 as "1.50"), you can run: * *

-     * Rounder::increment(0.5).withMinFraction(2)
+     * Precision::increment(0.5).withMinFraction(2)
      * 
* * @param roundingIncrement * The increment to which to round numbers. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static IncrementRounder increment(double roundingIncrement); + static IncrementPrecision increment(double roundingIncrement); /** - * Show numbers rounded and padded according to the rules for the currency unit. The most common rounding settings - * for currencies include Rounder.fixedFraction(2), Rounder.integer(), and - * Rounder.increment(0.05) for cash transactions ("nickel rounding"). + * Show numbers rounded and padded according to the rules for the currency unit. The most common + * rounding precision settings for currencies include Precision::fixedFraction(2), + * Precision::integer(), and Precision::increment(0.05) for cash transactions + * ("nickel rounding"). * *

* The exact rounding details will be resolved at runtime based on the currency unit specified in the @@ -887,24 +639,28 @@ class U_I18N_API Rounder : public UMemory { * @param currencyUsage * Either STANDARD (for digital transactions) or CASH (for transactions where the rounding increment may * be limited by the available denominations of cash or coins). - * @return A CurrencyRounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A CurrencyPrecision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - static CurrencyRounder currency(UCurrencyUsage currencyUsage); + static CurrencyPrecision currency(UCurrencyUsage currencyUsage); +#ifndef U_HIDE_DEPRECATED_API /** * Sets the rounding mode to use when picking the direction to round (up or down). Common values * include HALF_EVEN, HALF_UP, and FLOOR. The default is HALF_EVEN. * * @param roundingMode * The RoundingMode to use. - * @return A Rounder for passing to the NumberFormatter rounding() setter. - * @draft ICU 60 + * @return A Precision for passing to the NumberFormatter precision() setter. + * @deprecated ICU 62 Use the top-level roundingMode() setting instead. + * This method will be removed in ICU 64. + * See http://bugs.icu-project.org/trac/ticket/13746 */ - Rounder withMode(UNumberFormatRoundingMode roundingMode) const; + Precision withMode(UNumberFormatRoundingMode roundingMode) const; +#endif /* U_HIDE_DEPRECATED_API */ private: - enum RounderType { + enum PrecisionType { RND_BOGUS, RND_NONE, RND_FRACTION, @@ -912,11 +668,10 @@ class U_I18N_API Rounder : public UMemory { RND_FRACTION_SIGNIFICANT, RND_INCREMENT, RND_CURRENCY, - RND_PASS_THROUGH, RND_ERROR } fType; - union RounderUnion { + union PrecisionUnion { struct FractionSignificantSettings { // For RND_FRACTION, RND_SIGNIFICANT, and RND_FRACTION_SIGNIFICANT impl::digits_t fMinFrac; @@ -927,24 +682,27 @@ class U_I18N_API Rounder : public UMemory { struct IncrementSettings { double fIncrement; impl::digits_t fMinFrac; + impl::digits_t fMaxFrac; } increment; // For RND_INCREMENT UCurrencyUsage currencyUsage; // For RND_CURRENCY UErrorCode errorCode; // For RND_ERROR } fUnion; - typedef RounderUnion::FractionSignificantSettings FractionSignificantSettings; - typedef RounderUnion::IncrementSettings IncrementSettings; + typedef PrecisionUnion::FractionSignificantSettings FractionSignificantSettings; + typedef PrecisionUnion::IncrementSettings IncrementSettings; + /** The Precision encapsulates the RoundingMode when used within the implementation. */ UNumberFormatRoundingMode fRoundingMode; - Rounder(const RounderType &type, const RounderUnion &union_, UNumberFormatRoundingMode roundingMode) + Precision(const PrecisionType& type, const PrecisionUnion& union_, + UNumberFormatRoundingMode roundingMode) : fType(type), fUnion(union_), fRoundingMode(roundingMode) {} - Rounder(UErrorCode errorCode) : fType(RND_ERROR) { + Precision(UErrorCode errorCode) : fType(RND_ERROR) { fUnion.errorCode = errorCode; } - Rounder() : fType(RND_BOGUS) {} + Precision() : fType(RND_BOGUS) {} bool isBogus() const { return fType == RND_BOGUS; @@ -958,47 +716,21 @@ class U_I18N_API Rounder : public UMemory { return FALSE; } - // On the parent type so that this method can be called internally on Rounder instances. - Rounder withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const; - - /** NON-CONST: mutates the current instance. */ - void setLocaleData(const CurrencyUnit ¤cy, UErrorCode &status); - - void apply(impl::DecimalQuantity &value, UErrorCode &status) const; - - /** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */ - void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status); - - /** - * Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude - * adjustment), applies the adjustment, rounds, and returns the chosen multiplier. - * - *

- * In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we - * need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you - * guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then - * change your multiplier to be -6, and you get 1.0E6, which is correct. - * - * @param input The quantity to process. - * @param producer Function to call to return a multiplier based on a magnitude. - * @return The number of orders of magnitude the input was adjusted by this method. - */ - int32_t - chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer, - UErrorCode &status); + // On the parent type so that this method can be called internally on Precision instances. + Precision withCurrency(const CurrencyUnit ¤cy, UErrorCode &status) const; - static FractionRounder constructFraction(int32_t minFrac, int32_t maxFrac); + static FractionPrecision constructFraction(int32_t minFrac, int32_t maxFrac); - static Rounder constructSignificant(int32_t minSig, int32_t maxSig); + static Precision constructSignificant(int32_t minSig, int32_t maxSig); - static Rounder - constructFractionSignificant(const FractionRounder &base, int32_t minSig, int32_t maxSig); + static Precision + constructFractionSignificant(const FractionPrecision &base, int32_t minSig, int32_t maxSig); - static IncrementRounder constructIncrement(double increment, int32_t minFrac); + static IncrementPrecision constructIncrement(double increment, int32_t minFrac); - static CurrencyRounder constructCurrency(UCurrencyUsage usage); + static CurrencyPrecision constructCurrency(UCurrencyUsage usage); - static Rounder constructPassThrough(); + static Precision constructPassThrough(); // To allow MacroProps/MicroProps to initialize bogus instances: friend struct impl::MacroProps; @@ -1007,28 +739,31 @@ class U_I18N_API Rounder : public UMemory { // To allow NumberFormatterImpl to access isBogus() and other internal methods: friend class impl::NumberFormatterImpl; - // To give access to apply() and chooseMultiplierAndApply(): - friend class impl::MutablePatternModifier; - friend class impl::LongNameHandler; - friend class impl::ScientificHandler; - friend class impl::CompactHandler; + // To allow NumberPropertyMapper to create instances from DecimalFormatProperties: + friend class impl::NumberPropertyMapper; + + // To allow access to the main implementation class: + friend class impl::RoundingImpl; // To allow child classes to call private methods: - friend class FractionRounder; - friend class CurrencyRounder; - friend class IncrementRounder; + friend class FractionPrecision; + friend class CurrencyPrecision; + friend class IncrementPrecision; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; /** - * A class that defines a rounding strategy based on a number of fraction places and optionally significant digits to be + * A class that defines a rounding precision based on a number of fraction places and optionally significant digits to be * used when formatting numbers in NumberFormatter. * *

- * To create a FractionRounder, use one of the factory methods on Rounder. + * To create a FractionPrecision, use one of the factory methods on Precision. * * @draft ICU 60 */ -class U_I18N_API FractionRounder : public Rounder { +class U_I18N_API FractionPrecision : public Precision { public: /** * Ensure that no less than this number of significant digits are retained when rounding according to fraction @@ -1043,10 +778,10 @@ class U_I18N_API FractionRounder : public Rounder { * * @param minSignificantDigits * The number of significant figures to guarantee. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - Rounder withMinDigits(int32_t minSignificantDigits) const; + Precision withMinDigits(int32_t minSignificantDigits) const; /** * Ensure that no more than this number of significant digits are retained when rounding according to fraction @@ -1062,36 +797,36 @@ class U_I18N_API FractionRounder : public Rounder { * * @param maxSignificantDigits * Round the number to no more than this number of significant figures. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - Rounder withMaxDigits(int32_t maxSignificantDigits) const; + Precision withMaxDigits(int32_t maxSignificantDigits) const; private: // Inherit constructor - using Rounder::Rounder; + using Precision::Precision; // To allow parent class to call this class's constructor: - friend class Rounder; + friend class Precision; }; /** - * A class that defines a rounding strategy parameterized by a currency to be used when formatting numbers in + * A class that defines a rounding precision parameterized by a currency to be used when formatting numbers in * NumberFormatter. * *

- * To create a CurrencyRounder, use one of the factory methods on Rounder. + * To create a CurrencyPrecision, use one of the factory methods on Precision. * * @draft ICU 60 */ -class U_I18N_API CurrencyRounder : public Rounder { +class U_I18N_API CurrencyPrecision : public Precision { public: /** - * Associates a currency with this rounding strategy. + * Associates a currency with this rounding precision. * *

* Calling this method is not required, because the currency specified in unit() - * is automatically applied to currency rounding strategies. However, + * is automatically applied to currency rounding precisions. However, * this method enables you to override that automatic association. * *

@@ -1099,30 +834,30 @@ class U_I18N_API CurrencyRounder : public Rounder { * currency format. * * @param currency - * The currency to associate with this rounding strategy. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * The currency to associate with this rounding precision. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - Rounder withCurrency(const CurrencyUnit ¤cy) const; + Precision withCurrency(const CurrencyUnit ¤cy) const; private: // Inherit constructor - using Rounder::Rounder; + using Precision::Precision; // To allow parent class to call this class's constructor: - friend class Rounder; + friend class Precision; }; /** - * A class that defines a rounding strategy parameterized by a rounding increment to be used when formatting numbers in + * A class that defines a rounding precision parameterized by a rounding increment to be used when formatting numbers in * NumberFormatter. * *

- * To create an IncrementRounder, use one of the factory methods on Rounder. + * To create an IncrementPrecision, use one of the factory methods on Precision. * * @draft ICU 60 */ -class U_I18N_API IncrementRounder : public Rounder { +class U_I18N_API IncrementPrecision : public Precision { public: /** * Specifies the minimum number of fraction digits to render after the decimal separator, padding with zeros if @@ -1136,17 +871,17 @@ class U_I18N_API IncrementRounder : public Rounder { * Note: In ICU4J, this functionality is accomplished via the scale of the BigDecimal rounding increment. * * @param minFrac The minimum number of digits after the decimal separator. - * @return A Rounder for chaining or passing to the NumberFormatter rounding() setter. + * @return A precision for chaining or passing to the NumberFormatter precision() setter. * @draft ICU 60 */ - Rounder withMinFraction(int32_t minFrac) const; + Precision withMinFraction(int32_t minFrac) const; private: // Inherit constructor - using Rounder::Rounder; + using Precision::Precision; // To allow parent class to call this class's constructor: - friend class Rounder; + friend class Precision; }; /** @@ -1170,7 +905,6 @@ class U_I18N_API IntegerWidth : public UMemory { * The minimum number of places before the decimal separator. * @return An IntegerWidth for chaining or passing to the NumberFormatter integerWidth() setter. * @draft ICU 60 - * @see NumberFormatter */ static IntegerWidth zeroFillTo(int32_t minInt); @@ -1184,7 +918,6 @@ class U_I18N_API IntegerWidth : public UMemory { * truncation. * @return An IntegerWidth for passing to the NumberFormatter integerWidth() setter. * @draft ICU 60 - * @see NumberFormatter */ IntegerWidth truncateAt(int32_t maxInt); @@ -1193,12 +926,13 @@ class U_I18N_API IntegerWidth : public UMemory { struct { impl::digits_t fMinInt; impl::digits_t fMaxInt; + bool fFormatFailIfMoreThanMaxDigits; } minMaxInt; UErrorCode errorCode; } fUnion; bool fHasError = false; - IntegerWidth(impl::digits_t minInt, impl::digits_t maxInt); + IntegerWidth(impl::digits_t minInt, impl::digits_t maxInt, bool formatFailIfMoreThanMaxDigits); IntegerWidth(UErrorCode errorCode) { // NOLINT fUnion.errorCode = errorCode; @@ -1209,6 +943,11 @@ class U_I18N_API IntegerWidth : public UMemory { fUnion.minMaxInt.fMinInt = -1; } + /** Returns the default instance. */ + static IntegerWidth standard() { + return IntegerWidth::zeroFillTo(1); + } + bool isBogus() const { return !fHasError && fUnion.minMaxInt.fMinInt == -1; } @@ -1223,69 +962,214 @@ class U_I18N_API IntegerWidth : public UMemory { void apply(impl::DecimalQuantity &quantity, UErrorCode &status) const; + bool operator==(const IntegerWidth& other) const; + // To allow MacroProps/MicroProps to initialize empty instances: friend struct impl::MacroProps; friend struct impl::MicroProps; // To allow NumberFormatterImpl to access isBogus() and perform other operations: friend class impl::NumberFormatterImpl; -}; - -namespace impl { - -// Do not enclose entire SymbolsWrapper with #ifndef U_HIDE_INTERNAL_API, needed for a protected field -/** @internal */ -class U_I18N_API SymbolsWrapper : public UMemory { - public: - /** @internal */ - SymbolsWrapper() : fType(SYMPTR_NONE), fPtr{nullptr} {} - /** @internal */ - SymbolsWrapper(const SymbolsWrapper &other); - - /** @internal */ - ~SymbolsWrapper(); + // So that NumberPropertyMapper can create instances + friend class impl::NumberPropertyMapper; - /** @internal */ - SymbolsWrapper &operator=(const SymbolsWrapper &other); + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; +}; -#ifndef U_HIDE_INTERNAL_API +/** + * A class that defines a quantity by which a number should be multiplied when formatting. + * + *

+ * To create a Scale, use one of the factory methods. + * + * @draft ICU 62 + */ +class U_I18N_API Scale : public UMemory { + public: /** - * The provided object is copied, but we do not adopt it. - * @internal + * Do not change the value of numbers when formatting or parsing. + * + * @return A Scale to prevent any multiplication. + * @draft ICU 62 */ - void setTo(const DecimalFormatSymbols &dfs); + static Scale none(); /** - * Adopt the provided object. - * @internal + * Multiply numbers by a power of ten before formatting. Useful for combining with a percent unit: + * + *

+     * NumberFormatter::with().unit(NoUnit::percent()).multiplier(Scale::powerOfTen(2))
+     * 
+ * + * @return A Scale for passing to the setter in NumberFormatter. + * @draft ICU 62 */ - void setTo(const NumberingSystem *ns); + static Scale powerOfTen(int32_t power); /** - * Whether the object is currently holding a DecimalFormatSymbols. - * @internal + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + * + * This method takes a string in a decimal number format with syntax + * as defined in the Decimal Arithmetic Specification, available at + * http://speleotrove.com/decimal + * + * Also see the version of this method that takes a double. + * + * @return A Scale for passing to the setter in NumberFormatter. + * @draft ICU 62 */ - bool isDecimalFormatSymbols() const; + static Scale byDecimal(StringPiece multiplicand); /** - * Whether the object is currently holding a NumberingSystem. - * @internal + * Multiply numbers by an arbitrary value before formatting. Useful for unit conversions. + * + * This method takes a double; also see the version of this method that takes an exact decimal. + * + * @return A Scale for passing to the setter in NumberFormatter. + * @draft ICU 62 */ - bool isNumberingSystem() const; + static Scale byDouble(double multiplicand); /** - * Get the DecimalFormatSymbols pointer. No ownership change. - * @internal + * Multiply a number by both a power of ten and by an arbitrary double value. + * + * @return A Scale for passing to the setter in NumberFormatter. + * @draft ICU 62 */ - const DecimalFormatSymbols *getDecimalFormatSymbols() const; + static Scale byDoubleAndPowerOfTen(double multiplicand, int32_t power); - /** + // We need a custom destructor for the DecNum, which means we need to declare + // the copy/move constructor/assignment quartet. + + /** @draft ICU 62 */ + Scale(const Scale& other); + + /** @draft ICU 62 */ + Scale& operator=(const Scale& other); + + /** @draft ICU 62 */ + Scale(Scale&& src) U_NOEXCEPT; + + /** @draft ICU 62 */ + Scale& operator=(Scale&& src) U_NOEXCEPT; + + /** @draft ICU 62 */ + ~Scale(); + +#ifndef U_HIDE_INTERNAL_API + /** @internal */ + Scale(int32_t magnitude, impl::DecNum* arbitraryToAdopt); +#endif /* U_HIDE_INTERNAL_API */ + + private: + int32_t fMagnitude; + impl::DecNum* fArbitrary; + UErrorCode fError; + + Scale(UErrorCode error) : fMagnitude(0), fArbitrary(nullptr), fError(error) {} + + Scale() : fMagnitude(0), fArbitrary(nullptr), fError(U_ZERO_ERROR) {} + + bool isValid() const { + return fMagnitude != 0 || fArbitrary != nullptr; + } + + UBool copyErrorTo(UErrorCode &status) const { + if (fError != U_ZERO_ERROR) { + status = fError; + return TRUE; + } + return FALSE; + } + + void applyTo(impl::DecimalQuantity& quantity) const; + + void applyReciprocalTo(impl::DecimalQuantity& quantity) const; + + // To allow MacroProps/MicroProps to initialize empty instances: + friend struct impl::MacroProps; + friend struct impl::MicroProps; + + // To allow NumberFormatterImpl to access isBogus() and perform other operations: + friend class impl::NumberFormatterImpl; + + // To allow the helper class MultiplierFormatHandler access to private fields: + friend class impl::MultiplierFormatHandler; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; + + // To allow access to parsing code: + friend class ::icu::numparse::impl::NumberParserImpl; + friend class ::icu::numparse::impl::MultiplierParseHandler; +}; + +namespace impl { + +// Do not enclose entire SymbolsWrapper with #ifndef U_HIDE_INTERNAL_API, needed for a protected field +/** @internal */ +class U_I18N_API SymbolsWrapper : public UMemory { + public: + /** @internal */ + SymbolsWrapper() : fType(SYMPTR_NONE), fPtr{nullptr} {} + + /** @internal */ + SymbolsWrapper(const SymbolsWrapper &other); + + /** @internal */ + SymbolsWrapper &operator=(const SymbolsWrapper &other); + + /** @internal */ + SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT; + + /** @internal */ + SymbolsWrapper &operator=(SymbolsWrapper&& src) U_NOEXCEPT; + + /** @internal */ + ~SymbolsWrapper(); + +#ifndef U_HIDE_INTERNAL_API + + /** + * The provided object is copied, but we do not adopt it. + * @internal + */ + void setTo(const DecimalFormatSymbols &dfs); + + /** + * Adopt the provided object. + * @internal + */ + void setTo(const NumberingSystem *ns); + + /** + * Whether the object is currently holding a DecimalFormatSymbols. + * @internal + */ + bool isDecimalFormatSymbols() const; + + /** + * Whether the object is currently holding a NumberingSystem. + * @internal + */ + bool isNumberingSystem() const; + + /** + * Get the DecimalFormatSymbols pointer. No ownership change. + * @internal + */ + const DecimalFormatSymbols *getDecimalFormatSymbols() const; + + /** * Get the NumberingSystem pointer. No ownership change. * @internal */ const NumberingSystem *getNumberingSystem() const; +#endif // U_HIDE_INTERNAL_API + /** @internal */ UBool copyErrorTo(UErrorCode &status) const { if (fType == SYMPTR_DFS && fPtr.dfs == nullptr) { @@ -1297,7 +1181,6 @@ class U_I18N_API SymbolsWrapper : public UMemory { } return FALSE; } -#endif // U_HIDE_INTERNAL_API private: enum SymbolsPointerType { @@ -1311,6 +1194,8 @@ class U_I18N_API SymbolsWrapper : public UMemory { void doCopyFrom(const SymbolsWrapper &other); + void doMoveFrom(SymbolsWrapper&& src); + void doCleanup(); }; @@ -1322,13 +1207,28 @@ class U_I18N_API Grouper : public UMemory { /** @internal */ static Grouper forStrategy(UGroupingStrategy grouping); + /** + * Resolve the values in Properties to a Grouper object. + * @internal + */ + static Grouper forProperties(const DecimalFormatProperties& properties); + // Future: static Grouper forProperties(DecimalFormatProperties& properties); /** @internal */ - Grouper(int16_t grouping1, int16_t grouping2, int16_t minGrouping) - : fGrouping1(grouping1), fGrouping2(grouping2), fMinGrouping(minGrouping) {} + Grouper(int16_t grouping1, int16_t grouping2, int16_t minGrouping, UGroupingStrategy strategy) + : fGrouping1(grouping1), + fGrouping2(grouping2), + fMinGrouping(minGrouping), + fStrategy(strategy) {} #endif // U_HIDE_INTERNAL_API + /** @internal */ + int16_t getPrimary() const; + + /** @internal */ + int16_t getSecondary() const; + private: /** * The grouping sizes, with the following special values: @@ -1342,7 +1242,7 @@ class U_I18N_API Grouper : public UMemory { int16_t fGrouping2; /** - * The minimum gropuing size, with the following special values: + * The minimum grouping size, with the following special values: *
    *
  • -2 = needs locale data *
  • -3 = no less than 2 @@ -1350,6 +1250,12 @@ class U_I18N_API Grouper : public UMemory { */ int16_t fMinGrouping; + /** + * The UGroupingStrategy that was used to create this Grouper, or UNUM_GROUPING_COUNT if this + * was not created from a UGroupingStrategy. + */ + UGroupingStrategy fStrategy; + Grouper() : fGrouping1(-3) {}; bool isBogus() const { @@ -1367,6 +1273,12 @@ class U_I18N_API Grouper : public UMemory { // To allow NumberFormatterImpl to access isBogus() and perform other operations: friend class NumberFormatterImpl; + + // To allow NumberParserImpl to perform setLocaleData(): + friend class ::icu::numparse::impl::NumberParserImpl; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; // Do not enclose entire Padder with #ifndef U_HIDE_INTERNAL_API, needed for a protected field @@ -1381,6 +1293,9 @@ class U_I18N_API Padder : public UMemory { static Padder codePoints(UChar32 cp, int32_t targetWidth, UNumberFormatPadPosition position); #endif // U_HIDE_INTERNAL_API + /** @internal */ + static Padder forProperties(const DecimalFormatProperties& properties); + private: UChar32 fWidth; // -3 = error; -2 = bogus; -1 = no padding union { @@ -1427,6 +1342,9 @@ class U_I18N_API Padder : public UMemory { // To allow NumberFormatterImpl to access isBogus() and perform other operations: friend class impl::NumberFormatterImpl; + + // To allow access to the skeleton generation code: + friend class impl::GeneratorHelpers; }; // Do not enclose entire MacroProps with #ifndef U_HIDE_INTERNAL_API, needed for a protected field @@ -1442,7 +1360,10 @@ struct U_I18N_API MacroProps : public UMemory { MeasureUnit perUnit; // = NoUnit::base(); /** @internal */ - Rounder rounder; // = Rounder(); (bogus) + Precision precision; // = Precision(); (bogus) + + /** @internal */ + UNumberFormatRoundingMode roundingMode = UNUM_ROUND_HALFEVEN; /** @internal */ Grouper grouper; // = Grouper(); (bogus) @@ -1468,20 +1389,33 @@ struct U_I18N_API MacroProps : public UMemory { UNumberDecimalSeparatorDisplay decimal = UNUM_DECIMAL_SEPARATOR_COUNT; /** @internal */ - PluralRules *rules = nullptr; // no ownership + Scale scale; // = Scale(); (benign value) + + /** @internal */ + const AffixPatternProvider* affixProvider = nullptr; // no ownership + + /** @internal */ + const PluralRules* rules = nullptr; // no ownership + + /** @internal */ + const CurrencySymbols* currencySymbols = nullptr; // no ownership /** @internal */ int32_t threshold = DEFAULT_THRESHOLD; + + /** @internal */ Locale locale; + // NOTE: Uses default copy and move constructors. + /** * Check all members for errors. * @internal */ bool copyErrorTo(UErrorCode &status) const { - return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || + return notation.copyErrorTo(status) || precision.copyErrorTo(status) || padder.copyErrorTo(status) || integerWidth.copyErrorTo(status) || - symbols.copyErrorTo(status); + symbols.copyErrorTo(status) || scale.copyErrorTo(status); } }; @@ -1505,7 +1439,7 @@ class U_I18N_API NumberFormatterSettings { * *

    * All notation styles will be properly localized with locale data, and all notation styles are compatible with - * units, rounding strategies, and other number formatter settings. + * units, rounding precisions, and other number formatter settings. * *

    * Pass this method the return value of a {@link Notation} factory method. For example: @@ -1522,7 +1456,18 @@ class U_I18N_API NumberFormatterSettings { * @see Notation * @draft ICU 60 */ - Derived notation(const Notation ¬ation) const; + Derived notation(const Notation ¬ation) const &; + + /** + * Overload of notation() for use on an rvalue reference. + * + * @param notation + * The notation strategy to use. + * @return The fluent chain. + * @see #notation + * @draft ICU 62 + */ + Derived notation(const Notation ¬ation) &&; /** * Specifies the unit (unit of measure, currency, or percent) to associate with rendered numbers. @@ -1534,7 +1479,7 @@ class U_I18N_API NumberFormatterSettings { *

* * All units will be properly localized with locale data, and all units are compatible with notation styles, - * rounding strategies, and other number formatter settings. + * rounding precisions, and other number formatter settings. * * Pass this method any instance of {@link MeasureUnit}. For units of measure (which often involve the * factory methods that return a pointer): @@ -1568,7 +1513,18 @@ class U_I18N_API NumberFormatterSettings { * @see #perUnit * @draft ICU 60 */ - Derived unit(const icu::MeasureUnit &unit) const; + Derived unit(const icu::MeasureUnit &unit) const &; + + /** + * Overload of unit() for use on an rvalue reference. + * + * @param unit + * The unit to render. + * @return The fluent chain. + * @see #unit + * @draft ICU 62 + */ + Derived unit(const icu::MeasureUnit &unit) &&; /** * Like unit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory @@ -1585,7 +1541,18 @@ class U_I18N_API NumberFormatterSettings { * @see MeasureUnit * @draft ICU 60 */ - Derived adoptUnit(icu::MeasureUnit *unit) const; + Derived adoptUnit(icu::MeasureUnit *unit) const &; + + /** + * Overload of adoptUnit() for use on an rvalue reference. + * + * @param unit + * The unit to render. + * @return The fluent chain. + * @see #adoptUnit + * @draft ICU 62 + */ + Derived adoptUnit(icu::MeasureUnit *unit) &&; /** * Sets a unit to be used in the denominator. For example, to format "3 m/s", pass METER to the unit and SECOND to @@ -1604,7 +1571,18 @@ class U_I18N_API NumberFormatterSettings { * @see #unit * @draft ICU 61 */ - Derived perUnit(const icu::MeasureUnit &perUnit) const; + Derived perUnit(const icu::MeasureUnit &perUnit) const &; + + /** + * Overload of perUnit() for use on an rvalue reference. + * + * @param perUnit + * The unit to render in the denominator. + * @return The fluent chain. + * @see #perUnit + * @draft ICU 62 + */ + Derived perUnit(const icu::MeasureUnit &perUnit) &&; /** * Like perUnit(), but takes ownership of a pointer. Convenient for use with the MeasureFormat factory @@ -1623,10 +1601,21 @@ class U_I18N_API NumberFormatterSettings { * @see MeasureUnit * @draft ICU 61 */ - Derived adoptPerUnit(icu::MeasureUnit *perUnit) const; + Derived adoptPerUnit(icu::MeasureUnit *perUnit) const &; /** - * Specifies the rounding strategy to use when formatting numbers. + * Overload of adoptPerUnit() for use on an rvalue reference. + * + * @param perUnit + * The unit to render in the denominator. + * @return The fluent chain. + * @see #adoptPerUnit + * @draft ICU 62 + */ + Derived adoptPerUnit(icu::MeasureUnit *perUnit) &&; + + /** + * Specifies the rounding precision to use when formatting numbers. * *
    *
  • Round to 3 decimal places: "3.142" @@ -1636,27 +1625,77 @@ class U_I18N_API NumberFormatterSettings { *
* *

- * Pass this method the return value of one of the factory methods on {@link Rounder}. For example: + * Pass this method the return value of one of the factory methods on {@link Precision}. For example: * *

-     * NumberFormatter::with().rounding(Rounder::fixedFraction(2))
+     * NumberFormatter::with().precision(Precision::fixedFraction(2))
      * 
* *

* In most cases, the default rounding strategy is to round to 6 fraction places; i.e., - * Rounder.maxFraction(6). The exceptions are if compact notation is being used, then the compact + * Precision.maxFraction(6). The exceptions are if compact notation is being used, then the compact * notation rounding strategy is used (see {@link Notation#compactShort} for details), or if the unit is a currency, - * then standard currency rounding is used, which varies from currency to currency (see {@link Rounder#currency} for + * then standard currency rounding is used, which varies from currency to currency (see {@link Precision#currency} for * details). * - * @param rounder - * The rounding strategy to use. + * @param precision + * The rounding precision to use. * @return The fluent chain. - * @see Rounder - * @provisional This API might change or be removed in a future release. - * @draft ICU 60 + * @see Precision + * @draft ICU 62 + */ + Derived precision(const Precision& precision) const &; + + /** + * Overload of precision() for use on an rvalue reference. + * + * @param precision + * The rounding precision to use. + * @return The fluent chain. + * @see #precision + * @draft ICU 62 + */ + Derived precision(const Precision& precision) &&; + +#ifndef U_HIDE_DEPRECATED_API + // Compatibility method that will be removed in ICU 64. + // Use precision() instead. + // See http://bugs.icu-project.org/trac/ticket/13746 + /** @deprecated ICU 62 */ + Derived rounding(const Rounder& rounder) const & { + return precision(rounder); + } +#endif /* U_HIDE_DEPRECATED_API */ + + /** + * Specifies how to determine the direction to round a number when it has more digits than fit in the + * desired precision. When formatting 1.235: + * + *

    + *
  • Ceiling rounding mode with integer precision: "2" + *
  • Half-down rounding mode with 2 fixed fraction digits: "1.23" + *
  • Half-up rounding mode with 2 fixed fraction digits: "1.24" + *
+ * + * The default is HALF_EVEN. For more information on rounding mode, see the ICU userguide here: + * + * http://userguide.icu-project.org/formatparse/numbers/rounding-modes + * + * @param roundingMode The rounding mode to use. + * @return The fluent chain. + * @draft ICU 62 + */ + Derived roundingMode(UNumberFormatRoundingMode roundingMode) const &; + + /** + * Overload of roundingMode() for use on an rvalue reference. + * + * @param roundingMode The rounding mode to use. + * @return The fluent chain. + * @see #roundingMode + * @draft ICU 62 */ - Derived rounding(const Rounder &rounder) const; + Derived roundingMode(UNumberFormatRoundingMode roundingMode) &&; /** * Specifies the grouping strategy to use when formatting numbers. @@ -1685,7 +1724,19 @@ class U_I18N_API NumberFormatterSettings { * @return The fluent chain. * @draft ICU 61 */ - Derived grouping(const UGroupingStrategy &strategy) const; + Derived grouping(UGroupingStrategy strategy) const &; + + /** + * Overload of grouping() for use on an rvalue reference. + * + * @param strategy + * The grouping strategy to use. + * @return The fluent chain. + * @see #grouping + * @provisional This API might change or be removed in a future release. + * @draft ICU 62 + */ + Derived grouping(UGroupingStrategy strategy) &&; /** * Specifies the minimum and maximum number of digits to render before the decimal mark. @@ -1711,7 +1762,18 @@ class U_I18N_API NumberFormatterSettings { * @see IntegerWidth * @draft ICU 60 */ - Derived integerWidth(const IntegerWidth &style) const; + Derived integerWidth(const IntegerWidth &style) const &; + + /** + * Overload of integerWidth() for use on an rvalue reference. + * + * @param style + * The integer width to use. + * @return The fluent chain. + * @see #integerWidth + * @draft ICU 62 + */ + Derived integerWidth(const IntegerWidth &style) &&; /** * Specifies the symbols (decimal separator, grouping separator, percent sign, numerals, etc.) to use when rendering @@ -1741,8 +1803,8 @@ class U_I18N_API NumberFormatterSettings { * after passing it into the fluent chain will not be seen. * *

- * Note: Calling this method will override the NumberingSystem previously specified in - * {@link #symbols(NumberingSystem)}. + * Note: Calling this method will override any previously specified DecimalFormatSymbols + * or NumberingSystem. * *

* The default is to choose the symbols based on the locale specified in the fluent chain. @@ -1753,7 +1815,18 @@ class U_I18N_API NumberFormatterSettings { * @see DecimalFormatSymbols * @draft ICU 60 */ - Derived symbols(const DecimalFormatSymbols &symbols) const; + Derived symbols(const DecimalFormatSymbols &symbols) const &; + + /** + * Overload of symbols() for use on an rvalue reference. + * + * @param symbols + * The DecimalFormatSymbols to use. + * @return The fluent chain. + * @see #symbols + * @draft ICU 62 + */ + Derived symbols(const DecimalFormatSymbols &symbols) &&; /** * Specifies that the given numbering system should be used when fetching symbols. @@ -1773,8 +1846,8 @@ class U_I18N_API NumberFormatterSettings { * * *

- * Note: Calling this method will override the DecimalFormatSymbols previously specified in - * {@link #symbols(DecimalFormatSymbols)}. + * Note: Calling this method will override any previously specified DecimalFormatSymbols + * or NumberingSystem. * *

* The default is to choose the best numbering system for the locale. @@ -1788,7 +1861,18 @@ class U_I18N_API NumberFormatterSettings { * @see NumberingSystem * @draft ICU 60 */ - Derived adoptSymbols(NumberingSystem *symbols) const; + Derived adoptSymbols(NumberingSystem *symbols) const &; + + /** + * Overload of adoptSymbols() for use on an rvalue reference. + * + * @param symbols + * The NumberingSystem to use. + * @return The fluent chain. + * @see #adoptSymbols + * @draft ICU 62 + */ + Derived adoptSymbols(NumberingSystem *symbols) &&; /** * Sets the width of the unit (measure unit or currency). Most common values: @@ -1815,7 +1899,18 @@ class U_I18N_API NumberFormatterSettings { * @see UNumberUnitWidth * @draft ICU 60 */ - Derived unitWidth(const UNumberUnitWidth &width) const; + Derived unitWidth(UNumberUnitWidth width) const &; + + /** + * Overload of unitWidth() for use on an rvalue reference. + * + * @param width + * The width to use when rendering numbers. + * @return The fluent chain. + * @see #unitWidth + * @draft ICU 62 + */ + Derived unitWidth(UNumberUnitWidth width) &&; /** * Sets the plus/minus sign display strategy. Most common values: @@ -1836,14 +1931,24 @@ class U_I18N_API NumberFormatterSettings { *

* The default is AUTO sign display. * - * @param width + * @param style * The sign display strategy to use when rendering numbers. * @return The fluent chain * @see UNumberSignDisplay - * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived sign(const UNumberSignDisplay &width) const; + Derived sign(UNumberSignDisplay style) const &; + + /** + * Overload of sign() for use on an rvalue reference. + * + * @param style + * The sign display strategy to use when rendering numbers. + * @return The fluent chain. + * @see #sign + * @draft ICU 62 + */ + Derived sign(UNumberSignDisplay style) &&; /** * Sets the decimal separator display strategy. This affects integer numbers with no fraction part. Most common @@ -1864,23 +1969,73 @@ class U_I18N_API NumberFormatterSettings { *

* The default is AUTO decimal separator display. * - * @param width + * @param style * The decimal separator display strategy to use when rendering numbers. * @return The fluent chain * @see UNumberDecimalSeparatorDisplay - * @provisional This API might change or be removed in a future release. * @draft ICU 60 */ - Derived decimal(const UNumberDecimalSeparatorDisplay &width) const; + Derived decimal(UNumberDecimalSeparatorDisplay style) const &; + + /** + * Overload of decimal() for use on an rvalue reference. + * + * @param style + * The decimal separator display strategy to use when rendering numbers. + * @return The fluent chain. + * @see #decimal + * @draft ICU 62 + */ + Derived decimal(UNumberDecimalSeparatorDisplay style) &&; + + /** + * Sets a scale (multiplier) to be used to scale the number by an arbitrary amount before formatting. + * Most common values: + * + *

    + *
  • Multiply by 100: useful for percentages. + *
  • Multiply by an arbitrary value: useful for unit conversions. + *
+ * + *

+ * Pass an element from a {@link Scale} factory method to this setter. For example: + * + *

+     * NumberFormatter::with().scale(Scale::powerOfTen(2))
+     * 
+ * + *

+ * The default is to not apply any multiplier. + * + * @param scale + * The scale to apply when rendering numbers. + * @return The fluent chain + * @draft ICU 62 + */ + Derived scale(const Scale &scale) const &; + + /** + * Overload of scale() for use on an rvalue reference. + * + * @param scale + * The scale to apply when rendering numbers. + * @return The fluent chain. + * @see #scale + * @draft ICU 62 + */ + Derived scale(const Scale &scale) &&; #ifndef U_HIDE_INTERNAL_API /** - * Set the padding strategy. May be added to ICU 61; see #13338. + * Set the padding strategy. May be added in the future; see #13338. * * @internal ICU 60: This API is ICU internal only. */ - Derived padding(const impl::Padder &padder) const; + Derived padding(const impl::Padder &padder) const &; + + /** @internal */ + Derived padding(const impl::Padder &padder) &&; /** * Internal fluent setter to support a custom regulation threshold. A threshold of 1 causes the data structures to @@ -1888,10 +2043,45 @@ class U_I18N_API NumberFormatterSettings { * * @internal ICU 60: This API is ICU internal only. */ - Derived threshold(int32_t threshold) const; + Derived threshold(int32_t threshold) const &; + + /** @internal */ + Derived threshold(int32_t threshold) &&; + + /** + * Internal fluent setter to overwrite the entire macros object. + * + * @internal ICU 60: This API is ICU internal only. + */ + Derived macros(const impl::MacroProps& macros) const &; + + /** @internal */ + Derived macros(const impl::MacroProps& macros) &&; + + /** @internal */ + Derived macros(impl::MacroProps&& macros) const &; + + /** @internal */ + Derived macros(impl::MacroProps&& macros) &&; #endif /* U_HIDE_INTERNAL_API */ + /** + * Creates a skeleton string representation of this number formatter. A skeleton string is a + * locale-agnostic serialized form of a number formatter. + * + * Not all options are capable of being represented in the skeleton string; for example, a + * DecimalFormatSymbols object. If any such option is encountered, the error code is set to + * U_UNSUPPORTED_ERROR. + * + * The returned skeleton is in normalized form, such that two number formatters with equivalent + * behavior should produce the same skeleton. + * + * @return A number skeleton string with behavior corresponding to this number formatter. + * @draft ICU 62 + */ + UnicodeString toSkeleton(UErrorCode& status) const; + /** * Sets the UErrorCode if an error occurred in the fluent chain. * Preserves older error codes in the outErrorCode. @@ -1905,7 +2095,9 @@ class U_I18N_API NumberFormatterSettings { } fMacros.copyErrorTo(outErrorCode); return U_FAILURE(outErrorCode); - } + }; + + // NOTE: Uses default copy and move constructors. protected: impl::MacroProps fMacros; @@ -1944,21 +2136,58 @@ class U_I18N_API UnlocalizedNumberFormatter * @return The fluent chain. * @draft ICU 60 */ - LocalizedNumberFormatter locale(const icu::Locale &locale) const; + LocalizedNumberFormatter locale(const icu::Locale &locale) const &; + + /** + * Overload of locale() for use on an rvalue reference. + * + * @param locale + * The locale to use when loading data for number formatting. + * @return The fluent chain. + * @see #locale + * @draft ICU 62 + */ + LocalizedNumberFormatter locale(const icu::Locale &locale) &&; + + /** + * Default constructor: puts the formatter into a valid but undefined state. + * + * @draft ICU 62 + */ + UnlocalizedNumberFormatter() = default; // Make default copy constructor call the NumberFormatterSettings copy constructor. /** * Returns a copy of this UnlocalizedNumberFormatter. * @draft ICU 60 */ - UnlocalizedNumberFormatter(const UnlocalizedNumberFormatter &other) : UnlocalizedNumberFormatter( - static_cast &>(other)) {} + UnlocalizedNumberFormatter(const UnlocalizedNumberFormatter &other); + + /** + * Move constructor: + * The source UnlocalizedNumberFormatter will be left in a valid but undefined state. + * @draft ICU 62 + */ + UnlocalizedNumberFormatter(UnlocalizedNumberFormatter&& src) U_NOEXCEPT; + + /** + * Copy assignment operator. + * @draft ICU 62 + */ + UnlocalizedNumberFormatter& operator=(const UnlocalizedNumberFormatter& other); + + /** + * Move assignment operator: + * The source UnlocalizedNumberFormatter will be left in a valid but undefined state. + * @draft ICU 62 + */ + UnlocalizedNumberFormatter& operator=(UnlocalizedNumberFormatter&& src) U_NOEXCEPT; private: - UnlocalizedNumberFormatter() = default; + explicit UnlocalizedNumberFormatter(const NumberFormatterSettings& other); explicit UnlocalizedNumberFormatter( - const NumberFormatterSettings &other); + NumberFormatterSettings&& src) U_NOEXCEPT; // To give the fluent setters access to this class's constructor: friend class NumberFormatterSettings; @@ -2016,15 +2245,99 @@ class U_I18N_API LocalizedNumberFormatter * @return A FormattedNumber object; call .toString() to get the string. * @draft ICU 60 */ - FormattedNumber formatDecimal(StringPiece value, UErrorCode &status) const; + FormattedNumber formatDecimal(StringPiece value, UErrorCode& status) const; + +#ifndef U_HIDE_INTERNAL_API + + /** Internal method. + * @internal + */ + FormattedNumber formatDecimalQuantity(const impl::DecimalQuantity& dq, UErrorCode& status) const; + + /** Internal method for DecimalFormat compatibility. + * @internal + */ + void getAffixImpl(bool isPrefix, bool isNegative, UnicodeString& result, UErrorCode& status) const; + + /** + * Internal method for testing. + * @internal + */ + const impl::NumberFormatterImpl* getCompiled() const; + + /** + * Internal method for testing. + * @internal + */ + int32_t getCallCount() const; + +#endif + + /** + * Creates a representation of this LocalizedNumberFormat as an icu::Format, enabling the use + * of this number formatter with APIs that need an object of that type, such as MessageFormat. + * + * This API is not intended to be used other than for enabling API compatibility. The formatDouble, + * formatInt, and formatDecimal methods should normally be used when formatting numbers, not the Format + * object returned by this method. + * + * The caller owns the returned object and must delete it when finished. + * + * @return A Format wrapping this LocalizedNumberFormatter. + * @draft ICU 62 + */ + Format* toFormat(UErrorCode& status) const; + + /** + * Default constructor: puts the formatter into a valid but undefined state. + * + * @draft ICU 62 + */ + LocalizedNumberFormatter() = default; // Make default copy constructor call the NumberFormatterSettings copy constructor. /** * Returns a copy of this LocalizedNumberFormatter. * @draft ICU 60 */ - LocalizedNumberFormatter(const LocalizedNumberFormatter &other) : LocalizedNumberFormatter( - static_cast &>(other)) {} + LocalizedNumberFormatter(const LocalizedNumberFormatter &other); + + /** + * Move constructor: + * The source LocalizedNumberFormatter will be left in a valid but undefined state. + * @draft ICU 62 + */ + LocalizedNumberFormatter(LocalizedNumberFormatter&& src) U_NOEXCEPT; + + /** + * Copy assignment operator. + * @draft ICU 62 + */ + LocalizedNumberFormatter& operator=(const LocalizedNumberFormatter& other); + + /** + * Move assignment operator: + * The source LocalizedNumberFormatter will be left in a valid but undefined state. + * @draft ICU 62 + */ + LocalizedNumberFormatter& operator=(LocalizedNumberFormatter&& src) U_NOEXCEPT; + +#ifndef U_HIDE_INTERNAL_API + + /** + * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a static code path + * for the first few calls, and compiling a more efficient data structure if called repeatedly. + * + *

+ * This function is very hot, being called in every call to the number formatting pipeline. + * + * @param results + * The results object. This method will mutate it to save the results. + * @internal + */ + void formatImpl(impl::UFormattedNumberData *results, UErrorCode &status) const; + +#endif /** * Destruct this LocalizedNumberFormatter, cleaning up any memory it might own. @@ -2033,27 +2346,25 @@ class U_I18N_API LocalizedNumberFormatter ~LocalizedNumberFormatter(); private: + // Note: fCompiled can't be a LocalPointer because impl::NumberFormatterImpl is defined in an internal + // header, and LocalPointer needs the full class definition in order to delete the instance. const impl::NumberFormatterImpl* fCompiled {nullptr}; char fUnsafeCallCount[8] {}; // internally cast to u_atomic_int32_t - LocalizedNumberFormatter() = default; + explicit LocalizedNumberFormatter(const NumberFormatterSettings& other); - explicit LocalizedNumberFormatter(const NumberFormatterSettings &other); + explicit LocalizedNumberFormatter(NumberFormatterSettings&& src) U_NOEXCEPT; LocalizedNumberFormatter(const impl::MacroProps ¯os, const Locale &locale); + LocalizedNumberFormatter(impl::MacroProps &¯os, const Locale &locale); + + void lnfMoveHelper(LocalizedNumberFormatter&& src); + /** - * This is the core entrypoint to the number formatting pipeline. It performs self-regulation: a static code path - * for the first few calls, and compiling a more efficient data structure if called repeatedly. - * - *

- * This function is very hot, being called in every call to the number formatting pipeline. - * - * @param results - * The results object. This method takes ownership. - * @return The formatted number result. + * @return true if the compiled formatter is available. */ - FormattedNumber formatImpl(impl::NumberFormatterResults *results, UErrorCode &status) const; + bool computeCompiled(UErrorCode& status) const; // To give the fluent setters access to this class's constructor: friend class NumberFormatterSettings; @@ -2071,25 +2382,57 @@ class U_I18N_API LocalizedNumberFormatter */ class U_I18N_API FormattedNumber : public UMemory { public: +#ifndef U_HIDE_DEPRECATED_API /** * Returns a UnicodeString representation of the formatted number. * * @return a UnicodeString containing the localized number. - * @draft ICU 60 + * @deprecated ICU 62 Use the version of this method with an error code instead. + * This method was never @stable and will be removed in a future release. + * See http://bugs.icu-project.org/trac/ticket/13746 */ UnicodeString toString() const; +#endif /* U_HIDE_DEPRECATED_API */ + + /** + * Returns a UnicodeString representation of the formatted number. + * + * @param status + * Set if an error occurs while formatting the number to the UnicodeString. + * @return a UnicodeString containing the localized number. + * @draft ICU 62 + */ + UnicodeString toString(UErrorCode& status) const; +#ifndef U_HIDE_DEPRECATED_API /** * Appends the formatted number to an Appendable. * * @param appendable * The Appendable to which to append the formatted number string. * @return The same Appendable, for chaining. - * @draft ICU 60 + * @deprecated ICU 62 Use the version of this method with an error code instead. + * This method was never @stable and will be removed in a future release. + * See http://bugs.icu-project.org/trac/ticket/13746 * @see Appendable */ Appendable &appendTo(Appendable &appendable); +#endif /* U_HIDE_DEPRECATED_API */ + /** + * Appends the formatted number to an Appendable. + * + * @param appendable + * The Appendable to which to append the formatted number string. + * @param status + * Set if an error occurs while formatting the number to the Appendable. + * @return The same Appendable, for chaining. + * @draft ICU 62 + * @see Appendable + */ + Appendable &appendTo(Appendable &appendable, UErrorCode& status); + +#ifndef U_HIDE_DEPRECATED_API /** * Determine the start and end indices of the first occurrence of the given field in the output string. * This allows you to determine the locations of the integer part, fraction part, and sign. @@ -2106,11 +2449,47 @@ class U_I18N_API FormattedNumber : public UMemory { * The FieldPosition to populate with the start and end indices of the desired field. * @param status * Set if an error occurs while populating the FieldPosition. - * @draft ICU 60 + * @deprecated ICU 62 Use {@link #nextFieldPosition} instead. This method will be removed in a future + * release. See http://bugs.icu-project.org/trac/ticket/13746 * @see UNumberFormatFields */ void populateFieldPosition(FieldPosition &fieldPosition, UErrorCode &status); +#endif /* U_HIDE_DEPRECATED_API */ + + /** + * Determines the start and end indices of the next occurrence of the given field in the + * output string. This allows you to determine the locations of, for example, the integer part, + * fraction part, or symbols. + * + * If a field occurs just once, calling this method will find that occurrence and return it. If a + * field occurs multiple times, this method may be called repeatedly with the following pattern: + * + *

+     * FieldPosition fpos(UNUM_GROUPING_SEPARATOR_FIELD);
+     * while (formattedNumber.nextFieldPosition(fpos, status)) {
+     *   // do something with fpos.
+     * }
+     * 
+ * + * This method is useful if you know which field to query. If you want all available field position + * information, use #getAllFieldPositions(). + * + * @param fieldPosition + * Input+output variable. On input, the "field" property determines which field to look + * up, and the "beginIndex" and "endIndex" properties determine where to begin the search. + * On output, the "beginIndex" is set to the beginning of the first occurrence of the + * field with either begin or end indices after the input indices, "endIndex" is set to + * the end of that occurrence of the field (exclusive index). If a field position is not + * found, the method returns FALSE and the FieldPosition may or may not be changed. + * @param status + * Set if an error occurs while populating the FieldPosition. + * @return TRUE if a new occurrence of the field was found; FALSE otherwise. + * @draft ICU 62 + * @see UNumberFormatFields + */ + UBool nextFieldPosition(FieldPosition& fieldPosition, UErrorCode& status) const; +#ifndef U_HIDE_DEPRECATED_API /** * Export the formatted number to a FieldPositionIterator. This allows you to determine which characters in * the output string correspond to which fields, such as the integer part, fraction part, and sign. @@ -2122,10 +2501,67 @@ class U_I18N_API FormattedNumber : public UMemory { * The FieldPositionIterator to populate with all of the fields present in the formatted number. * @param status * Set if an error occurs while populating the FieldPositionIterator. - * @draft ICU 60 + * @deprecated ICU 62 Use {@link #getAllFieldPositions} instead. This method will be removed in a + * future release. See http://bugs.icu-project.org/trac/ticket/13746 * @see UNumberFormatFields */ void populateFieldPositionIterator(FieldPositionIterator &iterator, UErrorCode &status); +#endif /* U_HIDE_DEPRECATED_API */ + + /** + * Export the formatted number to a FieldPositionIterator. This allows you to determine which characters in + * the output string correspond to which fields, such as the integer part, fraction part, and sign. + * + * If information on only one field is needed, use #nextFieldPosition() instead. + * + * @param iterator + * The FieldPositionIterator to populate with all of the fields present in the formatted number. + * @param status + * Set if an error occurs while populating the FieldPositionIterator. + * @draft ICU 62 + * @see UNumberFormatFields + */ + void getAllFieldPositions(FieldPositionIterator &iterator, UErrorCode &status) const; + +#ifndef U_HIDE_INTERNAL_API + + /** + * Gets the raw DecimalQuantity for plural rule selection. + * @internal + */ + void getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const; + + /** + * Populates the mutable builder type FieldPositionIteratorHandler. + * @internal + */ + void getAllFieldPositionsImpl(FieldPositionIteratorHandler& fpih, UErrorCode& status) const; + +#endif + + /** + * Copying not supported; use move constructor instead. + */ + FormattedNumber(const FormattedNumber&) = delete; + + /** + * Copying not supported; use move assignment instead. + */ + FormattedNumber& operator=(const FormattedNumber&) = delete; + + /** + * Move constructor: + * Leaves the source FormattedNumber in an undefined state. + * @draft ICU 62 + */ + FormattedNumber(FormattedNumber&& src) U_NOEXCEPT; + + /** + * Move assignment: + * Leaves the source FormattedNumber in an undefined state. + * @draft ICU 62 + */ + FormattedNumber& operator=(FormattedNumber&& src) U_NOEXCEPT; /** * Destruct an instance of FormattedNumber, cleaning up any memory it might own. @@ -2134,13 +2570,17 @@ class U_I18N_API FormattedNumber : public UMemory { ~FormattedNumber(); private: - // Can't use LocalPointer because NumberFormatterResults is forward-declared - const impl::NumberFormatterResults *fResults; + // Can't use LocalPointer because UFormattedNumberData is forward-declared + const impl::UFormattedNumberData *fResults; // Error code for the terminal methods UErrorCode fErrorCode; - explicit FormattedNumber(impl::NumberFormatterResults *results) + /** + * Internal constructor from data type. Adopts the data pointer. + * @internal + */ + explicit FormattedNumber(impl::UFormattedNumberData *results) : fResults(results), fErrorCode(U_ZERO_ERROR) {}; explicit FormattedNumber(UErrorCode errorCode) @@ -2177,9 +2617,21 @@ class U_I18N_API NumberFormatter final { */ static LocalizedNumberFormatter withLocale(const Locale &locale); + /** + * Call this method at the beginning of a NumberFormatter fluent chain to create an instance based + * on a given number skeleton string. + * + * @param skeleton + * The skeleton string off of which to base this NumberFormatter. + * @param status + * Set to U_NUMBER_SKELETON_SYNTAX_ERROR if the skeleton was invalid. + * @return An UnlocalizedNumberFormatter, to be used for chaining. + * @draft ICU 62 + */ + static UnlocalizedNumberFormatter forSkeleton(const UnicodeString& skeleton, UErrorCode& status); + /** * Use factory methods instead of the constructor to create a NumberFormatter. - * @draft ICU 60 */ NumberFormatter() = delete; }; diff --git a/deps/icu-small/source/i18n/unicode/numfmt.h b/deps/icu-small/source/i18n/unicode/numfmt.h index 1332f5256628e3..572e6afc71b807 100644 --- a/deps/icu-small/source/i18n/unicode/numfmt.h +++ b/deps/icu-small/source/i18n/unicode/numfmt.h @@ -555,16 +555,18 @@ class U_I18N_API NumberFormat : public Format { UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const; -public: + +// Can't use #ifndef U_HIDE_INTERNAL_API because these are virtual methods + /** * Format a decimal number. - * The number is a DigitList wrapper onto a floating point decimal number. + * The number is a DecimalQuantity wrapper onto a floating point decimal number. * The default implementation in NumberFormat converts the decimal number * to a double and formats that. Subclasses of NumberFormat that want * to specifically handle big decimal numbers must override this method. * class DecimalFormat does so. * - * @param number The number, a DigitList format Decimal Floating Point. + * @param number The number, a DecimalQuantity format Decimal Floating Point. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param posIter On return, can be used to iterate over positions @@ -573,20 +575,20 @@ class U_I18N_API NumberFormat : public Format { * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, + virtual UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const; /** * Format a decimal number. - * The number is a DigitList wrapper onto a floating point decimal number. + * The number is a DecimalQuantity wrapper onto a floating point decimal number. * The default implementation in NumberFormat converts the decimal number * to a double and formats that. Subclasses of NumberFormat that want * to specifically handle big decimal numbers must override this method. * class DecimalFormat does so. * - * @param number The number, a DigitList format Decimal Floating Point. + * @param number The number, a DecimalQuantity format Decimal Floating Point. * @param appendTo Output parameter to receive result. * Result is appended to existing contents. * @param pos On input: an alignment field, if desired. @@ -595,13 +597,11 @@ class U_I18N_API NumberFormat : public Format { * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, + virtual UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const; -public: - /** * Return a long if possible (e.g. within range LONG_MAX, * LONG_MAX], and with no decimals), otherwise a double. If diff --git a/deps/icu-small/source/i18n/unicode/plurrule.h b/deps/icu-small/source/i18n/unicode/plurrule.h index d372d79c845179..03dea3f1b92988 100644 --- a/deps/icu-small/source/i18n/unicode/plurrule.h +++ b/deps/icu-small/source/i18n/unicode/plurrule.h @@ -44,7 +44,6 @@ U_NAMESPACE_BEGIN class Hashtable; class IFixedDecimal; -class VisibleDigitsWithExponent; class RuleChain; class PluralRuleParser; class PluralKeywordEnumeration; @@ -348,30 +347,10 @@ class U_I18N_API PluralRules : public UObject { UnicodeString select(double number) const; #ifndef U_HIDE_INTERNAL_API - /** - * Given a number and a format, returns the keyword of the first applicable - * rule for this PluralRules object. - * Note: This internal preview interface may be removed in the future if - * an architecturally cleaner solution reaches stable status. - * @param obj The numeric object for which the rule should be determined. - * @param fmt The NumberFormat specifying how the number will be formatted - * (this can affect the plural form, e.g. "1 dollar" vs "1.0 dollars"). - * @param status Input/output parameter. If at entry this indicates a - * failure status, the method returns immediately; otherwise - * this is set to indicate the outcome of the call. - * @return The keyword of the selected rule. Undefined in the case of an error. - * @internal ICU 59 technology preview, may be removed in the future - */ - UnicodeString select(const Formattable& obj, const NumberFormat& fmt, UErrorCode& status) const; - /** * @internal */ UnicodeString select(const IFixedDecimal &number) const; - /** - * @internal - */ - UnicodeString select(const VisibleDigitsWithExponent &number) const; #endif /* U_HIDE_INTERNAL_API */ /** diff --git a/deps/icu-small/source/i18n/unicode/rbnf.h b/deps/icu-small/source/i18n/unicode/rbnf.h index d8d33420c2a015..2d284909f8a1d4 100644 --- a/deps/icu-small/source/i18n/unicode/rbnf.h +++ b/deps/icu-small/source/i18n/unicode/rbnf.h @@ -884,7 +884,7 @@ class U_I18N_API RuleBasedNumberFormat : public NumberFormat { * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, + virtual UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPositionIterator* posIter, UErrorCode& status) const; @@ -906,7 +906,7 @@ class U_I18N_API RuleBasedNumberFormat : public NumberFormat { * @return Reference to 'appendTo' parameter. * @internal */ - virtual UnicodeString& format(const DigitList &number, + virtual UnicodeString& format(const number::impl::DecimalQuantity &number, UnicodeString& appendTo, FieldPosition& pos, UErrorCode& status) const; diff --git a/deps/icu-small/source/i18n/unicode/scientificnumberformatter.h b/deps/icu-small/source/i18n/unicode/scientificnumberformatter.h index 30edee7ecce289..6c99a246625e2e 100644 --- a/deps/icu-small/source/i18n/unicode/scientificnumberformatter.h +++ b/deps/icu-small/source/i18n/unicode/scientificnumberformatter.h @@ -24,7 +24,6 @@ U_NAMESPACE_BEGIN class FieldPositionIterator; -class DecimalFormatStaticSets; class DecimalFormatSymbols; class DecimalFormat; class Formattable; @@ -150,7 +149,6 @@ class U_I18N_API ScientificNumberFormatter : public UObject { const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets &decimalFormatSets, UnicodeString &appendTo, UErrorCode &status) const = 0; private: @@ -165,7 +163,6 @@ class U_I18N_API ScientificNumberFormatter : public UObject { const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets &decimalFormatSets, UnicodeString &appendTo, UErrorCode &status) const; }; @@ -184,7 +181,6 @@ class U_I18N_API ScientificNumberFormatter : public UObject { const UnicodeString &original, FieldPositionIterator &fpi, const UnicodeString &preExponent, - const DecimalFormatStaticSets &decimalFormatSets, UnicodeString &appendTo, UErrorCode &status) const; private: @@ -211,7 +207,6 @@ class U_I18N_API ScientificNumberFormatter : public UObject { UnicodeString fPreExponent; DecimalFormat *fDecimalFormat; Style *fStyle; - const DecimalFormatStaticSets *fStaticSets; }; diff --git a/deps/icu-small/source/i18n/unicode/smpdtfmt.h b/deps/icu-small/source/i18n/unicode/smpdtfmt.h index 9801b29bdb749b..305412b8d1535f 100644 --- a/deps/icu-small/source/i18n/unicode/smpdtfmt.h +++ b/deps/icu-small/source/i18n/unicode/smpdtfmt.h @@ -50,6 +50,10 @@ class TimeZoneFormat; class SharedNumberFormat; class SimpleDateFormatMutableNFs; +namespace number { +class LocalizedNumberFormatter; +} + /** * * SimpleDateFormat is a concrete class for formatting and parsing dates in a @@ -1268,7 +1272,6 @@ class U_I18N_API SimpleDateFormat: public DateFormat { int32_t fieldNum, FieldPositionHandler& handler, Calendar& cal, - SimpleDateFormatMutableNFs &mutableNFs, UErrorCode& status) const; // in case of illegal argument /** @@ -1284,7 +1287,7 @@ class U_I18N_API SimpleDateFormat: public DateFormat { * @param minDigits Minimum number of digits the result should have * @param maxDigits Maximum number of digits the result should have */ - void zeroPaddingNumber(NumberFormat *currentNumberFormat, + void zeroPaddingNumber(const NumberFormat *currentNumberFormat, UnicodeString &appendTo, int32_t value, int32_t minDigits, @@ -1414,21 +1417,21 @@ class U_I18N_API SimpleDateFormat: public DateFormat { */ int32_t subParse(const UnicodeString& text, int32_t& start, char16_t ch, int32_t count, UBool obeyCount, UBool allowNegative, UBool ambiguousYear[], int32_t& saveHebrewMonth, Calendar& cal, - int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, SimpleDateFormatMutableNFs &mutableNFs, + int32_t patLoc, MessageFormat * numericLeapMonthFormatter, UTimeZoneFormatTimeType *tzTimeType, int32_t *dayPeriod=NULL) const; void parseInt(const UnicodeString& text, Formattable& number, ParsePosition& pos, UBool allowNegative, - NumberFormat *fmt) const; + const NumberFormat *fmt) const; void parseInt(const UnicodeString& text, Formattable& number, int32_t maxDigits, ParsePosition& pos, UBool allowNegative, - NumberFormat *fmt) const; + const NumberFormat *fmt) const; int32_t checkIntSuffix(const UnicodeString& text, int32_t start, int32_t patLoc, UBool isNegative) const; @@ -1495,6 +1498,16 @@ class U_I18N_API SimpleDateFormat: public DateFormat { */ int32_t skipUWhiteSpace(const UnicodeString& text, int32_t pos) const; + /** + * Initialize LocalizedNumberFormatter instances used for speedup. + */ + void initFastNumberFormatters(UErrorCode& status); + + /** + * Delete the LocalizedNumberFormatter instances used for speedup. + */ + void freeFastNumberFormatters(); + /** * Initialize NumberFormat instances used for numbering system overrides. */ @@ -1518,7 +1531,7 @@ class U_I18N_API SimpleDateFormat: public DateFormat { /** * Lazy TimeZoneFormat instantiation, semantically const */ - TimeZoneFormat *tzFormat() const; + TimeZoneFormat *tzFormat(UErrorCode &status) const; const NumberFormat* getNumberFormatByIndex(UDateFormatField index) const; @@ -1611,6 +1624,20 @@ class U_I18N_API SimpleDateFormat: public DateFormat { */ const SharedNumberFormat **fSharedNumberFormatters; + enum NumberFormatterKey { + SMPDTFMT_NF_1x10, + SMPDTFMT_NF_2x10, + SMPDTFMT_NF_3x10, + SMPDTFMT_NF_4x10, + SMPDTFMT_NF_2x2, + SMPDTFMT_NF_COUNT + }; + + /** + * Number formatters pre-allocated for fast performance on the most common integer lengths. + */ + const number::LocalizedNumberFormatter* fFastNumberFormatters[SMPDTFMT_NF_COUNT] = {}; + UBool fHaveDefaultCentury; BreakIterator* fCapitalizationBrkIter; diff --git a/deps/icu-small/source/i18n/unicode/timezone.h b/deps/icu-small/source/i18n/unicode/timezone.h index d4cd7cb36d326a..bbbb6b958e4514 100644 --- a/deps/icu-small/source/i18n/unicode/timezone.h +++ b/deps/icu-small/source/i18n/unicode/timezone.h @@ -284,6 +284,8 @@ class U_I18N_API TimeZone : public UObject { * and may return a different TimeZone from the one returned by * TimeZone::createDefault(). * + *

This function is not thread safe.

+ * * @return A new instance of TimeZone detected from the current host system * configuration. * @stable ICU 55 diff --git a/deps/icu-small/source/i18n/unicode/unum.h b/deps/icu-small/source/i18n/unicode/unum.h index 0e7b9fffbab2b2..8b76014b1683da 100644 --- a/deps/icu-small/source/i18n/unicode/unum.h +++ b/deps/icu-small/source/i18n/unicode/unum.h @@ -29,12 +29,13 @@ /** * \file - * \brief C API: NumberFormat + * \brief C API: Compatibility APIs for number formatting. * *

Number Format C API

* - *

IMPORTANT: New users with C++ capabilities are - * strongly encouraged to see if numberformatter.h fits their use case. + *

IMPORTANT: New users with are strongly encouraged to + * see if unumberformatter.h fits their use case. Although not deprecated, + * this header is provided for backwards compatibility only. * * Number Format C API Provides functions for * formatting and parsing a number. Also provides methods for @@ -399,6 +400,10 @@ typedef enum UNumberFormatFields { * number format is opened using the given pattern, which must conform * to the syntax described in DecimalFormat or RuleBasedNumberFormat, * respectively. + * + *

NOTE:: New users with are strongly encouraged to + * use unumf_openWithSkeletonAndLocale instead of unum_open. + * * @param pattern A pattern specifying the format to use. * This parameter is ignored unless the style is * UNUM_PATTERN_DECIMAL or UNUM_PATTERN_RULEBASED. @@ -1013,6 +1018,8 @@ typedef enum UNumberFormatAttribute { *

Example: setting the scale to 3, 123 formats as "123,000" *

Example: setting the scale to -4, 123 formats as "0.0123" * + * This setting is analogous to getMultiplierScale() and setMultiplierScale() in decimfmt.h. + * * @stable ICU 51 */ UNUM_SCALE = 21, #ifndef U_HIDE_INTERNAL_API @@ -1052,7 +1059,7 @@ typedef enum UNumberFormatAttribute { * Default: 0 (unset) * @stable ICU 50 */ - UNUM_PARSE_NO_EXPONENT, + UNUM_PARSE_NO_EXPONENT = 0x1001, /** * if this attribute is set to 1, specifies that, if the pattern contains a @@ -1067,7 +1074,21 @@ typedef enum UNumberFormatAttribute { /* The following cannot be #ifndef U_HIDE_INTERNAL_API, needed in .h file variable declararions */ /** Limit of boolean attributes. * @internal */ - UNUM_LIMIT_BOOLEAN_ATTRIBUTE = 0x1003 + UNUM_LIMIT_BOOLEAN_ATTRIBUTE = 0x1003, + + /** + * Whether parsing is sensitive to case (lowercase/uppercase). + * TODO: Add to the test suite. + * @internal This API is a technical preview. It may change in an upcoming release. + */ + UNUM_PARSE_CASE_SENSITIVE = 0x1004, + + /** + * Formatting: whether to show the plus sign on non-negative numbers. + * TODO: Add to the test suite. + * @internal This API is a technical preview. It may change in an upcoming release. + */ + UNUM_SIGN_ALWAYS_SHOWN = 0x1005, } UNumberFormatAttribute; /** diff --git a/deps/icu-small/source/i18n/unicode/unumberformatter.h b/deps/icu-small/source/i18n/unicode/unumberformatter.h new file mode 100644 index 00000000000000..b37f80c503a76d --- /dev/null +++ b/deps/icu-small/source/i18n/unicode/unumberformatter.h @@ -0,0 +1,666 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING +#ifndef __UNUMBERFORMATTER_H__ +#define __UNUMBERFORMATTER_H__ + +#include "unicode/ufieldpositer.h" +#include "unicode/umisc.h" + + +/** + * \file + * \brief C-compatible API for localized number formatting; not recommended for C++. + * + * This is the C-compatible version of the NumberFormatter API introduced in ICU 60. C++ users should + * include unicode/numberformatter.h and use the proper C++ APIs. + * + * The C API accepts a number skeleton string for specifying the settings for formatting, which covers a + * very large subset of all possible number formatting features. For more information on number skeleton + * strings, see unicode/numberformatter.h. + * + * When using UNumberFormatter, which is treated as immutable, the results are exported to a mutable + * UFormattedNumber object, which you subsequently use for populating your string buffer or iterating over + * the fields. + * + * Example code: + *

+ * // Setup:
+ * UErrorCode ec = U_ZERO_ERROR;
+ * UNumberFormatter* uformatter = unumf_openForSkeletonAndLocale(u"precision-integer", -1, "en", &ec);
+ * UFormattedNumber* uresult = unumf_openResult(&ec);
+ * if (U_FAILURE(ec)) { return; }
+ *
+ * // Format a double:
+ * unumf_formatDouble(uformatter, 5142.3, uresult, &ec);
+ * if (U_FAILURE(ec)) { return; }
+ *
+ * // Export the string to a malloc'd buffer:
+ * int32_t len = unumf_resultToString(uresult, NULL, 0, &ec);
+ * // at this point, ec == U_BUFFER_OVERFLOW_ERROR
+ * ec = U_ZERO_ERROR;
+ * UChar* buffer = (UChar*) malloc((len+1)*sizeof(UChar));
+ * unumf_resultToString(uresult, buffer, len+1, &ec);
+ * if (U_FAILURE(ec)) { return; }
+ * // buffer should equal "5,142"
+ *
+ * // Cleanup:
+ * unumf_close(uformatter);
+ * unumf_closeResult(uresult);
+ * free(buffer);
+ * 
+ * + * If you are a C++ user linking against the C libraries, you can use the LocalPointer versions of these + * APIs. The following example uses LocalPointer with the decimal number and field position APIs: + * + *
+ * // Setup:
+ * LocalUNumberFormatterPointer uformatter(unumf_openForSkeletonAndLocale(u"percent", -1, "en", &ec));
+ * LocalUFormattedNumberPointer uresult(unumf_openResult(&ec));
+ * if (U_FAILURE(ec)) { return; }
+ *
+ * // Format a decimal number:
+ * unumf_formatDecimal(uformatter.getAlias(), "9.87E-3", -1, uresult.getAlias(), &ec);
+ * if (U_FAILURE(ec)) { return; }
+ *
+ * // Get the location of the percent sign:
+ * UFieldPosition ufpos = {UNUM_PERCENT_FIELD, 0, 0};
+ * unumf_resultNextFieldPosition(uresult.getAlias(), &ufpos, &ec);
+ * // ufpos should contain beginIndex=7 and endIndex=8 since the string is "0.00987%"
+ *
+ * // No need to do any cleanup since we are using LocalPointer.
+ * 
+ */ + + +#ifndef U_HIDE_DRAFT_API +/** + * An enum declaring how to render units, including currencies. Example outputs when formatting 123 USD and 123 + * meters in en-CA: + * + *

+ *

    + *
  • NARROW*: "$123.00" and "123 m" + *
  • SHORT: "US$ 123.00" and "123 m" + *
  • FULL_NAME: "123.00 US dollars" and "123 meters" + *
  • ISO_CODE: "USD 123.00" and undefined behavior + *
  • HIDDEN: "123.00" and "123" + *
+ * + *

+ * This enum is similar to {@link com.ibm.icu.text.MeasureFormat.FormatWidth}. + * + * @draft ICU 60 + */ +typedef enum UNumberUnitWidth { + /** + * Print an abbreviated version of the unit name. Similar to SHORT, but always use the shortest available + * abbreviation or symbol. This option can be used when the context hints at the identity of the unit. For more + * information on the difference between NARROW and SHORT, see SHORT. + * + *

+ * In CLDR, this option corresponds to the "Narrow" format for measure units and the "¤¤¤¤¤" placeholder for + * currencies. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_NARROW, + + /** + * Print an abbreviated version of the unit name. Similar to NARROW, but use a slightly wider abbreviation or + * symbol when there may be ambiguity. This is the default behavior. + * + *

+ * For example, in es-US, the SHORT form for Fahrenheit is "{0} °F", but the NARROW form is "{0}°", + * since Fahrenheit is the customary unit for temperature in that locale. + * + *

+ * In CLDR, this option corresponds to the "Short" format for measure units and the "¤" placeholder for + * currencies. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_SHORT, + + /** + * Print the full name of the unit, without any abbreviations. + * + *

+ * In CLDR, this option corresponds to the default format for measure units and the "¤¤¤" placeholder for + * currencies. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_FULL_NAME, + + /** + * Use the three-digit ISO XXX code in place of the symbol for displaying currencies. The behavior of this + * option is currently undefined for use with measure units. + * + *

+ * In CLDR, this option corresponds to the "¤¤" placeholder for currencies. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_ISO_CODE, + + /** + * Format the number according to the specified unit, but do not display the unit. For currencies, apply + * monetary symbols and formats as with SHORT, but omit the currency symbol. For measure units, the behavior is + * equivalent to not specifying the unit at all. + * + * @draft ICU 60 + */ + UNUM_UNIT_WIDTH_HIDDEN, + + /** + * One more than the highest UNumberUnitWidth value. + * + * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_UNIT_WIDTH_COUNT +} UNumberUnitWidth; +#endif /* U_HIDE_DRAFT_API */ + +#ifndef U_HIDE_DRAFT_API +/** + * An enum declaring the strategy for when and how to display grouping separators (i.e., the + * separator, often a comma or period, after every 2-3 powers of ten). The choices are several + * pre-built strategies for different use cases that employ locale data whenever possible. Example + * outputs for 1234 and 1234567 in en-IN: + * + *

    + *
  • OFF: 1234 and 12345 + *
  • MIN2: 1234 and 12,34,567 + *
  • AUTO: 1,234 and 12,34,567 + *
  • ON_ALIGNED: 1,234 and 12,34,567 + *
  • THOUSANDS: 1,234 and 1,234,567 + *
+ * + *

+ * The default is AUTO, which displays grouping separators unless the locale data says that grouping + * is not customary. To force grouping for all numbers greater than 1000 consistently across locales, + * use ON_ALIGNED. On the other hand, to display grouping less frequently than the default, use MIN2 + * or OFF. See the docs of each option for details. + * + *

+ * Note: This enum specifies the strategy for grouping sizes. To set which character to use as the + * grouping separator, use the "symbols" setter. + * + * @draft ICU 61 -- TODO: This should be renamed to UNumberGroupingStrategy before promoting to stable, + * for consistency with the other enums. + */ +typedef enum UGroupingStrategy { + /** + * Do not display grouping separators in any locale. + * + * @draft ICU 61 + */ + UNUM_GROUPING_OFF, + + /** + * Display grouping using locale defaults, except do not show grouping on values smaller than + * 10000 (such that there is a minimum of two digits before the first separator). + * + *

+ * Note that locales may restrict grouping separators to be displayed only on 1 million or + * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). + * + *

+ * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @draft ICU 61 + */ + UNUM_GROUPING_MIN2, + + /** + * Display grouping using the default strategy for all locales. This is the default behavior. + * + *

+ * Note that locales may restrict grouping separators to be displayed only on 1 million or + * greater (for example, ee and hu) or disable grouping altogether (for example, bg currency). + * + *

+ * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @draft ICU 61 + */ + UNUM_GROUPING_AUTO, + + /** + * Always display the grouping separator on values of at least 1000. + * + *

+ * This option ignores the locale data that restricts or disables grouping, described in MIN2 and + * AUTO. This option may be useful to normalize the alignment of numbers, such as in a + * spreadsheet. + * + *

+ * Locale data is used to determine whether to separate larger numbers into groups of 2 + * (customary in South Asia) or groups of 3 (customary in Europe and the Americas). + * + * @draft ICU 61 + */ + UNUM_GROUPING_ON_ALIGNED, + + /** + * Use the Western defaults: groups of 3 and enabled for all numbers 1000 or greater. Do not use + * locale data for determining the grouping strategy. + * + * @draft ICU 61 + */ + UNUM_GROUPING_THOUSANDS, + + /** + * One more than the highest UGroupingStrategy value. + * + * @internal ICU 62: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_GROUPING_COUNT + +} UGroupingStrategy; +#endif /* U_HIDE_DRAFT_API */ + +#ifndef U_HIDE_DRAFT_API +/** + * An enum declaring how to denote positive and negative numbers. Example outputs when formatting + * 123, 0, and -123 in en-US: + * + *

    + *
  • AUTO: "123", "0", and "-123" + *
  • ALWAYS: "+123", "+0", and "-123" + *
  • NEVER: "123", "0", and "123" + *
  • ACCOUNTING: "$123", "$0", and "($123)" + *
  • ACCOUNTING_ALWAYS: "+$123", "+$0", and "($123)" + *
  • EXCEPT_ZERO: "+123", "0", and "-123" + *
  • ACCOUNTING_EXCEPT_ZERO: "+$123", "$0", and "($123)" + *
+ * + *

+ * The exact format, including the position and the code point of the sign, differ by locale. + * + * @draft ICU 60 + */ +typedef enum UNumberSignDisplay { + /** + * Show the minus sign on negative numbers, and do not show the sign on positive numbers. This is the default + * behavior. + * + * @draft ICU 60 + */ + UNUM_SIGN_AUTO, + + /** + * Show the minus sign on negative numbers and the plus sign on positive numbers, including zero. + * To hide the sign on zero, see {@link UNUM_SIGN_EXCEPT_ZERO}. + * + * @draft ICU 60 + */ + UNUM_SIGN_ALWAYS, + + /** + * Do not show the sign on positive or negative numbers. + * + * @draft ICU 60 + */ + UNUM_SIGN_NEVER, + + /** + * Use the locale-dependent accounting format on negative numbers, and do not show the sign on positive numbers. + * + *

+ * The accounting format is defined in CLDR and varies by locale; in many Western locales, the format is a pair + * of parentheses around the number. + * + *

+ * Note: Since CLDR defines the accounting format in the monetary context only, this option falls back to the + * AUTO sign display strategy when formatting without a currency unit. This limitation may be lifted in the + * future. + * + * @draft ICU 60 + */ + UNUM_SIGN_ACCOUNTING, + + /** + * Use the locale-dependent accounting format on negative numbers, and show the plus sign on + * positive numbers, including zero. For more information on the accounting format, see the + * ACCOUNTING sign display strategy. To hide the sign on zero, see + * {@link UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO}. + * + * @draft ICU 60 + */ + UNUM_SIGN_ACCOUNTING_ALWAYS, + + /** + * Show the minus sign on negative numbers and the plus sign on positive numbers. Do not show a + * sign on zero. + * + * @draft ICU 61 + */ + UNUM_SIGN_EXCEPT_ZERO, + + /** + * Use the locale-dependent accounting format on negative numbers, and show the plus sign on + * positive numbers. Do not show a sign on zero. For more information on the accounting format, + * see the ACCOUNTING sign display strategy. + * + * @draft ICU 61 + */ + UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO, + + /** + * One more than the highest UNumberSignDisplay value. + * + * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_SIGN_COUNT +} UNumberSignDisplay; +#endif /* U_HIDE_DRAFT_API */ + +#ifndef U_HIDE_DRAFT_API +/** + * An enum declaring how to render the decimal separator. + * + *

+ *

    + *
  • UNUM_DECIMAL_SEPARATOR_AUTO: "1", "1.1" + *
  • UNUM_DECIMAL_SEPARATOR_ALWAYS: "1.", "1.1" + *
+ */ +typedef enum UNumberDecimalSeparatorDisplay { + /** + * Show the decimal separator when there are one or more digits to display after the separator, and do not show + * it otherwise. This is the default behavior. + * + * @draft ICU 60 + */ + UNUM_DECIMAL_SEPARATOR_AUTO, + + /** + * Always show the decimal separator, even if there are no digits to display after the separator. + * + * @draft ICU 60 + */ + UNUM_DECIMAL_SEPARATOR_ALWAYS, + + /** + * One more than the highest UNumberDecimalSeparatorDisplay value. + * + * @internal ICU 60: The numeric value may change over time; see ICU ticket #12420. + */ + UNUM_DECIMAL_SEPARATOR_COUNT +} UNumberDecimalSeparatorDisplay; +#endif /* U_HIDE_DRAFT_API */ + +#ifndef U_HIDE_DRAFT_API +/** + * C-compatible version of icu::number::LocalizedNumberFormatter. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +struct UNumberFormatter; +typedef struct UNumberFormatter UNumberFormatter; + + +/** + * C-compatible version of icu::number::FormattedNumber. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @draft ICU 62 + */ +struct UFormattedNumber; +typedef struct UFormattedNumber UFormattedNumber; + + +/** + * Creates a new UNumberFormatter for the given skeleton string and locale. This is currently the only + * method for creating a new UNumberFormatter. + * + * Objects of type UNumberFormatter returned by this method are threadsafe. + * + * For more details on skeleton strings, see the documentation in numberformatter.h. For more details on + * the usage of this API, see the documentation at the top of unumberformatter.h. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param skeleton The skeleton string, like u"percent precision-integer" + * @param skeletonLen The number of UChars in the skeleton string, or -1 it it is NUL-terminated. + * @param locale The NUL-terminated locale ID. + * @param ec Set if an error occurs. + * @draft ICU 62 + */ +U_DRAFT UNumberFormatter* U_EXPORT2 +unumf_openForSkeletonAndLocale(const UChar* skeleton, int32_t skeletonLen, const char* locale, + UErrorCode* ec); + + +/** + * Creates a new UFormattedNumber for holding the result of a number formatting operation. + * + * Objects of type UFormattedNumber are not guaranteed to be threadsafe. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param ec Set if an error occurs. + * @draft ICU 62 + */ +U_DRAFT UFormattedNumber* U_EXPORT2 +unumf_openResult(UErrorCode* ec); + + +/** + * Uses a UNumberFormatter to format an integer to a UFormattedNumber. A string, field position, and other + * information can be retrieved from the UFormattedNumber. + * + * The UNumberFormatter can be shared between threads. Each thread should have its own local + * UFormattedNumber, however, for storing the result of the formatting operation. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param uformatter A formatter object created by unumf_openForSkeletonAndLocale or similar. + * @param value The number to be formatted. + * @param uresult The object that will be mutated to store the result; see unumf_openResult. + * @param ec Set if an error occurs. + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_formatInt(const UNumberFormatter* uformatter, int64_t value, UFormattedNumber* uresult, + UErrorCode* ec); + + +/** + * Uses a UNumberFormatter to format a double to a UFormattedNumber. A string, field position, and other + * information can be retrieved from the UFormattedNumber. + * + * The UNumberFormatter can be shared between threads. Each thread should have its own local + * UFormattedNumber, however, for storing the result of the formatting operation. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param uformatter A formatter object created by unumf_openForSkeletonAndLocale or similar. + * @param value The number to be formatted. + * @param uresult The object that will be mutated to store the result; see unumf_openResult. + * @param ec Set if an error occurs. + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_formatDouble(const UNumberFormatter* uformatter, double value, UFormattedNumber* uresult, + UErrorCode* ec); + + +/** + * Uses a UNumberFormatter to format a decimal number to a UFormattedNumber. A string, field position, and + * other information can be retrieved from the UFormattedNumber. + * + * The UNumberFormatter can be shared between threads. Each thread should have its own local + * UFormattedNumber, however, for storing the result of the formatting operation. + * + * The syntax of the unformatted number is a "numeric string" as defined in the Decimal Arithmetic + * Specification, available at http://speleotrove.com/decimal + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param uformatter A formatter object created by unumf_openForSkeletonAndLocale or similar. + * @param value The numeric string to be formatted. + * @param valueLen The length of the numeric string, or -1 if it is NUL-terminated. + * @param uresult The object that will be mutated to store the result; see unumf_openResult. + * @param ec Set if an error occurs. + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_formatDecimal(const UNumberFormatter* uformatter, const char* value, int32_t valueLen, + UFormattedNumber* uresult, UErrorCode* ec); + + +/** + * Extracts the result number string out of a UFormattedNumber to a UChar buffer if possible. + * If bufferCapacity is greater than the required length, a terminating NUL is written. + * If bufferCapacity is less than the required length, an error code is set. + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param uresult The object containing the formatted number. + * @param buffer Where to save the string output. + * @param bufferCapacity The number of UChars available in the buffer. + * @param ec Set if an error occurs. + * @return The required length. + * @draft ICU 62 + */ +U_DRAFT int32_t U_EXPORT2 +unumf_resultToString(const UFormattedNumber* uresult, UChar* buffer, int32_t bufferCapacity, + UErrorCode* ec); + + +/** + * Determines the start and end indices of the next occurrence of the given field in the + * output string. This allows you to determine the locations of, for example, the integer part, + * fraction part, or symbols. + * + * If a field occurs just once, calling this method will find that occurrence and return it. If a + * field occurs multiple times, this method may be called repeatedly with the following pattern: + * + *
+ * UFieldPosition ufpos = {UNUM_GROUPING_SEPARATOR_FIELD, 0, 0};
+ * while (unumf_resultNextFieldPosition(uresult, ufpos, &ec)) {
+ *   // do something with ufpos.
+ * }
+ * 
+ * + * This method is useful if you know which field to query. If you want all available field position + * information, use unumf_resultGetAllFieldPositions(). + * + * NOTE: All fields of the UFieldPosition must be initialized before calling this method. + * + * @param fieldPosition + * Input+output variable. On input, the "field" property determines which field to look up, + * and the "endIndex" property determines where to begin the search. On output, the + * "beginIndex" field is set to the beginning of the first occurrence of the field after the + * input "endIndex", and "endIndex" is set to the end of that occurrence of the field + * (exclusive index). If a field position is not found, the FieldPosition is not changed and + * the method returns FALSE. + * @param ec Set if an error occurs. + * @draft ICU 62 + */ +U_DRAFT UBool U_EXPORT2 +unumf_resultNextFieldPosition(const UFormattedNumber* uresult, UFieldPosition* ufpos, UErrorCode* ec); + + +/** + * Populates the given iterator with all fields in the formatted output string. This allows you to + * determine the locations of the integer part, fraction part, and sign. + * + * If you need information on only one field, use unumf_resultNextFieldPosition(). + * + * @param uresult The object containing the formatted number. + * @param fpositer + * A pointer to a UFieldPositionIterator created by {@link #ufieldpositer_open}. Iteration + * information already present in the UFieldPositionIterator is deleted, and the iterator is reset + * to apply to the fields in the formatted string created by this function call. The field values + * and indexes returned by {@link #ufieldpositer_next} represent fields denoted by + * the UNumberFormatFields enum. Fields are not returned in a guaranteed order. Fields cannot + * overlap, but they may nest. For example, 1234 could format as "1,234" which might consist of a + * grouping separator field for ',' and an integer field encompassing the entire string. + * @param ec Set if an error occurs. + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_resultGetAllFieldPositions(const UFormattedNumber* uresult, UFieldPositionIterator* ufpositer, + UErrorCode* ec); + + +/** + * Releases the UNumberFormatter created by unumf_openForSkeletonAndLocale(). + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param uformatter An object created by unumf_openForSkeletonAndLocale(). + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_close(UNumberFormatter* uformatter); + + +/** + * Releases the UFormattedNumber created by unumf_openResult(). + * + * NOTE: This is a C-compatible API; C++ users should build against numberformatter.h instead. + * + * @param uresult An object created by unumf_openResult(). + * @draft ICU 62 + */ +U_DRAFT void U_EXPORT2 +unumf_closeResult(UFormattedNumber* uresult); + + +#if U_SHOW_CPLUSPLUS_API +U_NAMESPACE_BEGIN + +/** + * \class LocalUNumberFormatterPointer + * "Smart pointer" class; closes a UNumberFormatter via unumf_close(). + * For most methods see the LocalPointerBase base class. + * + * Usage: + *
+ * LocalUNumberFormatterPointer uformatter(unumf_openForSkeletonAndLocale(...));
+ * // no need to explicitly call unumf_close()
+ * 
+ * + * @see LocalPointerBase + * @see LocalPointer + * @draft ICU 62 + */ +U_DEFINE_LOCAL_OPEN_POINTER(LocalUNumberFormatterPointer, UNumberFormatter, unumf_close); + +/** + * \class LocalUNumberFormatterPointer + * "Smart pointer" class; closes a UFormattedNumber via unumf_closeResult(). + * For most methods see the LocalPointerBase base class. + * + * Usage: + *
+ * LocalUFormattedNumberPointer uformatter(unumf_openResult(...));
+ * // no need to explicitly call unumf_closeResult()
+ * 
+ * + * @see LocalPointerBase + * @see LocalPointer + * @draft ICU 62 + */ +U_DEFINE_LOCAL_OPEN_POINTER(LocalUFormattedNumberPointer, UFormattedNumber, unumf_closeResult); + +U_NAMESPACE_END +#endif // U_SHOW_CPLUSPLUS_API + +#endif /* U_HIDE_DRAFT_API */ + +#endif //__UNUMBERFORMATTER_H__ +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/unicode/uspoof.h b/deps/icu-small/source/i18n/unicode/uspoof.h index 9fcfcd3ede836c..781a88247fe4c5 100644 --- a/deps/icu-small/source/i18n/unicode/uspoof.h +++ b/deps/icu-small/source/i18n/unicode/uspoof.h @@ -477,7 +477,7 @@ typedef enum USpoofChecks { */ USPOOF_CHAR_LIMIT = 64, - /** + /** * Check that an identifier does not mix numbers from different numbering systems. * For more information, see UTS 39 section 5.3. * @@ -485,6 +485,29 @@ typedef enum USpoofChecks { */ USPOOF_MIXED_NUMBERS = 128, +#ifndef U_HIDE_DRAFT_API + /** + * Check that an identifier does not have a combining character following a character in which that + * combining character would be hidden; for example 'i' followed by a U+0307 combining dot. + * + * More specifically, the following characters are forbidden from preceding a U+0307: + *
    + *
  • Those with the Soft_Dotted Unicode property (which includes 'i' and 'j')
  • + *
  • Latin lowercase letter 'l'
  • + *
  • Dotless 'i' and 'j' ('ı' and 'ȷ', U+0131 and U+0237)
  • + *
  • Any character whose confusable prototype ends with such a character + * (Soft_Dotted, 'l', 'ı', or 'ȷ')
  • + *
+ * In addition, combining characters are allowed between the above characters and U+0307 except those + * with combining class 0 or combining class "Above" (230, same class as U+0307). + * + * This list and the number of combing characters considered by this check may grow over time. + * + * @draft ICU 62 + */ + USPOOF_HIDDEN_OVERLAY = 256, +#endif /* U_HIDE_DRAFT_API */ + /** * Enable all spoof checks. * diff --git a/deps/icu-small/source/i18n/upluralrules.cpp b/deps/icu-small/source/i18n/upluralrules.cpp index 24e74e3ee223f5..bba6dfe3101ec3 100644 --- a/deps/icu-small/source/i18n/upluralrules.cpp +++ b/deps/icu-small/source/i18n/upluralrules.cpp @@ -17,9 +17,44 @@ #include "unicode/unistr.h" #include "unicode/unum.h" #include "unicode/numfmt.h" +#include "number_decimalquantity.h" U_NAMESPACE_USE +namespace { + +/** + * Given a number and a format, returns the keyword of the first applicable + * rule for the PluralRules object. + * @param rules The plural rules. + * @param obj The numeric object for which the rule should be determined. + * @param fmt The NumberFormat specifying how the number will be formatted + * (this can affect the plural form, e.g. "1 dollar" vs "1.0 dollars"). + * @param status Input/output parameter. If at entry this indicates a + * failure status, the method returns immediately; otherwise + * this is set to indicate the outcome of the call. + * @return The keyword of the selected rule. Undefined in the case of an error. + */ +UnicodeString select(const PluralRules &rules, const Formattable& obj, const NumberFormat& fmt, UErrorCode& status) { + if (U_SUCCESS(status)) { + const DecimalFormat *decFmt = dynamic_cast(&fmt); + if (decFmt != NULL) { + number::impl::DecimalQuantity dq; + decFmt->formatToDecimalQuantity(obj, dq, status); + if (U_SUCCESS(status)) { + return rules.select(dq); + } + } else { + double number = obj.getDouble(status); + if (U_SUCCESS(status)) { + return rules.select(number); + } + } + } + return UnicodeString(); +} + +} // namespace U_CAPI UPluralRules* U_EXPORT2 uplrules_open(const char *locale, UErrorCode *status) @@ -73,7 +108,7 @@ uplrules_selectWithFormat(const UPluralRules *uplrules, return 0; } Formattable obj(number); - UnicodeString result = plrules->select(obj, *nf, *status); + UnicodeString result = select(*plrules, obj, *nf, *status); return result.extract(keyword, capacity, *status); } diff --git a/deps/icu-small/source/i18n/uspoof.cpp b/deps/icu-small/source/i18n/uspoof.cpp index 019819b11cdde1..710adcd08daeaf 100644 --- a/deps/icu-small/source/i18n/uspoof.cpp +++ b/deps/icu-small/source/i18n/uspoof.cpp @@ -55,75 +55,96 @@ uspoof_cleanup(void) { } static void U_CALLCONV initializeStatics(UErrorCode &status) { - static const char *inclusionPat = - "['\\-.\\:\\u00B7\\u0375\\u058A\\u05F3\\u05F4\\u06FD\\u06FE\\u0F0B\\u200C\\u200D\\u2010\\u" - "2019\\u2027\\u30A0\\u30FB]"; - gInclusionSet = new UnicodeSet(UnicodeString(inclusionPat, -1, US_INV), status); + static const char16_t *inclusionPat = + u"['\\-.\\:\\u00B7\\u0375\\u058A\\u05F3\\u05F4\\u06FD\\u06FE\\u0F0B\\u200C" + u"\\u200D\\u2010\\u2019\\u2027\\u30A0\\u30FB]"; + gInclusionSet = new UnicodeSet(UnicodeString(inclusionPat), status); + if (gInclusionSet == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } gInclusionSet->freeze(); - // Note: data from http://unicode.org/Public/security/9.0.0/IdentifierStatus.txt + // Note: data from IdentifierStatus.txt & IdentifierType.txt // There is tooling to generate this constant in the unicodetools project: // org.unicode.text.tools.RecommendedSetGenerator // It will print the Java and C++ code to the console for easy copy-paste into this file. // Note: concatenated string constants do not work with UNICODE_STRING_SIMPLE on all platforms. - static const char *recommendedPat = - "[0-9A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u0131\\u0134-\\u013E\\u0141-\\u014" - "8\\u014A-\\u017E\\u018F\\u01A0\\u01A1\\u01AF\\u01B0\\u01CD-\\u01DC\\u01DE-\\u01E3\\u01E" - "6-\\u01F0\\u01F4\\u01F5\\u01F8-\\u021B\\u021E\\u021F\\u0226-\\u0233\\u0259\\u02BB\\u02B" - "C\\u02EC\\u0300-\\u0304\\u0306-\\u030C\\u030F-\\u0311\\u0313\\u0314\\u031B\\u0323-\\u03" - "28\\u032D\\u032E\\u0330\\u0331\\u0335\\u0338\\u0339\\u0342\\u0345\\u037B-\\u037D\\u0386" - "\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03CE\\u03FC-\\u045F\\u048A-\\u0529\\u05" - "2E\\u052F\\u0531-\\u0556\\u0559\\u0561-\\u0586\\u05B4\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0" - "620-\\u063F\\u0641-\\u0655\\u0660-\\u0669\\u0670-\\u0672\\u0674\\u0679-\\u068D\\u068F-" - "\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE-\\u06FC\\u06FF\\u0750-\\u07B1\\u08A0-\\u08AC\\u08B2" - "\\u08B6-\\u08BD\\u0901-\\u094D\\u094F\\u0950\\u0956\\u0957\\u0960-\\u0963\\u0966-\\u096" - "F\\u0971-\\u0977\\u0979-\\u097F\\u0981-\\u0983\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u0" - "9A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BC-\\u09C4\\u09C7\\u09C8\\u09CB-\\u09CE\\u" - "09D7\\u09E0-\\u09E3\\u09E6-\\u09F1\\u0A01-\\u0A03\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-" - "\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A35\\u0A38\\u0A39\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\" - "u0A4B-\\u0A4D\\u0A5C\\u0A66-\\u0A74\\u0A81-\\u0A83\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A9" - "3-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABC-\\u0AC5\\u0AC7-\\u0AC9\\u0" - "ACB-\\u0ACD\\u0AD0\\u0AE0-\\u0AE3\\u0AE6-\\u0AEF\\u0B01-\\u0B03\\u0B05-\\u0B0C\\u0B0F\\" - "u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3C-\\u0B43\\u0B47" - "\\u0B48\\u0B4B-\\u0B4D\\u0B56\\u0B57\\u0B5F-\\u0B61\\u0B66-\\u0B6F\\u0B71\\u0B82\\u0B83" - "\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3" - "\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BBE-\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCD\\u0B" - "D0\\u0BD7\\u0BE6-\\u0BEF\\u0C01-\\u0C03\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u" - "0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D-\\u0C44\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56" - "\\u0C60\\u0C61\\u0C66-\\u0C6F\\u0C80\\u0C82\\u0C83\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92" - "-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBC-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0" - "CD5\\u0CD6\\u0CE0-\\u0CE3\\u0CE6-\\u0CEF\\u0CF1\\u0CF2\\u0D02\\u0D03\\u0D05-\\u0D0C\\u0" - "D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D-\\u0D43\\u0D46-\\u0D48\\u0D4A-\\u0D4E\\u0D54-\\u0D57" - "\\u0D60\\u0D61\\u0D66-\\u0D6F\\u0D7A-\\u0D7F\\u0D82\\u0D83\\u0D85-\\u0D8E\\u0D91-\\u0D9" - "6\\u0D9A-\\u0DA5\\u0DA7-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0DCA\\u0DCF-\\u0" - "DD4\\u0DD6\\u0DD8-\\u0DDE\\u0DF2\\u0E01-\\u0E32\\u0E34-\\u0E3A\\u0E40-\\u0E4E\\u0E50-\\" - "u0E59\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u" - "0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB2\\u0EB4-\\u0EB9\\u0EBB-\\u0EBD\\" - "u0EC0-\\u0EC4\\u0EC6\\u0EC8-\\u0ECD\\u0ED0-\\u0ED9\\u0EDE\\u0EDF\\u0F00\\u0F20-\\u0F29" - "\\u0F35\\u0F37\\u0F3E-\\u0F42\\u0F44-\\u0F47\\u0F49-\\u0F4C\\u0F4E-\\u0F51\\u0F53-\\u0F" - "56\\u0F58-\\u0F5B\\u0F5D-\\u0F68\\u0F6A-\\u0F6C\\u0F71\\u0F72\\u0F74\\u0F7A-\\u0F80\\u0" - "F82-\\u0F84\\u0F86-\\u0F92\\u0F94-\\u0F97\\u0F99-\\u0F9C\\u0F9E-\\u0FA1\\u0FA3-\\u0FA6" - "\\u0FA8-\\u0FAB\\u0FAD-\\u0FB8\\u0FBA-\\u0FBC\\u0FC6\\u1000-\\u1049\\u1050-\\u109D\\u10" - "C7\\u10CD\\u10D0-\\u10F0\\u10F7-\\u10FA\\u10FD-\\u10FF\\u1200-\\u1248\\u124A-\\u124D\\u" - "1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2" - "-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1" - "315\\u1318-\\u135A\\u135D-\\u135F\\u1380-\\u138F\\u1780-\\u17A2\\u17A5-\\u17A7\\u17A9-" - "\\u17B3\\u17B6-\\u17CA\\u17D2\\u17D7\\u17DC\\u17E0-\\u17E9\\u1C80-\\u1C88\\u1E00-\\u1E9" - "9\\u1E9E\\u1EA0-\\u1EF9\\u1F00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1" - "F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F70\\u1F72\\u1F74\\u1F76\\u1F78\\u1F7A\\u1F" - "7C\\u1F80-\\u1FB4\\u1FB6-\\u1FBA\\u1FBC\\u1FC2-\\u1FC4\\u1FC6-\\u1FC8\\u1FCA\\u1FCC\\u1" - "FD0-\\u1FD2\\u1FD6-\\u1FDA\\u1FE0-\\u1FE2\\u1FE4-\\u1FEA\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-" - "\\u1FF8\\u1FFA\\u1FFC\\u2D27\\u2D2D\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0" - "-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u3" - "005-\\u3007\\u3041-\\u3096\\u3099\\u309A\\u309D\\u309E\\u30A1-\\u30FA\\u30FC-\\u30FE\\u" - "3105-\\u312D\\u31A0-\\u31BA\\u3400-\\u4DB5\\u4E00-\\u9FD5\\uA660\\uA661\\uA674-\\uA67B" - "\\uA67F\\uA69F\\uA717-\\uA71F\\uA788\\uA78D\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7AE" - "\\uA7FA\\uA9E7-\\uA9FE\\uAA60-\\uAA76\\uAA7A-\\uAA7F\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB" - "11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAC00-\\uD7A3\\uFA0E\\uFA0F\\uFA11\\uFA13\\uF" - "A14\\uFA1F\\uFA21\\uFA23\\uFA24\\uFA27-\\uFA29\\U00020000-\\U0002A6D6\\U0002A700-\\U0" - "002B734\\U0002B740-\\U0002B81D\\U0002B820-\\U0002CEA1]"; - - gRecommendedSet = new UnicodeSet(UnicodeString(recommendedPat, -1, US_INV), status); + static const char16_t *recommendedPat = + u"[0-9A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u0131\\u0134-\\u013E" + u"\\u0141-\\u0148\\u014A-\\u017E\\u018F\\u01A0\\u01A1\\u01AF\\u01B0\\u01CD-" + u"\\u01DC\\u01DE-\\u01E3\\u01E6-\\u01F0\\u01F4\\u01F5\\u01F8-\\u021B\\u021E" + u"\\u021F\\u0226-\\u0233\\u0259\\u02BB\\u02BC\\u02EC\\u0300-\\u0304\\u0306-" + u"\\u030C\\u030F-\\u0311\\u0313\\u0314\\u031B\\u0323-\\u0328\\u032D\\u032E" + u"\\u0330\\u0331\\u0335\\u0338\\u0339\\u0342\\u0345\\u037B-\\u037D\\u0386" + u"\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03CE\\u03FC-\\u045F\\u048A-" + u"\\u0529\\u052E\\u052F\\u0531-\\u0556\\u0559\\u0560-\\u0586\\u0588\\u05B4" + u"\\u05D0-\\u05EA\\u05EF-\\u05F2\\u0620-\\u063F\\u0641-\\u0655\\u0660-\\u0669" + u"\\u0670-\\u0672\\u0674\\u0679-\\u068D\\u068F-\\u06D3\\u06D5\\u06E5\\u06E6" + u"\\u06EE-\\u06FC\\u06FF\\u0750-\\u07B1\\u08A0-\\u08AC\\u08B2\\u08B6-\\u08BD" + u"\\u0901-\\u094D\\u094F\\u0950\\u0956\\u0957\\u0960-\\u0963\\u0966-\\u096F" + u"\\u0971-\\u0977\\u0979-\\u097F\\u0981-\\u0983\\u0985-\\u098C\\u098F\\u0990" + u"\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BC-\\u09C4\\u09C7" + u"\\u09C8\\u09CB-\\u09CE\\u09D7\\u09E0-\\u09E3\\u09E6-\\u09F1\\u09FC\\u09FE" + u"\\u0A01-\\u0A03\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30" + u"\\u0A32\\u0A35\\u0A38\\u0A39\\u0A3C\\u0A3E-\\u0A42\\u0A47\\u0A48\\u0A4B-" + u"\\u0A4D\\u0A5C\\u0A66-\\u0A74\\u0A81-\\u0A83\\u0A85-\\u0A8D\\u0A8F-\\u0A91" + u"\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABC-\\u0AC5" + u"\\u0AC7-\\u0AC9\\u0ACB-\\u0ACD\\u0AD0\\u0AE0-\\u0AE3\\u0AE6-\\u0AEF\\u0AFA-" + u"\\u0AFF\\u0B01-\\u0B03\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-" + u"\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3C-\\u0B43\\u0B47\\u0B48\\u0B4B-" + u"\\u0B4D\\u0B56\\u0B57\\u0B5F-\\u0B61\\u0B66-\\u0B6F\\u0B71\\u0B82\\u0B83" + u"\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E" + u"\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BBE-\\u0BC2\\u0BC6-" + u"\\u0BC8\\u0BCA-\\u0BCD\\u0BD0\\u0BD7\\u0BE6-\\u0BEF\\u0C01-\\u0C0C\\u0C0E-" + u"\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C33\\u0C35-\\u0C39\\u0C3D-\\u0C44\\u0C46-" + u"\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C60\\u0C61\\u0C66-\\u0C6F\\u0C80" + u"\\u0C82\\u0C83\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3" + u"\\u0CB5-\\u0CB9\\u0CBC-\\u0CC4\\u0CC6-\\u0CC8\\u0CCA-\\u0CCD\\u0CD5\\u0CD6" + u"\\u0CE0-\\u0CE3\\u0CE6-\\u0CEF\\u0CF1\\u0CF2\\u0D00\\u0D02\\u0D03\\u0D05-" + u"\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D43\\u0D46-\\u0D48\\u0D4A-\\u0D4E\\u0D54-" + u"\\u0D57\\u0D60\\u0D61\\u0D66-\\u0D6F\\u0D7A-\\u0D7F\\u0D82\\u0D83\\u0D85-" + u"\\u0D8E\\u0D91-\\u0D96\\u0D9A-\\u0DA5\\u0DA7-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD" + u"\\u0DC0-\\u0DC6\\u0DCA\\u0DCF-\\u0DD4\\u0DD6\\u0DD8-\\u0DDE\\u0DF2\\u0E01-" + u"\\u0E32\\u0E34-\\u0E3A\\u0E40-\\u0E4E\\u0E50-\\u0E59\\u0E81\\u0E82\\u0E84" + u"\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3" + u"\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB2\\u0EB4-\\u0EB9\\u0EBB-\\u0EBD" + u"\\u0EC0-\\u0EC4\\u0EC6\\u0EC8-\\u0ECD\\u0ED0-\\u0ED9\\u0EDE\\u0EDF\\u0F00" + u"\\u0F20-\\u0F29\\u0F35\\u0F37\\u0F3E-\\u0F42\\u0F44-\\u0F47\\u0F49-\\u0F4C" + u"\\u0F4E-\\u0F51\\u0F53-\\u0F56\\u0F58-\\u0F5B\\u0F5D-\\u0F68\\u0F6A-\\u0F6C" + u"\\u0F71\\u0F72\\u0F74\\u0F7A-\\u0F80\\u0F82-\\u0F84\\u0F86-\\u0F92\\u0F94-" + u"\\u0F97\\u0F99-\\u0F9C\\u0F9E-\\u0FA1\\u0FA3-\\u0FA6\\u0FA8-\\u0FAB\\u0FAD-" + u"\\u0FB8\\u0FBA-\\u0FBC\\u0FC6\\u1000-\\u1049\\u1050-\\u109D\\u10C7\\u10CD" + u"\\u10D0-\\u10F0\\u10F7-\\u10FA\\u10FD-\\u10FF\\u1200-\\u1248\\u124A-\\u124D" + u"\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-" + u"\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6" + u"\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u135D-\\u135F\\u1380-\\u138F" + u"\\u1780-\\u17A2\\u17A5-\\u17A7\\u17A9-\\u17B3\\u17B6-\\u17CA\\u17D2\\u17D7" + u"\\u17DC\\u17E0-\\u17E9\\u1C80-\\u1C88\\u1C90-\\u1CBA\\u1CBD-\\u1CBF\\u1E00-" + u"\\u1E99\\u1E9E\\u1EA0-\\u1EF9\\u1F00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45" + u"\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F70\\u1F72" + u"\\u1F74\\u1F76\\u1F78\\u1F7A\\u1F7C\\u1F80-\\u1FB4\\u1FB6-\\u1FBA\\u1FBC" + u"\\u1FC2-\\u1FC4\\u1FC6-\\u1FC8\\u1FCA\\u1FCC\\u1FD0-\\u1FD2\\u1FD6-\\u1FDA" + u"\\u1FE0-\\u1FE2\\u1FE4-\\u1FEA\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FF8\\u1FFA" + u"\\u1FFC\\u2D27\\u2D2D\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-" + u"\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-" + u"\\u2DDE\\u3005-\\u3007\\u3041-\\u3096\\u3099\\u309A\\u309D\\u309E\\u30A1-" + u"\\u30FA\\u30FC-\\u30FE\\u3105-\\u312F\\u31A0-\\u31BA\\u3400-\\u4DB5\\u4E00-" + u"\\u9FEF\\uA660\\uA661\\uA674-\\uA67B\\uA67F\\uA69F\\uA717-\\uA71F\\uA788" + u"\\uA78D\\uA78E\\uA790-\\uA793\\uA7A0-\\uA7AA\\uA7AE\\uA7AF\\uA7B8\\uA7B9" + u"\\uA7FA\\uA9E7-\\uA9FE\\uAA60-\\uAA76\\uAA7A-\\uAA7F\\uAB01-\\uAB06\\uAB09-" + u"\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAC00-\\uD7A3\\uFA0E" + u"\\uFA0F\\uFA11\\uFA13\\uFA14\\uFA1F\\uFA21\\uFA23\\uFA24\\uFA27-\\uFA29" + u"\\U0001133B\\U0001B002-\\U0001B11E\\U00020000-\\U0002A6D6\\U0002A700-" + u"\\U0002B734\\U0002B740-\\U0002B81D\\U0002B820-\\U0002CEA1\\U0002CEB0-" + u"\\U0002EBE0]"; + + gRecommendedSet = new UnicodeSet(UnicodeString(recommendedPat), status); + if (gRecommendedSet == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + delete gInclusionSet; + return; + } gRecommendedSet->freeze(); gNfdNormalizer = Normalizer2::getNFDInstance(status); ucln_i18n_registerCleanup(UCLN_I18N_SPOOF, uspoof_cleanup); @@ -140,12 +161,13 @@ uspoof_open(UErrorCode *status) { return NULL; } SpoofImpl *si = new SpoofImpl(*status); - if (U_SUCCESS(*status) && si == NULL) { + if (si == NULL) { *status = U_MEMORY_ALLOCATION_ERROR; + return NULL; } if (U_FAILURE(*status)) { delete si; - si = NULL; + return NULL; } return si->asUSpoofChecker(); } @@ -157,18 +179,38 @@ uspoof_openFromSerialized(const void *data, int32_t length, int32_t *pActualLeng if (U_FAILURE(*status)) { return NULL; } + + if (data == NULL) { + *status = U_ILLEGAL_ARGUMENT_ERROR; + return NULL; + } + umtx_initOnce(gSpoofInitStaticsOnce, &initializeStatics, *status); + if (U_FAILURE(*status)) + { + return NULL; + } + SpoofData *sd = new SpoofData(data, length, *status); - SpoofImpl *si = new SpoofImpl(sd, *status); + if (sd == NULL) { + *status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + if (U_FAILURE(*status)) { delete sd; - delete si; return NULL; } - if (sd == NULL || si == NULL) { + + SpoofImpl *si = new SpoofImpl(sd, *status); + if (si == NULL) { *status = U_MEMORY_ALLOCATION_ERROR; - delete sd; - delete si; + delete sd; // explicit delete as the destructor for si won't be called. + return NULL; + } + + if (U_FAILURE(*status)) { + delete si; // no delete for sd, as the si destructor will delete it. return NULL; } @@ -186,6 +228,10 @@ uspoof_clone(const USpoofChecker *sc, UErrorCode *status) { return NULL; } SpoofImpl *result = new SpoofImpl(*src, *status); // copy constructor + if (result == NULL) { + *status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } if (U_FAILURE(*status)) { delete result; result = NULL; @@ -524,6 +570,13 @@ int32_t checkImpl(const SpoofImpl* This, const UnicodeString& id, CheckResult* c checkResult->fNumerics = numerics; // UnicodeSet::operator= } + if (0 != (This->fChecks & USPOOF_HIDDEN_OVERLAY)) { + int32_t index = This->findHiddenOverlay(id, *status); + if (index != -1) { + result |= USPOOF_HIDDEN_OVERLAY; + } + } + if (0 != (This->fChecks & USPOOF_CHAR_LIMIT)) { int32_t i; diff --git a/deps/icu-small/source/i18n/uspoof_build.cpp b/deps/icu-small/source/i18n/uspoof_build.cpp index 7d2440e5af65e8..7087c1ce596ced 100644 --- a/deps/icu-small/source/i18n/uspoof_build.cpp +++ b/deps/icu-small/source/i18n/uspoof_build.cpp @@ -71,8 +71,29 @@ uspoof_openFromSource(const char *confusables, int32_t confusablesLen, // Set up a shell of a spoof detector, with empty data. SpoofData *newSpoofData = new SpoofData(*status); + + if (newSpoofData == NULL) { + *status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } + + if (U_FAILURE(*status)) { + delete newSpoofData; + return NULL; + } SpoofImpl *This = new SpoofImpl(newSpoofData, *status); + if (This == NULL) { + *status = U_MEMORY_ALLOCATION_ERROR; + delete newSpoofData; // explicit delete as the destructor for SpoofImpl won't be called. + return NULL; + } + + if (U_FAILURE(*status)) { + delete This; // no delete for newSpoofData, as the SpoofImpl destructor will delete it. + return NULL; + } + // Compile the binary data from the source (text) format. ConfusabledataBuilder::buildConfusableData(This, confusables, confusablesLen, errorType, pe, *status); diff --git a/deps/icu-small/source/i18n/uspoof_conf.cpp b/deps/icu-small/source/i18n/uspoof_conf.cpp index 3a061d9dfcffe1..672b3e0a6c8023 100644 --- a/deps/icu-small/source/i18n/uspoof_conf.cpp +++ b/deps/icu-small/source/i18n/uspoof_conf.cpp @@ -76,6 +76,10 @@ SPUString::~SPUString() { SPUStringPool::SPUStringPool(UErrorCode &status) : fVec(NULL), fHash(NULL) { fVec = new UVector(status); + if (fVec == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } fHash = uhash_open(uhash_hashUnicodeString, // key hash function uhash_compareUnicodeString, // Key Comparator NULL, // Value Comparator @@ -136,6 +140,10 @@ SPUString *SPUStringPool::addString(UnicodeString *src, UErrorCode &status) { delete src; } else { hashedString = new SPUString(src); + if (hashedString == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return NULL; + } uhash_put(fHash, src, hashedString, &status); fVec->addElement(hashedString, status); } @@ -160,11 +168,32 @@ ConfusabledataBuilder::ConfusabledataBuilder(SpoofImpl *spImpl, UErrorCode &stat if (U_FAILURE(status)) { return; } - fTable = uhash_open(uhash_hashLong, uhash_compareLong, NULL, &status); - fKeySet = new UnicodeSet(); - fKeyVec = new UVector(status); - fValueVec = new UVector(status); + + fTable = uhash_open(uhash_hashLong, uhash_compareLong, NULL, &status); + + fKeySet = new UnicodeSet(); + if (fKeySet == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + + fKeyVec = new UVector(status); + if (fKeyVec == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + + fValueVec = new UVector(status); + if (fValueVec == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + stringPool = new SPUStringPool(status); + if (stringPool == NULL) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } } diff --git a/deps/icu-small/source/i18n/uspoof_impl.cpp b/deps/icu-small/source/i18n/uspoof_impl.cpp index 0ca85c00a98a8d..c1034c2e53f6e9 100644 --- a/deps/icu-small/source/i18n/uspoof_impl.cpp +++ b/deps/icu-small/source/i18n/uspoof_impl.cpp @@ -377,6 +377,43 @@ URestrictionLevel SpoofImpl::getRestrictionLevel(const UnicodeString& input, UEr return USPOOF_MINIMALLY_RESTRICTIVE; } +int32_t SpoofImpl::findHiddenOverlay(const UnicodeString& input, UErrorCode&) const { + bool sawLeadCharacter = false; + for (int32_t i=0; iconfusableLookup(cp, skelStr); + UChar32 finalCp = skelStr.char32At(skelStr.moveIndex32(skelStr.length(), -1)); + if (finalCp != cp && isIllegalCombiningDotLeadCharacterNoLookup(finalCp)) { + return true; + } + return false; +} + // Convert a text format hex number. Utility function used by builder code. Static. @@ -532,24 +569,25 @@ uspoof_cleanupDefaultData(void) { if (gDefaultSpoofData) { // Will delete, assuming all user-level spoof checkers were closed. gDefaultSpoofData->removeReference(); - gDefaultSpoofData = NULL; + gDefaultSpoofData = nullptr; gSpoofInitDefaultOnce.reset(); } return TRUE; } static void U_CALLCONV uspoof_loadDefaultData(UErrorCode& status) { - UDataMemory *udm = udata_openChoice(NULL, "cfu", "confusables", + UDataMemory *udm = udata_openChoice(nullptr, "cfu", "confusables", spoofDataIsAcceptable, - NULL, // context, would receive dataVersion if supplied. + nullptr, // context, would receive dataVersion if supplied. &status); if (U_FAILURE(status)) { return; } gDefaultSpoofData = new SpoofData(udm, status); if (U_FAILURE(status)) { delete gDefaultSpoofData; + gDefaultSpoofData = nullptr; return; } - if (gDefaultSpoofData == NULL) { + if (gDefaultSpoofData == nullptr) { status = U_MEMORY_ALLOCATION_ERROR; return; } @@ -590,6 +628,10 @@ SpoofData::SpoofData(const void *data, int32_t length, UErrorCode &status) status = U_INVALID_FORMAT_ERROR; return; } + if (data == NULL) { + status = U_ILLEGAL_ARGUMENT_ERROR; + return; + } void *ncData = const_cast(data); fRawData = static_cast(ncData); if (length < fRawData->fLength) { diff --git a/deps/icu-small/source/i18n/uspoof_impl.h b/deps/icu-small/source/i18n/uspoof_impl.h index 1184b8d9060662..470a31f2c979bc 100644 --- a/deps/icu-small/source/i18n/uspoof_impl.h +++ b/deps/icu-small/source/i18n/uspoof_impl.h @@ -83,6 +83,9 @@ class SpoofImpl : public UObject { void getNumerics(const UnicodeString& input, UnicodeSet& result, UErrorCode& status) const; URestrictionLevel getRestrictionLevel(const UnicodeString& input, UErrorCode& status) const; + int32_t findHiddenOverlay(const UnicodeString& input, UErrorCode& status) const; + bool isIllegalCombiningDotLeadCharacter(UChar32 cp) const; + /** parse a hex number. Untility used by the builders. */ static UChar32 ScanHex(const UChar *s, int32_t start, int32_t limit, UErrorCode &status); diff --git a/deps/icu-small/source/i18n/valueformatter.cpp b/deps/icu-small/source/i18n/valueformatter.cpp deleted file mode 100644 index e769f369d48609..00000000000000 --- a/deps/icu-small/source/i18n/valueformatter.cpp +++ /dev/null @@ -1,223 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines Corporation and -* others. All Rights Reserved. -******************************************************************************* -*/ - -#include "unicode/plurrule.h" -#include "unicode/unistr.h" -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "digitformatter.h" -#include "digitgrouping.h" -#include "digitinterval.h" -#include "digitlst.h" -#include "precision.h" -#include "plurrule_impl.h" -#include "smallintformatter.h" -#include "uassert.h" -#include "valueformatter.h" -#include "visibledigits.h" - -U_NAMESPACE_BEGIN - -ValueFormatter::~ValueFormatter() {} - -VisibleDigitsWithExponent & -ValueFormatter::toVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - switch (fType) { - case kFixedDecimal: - return fFixedPrecision->initVisibleDigitsWithExponent( - value, digits, status); - break; - case kScientificNotation: - return fScientificPrecision->initVisibleDigitsWithExponent( - value, digits, status); - break; - default: - U_ASSERT(FALSE); - break; - } - return digits; -} - -VisibleDigitsWithExponent & -ValueFormatter::toVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const { - switch (fType) { - case kFixedDecimal: - return fFixedPrecision->initVisibleDigitsWithExponent( - value, digits, status); - break; - case kScientificNotation: - return fScientificPrecision->initVisibleDigitsWithExponent( - value, digits, status); - break; - default: - U_ASSERT(FALSE); - break; - } - return digits; -} - -static UBool isNoGrouping( - const DigitGrouping &grouping, - int32_t value, - const FixedPrecision &precision) { - IntDigitCountRange range( - precision.fMin.getIntDigitCount(), - precision.fMax.getIntDigitCount()); - return grouping.isNoGrouping(value, range); -} - -UBool -ValueFormatter::isFastFormattable(int32_t value) const { - switch (fType) { - case kFixedDecimal: - { - if (value == INT32_MIN) { - return FALSE; - } - if (value < 0) { - value = -value; - } - return fFixedPrecision->isFastFormattable() && fFixedOptions->isFastFormattable() && isNoGrouping(*fGrouping, value, *fFixedPrecision); - } - case kScientificNotation: - return FALSE; - default: - U_ASSERT(FALSE); - break; - } - return FALSE; -} - -DigitList & -ValueFormatter::round(DigitList &value, UErrorCode &status) const { - if (value.isNaN() || value.isInfinite()) { - return value; - } - switch (fType) { - case kFixedDecimal: - return fFixedPrecision->round(value, 0, status); - case kScientificNotation: - return fScientificPrecision->round(value, status); - default: - U_ASSERT(FALSE); - break; - } - return value; -} - -UnicodeString & -ValueFormatter::formatInt32( - int32_t value, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - switch (fType) { - case kFixedDecimal: - { - IntDigitCountRange range( - fFixedPrecision->fMin.getIntDigitCount(), - fFixedPrecision->fMax.getIntDigitCount()); - return fDigitFormatter->formatPositiveInt32( - value, - range, - handler, - appendTo); - } - break; - case kScientificNotation: - default: - U_ASSERT(FALSE); - break; - } - return appendTo; -} - -UnicodeString & -ValueFormatter::format( - const VisibleDigitsWithExponent &value, - FieldPositionHandler &handler, - UnicodeString &appendTo) const { - switch (fType) { - case kFixedDecimal: - return fDigitFormatter->format( - value.getMantissa(), - *fGrouping, - *fFixedOptions, - handler, - appendTo); - break; - case kScientificNotation: - return fDigitFormatter->format( - value, - *fScientificOptions, - handler, - appendTo); - break; - default: - U_ASSERT(FALSE); - break; - } - return appendTo; -} - -int32_t -ValueFormatter::countChar32(const VisibleDigitsWithExponent &value) const { - switch (fType) { - case kFixedDecimal: - return fDigitFormatter->countChar32( - value.getMantissa(), - *fGrouping, - *fFixedOptions); - break; - case kScientificNotation: - return fDigitFormatter->countChar32( - value, - *fScientificOptions); - break; - default: - U_ASSERT(FALSE); - break; - } - return 0; -} - -void -ValueFormatter::prepareFixedDecimalFormatting( - const DigitFormatter &formatter, - const DigitGrouping &grouping, - const FixedPrecision &precision, - const DigitFormatterOptions &options) { - fType = kFixedDecimal; - fDigitFormatter = &formatter; - fGrouping = &grouping; - fFixedPrecision = &precision; - fFixedOptions = &options; -} - -void -ValueFormatter::prepareScientificFormatting( - const DigitFormatter &formatter, - const ScientificPrecision &precision, - const SciFormatterOptions &options) { - fType = kScientificNotation; - fDigitFormatter = &formatter; - fScientificPrecision = &precision; - fScientificOptions = &options; -} - -U_NAMESPACE_END - -#endif /* !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/valueformatter.h b/deps/icu-small/source/i18n/valueformatter.h deleted file mode 100644 index 836a05b17c5209..00000000000000 --- a/deps/icu-small/source/i18n/valueformatter.h +++ /dev/null @@ -1,161 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* -* Copyright (C) 2015, International Business Machines Corporation and * -* others. All Rights Reserved. * -******************************************************************************* -*/ - -#ifndef VALUEFORMATTER_H -#define VALUEFORMATTER_H - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" -#include "unicode/utypes.h" - - - -U_NAMESPACE_BEGIN - -class UnicodeString; -class DigitList; -class FieldPositionHandler; -class DigitGrouping; -class PluralRules; -class FixedPrecision; -class DigitFormatter; -class DigitFormatterOptions; -class ScientificPrecision; -class SciFormatterOptions; -class FixedDecimal; -class VisibleDigitsWithExponent; - - -/** - * A closure around rounding and formatting a value. As these instances are - * designed to be short lived (they only exist while formatting a value), they - * do not own their own attributes. Rather the caller maintains ownership of - * all attributes. A caller first calls a prepareXXX method on an instance - * to share its data before using that instance. Using an - * instance without first calling a prepareXXX method results in an - * assertion error and a program crash. - */ -class U_I18N_API ValueFormatter : public UObject { -public: - ValueFormatter() : fType(kFormatTypeCount) { - } - - virtual ~ValueFormatter(); - - /** - * This function is here only to support the protected round() method - * in DecimalFormat. It serves no ther purpose than that. - * - * @param value this value is rounded in place. - * @param status any error returned here. - */ - DigitList &round(DigitList &value, UErrorCode &status) const; - - /** - * Returns TRUE if the absolute value of value can be fast formatted - * using ValueFormatter::formatInt32. - */ - UBool isFastFormattable(int32_t value) const; - - /** - * Converts value to a VisibleDigitsWithExponent. - * Result may be fixed point or scientific. - */ - VisibleDigitsWithExponent &toVisibleDigitsWithExponent( - int64_t value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * Converts value to a VisibleDigitsWithExponent. - * Result may be fixed point or scientific. - */ - VisibleDigitsWithExponent &toVisibleDigitsWithExponent( - DigitList &value, - VisibleDigitsWithExponent &digits, - UErrorCode &status) const; - - /** - * formats positiveValue and appends to appendTo. Returns appendTo. - * @param positiveValue If negative, no negative sign is formatted. - * @param handler stores the field positions - * @param appendTo formatted value appended here. - */ - UnicodeString &format( - const VisibleDigitsWithExponent &positiveValue, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - - - /** - * formats positiveValue and appends to appendTo. Returns appendTo. - * value must be positive. Calling formatInt32 to format a value when - * isFastFormattable indicates that the value cannot be fast formatted - * results in undefined behavior. - */ - UnicodeString &formatInt32( - int32_t positiveValue, - FieldPositionHandler &handler, - UnicodeString &appendTo) const; - - /** - * Returns the number of code points needed to format. - * @param positiveValue if negative, the negative sign is not included - * in count. - */ - int32_t countChar32( - const VisibleDigitsWithExponent &positiveValue) const; - - /** - * Prepares this instance for fixed decimal formatting. - */ - void prepareFixedDecimalFormatting( - const DigitFormatter &formatter, - const DigitGrouping &grouping, - const FixedPrecision &precision, - const DigitFormatterOptions &options); - - /** - * Prepares this instance for scientific formatting. - */ - void prepareScientificFormatting( - const DigitFormatter &formatter, - const ScientificPrecision &precision, - const SciFormatterOptions &options); - -private: - ValueFormatter(const ValueFormatter &); - ValueFormatter &operator=(const ValueFormatter &); - enum FormatType { - kFixedDecimal, - kScientificNotation, - kFormatTypeCount - }; - - FormatType fType; - - // for fixed decimal and scientific formatting - const DigitFormatter *fDigitFormatter; - - // for fixed decimal formatting - const FixedPrecision *fFixedPrecision; - const DigitFormatterOptions *fFixedOptions; - const DigitGrouping *fGrouping; - - // for scientific formatting - const ScientificPrecision *fScientificPrecision; - const SciFormatterOptions *fScientificOptions; -}; - -U_NAMESPACE_END - -#endif /* !UCONFIG_NO_FORMATTING */ - -#endif /* VALUEFORMATTER_H */ diff --git a/deps/icu-small/source/i18n/visibledigits.cpp b/deps/icu-small/source/i18n/visibledigits.cpp deleted file mode 100644 index 03cfc68d255380..00000000000000 --- a/deps/icu-small/source/i18n/visibledigits.cpp +++ /dev/null @@ -1,186 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* - * Copyright (C) 2016, International Business Machines - * Corporation and others. All Rights Reserved. - * - * file name: visibledigits.cpp - */ - -#include - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "cstring.h" -#include "decNumber.h" -#include "digitlst.h" -#include "uassert.h" -#include "visibledigits.h" - -static const int32_t kNegative = 1; -static const int32_t kInfinite = 2; -static const int32_t kNaN = 4; - -U_NAMESPACE_BEGIN - -void VisibleDigits::setNegative() { - fFlags |= kNegative; -} - -void VisibleDigits::setNaN() { - fFlags |= kNaN; -} - -void VisibleDigits::setInfinite() { - fFlags |= kInfinite; -} - -void VisibleDigits::clear() { - fInterval.clear(); - fDigits.clear(); - fExponent = 0; - fFlags = 0; - fAbsIntValue = 0LL; - fAbsIntValueSet = FALSE; - fAbsDoubleValue = 0.0; - fAbsDoubleValueSet = FALSE; -} - -UBool VisibleDigits::isNegative() const { - return (fFlags & kNegative); -} - -UBool VisibleDigits::isNaN() const { - return (fFlags & kNaN); -} - -UBool VisibleDigits::isInfinite() const { - return (fFlags & kInfinite); -} - -int32_t VisibleDigits::getDigitByExponent(int32_t digitPos) const { - if (digitPos < fExponent || digitPos >= fExponent + fDigits.length()) { - return 0; - } - const char *ptr = fDigits.data(); - return ptr[digitPos - fExponent]; -} - -UBool VisibleDigits::isOverMaxDigits() const { - return (fExponent + fDigits.length() > fInterval.getMostSignificantExclusive()); -} - -UBool VisibleDigits::isNaNOrInfinity() const { - return (fFlags & (kInfinite | kNaN)) != 0; -} - -double VisibleDigits::computeAbsDoubleValue() const { - // Take care of NaN and infinity - if (isNaN()) { - return uprv_getNaN(); - } - if (isInfinite()) { - return uprv_getInfinity(); - } - - // stack allocate a decNumber to hold MAX_DBL_DIGITS+3 significant digits - struct { - decNumber decNum; - char digits[MAX_DBL_DIGITS+3]; - } decNumberWithStorage; - decNumber *numberPtr = &decNumberWithStorage.decNum; - - int32_t mostSig = fInterval.getMostSignificantExclusive(); - int32_t mostSigNonZero = fExponent + fDigits.length(); - int32_t end = mostSig > mostSigNonZero ? mostSigNonZero : mostSig; - int32_t leastSig = fInterval.getLeastSignificantInclusive(); - int32_t start = leastSig > fExponent ? leastSig : fExponent; - if (end <= start) { - return 0.0; - } - if (start < end - (MAX_DBL_DIGITS+3)) { - start = end - (MAX_DBL_DIGITS+3); - } - uint8_t *pos = numberPtr->lsu; - const char *src = &(fDigits.data()[start - fExponent]); - for (int32_t i = start; i < end; ++i) { - *pos++ = (uint8_t) (*src++); - } - numberPtr->exponent = start; - numberPtr->bits = 0; - numberPtr->digits = end - start; - char str[MAX_DBL_DIGITS+18]; - uprv_decNumberToString(numberPtr, str); - U_ASSERT(uprv_strlen(str) < MAX_DBL_DIGITS+18); - char *unused = NULL; - return DigitList::decimalStrToDouble(str, &unused); -} - -void VisibleDigits::getFixedDecimal( - double &source, int64_t &intValue, int64_t &f, int64_t &t, int32_t &v, UBool &hasIntValue) const { - source = 0.0; - intValue = 0; - f = 0; - t = 0; - v = 0; - hasIntValue = FALSE; - if (isNaNOrInfinity()) { - return; - } - - // source - if (fAbsDoubleValueSet) { - source = fAbsDoubleValue; - } else { - source = computeAbsDoubleValue(); - } - - // visible decimal digits - v = fInterval.getFracDigitCount(); - - // intValue - - // If we initialized from an int64 just use that instead of - // calculating - if (fAbsIntValueSet) { - intValue = fAbsIntValue; - } else { - int32_t startPos = fInterval.getMostSignificantExclusive(); - if (startPos > 18) { - startPos = 18; - } - // process the integer digits - for (int32_t i = startPos - 1; i >= 0; --i) { - intValue = intValue * 10LL + getDigitByExponent(i); - } - if (intValue == 0LL && startPos > 0) { - intValue = 100000000000000000LL; - } - } - - // f (decimal digits) - // skip over any leading 0's in fraction digits. - int32_t idx = -1; - for (; idx >= -v && getDigitByExponent(idx) == 0; --idx) - ; - - // Only process up to first 18 non zero fraction digits for decimalDigits - // since that is all we can fit into an int64. - for (int32_t i = idx; i >= -v && i > idx - 18; --i) { - f = f * 10LL + getDigitByExponent(i); - } - - // If we have no decimal digits, we don't have an integer value - hasIntValue = (f == 0LL); - - // t (decimal digits without trailing zeros) - t = f; - while (t > 0 && t % 10LL == 0) { - t /= 10; - } -} - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/deps/icu-small/source/i18n/visibledigits.h b/deps/icu-small/source/i18n/visibledigits.h deleted file mode 100644 index 03c8013e393374..00000000000000 --- a/deps/icu-small/source/i18n/visibledigits.h +++ /dev/null @@ -1,162 +0,0 @@ -// © 2016 and later: Unicode, Inc. and others. -// License & terms of use: http://www.unicode.org/copyright.html -/* -******************************************************************************* * Copyright (C) 2015, International Business Machines -* Corporation and others. All Rights Reserved. -******************************************************************************* -* visibledigits.h -* -* created on: 2015jun20 -* created by: Travis Keep -*/ - -#ifndef __VISIBLEDIGITS_H__ -#define __VISIBLEDIGITS_H__ - -#include "unicode/utypes.h" - -#if !UCONFIG_NO_FORMATTING - -#include "unicode/uobject.h" - -#include "charstr.h" -#include "digitinterval.h" - -U_NAMESPACE_BEGIN - -class DigitList; - -/** - * VisibleDigits represents the digits visible for formatting. - * Once initialized using a FixedPrecision instance, VisibleDigits instances - * remain unchanged until they are initialized again. A VisibleDigits with - * a numeric value equal to 3.0 could be "3", "3.0", "3.00" or even "003.0" - * depending on settings of the FixedPrecision instance used to initialize it. - */ -class U_I18N_API VisibleDigits : public UMemory { -public: - VisibleDigits() : fExponent(0), fFlags(0), fAbsIntValue(0), fAbsIntValueSet(FALSE), fAbsDoubleValue(0.0), fAbsDoubleValueSet(FALSE) { } - - UBool isNegative() const; - UBool isNaN() const; - UBool isInfinite() const; - UBool isNaNOrInfinity() const; - - /** - * Gets the digit at particular exponent, if number is 987.6, then - * getDigit(2) == 9 and gitDigit(0) == 7 and gitDigit(-1) == 6. - * If isNaN() or isInfinity() return TRUE, then the result of this - * function is undefined. - */ - int32_t getDigitByExponent(int32_t digitPos) const; - - /** - * Returns the digit interval which indicates the leftmost and rightmost - * position of this instance. - * If isNaN() or isInfinity() return TRUE, then the result of this - * function is undefined. - */ - const DigitInterval &getInterval() const { return fInterval; } - - /** - * Gets the parameters needed to create a FixedDecimal. - */ - void getFixedDecimal(double &source, int64_t &intValue, int64_t &f, int64_t &t, int32_t &v, UBool &hasIntValue) const; - - -private: - /** - * The digits, least significant first. Both the least and most - * significant digit in this list are non-zero; however, digits in the - * middle may be zero. This field contains values between (char) 0, and - * (char) 9 inclusive. - */ - CharString fDigits; - - /** - * The range of displayable digits. This field is needed to account for - * any leading and trailing zeros which are not stored in fDigits. - */ - DigitInterval fInterval; - - /** - * The exponent value of the least significant digit in fDigits. For - * example, fExponent = 2 and fDigits = {7, 8, 5} represents 58700. - */ - int32_t fExponent; - - /** - * Contains flags such as NaN, Inf, and negative. - */ - int32_t fFlags; - - /** - * Contains the absolute value of the digits left of the decimal place - * if fAbsIntValueSet is TRUE - */ - int64_t fAbsIntValue; - - /** - * Indicates whether or not fAbsIntValue is set. - */ - UBool fAbsIntValueSet; - - /** - * Contains the absolute value of the value this instance represents - * if fAbsDoubleValueSet is TRUE - */ - double fAbsDoubleValue; - - /** - * Indicates whether or not fAbsDoubleValue is set. - */ - UBool fAbsDoubleValueSet; - - void setNegative(); - void setNaN(); - void setInfinite(); - void clear(); - double computeAbsDoubleValue() const; - UBool isOverMaxDigits() const; - - VisibleDigits(const VisibleDigits &); - VisibleDigits &operator=(const VisibleDigits &); - - friend class FixedPrecision; - friend class VisibleDigitsWithExponent; -}; - -/** - * A VisibleDigits with a possible exponent. - */ -class U_I18N_API VisibleDigitsWithExponent : public UMemory { -public: - VisibleDigitsWithExponent() : fHasExponent(FALSE) { } - const VisibleDigits &getMantissa() const { return fMantissa; } - const VisibleDigits *getExponent() const { - return fHasExponent ? &fExponent : NULL; - } - void clear() { - fMantissa.clear(); - fExponent.clear(); - fHasExponent = FALSE; - } - UBool isNegative() const { return fMantissa.isNegative(); } - UBool isNaN() const { return fMantissa.isNaN(); } - UBool isInfinite() const { return fMantissa.isInfinite(); } -private: - VisibleDigitsWithExponent(const VisibleDigitsWithExponent &); - VisibleDigitsWithExponent &operator=( - const VisibleDigitsWithExponent &); - VisibleDigits fMantissa; - VisibleDigits fExponent; - UBool fHasExponent; - - friend class ScientificPrecision; - friend class FixedPrecision; -}; - - -U_NAMESPACE_END -#endif /* #if !UCONFIG_NO_FORMATTING */ -#endif // __VISIBLEDIGITS_H__ diff --git a/deps/icu-small/source/i18n/windtfmt.cpp b/deps/icu-small/source/i18n/windtfmt.cpp index e8e32abd3ff477..983fd46c122e6f 100644 --- a/deps/icu-small/source/i18n/windtfmt.cpp +++ b/deps/icu-small/source/i18n/windtfmt.cpp @@ -385,7 +385,8 @@ UnicodeString Win32DateFormat::setTimeZoneInfo(TIME_ZONE_INFORMATION *tzi, const for (int z = 0; z < ec; z += 1) { UnicodeString equiv = TimeZone::getEquivalentID(icuid, z); - if (found = uprv_getWindowsTimeZoneInfo(tzi, equiv.getBuffer(), equiv.length())) { + found = uprv_getWindowsTimeZoneInfo(tzi, equiv.getBuffer(), equiv.length()); + if (found) { break; } } diff --git a/deps/icu-small/source/i18n/zonemeta.cpp b/deps/icu-small/source/i18n/zonemeta.cpp index c386b0cae5e2ca..02562048a525da 100644 --- a/deps/icu-small/source/i18n/zonemeta.cpp +++ b/deps/icu-small/source/i18n/zonemeta.cpp @@ -690,6 +690,7 @@ ZoneMeta::createMetazoneMappings(const UnicodeString &tzid) { mzMappings = new UVector(deleteOlsonToMetaMappingEntry, NULL, status); if (U_FAILURE(status)) { delete mzMappings; + mzMappings = NULL; uprv_free(entry); break; } diff --git a/deps/icu-small/source/tools/toolutil/swapimpl.cpp b/deps/icu-small/source/tools/toolutil/swapimpl.cpp index a64a6a1703f2c7..f3f333a005eb43 100644 --- a/deps/icu-small/source/tools/toolutil/swapimpl.cpp +++ b/deps/icu-small/source/tools/toolutil/swapimpl.cpp @@ -336,7 +336,7 @@ ucase_swap(const UDataSwapper *ds, ((pInfo->formatVersion[0]==1 && pInfo->formatVersion[2]==UTRIE_SHIFT && pInfo->formatVersion[3]==UTRIE_INDEX_SHIFT) || - pInfo->formatVersion[0]==2 || pInfo->formatVersion[0]==3) + 2<=pInfo->formatVersion[0] || pInfo->formatVersion[0]<=4) )) { udata_printError(ds, "ucase_swap(): data format %02x.%02x.%02x.%02x (format version %02x) is not recognized as case mapping data\n", pInfo->dataFormat[0], pInfo->dataFormat[1], diff --git a/deps/icu-small/source/tools/toolutil/udbgutil.cpp b/deps/icu-small/source/tools/toolutil/udbgutil.cpp index 446e11aaf9084f..dcc80ebe069519 100644 --- a/deps/icu-small/source/tools/toolutil/udbgutil.cpp +++ b/deps/icu-small/source/tools/toolutil/udbgutil.cpp @@ -554,7 +554,6 @@ static const USystemParams systemParams[] = { #endif { "uconfig.internal_digitlist", paramInteger, "b", 1}, /* always 1 */ { "uconfig.have_parseallinput", paramInteger, "b", UCONFIG_HAVE_PARSEALLINPUT}, - { "uconfig.format_fastpaths_49",paramInteger, "b", UCONFIG_FORMAT_FASTPATHS_49}, }; From 25fef3d8d4682456b2db1571e3657d9144275dd7 Mon Sep 17 00:00:00 2001 From: Denys Otrishko Date: Mon, 9 Jul 2018 01:44:27 +0300 Subject: [PATCH 040/116] workers: fix invalid exit code in parent upon uncaught exception Now worker.on('exit') reports correct exit code (1) if worker has exited with uncaught exception. Fixes: https://github.com/nodejs/node/issues/21707 PR-URL: https://github.com/nodejs/node/pull/21713 Reviewed-By: Gus Caplan Reviewed-By: Yuta Hiroto Reviewed-By: Weijia Wang Reviewed-By: Anna Henningsen Reviewed-By: Colin Ihrig --- lib/internal/worker.js | 3 + test/parallel/test-worker-exit-code.js | 130 ++++++++++++++++++ .../test-worker-uncaught-exception-async.js | 4 + .../test-worker-uncaught-exception.js | 4 + 4 files changed, 141 insertions(+) create mode 100644 test/parallel/test-worker-exit-code.js diff --git a/lib/internal/worker.js b/lib/internal/worker.js index bcc864b5b8b330..83389d204d285f 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -454,6 +454,9 @@ function setupChild(evalScript) { debug(`[${threadId}] fatal exception caught = ${caught}`); if (!caught) { + // set correct code (uncaughtException) for [kOnExit](code) handler + process.exitCode = 1; + let serialized; try { serialized = serializeError(error); diff --git a/test/parallel/test-worker-exit-code.js b/test/parallel/test-worker-exit-code.js new file mode 100644 index 00000000000000..bb47e1cece7a62 --- /dev/null +++ b/test/parallel/test-worker-exit-code.js @@ -0,0 +1,130 @@ +// Flags: --experimental-worker +'use strict'; +const common = require('../common'); + +// This test checks that Worker has correct exit codes on parent side +// in multiple situations. + +const assert = require('assert'); +const worker = require('worker_threads'); +const { Worker, isMainThread, parentPort } = worker; + +if (isMainThread) { + parent(); +} else { + if (!parentPort) { + console.error('Parent port must not be null'); + process.exit(100); + return; + } + parentPort.once('message', (msg) => { + switch (msg) { + case 'child1': + return child1(); + case 'child2': + return child2(); + case 'child3': + return child3(); + case 'child4': + return child4(); + case 'child5': + return child5(); + case 'child6': + return child6(); + case 'child7': + return child7(); + default: + throw new Error('invalid'); + } + }); +} + +function child1() { + process.exitCode = 42; + process.on('exit', (code) => { + assert.strictEqual(code, 42); + }); +} + +function child2() { + process.exitCode = 99; + process.on('exit', (code) => { + assert.strictEqual(code, 42); + }); + process.exit(42); +} + +function child3() { + process.exitCode = 99; + process.on('exit', (code) => { + assert.strictEqual(code, 0); + }); + process.exit(0); +} + +function child4() { + process.exitCode = 99; + process.on('exit', (code) => { + // cannot use assert because it will be uncaughtException -> 1 exit code + // that will render this test useless + if (code !== 1) { + console.error('wrong code! expected 1 for uncaughtException'); + process.exit(99); + } + }); + throw new Error('ok'); +} + +function child5() { + process.exitCode = 95; + process.on('exit', (code) => { + assert.strictEqual(code, 95); + process.exitCode = 99; + }); +} + +function child6() { + process.on('exit', (code) => { + assert.strictEqual(code, 0); + }); + process.on('uncaughtException', common.mustCall(() => { + // handle + })); + throw new Error('ok'); +} + +function child7() { + process.on('exit', (code) => { + assert.strictEqual(code, 97); + }); + process.on('uncaughtException', common.mustCall(() => { + process.exitCode = 97; + })); + throw new Error('ok'); +} + +function parent() { + const test = (arg, exit, error = null) => { + const w = new Worker(__filename); + w.on('exit', common.mustCall((code) => { + assert.strictEqual( + code, exit, + `wrong exit for ${arg}\nexpected:${exit} but got:${code}`); + console.log(`ok - ${arg} exited with ${exit}`); + })); + if (error) { + w.on('error', common.mustCall((err) => { + assert(error.test(err)); + })); + } + w.postMessage(arg); + }; + + test('child1', 42); + test('child2', 42); + test('child3', 0); + test('child4', 1, /^Error: ok$/); + test('child5', 99); + test('child6', 0); + test('child7', 97); +} diff --git a/test/parallel/test-worker-uncaught-exception-async.js b/test/parallel/test-worker-uncaught-exception-async.js index c3f0c8dec59f09..f820976c11ebcd 100644 --- a/test/parallel/test-worker-uncaught-exception-async.js +++ b/test/parallel/test-worker-uncaught-exception-async.js @@ -12,6 +12,10 @@ if (!process.env.HAS_STARTED_WORKER) { w.on('error', common.mustCall((err) => { assert(/^Error: foo$/.test(err)); })); + w.on('exit', common.mustCall((code) => { + // uncaughtException is code 1 + assert.strictEqual(code, 1); + })); } else { setImmediate(() => { throw new Error('foo'); diff --git a/test/parallel/test-worker-uncaught-exception.js b/test/parallel/test-worker-uncaught-exception.js index b7d9f8a2928ec1..67b861e22619aa 100644 --- a/test/parallel/test-worker-uncaught-exception.js +++ b/test/parallel/test-worker-uncaught-exception.js @@ -12,6 +12,10 @@ if (!process.env.HAS_STARTED_WORKER) { w.on('error', common.mustCall((err) => { assert(/^Error: foo$/.test(err)); })); + w.on('exit', common.mustCall((code) => { + // uncaughtException is code 1 + assert.strictEqual(code, 1); + })); } else { throw new Error('foo'); } From cd6601b87a629f0c82ff28c556929ad68a8761a9 Mon Sep 17 00:00:00 2001 From: Sergey Zelenov Date: Thu, 5 Jul 2018 18:24:56 +0200 Subject: [PATCH 041/116] doc: fix HTTP res 'finish' description PR-URL: https://github.com/nodejs/node/pull/21670 Reviewed-By: James M Snell --- doc/api/http.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/api/http.md b/doc/api/http.md index aa36e304630855..fe1c387654e9c8 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -1025,8 +1025,6 @@ emitted when the last segment of the response headers and body have been handed off to the operating system for transmission over the network. It does not imply that the client has received anything yet. -After this event, no more events will be emitted on the response object. - ### response.addTrailers(headers) + +Set `process.title` on startup. + ### `--tls-cipher-list=list` - -* [About these Docs](documentation.html) -* [Usage & Example](synopsis.html) - -
- -* [Assertion Testing](assert.html) -* [Async Hooks](async_hooks.html) -* [Buffer](buffer.html) -* [C++ Addons](addons.html) -* [C/C++ Addons - N-API](n-api.html) -* [Child Processes](child_process.html) -* [Cluster](cluster.html) -* [Command Line Options](cli.html) -* [Console](console.html) -* [Crypto](crypto.html) -* [Debugger](debugger.html) -* [Deprecated APIs](deprecations.html) -* [DNS](dns.html) -* [Domain](domain.html) -* [ECMAScript Modules](esm.html) -* [Errors](errors.html) -* [Events](events.html) -* [File System](fs.html) -* [Globals](globals.html) -* [HTTP](http.html) -* [HTTP/2](http2.html) -* [HTTPS](https.html) -* [Inspector](inspector.html) -* [Internationalization](intl.html) -* [Modules](modules.html) -* [Net](net.html) -* [OS](os.html) -* [Path](path.html) -* [Performance Hooks](perf_hooks.html) -* [Process](process.html) -* [Punycode](punycode.html) -* [Query Strings](querystring.html) -* [Readline](readline.html) -* [REPL](repl.html) -* [Stream](stream.html) -* [String Decoder](string_decoder.html) -* [Timers](timers.html) -* [TLS/SSL](tls.html) -* [Trace Events](tracing.html) -* [TTY](tty.html) -* [UDP/Datagram](dgram.html) -* [URL](url.html) -* [Utilities](util.html) -* [V8](v8.html) -* [VM](vm.html) -* [Worker Threads](worker_threads.html) -* [ZLIB](zlib.html) - -
- -* [GitHub Repo & Issue Tracker](https://github.com/nodejs/node) -* [Mailing List](https://groups.google.com/group/nodejs) diff --git a/doc/api/all.md b/doc/api/all.md deleted file mode 100644 index 47216b695d3351..00000000000000 --- a/doc/api/all.md +++ /dev/null @@ -1,50 +0,0 @@ - -@include documentation -@include synopsis -@include assert -@include async_hooks -@include buffer -@include addons -@include n-api -@include child_process -@include cluster -@include cli -@include console -@include crypto -@include debugger -@include deprecations -@include dns -@include domain -@include esm -@include errors -@include events -@include fs -@include globals -@include http -@include http2 -@include https -@include inspector -@include intl -@include modules -@include net -@include os -@include path -@include perf_hooks -@include process -@include punycode -@include querystring -@include readline -@include repl -@include stream -@include string_decoder -@include timers -@include tls -@include tracing -@include tty -@include dgram -@include url -@include util -@include v8 -@include vm -@include worker_threads -@include zlib diff --git a/doc/api/index.md b/doc/api/index.md index 400faf6e5ed34a..0b43097bf294de 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -1 +1,64 @@ -@include _toc.md + + + + +* [About these Docs](documentation.html) +* [Usage & Example](synopsis.html) + +
+ +* [Assertion Testing](assert.html) +* [Async Hooks](async_hooks.html) +* [Buffer](buffer.html) +* [C++ Addons](addons.html) +* [C/C++ Addons - N-API](n-api.html) +* [Child Processes](child_process.html) +* [Cluster](cluster.html) +* [Command Line Options](cli.html) +* [Console](console.html) +* [Crypto](crypto.html) +* [Debugger](debugger.html) +* [Deprecated APIs](deprecations.html) +* [DNS](dns.html) +* [Domain](domain.html) +* [ECMAScript Modules](esm.html) +* [Errors](errors.html) +* [Events](events.html) +* [File System](fs.html) +* [Globals](globals.html) +* [HTTP](http.html) +* [HTTP/2](http2.html) +* [HTTPS](https.html) +* [Inspector](inspector.html) +* [Internationalization](intl.html) +* [Modules](modules.html) +* [Net](net.html) +* [OS](os.html) +* [Path](path.html) +* [Performance Hooks](perf_hooks.html) +* [Process](process.html) +* [Punycode](punycode.html) +* [Query Strings](querystring.html) +* [Readline](readline.html) +* [REPL](repl.html) +* [Stream](stream.html) +* [String Decoder](string_decoder.html) +* [Timers](timers.html) +* [TLS/SSL](tls.html) +* [Trace Events](tracing.html) +* [TTY](tty.html) +* [UDP/Datagram](dgram.html) +* [URL](url.html) +* [Utilities](util.html) +* [V8](v8.html) +* [VM](vm.html) +* [Worker Threads](worker_threads.html) +* [ZLIB](zlib.html) + +
+ +* [GitHub Repo & Issue Tracker](https://github.com/nodejs/node) +* [Mailing List](https://groups.google.com/group/nodejs) diff --git a/test/doctool/test-doctool-html.js b/test/doctool/test-doctool-html.js index 0ca46c0adedfc1..a6119c16903a74 100644 --- a/test/doctool/test-doctool-html.js +++ b/test/doctool/test-doctool-html.js @@ -11,7 +11,6 @@ try { const assert = require('assert'); const { readFile } = require('fs'); const fixtures = require('../common/fixtures'); -const processIncludes = require('../../tools/doc/preprocess.js'); const toHTML = require('../../tools/doc/html.js'); // Test data is a list of objects with two properties. @@ -64,15 +63,6 @@ const testData = [ ' ' + '

Describe Something in more detail here.

' }, - { - file: fixtures.path('doc_with_includes.md'), - html: '' + - '

Look here!

' + - '' + - '

foobar#

' + - '

I exist and am being linked to.

' - }, { file: fixtures.path('sample_document.md'), html: '
  1. fish
  2. fish

  3. Redfish

  4. ' + @@ -90,36 +80,34 @@ testData.forEach(({ file, html, analyticsId }) => { readFile(file, 'utf8', common.mustCall((err, input) => { assert.ifError(err); - processIncludes(file, input, common.mustCall((err, preprocessed) => { - assert.ifError(err); - toHTML( - { - input: preprocessed, - filename: 'foo', - nodeVersion: process.version, - analytics: analyticsId, - }, - common.mustCall((err, output) => { - assert.ifError(err); + toHTML( + { + input: input, + filename: 'foo', + nodeVersion: process.version, + analytics: analyticsId, + }, + common.mustCall((err, output) => { + assert.ifError(err); - const actual = output.replace(spaces, ''); - // Assert that the input stripped of all whitespace contains the - // expected markup. - assert(actual.includes(expected)); + const actual = output.replace(spaces, ''); + // Assert that the input stripped of all whitespace contains the + // expected markup. + assert(actual.includes(expected)); - // Testing the insertion of Google Analytics script when - // an analytics id is provided. Should not be present by default. - const scriptDomain = 'google-analytics.com'; - if (includeAnalytics) { - assert(actual.includes(scriptDomain), - `Google Analytics script was not present in "${actual}"`); - } else { - assert.strictEqual(actual.includes(scriptDomain), false, - 'Google Analytics script was present in ' + - `"${actual}"`); - } - })); - })); + // Testing the insertion of Google Analytics script when + // an analytics id is provided. Should not be present by default. + const scriptDomain = 'google-analytics.com'; + if (includeAnalytics) { + assert(actual.includes(scriptDomain), + `Google Analytics script was not present in "${actual}"`); + } else { + assert.strictEqual(actual.includes(scriptDomain), false, + 'Google Analytics script was present in ' + + `"${actual}"`); + } + }) + ); })); }); diff --git a/test/doctool/test-make-doc.js b/test/doctool/test-make-doc.js index e019288f75cc1e..a11da0e97d8355 100644 --- a/test/doctool/test-make-doc.js +++ b/test/doctool/test-make-doc.js @@ -13,17 +13,16 @@ const path = require('path'); const apiPath = path.resolve(__dirname, '..', '..', 'out', 'doc', 'api'); const allDocs = fs.readdirSync(apiPath); -assert.ok(allDocs.includes('_toc.html')); +assert.ok(allDocs.includes('index.html')); const actualDocs = allDocs.filter( (name) => { const extension = path.extname(name); - return (extension === '.html' || extension === '.json') && - name !== '_toc.html'; + return extension === '.html' || extension === '.json'; } ); -const toc = fs.readFileSync(path.resolve(apiPath, '_toc.html'), 'utf8'); +const toc = fs.readFileSync(path.resolve(apiPath, 'index.html'), 'utf8'); const re = /href="([^/]+\.html)"/; const globalRe = new RegExp(re, 'g'); const links = toc.match(globalRe); @@ -32,8 +31,7 @@ assert.notStrictEqual(links, null); // Filter out duplicate links, leave just filenames, add expected JSON files. const linkedHtmls = [...new Set(links)].map((link) => link.match(re)[1]); const expectedJsons = linkedHtmls - .map((name) => name.replace('.html', '.json')) - .concat('_toc.json'); + .map((name) => name.replace('.html', '.json')); const expectedDocs = linkedHtmls.concat(expectedJsons); // Test that all the relative links in the TOC match to the actual documents. diff --git a/test/fixtures/doc_with_includes.md b/test/fixtures/doc_with_includes.md deleted file mode 100644 index 901bf0f1b0bf3b..00000000000000 --- a/test/fixtures/doc_with_includes.md +++ /dev/null @@ -1,2 +0,0 @@ -@include doc_inc_1 -@include doc_inc_2.md diff --git a/tools/doc/allhtml.js b/tools/doc/allhtml.js index fad530f1c44245..1c84e13d0ab79c 100644 --- a/tools/doc/allhtml.js +++ b/tools/doc/allhtml.js @@ -12,7 +12,7 @@ const htmlFiles = fs.readdirSync(source, 'utf8') .filter((name) => name.includes('.html') && name !== 'all.html'); // Read the table of contents. -const toc = fs.readFileSync(source + '/_toc.html', 'utf8'); +const toc = fs.readFileSync(source + '/index.html', 'utf8'); // Extract (and concatenate) the toc and apicontent from each document. let contents = ''; @@ -47,11 +47,12 @@ for (const link of toc.match(//g)) { seen[href] = true; } -// Replace various mentions of _toc with all. -let all = toc.replace(/_toc\.html/g, 'all.html') - .replace('_toc.json', 'all.json') - .replace('api-section-_toc', 'api-section-all') - .replace('data-id="_toc"', 'data-id="all"'); +// Replace various mentions of index with all. +let all = toc.replace(/index\.html/g, 'all.html') + .replace('', '') + .replace('index.json', 'all.json') + .replace('api-section-index', 'api-section-all') + .replace('data-id="index"', 'data-id="all"'); // Clean up the title. all = all.replace(/.*?\| /, '<title>'); diff --git a/tools/doc/alljson.js b/tools/doc/alljson.js new file mode 100644 index 00000000000000..7e027f764e7efd --- /dev/null +++ b/tools/doc/alljson.js @@ -0,0 +1,56 @@ +'use strict'; + +// Build all.json by combining the miscs, modules, classes, globals, and methods +// from the generated json files. + +const fs = require('fs'); + +const source = `${__dirname}/../../out/doc/api`; + +// Get a list of generated API documents. +const jsonFiles = fs.readdirSync(source, 'utf8') + .filter((name) => name.includes('.json') && name !== 'all.json'); + +// Read the table of contents. +const toc = fs.readFileSync(source + '/index.html', 'utf8'); + +// Initialize results. Only these four data values will be collected. +const results = { + miscs: [], + modules: [], + classes: [], + globals: [], + methods: [] +}; + +// Identify files that should be skipped. As files are processed, they +// are added to this list to prevent dupes. +const seen = { + 'all.json': true, + 'index.json': true +}; + +// Extract (and concatenate) the selected data from each document. +// Expand hrefs found in json to include source HTML file. +for (const link of toc.match(/<a.*?>/g)) { + const href = /href="(.*?)"/.exec(link)[1]; + const json = href.replace('.html', '.json'); + if (!jsonFiles.includes(json) || seen[json]) continue; + const data = JSON.parse( + fs.readFileSync(source + '/' + json, 'utf8') + .replace(/<a href=\\"#/g, `<a href=\\"${href}#`) + ); + + for (const property in data) { + if (results.hasOwnProperty(property)) { + results[property].push(...data[property]); + } + } + + // Mark source as seen. + seen[json] = true; +} + +// Write results. +fs.writeFileSync(source + '/all.json', + `${JSON.stringify(results, null, 2)}\n`, 'utf8'); diff --git a/tools/doc/generate.js b/tools/doc/generate.js index 9f217b19c7225f..8ed97610cf3bc2 100644 --- a/tools/doc/generate.js +++ b/tools/doc/generate.js @@ -21,7 +21,6 @@ 'use strict'; -const processIncludes = require('./preprocess.js'); const fs = require('fs'); // Parse the args. @@ -52,12 +51,6 @@ if (!filename) { } fs.readFile(filename, 'utf8', (er, input) => { - if (er) throw er; - // Process the input for @include lines. - processIncludes(filename, input, next); -}); - -function next(er, input) { if (er) throw er; switch (format) { case 'json': @@ -78,4 +71,4 @@ function next(er, input) { default: throw new Error(`Invalid format: ${format}`); } -} +}); diff --git a/tools/doc/html.js b/tools/doc/html.js index 871a55baf4676c..0e254f1203f7a6 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -36,8 +36,8 @@ marked.setOptions({ renderer }); const docPath = path.resolve(__dirname, '..', '..', 'doc'); -const gtocPath = path.join(docPath, 'api', '_toc.md'); -const gtocMD = fs.readFileSync(gtocPath, 'utf8').replace(/^@\/\/.*$/gm, ''); +const gtocPath = path.join(docPath, 'api', 'index.md'); +const gtocMD = fs.readFileSync(gtocPath, 'utf8').replace(/^<!--.*?-->/gms, ''); const gtocHTML = marked(gtocMD).replace( /<a href="(.*?)"/g, (all, href) => `<a class="nav-${href.replace('.html', '') diff --git a/tools/doc/preprocess.js b/tools/doc/preprocess.js deleted file mode 100644 index 554af2ccb77ae0..00000000000000 --- a/tools/doc/preprocess.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; - -module.exports = processIncludes; - -const path = require('path'); -const fs = require('fs'); - -const includeExpr = /^@include\s+([\w-]+)(?:\.md)?$/gmi; -const commentExpr = /^@\/\/.*$/gm; - -function processIncludes(inputFile, input, cb) { - const includes = input.match(includeExpr); - if (includes === null) - return cb(null, input.replace(commentExpr, '')); - - let errState = null; - let incCount = includes.length; - - includes.forEach((include) => { - const fname = include.replace(includeExpr, '$1.md'); - const fullFname = path.resolve(path.dirname(inputFile), fname); - - fs.readFile(fullFname, 'utf8', function(er, inc) { - if (errState) return; - if (er) return cb(errState = er); - incCount--; - - // Add comments to let the HTML generator know - // how the anchors for headings should look like. - inc = `<!-- [start-include:${fname}] -->\n` + - `${inc}\n<!-- [end-include:${fname}] -->\n`; - input = input.split(`${include}\n`).join(`${inc}\n`); - - if (incCount === 0) - return cb(null, input.replace(commentExpr, '')); - }); - }); -} From 48b16aad47d321885ae02ada6afe4ef2532c09b7 Mon Sep 17 00:00:00 2001 From: Jon Moss <me@jonathanmoss.me> Date: Sat, 7 Jul 2018 17:24:27 -0400 Subject: [PATCH 050/116] zlib: instance-ify two methods Both `Params` and `SetDictionary` take `ZCtx` as an arg, so just make them both instance methods on `ZCtx`. PR-URL: https://github.com/nodejs/node/pull/21702 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> --- src/node_zlib.cc | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 893209d4d42012..169816d16f48b6 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -486,7 +486,7 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork { write_js_callback, dictionary, dictionary_len); if (!ret) goto end; - SetDictionary(ctx); + ctx->SetDictionary(); end: return args.GetReturnValue().Set(ret); @@ -496,14 +496,14 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork { CHECK(args.Length() == 2 && "params(level, strategy)"); ZCtx* ctx; ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder()); - Params(ctx, args[0]->Int32Value(), args[1]->Int32Value()); + ctx->Params(args[0]->Int32Value(), args[1]->Int32Value()); } static void Reset(const FunctionCallbackInfo<Value> &args) { ZCtx* ctx; ASSIGN_OR_RETURN_UNWRAP(&ctx, args.Holder()); ctx->Reset(); - SetDictionary(ctx); + ctx->SetDictionary(); } static bool Init(ZCtx* ctx, int level, int windowBits, int memLevel, @@ -577,51 +577,47 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork { return true; } - static void SetDictionary(ZCtx* ctx) { - if (ctx->dictionary_ == nullptr) + void SetDictionary() { + if (dictionary_ == nullptr) return; - ctx->err_ = Z_OK; + err_ = Z_OK; - switch (ctx->mode_) { + switch (mode_) { case DEFLATE: case DEFLATERAW: - ctx->err_ = deflateSetDictionary(&ctx->strm_, - ctx->dictionary_, - ctx->dictionary_len_); + err_ = deflateSetDictionary(&strm_, dictionary_, dictionary_len_); break; case INFLATERAW: // The other inflate cases will have the dictionary set when inflate() // returns Z_NEED_DICT in Process() - ctx->err_ = inflateSetDictionary(&ctx->strm_, - ctx->dictionary_, - ctx->dictionary_len_); + err_ = inflateSetDictionary(&strm_, dictionary_, dictionary_len_); break; default: break; } - if (ctx->err_ != Z_OK) { - ctx->Error("Failed to set dictionary"); + if (err_ != Z_OK) { + Error("Failed to set dictionary"); } } - static void Params(ZCtx* ctx, int level, int strategy) { - AllocScope alloc_scope(ctx); + void Params(int level, int strategy) { + AllocScope alloc_scope(this); - ctx->err_ = Z_OK; + err_ = Z_OK; - switch (ctx->mode_) { + switch (mode_) { case DEFLATE: case DEFLATERAW: - ctx->err_ = deflateParams(&ctx->strm_, level, strategy); + err_ = deflateParams(&strm_, level, strategy); break; default: break; } - if (ctx->err_ != Z_OK && ctx->err_ != Z_BUF_ERROR) { - ctx->Error("Failed to set parameters"); + if (err_ != Z_OK && err_ != Z_BUF_ERROR) { + Error("Failed to set parameters"); } } From 07cce880bfd6cf6d17a499798d2699617e13750d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= <tniessen@tnie.de> Date: Tue, 12 Jun 2018 14:15:38 +0200 Subject: [PATCH 051/116] crypto: handle OpenSSL error queue in CipherBase This handles all errors produced by OpenSSL within the CipherBase class. API functions ensure that they do not add any new errors to the error queue. Also adds a couple of CHECKs and throws under certain conditions. PR-URL: https://github.com/nodejs/node/pull/21288 Fixes: https://github.com/nodejs/node/issues/21281 Refs: https://github.com/nodejs/node/pull/21287 Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> --- src/node_crypto.cc | 57 +++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 20975513f53911..ba65876e486a93 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -2581,6 +2581,7 @@ void CipherBase::Init(const char* cipher_type, int key_buf_len, unsigned int auth_tag_len) { HandleScope scope(env()->isolate()); + MarkPopErrorOnReturn mark_pop_error_on_return; #ifdef NODE_FIPS_MODE if (FIPS_mode()) { @@ -2605,6 +2606,7 @@ void CipherBase::Init(const char* cipher_type, 1, key, iv); + CHECK_NE(key_len, 0); ctx_.reset(EVP_CIPHER_CTX_new()); @@ -2613,7 +2615,11 @@ void CipherBase::Init(const char* cipher_type, EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); const bool encrypt = (kind_ == kCipher); - EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, nullptr, nullptr, encrypt); + if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, + nullptr, nullptr, encrypt)) { + return ThrowCryptoError(env(), ERR_get_error(), + "Failed to initialize cipher"); + } if (encrypt && (mode == EVP_CIPH_CTR_MODE || mode == EVP_CIPH_GCM_MODE || mode == EVP_CIPH_CCM_MODE)) { @@ -2632,12 +2638,15 @@ void CipherBase::Init(const char* cipher_type, CHECK_EQ(1, EVP_CIPHER_CTX_set_key_length(ctx_.get(), key_len)); - EVP_CipherInit_ex(ctx_.get(), - nullptr, - nullptr, - reinterpret_cast<unsigned char*>(key), - reinterpret_cast<unsigned char*>(iv), - encrypt); + if (1 != EVP_CipherInit_ex(ctx_.get(), + nullptr, + nullptr, + reinterpret_cast<unsigned char*>(key), + reinterpret_cast<unsigned char*>(iv), + encrypt)) { + return ThrowCryptoError(env(), ERR_get_error(), + "Failed to initialize cipher"); + } } @@ -2672,6 +2681,7 @@ void CipherBase::InitIv(const char* cipher_type, int iv_len, unsigned int auth_tag_len) { HandleScope scope(env()->isolate()); + MarkPopErrorOnReturn mark_pop_error_on_return; const EVP_CIPHER* const cipher = EVP_get_cipherbyname(cipher_type); if (cipher == nullptr) { @@ -2702,7 +2712,11 @@ void CipherBase::InitIv(const char* cipher_type, EVP_CIPHER_CTX_set_flags(ctx_.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); const bool encrypt = (kind_ == kCipher); - EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, nullptr, nullptr, encrypt); + if (1 != EVP_CipherInit_ex(ctx_.get(), cipher, nullptr, + nullptr, nullptr, encrypt)) { + return ThrowCryptoError(env(), ERR_get_error(), + "Failed to initialize cipher"); + } if (IsAuthenticatedMode()) { CHECK(has_iv); @@ -2715,12 +2729,15 @@ void CipherBase::InitIv(const char* cipher_type, return env()->ThrowError("Invalid key length"); } - EVP_CipherInit_ex(ctx_.get(), - nullptr, - nullptr, - reinterpret_cast<const unsigned char*>(key), - reinterpret_cast<const unsigned char*>(iv), - encrypt); + if (1 != EVP_CipherInit_ex(ctx_.get(), + nullptr, + nullptr, + reinterpret_cast<const unsigned char*>(key), + reinterpret_cast<const unsigned char*>(iv), + encrypt)) { + return ThrowCryptoError(env(), ERR_get_error(), + "Failed to initialize cipher"); + } } @@ -2765,6 +2782,7 @@ static bool IsValidGCMTagLength(unsigned int tag_len) { bool CipherBase::InitAuthenticated(const char* cipher_type, int iv_len, unsigned int auth_tag_len) { CHECK(IsAuthenticatedMode()); + MarkPopErrorOnReturn mark_pop_error_on_return; if (!EVP_CIPHER_CTX_ctrl(ctx_.get(), EVP_CTRL_AEAD_SET_IVLEN, @@ -2910,6 +2928,7 @@ void CipherBase::SetAuthTag(const FunctionCallbackInfo<Value>& args) { bool CipherBase::SetAAD(const char* data, unsigned int len, int plaintext_len) { if (!ctx_ || !IsAuthenticatedMode()) return false; + MarkPopErrorOnReturn mark_pop_error_on_return; int outlen; const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); @@ -2969,6 +2988,7 @@ CipherBase::UpdateResult CipherBase::Update(const char* data, int* out_len) { if (!ctx_) return kErrorState; + MarkPopErrorOnReturn mark_pop_error_on_return; const int mode = EVP_CIPHER_CTX_mode(ctx_.get()); @@ -2980,10 +3000,10 @@ CipherBase::UpdateResult CipherBase::Update(const char* data, // on first update: if (kind_ == kDecipher && IsAuthenticatedMode() && auth_tag_len_ > 0 && auth_tag_len_ != kNoAuthTagLength && !auth_tag_set_) { - EVP_CIPHER_CTX_ctrl(ctx_.get(), - EVP_CTRL_GCM_SET_TAG, - auth_tag_len_, - reinterpret_cast<unsigned char*>(auth_tag_)); + CHECK(EVP_CIPHER_CTX_ctrl(ctx_.get(), + EVP_CTRL_GCM_SET_TAG, + auth_tag_len_, + reinterpret_cast<unsigned char*>(auth_tag_))); auth_tag_set_ = true; } @@ -3061,6 +3081,7 @@ void CipherBase::Update(const FunctionCallbackInfo<Value>& args) { bool CipherBase::SetAutoPadding(bool auto_padding) { if (!ctx_) return false; + MarkPopErrorOnReturn mark_pop_error_on_return; return EVP_CIPHER_CTX_set_padding(ctx_.get(), auto_padding); } From 27d17d460013e256b6993707bbb6764391cba93b Mon Sep 17 00:00:00 2001 From: James M Snell <jasnell@gmail.com> Date: Fri, 22 Jun 2018 12:50:01 -0700 Subject: [PATCH 052/116] trace_events: add traced_value.cc/traced_value.h Port of the V8 internal v8::tracing::TracedValue that allows structured data to be included in the trace event. The v8 class is not exported in the public API so we cannot use it directly. This is a simplified and slightly modified port. This commit only adds the class, it does not add uses of it. Those will come in separate PRs/commits. PR-URL: https://github.com/nodejs/node/pull/21475 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> --- node.gyp | 3 + src/tracing/traced_value.cc | 224 +++++++++++++++++++++++++++++++ src/tracing/traced_value.h | 68 ++++++++++ test/cctest/test_traced_value.cc | 96 +++++++++++++ 4 files changed, 391 insertions(+) create mode 100644 src/tracing/traced_value.cc create mode 100644 src/tracing/traced_value.h create mode 100644 test/cctest/test_traced_value.cc diff --git a/node.gyp b/node.gyp index 207c72cdb56a29..35a81437afdfcf 100644 --- a/node.gyp +++ b/node.gyp @@ -377,6 +377,7 @@ 'src/tracing/node_trace_buffer.cc', 'src/tracing/node_trace_writer.cc', 'src/tracing/trace_event.cc', + 'src/tracing/traced_value.cc', 'src/tty_wrap.cc', 'src/udp_wrap.cc', 'src/util.cc', @@ -437,6 +438,7 @@ 'src/tracing/node_trace_buffer.h', 'src/tracing/node_trace_writer.h', 'src/tracing/trace_event.h', + 'src/tracing/traced_value.h', 'src/util.h', 'src/util-inl.h', 'deps/http_parser/http_parser.h', @@ -950,6 +952,7 @@ 'test/cctest/test_node_postmortem_metadata.cc', 'test/cctest/test_environment.cc', 'test/cctest/test_platform.cc', + 'test/cctest/test_traced_value.cc', 'test/cctest/test_util.cc', 'test/cctest/test_url.cc' ], diff --git a/src/tracing/traced_value.cc b/src/tracing/traced_value.cc new file mode 100644 index 00000000000000..e256df267eb5a9 --- /dev/null +++ b/src/tracing/traced_value.cc @@ -0,0 +1,224 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "tracing/traced_value.h" + +#include <math.h> +#include <sstream> +#include <stdio.h> +#include <string> + +#if defined(NODE_HAVE_I18N_SUPPORT) +#include <unicode/utf8.h> +#include <unicode/utypes.h> +#endif + +#if defined(_STLP_VENDOR_CSTD) +// STLPort doesn't import fpclassify into the std namespace. +#define FPCLASSIFY_NAMESPACE +#else +#define FPCLASSIFY_NAMESPACE std +#endif + +namespace node { +namespace tracing { + +namespace { + +std::string EscapeString(const char* value) { + std::string result; + result += '"'; + char number_buffer[10]; +#if defined(NODE_HAVE_I18N_SUPPORT) + int32_t len = strlen(value); + int32_t p = 0; + int32_t i = 0; + for (; i < len; p = i) { + UChar32 c; + U8_NEXT_OR_FFFD(value, i, len, c); + switch (c) { + case '\b': result += "\\b"; break; + case '\f': result += "\\f"; break; + case '\n': result += "\\n"; break; + case '\r': result += "\\r"; break; + case '\t': result += "\\t"; break; + case '\\': result += "\\\\"; break; + case '"': result += "\\\""; break; + default: + if (c < 32 || c > 126) { + snprintf( + number_buffer, arraysize(number_buffer), "\\u%04X", + static_cast<uint16_t>(static_cast<uint16_t>(c))); + result += number_buffer; + } else { + result.append(value + p, i - p); + } + } + } +#else + // If we do not have ICU, use a modified version of the non-UTF8 aware + // code from V8's own TracedValue implementation. Note, however, This + // will not produce correctly serialized results for UTF8 values. + while (*value) { + char c = *value++; + switch (c) { + case '\b': result += "\\b"; break; + case '\f': result += "\\f"; break; + case '\n': result += "\\n"; break; + case '\r': result += "\\r"; break; + case '\t': result += "\\t"; break; + case '\\': result += "\\\\"; break; + case '"': result += "\\\""; break; + default: + if (c < '\x20') { + snprintf( + number_buffer, arraysize(number_buffer), "\\u%04X", + static_cast<unsigned>(static_cast<unsigned char>(c))); + result += number_buffer; + } else { + result += c; + } + } + } +#endif // defined(NODE_HAVE_I18N_SUPPORT) + result += '"'; + return result; +} + +std::string DoubleToCString(double v) { + switch (FPCLASSIFY_NAMESPACE::fpclassify(v)) { + case FP_NAN: return "\"NaN\""; + case FP_INFINITE: return (v < 0.0 ? "\"-Infinity\"" : "\"Infinity\""); + case FP_ZERO: return "0"; + default: + // This is a far less sophisticated version than the one used inside v8. + std::ostringstream stream; + stream.imbue(std::locale("C")); // Ignore locale + stream << v; + return stream.str(); + } +} + +} // namespace + +std::unique_ptr<TracedValue> TracedValue::Create() { + return std::unique_ptr<TracedValue>(new TracedValue(false)); +} + +std::unique_ptr<TracedValue> TracedValue::CreateArray() { + return std::unique_ptr<TracedValue>(new TracedValue(true)); +} + +TracedValue::TracedValue(bool root_is_array) : + first_item_(true), root_is_array_(root_is_array) {} + +TracedValue::~TracedValue() {} + +void TracedValue::SetInteger(const char* name, int value) { + WriteName(name); + data_ += std::to_string(value); +} + +void TracedValue::SetDouble(const char* name, double value) { + WriteName(name); + data_ += DoubleToCString(value); +} + +void TracedValue::SetBoolean(const char* name, bool value) { + WriteName(name); + data_ += value ? "true" : "false"; +} + +void TracedValue::SetNull(const char* name) { + WriteName(name); + data_ += "null"; +} + +void TracedValue::SetString(const char* name, const char* value) { + WriteName(name); + data_ += EscapeString(value); +} + +void TracedValue::BeginDictionary(const char* name) { + WriteName(name); + data_ += '{'; + first_item_ = true; +} + +void TracedValue::BeginArray(const char* name) { + WriteName(name); + data_ += '['; + first_item_ = true; +} + +void TracedValue::AppendInteger(int value) { + WriteComma(); + data_ += std::to_string(value); +} + +void TracedValue::AppendDouble(double value) { + WriteComma(); + data_ += DoubleToCString(value); +} + +void TracedValue::AppendBoolean(bool value) { + WriteComma(); + data_ += value ? "true" : "false"; +} + +void TracedValue::AppendNull() { + WriteComma(); + data_ += "null"; +} + +void TracedValue::AppendString(const char* value) { + WriteComma(); + data_ += EscapeString(value); +} + +void TracedValue::BeginDictionary() { + WriteComma(); + data_ += '{'; + first_item_ = true; +} + +void TracedValue::BeginArray() { + WriteComma(); + data_ += '['; + first_item_ = true; +} + +void TracedValue::EndDictionary() { + data_ += '}'; + first_item_ = false; +} + +void TracedValue::EndArray() { + data_ += ']'; + first_item_ = false; +} + +void TracedValue::WriteComma() { + if (first_item_) { + first_item_ = false; + } else { + data_ += ','; + } +} + +void TracedValue::WriteName(const char* name) { + WriteComma(); + data_ += '"'; + data_ += name; + data_ += "\":"; +} + +void TracedValue::AppendAsTraceFormat(std::string* out) const { + *out += root_is_array_ ? '[' : '{'; + *out += data_; + *out += root_is_array_ ? ']' : '}'; +} + +} // namespace tracing +} // namespace node diff --git a/src/tracing/traced_value.h b/src/tracing/traced_value.h new file mode 100644 index 00000000000000..84e24c952528f5 --- /dev/null +++ b/src/tracing/traced_value.h @@ -0,0 +1,68 @@ +// Copyright 2016 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SRC_TRACING_TRACED_VALUE_H_ +#define SRC_TRACING_TRACED_VALUE_H_ + +#include "node_internals.h" +#include "v8.h" + +#include <stddef.h> +#include <memory> +#include <string> + +namespace node { +namespace tracing { + +class TracedValue : public v8::ConvertableToTraceFormat { + public: + ~TracedValue() override; + + static std::unique_ptr<TracedValue> Create(); + static std::unique_ptr<TracedValue> CreateArray(); + + void EndDictionary(); + void EndArray(); + + // These methods assume that |name| is a long lived "quoted" string. + void SetInteger(const char* name, int value); + void SetDouble(const char* name, double value); + void SetBoolean(const char* name, bool value); + void SetNull(const char* name); + void SetString(const char* name, const char* value); + void SetString(const char* name, const std::string& value) { + SetString(name, value.c_str()); + } + void BeginDictionary(const char* name); + void BeginArray(const char* name); + + void AppendInteger(int); + void AppendDouble(double); + void AppendBoolean(bool); + void AppendNull(); + void AppendString(const char*); + void AppendString(const std::string& value) { AppendString(value.c_str()); } + void BeginArray(); + void BeginDictionary(); + + // ConvertableToTraceFormat implementation. + void AppendAsTraceFormat(std::string* out) const override; + + private: + explicit TracedValue(bool root_is_array = false); + + void WriteComma(); + void WriteName(const char* name); + + std::string data_; + bool first_item_; + bool root_is_array_; + + DISALLOW_COPY_AND_ASSIGN(TracedValue); +}; + +} // namespace tracing +} // namespace node + +#endif // SRC_TRACING_TRACED_VALUE_H_ diff --git a/test/cctest/test_traced_value.cc b/test/cctest/test_traced_value.cc new file mode 100644 index 00000000000000..5329c78446ca6f --- /dev/null +++ b/test/cctest/test_traced_value.cc @@ -0,0 +1,96 @@ +#include "tracing/traced_value.h" + +#include <math.h> +#include <stddef.h> +#include <string.h> + +#include "gtest/gtest.h" + +using node::tracing::TracedValue; + +TEST(TracedValue, Object) { + auto traced_value = TracedValue::Create(); + traced_value->SetString("a", "b"); + traced_value->SetInteger("b", 1); + traced_value->SetDouble("c", 1.234); + traced_value->SetDouble("d", NAN); + traced_value->SetDouble("e", INFINITY); + traced_value->SetDouble("f", -INFINITY); + traced_value->SetDouble("g", 1.23e7); + traced_value->SetBoolean("h", false); + traced_value->SetBoolean("i", true); + traced_value->SetNull("j"); + traced_value->BeginDictionary("k"); + traced_value->SetString("l", "m"); + traced_value->EndDictionary(); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + + static const char* check = "{\"a\":\"b\",\"b\":1,\"c\":1.234,\"d\":\"NaN\"," + "\"e\":\"Infinity\",\"f\":\"-Infinity\",\"g\":" + "1.23e+07,\"h\":false,\"i\":true,\"j\":null,\"k\":" + "{\"l\":\"m\"}}"; + + EXPECT_EQ(check, string); +} + +TEST(TracedValue, Array) { + auto traced_value = TracedValue::CreateArray(); + traced_value->AppendString("a"); + traced_value->AppendInteger(1); + traced_value->AppendDouble(1.234); + traced_value->AppendDouble(NAN); + traced_value->AppendDouble(INFINITY); + traced_value->AppendDouble(-INFINITY); + traced_value->AppendDouble(1.23e7); + traced_value->AppendBoolean(false); + traced_value->AppendBoolean(true); + traced_value->AppendNull(); + traced_value->BeginDictionary(); + traced_value->BeginArray("foo"); + traced_value->EndArray(); + traced_value->EndDictionary(); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + + static const char* check = "[\"a\",1,1.234,\"NaN\",\"Infinity\"," + "\"-Infinity\",1.23e+07,false,true,null," + "{\"foo\":[]}]"; + + EXPECT_EQ(check, string); +} + +#define UTF8_SEQUENCE "1" "\xE2\x82\xAC" "23\"\x01\b\f\n\r\t\\" +#if defined(NODE_HAVE_I18N_SUPPORT) +# define UTF8_RESULT \ + "\"1\\u20AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"" +#else +# define UTF8_RESULT \ + "\"1\\u00E2\\u0082\\u00AC23\\\"\\u0001\\b\\f\\n\\r\\t\\\\\"" +#endif + +TEST(TracedValue, EscapingObject) { + auto traced_value = TracedValue::Create(); + traced_value->SetString("a", UTF8_SEQUENCE); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + + static const char* check = "{\"a\":" UTF8_RESULT "}"; + + EXPECT_EQ(check, string); +} + +TEST(TracedValue, EscapingArray) { + auto traced_value = TracedValue::CreateArray(); + traced_value->AppendString(UTF8_SEQUENCE); + + std::string string; + traced_value->AppendAsTraceFormat(&string); + + static const char* check = "[" UTF8_RESULT "]"; + + EXPECT_EQ(check, string); +} From 3096ee5a4b6e1ee56d1b4c1776975f26381e8a38 Mon Sep 17 00:00:00 2001 From: Gus Caplan <me@gus.host> Date: Fri, 8 Jun 2018 22:41:20 -0500 Subject: [PATCH 053/116] napi: add bigint support PR-URL: https://github.com/nodejs/node/pull/21226 Reviewed-By: Gabriel Schulhof <gabriel.schulhof@intel.com> Reviewed-By: Kyle Farnung <kfarnung@microsoft.com> --- doc/api/n-api.md | 164 ++++++++++++++++++++- src/node_api.cc | 137 ++++++++++++++++- src/node_api.h | 24 +++ src/node_api_types.h | 4 + test/addons-napi/test_bigint/binding.gyp | 8 + test/addons-napi/test_bigint/test.js | 45 ++++++ test/addons-napi/test_bigint/test_bigint.c | 142 ++++++++++++++++++ test/addons-napi/test_typedarray/test.js | 5 +- 8 files changed, 523 insertions(+), 6 deletions(-) create mode 100644 test/addons-napi/test_bigint/binding.gyp create mode 100644 test/addons-napi/test_bigint/test.js create mode 100644 test/addons-napi/test_bigint/test_bigint.c diff --git a/doc/api/n-api.md b/doc/api/n-api.md index cdb3fcaf8276bf..5fbe4bb07469c2 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -113,10 +113,9 @@ typedef enum { napi_escape_called_twice, napi_handle_scope_mismatch, napi_callback_scope_mismatch, -#ifdef NAPI_EXPERIMENTAL napi_queue_full, napi_closing, -#endif // NAPI_EXPERIMENTAL + napi_bigint_expected, } napi_status; ``` If additional information is required upon an API returning a failed status, @@ -1225,6 +1224,7 @@ typedef enum { napi_object, napi_function, napi_external, + napi_bigint, } napi_valuetype; ``` @@ -1250,6 +1250,8 @@ typedef enum { napi_uint32_array, napi_float32_array, napi_float64_array, + napi_bigint64_array, + napi_biguint64_array, } napi_typedarray_type; ``` @@ -1691,6 +1693,78 @@ This API is used to convert from the C `double` type to the JavaScript The JavaScript `Number` type is described in [Section 6.1.6][] of the ECMAScript Language Specification. +#### napi_create_bigint_int64 +<!-- YAML +added: REPLACEME +--> + +> Stability: 1 - Experimental + +```C +napi_status napi_create_bigint_int64(napi_env env, + int64_t value, + napi_value* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: Integer value to be represented in JavaScript. +- `[out] result`: A `napi_value` representing a JavaScript `BigInt`. + +Returns `napi_ok` if the API succeeded. + +This API converts the C `int64_t` type to the JavaScript `BigInt` type. + +#### napi_create_bigint_uint64 +<!-- YAML +added: REPLACEME +--> + +> Stability: 1 - Experimental + +```C +napi_status napi_create_bigint_uint64(napi_env env, + uint64_t vaue, + napi_value* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: Unsigned integer value to be represented in JavaScript. +- `[out] result`: A `napi_value` representing a JavaScript `BigInt`. + +Returns `napi_ok` if the API succeeded. + +This API converts the C `uint64_t` type to the JavaScript `BigInt` type. + +#### napi_create_bigint_words +<!-- YAML +added: REPLACEME +--> + +> Stability: 1 - Experimental + +```C +napi_status napi_create_bigint_words(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words, + napi_value* result); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] sign_bit`: Determines if the resulting `BigInt` will be positive or + negative. +- `[in] word_count`: The length of the `words` array. +- `[in] words`: An array of `uint64_t` little-endian 64-bit words. +- `[out] result`: A `napi_value` representing a JavaScript `BigInt`. + +Returns `napi_ok` if the API succeeded. + +This API converts an array of unsigned 64-bit words into a single `BigInt` +value. + +The resulting `BigInt` is calculated as: (–1)<sup>`sign_bit`</sup> (`words[0]` +× (2<sup>64</sup>)<sup>0</sup> + `words[1]` × (2<sup>64</sup>)<sup>1</sup> + …) + #### napi_create_string_latin1 <!-- YAML added: v8.0.0 @@ -1975,6 +2049,92 @@ in it returns `napi_number_expected`. This API returns the C double primitive equivalent of the given JavaScript `Number`. +#### napi_get_value_bigint_int64 +<!-- YAML +added: REPLACEME +--> + +> Stability: 1 - Experimental + +```C +napi_status napi_get_value_bigint_int64(napi_env env, + napi_value value, + int64_t* result, + bool* lossless); +``` + +- `[in] env`: The environment that the API is invoked under +- `[in] value`: `napi_value` representing JavaScript `BigInt`. +- `[out] result`: C `int64_t` primitive equivalent of the given JavaScript + `BigInt`. +- `[out] lossless`: Indicates whether the `BigInt` value was converted + losslessly. + +Returns `napi_ok` if the API succeeded. If a non-`BigInt` is passed in it +returns `napi_bigint_expected`. + +This API returns the C `int64_t` primitive equivalent of the given JavaScript +`BigInt`. If needed it will truncate the value, setting `lossless` to `false`. + + +#### napi_get_value_bigint_uint64 +<!-- YAML +added: REPLACEME +--> + +> Stability: 1 - Experimental + +```C +napi_status napi_get_value_bigint_uint64(napi_env env, + napi_value value, + uint64_t* result, + bool* lossless); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript `BigInt`. +- `[out] result`: C `uint64_t` primitive equivalent of the given JavaScript + `BigInt`. +- `[out] lossless`: Indicates whether the `BigInt` value was converted + losslessly. + +Returns `napi_ok` if the API succeeded. If a non-`BigInt` is passed in it +returns `napi_bigint_expected`. + +This API returns the C `uint64_t` primitive equivalent of the given JavaScript +`BigInt`. If needed it will truncate the value, setting `lossless` to `false`. + + +#### napi_get_value_bigint_words +<!-- YAML +added: REPLACEME +--> + +> Stability: 1 - Experimental + +```C +napi_status napi_get_value_bigint_words(napi_env env, + napi_value value, + size_t* word_count, + int* sign_bit, + uint64_t* words); +``` + +- `[in] env`: The environment that the API is invoked under. +- `[in] value`: `napi_value` representing JavaScript `BigInt`. +- `[out] sign_bit`: Integer representing if the JavaScript `BigInt` is positive + or negative. +- `[in/out] word_count`: Must be initialized to the length of the `words` + array. Upon return, it will be set to the actual number of words that + would be needed to store this `BigInt`. +- `[out] words`: Pointer to a pre-allocated 64-bit word array. + +Returns `napi_ok` if the API succeeded. + +This API converts a single `BigInt` value into a sign bit, 64-bit little-endian +array, and the number of elements in the array. `sign_bit` and `words` may be +both set to `NULL`, in order to get only `word_count`. + #### napi_get_value_external <!-- YAML added: v8.0.0 diff --git a/src/node_api.cc b/src/node_api.cc index 1d42435264cfec..ad0796570a4c14 100644 --- a/src/node_api.cc +++ b/src/node_api.cc @@ -926,7 +926,8 @@ const char* error_messages[] = {nullptr, "Invalid handle scope usage", "Invalid callback scope usage", "Thread-safe function queue is full", - "Thread-safe function handle is closing" + "Thread-safe function handle is closing", + "A bigint was expected", }; static inline napi_status napi_clear_last_error(napi_env env) { @@ -958,7 +959,7 @@ napi_status napi_get_last_error_info(napi_env env, // We don't have a napi_status_last as this would result in an ABI // change each time a message was added. static_assert( - node::arraysize(error_messages) == napi_closing + 1, + node::arraysize(error_messages) == napi_bigint_expected + 1, "Count of error messages must match count of error values"); CHECK_LE(env->last_error.error_code, napi_callback_scope_mismatch); @@ -1713,6 +1714,58 @@ napi_status napi_create_int64(napi_env env, return napi_clear_last_error(env); } +napi_status napi_create_bigint_int64(napi_env env, + int64_t value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::BigInt::New(env->isolate, value)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_bigint_uint64(napi_env env, + uint64_t value, + napi_value* result) { + CHECK_ENV(env); + CHECK_ARG(env, result); + + *result = v8impl::JsValueFromV8LocalValue( + v8::BigInt::NewFromUnsigned(env->isolate, value)); + + return napi_clear_last_error(env); +} + +napi_status napi_create_bigint_words(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words, + napi_value* result) { + NAPI_PREAMBLE(env); + CHECK_ARG(env, words); + CHECK_ARG(env, result); + + v8::Local<v8::Context> context = env->isolate->GetCurrentContext(); + + if (word_count > INT_MAX) { + napi_throw_range_error(env, nullptr, "Maximum BigInt size exceeded"); + return napi_set_last_error(env, napi_pending_exception); + } + + v8::MaybeLocal<v8::BigInt> b = v8::BigInt::NewFromWords( + context, sign_bit, word_count, words); + + if (try_catch.HasCaught()) { + return napi_set_last_error(env, napi_pending_exception); + } else { + CHECK_MAYBE_EMPTY(env, b, napi_generic_failure); + *result = v8impl::JsValueFromV8LocalValue(b.ToLocalChecked()); + return napi_clear_last_error(env); + } +} + napi_status napi_get_boolean(napi_env env, bool value, napi_value* result) { CHECK_ENV(env); CHECK_ARG(env, result); @@ -1878,6 +1931,8 @@ napi_status napi_typeof(napi_env env, if (v->IsNumber()) { *result = napi_number; + } else if (v->IsBigInt()) { + *result = napi_bigint; } else if (v->IsString()) { *result = napi_string; } else if (v->IsFunction()) { @@ -2201,6 +2256,72 @@ napi_status napi_get_value_int64(napi_env env, return napi_clear_last_error(env); } +napi_status napi_get_value_bigint_int64(napi_env env, + napi_value value, + int64_t* result, + bool* lossless) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + CHECK_ARG(env, lossless); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + + RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected); + + *result = val.As<v8::BigInt>()->Int64Value(lossless); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_bigint_uint64(napi_env env, + napi_value value, + uint64_t* result, + bool* lossless) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, result); + CHECK_ARG(env, lossless); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + + RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected); + + *result = val.As<v8::BigInt>()->Uint64Value(lossless); + + return napi_clear_last_error(env); +} + +napi_status napi_get_value_bigint_words(napi_env env, + napi_value value, + int* sign_bit, + size_t* word_count, + uint64_t* words) { + CHECK_ENV(env); + CHECK_ARG(env, value); + CHECK_ARG(env, word_count); + + v8::Local<v8::Value> val = v8impl::V8LocalValueFromJsValue(value); + + RETURN_STATUS_IF_FALSE(env, val->IsBigInt(), napi_bigint_expected); + + v8::Local<v8::BigInt> big = val.As<v8::BigInt>(); + + int word_count_int = *word_count; + + if (sign_bit == nullptr && words == nullptr) { + word_count_int = big->WordCount(); + } else { + CHECK_ARG(env, sign_bit); + CHECK_ARG(env, words); + big->ToWordsArray(sign_bit, &word_count_int, words); + } + + *word_count = word_count_int; + + return napi_clear_last_error(env); +} + napi_status napi_get_value_bool(napi_env env, napi_value value, bool* result) { // Omit NAPI_PREAMBLE and GET_RETURN_STATUS because V8 calls here cannot throw // JS exceptions. @@ -3139,6 +3260,14 @@ napi_status napi_create_typedarray(napi_env env, CREATE_TYPED_ARRAY( env, Float64Array, 8, buffer, byte_offset, length, typedArray); break; + case napi_bigint64_array: + CREATE_TYPED_ARRAY( + env, BigInt64Array, 8, buffer, byte_offset, length, typedArray); + break; + case napi_biguint64_array: + CREATE_TYPED_ARRAY( + env, BigUint64Array, 8, buffer, byte_offset, length, typedArray); + break; default: return napi_set_last_error(env, napi_invalid_arg); } @@ -3181,6 +3310,10 @@ napi_status napi_get_typedarray_info(napi_env env, *type = napi_float32_array; } else if (value->IsFloat64Array()) { *type = napi_float64_array; + } else if (value->IsBigInt64Array()) { + *type = napi_bigint64_array; + } else if (value->IsBigUint64Array()) { + *type = napi_biguint64_array; } } diff --git a/src/node_api.h b/src/node_api.h index b22592db15f110..1b2a392a0a0b1c 100644 --- a/src/node_api.h +++ b/src/node_api.h @@ -671,6 +671,30 @@ napi_unref_threadsafe_function(napi_env env, napi_threadsafe_function func); NAPI_EXTERN napi_status napi_ref_threadsafe_function(napi_env env, napi_threadsafe_function func); +NAPI_EXTERN napi_status napi_create_bigint_int64(napi_env env, + int64_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_bigint_uint64(napi_env env, + uint64_t value, + napi_value* result); +NAPI_EXTERN napi_status napi_create_bigint_words(napi_env env, + int sign_bit, + size_t word_count, + const uint64_t* words, + napi_value* result); +NAPI_EXTERN napi_status napi_get_value_bigint_int64(napi_env env, + napi_value value, + int64_t* result, + bool* lossless); +NAPI_EXTERN napi_status napi_get_value_bigint_uint64(napi_env env, + napi_value value, + uint64_t* result, + bool* lossless); +NAPI_EXTERN napi_status napi_get_value_bigint_words(napi_env env, + napi_value value, + int* sign_bit, + size_t* word_count, + uint64_t* words); #endif // NAPI_EXPERIMENTAL EXTERN_C_END diff --git a/src/node_api_types.h b/src/node_api_types.h index 3854a32f011048..10215d9aa3a0ff 100644 --- a/src/node_api_types.h +++ b/src/node_api_types.h @@ -46,6 +46,7 @@ typedef enum { napi_object, napi_function, napi_external, + napi_bigint, } napi_valuetype; typedef enum { @@ -58,6 +59,8 @@ typedef enum { napi_uint32_array, napi_float32_array, napi_float64_array, + napi_bigint64_array, + napi_biguint64_array, } napi_typedarray_type; typedef enum { @@ -78,6 +81,7 @@ typedef enum { napi_callback_scope_mismatch, napi_queue_full, napi_closing, + napi_bigint_expected, } napi_status; #ifdef NAPI_EXPERIMENTAL diff --git a/test/addons-napi/test_bigint/binding.gyp b/test/addons-napi/test_bigint/binding.gyp new file mode 100644 index 00000000000000..1b9f75bab4f737 --- /dev/null +++ b/test/addons-napi/test_bigint/binding.gyp @@ -0,0 +1,8 @@ +{ + "targets": [ + { + "target_name": "test_bigint", + "sources": [ "test_bigint.c" ] + } + ] +} diff --git a/test/addons-napi/test_bigint/test.js b/test/addons-napi/test_bigint/test.js new file mode 100644 index 00000000000000..b92c810459321b --- /dev/null +++ b/test/addons-napi/test_bigint/test.js @@ -0,0 +1,45 @@ +'use strict'; +const common = require('../../common'); +const assert = require('assert'); +const { + IsLossless, + TestInt64, + TestUint64, + TestWords, + CreateTooBigBigInt, +} = require(`./build/${common.buildType}/test_bigint`); + +[ + 0n, + -0n, + 1n, + -1n, + 100n, + 2121n, + -1233n, + 986583n, + -976675n, + 98765432213456789876546896323445679887645323232436587988766545658n, + -4350987086545760976737453646576078997096876957864353245245769809n, +].forEach((num) => { + if (num > -(2n ** 63n) && num < 2n ** 63n) { + assert.strictEqual(TestInt64(num), num); + assert.strictEqual(IsLossless(num, true), true); + } else { + assert.strictEqual(IsLossless(num, true), false); + } + + if (num >= 0 && num < 2n ** 64n) { + assert.strictEqual(TestUint64(num), num); + assert.strictEqual(IsLossless(num, false), true); + } else { + assert.strictEqual(IsLossless(num, false), false); + } + + assert.strictEqual(num, TestWords(num)); +}); + +assert.throws(CreateTooBigBigInt, { + name: 'RangeError', + message: 'Maximum BigInt size exceeded', +}); diff --git a/test/addons-napi/test_bigint/test_bigint.c b/test/addons-napi/test_bigint/test_bigint.c new file mode 100644 index 00000000000000..e3516628e88b35 --- /dev/null +++ b/test/addons-napi/test_bigint/test_bigint.c @@ -0,0 +1,142 @@ +#define NAPI_EXPERIMENTAL + +#include <inttypes.h> +#include <stdio.h> +#include <node_api.h> +#include "../common.h" + +static napi_value IsLossless(napi_env env, napi_callback_info info) { + size_t argc = 2; + napi_value args[2]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &args, NULL, NULL)); + + bool is_signed; + NAPI_CALL(env, napi_get_value_bool(env, args[1], &is_signed)); + + bool lossless; + + if (is_signed) { + int64_t input; + NAPI_CALL(env, napi_get_value_bigint_int64(env, args[0], &input, &lossless)); + } else { + uint64_t input; + NAPI_CALL(env, napi_get_value_bigint_uint64(env, args[0], &input, &lossless)); + } + + napi_value output; + NAPI_CALL(env, napi_get_boolean(env, lossless, &output)); + + return output; +} + +static napi_value TestInt64(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + int64_t input; + bool lossless; + NAPI_CALL(env, napi_get_value_bigint_int64(env, args[0], &input, &lossless)); + + napi_value output; + NAPI_CALL(env, napi_create_bigint_int64(env, input, &output)); + + return output; +} + +static napi_value TestUint64(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + uint64_t input; + bool lossless; + NAPI_CALL(env, napi_get_value_bigint_uint64( + env, args[0], &input, &lossless)); + + napi_value output; + NAPI_CALL(env, napi_create_bigint_uint64(env, input, &output)); + + return output; +} + +static napi_value TestWords(napi_env env, napi_callback_info info) { + size_t argc = 1; + napi_value args[1]; + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); + + NAPI_ASSERT(env, argc >= 1, "Wrong number of arguments"); + + napi_valuetype valuetype0; + NAPI_CALL(env, napi_typeof(env, args[0], &valuetype0)); + + NAPI_ASSERT(env, valuetype0 == napi_bigint, + "Wrong type of arguments. Expects a bigint as first argument."); + + size_t expected_word_count; + NAPI_CALL(env, napi_get_value_bigint_words( + env, args[0], NULL, &expected_word_count, NULL)); + + int sign_bit; + size_t word_count = 10; + uint64_t words[10]; + + NAPI_CALL(env, napi_get_value_bigint_words( + env, args[0], &sign_bit, &word_count, &words)); + + NAPI_ASSERT(env, word_count == expected_word_count, + "word counts do not match"); + + napi_value output; + NAPI_CALL(env, napi_create_bigint_words( + env, sign_bit, word_count, words, &output)); + + return output; +} + +// throws RangeError +static napi_value CreateTooBigBigInt(napi_env env, napi_callback_info info) { + int sign_bit = 0; + size_t word_count = SIZE_MAX; + uint64_t words[10]; + + napi_value output; + + NAPI_CALL(env, napi_create_bigint_words( + env, sign_bit, word_count, words, &output)); + + return output; +} + +static napi_value Init(napi_env env, napi_value exports) { + napi_property_descriptor descriptors[] = { + DECLARE_NAPI_PROPERTY("IsLossless", IsLossless), + DECLARE_NAPI_PROPERTY("TestInt64", TestInt64), + DECLARE_NAPI_PROPERTY("TestUint64", TestUint64), + DECLARE_NAPI_PROPERTY("TestWords", TestWords), + DECLARE_NAPI_PROPERTY("CreateTooBigBigInt", CreateTooBigBigInt), + }; + + NAPI_CALL(env, napi_define_properties( + env, exports, sizeof(descriptors) / sizeof(*descriptors), descriptors)); + + return exports; +} + +NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) diff --git a/test/addons-napi/test_typedarray/test.js b/test/addons-napi/test_typedarray/test.js index 41915b380be8e8..2a8cf18feb866c 100644 --- a/test/addons-napi/test_typedarray/test.js +++ b/test/addons-napi/test_typedarray/test.js @@ -42,7 +42,7 @@ assert.strictEqual(externalResult[2], 2); const buffer = new ArrayBuffer(128); const arrayTypes = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, - Float64Array ]; + Float64Array, BigInt64Array, BigUint64Array ]; arrayTypes.forEach((currentType) => { const template = Reflect.construct(currentType, buffer); @@ -64,7 +64,8 @@ arrayTypes.forEach((currentType) => { }); const nonByteArrayTypes = [ Int16Array, Uint16Array, Int32Array, Uint32Array, - Float32Array, Float64Array ]; + Float32Array, Float64Array, + BigInt64Array, BigUint64Array ]; nonByteArrayTypes.forEach((currentType) => { const template = Reflect.construct(currentType, buffer); assert.throws(() => { From e030dd7d6524641e2184a70ec14d5ddad80fb2c5 Mon Sep 17 00:00:00 2001 From: Gus Caplan <me@gus.host> Date: Sat, 7 Jul 2018 23:32:23 -0500 Subject: [PATCH 054/116] tools: add no-duplicate-requires rule PR-URL: https://github.com/nodejs/node/pull/21712 Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Weijia Wang <starkwang@126.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jon Moss <me@jonathanmoss.me> --- .eslintrc.js | 2 + benchmark/streams/creation.js | 10 +-- doc/api/worker_threads.md | 4 +- lib/internal/http2/core.js | 8 ++- lib/internal/modules/esm/translators.js | 7 +- test/.eslintrc.yaml | 1 + .../test-eslint-duplicate-requires.js | 25 +++++++ tools/eslint-rules/no-duplicate-requires.js | 70 +++++++++++++++++++ 8 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 test/parallel/test-eslint-duplicate-requires.js create mode 100644 tools/eslint-rules/no-duplicate-requires.js diff --git a/.eslintrc.js b/.eslintrc.js index 742f9ea6d38ef9..98568dd9bc4311 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -98,6 +98,7 @@ module.exports = { 'no-dupe-class-members': 'error', 'no-dupe-keys': 'error', 'no-duplicate-case': 'error', + 'no-duplicate-imports': 'error', 'no-empty-character-class': 'error', 'no-ex-assign': 'error', 'no-extra-boolean-cast': 'error', @@ -246,6 +247,7 @@ module.exports = { // Custom rules from eslint-plugin-node-core 'node-core/no-unescaped-regexp-dot': 'error', + 'node-core/no-duplicate-requires': 'error', }, globals: { Atomics: false, diff --git a/benchmark/streams/creation.js b/benchmark/streams/creation.js index 67187f91bd9cb1..46a0a547907c45 100644 --- a/benchmark/streams/creation.js +++ b/benchmark/streams/creation.js @@ -1,9 +1,11 @@ 'use strict'; const common = require('../common.js'); -const Duplex = require('stream').Duplex; -const Readable = require('stream').Readable; -const Transform = require('stream').Transform; -const Writable = require('stream').Writable; +const { + Duplex, + Readable, + Transform, + Writable, +} = require('stream'); const bench = common.createBenchmark(main, { n: [50e6], diff --git a/doc/api/worker_threads.md b/doc/api/worker_threads.md index d20fd0fce57c80..8b301f42a87620 100644 --- a/doc/api/worker_threads.md +++ b/doc/api/worker_threads.md @@ -286,7 +286,7 @@ For example: ```js const assert = require('assert'); const { - Worker, MessageChannel, MessagePort, isMainThread + Worker, MessageChannel, MessagePort, isMainThread, parentPort } = require('worker_threads'); if (isMainThread) { const worker = new Worker(__filename); @@ -296,7 +296,7 @@ if (isMainThread) { console.log('received:', value); }); } else { - require('worker_threads').once('message', (value) => { + parentPort.once('message', (value) => { assert(value.hereIsYourPort instanceof MessagePort); value.hereIsYourPort.postMessage('the worker is sending this'); value.hereIsYourPort.close(); diff --git a/lib/internal/http2/core.js b/lib/internal/http2/core.js index 5a4fa63335581d..db251caf5f73ef 100644 --- a/lib/internal/http2/core.js +++ b/lib/internal/http2/core.js @@ -24,8 +24,12 @@ const { kIncomingMessage } = require('_http_common'); const { kServerResponse } = require('_http_server'); const { StreamWrap } = require('_stream_wrap'); -const { defaultTriggerAsyncIdScope } = require('internal/async_hooks'); -const { async_id_symbol } = require('internal/async_hooks').symbols; +const { + defaultTriggerAsyncIdScope, + symbols: { + async_id_symbol, + }, +} = require('internal/async_hooks'); const { internalBinding } = require('internal/bootstrap/loaders'); const { codes: { diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index 7785865d6573f9..618f1adac8deb7 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -14,13 +14,14 @@ const fs = require('fs'); const { _makeLong } = require('path'); const { SafeMap } = require('internal/safe_globals'); const { URL } = require('url'); -const util = require('util'); -const debug = util.debuglog('esm'); -const readFileAsync = util.promisify(fs.readFile); +const { debuglog, promisify } = require('util'); +const readFileAsync = promisify(fs.readFile); const readFileSync = fs.readFileSync; const StringReplace = Function.call.bind(String.prototype.replace); const JsonParse = JSON.parse; +const debug = debuglog('esm'); + const translators = new SafeMap(); module.exports = translators; diff --git a/test/.eslintrc.yaml b/test/.eslintrc.yaml index c57d7505c984ff..25026fec5a103a 100644 --- a/test/.eslintrc.yaml +++ b/test/.eslintrc.yaml @@ -18,6 +18,7 @@ rules: node-core/number-isnan: error ## common module is mandatory in tests node-core/required-modules: [error, common] + node-core/no-duplicate-requires: off no-restricted-syntax: # Config copied from .eslintrc.js diff --git a/test/parallel/test-eslint-duplicate-requires.js b/test/parallel/test-eslint-duplicate-requires.js new file mode 100644 index 00000000000000..c7edaeed2b6b6e --- /dev/null +++ b/test/parallel/test-eslint-duplicate-requires.js @@ -0,0 +1,25 @@ +'use strict'; + +const common = require('../common'); + +common.skipIfEslintMissing(); + +const { RuleTester } = require('../../tools/node_modules/eslint'); +const rule = require('../../tools/eslint-rules/no-duplicate-requires'); + +new RuleTester().run('no-duplicate-requires', rule, { + valid: [ + { + code: 'require("a"); require("b"); (function() { require("a"); });', + }, + { + code: 'require(a); require(a);', + }, + ], + invalid: [ + { + code: 'require("a"); require("a");', + errors: [{ message: '\'a\' require is duplicated.' }], + }, + ], +}); diff --git a/tools/eslint-rules/no-duplicate-requires.js b/tools/eslint-rules/no-duplicate-requires.js new file mode 100644 index 00000000000000..595c22360112ca --- /dev/null +++ b/tools/eslint-rules/no-duplicate-requires.js @@ -0,0 +1,70 @@ +/** + * @fileoverview Ensure modules are not required twice at top level of a module + * @author devsnek + */ +'use strict'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + + +function isString(node) { + return node && node.type === 'Literal' && typeof node.value === 'string'; +} + +function isRequireCall(node) { + return node.callee.type === 'Identifier' && node.callee.name === 'require'; +} + +function isTopLevel(node) { + do { + if (node.type === 'FunctionDeclaration' || + node.type === 'FunctionExpression' || + node.type === 'ArrowFunctionExpression' || + node.type === 'ClassBody' || + node.type === 'MethodDefinition') { + return false; + } + } while (node = node.parent); + return true; +} + +module.exports = (context) => { + if (context.parserOptions.sourceType === 'module') { + return {}; + } + + function getRequiredModuleNameFromCall(node) { + // node has arguments and first argument is string + if (node.arguments.length && isString(node.arguments[0])) { + return node.arguments[0].value.trim(); + } + + return undefined; + } + + const required = new Set(); + + const rules = { + CallExpression: (node) => { + if (isRequireCall(node) && isTopLevel(node)) { + const moduleName = getRequiredModuleNameFromCall(node); + if (moduleName === undefined) { + return; + } + if (required.has(moduleName)) { + context.report( + node, + '\'{{moduleName}}\' require is duplicated.', + { moduleName } + ); + } else { + required.add(moduleName); + } + } + }, + }; + + return rules; +}; From c26ba082aede8b201030dfb0d34a91e01ffd1598 Mon Sep 17 00:00:00 2001 From: Rich Trott <rtrott@gmail.com> Date: Tue, 10 Jul 2018 22:30:56 -0700 Subject: [PATCH 055/116] tools: avoid global install of dmn for lint update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When updating ESLint, use npx to run dmn rather than installing dmn globally. PR-URL: https://github.com/nodejs/node/pull/21744 Reviewed-By: Roman Reiss <me@silverwind.io> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- tools/update-eslint.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/tools/update-eslint.sh b/tools/update-eslint.sh index 44f43a05d31e1a..3663ecf74283c0 100755 --- a/tools/update-eslint.sh +++ b/tools/update-eslint.sh @@ -2,7 +2,7 @@ # Shell script to update ESLint in the source tree to the latest release. -# Depends on npm and node being in $PATH. +# Depends on npm, npx, and node being in $PATH. # This script must be be in the tools directory when it runs because it uses # $BASH_SOURCE[0] to determine directories to work in. @@ -19,11 +19,8 @@ cd node_modules/eslint npm install --no-bin-links --production --no-package-lock eslint-plugin-markdown@next cd ../.. -# Install dmn if it is not in path. -type -P dmn || npm install -g dmn - # Use dmn to remove some unneeded files. -dmn -f clean +npx dmn -f clean cd .. mv eslint-tmp/node_modules/eslint node_modules/eslint From 3fffc7e95fa781cf57357691e78a94a6f05d0068 Mon Sep 17 00:00:00 2001 From: Shailesh Shekhawat <sshekhawat356@gmail.com> Date: Wed, 27 Jun 2018 22:05:32 +0800 Subject: [PATCH 056/116] errors: fix undefined HTTP2 and tls errors Includes implementation of tls, HTTP2 error with documentation. PR-URL: https://github.com/nodejs/node/pull/21564 Refs: https://github.com/nodejs/node/issues/21440 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> --- doc/api/errors.md | 10 ++++++++++ lib/internal/errors.js | 2 ++ 2 files changed, 12 insertions(+) diff --git a/doc/api/errors.md b/doc/api/errors.md index a122bc51e39ca6..be22aa1527727d 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1014,6 +1014,11 @@ provided. The `Http2Session` closed with a non-zero error code. +<a id="ERR_HTTP2_SETTINGS_CANCEL"></a> +### ERR_HTTP2_SETTINGS_CANCEL + +The `Http2Session` settings canceled. + <a id="ERR_HTTP2_SOCKET_BOUND"></a> ### ERR_HTTP2_SOCKET_BOUND @@ -1629,6 +1634,11 @@ recommended to use 2048 bits or larger for stronger security. A TLS/SSL handshake timed out. In this case, the server must also abort the connection. +<a id="ERR_TLS_RENEGOTIATE"></a> +### ERR_TLS_RENEGOTIATE + +An attempt to renegotiate the TLS session failed. + <a id="ERR_TLS_RENEGOTIATION_DISABLED"></a> ### ERR_TLS_RENEGOTIATION_DISABLED diff --git a/lib/internal/errors.js b/lib/internal/errors.js index c3f1c374011b05..aef05489b48c74 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -585,6 +585,7 @@ E('ERR_HTTP2_SEND_FILE', 'Directories cannot be sent', Error); E('ERR_HTTP2_SEND_FILE_NOSEEK', 'Offset or length can only be specified for regular files', Error); E('ERR_HTTP2_SESSION_ERROR', 'Session closed with error code %s', Error); +E('ERR_HTTP2_SETTINGS_CANCEL', 'HTTP2 session settings canceled', Error); E('ERR_HTTP2_SOCKET_BOUND', 'The socket is already bound to an Http2Session', Error); E('ERR_HTTP2_STATUS_101', @@ -812,6 +813,7 @@ E('ERR_TLS_CERT_ALTNAME_INVALID', 'Hostname/IP does not match certificate\'s altnames: %s', Error); E('ERR_TLS_DH_PARAM_SIZE', 'DH parameter size %s is less than 2048', Error); E('ERR_TLS_HANDSHAKE_TIMEOUT', 'TLS handshake timeout', Error); +E('ERR_TLS_RENEGOTIATE', 'Attempt to renegotiate TLS session failed', Error); E('ERR_TLS_RENEGOTIATION_DISABLED', 'TLS session renegotiation disabled for this socket', Error); From 3d93273bf73a767ef8b64bf68ca73c5aa58b7de4 Mon Sep 17 00:00:00 2001 From: XadillaX <i@2333.moe> Date: Wed, 30 May 2018 18:21:20 +0800 Subject: [PATCH 057/116] doc: add OS X to instead of only macOS The last OS X version is 10.11 El Captian, and the first macOS version is 10.12 Sierra. So >= 10.8 < 10.10 is called OS X and >= 10.10 is called OS X or macOS. Refs: https://en.wikipedia.org/wiki/MacOS#Release_history PR-URL: https://github.com/nodejs/node/pull/21033 Reviewed-By: Roman Reiss <me@silverwind.io> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Yorkie Liu <yorkiefixer@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> --- BUILDING.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BUILDING.md b/BUILDING.md index 48c287a319273e..a37ecb701ef04e 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -41,14 +41,14 @@ platforms in production. |--------------|--------------|----------------------------------|----------------------|------------------| | GNU/Linux | Tier 1 | kernel >= 2.6.32, glibc >= 2.12 | x64, arm | | | GNU/Linux | Tier 1 | kernel >= 3.10, glibc >= 2.17 | arm64 | | -| macOS | Tier 1 | >= 10.10 | x64 | | +| macOS/OS X | Tier 1 | >= 10.10 | x64 | | | Windows | Tier 1 | >= Windows 7/2008 R2/2012 R2 | x86, x64 | vs2017 | | SmartOS | Tier 2 | >= 15 < 16.4 | x86, x64 | see note1 | | FreeBSD | Tier 2 | >= 10 | x64 | | | GNU/Linux | Tier 2 | kernel >= 3.13.0, glibc >= 2.19 | ppc64le >=power8 | | | AIX | Tier 2 | >= 7.1 TL04 | ppc64be >=power7 | | | GNU/Linux | Tier 2 | kernel >= 3.10, glibc >= 2.17 | s390x | | -| macOS | Experimental | >= 10.8 < 10.10 | x64 | no test coverage | +| OS X | Experimental | >= 10.8 < 10.10 | x64 | no test coverage | | GNU/Linux | Experimental | kernel >= 2.6.32, glibc >= 2.12 | x86 | limited CI | | Linux (musl) | Experimental | musl >= 1.0 | x64 | | @@ -408,7 +408,7 @@ This version of Node.js does not support FIPS. It is possible to specify one or more JavaScript text files to be bundled in the binary as builtin modules when building Node.js. -### Unix / macOS +### Unix/macOS This command will make `/root/myModule.js` available via `require('/root/myModule')` and `./myModule2.js` available via From bba500d0eac378662bb7dbd4ef1ac4232cedca66 Mon Sep 17 00:00:00 2001 From: killagu <killa123@126.com> Date: Fri, 8 Jun 2018 12:42:27 +0800 Subject: [PATCH 058/116] http: fix request with option timeout and agent When request with both timeout and agent, timeout not work. This patch will fix it, socket timeout will set to request timeout before socket is connected, and socket timeout will reset to agent timeout after response end. Fixes: https://github.com/nodejs/node/issues/21185 PR-URL: https://github.com/nodejs/node/pull/21204 Reviewed-By: Khaidi Chu <i@2333.moe> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> --- doc/api/http.md | 2 + lib/_http_agent.js | 33 +++++++-- lib/_http_client.js | 74 ++++++++++--------- lib/net.js | 1 + test/parallel/test-http-client-set-timeout.js | 46 ++++++++++++ ...t-http-client-timeout-option-with-agent.js | 55 ++++++++++++++ 6 files changed, 169 insertions(+), 42 deletions(-) create mode 100644 test/parallel/test-http-client-set-timeout.js create mode 100644 test/parallel/test-http-client-timeout-option-with-agent.js diff --git a/doc/api/http.md b/doc/api/http.md index fe1c387654e9c8..578f21c45e30ca 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -126,6 +126,8 @@ added: v0.3.4 * `maxFreeSockets` {number} Maximum number of sockets to leave open in a free state. Only relevant if `keepAlive` is set to `true`. **Default:** `256`. + * `timeout` {number} Socket timeout in milliseconds. + This will set the timeout after the socket is connected. The default [`http.globalAgent`][] that is used by [`http.request()`][] has all of these values set to their respective defaults. diff --git a/lib/_http_agent.js b/lib/_http_agent.js index 67f3d667b28169..1a2920cf098298 100644 --- a/lib/_http_agent.js +++ b/lib/_http_agent.js @@ -66,7 +66,8 @@ function Agent(options) { if (socket.writable && this.requests[name] && this.requests[name].length) { - this.requests[name].shift().onSocket(socket); + const req = this.requests[name].shift(); + setRequestSocket(this, req, socket); if (this.requests[name].length === 0) { // don't leak delete this.requests[name]; @@ -176,12 +177,12 @@ Agent.prototype.addRequest = function addRequest(req, options, port/* legacy */, delete this.freeSockets[name]; this.reuseSocket(socket, req); - req.onSocket(socket); + setRequestSocket(this, req, socket); this.sockets[name].push(socket); } else if (sockLen < this.maxSockets) { debug('call onSocket', sockLen, freeLen); // If we are under maxSockets create a new one. - this.createSocket(req, options, handleSocketCreation(req, true)); + this.createSocket(req, options, handleSocketCreation(this, req, true)); } else { debug('wait for socket'); // We are over limit so we'll add it to the queue. @@ -305,9 +306,10 @@ Agent.prototype.removeSocket = function removeSocket(s, options) { if (this.requests[name] && this.requests[name].length) { debug('removeSocket, have a request, make a socket'); - var req = this.requests[name][0]; + const req = this.requests[name][0]; // If we have pending requests and a socket gets closed make a new one - this.createSocket(req, options, handleSocketCreation(req, false)); + const socketCreationHandler = handleSocketCreation(this, req, false); + this.createSocket(req, options, socketCreationHandler); } }; @@ -337,19 +339,36 @@ Agent.prototype.destroy = function destroy() { } }; -function handleSocketCreation(request, informRequest) { +function handleSocketCreation(agent, request, informRequest) { return function handleSocketCreation_Inner(err, socket) { if (err) { process.nextTick(emitErrorNT, request, err); return; } if (informRequest) - request.onSocket(socket); + setRequestSocket(agent, request, socket); else socket.emit('free'); }; } +function setRequestSocket(agent, req, socket) { + req.onSocket(socket); + const agentTimeout = agent.options.timeout || 0; + if (req.timeout === undefined || req.timeout === agentTimeout) { + return; + } + socket.setTimeout(req.timeout); + // reset timeout after response end + req.once('response', (res) => { + res.once('end', () => { + if (socket.timeout !== agentTimeout) { + socket.setTimeout(agentTimeout); + } + }); + }); +} + function emitErrorNT(emitter, err) { emitter.emit('error', err); } diff --git a/lib/_http_client.js b/lib/_http_client.js index d3616eb6b5e7f9..0612f5822a303f 100644 --- a/lib/_http_client.js +++ b/lib/_http_client.js @@ -639,18 +639,35 @@ function tickOnSocket(req, socket) { socket.on('end', socketOnEnd); socket.on('close', socketCloseListener); - if (req.timeout) { - const emitRequestTimeout = () => req.emit('timeout'); - socket.once('timeout', emitRequestTimeout); - req.once('response', (res) => { - res.once('end', () => { - socket.removeListener('timeout', emitRequestTimeout); - }); - }); + if (req.timeout !== undefined) { + listenSocketTimeout(req); } req.emit('socket', socket); } +function listenSocketTimeout(req) { + if (req.timeoutCb) { + return; + } + const emitRequestTimeout = () => req.emit('timeout'); + // Set timeoutCb so it will get cleaned up on request end. + req.timeoutCb = emitRequestTimeout; + // Delegate socket timeout event. + if (req.socket) { + req.socket.once('timeout', emitRequestTimeout); + } else { + req.on('socket', (socket) => { + socket.once('timeout', emitRequestTimeout); + }); + } + // Remove socket timeout listener after response end. + req.once('response', (res) => { + res.once('end', () => { + req.socket.removeListener('timeout', emitRequestTimeout); + }); + }); +} + ClientRequest.prototype.onSocket = function onSocket(socket) { process.nextTick(onSocketNT, this, socket); }; @@ -700,42 +717,29 @@ function _deferToConnect(method, arguments_, cb) { } ClientRequest.prototype.setTimeout = function setTimeout(msecs, callback) { + listenSocketTimeout(this); msecs = validateTimerDuration(msecs); if (callback) this.once('timeout', callback); - const emitTimeout = () => this.emit('timeout'); - - if (this.socket && this.socket.writable) { - if (this.timeoutCb) - this.socket.setTimeout(0, this.timeoutCb); - this.timeoutCb = emitTimeout; - this.socket.setTimeout(msecs, emitTimeout); - return this; - } - - // Set timeoutCb so that it'll get cleaned up on request end - this.timeoutCb = emitTimeout; if (this.socket) { - var sock = this.socket; - this.socket.once('connect', function() { - sock.setTimeout(msecs, emitTimeout); - }); - return this; + setSocketTimeout(this.socket, msecs); + } else { + this.once('socket', (sock) => setSocketTimeout(sock, msecs)); } - this.once('socket', function(sock) { - if (sock.connecting) { - sock.once('connect', function() { - sock.setTimeout(msecs, emitTimeout); - }); - } else { - sock.setTimeout(msecs, emitTimeout); - } - }); - return this; }; +function setSocketTimeout(sock, msecs) { + if (sock.connecting) { + sock.once('connect', function() { + sock.setTimeout(msecs); + }); + } else { + sock.setTimeout(msecs); + } +} + ClientRequest.prototype.setNoDelay = function setNoDelay(noDelay) { this._deferToConnect('setNoDelay', [noDelay]); }; diff --git a/lib/net.js b/lib/net.js index 2393539737910a..889f28b0aa7042 100644 --- a/lib/net.js +++ b/lib/net.js @@ -408,6 +408,7 @@ function writeAfterFIN(chunk, encoding, cb) { } Socket.prototype.setTimeout = function(msecs, callback) { + this.timeout = msecs; // Type checking identical to timers.enroll() msecs = validateTimerDuration(msecs); diff --git a/test/parallel/test-http-client-set-timeout.js b/test/parallel/test-http-client-set-timeout.js new file mode 100644 index 00000000000000..51284b42765493 --- /dev/null +++ b/test/parallel/test-http-client-set-timeout.js @@ -0,0 +1,46 @@ +'use strict'; +const common = require('../common'); + +// Test that `req.setTimeout` will fired exactly once. + +const assert = require('assert'); +const http = require('http'); + +const HTTP_CLIENT_TIMEOUT = 2000; + +const options = { + method: 'GET', + port: undefined, + host: '127.0.0.1', + path: '/', + timeout: HTTP_CLIENT_TIMEOUT, +}; + +const server = http.createServer(() => { + // Never respond. +}); + +server.listen(0, options.host, function() { + doRequest(); +}); + +function doRequest() { + options.port = server.address().port; + const req = http.request(options); + req.setTimeout(HTTP_CLIENT_TIMEOUT / 2); + req.on('error', () => { + // This space is intentionally left blank. + }); + req.on('close', common.mustCall(() => server.close())); + + let timeout_events = 0; + req.on('timeout', common.mustCall(() => { + timeout_events += 1; + })); + req.end(); + + setTimeout(function() { + req.destroy(); + assert.strictEqual(timeout_events, 1); + }, common.platformTimeout(HTTP_CLIENT_TIMEOUT)); +} diff --git a/test/parallel/test-http-client-timeout-option-with-agent.js b/test/parallel/test-http-client-timeout-option-with-agent.js new file mode 100644 index 00000000000000..a7f750a42e557b --- /dev/null +++ b/test/parallel/test-http-client-timeout-option-with-agent.js @@ -0,0 +1,55 @@ +'use strict'; +const common = require('../common'); + +// Test that when http request uses both timeout and agent, +// timeout will work as expected. + +const assert = require('assert'); +const http = require('http'); + +const HTTP_AGENT_TIMEOUT = 1000; +const HTTP_CLIENT_TIMEOUT = 3000; + +const agent = new http.Agent({ timeout: HTTP_AGENT_TIMEOUT }); +const options = { + method: 'GET', + port: undefined, + host: '127.0.0.1', + path: '/', + timeout: HTTP_CLIENT_TIMEOUT, + agent, +}; + +const server = http.createServer(() => { + // Never respond. +}); + +server.listen(0, options.host, function() { + doRequest(); +}); + +function doRequest() { + options.port = server.address().port; + const req = http.request(options); + const start = Date.now(); + req.on('error', () => { + // This space is intentionally left blank. + }); + req.on('close', common.mustCall(() => server.close())); + + let timeout_events = 0; + req.on('timeout', common.mustCall(() => { + timeout_events += 1; + const duration = Date.now() - start; + // The timeout event cannot be precisely timed. It will delay + // some number of milliseconds, so test it in second units. + assert.strictEqual(duration / 1000 | 0, HTTP_CLIENT_TIMEOUT / 1000); + })); + req.end(); + + setTimeout(function() { + req.destroy(); + assert.strictEqual(timeout_events, 1); + // Ensure the `timeout` event fired only once. + }, common.platformTimeout(HTTP_CLIENT_TIMEOUT * 2)); +} From 4b613d39765eb0529fc6b01b172d7e7e70beeaa1 Mon Sep 17 00:00:00 2001 From: Sam Ruby <rubys@intertwingly.net> Date: Fri, 29 Jun 2018 12:49:36 -0400 Subject: [PATCH 059/116] repl: make own properties shadow prototype properties Previously, the code displayed properties backwards (e.g., showing prototype properties before own properties). It also did uniqueness checks during this processing, so these checks were done backwards. After this change, the properties continue to be displayed backwards, but the uniqueness checks are done in the proper order. See also: https://github.com/nodejs/node/issues/21586 which was discovered during the testing of this fix. Fixes: https://github.com/nodejs/node/issues/15199 PR-URL: https://github.com/nodejs/node/pull/21588 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> --- lib/repl.js | 12 ++++++------ test/parallel/test-repl-tab-complete.js | 19 ++++++++++++++++++- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index 6345c742f68f33..92c90de7bb1646 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -1229,20 +1229,20 @@ function complete(line, callback) { // Completion group 0 is the "closest" // (least far up the inheritance chain) // so we put its completions last: to be closest in the REPL. - for (i = completionGroups.length - 1; i >= 0; i--) { + for (i = 0; i < completionGroups.length; i++) { group = completionGroups[i]; group.sort(); - for (var j = 0; j < group.length; j++) { + for (var j = group.length - 1; j >= 0; j--) { c = group[j]; if (!hasOwnProperty(uniq, c)) { - completions.push(c); + completions.unshift(c); uniq[c] = true; } } - completions.push(''); // Separator btwn groups + completions.unshift(''); // Separator btwn groups } - while (completions.length && completions[completions.length - 1] === '') { - completions.pop(); + while (completions.length && completions[0] === '') { + completions.shift(); } } diff --git a/test/parallel/test-repl-tab-complete.js b/test/parallel/test-repl-tab-complete.js index 0e6c4bb654910e..9d6ecf4e284811 100644 --- a/test/parallel/test-repl-tab-complete.js +++ b/test/parallel/test-repl-tab-complete.js @@ -149,7 +149,10 @@ putIn.run([ ' one:1', '};' ]); -testMe.complete('inner.o', getNoResultsFunction()); +// See: https://github.com/nodejs/node/issues/21586 +// testMe.complete('inner.o', getNoResultsFunction()); +testMe.complete('inner.o', common.mustCall(function(error, data) { +})); putIn.run(['.clear']); @@ -206,6 +209,20 @@ testMe.complete('toSt', common.mustCall(function(error, data) { assert.deepStrictEqual(data, [['toString'], 'toSt']); })); +// own properties should shadow properties on the prototype +putIn.run(['.clear']); +putIn.run([ + 'var x = Object.create(null);', + 'x.a = 1;', + 'x.b = 2;', + 'var y = Object.create(x);', + 'y.a = 3;', + 'y.c = 4;' +]); +testMe.complete('y.', common.mustCall(function(error, data) { + assert.deepStrictEqual(data, [['y.b', '', 'y.a', 'y.c'], 'y.']); +})); + // Tab complete provides built in libs for require() putIn.run(['.clear']); From c02fb88936acc3fbb654464d0edc25dcea2f286a Mon Sep 17 00:00:00 2001 From: Octavian Soldea <octavian.soldea@intel.com> Date: Thu, 5 Jul 2018 11:19:22 -0700 Subject: [PATCH 060/116] build: enabling lto at configure This modification allows for compiling with link time optimization (lto) using the flag --enable-lto. Refs: https://github.com/nodejs/node/issues/7400 PR-URL: https://github.com/nodejs/node/pull/21677 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> --- common.gypi | 11 +++++++++++ configure | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/common.gypi b/common.gypi index 65eb7caf575498..9d74b46a8d5c70 100644 --- a/common.gypi +++ b/common.gypi @@ -176,6 +176,17 @@ ['OS!="mac" and OS!="win"', { 'cflags': [ '-fno-omit-frame-pointer' ], }], + ['OS=="linux"', { + 'variables': { + 'lto': ' -flto=4 -fuse-linker-plugin -ffat-lto-objects ', + }, + 'conditions': [ + ['enable_lto=="true"', { + 'cflags': ['<(lto)'], + 'ldflags': ['<(lto)'], + },], + ], + },], ['OS == "android"', { 'cflags': [ '-fPIE' ], 'ldflags': [ '-fPIE', '-pie' ] diff --git a/configure b/configure index 094d8e09f745d9..068fb8a76999a1 100755 --- a/configure +++ b/configure @@ -157,6 +157,11 @@ parser.add_option("--enable-vtune-profiling", "JavaScript code executed in nodejs. This feature is only available " "for x32, x86, and x64 architectures.") +parser.add_option("--enable-lto", + action="store_true", + dest="enable_lto", + help="Enable compiling with lto of a binary. This feature is only available " + "on linux with gcc and g++.") parser.add_option("--link-module", action="append", @@ -932,6 +937,25 @@ def configure_node(o): else: o['variables']['node_enable_v8_vtunejit'] = 'false' + if flavor != 'linux' and (options.enable_lto): + raise Exception( + 'The lto option is supported only on linux.') + + if flavor == 'linux': + if options.enable_lto: + version_checked = (5, 4, 1) + for compiler in [(CC, 'c'), (CXX, 'c++')]: + ok, is_clang, clang_version, compiler_version = \ + try_check_compiler(compiler[0], compiler[1]) + compiler_version_num = tuple(map(int, compiler_version)) + if is_clang or compiler_version_num < version_checked: + version_checked_str = ".".join(map(str, version_checked)) + raise Exception( + 'The option --enable-lto is supported for gcc and gxx %s' + ' or newer only.' % (version_checked_str)) + + o['variables']['enable_lto'] = b(options.enable_lto) + if flavor in ('solaris', 'mac', 'linux', 'freebsd'): use_dtrace = not options.without_dtrace # Don't enable by default on linux and freebsd From f46536be23b5b31222fe33ae85bab0f92a28bb47 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Fri, 13 Jul 2018 15:07:23 +0200 Subject: [PATCH 061/116] test: fix timeouts when running worker tests with `--worker` These tests have been added after the original workers PR and time out when run inside a worker by themselves. This is needed for enabling `--worker` tests in our regular CI. Refs: https://github.com/nodejs/build/issues/1318 PR-URL: https://github.com/nodejs/node/pull/21791 Reviewed-By: Jon Moss <me@jonathanmoss.me> Reviewed-By: Refael Ackermann <refack@gmail.com> --- test/parallel/test-worker-exit-code.js | 6 ++++-- test/parallel/test-worker-onmessage.js | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/test/parallel/test-worker-exit-code.js b/test/parallel/test-worker-exit-code.js index bb47e1cece7a62..b621389b49ca6b 100644 --- a/test/parallel/test-worker-exit-code.js +++ b/test/parallel/test-worker-exit-code.js @@ -7,9 +7,11 @@ const common = require('../common'); const assert = require('assert'); const worker = require('worker_threads'); -const { Worker, isMainThread, parentPort } = worker; +const { Worker, parentPort } = worker; -if (isMainThread) { +// Do not use isMainThread so that this test itself can be run inside a Worker. +if (!process.env.HAS_STARTED_WORKER) { + process.env.HAS_STARTED_WORKER = 1; parent(); } else { if (!parentPort) { diff --git a/test/parallel/test-worker-onmessage.js b/test/parallel/test-worker-onmessage.js index 895536c15038be..2ae3d90f852ba9 100644 --- a/test/parallel/test-worker-onmessage.js +++ b/test/parallel/test-worker-onmessage.js @@ -2,9 +2,11 @@ 'use strict'; const common = require('../common'); const assert = require('assert'); -const { Worker, isMainThread, parentPort } = require('worker_threads'); +const { Worker, parentPort } = require('worker_threads'); -if (isMainThread) { +// Do not use isMainThread so that this test itself can be run inside a Worker. +if (!process.env.HAS_STARTED_WORKER) { + process.env.HAS_STARTED_WORKER = 1; const w = new Worker(__filename); w.on('message', common.mustCall((message) => { assert.strictEqual(message, 4); From 712809eb1b29fbfd304c942fe0b4f7acf682dc6c Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Sun, 10 Jun 2018 16:40:13 +0200 Subject: [PATCH 062/116] src: enable more detailed memory tracking This will enable more detailed heap snapshots based on a newer V8 API. This commit itself is not tied to that API and could be backported. PR-URL: https://github.com/nodejs/node/pull/21742 Reviewed-By: James M Snell <jasnell@gmail.com> --- node.gyp | 2 + src/async_wrap.cc | 21 +++- src/async_wrap.h | 1 - src/base_object-inl.h | 10 +- src/base_object.h | 13 ++- src/cares_wrap.cc | 77 +++++++++---- src/connect_wrap.h | 4 +- src/env.cc | 14 +++ src/fs_event_wrap.cc | 5 +- src/inspector_js_api.cc | 6 +- src/js_stream.h | 4 +- src/memory_tracker-inl.h | 107 +++++++++++++++++++ src/memory_tracker.h | 87 +++++++++++++++ src/module_wrap.h | 6 ++ src/node_contextify.cc | 4 + src/node_contextify.h | 1 + src/node_crypto.cc | 2 + src/node_crypto.h | 28 +++++ src/node_crypto_bio.h | 7 +- src/node_file.h | 27 ++++- src/node_http2.cc | 27 +++-- src/node_http2.h | 37 +++++-- src/node_http_parser.cc | 5 +- src/node_i18n.cc | 4 + src/node_messaging.cc | 22 ++-- src/node_messaging.h | 13 ++- src/node_serdes.cc | 10 ++ src/node_stat_watcher.h | 4 +- src/node_trace_events.cc | 5 + src/node_worker.cc | 4 - src/node_worker.h | 9 +- src/node_zlib.cc | 7 +- src/pipe_wrap.h | 4 +- src/process_wrap.cc | 4 +- src/sharedarraybuffer_metadata.cc | 4 + src/signal_wrap.cc | 4 +- src/stream_base.h | 11 +- src/stream_pipe.h | 4 +- src/tcp_wrap.h | 4 +- src/timer_wrap.cc | 4 +- src/tls_wrap.cc | 9 ++ src/tls_wrap.h | 2 +- src/tty_wrap.h | 4 +- src/udp_wrap.cc | 6 +- src/udp_wrap.h | 4 +- test/cctest/test_node_postmortem_metadata.cc | 20 +++- 46 files changed, 566 insertions(+), 91 deletions(-) create mode 100644 src/memory_tracker-inl.h create mode 100644 src/memory_tracker.h diff --git a/node.gyp b/node.gyp index 35a81437afdfcf..ae254278198edf 100644 --- a/node.gyp +++ b/node.gyp @@ -420,6 +420,8 @@ 'src/node_revert.h', 'src/node_i18n.h', 'src/node_worker.h', + 'src/memory_tracker.h', + 'src/memory_tracker-inl.h', 'src/pipe_wrap.h', 'src/tty_wrap.h', 'src/tcp_wrap.h', diff --git a/src/async_wrap.cc b/src/async_wrap.cc index 89e82b9ce364b6..e98dca3c56651b 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -76,14 +76,22 @@ class RetainedAsyncInfo: public RetainedObjectInfo { private: const char* label_; const AsyncWrap* wrap_; - const int length_; + const size_t length_; }; +static int OwnMemory(AsyncWrap* async_wrap) { + MemoryTracker tracker; + tracker.set_track_only_self(true); + tracker.Track(async_wrap); + return tracker.accumulated_size(); +} + + RetainedAsyncInfo::RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap) : label_(provider_names[class_id - NODE_ASYNC_ID_OFFSET]), wrap_(wrap), - length_(wrap->self_size()) { + length_(OwnMemory(wrap)) { } @@ -147,7 +155,9 @@ struct AsyncWrapObject : public AsyncWrap { inline AsyncWrapObject(Environment* env, Local<Object> object, ProviderType type) : AsyncWrap(env, object, type) {} - inline size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } }; @@ -252,7 +262,10 @@ class PromiseWrap : public AsyncWrap { : AsyncWrap(env, object, PROVIDER_PROMISE, -1, silent) { MakeWeak(); } - size_t self_size() const override { return sizeof(*this); } + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } static constexpr int kPromiseField = 1; static constexpr int kIsChainedPromiseField = 2; diff --git a/src/async_wrap.h b/src/async_wrap.h index 82c57910925be9..c1d90f8fad0541 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -174,7 +174,6 @@ class AsyncWrap : public BaseObject { int argc, v8::Local<v8::Value>* argv); - virtual size_t self_size() const = 0; virtual std::string diagnostic_name() const; static void WeakCallback(const v8::WeakCallbackInfo<DestroyParam> &info); diff --git a/src/base_object-inl.h b/src/base_object-inl.h index 06a29223973c5d..d067a807cb8e14 100644 --- a/src/base_object-inl.h +++ b/src/base_object-inl.h @@ -61,11 +61,11 @@ Persistent<v8::Object>& BaseObject::persistent() { } -v8::Local<v8::Object> BaseObject::object() { +v8::Local<v8::Object> BaseObject::object() const { return PersistentToLocal(env_->isolate(), persistent_handle_); } -v8::Local<v8::Object> BaseObject::object(v8::Isolate* isolate) { +v8::Local<v8::Object> BaseObject::object(v8::Isolate* isolate) const { v8::Local<v8::Object> handle = object(); #ifdef DEBUG CHECK_EQ(handle->CreationContext()->GetIsolate(), isolate); @@ -91,12 +91,6 @@ T* BaseObject::FromJSObject(v8::Local<v8::Object> object) { } -void BaseObject::DeleteMe(void* data) { - BaseObject* self = static_cast<BaseObject*>(data); - delete self; -} - - void BaseObject::MakeWeak() { persistent_handle_.SetWeak( this, diff --git a/src/base_object.h b/src/base_object.h index 38291d598feb1c..e0f3f27950e7d0 100644 --- a/src/base_object.h +++ b/src/base_object.h @@ -25,6 +25,7 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS #include "node_persistent.h" +#include "memory_tracker-inl.h" #include "v8.h" #include <type_traits> // std::remove_reference @@ -32,7 +33,7 @@ namespace node { class Environment; -class BaseObject { +class BaseObject : public MemoryRetainer { public: // Associates this object with `object`. It uses the 0th internal field for // that, and in particular aborts if there is no such field. @@ -41,11 +42,11 @@ class BaseObject { // Returns the wrapped object. Returns an empty handle when // persistent.IsEmpty() is true. - inline v8::Local<v8::Object> object(); + inline v8::Local<v8::Object> object() const; // Same as the above, except it additionally verifies that this object // is associated with the passed Isolate in debug mode. - inline v8::Local<v8::Object> object(v8::Isolate* isolate); + inline v8::Local<v8::Object> object(v8::Isolate* isolate) const; inline Persistent<v8::Object>& persistent(); @@ -75,7 +76,9 @@ class BaseObject { private: BaseObject(); - static inline void DeleteMe(void* data); + v8::Local<v8::Object> WrappedObject() const override; + bool IsRootNode() const override; + static void DeleteMe(void* data); // persistent_handle_ needs to be at a fixed offset from the start of the // class because it is used by src/node_postmortem_metadata.cc to calculate @@ -83,6 +86,8 @@ class BaseObject { // position of members in memory are predictable. For more information please // refer to `doc/guides/node-postmortem-support.md` friend int GenDebugSymbols(); + friend class Environment; + Persistent<v8::Object> persistent_handle_; Environment* env_; }; diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index 05ef2b7e12090e..af564df8053955 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -121,10 +121,12 @@ inline const char* ToErrorCodeString(int status) { class ChannelWrap; -struct node_ares_task { +struct node_ares_task : public MemoryRetainer { ChannelWrap* channel; ares_socket_t sock; uv_poll_t poll_watcher; + + void MemoryInfo(MemoryTracker* tracker) const override; }; struct TaskHash { @@ -167,7 +169,12 @@ class ChannelWrap : public AsyncWrap { inline int active_query_count() { return active_query_count_; } inline node_ares_task_list* task_list() { return &task_list_; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + if (timer_handle_ != nullptr) + tracker->TrackFieldWithSize("timer handle", sizeof(*timer_handle_)); + tracker->TrackField("task list", task_list_); + } static void AresTimeout(uv_timer_t* handle); @@ -181,6 +188,11 @@ class ChannelWrap : public AsyncWrap { node_ares_task_list task_list_; }; +void node_ares_task::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackThis(this); + tracker->TrackField("channel", channel); +} + ChannelWrap::ChannelWrap(Environment* env, Local<Object> object) : AsyncWrap(env, object, PROVIDER_DNSCHANNEL), @@ -209,7 +221,10 @@ class GetAddrInfoReqWrap : public ReqWrap<uv_getaddrinfo_t> { Local<Object> req_wrap_obj, bool verbatim); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + bool verbatim() const { return verbatim_; } private: @@ -228,7 +243,9 @@ class GetNameInfoReqWrap : public ReqWrap<uv_getnameinfo_t> { public: GetNameInfoReqWrap(Environment* env, Local<Object> req_wrap_obj); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } }; GetNameInfoReqWrap::GetNameInfoReqWrap(Environment* env, @@ -270,13 +287,13 @@ void ares_poll_cb(uv_poll_t* watcher, int status, int events) { void ares_poll_close_cb(uv_poll_t* watcher) { node_ares_task* task = ContainerOf(&node_ares_task::poll_watcher, watcher); - free(task); + delete task; } /* Allocates and returns a new node_ares_task */ node_ares_task* ares_task_create(ChannelWrap* channel, ares_socket_t sock) { - auto task = node::UncheckedMalloc<node_ares_task>(1); + auto task = new node_ares_task(); if (task == nullptr) { /* Out of memory. */ @@ -1172,7 +1189,9 @@ class QueryAnyWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1349,7 +1368,9 @@ class QueryAWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1393,7 +1414,9 @@ class QueryAaaaWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1437,7 +1460,9 @@ class QueryCnameWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1468,7 +1493,9 @@ class QueryMxWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1499,7 +1526,9 @@ class QueryNsWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1530,7 +1559,9 @@ class QueryTxtWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1560,7 +1591,9 @@ class QuerySrvWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1589,7 +1622,9 @@ class QueryPtrWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1620,7 +1655,9 @@ class QueryNaptrWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1650,7 +1687,9 @@ class QuerySoaWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(unsigned char* buf, int len) override { @@ -1729,7 +1768,9 @@ class GetHostByAddrWrap: public QueryWrap { return 0; } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: void Parse(struct hostent* host) override { diff --git a/src/connect_wrap.h b/src/connect_wrap.h index 80eae7f9bb8290..587e4c6b0593e5 100644 --- a/src/connect_wrap.h +++ b/src/connect_wrap.h @@ -16,7 +16,9 @@ class ConnectWrap : public ReqWrap<uv_connect_t> { v8::Local<v8::Object> req_wrap_obj, AsyncWrap::ProviderType provider); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } }; } // namespace node diff --git a/src/env.cc b/src/env.cc index 6c906e2fb19c56..006173eaaeb847 100644 --- a/src/env.cc +++ b/src/env.cc @@ -653,4 +653,18 @@ void Environment::stop_sub_worker_contexts() { } } +// Not really any better place than env.cc at this moment. +void BaseObject::DeleteMe(void* data) { + BaseObject* self = static_cast<BaseObject*>(data); + delete self; +} + +Local<Object> BaseObject::WrappedObject() const { + return object(); +} + +bool BaseObject::IsRootNode() const { + return !persistent_handle_.IsWeak(); +} + } // namespace node diff --git a/src/fs_event_wrap.cc b/src/fs_event_wrap.cc index 164614ae81145f..7587ace8e3ce70 100644 --- a/src/fs_event_wrap.cc +++ b/src/fs_event_wrap.cc @@ -56,7 +56,10 @@ class FSEventWrap: public HandleWrap { static void New(const FunctionCallbackInfo<Value>& args); static void Start(const FunctionCallbackInfo<Value>& args); static void GetInitialized(const FunctionCallbackInfo<Value>& args); - size_t self_size() const override { return sizeof(*this); } + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: static const encoding kDefaultEncoding = UTF8; diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 104cd93b3cd9e0..13e91f283c912a 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -103,7 +103,11 @@ class JSBindingsConnection : public AsyncWrap { } } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("callback", callback_); + tracker->TrackFieldWithSize("session", sizeof(*session_)); + } private: std::unique_ptr<InspectorSession> session_; diff --git a/src/js_stream.h b/src/js_stream.h index b47a91a653ba7e..f3406ae83ee560 100644 --- a/src/js_stream.h +++ b/src/js_stream.h @@ -27,7 +27,9 @@ class JSStream : public AsyncWrap, public StreamBase { size_t count, uv_stream_t* send_handle) override; - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } protected: JSStream(Environment* env, v8::Local<v8::Object> obj); diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h new file mode 100644 index 00000000000000..758223492f6e71 --- /dev/null +++ b/src/memory_tracker-inl.h @@ -0,0 +1,107 @@ +#ifndef SRC_MEMORY_TRACKER_INL_H_ +#define SRC_MEMORY_TRACKER_INL_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include "memory_tracker.h" + +namespace node { + +template <typename T> +void MemoryTracker::TrackThis(const T* obj) { + accumulated_size_ += sizeof(T); +} + +void MemoryTracker::TrackFieldWithSize(const char* name, size_t size) { + accumulated_size_ += size; +} + +void MemoryTracker::TrackField(const char* name, const MemoryRetainer& value) { + TrackField(name, &value); +} + +void MemoryTracker::TrackField(const char* name, const MemoryRetainer* value) { + if (track_only_self_ || value == nullptr || seen_.count(value) > 0) return; + seen_.insert(value); + Track(value); +} + +template <typename T> +void MemoryTracker::TrackField(const char* name, + const std::unique_ptr<T>& value) { + TrackField(name, value.get()); +} + +template <typename T, typename Iterator> +void MemoryTracker::TrackField(const char* name, const T& value) { + if (value.begin() == value.end()) return; + size_t index = 0; + for (Iterator it = value.begin(); it != value.end(); ++it) + TrackField(std::to_string(index++).c_str(), *it); +} + +template <typename T> +void MemoryTracker::TrackField(const char* name, const std::queue<T>& value) { + struct ContainerGetter : public std::queue<T> { + static const typename std::queue<T>::container_type& Get( + const std::queue<T>& value) { + return value.*&ContainerGetter::c; + } + }; + + const auto& container = ContainerGetter::Get(value); + TrackField(name, container); +} + +template <typename T, typename test_for_number, typename dummy> +void MemoryTracker::TrackField(const char* name, const T& value) { + // For numbers, creating new nodes is not worth the overhead. + TrackFieldWithSize(name, sizeof(T)); +} + +template <typename T, typename U> +void MemoryTracker::TrackField(const char* name, const std::pair<T, U>& value) { + TrackField("first", value.first); + TrackField("second", value.second); +} + +template <typename T> +void MemoryTracker::TrackField(const char* name, + const std::basic_string<T>& value) { + TrackFieldWithSize(name, value.size() * sizeof(T)); +} + +template <typename T, typename Traits> +void MemoryTracker::TrackField(const char* name, + const v8::Persistent<T, Traits>& value) { +} + +template <typename T> +void MemoryTracker::TrackField(const char* name, const v8::Local<T>& value) { +} + +template <typename T> +void MemoryTracker::TrackField(const char* name, + const MallocedBuffer<T>& value) { + TrackFieldWithSize(name, value.size); +} + +void MemoryTracker::TrackField(const char* name, const uv_buf_t& value) { + TrackFieldWithSize(name, value.len); +} + +template <class NativeT, class V8T> +void MemoryTracker::TrackField(const char* name, + const AliasedBuffer<NativeT, V8T>& value) { + TrackField(name, value.GetJSArray()); +} + +void MemoryTracker::Track(const MemoryRetainer* value) { + value->MemoryInfo(this); +} + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_MEMORY_TRACKER_INL_H_ diff --git a/src/memory_tracker.h b/src/memory_tracker.h new file mode 100644 index 00000000000000..18822651f67873 --- /dev/null +++ b/src/memory_tracker.h @@ -0,0 +1,87 @@ +#ifndef SRC_MEMORY_TRACKER_H_ +#define SRC_MEMORY_TRACKER_H_ + +#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#include <unordered_set> +#include <queue> +#include <limits> +#include <uv.h> +#include <aliased_buffer.h> + +namespace node { + +class MemoryTracker; + +namespace crypto { +class NodeBIO; +} + +class MemoryRetainer { + public: + virtual ~MemoryRetainer() {} + + // Subclasses should implement this to provide information for heap snapshots. + virtual void MemoryInfo(MemoryTracker* tracker) const = 0; + + virtual v8::Local<v8::Object> WrappedObject() const { + return v8::Local<v8::Object>(); + } + + virtual bool IsRootNode() const { return false; } +}; + +class MemoryTracker { + public: + template <typename T> + inline void TrackThis(const T* obj); + + inline void TrackFieldWithSize(const char* name, size_t size); + + inline void TrackField(const char* name, const MemoryRetainer& value); + inline void TrackField(const char* name, const MemoryRetainer* value); + template <typename T> + inline void TrackField(const char* name, const std::unique_ptr<T>& value); + template <typename T, typename Iterator = typename T::const_iterator> + inline void TrackField(const char* name, const T& value); + template <typename T> + inline void TrackField(const char* name, const std::queue<T>& value); + template <typename T> + inline void TrackField(const char* name, const std::basic_string<T>& value); + template <typename T, typename test_for_number = + typename std::enable_if< + std::numeric_limits<T>::is_specialized, bool>::type, + typename dummy = bool> + inline void TrackField(const char* name, const T& value); + template <typename T, typename U> + inline void TrackField(const char* name, const std::pair<T, U>& value); + template <typename T, typename Traits> + inline void TrackField(const char* name, + const v8::Persistent<T, Traits>& value); + template <typename T> + inline void TrackField(const char* name, const v8::Local<T>& value); + template <typename T> + inline void TrackField(const char* name, const MallocedBuffer<T>& value); + inline void TrackField(const char* name, const uv_buf_t& value); + template <class NativeT, class V8T> + inline void TrackField(const char* name, + const AliasedBuffer<NativeT, V8T>& value); + + inline void Track(const MemoryRetainer* value); + inline size_t accumulated_size() const { return accumulated_size_; } + + inline void set_track_only_self(bool value) { track_only_self_ = value; } + + inline MemoryTracker() {} + + private: + bool track_only_self_ = false; + size_t accumulated_size_ = 0; + std::unordered_set<const MemoryRetainer*> seen_; +}; + +} // namespace node + +#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS + +#endif // SRC_MEMORY_TRACKER_H_ diff --git a/src/module_wrap.h b/src/module_wrap.h index 3969e2a37878fc..2d6f5c49d88988 100644 --- a/src/module_wrap.h +++ b/src/module_wrap.h @@ -33,6 +33,12 @@ class ModuleWrap : public BaseObject { v8::Local<v8::Module> module, v8::Local<v8::Object> meta); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("url", url_); + tracker->TrackField("resolve_cache", resolve_cache_); + } + private: ModuleWrap(Environment* env, v8::Local<v8::Object> object, diff --git a/src/node_contextify.cc b/src/node_contextify.cc index c8155c33ee3d48..b6ef5a37b2c47d 100644 --- a/src/node_contextify.cc +++ b/src/node_contextify.cc @@ -587,6 +587,10 @@ class ContextifyScript : public BaseObject { private: Persistent<UnboundScript> script_; + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + public: static void Init(Environment* env, Local<Object> target) { HandleScope scope(env->isolate()); diff --git a/src/node_contextify.h b/src/node_contextify.h index 2ebfef7f27374d..3d94fbc5c47947 100644 --- a/src/node_contextify.h +++ b/src/node_contextify.h @@ -49,6 +49,7 @@ class ContextifyContext { context()->GetEmbedderData(ContextEmbedderIndex::kSandboxObject)); } + template <typename T> static ContextifyContext* Get(const v8::PropertyCallbackInfo<T>& args); diff --git a/src/node_crypto.cc b/src/node_crypto.cc index ba65876e486a93..2c3f983d39e9e5 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -4567,6 +4567,8 @@ bool ECDH::IsKeyPairValid() { } +// TODO(addaleax): If there is an `AsyncWrap`, it currently has no access to +// this object. This makes proper reporting of memory usage impossible. struct CryptoJob : public ThreadPoolWork { Environment* const env; std::unique_ptr<AsyncWrap> async_wrap; diff --git a/src/node_crypto.h b/src/node_crypto.h index 4587a96e725f86..7df2660c779760 100644 --- a/src/node_crypto.h +++ b/src/node_crypto.h @@ -105,6 +105,10 @@ class SecureContext : public BaseObject { static void Initialize(Environment* env, v8::Local<v8::Object> target); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + SSLCtxPointer ctx_; X509Pointer cert_; X509Pointer issuer_; @@ -337,6 +341,10 @@ class CipherBase : public BaseObject { public: static void Initialize(Environment* env, v8::Local<v8::Object> target); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + protected: enum CipherKind { kCipher, @@ -407,6 +415,10 @@ class Hmac : public BaseObject { public: static void Initialize(Environment* env, v8::Local<v8::Object> target); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + protected: void HmacInit(const char* hash_type, const char* key, int key_len); bool HmacUpdate(const char* data, int len); @@ -430,6 +442,10 @@ class Hash : public BaseObject { public: static void Initialize(Environment* env, v8::Local<v8::Object> target); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + bool HashInit(const char* hash_type); bool HashUpdate(const char* data, int len); @@ -469,6 +485,10 @@ class SignBase : public BaseObject { Error Init(const char* sign_type); Error Update(const char* data, int len); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + protected: void CheckThrow(Error error); @@ -581,6 +601,10 @@ class DiffieHellman : public BaseObject { MakeWeak(); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + private: static void GetField(const v8::FunctionCallbackInfo<v8::Value>& args, const BIGNUM* (*get_field)(const DH*), @@ -606,6 +630,10 @@ class ECDH : public BaseObject { char* data, size_t len); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + protected: ECDH(Environment* env, v8::Local<v8::Object> wrap, ECKeyPointer&& key) : BaseObject(env, wrap), diff --git a/src/node_crypto_bio.h b/src/node_crypto_bio.h index 380a3a6b4c64f5..dea010fa0158b4 100644 --- a/src/node_crypto_bio.h +++ b/src/node_crypto_bio.h @@ -32,7 +32,7 @@ namespace node { namespace crypto { -class NodeBIO { +class NodeBIO : public MemoryRetainer { public: NodeBIO() : env_(nullptr), initial_(kInitialBufferLength), @@ -110,6 +110,11 @@ class NodeBIO { static NodeBIO* FromBIO(BIO* bio); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackFieldWithSize("buffer", length_); + } + private: static int New(BIO* bio); static int Free(BIO* bio); diff --git a/src/node_file.h b/src/node_file.h index a14a1b0f85e108..6b45dc881750a7 100644 --- a/src/node_file.h +++ b/src/node_file.h @@ -66,7 +66,6 @@ class FSReqBase : public ReqWrap<uv_fs_t> { const char* data() const { return has_data_ ? *buffer_ : nullptr; } enum encoding encoding() const { return encoding_; } - size_t self_size() const override { return sizeof(*this); } bool use_bigint() const { return use_bigint_; } private: @@ -92,6 +91,10 @@ class FSReqWrap : public FSReqBase { void ResolveStat(const uv_stat_t* stat) override; void SetReturnValue(const FunctionCallbackInfo<Value>& args) override; + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + private: DISALLOW_COPY_AND_ASSIGN(FSReqWrap); }; @@ -150,6 +153,11 @@ class FSReqPromise : public FSReqBase { args.GetReturnValue().Set(resolver->GetPromise()); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("stats_field_array", stats_field_array_); + } + private: bool finished_ = false; AliasedBuffer<NativeT, V8T> stats_field_array_; @@ -184,7 +192,10 @@ class FileHandleReadWrap : public ReqWrap<uv_fs_t> { return static_cast<FileHandleReadWrap*>(ReqWrap::from_req(req)); } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("buffer", buffer_); + } private: FileHandle* file_handle_; @@ -205,7 +216,6 @@ class FileHandle : public AsyncWrap, public StreamBase { static void New(const v8::FunctionCallbackInfo<v8::Value>& args); int fd() const { return fd_; } - size_t self_size() const override { return sizeof(*this); } // Will asynchronously close the FD and return a Promise that will // be resolved once closing is complete. @@ -233,6 +243,11 @@ class FileHandle : public AsyncWrap, public StreamBase { return UV_ENOSYS; // Not implemented (yet). } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("current_read", current_read_); + } + private: // Synchronous close that emits a warning void Close(); @@ -259,7 +274,11 @@ class FileHandle : public AsyncWrap, public StreamBase { FileHandle* file_handle(); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("promise", promise_); + tracker->TrackField("ref", ref_); + } void Resolve(); diff --git a/src/node_http2.cc b/src/node_http2.cc index e6aeb80a91add9..dfe0e4e7ae3e9a 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -737,7 +737,7 @@ inline void Http2Session::AddStream(Http2Stream* stream) { size_t size = streams_.size(); if (size > statistics_.max_concurrent_streams) statistics_.max_concurrent_streams = size; - IncrementCurrentSessionMemory(stream->self_size()); + IncrementCurrentSessionMemory(sizeof(*stream)); } @@ -745,7 +745,7 @@ inline void Http2Session::RemoveStream(Http2Stream* stream) { if (streams_.empty() || stream == nullptr) return; // Nothing to remove, item was never added? streams_.erase(stream->id()); - DecrementCurrentSessionMemory(stream->self_size()); + DecrementCurrentSessionMemory(sizeof(*stream)); } // Used as one of the Padding Strategy functions. Will attempt to ensure @@ -2694,7 +2694,7 @@ Http2Session::Http2Ping* Http2Session::PopPing() { if (!outstanding_pings_.empty()) { ping = outstanding_pings_.front(); outstanding_pings_.pop(); - DecrementCurrentSessionMemory(ping->self_size()); + DecrementCurrentSessionMemory(sizeof(*ping)); } return ping; } @@ -2703,7 +2703,7 @@ bool Http2Session::AddPing(Http2Session::Http2Ping* ping) { if (outstanding_pings_.size() == max_outstanding_pings_) return false; outstanding_pings_.push(ping); - IncrementCurrentSessionMemory(ping->self_size()); + IncrementCurrentSessionMemory(sizeof(*ping)); return true; } @@ -2712,7 +2712,7 @@ Http2Session::Http2Settings* Http2Session::PopSettings() { if (!outstanding_settings_.empty()) { settings = outstanding_settings_.front(); outstanding_settings_.pop(); - DecrementCurrentSessionMemory(settings->self_size()); + DecrementCurrentSessionMemory(sizeof(*settings)); } return settings; } @@ -2721,7 +2721,7 @@ bool Http2Session::AddSettings(Http2Session::Http2Settings* settings) { if (outstanding_settings_.size() == max_outstanding_settings_) return false; outstanding_settings_.push(settings); - IncrementCurrentSessionMemory(settings->self_size()); + IncrementCurrentSessionMemory(sizeof(*settings)); return true; } @@ -2766,6 +2766,21 @@ void Http2Session::Http2Ping::Done(bool ack, const uint8_t* payload) { } +void nghttp2_stream_write::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackThis(this); + if (req_wrap != nullptr) + tracker->TrackField("req_wrap", req_wrap->GetAsyncWrap()); + tracker->TrackField("buf", buf); +} + + +void nghttp2_header::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackThis(this); + tracker->TrackFieldWithSize("name", nghttp2_rcbuf_get_buf(name).len); + tracker->TrackFieldWithSize("value", nghttp2_rcbuf_get_buf(value).len); +} + + // Set up the process.binding('http2') binding. void Initialize(Local<Object> target, Local<Value> unused, diff --git a/src/node_http2.h b/src/node_http2.h index d90c3aed66b0a0..cbed2a267d171e 100644 --- a/src/node_http2.h +++ b/src/node_http2.h @@ -83,19 +83,23 @@ enum nghttp2_stream_options { STREAM_OPTION_GET_TRAILERS = 0x2, }; -struct nghttp2_stream_write { +struct nghttp2_stream_write : public MemoryRetainer { WriteWrap* req_wrap = nullptr; uv_buf_t buf; inline explicit nghttp2_stream_write(uv_buf_t buf_) : buf(buf_) {} inline nghttp2_stream_write(WriteWrap* req, uv_buf_t buf_) : req_wrap(req), buf(buf_) {} + + void MemoryInfo(MemoryTracker* tracker) const override; }; -struct nghttp2_header { +struct nghttp2_header : public MemoryRetainer { nghttp2_rcbuf* name = nullptr; nghttp2_rcbuf* value = nullptr; uint8_t flags = 0; + + void MemoryInfo(MemoryTracker* tracker) const override; }; @@ -617,7 +621,12 @@ class Http2Stream : public AsyncWrap, int DoWrite(WriteWrap* w, uv_buf_t* bufs, size_t count, uv_stream_t* send_handle) override; - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("current_headers", current_headers_); + tracker->TrackField("queue", queue_); + } + std::string diagnostic_name() const override; // JavaScript API @@ -793,7 +802,17 @@ class Http2Session : public AsyncWrap, public StreamListener { // Write data to the session ssize_t Write(const uv_buf_t* bufs, size_t nbufs); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("streams", streams_); + tracker->TrackField("outstanding_pings", outstanding_pings_); + tracker->TrackField("outstanding_settings", outstanding_settings_); + tracker->TrackField("outgoing_buffers", outgoing_buffers_); + tracker->TrackFieldWithSize("outgoing_storage", outgoing_storage_.size()); + tracker->TrackFieldWithSize("pending_rst_streams", + pending_rst_streams_.size() * sizeof(int32_t)); + } + std::string diagnostic_name() const override; // Schedule an RstStream for after the current write finishes. @@ -1109,7 +1128,10 @@ class Http2Session::Http2Ping : public AsyncWrap { public: explicit Http2Ping(Http2Session* session); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("session", session_); + } void Send(uint8_t* payload); void Done(bool ack, const uint8_t* payload = nullptr); @@ -1129,7 +1151,10 @@ class Http2Session::Http2Settings : public AsyncWrap { explicit Http2Settings(Environment* env); explicit Http2Settings(Http2Session* session); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("session", session_); + } void Send(); void Done(bool ack); diff --git a/src/node_http_parser.cc b/src/node_http_parser.cc index 7d96466c3933fc..e8fc1f22e4938a 100644 --- a/src/node_http_parser.cc +++ b/src/node_http_parser.cc @@ -155,8 +155,9 @@ class Parser : public AsyncWrap, public StreamListener { } - size_t self_size() const override { - return sizeof(*this); + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("current_buffer", current_buffer_); } diff --git a/src/node_i18n.cc b/src/node_i18n.cc index 4ace513810f1b8..288ad77f619188 100644 --- a/src/node_i18n.cc +++ b/src/node_i18n.cc @@ -250,6 +250,10 @@ class ConverterObject : public BaseObject, Converter { } } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + protected: ConverterObject(Environment* env, v8::Local<v8::Object> wrap, diff --git a/src/node_messaging.cc b/src/node_messaging.cc index 712add06d3e2bc..c28d0d4ea66560 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -325,6 +325,14 @@ Maybe<bool> Message::Serialize(Environment* env, return Just(true); } +void Message::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackThis(this); + tracker->TrackField("array_buffer_contents", array_buffer_contents_); + tracker->TrackFieldWithSize("shared_array_buffers", + shared_array_buffers_.size() * sizeof(shared_array_buffers_[0])); + tracker->TrackField("message_ports", message_ports_); +} + MessagePortData::MessagePortData(MessagePort* owner) : owner_(owner) { } MessagePortData::~MessagePortData() { @@ -332,6 +340,12 @@ MessagePortData::~MessagePortData() { Disentangle(); } +void MessagePortData::MemoryInfo(MemoryTracker* tracker) const { + Mutex::ScopedLock lock(mutex_); + tracker->TrackThis(this); + tracker->TrackField("incoming_messages", incoming_messages_); +} + void MessagePortData::AddToIncomingQueue(Message&& message) { // This function will be called by other threads. Mutex::ScopedLock lock(mutex_); @@ -688,14 +702,6 @@ void MessagePort::Drain(const FunctionCallbackInfo<Value>& args) { port->OnMessage(); } -size_t MessagePort::self_size() const { - Mutex::ScopedLock lock(data_->mutex_); - size_t sz = sizeof(*this) + sizeof(*data_); - for (const Message& msg : data_->incoming_messages_) - sz += sizeof(msg) + msg.main_message_buf_.size; - return sz; -} - void MessagePort::Entangle(MessagePort* a, MessagePort* b) { Entangle(a, b->data_.get()); } diff --git a/src/node_messaging.h b/src/node_messaging.h index 62ae633b9e0b8d..da10300aedbd42 100644 --- a/src/node_messaging.h +++ b/src/node_messaging.h @@ -15,7 +15,7 @@ class MessagePortData; class MessagePort; // Represents a single communication message. -class Message { +class Message : public MemoryRetainer { public: explicit Message(MallocedBuffer<char>&& payload = MallocedBuffer<char>()); @@ -55,6 +55,8 @@ class Message { return message_ports_; } + void MemoryInfo(MemoryTracker* tracker) const override; + private: MallocedBuffer<char> main_message_buf_; std::vector<MallocedBuffer<char>> array_buffer_contents_; @@ -66,7 +68,7 @@ class Message { // This contains all data for a `MessagePort` instance that is not tied to // a specific Environment/Isolate/event loop, for easier transfer between those. -class MessagePortData { +class MessagePortData : public MemoryRetainer { public: explicit MessagePortData(MessagePort* owner); ~MessagePortData(); @@ -94,6 +96,8 @@ class MessagePortData { // which can happen on either side of a worker. void Disentangle(); + void MemoryInfo(MemoryTracker* tracker) const override; + private: // After disentangling this message port, the owner handle (if any) // is asynchronously triggered, so that it can close down naturally. @@ -178,7 +182,10 @@ class MessagePort : public HandleWrap { // NULL pointer to the C++ MessagePort object is also detached. inline bool IsDetached() const; - size_t self_size() const override; + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("data", data_); + } private: void OnClose() override; diff --git a/src/node_serdes.cc b/src/node_serdes.cc index 520b350199245a..4b2cc60b3f7584 100644 --- a/src/node_serdes.cc +++ b/src/node_serdes.cc @@ -52,6 +52,11 @@ class SerializerContext : public BaseObject, static void WriteUint64(const FunctionCallbackInfo<Value>& args); static void WriteDouble(const FunctionCallbackInfo<Value>& args); static void WriteRawBytes(const FunctionCallbackInfo<Value>& args); + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + private: ValueSerializer serializer_; }; @@ -76,6 +81,11 @@ class DeserializerContext : public BaseObject, static void ReadUint64(const FunctionCallbackInfo<Value>& args); static void ReadDouble(const FunctionCallbackInfo<Value>& args); static void ReadRawBytes(const FunctionCallbackInfo<Value>& args); + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + private: const uint8_t* data_; const size_t length_; diff --git a/src/node_stat_watcher.h b/src/node_stat_watcher.h index 45150de785f9d1..baf6bdc14ee317 100644 --- a/src/node_stat_watcher.h +++ b/src/node_stat_watcher.h @@ -44,7 +44,9 @@ class StatWatcher : public HandleWrap { static void New(const v8::FunctionCallbackInfo<v8::Value>& args); static void Start(const v8::FunctionCallbackInfo<v8::Value>& args); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: static void Callback(uv_fs_poll_t* handle, diff --git a/src/node_trace_events.cc b/src/node_trace_events.cc index 985c706dc4a2f1..0904311dad865b 100644 --- a/src/node_trace_events.cc +++ b/src/node_trace_events.cc @@ -27,6 +27,11 @@ class NodeCategorySet : public BaseObject { const std::set<std::string>& GetCategories() { return categories_; } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackField("categories", categories_); + } + private: NodeCategorySet(Environment* env, Local<Object> wrap, diff --git a/src/node_worker.cc b/src/node_worker.cc index 6f325668b86921..3768d80a9c58d4 100644 --- a/src/node_worker.cc +++ b/src/node_worker.cc @@ -403,10 +403,6 @@ void Worker::Exit(int code) { } } -size_t Worker::self_size() const { - return sizeof(*this); -} - namespace { // Return the MessagePort that is global for this Environment and communicates diff --git a/src/node_worker.h b/src/node_worker.h index d802b5cfefdf77..bd737d4800fd79 100644 --- a/src/node_worker.h +++ b/src/node_worker.h @@ -25,7 +25,14 @@ class Worker : public AsyncWrap { // Wait for the worker thread to stop (in a blocking manner). void JoinThread(); - size_t self_size() const override; + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackFieldWithSize("isolate_data", sizeof(IsolateData)); + tracker->TrackFieldWithSize("env", sizeof(Environment)); + tracker->TrackFieldWithSize("thread_exit_async", sizeof(uv_async_t)); + tracker->TrackField("parent_port", parent_port_); + } + bool is_stopped() const; static void New(const v8::FunctionCallbackInfo<v8::Value>& args); diff --git a/src/node_zlib.cc b/src/node_zlib.cc index 169816d16f48b6..031666e19ad29c 100644 --- a/src/node_zlib.cc +++ b/src/node_zlib.cc @@ -646,7 +646,12 @@ class ZCtx : public AsyncWrap, public ThreadPoolWork { } } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackFieldWithSize("dictionary", dictionary_len_); + tracker->TrackFieldWithSize("zlib memory", + zlib_memory_ + unreported_allocations_); + } private: void Ref() { diff --git a/src/pipe_wrap.h b/src/pipe_wrap.h index d6e45c28ff7822..9ed4f153ae5c4c 100644 --- a/src/pipe_wrap.h +++ b/src/pipe_wrap.h @@ -45,7 +45,9 @@ class PipeWrap : public ConnectionWrap<PipeWrap, uv_pipe_t> { v8::Local<v8::Value> unused, v8::Local<v8::Context> context); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: PipeWrap(Environment* env, diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 54345b231bccce..3219b1a40b9c16 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -71,7 +71,9 @@ class ProcessWrap : public HandleWrap { target->Set(processString, constructor->GetFunction()); } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: static void New(const FunctionCallbackInfo<Value>& args) { diff --git a/src/sharedarraybuffer_metadata.cc b/src/sharedarraybuffer_metadata.cc index 86476a9f12c38b..95fed87c8d4ea0 100644 --- a/src/sharedarraybuffer_metadata.cc +++ b/src/sharedarraybuffer_metadata.cc @@ -47,6 +47,10 @@ class SABLifetimePartner : public BaseObject { MakeWeak(); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + SharedArrayBufferMetadataReference reference; }; diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc index 5117d3ab1d1988..346f442f61df0f 100644 --- a/src/signal_wrap.cc +++ b/src/signal_wrap.cc @@ -62,7 +62,9 @@ class SignalWrap : public HandleWrap { target->Set(signalString, constructor->GetFunction()); } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: static void New(const FunctionCallbackInfo<Value>& args) { diff --git a/src/stream_base.h b/src/stream_base.h index 405780619856ba..bbb20e52e1a8de 100644 --- a/src/stream_base.h +++ b/src/stream_base.h @@ -346,7 +346,10 @@ class SimpleShutdownWrap : public ShutdownWrap, public OtherBase { v8::Local<v8::Object> req_wrap_obj); AsyncWrap* GetAsyncWrap() override { return this; } - size_t self_size() const override { return sizeof(*this); } + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } }; template <typename OtherBase> @@ -356,7 +359,11 @@ class SimpleWriteWrap : public WriteWrap, public OtherBase { v8::Local<v8::Object> req_wrap_obj); AsyncWrap* GetAsyncWrap() override { return this; } - size_t self_size() const override { return sizeof(*this) + StorageSize(); } + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + tracker->TrackFieldWithSize("storage", StorageSize()); + } }; } // namespace node diff --git a/src/stream_pipe.h b/src/stream_pipe.h index 98d6dae11be841..b72a60941b610a 100644 --- a/src/stream_pipe.h +++ b/src/stream_pipe.h @@ -18,7 +18,9 @@ class StreamPipe : public AsyncWrap { static void Start(const v8::FunctionCallbackInfo<v8::Value>& args); static void Unpipe(const v8::FunctionCallbackInfo<v8::Value>& args); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: StreamBase* source(); diff --git a/src/tcp_wrap.h b/src/tcp_wrap.h index 2ab50f1fdcdfab..d6ca9306099e37 100644 --- a/src/tcp_wrap.h +++ b/src/tcp_wrap.h @@ -44,7 +44,9 @@ class TCPWrap : public ConnectionWrap<TCPWrap, uv_tcp_t> { v8::Local<v8::Value> unused, v8::Local<v8::Context> context); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: typedef uv_tcp_t HandleType; diff --git a/src/timer_wrap.cc b/src/timer_wrap.cc index 50541acc9b8a0d..9bce195203d686 100644 --- a/src/timer_wrap.cc +++ b/src/timer_wrap.cc @@ -72,7 +72,9 @@ class TimerWrap : public HandleWrap { ->GetFunction(env->context()).ToLocalChecked()).FromJust(); } - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: static void SetupTimers(const FunctionCallbackInfo<Value>& args) { diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index e731c0c130216b..0d0791b710c860 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -864,6 +864,15 @@ void TLSWrap::GetWriteQueueSize(const FunctionCallbackInfo<Value>& info) { } +void TLSWrap::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackThis(this); + tracker->TrackField("error", error_); + tracker->TrackField("pending_cleartext_input", pending_cleartext_input_); + tracker->TrackField("enc_in", crypto::NodeBIO::FromBIO(enc_in_)); + tracker->TrackField("enc_out", crypto::NodeBIO::FromBIO(enc_out_)); +} + + void TLSWrap::Initialize(Local<Object> target, Local<Value> unused, Local<Context> context) { diff --git a/src/tls_wrap.h b/src/tls_wrap.h index 1603d8919a472c..b45e379ca3f61c 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -76,7 +76,7 @@ class TLSWrap : public AsyncWrap, void NewSessionDoneCb(); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override; protected: inline StreamBase* underlying_stream() { diff --git a/src/tty_wrap.h b/src/tty_wrap.h index 91b07a570e9b50..cca5650ddb3964 100644 --- a/src/tty_wrap.h +++ b/src/tty_wrap.h @@ -38,7 +38,9 @@ class TTYWrap : public LibuvStreamWrap { uv_tty_t* UVHandle(); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: TTYWrap(Environment* env, diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 1d1ded449bd221..49f66914e2aa81 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -55,7 +55,11 @@ class SendWrap : public ReqWrap<uv_udp_send_t> { SendWrap(Environment* env, Local<Object> req_wrap_obj, bool have_callback); inline bool have_callback() const; size_t msg_size; - size_t self_size() const override { return sizeof(*this); } + + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } + private: const bool have_callback_; }; diff --git a/src/udp_wrap.h b/src/udp_wrap.h index 01eb8b961f0cf2..3792bcc459da23 100644 --- a/src/udp_wrap.h +++ b/src/udp_wrap.h @@ -64,7 +64,9 @@ class UDPWrap: public HandleWrap { SocketType type); uv_udp_t* UVHandle(); - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } private: typedef uv_udp_t HandleType; diff --git a/test/cctest/test_node_postmortem_metadata.cc b/test/cctest/test_node_postmortem_metadata.cc index f69df3ed227717..6f5db0fdf9272b 100644 --- a/test/cctest/test_node_postmortem_metadata.cc +++ b/test/cctest/test_node_postmortem_metadata.cc @@ -34,7 +34,9 @@ class DebugSymbolsTest : public EnvironmentTestFixture {}; class TestHandleWrap : public node::HandleWrap { public: - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(node::MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } TestHandleWrap(node::Environment* env, v8::Local<v8::Object> object, @@ -48,7 +50,9 @@ class TestHandleWrap : public node::HandleWrap { class TestReqWrap : public node::ReqWrap<uv_req_t> { public: - size_t self_size() const override { return sizeof(*this); } + void MemoryInfo(node::MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } TestReqWrap(node::Environment* env, v8::Local<v8::Object> object) : node::ReqWrap<uv_req_t>(env, @@ -67,6 +71,16 @@ TEST_F(DebugSymbolsTest, ExternalStringDataOffset) { NODE_OFF_EXTSTR_DATA); } +class DummyBaseObject : public node::BaseObject { + public: + DummyBaseObject(node::Environment* env, v8::Local<v8::Object> obj) : + BaseObject(env, obj) {} + + void MemoryInfo(node::MemoryTracker* tracker) const override { + tracker->TrackThis(this); + } +}; + TEST_F(DebugSymbolsTest, BaseObjectPersistentHandle) { const v8::HandleScope handle_scope(isolate_); const Argv argv; @@ -77,7 +91,7 @@ TEST_F(DebugSymbolsTest, BaseObjectPersistentHandle) { v8::Local<v8::Object> object = obj_templ->NewInstance(env.context()).ToLocalChecked(); - node::BaseObject obj(*env, object); + DummyBaseObject obj(*env, object); auto expected = reinterpret_cast<uintptr_t>(&obj.persistent()); auto calculated = reinterpret_cast<uintptr_t>(&obj) + From ada3f34cd4082454edef127c534c16579256c535 Mon Sep 17 00:00:00 2001 From: Jon Moss <me@jonathanmoss.me> Date: Fri, 13 Jul 2018 10:45:42 -0400 Subject: [PATCH 063/116] test: fix weird string error Previously getting this error when running `tap2junit` (what parses our `.tap` files in CI): ``` Traceback (most recent call last): File "/usr/local/bin/tap2junit", line 11, in <module> sys.exit(main()) File "/usr/local/lib/python2.7/site-packages/tap2junit/__main__.py", line 46, in main result.to_file(args.output, [result], prettyprint=False) File "/usr/local/lib/python2.7/site-packages/junit_xml/__init__.py", line 289, in to_file test_suites, prettyprint=prettyprint, encoding=encoding) File "/usr/local/lib/python2.7/site-packages/junit_xml/__init__.py", line 257, in to_xml_string ts_xml = ts.build_xml_doc(encoding=encoding) File "/usr/local/lib/python2.7/site-packages/junit_xml/__init__.py", line 221, in build_xml_doc attrs['message'] = decode(case.skipped_message, encoding) File "/usr/local/lib/python2.7/site-packages/junit_xml/__init__.py", line 68, in decode ret = unicode(var) UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 11: ordinal not in range(128) ``` PR-URL: https://github.com/nodejs/node/pull/21793 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> --- test/parallel/test-stdio-pipe-access.js | 2 +- test/parallel/test-stdio-pipe-redirect.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/parallel/test-stdio-pipe-access.js b/test/parallel/test-stdio-pipe-access.js index d32c5f0ba9c2e7..084fa4c038e7da 100644 --- a/test/parallel/test-stdio-pipe-access.js +++ b/test/parallel/test-stdio-pipe-access.js @@ -1,7 +1,7 @@ 'use strict'; const common = require('../common'); if (!common.isMainThread) - common.skip('Workers don’t have process-like stdio'); + common.skip("Workers don't have process-like stdio"); // Test if Node handles acessing process.stdin if it is a redirected // pipe without deadlocking diff --git a/test/parallel/test-stdio-pipe-redirect.js b/test/parallel/test-stdio-pipe-redirect.js index 60f16b5cb2f6df..fbde6ef8085457 100644 --- a/test/parallel/test-stdio-pipe-redirect.js +++ b/test/parallel/test-stdio-pipe-redirect.js @@ -1,7 +1,7 @@ 'use strict'; const common = require('../common'); if (!common.isMainThread) - common.skip('Workers don’t have process-like stdio'); + common.skip("Workers don't have process-like stdio"); // Test if Node handles redirecting one child process stdout to another // process stdin without crashing. From 7ab6efdb94905b0134bb2e816d9526a8e8c738fa Mon Sep 17 00:00:00 2001 From: Myles Borins <mylesborins@google.com> Date: Fri, 29 Jun 2018 18:49:42 -0400 Subject: [PATCH 064/116] doc: add policy for landing new npm releases This change in policy sets clear terms for when / how npm releases can be landed into master and how long they are expected to bake in the ecosystem. This is to cover all release types of npm including semver-major releases. What Node.js releases the updates land into are at the discretion of the release team. PR-URL: https://github.com/nodejs/node/pull/21594 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> --- doc/guides/maintaining-npm.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/guides/maintaining-npm.md b/doc/guides/maintaining-npm.md index 97da87feee64d3..6017e78fded70a 100644 --- a/doc/guides/maintaining-npm.md +++ b/doc/guides/maintaining-npm.md @@ -1,5 +1,19 @@ # Maintaining npm in Node.js +New pull requests should be opened when a "next" version of npm has +been released. Once the "next" version has been promoted to "latest" +the PR should be updated as necessary. + +Two weeks after the "latest" release has been promoted it can land on master +assuming no major regressions are found. There are no additional constraints +for Semver-Major releases. + +The specific Node.js release streams the new version will be able to land into +are at the discretion of the release and LTS teams. + +This process only covers full updates to new versions of npm. Cherry-picked +changes can be reviewed and landed via the normal consensus seeking process. + ## Step 1: Clone npm ```console From 42d75392c522823f89aab2f1f0514612365b3e85 Mon Sep 17 00:00:00 2001 From: Myles Borins <mylesborins@google.com> Date: Mon, 9 Jul 2018 16:22:46 -0400 Subject: [PATCH 065/116] deps: patch V8 to 6.7.288.49 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/21727 Refs: https://github.com/v8/v8/compare/6.7.288.46...6.7.288.49 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- deps/v8/DEPS | 2 ++ deps/v8/include/v8-version.h | 2 +- deps/v8/tools/whitespace.txt | 4 +--- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/deps/v8/DEPS b/deps/v8/DEPS index 4a00478633793d..7c4fe68361593d 100644 --- a/deps/v8/DEPS +++ b/deps/v8/DEPS @@ -15,6 +15,8 @@ deps = { Var('chromium_url') + '/chromium/src/build.git' + '@' + '73e352e758d90603e23bdc84734c12aa5817ab5f', 'v8/tools/gyp': Var('chromium_url') + '/external/gyp.git' + '@' + 'd61a9397e668fa9843c4aa7da9e79460fe590bfb', + 'v8/third_party/depot_tools': + Var('chromium_url') + '/chromium/tools/depot_tools.git' + '@' + '024a3317597b06418efea2d45aa54dd2a7030c8a', 'v8/third_party/icu': Var('chromium_url') + '/chromium/deps/icu.git' + '@' + 'd888fd2a1be890f4d35e43f68d6d79f42519a357', 'v8/third_party/instrumented_libraries': diff --git a/deps/v8/include/v8-version.h b/deps/v8/include/v8-version.h index 440a6068699ef0..09c47d7e91196d 100644 --- a/deps/v8/include/v8-version.h +++ b/deps/v8/include/v8-version.h @@ -11,7 +11,7 @@ #define V8_MAJOR_VERSION 6 #define V8_MINOR_VERSION 7 #define V8_BUILD_NUMBER 288 -#define V8_PATCH_LEVEL 46 +#define V8_PATCH_LEVEL 49 // Use 1 for candidates and 0 otherwise. // (Boolean macro values are not supported by all preprocessors.) diff --git a/deps/v8/tools/whitespace.txt b/deps/v8/tools/whitespace.txt index ed5e51f96a63e8..8a1a4d64c7aac8 100644 --- a/deps/v8/tools/whitespace.txt +++ b/deps/v8/tools/whitespace.txt @@ -7,6 +7,4 @@ A Smi balks into a war and says: The doubles heard this and started to unbox. The Smi looked at them when a crazy v8-autoroll account showed up... The autoroller bought a round of Himbeerbrause. Suddenly... -The bartender starts to shake the bottles....................... -. -. +The bartender starts to shake the bottles... From b2291296efb942f863f69b4be4d7059a123ebd7c Mon Sep 17 00:00:00 2001 From: Eugene Ostroukhov <eostroukhov@google.com> Date: Mon, 21 May 2018 16:59:04 -0700 Subject: [PATCH 066/116] inspector: split main thread interface from transport Workers debugging will require interfacing between the "main" inspector and per-worker isolate inspectors. This is consistent with what WS interface does. This change is a refactoring change and does not change the functionality. PR-URL: https://github.com/nodejs/node/pull/21182 Fixes: https://github.com/nodejs/node/issues/21725 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> --- node.gyp | 4 +- src/inspector/main_thread_interface.cc | 317 +++++++++++ src/inspector/main_thread_interface.h | 99 ++++ src/inspector_agent.cc | 285 ++++++---- src/inspector_agent.h | 39 +- src/inspector_io.cc | 549 ++++++++------------ src/inspector_io.h | 142 ++--- src/inspector_js_api.cc | 11 +- src/inspector_socket_server.cc | 31 +- src/inspector_socket_server.h | 4 - src/node.cc | 11 +- src/signal_wrap.cc | 2 +- test/cctest/test_inspector_socket_server.cc | 29 +- test/parallel/test-warn-sigprof.js | 1 + 14 files changed, 890 insertions(+), 634 deletions(-) create mode 100644 src/inspector/main_thread_interface.cc create mode 100644 src/inspector/main_thread_interface.h diff --git a/node.gyp b/node.gyp index ae254278198edf..e0bd57dde51686 100644 --- a/node.gyp +++ b/node.gyp @@ -490,12 +490,14 @@ 'src/inspector_js_api.cc', 'src/inspector_socket.cc', 'src/inspector_socket_server.cc', - 'src/inspector/tracing_agent.cc', + 'src/inspector/main_thread_interface.cc', 'src/inspector/node_string.cc', + 'src/inspector/tracing_agent.cc', 'src/inspector_agent.h', 'src/inspector_io.h', 'src/inspector_socket.h', 'src/inspector_socket_server.h', + 'src/inspector/main_thread_interface.h', 'src/inspector/node_string.h', 'src/inspector/tracing_agent.h', '<@(node_inspector_generated_sources)' diff --git a/src/inspector/main_thread_interface.cc b/src/inspector/main_thread_interface.cc new file mode 100644 index 00000000000000..da43c95bea5fe3 --- /dev/null +++ b/src/inspector/main_thread_interface.cc @@ -0,0 +1,317 @@ +#include "main_thread_interface.h" + +#include "node_mutex.h" +#include "v8-inspector.h" + +#include <unicode/unistr.h> + +namespace node { +namespace inspector { +namespace { + +using v8_inspector::StringView; +using v8_inspector::StringBuffer; + +template <typename T> +class DeleteRequest : public Request { + public: + explicit DeleteRequest(T* object) : object_(object) {} + void Call() override { + delete object_; + } + + private: + T* object_; +}; + +template <typename Target, typename Arg> +class SingleArgumentFunctionCall : public Request { + public: + using Fn = void (Target::*)(Arg); + + SingleArgumentFunctionCall(Target* target, Fn fn, Arg argument) + : target_(target), + fn_(fn), + arg_(std::move(argument)) {} + + void Call() override { + Apply(target_, fn_, std::move(arg_)); + } + + private: + template <typename Element> + void Apply(Element* target, Fn fn, Arg arg) { + (target->*fn)(std::move(arg)); + } + + Target* target_; + Fn fn_; + Arg arg_; +}; + +class PostMessageRequest : public Request { + public: + PostMessageRequest(InspectorSessionDelegate* delegate, + StringView message) + : delegate_(delegate), + message_(StringBuffer::create(message)) {} + + void Call() override { + delegate_->SendMessageToFrontend(message_->string()); + } + + private: + InspectorSessionDelegate* delegate_; + std::unique_ptr<StringBuffer> message_; +}; + +class DispatchMessagesTask : public v8::Task { + public: + explicit DispatchMessagesTask(MainThreadInterface* thread) + : thread_(thread) {} + + void Run() override { + thread_->DispatchMessages(); + } + + private: + MainThreadInterface* thread_; +}; + +void DisposePairCallback(uv_handle_t* ref) { + using AsyncAndInterface = std::pair<uv_async_t, MainThreadInterface*>; + AsyncAndInterface* pair = node::ContainerOf( + &AsyncAndInterface::first, reinterpret_cast<uv_async_t*>(ref)); + delete pair; +} + +template <typename T> +class AnotherThreadObjectReference { + public: + // We create it on whatever thread, just make sure it gets disposed on the + // proper thread. + AnotherThreadObjectReference(std::shared_ptr<MainThreadHandle> thread, + T* object) + : thread_(thread), object_(object) { + } + AnotherThreadObjectReference(AnotherThreadObjectReference&) = delete; + + ~AnotherThreadObjectReference() { + // Disappearing thread may cause a memory leak + CHECK(thread_->Post( + std::unique_ptr<DeleteRequest<T>>(new DeleteRequest<T>(object_)))); + object_ = nullptr; + } + + template <typename Fn, typename Arg> + void Post(Fn fn, Arg argument) const { + using R = SingleArgumentFunctionCall<T, Arg>; + thread_->Post(std::unique_ptr<R>(new R(object_, fn, std::move(argument)))); + } + + T* get() const { + return object_; + } + + private: + std::shared_ptr<MainThreadHandle> thread_; + T* object_; +}; + +class MainThreadSessionState { + public: + MainThreadSessionState( + std::shared_ptr<MainThreadHandle> thread, + bool prevent_shutdown) : thread_(thread), + prevent_shutdown_(prevent_shutdown) {} + + void Connect(std::unique_ptr<InspectorSessionDelegate> delegate) { + Agent* agent = thread_->GetInspectorAgent(); + if (agent != nullptr) + session_ = agent->Connect(std::move(delegate), prevent_shutdown_); + } + + void Dispatch(std::unique_ptr<StringBuffer> message) { + session_->Dispatch(message->string()); + } + + private: + std::shared_ptr<MainThreadHandle> thread_; + bool prevent_shutdown_; + std::unique_ptr<InspectorSession> session_; +}; + +class CrossThreadInspectorSession : public InspectorSession { + public: + CrossThreadInspectorSession( + int id, + std::shared_ptr<MainThreadHandle> thread, + std::unique_ptr<InspectorSessionDelegate> delegate, + bool prevent_shutdown) + : state_(thread, new MainThreadSessionState(thread, prevent_shutdown)) { + state_.Post(&MainThreadSessionState::Connect, std::move(delegate)); + } + + void Dispatch(const StringView& message) override { + state_.Post(&MainThreadSessionState::Dispatch, + StringBuffer::create(message)); + } + + private: + AnotherThreadObjectReference<MainThreadSessionState> state_; +}; + +class ThreadSafeDelegate : public InspectorSessionDelegate { + public: + ThreadSafeDelegate(std::shared_ptr<MainThreadHandle> thread, + std::unique_ptr<InspectorSessionDelegate> delegate) + : thread_(thread), delegate_(thread, delegate.release()) {} + + void SendMessageToFrontend(const v8_inspector::StringView& message) override { + thread_->Post(std::unique_ptr<Request>( + new PostMessageRequest(delegate_.get(), message))); + } + + private: + std::shared_ptr<MainThreadHandle> thread_; + AnotherThreadObjectReference<InspectorSessionDelegate> delegate_; +}; +} // namespace + + +MainThreadInterface::MainThreadInterface(Agent* agent, uv_loop_t* loop, + v8::Isolate* isolate, + v8::Platform* platform) + : agent_(agent), isolate_(isolate), + platform_(platform) { + main_thread_request_.reset(new AsyncAndInterface(uv_async_t(), this)); + CHECK_EQ(0, uv_async_init(loop, &main_thread_request_->first, + DispatchMessagesAsyncCallback)); + // Inspector uv_async_t should not prevent main loop shutdown. + uv_unref(reinterpret_cast<uv_handle_t*>(&main_thread_request_->first)); +} + +MainThreadInterface::~MainThreadInterface() { + if (handle_) + handle_->Reset(); +} + +// static +void MainThreadInterface::DispatchMessagesAsyncCallback(uv_async_t* async) { + AsyncAndInterface* asyncAndInterface = + node::ContainerOf(&AsyncAndInterface::first, async); + asyncAndInterface->second->DispatchMessages(); +} + +// static +void MainThreadInterface::CloseAsync(AsyncAndInterface* pair) { + uv_close(reinterpret_cast<uv_handle_t*>(&pair->first), DisposePairCallback); +} + +void MainThreadInterface::Post(std::unique_ptr<Request> request) { + Mutex::ScopedLock scoped_lock(requests_lock_); + bool needs_notify = requests_.empty(); + requests_.push_back(std::move(request)); + if (needs_notify) { + CHECK_EQ(0, uv_async_send(&main_thread_request_->first)); + if (isolate_ != nullptr && platform_ != nullptr) { + platform_->CallOnForegroundThread(isolate_, + new DispatchMessagesTask(this)); + isolate_->RequestInterrupt([](v8::Isolate* isolate, void* thread) { + static_cast<MainThreadInterface*>(thread)->DispatchMessages(); + }, this); + } + } + incoming_message_cond_.Broadcast(scoped_lock); +} + +bool MainThreadInterface::WaitForFrontendEvent() { + // We allow DispatchMessages reentry as we enter the pause. This is important + // to support debugging the code invoked by an inspector call, such + // as Runtime.evaluate + dispatching_messages_ = false; + if (dispatching_message_queue_.empty()) { + Mutex::ScopedLock scoped_lock(requests_lock_); + while (requests_.empty()) incoming_message_cond_.Wait(scoped_lock); + } + return true; +} + +void MainThreadInterface::DispatchMessages() { + if (dispatching_messages_) + return; + dispatching_messages_ = true; + bool had_messages = false; + do { + if (dispatching_message_queue_.empty()) { + Mutex::ScopedLock scoped_lock(requests_lock_); + requests_.swap(dispatching_message_queue_); + } + had_messages = !dispatching_message_queue_.empty(); + while (!dispatching_message_queue_.empty()) { + MessageQueue::value_type task; + std::swap(dispatching_message_queue_.front(), task); + dispatching_message_queue_.pop_front(); + task->Call(); + } + } while (had_messages); + dispatching_messages_ = false; +} + +std::shared_ptr<MainThreadHandle> MainThreadInterface::GetHandle() { + if (handle_ == nullptr) + handle_ = std::make_shared<MainThreadHandle>(this); + return handle_; +} + +std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string& message) { + icu::UnicodeString utf16 = icu::UnicodeString::fromUTF8( + icu::StringPiece(message.data(), message.length())); + StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()), + utf16.length()); + return StringBuffer::create(view); +} + +std::unique_ptr<InspectorSession> MainThreadHandle::Connect( + std::unique_ptr<InspectorSessionDelegate> delegate, + bool prevent_shutdown) { + return std::unique_ptr<InspectorSession>( + new CrossThreadInspectorSession(++next_session_id_, + shared_from_this(), + std::move(delegate), + prevent_shutdown)); +} + +bool MainThreadHandle::Post(std::unique_ptr<Request> request) { + Mutex::ScopedLock scoped_lock(block_lock_); + if (!main_thread_) + return false; + main_thread_->Post(std::move(request)); + return true; +} + +void MainThreadHandle::Reset() { + Mutex::ScopedLock scoped_lock(block_lock_); + main_thread_ = nullptr; +} + +Agent* MainThreadHandle::GetInspectorAgent() { + Mutex::ScopedLock scoped_lock(block_lock_); + if (main_thread_ == nullptr) + return nullptr; + return main_thread_->inspector_agent(); +} + +std::unique_ptr<InspectorSessionDelegate> +MainThreadHandle::MakeThreadSafeDelegate( + std::unique_ptr<InspectorSessionDelegate> delegate) { + return std::unique_ptr<InspectorSessionDelegate>( + new ThreadSafeDelegate(shared_from_this(), std::move(delegate))); +} + +bool MainThreadHandle::Expired() { + Mutex::ScopedLock scoped_lock(block_lock_); + return main_thread_ == nullptr; +} +} // namespace inspector +} // namespace node diff --git a/src/inspector/main_thread_interface.h b/src/inspector/main_thread_interface.h new file mode 100644 index 00000000000000..75df5ffe809048 --- /dev/null +++ b/src/inspector/main_thread_interface.h @@ -0,0 +1,99 @@ +#ifndef SRC_INSPECTOR_MAIN_THREAD_INTERFACE_H_ +#define SRC_INSPECTOR_MAIN_THREAD_INTERFACE_H_ + +#if !HAVE_INSPECTOR +#error("This header can only be used when inspector is enabled") +#endif + +#include "env.h" +#include "inspector_agent.h" +#include "node_mutex.h" + +#include <deque> +#include <memory> +#include <unordered_map> +#include <unordered_set> + +namespace v8_inspector { +class StringBuffer; +class StringView; +} // namespace v8_inspector + +namespace node { +namespace inspector { +class Request { + public: + virtual void Call() = 0; + virtual ~Request() {} +}; + +std::unique_ptr<v8_inspector::StringBuffer> Utf8ToStringView( + const std::string& message); + +using MessageQueue = std::deque<std::unique_ptr<Request>>; +class MainThreadInterface; + +class MainThreadHandle : public std::enable_shared_from_this<MainThreadHandle> { + public: + explicit MainThreadHandle(MainThreadInterface* main_thread) + : main_thread_(main_thread) {} + ~MainThreadHandle() { + CHECK_NULL(main_thread_); // main_thread_ should have called Reset + } + std::unique_ptr<InspectorSession> Connect( + std::unique_ptr<InspectorSessionDelegate> delegate, + bool prevent_shutdown); + bool Post(std::unique_ptr<Request> request); + Agent* GetInspectorAgent(); + std::unique_ptr<InspectorSessionDelegate> MakeThreadSafeDelegate( + std::unique_ptr<InspectorSessionDelegate> delegate); + bool Expired(); + + private: + void Reset(); + + MainThreadInterface* main_thread_; + Mutex block_lock_; + int next_session_id_ = 0; + + friend class MainThreadInterface; +}; + +class MainThreadInterface { + public: + MainThreadInterface(Agent* agent, uv_loop_t*, v8::Isolate* isolate, + v8::Platform* platform); + ~MainThreadInterface(); + + void DispatchMessages(); + void Post(std::unique_ptr<Request> request); + bool WaitForFrontendEvent(); + std::shared_ptr<MainThreadHandle> GetHandle(); + Agent* inspector_agent() { + return agent_; + } + + private: + using AsyncAndInterface = std::pair<uv_async_t, MainThreadInterface*>; + + static void DispatchMessagesAsyncCallback(uv_async_t* async); + static void CloseAsync(AsyncAndInterface*); + + MessageQueue requests_; + Mutex requests_lock_; // requests_ live across threads + // This queue is to maintain the order of the messages for the cases + // when we reenter the DispatchMessages function. + MessageQueue dispatching_message_queue_; + bool dispatching_messages_ = false; + ConditionVariable incoming_message_cond_; + // Used from any thread + Agent* const agent_; + v8::Isolate* const isolate_; + v8::Platform* const platform_; + DeleteFnPtr<AsyncAndInterface, CloseAsync> main_thread_request_; + std::shared_ptr<MainThreadHandle> handle_; +}; + +} // namespace inspector +} // namespace node +#endif // SRC_INSPECTOR_MAIN_THREAD_INTERFACE_H_ diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc index 2b03fd05b115a3..ebb8d8a161606d 100644 --- a/src/inspector_agent.cc +++ b/src/inspector_agent.cc @@ -1,6 +1,7 @@ #include "inspector_agent.h" #include "inspector_io.h" +#include "inspector/main_thread_interface.h" #include "inspector/node_string.h" #include "inspector/tracing_agent.h" #include "node/inspector/protocol/Protocol.h" @@ -49,7 +50,7 @@ class StartIoTask : public v8::Task { explicit StartIoTask(Agent* agent) : agent(agent) {} void Run() override { - agent->StartIoThread(false); + agent->StartIoThread(); } private: @@ -64,11 +65,11 @@ std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate, // Called on the main thread. void StartIoThreadAsyncCallback(uv_async_t* handle) { - static_cast<Agent*>(handle->data)->StartIoThread(false); + static_cast<Agent*>(handle->data)->StartIoThread(); } void StartIoInterrupt(Isolate* isolate, void* agent) { - static_cast<Agent*>(agent)->StartIoThread(false); + static_cast<Agent*>(agent)->StartIoThread(); } @@ -195,8 +196,10 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, public: explicit ChannelImpl(Environment* env, const std::unique_ptr<V8Inspector>& inspector, - std::unique_ptr<InspectorSessionDelegate> delegate) - : delegate_(std::move(delegate)) { + std::unique_ptr<InspectorSessionDelegate> delegate, + bool prevent_shutdown) + : delegate_(std::move(delegate)), + prevent_shutdown_(prevent_shutdown) { session_ = inspector->connect(1, this, StringView()); node_dispatcher_.reset(new protocol::UberDispatcher(this)); tracing_agent_.reset(new protocol::TracingAgent(env)); @@ -208,7 +211,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, tracing_agent_.reset(); // Dispose before the dispatchers } - void dispatchProtocolMessage(const StringView& message) { + std::string dispatchProtocolMessage(const StringView& message) { std::unique_ptr<protocol::DictionaryValue> parsed; std::string method; node_dispatcher_->getCommandName( @@ -219,6 +222,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, } else { node_dispatcher_->dispatch(std::move(parsed)); } + return method; } void schedulePauseOnNextStatement(const std::string& reason) { @@ -226,6 +230,10 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, session_->schedulePauseOnNextStatement(buffer->string(), buffer->string()); } + bool preventShutdown() { + return prevent_shutdown_; + } + private: void sendResponse( int callId, @@ -263,6 +271,7 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel, std::unique_ptr<InspectorSessionDelegate> delegate_; std::unique_ptr<v8_inspector::V8InspectorSession> session_; std::unique_ptr<protocol::UberDispatcher> node_dispatcher_; + bool prevent_shutdown_; }; class InspectorTimer { @@ -324,6 +333,44 @@ class InspectorTimerHandle { private: InspectorTimer* timer_; }; + +class SameThreadInspectorSession : public InspectorSession { + public: + SameThreadInspectorSession( + int session_id, std::shared_ptr<NodeInspectorClient> client) + : session_id_(session_id), client_(client) {} + ~SameThreadInspectorSession() override; + void Dispatch(const v8_inspector::StringView& message) override; + + private: + int session_id_; + std::weak_ptr<NodeInspectorClient> client_; +}; + +void NotifyClusterWorkersDebugEnabled(Environment* env) { + v8::Isolate* isolate = env->isolate(); + HandleScope handle_scope(isolate); + auto context = env->context(); + + // Send message to enable debug in cluster workers + Local<Object> process_object = env->process_object(); + Local<Value> emit_fn = + process_object->Get(context, FIXED_ONE_BYTE_STRING(isolate, "emit")) + .ToLocalChecked(); + // In case the thread started early during the startup + if (!emit_fn->IsFunction()) + return; + + Local<Object> message = Object::New(isolate); + message->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cmd"), + FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")).FromJust(); + Local<Value> argv[] = { + FIXED_ONE_BYTE_STRING(isolate, "internalMessage"), + message + }; + MakeCallback(env->isolate(), process_object, emit_fn.As<Function>(), + arraysize(argv), argv, {0, 0}); +} } // namespace class NodeInspectorClient : public V8InspectorClient { @@ -337,31 +384,18 @@ class NodeInspectorClient : public V8InspectorClient { } void runMessageLoopOnPause(int context_group_id) override { - runMessageLoop(false); - } - - void runMessageLoop(bool ignore_terminated) { - if (running_nested_loop_) - return; - terminated_ = false; - running_nested_loop_ = true; - MultiIsolatePlatform* platform = env_->isolate_data()->platform(); - while ((ignore_terminated || !terminated_) && waitForFrontendEvent()) { - while (platform->FlushForegroundTasks(env_->isolate())) {} - } - terminated_ = false; - running_nested_loop_ = false; + waiting_for_resume_ = true; + runMessageLoop(); } - bool waitForFrontendEvent() { - InspectorIo* io = env_->inspector_agent()->io(); - if (io == nullptr) - return false; - return io->WaitForFrontendEvent(); + void waitForIoShutdown() { + waiting_for_io_shutdown_ = true; + runMessageLoop(); } - double currentTimeMS() override { - return uv_hrtime() * 1.0 / NANOS_PER_MSEC; + void waitForFrontend() { + waiting_for_frontend_ = true; + runMessageLoop(); } void maxAsyncCallStackDepthChanged(int depth) override { @@ -398,16 +432,17 @@ class NodeInspectorClient : public V8InspectorClient { } void quitMessageLoopOnPause() override { - terminated_ = true; + waiting_for_resume_ = false; } - int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate) { + int connectFrontend(std::unique_ptr<InspectorSessionDelegate> delegate, + bool prevent_shutdown) { events_dispatched_ = true; int session_id = next_session_id_++; // TODO(addaleax): Revert back to using make_unique once we get issues // with CI resolved (i.e. revert the patch that added this comment). channels_[session_id].reset( - new ChannelImpl(env_, client_, std::move(delegate))); + new ChannelImpl(env_, client_, std::move(delegate), prevent_shutdown)); return session_id; } @@ -418,7 +453,10 @@ class NodeInspectorClient : public V8InspectorClient { void dispatchMessageFromFrontend(int session_id, const StringView& message) { events_dispatched_ = true; - channels_[session_id]->dispatchProtocolMessage(message); + std::string method = + channels_[session_id]->dispatchProtocolMessage(message); + if (waiting_for_frontend_) + waiting_for_frontend_ = method != "Runtime.runIfWaitingForDebugger"; } Local<Context> ensureDefaultContextInGroup(int contextGroupId) override { @@ -509,116 +547,150 @@ class NodeInspectorClient : public V8InspectorClient { } bool hasConnectedSessions() { + for (const auto& id_channel : channels_) { + // Other sessions are "invisible" more most purposes + if (id_channel.second->preventShutdown()) + return true; + } + return false; + } + + std::shared_ptr<MainThreadHandle> getThreadHandle() { + if (interface_ == nullptr) { + interface_.reset(new MainThreadInterface( + env_->inspector_agent(), env_->event_loop(), env_->isolate(), + env_->isolate_data()->platform())); + } + return interface_->GetHandle(); + } + + bool IsActive() { return !channels_.empty(); } private: + bool shouldRunMessageLoop() { + if (waiting_for_frontend_) + return true; + if (waiting_for_io_shutdown_ || waiting_for_resume_) + return hasConnectedSessions(); + return false; + } + + void runMessageLoop() { + if (running_nested_loop_) + return; + + running_nested_loop_ = true; + + MultiIsolatePlatform* platform = env_->isolate_data()->platform(); + while (shouldRunMessageLoop()) { + if (interface_ && hasConnectedSessions()) + interface_->WaitForFrontendEvent(); + while (platform->FlushForegroundTasks(env_->isolate())) {} + } + running_nested_loop_ = false; + } + + double currentTimeMS() override { + return uv_hrtime() * 1.0 / NANOS_PER_MSEC; + } + node::Environment* env_; - bool terminated_ = false; bool running_nested_loop_ = false; std::unique_ptr<V8Inspector> client_; std::unordered_map<int, std::unique_ptr<ChannelImpl>> channels_; std::unordered_map<void*, InspectorTimerHandle> timers_; int next_session_id_ = 1; bool events_dispatched_ = false; + bool waiting_for_resume_ = false; + bool waiting_for_frontend_ = false; + bool waiting_for_io_shutdown_ = false; + // Allows accessing Inspector from non-main threads + std::unique_ptr<MainThreadInterface> interface_; }; Agent::Agent(Environment* env) : parent_env_(env) {} -// Destructor needs to be defined here in implementation file as the header -// does not have full definition of some classes. -Agent::~Agent() { -} +Agent::~Agent() = default; -bool Agent::Start(const char* path, const DebugOptions& options) { - path_ = path == nullptr ? "" : path; +bool Agent::Start(const std::string& path, const DebugOptions& options) { + path_ = path; debug_options_ = options; client_ = std::make_shared<NodeInspectorClient>(parent_env_); - CHECK_EQ(0, uv_async_init(uv_default_loop(), - &start_io_thread_async, - StartIoThreadAsyncCallback)); - start_io_thread_async.data = this; - uv_unref(reinterpret_cast<uv_handle_t*>(&start_io_thread_async)); - - // Ignore failure, SIGUSR1 won't work, but that should not block node start. - StartDebugSignalHandler(); - if (options.inspector_enabled()) { - // This will return false if listen failed on the inspector port. - return StartIoThread(options.wait_for_connect()); + if (parent_env_->is_main_thread()) { + CHECK_EQ(0, uv_async_init(parent_env_->event_loop(), + &start_io_thread_async, + StartIoThreadAsyncCallback)); + uv_unref(reinterpret_cast<uv_handle_t*>(&start_io_thread_async)); + start_io_thread_async.data = this; + // Ignore failure, SIGUSR1 won't work, but that should not block node start. + StartDebugSignalHandler(); + } + + bool wait_for_connect = options.wait_for_connect(); + if (!options.inspector_enabled() || !StartIoThread()) { + return false; + } + if (wait_for_connect) { + HandleScope scope(parent_env_->isolate()); + parent_env_->process_object()->DefineOwnProperty( + parent_env_->context(), + FIXED_ONE_BYTE_STRING(parent_env_->isolate(), "_breakFirstLine"), + True(parent_env_->isolate()), + static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontEnum)) + .FromJust(); + client_->waitForFrontend(); } return true; } -bool Agent::StartIoThread(bool wait_for_connect) { +bool Agent::StartIoThread() { if (io_ != nullptr) return true; CHECK_NOT_NULL(client_); - io_ = std::unique_ptr<InspectorIo>( - new InspectorIo(parent_env_, path_, debug_options_, wait_for_connect)); - if (!io_->Start()) { - client_.reset(); + io_ = InspectorIo::Start( + client_->getThreadHandle(), path_, debug_options_); + if (io_ == nullptr) { return false; } - - v8::Isolate* isolate = parent_env_->isolate(); - HandleScope handle_scope(isolate); - auto context = parent_env_->context(); - - // Send message to enable debug in workers - Local<Object> process_object = parent_env_->process_object(); - Local<Value> emit_fn = - process_object->Get(context, FIXED_ONE_BYTE_STRING(isolate, "emit")) - .ToLocalChecked(); - // In case the thread started early during the startup - if (!emit_fn->IsFunction()) - return true; - - Local<Object> message = Object::New(isolate); - message->Set(context, FIXED_ONE_BYTE_STRING(isolate, "cmd"), - FIXED_ONE_BYTE_STRING(isolate, "NODE_DEBUG_ENABLED")).FromJust(); - Local<Value> argv[] = { - FIXED_ONE_BYTE_STRING(isolate, "internalMessage"), - message - }; - MakeCallback(parent_env_->isolate(), process_object, emit_fn.As<Function>(), - arraysize(argv), argv, {0, 0}); - + NotifyClusterWorkersDebugEnabled(parent_env_); return true; } void Agent::Stop() { - if (io_ != nullptr) { - io_->Stop(); - io_.reset(); - } + io_.reset(); } std::unique_ptr<InspectorSession> Agent::Connect( - std::unique_ptr<InspectorSessionDelegate> delegate) { - int session_id = client_->connectFrontend(std::move(delegate)); + std::unique_ptr<InspectorSessionDelegate> delegate, + bool prevent_shutdown) { + CHECK_NOT_NULL(client_); + int session_id = client_->connectFrontend(std::move(delegate), + prevent_shutdown); return std::unique_ptr<InspectorSession>( - new InspectorSession(session_id, client_)); + new SameThreadInspectorSession(session_id, client_)); } void Agent::WaitForDisconnect() { CHECK_NOT_NULL(client_); + if (client_->hasConnectedSessions()) { + fprintf(stderr, "Waiting for the debugger to disconnect...\n"); + fflush(stderr); + } // TODO(addaleax): Maybe this should use an at-exit hook for the Environment // or something similar? client_->contextDestroyed(parent_env_->context()); if (io_ != nullptr) { - io_->WaitForDisconnect(); - // There is a bug in V8 Inspector (https://crbug.com/834056) that - // calls V8InspectorClient::quitMessageLoopOnPause when a session - // disconnects. We are using this flag to ignore those calls so the message - // loop is spinning as long as there's a reason to expect inspector messages - client_->runMessageLoop(true); + io_->StopAcceptingNewConnections(); + client_->waitForIoShutdown(); } } void Agent::FatalException(Local<Value> error, Local<v8::Message> message) { - if (!IsStarted()) + if (!IsListening()) return; client_->FatalException(error, message); WaitForDisconnect(); @@ -718,26 +790,35 @@ void Agent::ContextCreated(Local<Context> context, const ContextInfo& info) { client_->contextCreated(context, info); } -bool Agent::IsWaitingForConnect() { +bool Agent::WillWaitForConnect() { return debug_options_.wait_for_connect(); } -bool Agent::HasConnectedSessions() { +bool Agent::IsActive() { if (client_ == nullptr) return false; - return client_->hasConnectedSessions(); + return io_ != nullptr || client_->IsActive(); } -InspectorSession::InspectorSession(int session_id, - std::shared_ptr<NodeInspectorClient> client) - : session_id_(session_id), client_(client) {} +void Agent::WaitForConnect() { + CHECK_NOT_NULL(client_); + client_->waitForFrontend(); +} -InspectorSession::~InspectorSession() { - client_->disconnectFrontend(session_id_); +SameThreadInspectorSession::~SameThreadInspectorSession() { + auto client = client_.lock(); + if (client) + client->disconnectFrontend(session_id_); } -void InspectorSession::Dispatch(const StringView& message) { - client_->dispatchMessageFromFrontend(session_id_, message); +void SameThreadInspectorSession::Dispatch( + const v8_inspector::StringView& message) { + auto client = client_.lock(); + if (client) + client->dispatchMessageFromFrontend(session_id_, message); } + + + } // namespace inspector } // namespace node diff --git a/src/inspector_agent.h b/src/inspector_agent.h index 7295d048b64357..dcd6e13aba275f 100644 --- a/src/inspector_agent.h +++ b/src/inspector_agent.h @@ -20,7 +20,6 @@ class StringView; namespace node { // Forward declaration to break recursive dependency chain with src/env.h. class Environment; -class NodePlatform; struct ContextInfo; namespace inspector { @@ -29,12 +28,8 @@ class NodeInspectorClient; class InspectorSession { public: - InspectorSession(int session_id, std::shared_ptr<NodeInspectorClient> client); - ~InspectorSession(); - void Dispatch(const v8_inspector::StringView& message); - private: - int session_id_; - std::shared_ptr<NodeInspectorClient> client_; + virtual ~InspectorSession() {} + virtual void Dispatch(const v8_inspector::StringView& message) = 0; }; class InspectorSessionDelegate { @@ -50,15 +45,21 @@ class Agent { ~Agent(); // Create client_, may create io_ if option enabled - bool Start(const char* path, const DebugOptions& options); + bool Start(const std::string& path, const DebugOptions& options); // Stop and destroy io_ void Stop(); - bool IsStarted() { return !!client_; } - - // IO thread started, and client connected - bool IsWaitingForConnect(); - + bool IsListening() { return io_ != nullptr; } + // Returns true if the Node inspector is actually in use. It will be true + // if either the user explicitely opted into inspector (e.g. with the + // --inspect command line flag) or if inspector JS API had been used. + bool IsActive(); + + // Option is set to wait for session connection + bool WillWaitForConnect(); + // Blocks till frontend connects and sends "runIfWaitingForDebugger" + void WaitForConnect(); + // Blocks till all the sessions with "WaitForDisconnectOnShutdown" disconnect void WaitForDisconnect(); void FatalException(v8::Local<v8::Value> error, v8::Local<v8::Message> message); @@ -77,22 +78,20 @@ class Agent { void EnableAsyncHook(); void DisableAsyncHook(); - // Called by the WS protocol and JS binding to create inspector sessions. + // Called to create inspector sessions that can be used from the main thread. // The inspector responds by using the delegate to send messages back. std::unique_ptr<InspectorSession> Connect( - std::unique_ptr<InspectorSessionDelegate> delegate); + std::unique_ptr<InspectorSessionDelegate> delegate, + bool prevent_shutdown); void PauseOnNextJavascriptStatement(const std::string& reason); - // Returns true as long as there is at least one connected session. - bool HasConnectedSessions(); - InspectorIo* io() { return io_.get(); } // Can only be called from the main thread. - bool StartIoThread(bool wait_for_connect); + bool StartIoThread(); // Calls StartIoThread() from off the main thread. void RequestIoThreadStart(); @@ -105,7 +104,9 @@ class Agent { const node::Persistent<v8::Function>& fn); node::Environment* parent_env_; + // Encapsulates majority of the Inspector functionality std::shared_ptr<NodeInspectorClient> client_; + // Interface for transports, e.g. WebSocket server std::unique_ptr<InspectorIo> io_; std::string path_; DebugOptions debug_options_; diff --git a/src/inspector_io.cc b/src/inspector_io.cc index 78ecce7398bd59..41fea546a83265 100644 --- a/src/inspector_io.cc +++ b/src/inspector_io.cc @@ -1,6 +1,7 @@ #include "inspector_io.h" #include "inspector_socket_server.h" +#include "inspector/main_thread_interface.h" #include "inspector/node_string.h" #include "env-inl.h" #include "debug_utils.h" @@ -11,23 +12,16 @@ #include "util.h" #include "zlib.h" -#include <sstream> -#include <unicode/unistr.h> - +#include <deque> #include <string.h> #include <vector> - namespace node { namespace inspector { namespace { -using AsyncAndAgent = std::pair<uv_async_t, Agent*>; using v8_inspector::StringBuffer; using v8_inspector::StringView; -template <typename Transport> -using TransportAndIo = std::pair<Transport*, InspectorIo*>; - std::string ScriptPath(uv_loop_t* loop, const std::string& script_name) { std::string script_path; @@ -64,45 +58,151 @@ std::string GenerateID() { return uuid; } -void HandleSyncCloseCb(uv_handle_t* handle) { - *static_cast<bool*>(handle->data) = true; -} +class RequestToServer { + public: + RequestToServer(TransportAction action, + int session_id, + std::unique_ptr<v8_inspector::StringBuffer> message) + : action_(action), + session_id_(session_id), + message_(std::move(message)) {} + + void Dispatch(InspectorSocketServer* server) const { + switch (action_) { + case TransportAction::kKill: + server->TerminateConnections(); + // Fallthrough + case TransportAction::kStop: + server->Stop(); + break; + case TransportAction::kSendMessage: + server->Send( + session_id_, + protocol::StringUtil::StringViewToUtf8(message_->string())); + break; + } + } -void CloseAsyncAndLoop(uv_async_t* async) { - bool is_closed = false; - async->data = &is_closed; - uv_close(reinterpret_cast<uv_handle_t*>(async), HandleSyncCloseCb); - while (!is_closed) - uv_run(async->loop, UV_RUN_ONCE); - async->data = nullptr; - CheckedUvLoopClose(async->loop); -} + private: + TransportAction action_; + int session_id_; + std::unique_ptr<v8_inspector::StringBuffer> message_; +}; -// Delete main_thread_req_ on async handle close -void ReleasePairOnAsyncClose(uv_handle_t* async) { - std::unique_ptr<AsyncAndAgent> pair(node::ContainerOf(&AsyncAndAgent::first, - reinterpret_cast<uv_async_t*>(async))); - // Unique_ptr goes out of scope here and pointer is deleted. -} +class RequestQueueData { + public: + using MessageQueue = std::deque<RequestToServer>; + + explicit RequestQueueData(uv_loop_t* loop) + : handle_(std::make_shared<RequestQueue>(this)) { + int err = uv_async_init(loop, &async_, [](uv_async_t* async) { + RequestQueueData* wrapper = + node::ContainerOf(&RequestQueueData::async_, async); + wrapper->DoDispatch(); + }); + CHECK_EQ(0, err); + } + + static void CloseAndFree(RequestQueueData* queue); + + void Post(int session_id, + TransportAction action, + std::unique_ptr<StringBuffer> message) { + Mutex::ScopedLock scoped_lock(state_lock_); + bool notify = messages_.empty(); + messages_.emplace_back(action, session_id, std::move(message)); + if (notify) { + CHECK_EQ(0, uv_async_send(&async_)); + incoming_message_cond_.Broadcast(scoped_lock); + } + } + + void Wait() { + Mutex::ScopedLock scoped_lock(state_lock_); + if (messages_.empty()) { + incoming_message_cond_.Wait(scoped_lock); + } + } + + void SetServer(InspectorSocketServer* server) { + server_ = server; + } + std::shared_ptr<RequestQueue> handle() { + return handle_; + } + + private: + ~RequestQueueData() = default; + + MessageQueue GetMessages() { + Mutex::ScopedLock scoped_lock(state_lock_); + MessageQueue messages; + messages_.swap(messages); + return messages; + } + + void DoDispatch() { + if (server_ == nullptr) + return; + for (const auto& request : GetMessages()) { + request.Dispatch(server_); + } + } + + std::shared_ptr<RequestQueue> handle_; + uv_async_t async_; + InspectorSocketServer* server_ = nullptr; + MessageQueue messages_; + Mutex state_lock_; // Locked before mutating the queue. + ConditionVariable incoming_message_cond_; +}; } // namespace -std::unique_ptr<StringBuffer> Utf8ToStringView(const std::string& message) { - icu::UnicodeString utf16 = - icu::UnicodeString::fromUTF8(icu::StringPiece(message.data(), - message.length())); - StringView view(reinterpret_cast<const uint16_t*>(utf16.getBuffer()), - utf16.length()); - return StringBuffer::create(view); -} +class RequestQueue { + public: + explicit RequestQueue(RequestQueueData* data) : data_(data) {} + + void Reset() { + Mutex::ScopedLock scoped_lock(lock_); + data_ = nullptr; + } + void Post(int session_id, + TransportAction action, + std::unique_ptr<StringBuffer> message) { + Mutex::ScopedLock scoped_lock(lock_); + if (data_ != nullptr) + data_->Post(session_id, action, std::move(message)); + } + + void SetServer(InspectorSocketServer* server) { + Mutex::ScopedLock scoped_lock(lock_); + if (data_ != nullptr) + data_->SetServer(server); + } + + bool Expired() { + Mutex::ScopedLock scoped_lock(lock_); + return data_ == nullptr; + } + + private: + RequestQueueData* data_; + Mutex lock_; +}; class IoSessionDelegate : public InspectorSessionDelegate { public: - explicit IoSessionDelegate(InspectorIo* io, int id) : io_(io), id_(id) { } - void SendMessageToFrontend(const v8_inspector::StringView& message) override; + explicit IoSessionDelegate(std::shared_ptr<RequestQueue> queue, int id) + : request_queue_(queue), id_(id) { } + void SendMessageToFrontend(const v8_inspector::StringView& message) override { + request_queue_->Post(id_, TransportAction::kSendMessage, + StringBuffer::create(message)); + } + private: - InspectorIo* io_; + std::shared_ptr<RequestQueue> request_queue_; int id_; }; @@ -110,361 +210,133 @@ class IoSessionDelegate : public InspectorSessionDelegate { // mostly session start, message received, and session end. class InspectorIoDelegate: public node::inspector::SocketServerDelegate { public: - InspectorIoDelegate(InspectorIo* io, const std::string& target_id, + InspectorIoDelegate(std::shared_ptr<RequestQueueData> queue, + std::shared_ptr<MainThreadHandle> main_threade, + const std::string& target_id, const std::string& script_path, - const std::string& script_name, bool wait); + const std::string& script_name); ~InspectorIoDelegate() { - io_->ServerDone(); } - // Calls PostIncomingMessage() with appropriate InspectorAction: - // kStartSession + void StartSession(int session_id, const std::string& target_id) override; - // kSendMessage void MessageReceived(int session_id, const std::string& message) override; - // kEndSession void EndSession(int session_id) override; std::vector<std::string> GetTargetIds() override; std::string GetTargetTitle(const std::string& id) override; std::string GetTargetUrl(const std::string& id) override; - void AssignServer(InspectorSocketServer* server) override { - server_ = server; + request_queue_->SetServer(server); } private: - InspectorIo* io_; - int session_id_; + std::shared_ptr<RequestQueueData> request_queue_; + std::shared_ptr<MainThreadHandle> main_thread_; + std::unordered_map<int, std::unique_ptr<InspectorSession>> sessions_; const std::string script_name_; const std::string script_path_; const std::string target_id_; - bool waiting_; - InspectorSocketServer* server_; }; -void InterruptCallback(v8::Isolate*, void* agent) { - InspectorIo* io = static_cast<Agent*>(agent)->io(); - if (io != nullptr) - io->DispatchMessages(); -} - -class DispatchMessagesTask : public v8::Task { - public: - explicit DispatchMessagesTask(Agent* agent) : agent_(agent) {} - - void Run() override { - InspectorIo* io = agent_->io(); - if (io != nullptr) - io->DispatchMessages(); +// static +std::unique_ptr<InspectorIo> InspectorIo::Start( + std::shared_ptr<MainThreadHandle> main_thread, + const std::string& path, + const DebugOptions& options) { + auto io = std::unique_ptr<InspectorIo>( + new InspectorIo(main_thread, path, options)); + if (io->request_queue_->Expired()) { // Thread is not running + return nullptr; } - - private: - Agent* agent_; -}; - -InspectorIo::InspectorIo(Environment* env, const std::string& path, - const DebugOptions& options, bool wait_for_connect) - : options_(options), thread_(), state_(State::kNew), - parent_env_(env), thread_req_(), - platform_(parent_env_->isolate_data()->platform()), - dispatching_messages_(false), script_name_(path), - wait_for_connect_(wait_for_connect), port_(-1), - id_(GenerateID()) { - main_thread_req_ = new AsyncAndAgent({uv_async_t(), env->inspector_agent()}); - CHECK_EQ(0, uv_async_init(env->event_loop(), &main_thread_req_->first, - InspectorIo::MainThreadReqAsyncCb)); - uv_unref(reinterpret_cast<uv_handle_t*>(&main_thread_req_->first)); - CHECK_EQ(0, uv_sem_init(&thread_start_sem_, 0)); + return io; } -InspectorIo::~InspectorIo() { - uv_sem_destroy(&thread_start_sem_); - uv_close(reinterpret_cast<uv_handle_t*>(&main_thread_req_->first), - ReleasePairOnAsyncClose); -} - -bool InspectorIo::Start() { - CHECK_EQ(state_, State::kNew); +InspectorIo::InspectorIo(std::shared_ptr<MainThreadHandle> main_thread, + const std::string& path, + const DebugOptions& options) + : main_thread_(main_thread), options_(options), + thread_(), script_name_(path), id_(GenerateID()) { + Mutex::ScopedLock scoped_lock(thread_start_lock_); CHECK_EQ(uv_thread_create(&thread_, InspectorIo::ThreadMain, this), 0); - uv_sem_wait(&thread_start_sem_); - - if (state_ == State::kError) { - return false; - } - state_ = State::kAccepting; - if (wait_for_connect_) { - DispatchMessages(); - } - return true; + thread_start_condition_.Wait(scoped_lock); } -void InspectorIo::Stop() { - CHECK_IMPLIES(sessions_.empty(), state_ == State::kAccepting); - Write(TransportAction::kKill, 0, StringView()); +InspectorIo::~InspectorIo() { + request_queue_->Post(0, TransportAction::kKill, nullptr); int err = uv_thread_join(&thread_); CHECK_EQ(err, 0); - state_ = State::kShutDown; - DispatchMessages(); } -bool InspectorIo::IsStarted() { - return platform_ != nullptr; -} - -void InspectorIo::WaitForDisconnect() { - if (state_ == State::kAccepting) - state_ = State::kDone; - if (!sessions_.empty()) { - state_ = State::kShutDown; - Write(TransportAction::kStop, 0, StringView()); - fprintf(stderr, "Waiting for the debugger to disconnect...\n"); - fflush(stderr); - } +void InspectorIo::StopAcceptingNewConnections() { + request_queue_->Post(0, TransportAction::kStop, nullptr); } // static void InspectorIo::ThreadMain(void* io) { - static_cast<InspectorIo*>(io)->ThreadMain<InspectorSocketServer>(); -} - -// static -template <typename Transport> -void InspectorIo::IoThreadAsyncCb(uv_async_t* async) { - TransportAndIo<Transport>* transport_and_io = - static_cast<TransportAndIo<Transport>*>(async->data); - if (transport_and_io == nullptr) { - return; - } - Transport* transport = transport_and_io->first; - InspectorIo* io = transport_and_io->second; - MessageQueue<TransportAction> outgoing_message_queue; - io->SwapBehindLock(&io->outgoing_message_queue_, &outgoing_message_queue); - for (const auto& outgoing : outgoing_message_queue) { - int session_id = std::get<1>(outgoing); - switch (std::get<0>(outgoing)) { - case TransportAction::kKill: - transport->TerminateConnections(); - // Fallthrough - case TransportAction::kStop: - transport->Stop(); - break; - case TransportAction::kSendMessage: - transport->Send(session_id, - protocol::StringUtil::StringViewToUtf8( - std::get<2>(outgoing)->string())); - break; - case TransportAction::kAcceptSession: - transport->AcceptSession(session_id); - break; - case TransportAction::kDeclineSession: - transport->DeclineSession(session_id); - break; - } - } + static_cast<InspectorIo*>(io)->ThreadMain(); } -template <typename Transport> void InspectorIo::ThreadMain() { uv_loop_t loop; loop.data = nullptr; int err = uv_loop_init(&loop); CHECK_EQ(err, 0); - thread_req_.data = nullptr; - err = uv_async_init(&loop, &thread_req_, IoThreadAsyncCb<Transport>); - CHECK_EQ(err, 0); + std::shared_ptr<RequestQueueData> queue(new RequestQueueData(&loop), + RequestQueueData::CloseAndFree); std::string script_path = ScriptPath(&loop, script_name_); - auto delegate = std::unique_ptr<InspectorIoDelegate>( - new InspectorIoDelegate(this, id_, script_path, script_name_, - wait_for_connect_)); - Transport server(std::move(delegate), &loop, options_.host_name(), - options_.port()); - TransportAndIo<Transport> queue_transport(&server, this); - thread_req_.data = &queue_transport; - if (!server.Start()) { - state_ = State::kError; // Safe, main thread is waiting on semaphore - CloseAsyncAndLoop(&thread_req_); - uv_sem_post(&thread_start_sem_); - return; - } - port_ = server.Port(); // Safe, main thread is waiting on semaphore. - if (!wait_for_connect_) { - uv_sem_post(&thread_start_sem_); + std::unique_ptr<InspectorIoDelegate> delegate( + new InspectorIoDelegate(queue, main_thread_, id_, + script_path, script_name_)); + InspectorSocketServer server(std::move(delegate), &loop, + options_.host_name(), options_.port()); + request_queue_ = queue->handle(); + // Its lifetime is now that of the server delegate + queue.reset(); + { + Mutex::ScopedLock scoped_lock(thread_start_lock_); + if (server.Start()) { + port_ = server.Port(); + } + thread_start_condition_.Broadcast(scoped_lock); } uv_run(&loop, UV_RUN_DEFAULT); - thread_req_.data = nullptr; CheckedUvLoopClose(&loop); } -template <typename ActionType> -bool InspectorIo::AppendMessage(MessageQueue<ActionType>* queue, - ActionType action, int session_id, - std::unique_ptr<StringBuffer> buffer) { - Mutex::ScopedLock scoped_lock(state_lock_); - bool trigger_pumping = queue->empty(); - queue->push_back(std::make_tuple(action, session_id, std::move(buffer))); - return trigger_pumping; -} - -template <typename ActionType> -void InspectorIo::SwapBehindLock(MessageQueue<ActionType>* vector1, - MessageQueue<ActionType>* vector2) { - Mutex::ScopedLock scoped_lock(state_lock_); - vector1->swap(*vector2); -} - -void InspectorIo::PostIncomingMessage(InspectorAction action, int session_id, - const std::string& message) { - Debug(parent_env_, DebugCategory::INSPECTOR_SERVER, - ">>> %s\n", message.c_str()); - if (AppendMessage(&incoming_message_queue_, action, session_id, - Utf8ToStringView(message))) { - Agent* agent = main_thread_req_->second; - v8::Isolate* isolate = parent_env_->isolate(); - platform_->CallOnForegroundThread(isolate, - new DispatchMessagesTask(agent)); - isolate->RequestInterrupt(InterruptCallback, agent); - CHECK_EQ(0, uv_async_send(&main_thread_req_->first)); - } - Mutex::ScopedLock scoped_lock(state_lock_); - incoming_message_cond_.Broadcast(scoped_lock); -} - std::vector<std::string> InspectorIo::GetTargetIds() const { return { id_ }; } -TransportAction InspectorIo::Attach(int session_id) { - Agent* agent = parent_env_->inspector_agent(); - fprintf(stderr, "Debugger attached.\n"); - sessions_[session_id] = agent->Connect(std::unique_ptr<IoSessionDelegate>( - new IoSessionDelegate(this, session_id))); - return TransportAction::kAcceptSession; -} - -void InspectorIo::DispatchMessages() { - if (dispatching_messages_) - return; - dispatching_messages_ = true; - bool had_messages = false; - do { - if (dispatching_message_queue_.empty()) - SwapBehindLock(&incoming_message_queue_, &dispatching_message_queue_); - had_messages = !dispatching_message_queue_.empty(); - while (!dispatching_message_queue_.empty()) { - MessageQueue<InspectorAction>::value_type task; - std::swap(dispatching_message_queue_.front(), task); - dispatching_message_queue_.pop_front(); - int id = std::get<1>(task); - StringView message = std::get<2>(task)->string(); - switch (std::get<0>(task)) { - case InspectorAction::kStartSession: - Write(Attach(id), id, StringView()); - break; - case InspectorAction::kStartSessionUnconditionally: - Attach(id); - break; - case InspectorAction::kEndSession: - sessions_.erase(id); - if (!sessions_.empty()) - continue; - if (state_ == State::kShutDown) { - state_ = State::kDone; - } else { - state_ = State::kAccepting; - } - break; - case InspectorAction::kSendMessage: - auto session = sessions_.find(id); - if (session != sessions_.end() && session->second) { - session->second->Dispatch(message); - } - break; - } - } - } while (had_messages); - dispatching_messages_ = false; -} - -// static -void InspectorIo::MainThreadReqAsyncCb(uv_async_t* req) { - AsyncAndAgent* pair = node::ContainerOf(&AsyncAndAgent::first, req); - // Note that this may be called after io was closed or even after a new - // one was created and ran. - InspectorIo* io = pair->second->io(); - if (io != nullptr) - io->DispatchMessages(); -} - -void InspectorIo::Write(TransportAction action, int session_id, - const StringView& inspector_message) { - std::string message_str = - protocol::StringUtil::StringViewToUtf8(inspector_message); - Debug(parent_env_, DebugCategory::INSPECTOR_SERVER, - "<<< %s\n", message_str.c_str()); - AppendMessage(&outgoing_message_queue_, action, session_id, - StringBuffer::create(inspector_message)); - int err = uv_async_send(&thread_req_); - CHECK_EQ(0, err); -} - -bool InspectorIo::WaitForFrontendEvent() { - // We allow DispatchMessages reentry as we enter the pause. This is important - // to support debugging the code invoked by an inspector call, such - // as Runtime.evaluate - dispatching_messages_ = false; - Mutex::ScopedLock scoped_lock(state_lock_); - if (sessions_.empty()) - return false; - if (dispatching_message_queue_.empty() && incoming_message_queue_.empty()) { - incoming_message_cond_.Wait(scoped_lock); - } - return true; -} - -InspectorIoDelegate::InspectorIoDelegate(InspectorIo* io, - const std::string& target_id, - const std::string& script_path, - const std::string& script_name, - bool wait) - : io_(io), - session_id_(0), - script_name_(script_name), - script_path_(script_path), - target_id_(target_id), - waiting_(wait), - server_(nullptr) { } - +InspectorIoDelegate::InspectorIoDelegate( + std::shared_ptr<RequestQueueData> queue, + std::shared_ptr<MainThreadHandle> main_thread, + const std::string& target_id, + const std::string& script_path, + const std::string& script_name) + : request_queue_(queue), main_thread_(main_thread), + script_name_(script_name), script_path_(script_path), + target_id_(target_id) {} void InspectorIoDelegate::StartSession(int session_id, const std::string& target_id) { - session_id_ = session_id; - InspectorAction action = InspectorAction::kStartSession; - if (waiting_) { - action = InspectorAction::kStartSessionUnconditionally; - server_->AcceptSession(session_id); + auto session = main_thread_->Connect( + std::unique_ptr<InspectorSessionDelegate>( + new IoSessionDelegate(request_queue_->handle(), session_id)), true); + if (session) { + sessions_[session_id] = std::move(session); + fprintf(stderr, "Debugger attached.\n"); } - io_->PostIncomingMessage(action, session_id, ""); } void InspectorIoDelegate::MessageReceived(int session_id, const std::string& message) { - // TODO(pfeldman): Instead of blocking execution while debugger - // engages, node should wait for the run callback from the remote client - // and initiate its startup. This is a change to node.cc that should be - // upstreamed separately. - if (waiting_) { - if (message.find("\"Runtime.runIfWaitingForDebugger\"") != - std::string::npos) { - waiting_ = false; - io_->ResumeStartup(); - } - } - io_->PostIncomingMessage(InspectorAction::kSendMessage, session_id, - message); + auto session = sessions_.find(session_id); + if (session != sessions_.end()) + session->second->Dispatch(Utf8ToStringView(message)->string()); } void InspectorIoDelegate::EndSession(int session_id) { - io_->PostIncomingMessage(InspectorAction::kEndSession, session_id, ""); + sessions_.erase(session_id); } std::vector<std::string> InspectorIoDelegate::GetTargetIds() { @@ -479,10 +351,17 @@ std::string InspectorIoDelegate::GetTargetUrl(const std::string& id) { return "file://" + script_path_; } -void IoSessionDelegate::SendMessageToFrontend( - const v8_inspector::StringView& message) { - io_->Write(TransportAction::kSendMessage, id_, message); +// static +void RequestQueueData::CloseAndFree(RequestQueueData* queue) { + queue->handle_->Reset(); + queue->handle_.reset(); + uv_close(reinterpret_cast<uv_handle_t*>(&queue->async_), + [](uv_handle_t* handle) { + uv_async_t* async = reinterpret_cast<uv_async_t*>(handle); + RequestQueueData* wrapper = + node::ContainerOf(&RequestQueueData::async_, async); + delete wrapper; + }); } - } // namespace inspector } // namespace node diff --git a/src/inspector_io.h b/src/inspector_io.h index c897a44a528ec2..7c43d212f0422e 100644 --- a/src/inspector_io.h +++ b/src/inspector_io.h @@ -6,8 +6,6 @@ #include "node_mutex.h" #include "uv.h" -#include <deque> -#include <unordered_map> #include <memory> #include <stddef.h> @@ -16,17 +14,14 @@ #endif -// Forward declaration to break recursive dependency chain with src/env.h. -namespace node { -class Environment; -} // namespace node - namespace v8_inspector { class StringBuffer; class StringView; } // namespace v8_inspector namespace node { +// Forward declaration to break recursive dependency chain with src/env.h. +class Environment; namespace inspector { std::string FormatWsAddress(const std::string& host, int port, @@ -34,143 +29,64 @@ std::string FormatWsAddress(const std::string& host, int port, bool include_protocol); class InspectorIoDelegate; - -enum class InspectorAction { - kStartSession, - kStartSessionUnconditionally, // First attach with --inspect-brk - kEndSession, - kSendMessage -}; +class MainThreadHandle; +class RequestQueue; // kKill closes connections and stops the server, kStop only stops the server enum class TransportAction { kKill, kSendMessage, - kStop, - kAcceptSession, - kDeclineSession + kStop }; class InspectorIo { public: - InspectorIo(node::Environment* env, const std::string& path, - const DebugOptions& options, bool wait_for_connect); - + // Start the inspector agent thread, waiting for it to initialize + // bool Start(); + // Returns empty pointer if thread was not started + static std::unique_ptr<InspectorIo> Start( + std::shared_ptr<MainThreadHandle> main_thread, const std::string& path, + const DebugOptions& options); + + // Will block till the transport thread shuts down ~InspectorIo(); - // Start the inspector agent thread, waiting for it to initialize, - // and waiting as well for a connection if wait_for_connect. - bool Start(); - // Stop the inspector agent thread. - void Stop(); - - bool IsStarted(); - - void WaitForDisconnect(); - // Called from thread to queue an incoming message and trigger - // DispatchMessages() on the main thread. - void PostIncomingMessage(InspectorAction action, int session_id, - const std::string& message); - void ResumeStartup() { - uv_sem_post(&thread_start_sem_); - } - void ServerDone() { - uv_close(reinterpret_cast<uv_handle_t*>(&thread_req_), nullptr); - } - bool WaitForFrontendEvent(); - int port() const { return port_; } + void StopAcceptingNewConnections(); std::string host() const { return options_.host_name(); } + int port() const { return port_; } std::vector<std::string> GetTargetIds() const; private: - template <typename Action> - using MessageQueue = - std::deque<std::tuple<Action, int, - std::unique_ptr<v8_inspector::StringBuffer>>>; - enum class State { - kNew, - kAccepting, - kDone, - kError, - kShutDown - }; - - // Callback for main_thread_req_'s uv_async_t - static void MainThreadReqAsyncCb(uv_async_t* req); + InspectorIo(std::shared_ptr<MainThreadHandle> handle, + const std::string& path, const DebugOptions& options); // Wrapper for agent->ThreadMain() static void ThreadMain(void* agent); // Runs a uv_loop_t - template <typename Transport> void ThreadMain(); - // Called by ThreadMain's loop when triggered by thread_req_, writes - // messages from outgoing_message_queue to the InspectorSockerServer - template <typename Transport> static void IoThreadAsyncCb(uv_async_t* async); - - void DispatchMessages(); - // Write action to outgoing_message_queue, and wake the thread - void Write(TransportAction action, int session_id, - const v8_inspector::StringView& message); - // Thread-safe append of message to a queue. Return true if the queue - // used to be empty. - template <typename ActionType> - bool AppendMessage(MessageQueue<ActionType>* vector, ActionType action, - int session_id, - std::unique_ptr<v8_inspector::StringBuffer> buffer); - // Used as equivalent of a thread-safe "pop" of an entire queue's content. - template <typename ActionType> - void SwapBehindLock(MessageQueue<ActionType>* vector1, - MessageQueue<ActionType>* vector2); - // Attach session to an inspector. Either kAcceptSession or kDeclineSession - TransportAction Attach(int session_id); - + void ThreadMain(); + + // This is a thread-safe object that will post async tasks. It lives as long + // as an Inspector object lives (almost as long as an Isolate). + std::shared_ptr<MainThreadHandle> main_thread_; + // Used to post on a frontend interface thread, lives while the server is + // running + std::shared_ptr<RequestQueue> request_queue_; const DebugOptions options_; // The IO thread runs its own uv_loop to implement the TCP server off // the main thread. uv_thread_t thread_; - // Used by Start() to wait for thread to initialize, or for it to initialize - // and receive a connection if wait_for_connect was requested. - uv_sem_t thread_start_sem_; - - State state_; - node::Environment* parent_env_; - - // Attached to the uv_loop in ThreadMain() - uv_async_t thread_req_; - // Note that this will live while the async is being closed - likely, past - // the parent object lifespan - std::pair<uv_async_t, Agent*>* main_thread_req_; - // Will be used to post tasks from another thread - v8::Platform* const platform_; - - // Message queues - ConditionVariable incoming_message_cond_; - Mutex state_lock_; // Locked before mutating either queue. - MessageQueue<InspectorAction> incoming_message_queue_; - MessageQueue<TransportAction> outgoing_message_queue_; - // This queue is to maintain the order of the messages for the cases - // when we reenter the DispatchMessages function. - MessageQueue<InspectorAction> dispatching_message_queue_; - - bool dispatching_messages_; + // For setting up interthread communications + Mutex thread_start_lock_; + ConditionVariable thread_start_condition_; std::string script_name_; - std::string script_path_; - const bool wait_for_connect_; - int port_; - std::unordered_map<int, std::unique_ptr<InspectorSession>> sessions_; + int port_ = -1; // May be accessed from any thread const std::string id_; - - friend class DispatchMessagesTask; - friend class IoSessionDelegate; - friend void InterruptCallback(v8::Isolate*, void* agent); }; -std::unique_ptr<v8_inspector::StringBuffer> Utf8ToStringView( - const std::string& message); - } // namespace inspector } // namespace node diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 13e91f283c912a..268c25aeb4a233 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -66,7 +66,7 @@ class JSBindingsConnection : public AsyncWrap { callback_(env->isolate(), callback) { Agent* inspector = env->inspector_agent(); session_ = inspector->Connect(std::unique_ptr<JSBindingsSessionDelegate>( - new JSBindingsSessionDelegate(env, this))); + new JSBindingsSessionDelegate(env, this)), false); } void OnMessage(Local<Value> value) { @@ -116,7 +116,7 @@ class JSBindingsConnection : public AsyncWrap { static bool InspectorEnabled(Environment* env) { Agent* agent = env->inspector_agent(); - return agent->io() != nullptr || agent->HasConnectedSessions(); + return agent->IsActive(); } void AddCommandLineAPI(const FunctionCallbackInfo<Value>& info) { @@ -251,8 +251,9 @@ void Open(const FunctionCallbackInfo<Value>& args) { if (args.Length() > 2 && args[2]->IsBoolean()) { wait_for_connect = args[2]->BooleanValue(env->context()).FromJust(); } - - agent->StartIoThread(wait_for_connect); + agent->StartIoThread(); + if (wait_for_connect) + agent->WaitForConnect(); } void Url(const FunctionCallbackInfo<Value>& args) { @@ -283,7 +284,7 @@ void Initialize(Local<Object> target, Local<Value> unused, Agent* agent = env->inspector_agent(); env->SetMethod(target, "consoleCall", InspectorConsoleCall); env->SetMethod(target, "addCommandLineAPI", AddCommandLineAPI); - if (agent->IsWaitingForConnect()) + if (agent->WillWaitForConnect()) env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart); env->SetMethod(target, "open", Open); env->SetMethod(target, "url", Url); diff --git a/src/inspector_socket_server.cc b/src/inspector_socket_server.cc index 174dc7c726f21c..1621b408b43274 100644 --- a/src/inspector_socket_server.cc +++ b/src/inspector_socket_server.cc @@ -173,11 +173,8 @@ class SocketSession { InspectorSocket* ws_socket() { return ws_socket_.get(); } - void set_ws_key(const std::string& ws_key) { - ws_key_ = ws_key; - } - void Accept() { - ws_socket_->AcceptUpgrade(ws_key_); + void Accept(const std::string& ws_key) { + ws_socket_->AcceptUpgrade(ws_key); } void Decline() { ws_socket_->CancelHandshake(); @@ -208,7 +205,6 @@ class SocketSession { const int id_; InspectorSocket::Pointer ws_socket_; const int server_port_; - std::string ws_key_; }; class ServerSocket { @@ -260,11 +256,11 @@ void InspectorSocketServer::SessionStarted(int session_id, const std::string& ws_key) { SocketSession* session = Session(session_id); if (!TargetExists(id)) { - Session(session_id)->Decline(); + session->Decline(); return; } connected_sessions_[session_id].first = id; - session->set_ws_key(ws_key); + session->Accept(ws_key); delegate_->StartSession(session_id, id); } @@ -404,6 +400,8 @@ bool InspectorSocketServer::Start() { } void InspectorSocketServer::Stop() { + if (state_ == ServerState::kStopped) + return; CHECK_EQ(state_, ServerState::kRunning); state_ = ServerState::kStopped; server_sockets_.clear(); @@ -446,23 +444,6 @@ void InspectorSocketServer::Accept(int server_port, } } -void InspectorSocketServer::AcceptSession(int session_id) { - SocketSession* session = Session(session_id); - if (session == nullptr) { - delegate_->EndSession(session_id); - } else { - session->Accept(); - } -} - -void InspectorSocketServer::DeclineSession(int session_id) { - auto it = connected_sessions_.find(session_id); - if (it != connected_sessions_.end()) { - it->second.first.clear(); - it->second.second->Decline(); - } -} - void InspectorSocketServer::Send(int session_id, const std::string& message) { SocketSession* session = Session(session_id); if (session != nullptr) { diff --git a/src/inspector_socket_server.h b/src/inspector_socket_server.h index bbc2195095772f..271be6ec555bf1 100644 --- a/src/inspector_socket_server.h +++ b/src/inspector_socket_server.h @@ -54,10 +54,6 @@ class InspectorSocketServer { void Send(int session_id, const std::string& message); // kKill void TerminateConnections(); - // kAcceptSession - void AcceptSession(int session_id); - // kDeclineSession - void DeclineSession(int session_id); int Port() const; // Session connection lifecycle diff --git a/src/node.cc b/src/node.cc index bbf76853ed3c8f..a9318cb82b47c5 100644 --- a/src/node.cc +++ b/src/node.cc @@ -323,11 +323,12 @@ static struct { // Inspector agent can't fail to start, but if it was configured to listen // right away on the websocket port and fails to bind/etc, this will return // false. - return env->inspector_agent()->Start(script_path, options); + return env->inspector_agent()->Start( + script_path == nullptr ? "" : script_path, options); } bool InspectorStarted(Environment* env) { - return env->inspector_agent()->IsStarted(); + return env->inspector_agent()->IsListening(); } #endif // HAVE_INSPECTOR @@ -1510,7 +1511,7 @@ void InitGroups(const FunctionCallbackInfo<Value>& args) { static void WaitForInspectorDisconnect(Environment* env) { #if HAVE_INSPECTOR - if (env->inspector_agent()->HasConnectedSessions()) { + if (env->inspector_agent()->IsActive()) { // Restore signal dispositions, the app is done and is no longer // capable of handling signals. #if defined(__POSIX__) && !defined(NODE_SHARED_MODE) @@ -3517,7 +3518,7 @@ static void ParseArgs(int* argc, static void StartInspector(Environment* env, const char* path, DebugOptions debug_options) { #if HAVE_INSPECTOR - CHECK(!env->inspector_agent()->IsStarted()); + CHECK(!env->inspector_agent()->IsListening()); v8_platform.StartInspector(env, path, debug_options); #endif // HAVE_INSPECTOR } @@ -3660,7 +3661,7 @@ static void DebugProcess(const FunctionCallbackInfo<Value>& args) { static void DebugEnd(const FunctionCallbackInfo<Value>& args) { #if HAVE_INSPECTOR Environment* env = Environment::GetCurrent(args); - if (env->inspector_agent()->IsStarted()) { + if (env->inspector_agent()->IsListening()) { env->inspector_agent()->Stop(); } #endif diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc index 346f442f61df0f..f44b232a1a4ad2 100644 --- a/src/signal_wrap.cc +++ b/src/signal_wrap.cc @@ -92,7 +92,7 @@ class SignalWrap : public HandleWrap { #if defined(__POSIX__) && HAVE_INSPECTOR if (signum == SIGPROF) { Environment* env = Environment::GetCurrent(args); - if (env->inspector_agent()->IsStarted()) { + if (env->inspector_agent()->IsListening()) { ProcessEmitWarning(env, "process.on(SIGPROF) is reserved while debugging"); return; diff --git a/test/cctest/test_inspector_socket_server.cc b/test/cctest/test_inspector_socket_server.cc index 60b7eefc5e997f..349356ef56c9fc 100644 --- a/test/cctest/test_inspector_socket_server.cc +++ b/test/cctest/test_inspector_socket_server.cc @@ -14,7 +14,6 @@ static const char CLIENT_CLOSE_FRAME[] = "\x88\x80\x2D\x0E\x1E\xFA"; static const char SERVER_CLOSE_FRAME[] = "\x88\x00"; static const char MAIN_TARGET_ID[] = "main-target"; -static const char UNCONNECTABLE_TARGET_ID[] = "unconnectable-target"; static const char WS_HANDSHAKE_RESPONSE[] = "HTTP/1.1 101 Switching Protocols\r\n" @@ -258,10 +257,6 @@ class ServerHolder { return server_->done(); } - void Connected() { - connected++; - } - void Disconnected() { disconnected++; } @@ -270,9 +265,10 @@ class ServerHolder { delegate_done = true; } - void PrepareSession(int id) { + void Connected(int id) { buffer_.clear(); session_id_ = id; + connected++; } void Received(const std::string& message) { @@ -319,15 +315,9 @@ class TestSocketServerDelegate : public SocketServerDelegate { void StartSession(int session_id, const std::string& target_id) override { session_id_ = session_id; - harness_->PrepareSession(session_id_); CHECK_NE(targets_.end(), std::find(targets_.begin(), targets_.end(), target_id)); - if (target_id == UNCONNECTABLE_TARGET_ID) { - server_->DeclineSession(session_id); - return; - } - harness_->Connected(); - server_->AcceptSession(session_id); + harness_->Connected(session_id_); } void MessageReceived(int session_id, const std::string& message) override { @@ -363,7 +353,7 @@ ServerHolder::ServerHolder(bool has_targets, uv_loop_t* loop, const std::string host, int port, FILE* out) { std::vector<std::string> targets; if (has_targets) - targets = { MAIN_TARGET_ID, UNCONNECTABLE_TARGET_ID }; + targets = { MAIN_TARGET_ID }; std::unique_ptr<TestSocketServerDelegate> delegate( new TestSocketServerDelegate(this, targets)); server_.reset( @@ -414,15 +404,6 @@ TEST_F(InspectorSocketServerTest, InspectorSessions) { well_behaved_socket.Close(); - // Declined connection - SocketWrapper declined_target_socket(&loop); - declined_target_socket.Connect(HOST, server.port()); - declined_target_socket.Write(WsHandshakeRequest(UNCONNECTABLE_TARGET_ID)); - declined_target_socket.Expect("HTTP/1.0 400 Bad Request"); - declined_target_socket.ExpectEOF(); - EXPECT_EQ(1, server.connected); - EXPECT_EQ(1, server.disconnected); - // Bogus target - start session callback should not even be invoked SocketWrapper bogus_target_socket(&loop); bogus_target_socket.Connect(HOST, server.port()); @@ -491,7 +472,7 @@ TEST_F(InspectorSocketServerTest, ServerWithoutTargets) { // Declined connection SocketWrapper socket(&loop); socket.Connect(HOST, server.port()); - socket.Write(WsHandshakeRequest(UNCONNECTABLE_TARGET_ID)); + socket.Write(WsHandshakeRequest("any target id")); socket.Expect("HTTP/1.0 400 Bad Request"); socket.ExpectEOF(); server->Stop(); diff --git a/test/parallel/test-warn-sigprof.js b/test/parallel/test-warn-sigprof.js index 71ac25443bc266..e5335215d743b6 100644 --- a/test/parallel/test-warn-sigprof.js +++ b/test/parallel/test-warn-sigprof.js @@ -1,3 +1,4 @@ +// Flags: --inspect=0 'use strict'; const common = require('../common'); From ae5d5658b91cdac4fe55b9e5e8c8e275449b4635 Mon Sep 17 00:00:00 2001 From: Rich Trott <rtrott@gmail.com> Date: Wed, 11 Jul 2018 20:04:28 -0700 Subject: [PATCH 067/116] test: fix flaky watchFile() The regression that test-fs-watch-file-enoent-after-deletion was written to test for involves whether or not the callback runs or not. Checking what the file watcher reports unfortunately is subject to race conditions on Windows (and possibly elsewhere) because the file watcher returns control to the event loop before it may be receiving data from the OS. So remove those assertions. The test still checks what it is supposed to check, but is no longer subject to race conditions. Fixes: https://github.com/nodejs/node/issues/21692 PR-URL: https://github.com/nodejs/node/pull/21694 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- .../test-fs-watch-file-enoent-after-deletion.js | 3 --- 1 file changed, 3 deletions(-) rename test/{sequential => parallel}/test-fs-watch-file-enoent-after-deletion.js (94%) diff --git a/test/sequential/test-fs-watch-file-enoent-after-deletion.js b/test/parallel/test-fs-watch-file-enoent-after-deletion.js similarity index 94% rename from test/sequential/test-fs-watch-file-enoent-after-deletion.js rename to test/parallel/test-fs-watch-file-enoent-after-deletion.js index f5b9012acabe58..83c7516c9c6ca9 100644 --- a/test/sequential/test-fs-watch-file-enoent-after-deletion.js +++ b/test/parallel/test-fs-watch-file-enoent-after-deletion.js @@ -32,7 +32,6 @@ const common = require('../common'); // stopped it from getting emitted. // https://github.com/nodejs/node-v0.x-archive/issues/4027 -const assert = require('assert'); const path = require('path'); const fs = require('fs'); @@ -43,8 +42,6 @@ const filename = path.join(tmpdir.path, 'watched'); fs.writeFileSync(filename, 'quis custodiet ipsos custodes'); fs.watchFile(filename, { interval: 50 }, common.mustCall(function(curr, prev) { - assert.strictEqual(prev.nlink, 1); - assert.strictEqual(curr.nlink, 0); fs.unwatchFile(filename); })); From 15026511b857b5d9ba8050ecea022fb908eca04b Mon Sep 17 00:00:00 2001 From: Rich Trott <rtrott@gmail.com> Date: Thu, 12 Jul 2018 19:47:52 -0700 Subject: [PATCH 068/116] test: remove timer in fs.watchFile() test The timer was there to address a race condition that has been removed from the test. Remove it. PR-URL: https://github.com/nodejs/node/pull/21694 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- test/parallel/test-fs-watch-file-enoent-after-deletion.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-fs-watch-file-enoent-after-deletion.js b/test/parallel/test-fs-watch-file-enoent-after-deletion.js index 83c7516c9c6ca9..6eaa4c68c58bc8 100644 --- a/test/parallel/test-fs-watch-file-enoent-after-deletion.js +++ b/test/parallel/test-fs-watch-file-enoent-after-deletion.js @@ -45,4 +45,4 @@ fs.watchFile(filename, { interval: 50 }, common.mustCall(function(curr, prev) { fs.unwatchFile(filename); })); -setTimeout(fs.unlinkSync, common.platformTimeout(300), filename); +fs.unlinkSync(filename); From 0de0f89d0c524049045ca5722ece8ecbc5f86ee7 Mon Sep 17 00:00:00 2001 From: Rich Trott <rtrott@gmail.com> Date: Sat, 7 Jul 2018 15:33:06 -0700 Subject: [PATCH 069/116] doc: add "Edit on GitHub" link Proof of concept for an "Edit on GitHub" link, inspired by the Serverless docs. One issue is that the link is to the version of the docs on the master branch even if the person was reading a different version of the doc. I don't consider that a big problem, although we can always remove the link if it turns out to be a big problem. I don't think there is a good solution. PRs need to be opened against the master branch generally. Having a bunch of PRs against staging branches is probably not what we want. If there's an update to one version of the doc, there will usually be an update to other versions. PR-URL: https://github.com/nodejs/node/pull/21703 Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Yuta Hiroto <hello@hiroppy.me> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: Roman Reiss <me@silverwind.io> --- doc/api_assets/style.css | 5 +++++ doc/template.html | 1 + tools/doc/allhtml.js | 3 ++- tools/doc/html.js | 8 ++++++++ 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/doc/api_assets/style.css b/doc/api_assets/style.css index b4da3375d5b521..f59f3770048097 100644 --- a/doc/api_assets/style.css +++ b/doc/api_assets/style.css @@ -504,6 +504,11 @@ th > *:last-child, td > *:last-child { visibility: hidden; } +.github_icon { + vertical-align: middle; + margin: -2px 3px 0 0; +} + @media only screen and (max-width: 1024px) { #content { overflow: visible; diff --git a/doc/template.html b/doc/template.html index bb3e2bf8b4f2f0..fdcf0d5821b56f 100644 --- a/doc/template.html +++ b/doc/template.html @@ -35,6 +35,7 @@ <h1>Node.js __VERSION__ Documentation</h1> <a href="__FILENAME__.json">View as JSON</a> </li> __ALTDOCS__ + __EDIT_ON_GITHUB__ </ul> </div> <hr> diff --git a/tools/doc/allhtml.js b/tools/doc/allhtml.js index 1c84e13d0ab79c..d185538ab683b6 100644 --- a/tools/doc/allhtml.js +++ b/tools/doc/allhtml.js @@ -52,7 +52,8 @@ let all = toc.replace(/index\.html/g, 'all.html') .replace('<a href="all.html" name="toc">', '<a href="index.html" name="toc">') .replace('index.json', 'all.json') .replace('api-section-index', 'api-section-all') - .replace('data-id="index"', 'data-id="all"'); + .replace('data-id="index"', 'data-id="all"') + .replace(/<li class="edit_on_github">.*?<\/li>/, ''); // Clean up the title. all = all.replace(/<title>.*?\| /, '<title>'); diff --git a/tools/doc/html.js b/tools/doc/html.js index 0e254f1203f7a6..0f3293dadd51d1 100644 --- a/tools/doc/html.js +++ b/tools/doc/html.js @@ -96,6 +96,8 @@ function toHTML({ input, filename, nodeVersion, analytics }, cb) { HTML = HTML.replace('__ALTDOCS__', ''); } + HTML = HTML.replace('__EDIT_ON_GITHUB__', editOnGitHub(filename)); + // Content insertion has to be the last thing we do with the lexed tokens, // because it's destructive. HTML = HTML.replace('__CONTENT__', marked.parser(lexed)); @@ -377,3 +379,9 @@ function altDocs(filename, docCreated) { </li> ` : ''; } + +// eslint-disable-next-line max-len +const githubLogo = '<span class="github_icon"><svg height="16" width="16" viewBox="0 0 16.1 16.1" fill="currentColor"><path d="M8 0a8 8 0 0 0-2.5 15.6c.4 0 .5-.2.5-.4v-1.5c-2 .4-2.5-.5-2.7-1 0-.1-.5-.9-.8-1-.3-.2-.7-.6 0-.6.6 0 1 .6 1.2.8.7 1.2 1.9 1 2.4.7 0-.5.2-.9.5-1-1.8-.3-3.7-1-3.7-4 0-.9.3-1.6.8-2.2 0-.2-.3-1 .1-2 0 0 .7-.3 2.2.7a7.4 7.4 0 0 1 4 0c1.5-1 2.2-.8 2.2-.8.5 1.1.2 2 .1 2.1.5.6.8 1.3.8 2.2 0 3-1.9 3.7-3.6 4 .3.2.5.7.5 1.4v2.2c0 .2.1.5.5.4A8 8 0 0 0 16 8a8 8 0 0 0-8-8z"/></svg></span>'; +function editOnGitHub(filename) { + return `<li class="edit_on_github"><a href="https://github.com/nodejs/node/edit/master/doc/api/${filename}.md">${githubLogo}Edit on GitHub</a></li>`; +} From cb698111c487f0ecba43515d1ff0ea67377ca912 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Wed, 11 Jul 2018 01:28:30 +0200 Subject: [PATCH 070/116] src: add comment on CallbackScope exception behaviour PR-URL: https://github.com/nodejs/node/pull/21743 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Shelley Vohr <codebytere@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- src/node.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/node.h b/src/node.h index ed2c074b922687..305d605ef4a33a 100644 --- a/src/node.h +++ b/src/node.h @@ -683,6 +683,10 @@ class InternalCallbackScope; * * This object should be stack-allocated to ensure that it is contained in a * valid HandleScope. + * + * Exceptions happening within this scope will be treated like uncaught + * exceptions. If this behaviour is undesirable, a new `v8::TryCatch` scope + * needs to be created inside of this scope. */ class NODE_EXTERN CallbackScope { public: From aa5994f2b94d109fc6ad200ff3b638e8a83eea5c Mon Sep 17 00:00:00 2001 From: XhmikosR <xhmikosr@gmail.com> Date: Mon, 9 Jul 2018 18:01:13 +0300 Subject: [PATCH 071/116] src,tools: use https://nodejs.org URL when possible. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/21719 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Jon Moss <me@jonathanmoss.me> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- src/node_buffer.cc | 2 +- src/string_bytes.cc | 4 ++-- tools/msvs/msi/product.wxs | 2 +- tools/pkgsrc/description | 2 +- tools/rpm/node.spec | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 9465145ac37eb9..f6ebf82524d0a8 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -483,7 +483,7 @@ void StringSlice<UCS2>(const FunctionCallbackInfo<Value>& args) { bool release = false; // Node's "ucs2" encoding expects LE character data inside a Buffer, so we - // need to reorder on BE platforms. See http://nodejs.org/api/buffer.html + // need to reorder on BE platforms. See https://nodejs.org/api/buffer.html // regarding Node's "ucs2" encoding specification. const bool aligned = (reinterpret_cast<uintptr_t>(data) % sizeof(*buf) == 0); if (IsLittleEndian() && !aligned) { diff --git a/src/string_bytes.cc b/src/string_bytes.cc index 0bc2ec044ebb55..83c74d2f182021 100644 --- a/src/string_bytes.cc +++ b/src/string_bytes.cc @@ -343,7 +343,7 @@ size_t StringBytes::Write(Isolate* isolate, // Node's "ucs2" encoding wants LE character data stored in // the Buffer, so we need to reorder on BE platforms. See - // http://nodejs.org/api/buffer.html regarding Node's "ucs2" + // https://nodejs.org/api/buffer.html regarding Node's "ucs2" // encoding specification if (IsBigEndian()) SwapBytes16(buf, nbytes); @@ -709,7 +709,7 @@ MaybeLocal<Value> StringBytes::Encode(Isolate* isolate, // Node's "ucs2" encoding expects LE character data inside a // Buffer, so we need to reorder on BE platforms. See - // http://nodejs.org/api/buffer.html regarding Node's "ucs2" + // https://nodejs.org/api/buffer.html regarding Node's "ucs2" // encoding specification if (IsBigEndian()) { uint16_t* dst = node::UncheckedMalloc<uint16_t>(buflen); diff --git a/tools/msvs/msi/product.wxs b/tools/msvs/msi/product.wxs index c8a89d725509db..e9e4e33751d254 100755 --- a/tools/msvs/msi/product.wxs +++ b/tools/msvs/msi/product.wxs @@ -254,7 +254,7 @@ KeyPath="yes"/> <util:InternetShortcut Id="WebsiteShortcut" Name="Node.js website" - Target="http://nodejs.org" + Target="https://nodejs.org/" Type="url"/> <util:InternetShortcut Id="DocsShortcut" Name="Node.js documentation" diff --git a/tools/pkgsrc/description b/tools/pkgsrc/description index 1cf2a08512e67f..b070f52fb6b092 100644 --- a/tools/pkgsrc/description +++ b/tools/pkgsrc/description @@ -4,4 +4,4 @@ intended for writing scalable network programs such as web servers. Packaged by nodejs.org Homepage: -http://nodejs.org/ +https://nodejs.org/ diff --git a/tools/rpm/node.spec b/tools/rpm/node.spec index 39b98ec5541c53..83c7b67b2a39a5 100644 --- a/tools/rpm/node.spec +++ b/tools/rpm/node.spec @@ -22,7 +22,7 @@ Summary: Node.js is a platform for building fast, scalable network applications. Group: Development/Languages License: MIT URL: https://nodejs.org/ -Source0: http://nodejs.org/dist/v%{_version}/node-v%{_version}.tar.gz +Source0: https://nodejs.org/dist/v%{_version}/node-v%{_version}.tar.gz BuildRequires: gcc BuildRequires: gcc-c++ BuildRequires: glibc-devel From 961f6e8623ac297e16d40ba0ac1cc56b91754773 Mon Sep 17 00:00:00 2001 From: Denys Otrishko <shishugi@gmail.com> Date: Tue, 10 Jul 2018 18:44:16 +0300 Subject: [PATCH 072/116] process: fix process.exitCode handling for fatalException * set process.exitCode before calling 'exit' handlers so that there will not be a situation where process.exitCode !== code in 'exit' callback during uncaughtException handling * don't ignore process.exitCode set in 'exit' callback when failed with uncaughtException and there is no uncaughtException listener PR-URL: https://github.com/nodejs/node/pull/21739 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- doc/api/process.md | 8 ++- lib/internal/bootstrap/node.js | 1 + lib/internal/worker.js | 3 - src/node.cc | 11 +++- test/parallel/test-process-exit-code.js | 56 ++++++++++++++++++- test/parallel/test-worker-exit-code.js | 28 +++++++++- .../test-worker-uncaught-exception.js | 10 ++++ 7 files changed, 109 insertions(+), 8 deletions(-) diff --git a/doc/api/process.md b/doc/api/process.md index ebd84afd698872..80b70ae1151370 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -151,9 +151,13 @@ added: v0.1.18 The `'uncaughtException'` event is emitted when an uncaught JavaScript exception bubbles all the way back to the event loop. By default, Node.js -handles such exceptions by printing the stack trace to `stderr` and exiting. +handles such exceptions by printing the stack trace to `stderr` and exiting +with code 1, overriding any previously set [`process.exitCode`][]. Adding a handler for the `'uncaughtException'` event overrides this default -behavior. +behavior. You may also change the [`process.exitCode`][] in +`'uncaughtException'` handler which will result in process exiting with +provided exit code, otherwise in the presence of such handler the process will +exit with 0. The listener function is called with the `Error` object passed as the only argument. diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index df5d667aeb7b39..4256de80c2e8f5 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -449,6 +449,7 @@ try { if (!process._exiting) { process._exiting = true; + process.exitCode = 1; process.emit('exit', 1); } } catch { diff --git a/lib/internal/worker.js b/lib/internal/worker.js index 83389d204d285f..bcc864b5b8b330 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -454,9 +454,6 @@ function setupChild(evalScript) { debug(`[${threadId}] fatal exception caught = ${caught}`); if (!caught) { - // set correct code (uncaughtException) for [kOnExit](code) handler - process.exitCode = 1; - let serialized; try { serialized = serializeError(error); diff --git a/src/node.cc b/src/node.cc index a9318cb82b47c5..e2d9b426d0cf97 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1952,7 +1952,16 @@ void FatalException(Isolate* isolate, exit(7); } else if (caught->IsFalse()) { ReportException(env, error, message); - exit(1); + + // fatal_exception_function call before may have set a new exit code -> + // read it again, otherwise use default for uncaughtException 1 + Local<String> exit_code = env->exit_code_string(); + Local<Value> code; + if (!process_object->Get(env->context(), exit_code).ToLocal(&code) || + !code->IsInt32()) { + exit(1); + } + exit(code.As<v8::Int32>()->Value()); } } } diff --git a/test/parallel/test-process-exit-code.js b/test/parallel/test-process-exit-code.js index f5f8099c8d2439..20004a9d7d6f58 100644 --- a/test/parallel/test-process-exit-code.js +++ b/test/parallel/test-process-exit-code.js @@ -34,6 +34,14 @@ switch (process.argv[2]) { return child4(); case 'child5': return child5(); + case 'child6': + return child6(); + case 'child7': + return child7(); + case 'child8': + return child8(); + case 'child9': + return child9(); case undefined: return parent(); default: @@ -43,6 +51,7 @@ switch (process.argv[2]) { function child1() { process.exitCode = 42; process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 42); assert.strictEqual(code, 42); }); } @@ -50,6 +59,7 @@ function child1() { function child2() { process.exitCode = 99; process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 42); assert.strictEqual(code, 42); }); process.exit(42); @@ -58,6 +68,7 @@ function child2() { function child3() { process.exitCode = 99; process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 0); assert.strictEqual(code, 0); }); process.exit(0); @@ -66,7 +77,7 @@ function child3() { function child4() { process.exitCode = 99; process.on('exit', function(code) { - if (code !== 1) { + if (code !== 1 || process.exitCode !== 1) { console.log('wrong code! expected 1 for uncaughtException'); process.exit(99); } @@ -77,11 +88,50 @@ function child4() { function child5() { process.exitCode = 95; process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 95); assert.strictEqual(code, 95); process.exitCode = 99; }); } +function child6() { + process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 0); + assert.strictEqual(code, 0); + }); + process.on('uncaughtException', () => {}); + throw new Error('ok'); +} + +function child7() { + process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 97); + assert.strictEqual(code, 97); + }); + process.on('uncaughtException', () => { + process.exitCode = 97; + }); + throw new Error('ok'); +} + +function child8() { + process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 1); + assert.strictEqual(code, 1); + process.exitCode = 98; + }); + throw new Error('ok'); +} + +function child9() { + process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 1); + assert.strictEqual(code, 1); + process.exitCode = 0; + }); + throw new Error('ok'); +} + function parent() { const { spawn } = require('child_process'); const node = process.execPath; @@ -102,4 +152,8 @@ function parent() { test('child3', 0); test('child4', 1); test('child5', 99); + test('child6', 0); + test('child7', 97); + test('child8', 98); + test('child9', 0); } diff --git a/test/parallel/test-worker-exit-code.js b/test/parallel/test-worker-exit-code.js index b621389b49ca6b..0a1e9bb95370eb 100644 --- a/test/parallel/test-worker-exit-code.js +++ b/test/parallel/test-worker-exit-code.js @@ -35,6 +35,10 @@ if (!process.env.HAS_STARTED_WORKER) { return child6(); case 'child7': return child7(); + case 'child8': + return child8(); + case 'child9': + return child9(); default: throw new Error('invalid'); } @@ -105,6 +109,24 @@ function child7() { throw new Error('ok'); } +function child8() { + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 1); + assert.strictEqual(code, 1); + process.exitCode = 98; + }); + throw new Error('ok'); +} + +function child9() { + process.on('exit', function(code) { + assert.strictEqual(process.exitCode, 1); + assert.strictEqual(code, 1); + process.exitCode = 0; + }); + throw new Error('ok'); +} + function parent() { const test = (arg, exit, error = null) => { const w = new Worker(__filename); @@ -116,7 +138,9 @@ function parent() { })); if (error) { w.on('error', common.mustCall((err) => { - assert(error.test(err)); + console.log(err); + assert(error.test(err), + `wrong error for ${arg}\nexpected:${error} but got:${err}`); })); } w.postMessage(arg); @@ -129,4 +153,6 @@ function parent() { test('child5', 99); test('child6', 0); test('child7', 97); + test('child8', 98, /^Error: ok$/); + test('child9', 0, /^Error: ok$/); } diff --git a/test/parallel/test-worker-uncaught-exception.js b/test/parallel/test-worker-uncaught-exception.js index 67b861e22619aa..8193dccbd1ff2f 100644 --- a/test/parallel/test-worker-uncaught-exception.js +++ b/test/parallel/test-worker-uncaught-exception.js @@ -10,6 +10,7 @@ if (!process.env.HAS_STARTED_WORKER) { const w = new Worker(__filename); w.on('message', common.mustNotCall()); w.on('error', common.mustCall((err) => { + console.log(err.message); assert(/^Error: foo$/.test(err)); })); w.on('exit', common.mustCall((code) => { @@ -17,5 +18,14 @@ if (!process.env.HAS_STARTED_WORKER) { assert.strictEqual(code, 1); })); } else { + // cannot use common.mustCall as it cannot catch this + let called = false; + process.on('exit', (code) => { + if (!called) { + called = true; + } else { + assert.fail('Exit callback called twice in worker'); + } + }); throw new Error('foo'); } From 600349aaba936c644e42ab41098a43c8c7f0fd1a Mon Sep 17 00:00:00 2001 From: Denys Otrishko <shishugi@gmail.com> Date: Wed, 11 Jul 2018 00:58:01 +0300 Subject: [PATCH 073/116] test: refactor process/worker exitCode tests PR-URL: https://github.com/nodejs/node/pull/21739 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- test/fixtures/process-exit-code-cases.js | 113 +++++++++++++++++++ test/parallel/test-process-exit-code.js | 131 +++-------------------- test/parallel/test-worker-exit-code.js | 123 ++------------------- 3 files changed, 133 insertions(+), 234 deletions(-) create mode 100644 test/fixtures/process-exit-code-cases.js diff --git a/test/fixtures/process-exit-code-cases.js b/test/fixtures/process-exit-code-cases.js new file mode 100644 index 00000000000000..c8c4a2ebe6c7ff --- /dev/null +++ b/test/fixtures/process-exit-code-cases.js @@ -0,0 +1,113 @@ +'use strict'; + +const assert = require('assert'); + +const cases = []; +module.exports = cases; + +function exitsOnExitCodeSet() { + process.exitCode = 42; + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 42); + assert.strictEqual(code, 42); + }); +} +cases.push({ func: exitsOnExitCodeSet, result: 42 }); + +function changesCodeViaExit() { + process.exitCode = 99; + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 42); + assert.strictEqual(code, 42); + }); + process.exit(42); +} +cases.push({ func: changesCodeViaExit, result: 42 }); + +function changesCodeZeroExit() { + process.exitCode = 99; + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 0); + assert.strictEqual(code, 0); + }); + process.exit(0); +} +cases.push({ func: changesCodeZeroExit, result: 0 }); + +function exitWithOneOnUncaught() { + process.exitCode = 99; + process.on('exit', (code) => { + // cannot use assert because it will be uncaughtException -> 1 exit code + // that will render this test useless + if (code !== 1 || process.exitCode !== 1) { + console.log('wrong code! expected 1 for uncaughtException'); + process.exit(99); + } + }); + throw new Error('ok'); +} +cases.push({ + func: exitWithOneOnUncaught, + result: 1, + error: /^Error: ok$/, +}); + +function changeCodeInsideExit() { + process.exitCode = 95; + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 95); + assert.strictEqual(code, 95); + process.exitCode = 99; + }); +} +cases.push({ func: changeCodeInsideExit, result: 99 }); + +function zeroExitWithUncaughtHandler() { + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 0); + assert.strictEqual(code, 0); + }); + process.on('uncaughtException', () => {}); + throw new Error('ok'); +} +cases.push({ func: zeroExitWithUncaughtHandler, result: 0 }); + +function changeCodeInUncaughtHandler() { + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 97); + assert.strictEqual(code, 97); + }); + process.on('uncaughtException', () => { + process.exitCode = 97; + }); + throw new Error('ok'); +} +cases.push({ func: changeCodeInUncaughtHandler, result: 97 }); + +function changeCodeInExitWithUncaught() { + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 1); + assert.strictEqual(code, 1); + process.exitCode = 98; + }); + throw new Error('ok'); +} +cases.push({ + func: changeCodeInExitWithUncaught, + result: 98, + error: /^Error: ok$/, +}); + +function exitWithZeroInExitWithUncaught() { + process.on('exit', (code) => { + assert.strictEqual(process.exitCode, 1); + assert.strictEqual(code, 1); + process.exitCode = 0; + }); + throw new Error('ok'); +} +cases.push({ + func: exitWithZeroInExitWithUncaught, + result: 0, + error: /^Error: ok$/, +}); diff --git a/test/parallel/test-process-exit-code.js b/test/parallel/test-process-exit-code.js index 20004a9d7d6f58..9059b0b5c22487 100644 --- a/test/parallel/test-process-exit-code.js +++ b/test/parallel/test-process-exit-code.js @@ -23,113 +23,18 @@ require('../common'); const assert = require('assert'); -switch (process.argv[2]) { - case 'child1': - return child1(); - case 'child2': - return child2(); - case 'child3': - return child3(); - case 'child4': - return child4(); - case 'child5': - return child5(); - case 'child6': - return child6(); - case 'child7': - return child7(); - case 'child8': - return child8(); - case 'child9': - return child9(); - case undefined: - return parent(); - default: - throw new Error('invalid'); -} - -function child1() { - process.exitCode = 42; - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 42); - assert.strictEqual(code, 42); - }); -} - -function child2() { - process.exitCode = 99; - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 42); - assert.strictEqual(code, 42); - }); - process.exit(42); -} - -function child3() { - process.exitCode = 99; - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 0); - assert.strictEqual(code, 0); - }); - process.exit(0); -} - -function child4() { - process.exitCode = 99; - process.on('exit', function(code) { - if (code !== 1 || process.exitCode !== 1) { - console.log('wrong code! expected 1 for uncaughtException'); - process.exit(99); - } - }); - throw new Error('ok'); -} - -function child5() { - process.exitCode = 95; - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 95); - assert.strictEqual(code, 95); - process.exitCode = 99; - }); -} - -function child6() { - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 0); - assert.strictEqual(code, 0); - }); - process.on('uncaughtException', () => {}); - throw new Error('ok'); -} - -function child7() { - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 97); - assert.strictEqual(code, 97); - }); - process.on('uncaughtException', () => { - process.exitCode = 97; - }); - throw new Error('ok'); -} - -function child8() { - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 1); - assert.strictEqual(code, 1); - process.exitCode = 98; - }); - throw new Error('ok'); -} +const testCases = require('../fixtures/process-exit-code-cases'); -function child9() { - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 1); - assert.strictEqual(code, 1); - process.exitCode = 0; - }); - throw new Error('ok'); +if (!process.argv[2]) { + parent(); +} else { + const i = parseInt(process.argv[2]); + if (Number.isNaN(i)) { + console.log('Invalid test case index'); + process.exit(100); + return; + } + testCases[i].func(); } function parent() { @@ -138,22 +43,14 @@ function parent() { const f = __filename; const option = { stdio: [ 0, 1, 'ignore' ] }; - const test = (arg, exit) => { + const test = (arg, name = 'child', exit) => { spawn(node, [f, arg], option).on('exit', (code) => { assert.strictEqual( code, exit, - `wrong exit for ${arg}\nexpected:${exit} but got:${code}`); + `wrong exit for ${arg}-${name}\nexpected:${exit} but got:${code}`); console.log(`ok - ${arg} exited with ${exit}`); }); }; - test('child1', 42); - test('child2', 42); - test('child3', 0); - test('child4', 1); - test('child5', 99); - test('child6', 0); - test('child7', 97); - test('child8', 98); - test('child9', 0); + testCases.forEach((tc, i) => test(i, tc.func.name, tc.result)); } diff --git a/test/parallel/test-worker-exit-code.js b/test/parallel/test-worker-exit-code.js index 0a1e9bb95370eb..f09b834f853b12 100644 --- a/test/parallel/test-worker-exit-code.js +++ b/test/parallel/test-worker-exit-code.js @@ -9,6 +9,8 @@ const assert = require('assert'); const worker = require('worker_threads'); const { Worker, parentPort } = worker; +const testCases = require('../fixtures/process-exit-code-cases'); + // Do not use isMainThread so that this test itself can be run inside a Worker. if (!process.env.HAS_STARTED_WORKER) { process.env.HAS_STARTED_WORKER = 1; @@ -19,121 +21,16 @@ if (!process.env.HAS_STARTED_WORKER) { process.exit(100); return; } - parentPort.once('message', (msg) => { - switch (msg) { - case 'child1': - return child1(); - case 'child2': - return child2(); - case 'child3': - return child3(); - case 'child4': - return child4(); - case 'child5': - return child5(); - case 'child6': - return child6(); - case 'child7': - return child7(); - case 'child8': - return child8(); - case 'child9': - return child9(); - default: - throw new Error('invalid'); - } - }); -} - -function child1() { - process.exitCode = 42; - process.on('exit', (code) => { - assert.strictEqual(code, 42); - }); -} - -function child2() { - process.exitCode = 99; - process.on('exit', (code) => { - assert.strictEqual(code, 42); - }); - process.exit(42); -} - -function child3() { - process.exitCode = 99; - process.on('exit', (code) => { - assert.strictEqual(code, 0); - }); - process.exit(0); -} - -function child4() { - process.exitCode = 99; - process.on('exit', (code) => { - // cannot use assert because it will be uncaughtException -> 1 exit code - // that will render this test useless - if (code !== 1) { - console.error('wrong code! expected 1 for uncaughtException'); - process.exit(99); - } - }); - throw new Error('ok'); -} - -function child5() { - process.exitCode = 95; - process.on('exit', (code) => { - assert.strictEqual(code, 95); - process.exitCode = 99; - }); -} - -function child6() { - process.on('exit', (code) => { - assert.strictEqual(code, 0); - }); - process.on('uncaughtException', common.mustCall(() => { - // handle - })); - throw new Error('ok'); -} - -function child7() { - process.on('exit', (code) => { - assert.strictEqual(code, 97); - }); - process.on('uncaughtException', common.mustCall(() => { - process.exitCode = 97; - })); - throw new Error('ok'); -} - -function child8() { - process.on('exit', (code) => { - assert.strictEqual(process.exitCode, 1); - assert.strictEqual(code, 1); - process.exitCode = 98; - }); - throw new Error('ok'); -} - -function child9() { - process.on('exit', function(code) { - assert.strictEqual(process.exitCode, 1); - assert.strictEqual(code, 1); - process.exitCode = 0; - }); - throw new Error('ok'); + parentPort.once('message', (msg) => testCases[msg].func()); } function parent() { - const test = (arg, exit, error = null) => { + const test = (arg, name = 'worker', exit, error = null) => { const w = new Worker(__filename); w.on('exit', common.mustCall((code) => { assert.strictEqual( code, exit, - `wrong exit for ${arg}\nexpected:${exit} but got:${code}`); + `wrong exit for ${arg}-${name}\nexpected:${exit} but got:${code}`); console.log(`ok - ${arg} exited with ${exit}`); })); if (error) { @@ -146,13 +43,5 @@ function parent() { w.postMessage(arg); }; - test('child1', 42); - test('child2', 42); - test('child3', 0); - test('child4', 1, /^Error: ok$/); - test('child5', 99); - test('child6', 0); - test('child7', 97); - test('child8', 98, /^Error: ok$/); - test('child9', 0, /^Error: ok$/); + testCases.forEach((tc, i) => test(i, tc.func.name, tc.result, tc.error)); } From b0943a655efee2b1ca023cac9668a788072d07c2 Mon Sep 17 00:00:00 2001 From: Denys Otrishko <shishugi@gmail.com> Date: Fri, 13 Jul 2018 18:45:18 +0300 Subject: [PATCH 074/116] worker: exit after uncaught exception Previously even after uncaught exception the worker would continue to execute until there is no more work to do. PR-URL: https://github.com/nodejs/node/pull/21739 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- lib/internal/worker.js | 2 ++ .../test-worker-uncaught-exception-async.js | 14 ++++++++++++++ test/parallel/test-worker-uncaught-exception.js | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/lib/internal/worker.js b/lib/internal/worker.js index bcc864b5b8b330..26c16f86e3e8fc 100644 --- a/lib/internal/worker.js +++ b/lib/internal/worker.js @@ -467,6 +467,8 @@ function setupChild(evalScript) { else port.postMessage({ type: messageTypes.COULD_NOT_SERIALIZE_ERROR }); clearAsyncIdStack(); + + process.exit(); } } } diff --git a/test/parallel/test-worker-uncaught-exception-async.js b/test/parallel/test-worker-uncaught-exception-async.js index f820976c11ebcd..862b1a66d619c3 100644 --- a/test/parallel/test-worker-uncaught-exception-async.js +++ b/test/parallel/test-worker-uncaught-exception-async.js @@ -10,6 +10,7 @@ if (!process.env.HAS_STARTED_WORKER) { const w = new Worker(__filename); w.on('message', common.mustNotCall()); w.on('error', common.mustCall((err) => { + console.log(err.message); assert(/^Error: foo$/.test(err)); })); w.on('exit', common.mustCall((code) => { @@ -17,6 +18,19 @@ if (!process.env.HAS_STARTED_WORKER) { assert.strictEqual(code, 1); })); } else { + // cannot use common.mustCall as it cannot catch this + let called = false; + process.on('exit', (code) => { + if (!called) { + called = true; + } else { + assert.fail('Exit callback called twice in worker'); + } + }); + + setTimeout(() => assert.fail('Timeout executed after uncaughtException'), + 2000); + setImmediate(() => { throw new Error('foo'); }); diff --git a/test/parallel/test-worker-uncaught-exception.js b/test/parallel/test-worker-uncaught-exception.js index 8193dccbd1ff2f..95c142b6c70d64 100644 --- a/test/parallel/test-worker-uncaught-exception.js +++ b/test/parallel/test-worker-uncaught-exception.js @@ -27,5 +27,9 @@ if (!process.env.HAS_STARTED_WORKER) { assert.fail('Exit callback called twice in worker'); } }); + + setTimeout(() => assert.fail('Timeout executed after uncaughtException'), + 2000); + throw new Error('foo'); } From a68b7dda5f3b754dced9a4e2e58762533a1efe34 Mon Sep 17 00:00:00 2001 From: James M Snell <jasnell@gmail.com> Date: Sat, 2 Jun 2018 17:27:03 -0700 Subject: [PATCH 075/116] src: add node_process.cc MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Begin moving `process` object function definitions out of `node.cc` ... continuing the process of making `node.cc` smaller and easier to maintain. PR-URL: https://github.com/nodejs/node/pull/21105 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Evan Lucas <evanlucas@me.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Backport-PR-URL: https://github.com/nodejs/node/pull/21799 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Michaël Zasso <targos@protonmail.com> --- node.gyp | 1 + src/node.cc | 568 +------------------------------------------ src/node_internals.h | 14 ++ src/node_process.cc | 542 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 566 insertions(+), 559 deletions(-) create mode 100644 src/node_process.cc diff --git a/node.gyp b/node.gyp index e0bd57dde51686..89ebb0fb95c945 100644 --- a/node.gyp +++ b/node.gyp @@ -350,6 +350,7 @@ 'src/node_platform.cc', 'src/node_perf.cc', 'src/node_postmortem_metadata.cc', + 'src/node_process.cc', 'src/node_serdes.cc', 'src/node_trace_events.cc', 'src/node_types.cc', diff --git a/src/node.cc b/src/node.cc index e2d9b426d0cf97..ec2d79f9f33280 100644 --- a/src/node.cc +++ b/src/node.cc @@ -138,11 +138,9 @@ using v8::Boolean; using v8::Context; using v8::EscapableHandleScope; using v8::Exception; -using v8::Float64Array; using v8::Function; using v8::FunctionCallbackInfo; using v8::HandleScope; -using v8::HeapStatistics; using v8::Integer; using v8::Isolate; using v8::Just; @@ -164,7 +162,6 @@ using v8::ScriptOrigin; using v8::SealHandleScope; using v8::String; using v8::TryCatch; -using v8::Uint32Array; using v8::Undefined; using v8::V8; using v8::Value; @@ -284,7 +281,7 @@ bool v8_initialized = false; bool linux_at_secure = false; // process-relative uptime base, initialized at start-up -static double prog_start_time; +double prog_start_time; static Mutex node_isolate_mutex; static v8::Isolate* node_isolate; @@ -388,7 +385,7 @@ static struct { static const unsigned kMaxSignal = 32; #endif -static void PrintErrorString(const char* format, ...) { +void PrintErrorString(const char* format, ...) { va_list ap; va_start(ap, format); #ifdef _WIN32 @@ -1104,411 +1101,6 @@ NO_RETURN void Assert(const char* const (*args)[4]) { Abort(); } - -static void Abort(const FunctionCallbackInfo<Value>& args) { - Abort(); -} - - -void Chdir(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - if (args.Length() != 1 || !args[0]->IsString()) { - return env->ThrowTypeError("Bad argument."); - } - - node::Utf8Value path(args.GetIsolate(), args[0]); - int err = uv_chdir(*path); - if (err) { - return env->ThrowUVException(err, "chdir", nullptr, *path, nullptr); - } -} - - -static void Cwd(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); -#ifdef _WIN32 - /* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */ - char buf[MAX_PATH * 4]; -#else - char buf[PATH_MAX]; -#endif - - size_t cwd_len = sizeof(buf); - int err = uv_cwd(buf, &cwd_len); - if (err) { - return env->ThrowUVException(err, "uv_cwd"); - } - - Local<String> cwd = String::NewFromUtf8(env->isolate(), - buf, - String::kNormalString, - cwd_len); - args.GetReturnValue().Set(cwd); -} - - -void Umask(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - uint32_t old; - - if (args.Length() < 1 || args[0]->IsUndefined()) { - old = umask(0); - umask(static_cast<mode_t>(old)); - } else if (!args[0]->IsInt32() && !args[0]->IsString()) { - return env->ThrowTypeError("argument must be an integer or octal string."); - } else { - int oct; - if (args[0]->IsInt32()) { - oct = args[0]->Uint32Value(); - } else { - oct = 0; - node::Utf8Value str(env->isolate(), args[0]); - - // Parse the octal string. - for (size_t i = 0; i < str.length(); i++) { - char c = (*str)[i]; - if (c > '7' || c < '0') { - return env->ThrowTypeError("invalid octal string"); - } - oct *= 8; - oct += c - '0'; - } - } - old = umask(static_cast<mode_t>(oct)); - } - - args.GetReturnValue().Set(old); -} - - -#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) - -static const uid_t uid_not_found = static_cast<uid_t>(-1); -static const gid_t gid_not_found = static_cast<gid_t>(-1); - - -static uid_t uid_by_name(const char* name) { - struct passwd pwd; - struct passwd* pp; - char buf[8192]; - - errno = 0; - pp = nullptr; - - if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { - return pp->pw_uid; - } - - return uid_not_found; -} - - -static char* name_by_uid(uid_t uid) { - struct passwd pwd; - struct passwd* pp; - char buf[8192]; - int rc; - - errno = 0; - pp = nullptr; - - if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && - pp != nullptr) { - return strdup(pp->pw_name); - } - - if (rc == 0) { - errno = ENOENT; - } - - return nullptr; -} - - -static gid_t gid_by_name(const char* name) { - struct group pwd; - struct group* pp; - char buf[8192]; - - errno = 0; - pp = nullptr; - - if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) { - return pp->gr_gid; - } - - return gid_not_found; -} - - -#if 0 // For future use. -static const char* name_by_gid(gid_t gid) { - struct group pwd; - struct group* pp; - char buf[8192]; - int rc; - - errno = 0; - pp = nullptr; - - if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && - pp != nullptr) { - return strdup(pp->gr_name); - } - - if (rc == 0) { - errno = ENOENT; - } - - return nullptr; -} -#endif - - -static uid_t uid_by_name(Isolate* isolate, Local<Value> value) { - if (value->IsUint32()) { - return static_cast<uid_t>(value->Uint32Value()); - } else { - node::Utf8Value name(isolate, value); - return uid_by_name(*name); - } -} - - -static gid_t gid_by_name(Isolate* isolate, Local<Value> value) { - if (value->IsUint32()) { - return static_cast<gid_t>(value->Uint32Value()); - } else { - node::Utf8Value name(isolate, value); - return gid_by_name(*name); - } -} - -static void GetUid(const FunctionCallbackInfo<Value>& args) { - // uid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast<uint32_t>(getuid())); -} - - -static void GetGid(const FunctionCallbackInfo<Value>& args) { - // gid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast<uint32_t>(getgid())); -} - - -static void GetEUid(const FunctionCallbackInfo<Value>& args) { - // uid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast<uint32_t>(geteuid())); -} - - -static void GetEGid(const FunctionCallbackInfo<Value>& args) { - // gid_t is an uint32_t on all supported platforms. - args.GetReturnValue().Set(static_cast<uint32_t>(getegid())); -} - - -void SetGid(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("setgid argument must be a number or a string"); - } - - gid_t gid = gid_by_name(env->isolate(), args[0]); - - if (gid == gid_not_found) { - return env->ThrowError("setgid group id does not exist"); - } - - if (setgid(gid)) { - return env->ThrowErrnoException(errno, "setgid"); - } -} - - -void SetEGid(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("setegid argument must be a number or string"); - } - - gid_t gid = gid_by_name(env->isolate(), args[0]); - - if (gid == gid_not_found) { - return env->ThrowError("setegid group id does not exist"); - } - - if (setegid(gid)) { - return env->ThrowErrnoException(errno, "setegid"); - } -} - - -void SetUid(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("setuid argument must be a number or a string"); - } - - uid_t uid = uid_by_name(env->isolate(), args[0]); - - if (uid == uid_not_found) { - return env->ThrowError("setuid user id does not exist"); - } - - if (setuid(uid)) { - return env->ThrowErrnoException(errno, "setuid"); - } -} - - -void SetEUid(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - CHECK(env->is_main_thread()); - - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("seteuid argument must be a number or string"); - } - - uid_t uid = uid_by_name(env->isolate(), args[0]); - - if (uid == uid_not_found) { - return env->ThrowError("seteuid user id does not exist"); - } - - if (seteuid(uid)) { - return env->ThrowErrnoException(errno, "seteuid"); - } -} - - -static void GetGroups(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - - int ngroups = getgroups(0, nullptr); - - if (ngroups == -1) { - return env->ThrowErrnoException(errno, "getgroups"); - } - - gid_t* groups = new gid_t[ngroups]; - - ngroups = getgroups(ngroups, groups); - - if (ngroups == -1) { - delete[] groups; - return env->ThrowErrnoException(errno, "getgroups"); - } - - Local<Array> groups_list = Array::New(env->isolate(), ngroups); - bool seen_egid = false; - gid_t egid = getegid(); - - for (int i = 0; i < ngroups; i++) { - groups_list->Set(i, Integer::New(env->isolate(), groups[i])); - if (groups[i] == egid) - seen_egid = true; - } - - delete[] groups; - - if (seen_egid == false) { - groups_list->Set(ngroups, Integer::New(env->isolate(), egid)); - } - - args.GetReturnValue().Set(groups_list); -} - - -void SetGroups(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - - if (!args[0]->IsArray()) { - return env->ThrowTypeError("argument 1 must be an array"); - } - - Local<Array> groups_list = args[0].As<Array>(); - size_t size = groups_list->Length(); - gid_t* groups = new gid_t[size]; - - for (size_t i = 0; i < size; i++) { - gid_t gid = gid_by_name(env->isolate(), groups_list->Get(i)); - - if (gid == gid_not_found) { - delete[] groups; - return env->ThrowError("group name not found"); - } - - groups[i] = gid; - } - - int rc = setgroups(size, groups); - delete[] groups; - - if (rc == -1) { - return env->ThrowErrnoException(errno, "setgroups"); - } -} - - -void InitGroups(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - - if (!args[0]->IsUint32() && !args[0]->IsString()) { - return env->ThrowTypeError("argument 1 must be a number or a string"); - } - - if (!args[1]->IsUint32() && !args[1]->IsString()) { - return env->ThrowTypeError("argument 2 must be a number or a string"); - } - - node::Utf8Value arg0(env->isolate(), args[0]); - gid_t extra_group; - bool must_free; - char* user; - - if (args[0]->IsUint32()) { - user = name_by_uid(args[0]->Uint32Value()); - must_free = true; - } else { - user = *arg0; - must_free = false; - } - - if (user == nullptr) { - return env->ThrowError("initgroups user not found"); - } - - extra_group = gid_by_name(env->isolate(), args[1]); - - if (extra_group == gid_not_found) { - if (must_free) - free(user); - return env->ThrowError("initgroups extra group not found"); - } - - int rc = initgroups(user, extra_group); - - if (must_free) { - free(user); - } - - if (rc) { - return env->ThrowErrnoException(errno, "initgroups"); - } -} - -#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) - - static void WaitForInspectorDisconnect(Environment* env) { #if HAVE_INSPECTOR if (env->inspector_agent()->IsActive()) { @@ -1537,113 +1129,6 @@ static void Exit(const FunctionCallbackInfo<Value>& args) { env->Exit(args[0]->Int32Value()); } - -static void Uptime(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - double uptime; - - uv_update_time(env->event_loop()); - uptime = uv_now(env->event_loop()) - prog_start_time; - - args.GetReturnValue().Set(Number::New(env->isolate(), uptime / 1000)); -} - - -void MemoryUsage(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - - size_t rss; - int err = uv_resident_set_memory(&rss); - if (err) { - return env->ThrowUVException(err, "uv_resident_set_memory"); - } - - Isolate* isolate = env->isolate(); - // V8 memory usage - HeapStatistics v8_heap_stats; - isolate->GetHeapStatistics(&v8_heap_stats); - - // Get the double array pointer from the Float64Array argument. - CHECK(args[0]->IsFloat64Array()); - Local<Float64Array> array = args[0].As<Float64Array>(); - CHECK_EQ(array->Length(), 4); - Local<ArrayBuffer> ab = array->Buffer(); - double* fields = static_cast<double*>(ab->GetContents().Data()); - - fields[0] = rss; - fields[1] = v8_heap_stats.total_heap_size(); - fields[2] = v8_heap_stats.used_heap_size(); - fields[3] = isolate->AdjustAmountOfExternalAllocatedMemory(0); -} - - -static void Kill(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - - if (args.Length() != 2) { - return env->ThrowError("Bad argument."); - } - - int pid = args[0]->Int32Value(); - int sig = args[1]->Int32Value(); - int err = uv_kill(pid, sig); - args.GetReturnValue().Set(err); -} - -// used in Hrtime() below -#define NANOS_PER_SEC 1000000000 - -// Hrtime exposes libuv's uv_hrtime() high-resolution timer. -// The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, -// so this function instead fills in an Uint32Array with 3 entries, -// to avoid any integer overflow possibility. -// The first two entries contain the second part of the value -// broken into the upper/lower 32 bits to be converted back in JS, -// because there is no Uint64Array in JS. -// The third entry contains the remaining nanosecond part of the value. -void Hrtime(const FunctionCallbackInfo<Value>& args) { - uint64_t t = uv_hrtime(); - - Local<ArrayBuffer> ab = args[0].As<Uint32Array>()->Buffer(); - uint32_t* fields = static_cast<uint32_t*>(ab->GetContents().Data()); - - fields[0] = (t / NANOS_PER_SEC) >> 32; - fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; - fields[2] = t % NANOS_PER_SEC; -} - -// Microseconds in a second, as a float, used in CPUUsage() below -#define MICROS_PER_SEC 1e6 - -// CPUUsage use libuv's uv_getrusage() this-process resource usage accessor, -// to access ru_utime (user CPU time used) and ru_stime (system CPU time used), -// which are uv_timeval_t structs (long tv_sec, long tv_usec). -// Returns those values as Float64 microseconds in the elements of the array -// passed to the function. -void CPUUsage(const FunctionCallbackInfo<Value>& args) { - uv_rusage_t rusage; - - // Call libuv to get the values we'll return. - int err = uv_getrusage(&rusage); - if (err) { - // On error, return the strerror version of the error code. - Local<String> errmsg = OneByteString(args.GetIsolate(), uv_strerror(err)); - args.GetReturnValue().Set(errmsg); - return; - } - - // Get the double array pointer from the Float64Array argument. - CHECK(args[0]->IsFloat64Array()); - Local<Float64Array> array = args[0].As<Float64Array>(); - CHECK_EQ(array->Length(), 2); - Local<ArrayBuffer> ab = array->Buffer(); - double* fields = static_cast<double*>(ab->GetContents().Data()); - - // Set the Float64Array elements to be user / system values in microseconds. - fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec; - fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec; -} - extern "C" void node_module_register(void* m) { struct node_module* mp = reinterpret_cast<struct node_module*>(m); @@ -2449,18 +1934,6 @@ static void DebugEnd(const FunctionCallbackInfo<Value>& args); namespace { -void StartProfilerIdleNotifier(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - env->StartProfilerIdleNotifier(); -} - - -void StopProfilerIdleNotifier(const FunctionCallbackInfo<Value>& args) { - Environment* env = Environment::GetCurrent(args); - env->StopProfilerIdleNotifier(); -} - - #define READONLY_PROPERTY(obj, str, var) \ do { \ obj->DefineOwnProperty(env->context(), \ @@ -2790,6 +2263,8 @@ void SetupProcessObject(Environment* env, // define various internal methods if (env->is_main_thread()) { + env->SetMethod(process, "_debugProcess", DebugProcess); + env->SetMethod(process, "_debugEnd", DebugEnd); env->SetMethod(process, "_startProfilerIdleNotifier", StartProfilerIdleNotifier); @@ -2800,11 +2275,14 @@ void SetupProcessObject(Environment* env, env->SetMethod(process, "chdir", Chdir); env->SetMethod(process, "umask", Umask); } - env->SetMethod(process, "_getActiveRequests", GetActiveRequests); env->SetMethod(process, "_getActiveHandles", GetActiveHandles); - env->SetMethod(process, "reallyExit", Exit); + env->SetMethod(process, "_kill", Kill); + env->SetMethod(process, "cwd", Cwd); + env->SetMethod(process, "dlopen", DLOpen); + env->SetMethod(process, "reallyExit", Exit); + env->SetMethod(process, "uptime", Uptime); #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) env->SetMethod(process, "getuid", GetUid); @@ -2813,21 +2291,6 @@ void SetupProcessObject(Environment* env, env->SetMethod(process, "getegid", GetEGid); env->SetMethod(process, "getgroups", GetGroups); #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) - - env->SetMethod(process, "_kill", Kill); - env->SetMethod(process, "dlopen", DLOpen); - - if (env->is_main_thread()) { - env->SetMethod(process, "_debugProcess", DebugProcess); - env->SetMethod(process, "_debugEnd", DebugEnd); - } - - env->SetMethod(process, "hrtime", Hrtime); - - env->SetMethod(process, "cpuUsage", CPUUsage); - - env->SetMethod(process, "uptime", Uptime); - env->SetMethod(process, "memoryUsage", MemoryUsage); } @@ -2848,19 +2311,6 @@ void SignalExit(int signo) { } -// Most of the time, it's best to use `console.error` to write -// to the process.stderr stream. However, in some cases, such as -// when debugging the stream.Writable class or the process.nextTick -// function, it is useful to bypass JavaScript entirely. -void RawDebug(const FunctionCallbackInfo<Value>& args) { - CHECK(args.Length() == 1 && args[0]->IsString() && - "must be called with a single string"); - node::Utf8Value message(args.GetIsolate(), args[0]); - PrintErrorString("%s\n", *message); - fflush(stderr); -} - - static MaybeLocal<Function> GetBootstrapper( Environment* env, Local<String> source, diff --git a/src/node_internals.h b/src/node_internals.h index 042b685c5939ae..5371a5502174fd 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -931,12 +931,21 @@ static inline const char* errno_string(int errorno) { // Functions defined in node.cc that are exposed via the bootstrapper object +extern double prog_start_time; +void PrintErrorString(const char* format, ...); + +void Abort(const v8::FunctionCallbackInfo<v8::Value>& args); void Chdir(const v8::FunctionCallbackInfo<v8::Value>& args); void CPUUsage(const v8::FunctionCallbackInfo<v8::Value>& args); +void Cwd(const v8::FunctionCallbackInfo<v8::Value>& args); void Hrtime(const v8::FunctionCallbackInfo<v8::Value>& args); +void Kill(const v8::FunctionCallbackInfo<v8::Value>& args); void MemoryUsage(const v8::FunctionCallbackInfo<v8::Value>& args); void RawDebug(const v8::FunctionCallbackInfo<v8::Value>& args); +void StartProfilerIdleNotifier(const v8::FunctionCallbackInfo<v8::Value>& args); +void StopProfilerIdleNotifier(const v8::FunctionCallbackInfo<v8::Value>& args); void Umask(const v8::FunctionCallbackInfo<v8::Value>& args); +void Uptime(const v8::FunctionCallbackInfo<v8::Value>& args); #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) void SetGid(const v8::FunctionCallbackInfo<v8::Value>& args); @@ -945,6 +954,11 @@ void SetUid(const v8::FunctionCallbackInfo<v8::Value>& args); void SetEUid(const v8::FunctionCallbackInfo<v8::Value>& args); void SetGroups(const v8::FunctionCallbackInfo<v8::Value>& args); void InitGroups(const v8::FunctionCallbackInfo<v8::Value>& args); +void GetUid(const v8::FunctionCallbackInfo<v8::Value>& args); +void GetGid(const v8::FunctionCallbackInfo<v8::Value>& args); +void GetEUid(const v8::FunctionCallbackInfo<v8::Value>& args); +void GetEGid(const v8::FunctionCallbackInfo<v8::Value>& args); +void GetGroups(const v8::FunctionCallbackInfo<v8::Value>& args); #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) } // namespace node diff --git a/src/node_process.cc b/src/node_process.cc new file mode 100644 index 00000000000000..3313f85627b16f --- /dev/null +++ b/src/node_process.cc @@ -0,0 +1,542 @@ +#include "node.h" +#include "node_internals.h" +#include "env.h" +#include "env-inl.h" +#include "util.h" +#include "util-inl.h" +#include "uv.h" +#include "v8.h" + +#include <limits.h> // PATH_MAX +#include <stdio.h> + +#if defined(_MSC_VER) +#include <direct.h> +#include <io.h> +#define umask _umask +typedef int mode_t; +#else +#include <pthread.h> +#include <sys/resource.h> // getrlimit, setrlimit +#include <termios.h> // tcgetattr, tcsetattr +#include <unistd.h> // setuid, getuid +#endif + +#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) +#include <pwd.h> // getpwnam() +#include <grp.h> // getgrnam() +#endif + +namespace node { + +using v8::Array; +using v8::ArrayBuffer; +using v8::Float64Array; +using v8::FunctionCallbackInfo; +using v8::HeapStatistics; +using v8::Integer; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::String; +using v8::Uint32; +using v8::Uint32Array; +using v8::Value; + +// Microseconds in a second, as a float, used in CPUUsage() below +#define MICROS_PER_SEC 1e6 +// used in Hrtime() below +#define NANOS_PER_SEC 1000000000 + +void Abort(const FunctionCallbackInfo<Value>& args) { + Abort(); +} + +void Chdir(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + if (args.Length() != 1 || !args[0]->IsString()) + return env->ThrowTypeError("Bad argument."); + + Utf8Value path(args.GetIsolate(), args[0]); + int err = uv_chdir(*path); + if (err) + return env->ThrowUVException(err, "chdir", nullptr, *path, nullptr); +} + +// CPUUsage use libuv's uv_getrusage() this-process resource usage accessor, +// to access ru_utime (user CPU time used) and ru_stime (system CPU time used), +// which are uv_timeval_t structs (long tv_sec, long tv_usec). +// Returns those values as Float64 microseconds in the elements of the array +// passed to the function. +void CPUUsage(const FunctionCallbackInfo<Value>& args) { + uv_rusage_t rusage; + + // Call libuv to get the values we'll return. + int err = uv_getrusage(&rusage); + if (err) { + // On error, return the strerror version of the error code. + Local<String> errmsg = OneByteString(args.GetIsolate(), uv_strerror(err)); + return args.GetReturnValue().Set(errmsg); + } + + // Get the double array pointer from the Float64Array argument. + CHECK(args[0]->IsFloat64Array()); + Local<Float64Array> array = args[0].As<Float64Array>(); + CHECK_EQ(array->Length(), 2); + Local<ArrayBuffer> ab = array->Buffer(); + double* fields = static_cast<double*>(ab->GetContents().Data()); + + // Set the Float64Array elements to be user / system values in microseconds. + fields[0] = MICROS_PER_SEC * rusage.ru_utime.tv_sec + rusage.ru_utime.tv_usec; + fields[1] = MICROS_PER_SEC * rusage.ru_stime.tv_sec + rusage.ru_stime.tv_usec; +} + +void Cwd(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); +#ifdef _WIN32 + /* MAX_PATH is in characters, not bytes. Make sure we have enough headroom. */ + char buf[MAX_PATH * 4]; +#else + char buf[PATH_MAX]; +#endif + + size_t cwd_len = sizeof(buf); + int err = uv_cwd(buf, &cwd_len); + if (err) + return env->ThrowUVException(err, "uv_cwd"); + + Local<String> cwd = String::NewFromUtf8(env->isolate(), + buf, + String::kNormalString, + cwd_len); + args.GetReturnValue().Set(cwd); +} + +// Hrtime exposes libuv's uv_hrtime() high-resolution timer. +// The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, +// so this function instead fills in an Uint32Array with 3 entries, +// to avoid any integer overflow possibility. +// The first two entries contain the second part of the value +// broken into the upper/lower 32 bits to be converted back in JS, +// because there is no Uint64Array in JS. +// The third entry contains the remaining nanosecond part of the value. +void Hrtime(const FunctionCallbackInfo<Value>& args) { + uint64_t t = uv_hrtime(); + + Local<ArrayBuffer> ab = args[0].As<Uint32Array>()->Buffer(); + uint32_t* fields = static_cast<uint32_t*>(ab->GetContents().Data()); + + fields[0] = (t / NANOS_PER_SEC) >> 32; + fields[1] = (t / NANOS_PER_SEC) & 0xffffffff; + fields[2] = t % NANOS_PER_SEC; +} + +void Kill(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + if (args.Length() != 2) + return env->ThrowError("Bad argument."); + + int pid = args[0]->Int32Value(); + int sig = args[1]->Int32Value(); + int err = uv_kill(pid, sig); + args.GetReturnValue().Set(err); +} + + +void MemoryUsage(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + size_t rss; + int err = uv_resident_set_memory(&rss); + if (err) + return env->ThrowUVException(err, "uv_resident_set_memory"); + + Isolate* isolate = env->isolate(); + // V8 memory usage + HeapStatistics v8_heap_stats; + isolate->GetHeapStatistics(&v8_heap_stats); + + // Get the double array pointer from the Float64Array argument. + CHECK(args[0]->IsFloat64Array()); + Local<Float64Array> array = args[0].As<Float64Array>(); + CHECK_EQ(array->Length(), 4); + Local<ArrayBuffer> ab = array->Buffer(); + double* fields = static_cast<double*>(ab->GetContents().Data()); + + fields[0] = rss; + fields[1] = v8_heap_stats.total_heap_size(); + fields[2] = v8_heap_stats.used_heap_size(); + fields[3] = isolate->AdjustAmountOfExternalAllocatedMemory(0); +} + +// Most of the time, it's best to use `console.error` to write +// to the process.stderr stream. However, in some cases, such as +// when debugging the stream.Writable class or the process.nextTick +// function, it is useful to bypass JavaScript entirely. +void RawDebug(const FunctionCallbackInfo<Value>& args) { + CHECK(args.Length() == 1 && args[0]->IsString() && + "must be called with a single string"); + Utf8Value message(args.GetIsolate(), args[0]); + PrintErrorString("%s\n", *message); + fflush(stderr); +} + +void StartProfilerIdleNotifier(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + env->StartProfilerIdleNotifier(); +} + + +void StopProfilerIdleNotifier(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + env->StopProfilerIdleNotifier(); +} + +void Umask(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + uint32_t old; + + if (args.Length() < 1 || args[0]->IsUndefined()) { + old = umask(0); + umask(static_cast<mode_t>(old)); + } else if (!args[0]->IsInt32() && !args[0]->IsString()) { + return env->ThrowTypeError("argument must be an integer or octal string."); + } else { + int oct; + if (args[0]->IsInt32()) { + oct = args[0]->Uint32Value(); + } else { + oct = 0; + Utf8Value str(env->isolate(), args[0]); + + // Parse the octal string. + for (size_t i = 0; i < str.length(); i++) { + char c = (*str)[i]; + if (c > '7' || c < '0') + return env->ThrowTypeError("invalid octal string"); + oct *= 8; + oct += c - '0'; + } + } + old = umask(static_cast<mode_t>(oct)); + } + + args.GetReturnValue().Set(old); +} + +void Uptime(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + double uptime; + + uv_update_time(env->event_loop()); + uptime = uv_now(env->event_loop()) - prog_start_time; + + args.GetReturnValue().Set(Number::New(env->isolate(), uptime / 1000)); +} + + +#if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) + +static const uid_t uid_not_found = static_cast<uid_t>(-1); +static const gid_t gid_not_found = static_cast<gid_t>(-1); + + +static uid_t uid_by_name(const char* name) { + struct passwd pwd; + struct passwd* pp; + char buf[8192]; + + errno = 0; + pp = nullptr; + + if (getpwnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) + return pp->pw_uid; + + return uid_not_found; +} + + +static char* name_by_uid(uid_t uid) { + struct passwd pwd; + struct passwd* pp; + char buf[8192]; + int rc; + + errno = 0; + pp = nullptr; + + if ((rc = getpwuid_r(uid, &pwd, buf, sizeof(buf), &pp)) == 0 && + pp != nullptr) { + return strdup(pp->pw_name); + } + + if (rc == 0) + errno = ENOENT; + + return nullptr; +} + + +static gid_t gid_by_name(const char* name) { + struct group pwd; + struct group* pp; + char buf[8192]; + + errno = 0; + pp = nullptr; + + if (getgrnam_r(name, &pwd, buf, sizeof(buf), &pp) == 0 && pp != nullptr) + return pp->gr_gid; + + return gid_not_found; +} + + +#if 0 // For future use. +static const char* name_by_gid(gid_t gid) { + struct group pwd; + struct group* pp; + char buf[8192]; + int rc; + + errno = 0; + pp = nullptr; + + if ((rc = getgrgid_r(gid, &pwd, buf, sizeof(buf), &pp)) == 0 && + pp != nullptr) { + return strdup(pp->gr_name); + } + + if (rc == 0) { + errno = ENOENT; + } + + return nullptr; +} +#endif + + +static uid_t uid_by_name(Isolate* isolate, Local<Value> value) { + if (value->IsUint32()) { + return static_cast<uid_t>(value->Uint32Value()); + } else { + Utf8Value name(isolate, value); + return uid_by_name(*name); + } +} + + +static gid_t gid_by_name(Isolate* isolate, Local<Value> value) { + if (value->IsUint32()) { + return static_cast<gid_t>(value->Uint32Value()); + } else { + Utf8Value name(isolate, value); + return gid_by_name(*name); + } +} + +void GetUid(const FunctionCallbackInfo<Value>& args) { + // uid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast<uint32_t>(getuid())); +} + + +void GetGid(const FunctionCallbackInfo<Value>& args) { + // gid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast<uint32_t>(getgid())); +} + + +void GetEUid(const FunctionCallbackInfo<Value>& args) { + // uid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast<uint32_t>(geteuid())); +} + + +void GetEGid(const FunctionCallbackInfo<Value>& args) { + // gid_t is an uint32_t on all supported platforms. + args.GetReturnValue().Set(static_cast<uint32_t>(getegid())); +} + + +void SetGid(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + if (!args[0]->IsUint32() && !args[0]->IsString()) + return env->ThrowTypeError("setgid argument must be a number or a string"); + + gid_t gid = gid_by_name(env->isolate(), args[0]); + + if (gid == gid_not_found) + return env->ThrowError("setgid group id does not exist"); + + if (setgid(gid)) + return env->ThrowErrnoException(errno, "setgid"); +} + + +void SetEGid(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + if (!args[0]->IsUint32() && !args[0]->IsString()) + return env->ThrowTypeError("setegid argument must be a number or string"); + + gid_t gid = gid_by_name(env->isolate(), args[0]); + + if (gid == gid_not_found) + return env->ThrowError("setegid group id does not exist"); + + if (setegid(gid)) + return env->ThrowErrnoException(errno, "setegid"); +} + + +void SetUid(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + if (!args[0]->IsUint32() && !args[0]->IsString()) + return env->ThrowTypeError("setuid argument must be a number or a string"); + + uid_t uid = uid_by_name(env->isolate(), args[0]); + + if (uid == uid_not_found) + return env->ThrowError("setuid user id does not exist"); + + if (setuid(uid)) + return env->ThrowErrnoException(errno, "setuid"); +} + + +void SetEUid(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(env->is_main_thread()); + + if (!args[0]->IsUint32() && !args[0]->IsString()) + return env->ThrowTypeError("seteuid argument must be a number or string"); + + uid_t uid = uid_by_name(env->isolate(), args[0]); + + if (uid == uid_not_found) + return env->ThrowError("seteuid user id does not exist"); + + if (seteuid(uid)) + return env->ThrowErrnoException(errno, "seteuid"); +} + + +void GetGroups(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + int ngroups = getgroups(0, nullptr); + + if (ngroups == -1) + return env->ThrowErrnoException(errno, "getgroups"); + + gid_t* groups = new gid_t[ngroups]; + + ngroups = getgroups(ngroups, groups); + + if (ngroups == -1) { + delete[] groups; + return env->ThrowErrnoException(errno, "getgroups"); + } + + Local<Array> groups_list = Array::New(env->isolate(), ngroups); + bool seen_egid = false; + gid_t egid = getegid(); + + for (int i = 0; i < ngroups; i++) { + groups_list->Set(i, Integer::New(env->isolate(), groups[i])); + if (groups[i] == egid) + seen_egid = true; + } + + delete[] groups; + + if (seen_egid == false) + groups_list->Set(ngroups, Integer::New(env->isolate(), egid)); + + args.GetReturnValue().Set(groups_list); +} + + +void SetGroups(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + if (!args[0]->IsArray()) + return env->ThrowTypeError("argument 1 must be an array"); + + Local<Array> groups_list = args[0].As<Array>(); + size_t size = groups_list->Length(); + gid_t* groups = new gid_t[size]; + + for (size_t i = 0; i < size; i++) { + gid_t gid = gid_by_name(env->isolate(), groups_list->Get(i)); + + if (gid == gid_not_found) { + delete[] groups; + return env->ThrowError("group name not found"); + } + + groups[i] = gid; + } + + int rc = setgroups(size, groups); + delete[] groups; + + if (rc == -1) + return env->ThrowErrnoException(errno, "setgroups"); +} + + +void InitGroups(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + + if (!args[0]->IsUint32() && !args[0]->IsString()) + return env->ThrowTypeError("argument 1 must be a number or a string"); + + if (!args[1]->IsUint32() && !args[1]->IsString()) + return env->ThrowTypeError("argument 2 must be a number or a string"); + + Utf8Value arg0(env->isolate(), args[0]); + gid_t extra_group; + bool must_free; + char* user; + + if (args[0]->IsUint32()) { + user = name_by_uid(args[0]->Uint32Value()); + must_free = true; + } else { + user = *arg0; + must_free = false; + } + + if (user == nullptr) + return env->ThrowError("initgroups user not found"); + + extra_group = gid_by_name(env->isolate(), args[1]); + + if (extra_group == gid_not_found) { + if (must_free) + free(user); + return env->ThrowError("initgroups extra group not found"); + } + + int rc = initgroups(user, extra_group); + + if (must_free) + free(user); + + if (rc) + return env->ThrowErrnoException(errno, "initgroups"); +} + +#endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) + +} // namespace node From f1b18ba4120966f9c57faa474b523a36c961fc3b Mon Sep 17 00:00:00 2001 From: Joyee Cheung <joyeec9h3@gmail.com> Date: Mon, 11 Jun 2018 17:37:09 +0800 Subject: [PATCH 076/116] process: implement process.hrtime.bigint() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/21256 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> --- doc/api/process.md | 32 +++++++++++++++++++++ lib/internal/bootstrap/node.js | 5 ++-- lib/internal/process.js | 10 ++++++- src/bootstrapper.cc | 1 + src/node_internals.h | 1 + src/node_process.cc | 11 +++++++ test/parallel/test-process-hrtime-bigint.js | 14 +++++++++ 7 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 test/parallel/test-process-hrtime-bigint.js diff --git a/doc/api/process.md b/doc/api/process.md index 80b70ae1151370..df544e7f0f17b9 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -1163,6 +1163,9 @@ added: v0.7.6 * `time` {integer[]} The result of a previous call to `process.hrtime()` * Returns: {integer[]} +This is the legacy version of [`process.hrtime.bigint()`][] +before `bigint` was introduced in JavaScript. + The `process.hrtime()` method returns the current high-resolution real time in a `[seconds, nanoseconds]` tuple `Array`, where `nanoseconds` is the remaining part of the real time that can't be represented in second precision. @@ -1191,6 +1194,33 @@ setTimeout(() => { }, 1000); ``` +## process.hrtime.bigint() +<!-- YAML +added: REPLACEME +--> + +* Returns: {bigint} + +The `bigint` version of the [`process.hrtime()`][] method returning the +current high-resolution real time in a `bigint`. + +Unlike [`process.hrtime()`][], it does not support an additional `time` +argument since the difference can just be computed directly +by subtraction of the two `bigint`s. + +```js +const start = process.hrtime.bigint(); +// 191051479007711n + +setTimeout(() => { + const end = process.hrtime.bigint(); + // 191052633396993n + + console.log(`Benchmark took ${end - start} nanoseconds`); + // Benchmark took 1154389282 nanoseconds +}, 1000); +``` + ## process.initgroups(user, extraGroup) <!-- YAML added: v0.9.4 @@ -2035,6 +2065,8 @@ cases: [`process.execPath`]: #process_process_execpath [`process.exit()`]: #process_process_exit_code [`process.exitCode`]: #process_process_exitcode +[`process.hrtime()`]: #process_process_hrtime_time +[`process.hrtime.bigint()`]: #process_process_hrtime_bigint [`process.kill()`]: #process_process_kill_pid_signal [`process.setUncaughtExceptionCaptureCallback()`]: process.html#process_process_setuncaughtexceptioncapturecallback_fn [`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 4256de80c2e8f5..61146a32aad010 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -18,7 +18,8 @@ // object. { _setupProcessObject, _setupNextTick, _setupPromises, _chdir, _cpuUsage, - _hrtime, _memoryUsage, _rawDebug, + _hrtime, _hrtimeBigInt, + _memoryUsage, _rawDebug, _umask, _initgroups, _setegid, _seteuid, _setgid, _setuid, _setgroups, _shouldAbortOnUncaughtToggle }, @@ -66,7 +67,7 @@ NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE, } = perf.constants; - _process.setup_hrtime(_hrtime); + _process.setup_hrtime(_hrtime, _hrtimeBigInt); _process.setup_cpuUsage(_cpuUsage); _process.setupMemoryUsage(_memoryUsage); _process.setupKillAndExit(); diff --git a/lib/internal/process.js b/lib/internal/process.js index 45d9c77b32007f..098edaeaaff183 100644 --- a/lib/internal/process.js +++ b/lib/internal/process.js @@ -90,7 +90,7 @@ function setup_cpuUsage(_cpuUsage) { // The 3 entries filled in by the original process.hrtime contains // the upper/lower 32 bits of the second part of the value, // and the remaining nanoseconds of the value. -function setup_hrtime(_hrtime) { +function setup_hrtime(_hrtime, _hrtimeBigInt) { const hrValues = new Uint32Array(3); process.hrtime = function hrtime(time) { @@ -115,6 +115,14 @@ function setup_hrtime(_hrtime) { hrValues[2] ]; }; + + // Use a BigUint64Array in the closure because V8 does not have an API for + // creating a BigInt out of a uint64_t yet. + const hrBigintValues = new BigUint64Array(1); + process.hrtime.bigint = function() { + _hrtimeBigInt(hrBigintValues); + return hrBigintValues[0]; + }; } function setupMemoryUsage(_memoryUsage) { diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index f9db02562d9c8a..c470b99445fbd1 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -109,6 +109,7 @@ void SetupBootstrapObject(Environment* env, BOOTSTRAP_METHOD(_chdir, Chdir); BOOTSTRAP_METHOD(_cpuUsage, CPUUsage); BOOTSTRAP_METHOD(_hrtime, Hrtime); + BOOTSTRAP_METHOD(_hrtimeBigInt, HrtimeBigInt); BOOTSTRAP_METHOD(_memoryUsage, MemoryUsage); BOOTSTRAP_METHOD(_rawDebug, RawDebug); BOOTSTRAP_METHOD(_umask, Umask); diff --git a/src/node_internals.h b/src/node_internals.h index 5371a5502174fd..cd791f8c059caf 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -939,6 +939,7 @@ void Chdir(const v8::FunctionCallbackInfo<v8::Value>& args); void CPUUsage(const v8::FunctionCallbackInfo<v8::Value>& args); void Cwd(const v8::FunctionCallbackInfo<v8::Value>& args); void Hrtime(const v8::FunctionCallbackInfo<v8::Value>& args); +void HrtimeBigInt(const v8::FunctionCallbackInfo<v8::Value>& args); void Kill(const v8::FunctionCallbackInfo<v8::Value>& args); void MemoryUsage(const v8::FunctionCallbackInfo<v8::Value>& args); void RawDebug(const v8::FunctionCallbackInfo<v8::Value>& args); diff --git a/src/node_process.cc b/src/node_process.cc index 3313f85627b16f..04cf577dba538f 100644 --- a/src/node_process.cc +++ b/src/node_process.cc @@ -31,6 +31,7 @@ namespace node { using v8::Array; using v8::ArrayBuffer; +using v8::BigUint64Array; using v8::Float64Array; using v8::FunctionCallbackInfo; using v8::HeapStatistics; @@ -114,7 +115,11 @@ void Cwd(const FunctionCallbackInfo<Value>& args) { args.GetReturnValue().Set(cwd); } + // Hrtime exposes libuv's uv_hrtime() high-resolution timer. + +// This is the legacy version of hrtime before BigInt was introduced in +// JavaScript. // The value returned by uv_hrtime() is a 64-bit int representing nanoseconds, // so this function instead fills in an Uint32Array with 3 entries, // to avoid any integer overflow possibility. @@ -133,6 +138,12 @@ void Hrtime(const FunctionCallbackInfo<Value>& args) { fields[2] = t % NANOS_PER_SEC; } +void HrtimeBigInt(const FunctionCallbackInfo<Value>& args) { + Local<ArrayBuffer> ab = args[0].As<BigUint64Array>()->Buffer(); + uint64_t* fields = static_cast<uint64_t*>(ab->GetContents().Data()); + fields[0] = uv_hrtime(); +} + void Kill(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); diff --git a/test/parallel/test-process-hrtime-bigint.js b/test/parallel/test-process-hrtime-bigint.js new file mode 100644 index 00000000000000..e5ce40a994d815 --- /dev/null +++ b/test/parallel/test-process-hrtime-bigint.js @@ -0,0 +1,14 @@ +'use strict'; + +// Tests that process.hrtime.bigint() works. + +require('../common'); +const assert = require('assert'); + +const start = process.hrtime.bigint(); +assert.strictEqual(typeof start, 'bigint'); + +const end = process.hrtime.bigint(); +assert.strictEqual(typeof end, 'bigint'); + +assert(end - start >= 0n); From 4433ecbf30747c71a52e117a4a086dd0d678d5db Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater <ruben@bridgewater.de> Date: Fri, 25 May 2018 12:11:37 +0200 Subject: [PATCH 077/116] lib: refactor cli table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cli table used multi line template strings which are normally not used in our code base and it also upper cased a regular function name. This is changed by this patch. PR-URL: https://github.com/nodejs/node/pull/20960 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> --- lib/internal/cli_table.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/internal/cli_table.js b/lib/internal/cli_table.js index 4c07d92eebdaa7..f6c711ece8e0ad 100644 --- a/lib/internal/cli_table.js +++ b/lib/internal/cli_table.js @@ -51,11 +51,11 @@ const table = (head, columns) => { for (var i = 0; i < head.length; i++) { const column = columns[i]; for (var j = 0; j < longestColumn; j++) { - if (!rows[j]) + if (rows[j] === undefined) rows[j] = []; - const v = rows[j][i] = HasOwnProperty(column, j) ? column[j] : ''; + const value = rows[j][i] = HasOwnProperty(column, j) ? column[j] : ''; const width = columnWidths[i] || 0; - const counted = countSymbols(v); + const counted = countSymbols(value); columnWidths[i] = Math.max(width, counted); } } @@ -63,19 +63,16 @@ const table = (head, columns) => { const divider = columnWidths.map((i) => tableChars.middleMiddle.repeat(i + 2)); - const tl = tableChars.topLeft; - const tr = tableChars.topRight; - const lm = tableChars.leftMiddle; - let result = `${tl}${divider.join(tableChars.topMiddle)}${tr} -${renderRow(head, columnWidths)} -${lm}${divider.join(tableChars.rowMiddle)}${tableChars.rightMiddle} -`; + let result = `${tableChars.topLeft}${divider.join(tableChars.topMiddle)}` + + `${tableChars.topRight}\n${renderRow(head, columnWidths)}\n` + + `${tableChars.leftMiddle}${divider.join(tableChars.rowMiddle)}` + + `${tableChars.rightMiddle}\n`; for (const row of rows) result += `${renderRow(row, columnWidths)}\n`; - result += `${tableChars.bottomLeft}${ - divider.join(tableChars.bottomMiddle)}${tableChars.bottomRight}`; + result += `${tableChars.bottomLeft}${divider.join(tableChars.bottomMiddle)}` + + tableChars.bottomRight; return result; }; From a09bdb58478db6355a5a22c1dc8fbfbd94773cd2 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater <ruben@bridgewater.de> Date: Fri, 25 May 2018 12:12:40 +0200 Subject: [PATCH 078/116] test: improve console table error output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The former error output was not readable in case of an error. This improves it by splitting the lines and therefore creating a nice and readable diff. PR-URL: https://github.com/nodejs/node/pull/20960 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> --- test/parallel/test-console-table.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-console-table.js b/test/parallel/test-console-table.js index 2b63556acaa478..9ff4641d65923c 100644 --- a/test/parallel/test-console-table.js +++ b/test/parallel/test-console-table.js @@ -17,7 +17,10 @@ function test(data, only, expected) { only = undefined; } console.table(data, only); - assert.strictEqual(queue.shift(), expected.trimLeft()); + assert.deepStrictEqual( + queue.shift().split('\n'), + expected.trimLeft().split('\n') + ); } common.expectsError(() => console.table([], false), { From 466601f47f599e138a18e42c5a0be2dbafd1f8ef Mon Sep 17 00:00:00 2001 From: Daniel Bevenius <daniel.bevenius@gmail.com> Date: Mon, 18 Jun 2018 05:26:43 +0200 Subject: [PATCH 079/116] src: remove .h if -inl.h is already included This commit removes the normal header file include if an internal one is specified as per the CPP_STYLE_GUIDE. PR-URL: https://github.com/nodejs/node/pull/21381 Reviewed-By: Matheus Marchini <matheus@sthima.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> --- src/bootstrapper.cc | 1 - src/callback_scope.cc | 2 -- src/exceptions.cc | 2 -- src/node_encoding.cc | 2 -- src/node_platform.cc | 1 - src/node_process.cc | 2 -- 6 files changed, 10 deletions(-) diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index c470b99445fbd1..8bcc0493f5be6d 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1,5 +1,4 @@ #include "node.h" -#include "env.h" #include "env-inl.h" #include "node_internals.h" #include "v8.h" diff --git a/src/callback_scope.cc b/src/callback_scope.cc index 23e6d5b0632f2c..feb7e23b6e5f84 100644 --- a/src/callback_scope.cc +++ b/src/callback_scope.cc @@ -1,7 +1,5 @@ #include "node.h" -#include "async_wrap.h" #include "async_wrap-inl.h" -#include "env.h" #include "env-inl.h" #include "v8.h" diff --git a/src/exceptions.cc b/src/exceptions.cc index 0007854595a467..4bd5ab13134c86 100644 --- a/src/exceptions.cc +++ b/src/exceptions.cc @@ -1,8 +1,6 @@ #include "node.h" #include "node_internals.h" -#include "env.h" #include "env-inl.h" -#include "util.h" #include "util-inl.h" #include "v8.h" #include "uv.h" diff --git a/src/node_encoding.cc b/src/node_encoding.cc index 467f04e24568cb..d5c6bcab488dd2 100644 --- a/src/node_encoding.cc +++ b/src/node_encoding.cc @@ -1,8 +1,6 @@ #include "node.h" -#include "env.h" #include "env-inl.h" #include "string_bytes.h" -#include "util.h" #include "util-inl.h" #include "v8.h" diff --git a/src/node_platform.cc b/src/node_platform.cc index fdca115e5f8f03..947622a219cdef 100644 --- a/src/node_platform.cc +++ b/src/node_platform.cc @@ -1,7 +1,6 @@ #include "node_platform.h" #include "node_internals.h" -#include "env.h" #include "env-inl.h" #include "util.h" #include <algorithm> diff --git a/src/node_process.cc b/src/node_process.cc index 04cf577dba538f..43982e6f39f769 100644 --- a/src/node_process.cc +++ b/src/node_process.cc @@ -1,8 +1,6 @@ #include "node.h" #include "node_internals.h" -#include "env.h" #include "env-inl.h" -#include "util.h" #include "util-inl.h" #include "uv.h" #include "v8.h" From 51d613db2d3984299fa9f702c96ba2560d040bb9 Mon Sep 17 00:00:00 2001 From: Timothy Gu <timothygu99@gmail.com> Date: Fri, 22 Jun 2018 01:25:12 -0400 Subject: [PATCH 080/116] src: start annotating native code side effect PR-URL: https://github.com/nodejs/node/pull/21458 Refs: https://github.com/nodejs/node/issues/20977 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> --- src/cares_wrap.cc | 6 +-- src/env-inl.h | 70 +++++++++++++++++++++++++++++--- src/env.h | 17 +++++++- src/inspector_js_api.cc | 4 +- src/module_wrap.cc | 10 ++--- src/node.cc | 20 ++++++---- src/node_buffer.cc | 28 ++++++------- src/node_crypto.cc | 88 +++++++++++++++++++++++------------------ src/node_types.cc | 8 ++-- src/node_url.cc | 8 ++-- src/node_util.cc | 15 +++---- src/node_v8.cc | 3 +- src/stream_base-inl.h | 33 ++++++++-------- src/tty_wrap.cc | 8 ++-- 14 files changed, 206 insertions(+), 112 deletions(-) diff --git a/src/cares_wrap.cc b/src/cares_wrap.cc index af564df8053955..69a3d46668193a 100644 --- a/src/cares_wrap.cc +++ b/src/cares_wrap.cc @@ -2148,8 +2148,8 @@ void Initialize(Local<Object> target, env->SetMethod(target, "getaddrinfo", GetAddrInfo); env->SetMethod(target, "getnameinfo", GetNameInfo); - env->SetMethod(target, "isIPv6", IsIPv6); - env->SetMethod(target, "canonicalizeIP", CanonicalizeIP); + env->SetMethodNoSideEffect(target, "isIPv6", IsIPv6); + env->SetMethodNoSideEffect(target, "canonicalizeIP", CanonicalizeIP); env->SetMethod(target, "strerror", StrError); @@ -2206,7 +2206,7 @@ void Initialize(Local<Object> target, env->SetProtoMethod(channel_wrap, "querySoa", Query<QuerySoaWrap>); env->SetProtoMethod(channel_wrap, "getHostByAddr", Query<GetHostByAddrWrap>); - env->SetProtoMethod(channel_wrap, "getServers", GetServers); + env->SetProtoMethodNoSideEffect(channel_wrap, "getServers", GetServers); env->SetProtoMethod(channel_wrap, "setServers", SetServers); env->SetProtoMethod(channel_wrap, "cancel", Cancel); diff --git a/src/env-inl.h b/src/env-inl.h index 8b878431ac65a3..b436b23b678015 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -681,17 +681,41 @@ inline void Environment::ThrowUVException(int errorno, inline v8::Local<v8::FunctionTemplate> Environment::NewFunctionTemplate(v8::FunctionCallback callback, v8::Local<v8::Signature> signature, - v8::ConstructorBehavior behavior) { + v8::ConstructorBehavior behavior, + v8::SideEffectType side_effect_type) { v8::Local<v8::External> external = as_external(); return v8::FunctionTemplate::New(isolate(), callback, external, - signature, 0, behavior); + signature, 0, behavior, side_effect_type); } inline void Environment::SetMethod(v8::Local<v8::Object> that, const char* name, v8::FunctionCallback callback) { v8::Local<v8::Function> function = - NewFunctionTemplate(callback)->GetFunction(); + NewFunctionTemplate(callback, + v8::Local<v8::Signature>(), + // TODO(TimothyGu): Investigate if SetMethod is ever + // used for constructors. + v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasSideEffect)->GetFunction(); + // kInternalized strings are created in the old space. + const v8::NewStringType type = v8::NewStringType::kInternalized; + v8::Local<v8::String> name_string = + v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked(); + that->Set(name_string, function); + function->SetName(name_string); // NODE_SET_METHOD() compatibility. +} + +inline void Environment::SetMethodNoSideEffect(v8::Local<v8::Object> that, + const char* name, + v8::FunctionCallback callback) { + v8::Local<v8::Function> function = + NewFunctionTemplate(callback, + v8::Local<v8::Signature>(), + // TODO(TimothyGu): Investigate if SetMethod is ever + // used for constructors. + v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasNoSideEffect)->GetFunction(); // kInternalized strings are created in the old space. const v8::NewStringType type = v8::NewStringType::kInternalized; v8::Local<v8::String> name_string = @@ -705,7 +729,24 @@ inline void Environment::SetProtoMethod(v8::Local<v8::FunctionTemplate> that, v8::FunctionCallback callback) { v8::Local<v8::Signature> signature = v8::Signature::New(isolate(), that); v8::Local<v8::FunctionTemplate> t = - NewFunctionTemplate(callback, signature, v8::ConstructorBehavior::kThrow); + NewFunctionTemplate(callback, signature, v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasSideEffect); + // kInternalized strings are created in the old space. + const v8::NewStringType type = v8::NewStringType::kInternalized; + v8::Local<v8::String> name_string = + v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked(); + that->PrototypeTemplate()->Set(name_string, t); + t->SetClassName(name_string); // NODE_SET_PROTOTYPE_METHOD() compatibility. +} + +inline void Environment::SetProtoMethodNoSideEffect( + v8::Local<v8::FunctionTemplate> that, + const char* name, + v8::FunctionCallback callback) { + v8::Local<v8::Signature> signature = v8::Signature::New(isolate(), that); + v8::Local<v8::FunctionTemplate> t = + NewFunctionTemplate(callback, signature, v8::ConstructorBehavior::kThrow, + v8::SideEffectType::kHasNoSideEffect); // kInternalized strings are created in the old space. const v8::NewStringType type = v8::NewStringType::kInternalized; v8::Local<v8::String> name_string = @@ -717,7 +758,26 @@ inline void Environment::SetProtoMethod(v8::Local<v8::FunctionTemplate> that, inline void Environment::SetTemplateMethod(v8::Local<v8::FunctionTemplate> that, const char* name, v8::FunctionCallback callback) { - v8::Local<v8::FunctionTemplate> t = NewFunctionTemplate(callback); + v8::Local<v8::FunctionTemplate> t = + NewFunctionTemplate(callback, v8::Local<v8::Signature>(), + v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasSideEffect); + // kInternalized strings are created in the old space. + const v8::NewStringType type = v8::NewStringType::kInternalized; + v8::Local<v8::String> name_string = + v8::String::NewFromUtf8(isolate(), name, type).ToLocalChecked(); + that->Set(name_string, t); + t->SetClassName(name_string); // NODE_SET_METHOD() compatibility. +} + +inline void Environment::SetTemplateMethodNoSideEffect( + v8::Local<v8::FunctionTemplate> that, + const char* name, + v8::FunctionCallback callback) { + v8::Local<v8::FunctionTemplate> t = + NewFunctionTemplate(callback, v8::Local<v8::Signature>(), + v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasNoSideEffect); // kInternalized strings are created in the old space. const v8::NewStringType type = v8::NewStringType::kInternalized; v8::Local<v8::String> name_string = diff --git a/src/env.h b/src/env.h index 38be74542b2599..ea50d019c2327f 100644 --- a/src/env.h +++ b/src/env.h @@ -751,12 +751,15 @@ class Environment { v8::Local<v8::Signature> signature = v8::Local<v8::Signature>(), v8::ConstructorBehavior behavior = - v8::ConstructorBehavior::kAllow); + v8::ConstructorBehavior::kAllow, + v8::SideEffectType side_effect = + v8::SideEffectType::kHasSideEffect); // Convenience methods for NewFunctionTemplate(). inline void SetMethod(v8::Local<v8::Object> that, const char* name, v8::FunctionCallback callback); + inline void SetProtoMethod(v8::Local<v8::FunctionTemplate> that, const char* name, v8::FunctionCallback callback); @@ -764,6 +767,18 @@ class Environment { const char* name, v8::FunctionCallback callback); + // Safe variants denote the function has no side effects. + inline void SetMethodNoSideEffect(v8::Local<v8::Object> that, + const char* name, + v8::FunctionCallback callback); + inline void SetProtoMethodNoSideEffect(v8::Local<v8::FunctionTemplate> that, + const char* name, + v8::FunctionCallback callback); + inline void SetTemplateMethodNoSideEffect( + v8::Local<v8::FunctionTemplate> that, + const char* name, + v8::FunctionCallback callback); + void BeforeExit(void (*cb)(void* arg), void* arg); void RunBeforeExitCallbacks(); void AtExit(void (*cb)(void* arg), void* arg); diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc index 268c25aeb4a233..a8e2e8ecafeefe 100644 --- a/src/inspector_js_api.cc +++ b/src/inspector_js_api.cc @@ -287,7 +287,7 @@ void Initialize(Local<Object> target, Local<Value> unused, if (agent->WillWaitForConnect()) env->SetMethod(target, "callAndPauseOnStart", CallAndPauseOnStart); env->SetMethod(target, "open", Open); - env->SetMethod(target, "url", Url); + env->SetMethodNoSideEffect(target, "url", Url); env->SetMethod(target, "asyncTaskScheduled", AsyncTaskScheduledWrapper); env->SetMethod(target, "asyncTaskCanceled", @@ -298,7 +298,7 @@ void Initialize(Local<Object> target, Local<Value> unused, InvokeAsyncTaskFnWithId<&Agent::AsyncTaskFinished>); env->SetMethod(target, "registerAsyncHook", RegisterAsyncHookWrapper); - env->SetMethod(target, "isEnabled", IsEnabled); + env->SetMethodNoSideEffect(target, "isEnabled", IsEnabled); auto conn_str = FIXED_ONE_BYTE_STRING(env->isolate(), "Connection"); Local<FunctionTemplate> tmpl = diff --git a/src/module_wrap.cc b/src/module_wrap.cc index 7569ef106688e2..3aba52dcde7780 100644 --- a/src/module_wrap.cc +++ b/src/module_wrap.cc @@ -789,11 +789,11 @@ void ModuleWrap::Initialize(Local<Object> target, env->SetProtoMethod(tpl, "link", Link); env->SetProtoMethod(tpl, "instantiate", Instantiate); env->SetProtoMethod(tpl, "evaluate", Evaluate); - env->SetProtoMethod(tpl, "namespace", Namespace); - env->SetProtoMethod(tpl, "getStatus", GetStatus); - env->SetProtoMethod(tpl, "getError", GetError); - env->SetProtoMethod(tpl, "getStaticDependencySpecifiers", - GetStaticDependencySpecifiers); + env->SetProtoMethodNoSideEffect(tpl, "namespace", Namespace); + env->SetProtoMethodNoSideEffect(tpl, "getStatus", GetStatus); + env->SetProtoMethodNoSideEffect(tpl, "getError", GetError); + env->SetProtoMethodNoSideEffect(tpl, "getStaticDependencySpecifiers", + GetStaticDependencySpecifiers); target->Set(FIXED_ONE_BYTE_STRING(isolate, "ModuleWrap"), tpl->GetFunction()); env->SetMethod(target, "resolve", Resolve); diff --git a/src/node.cc b/src/node.cc index ec2d79f9f33280..627f4958e0278c 100644 --- a/src/node.cc +++ b/src/node.cc @@ -160,6 +160,7 @@ using v8::Promise; using v8::PropertyCallbackInfo; using v8::ScriptOrigin; using v8::SealHandleScope; +using v8::SideEffectType; using v8::String; using v8::TryCatch; using v8::Undefined; @@ -1969,7 +1970,10 @@ void SetupProcessObject(Environment* env, title_string, ProcessTitleGetter, env->is_main_thread() ? ProcessTitleSetter : nullptr, - env->as_external()).FromJust()); + env->as_external(), + v8::DEFAULT, + v8::None, + SideEffectType::kHasNoSideEffect).FromJust()); // process.version READONLY_PROPERTY(process, @@ -2279,17 +2283,17 @@ void SetupProcessObject(Environment* env, env->SetMethod(process, "_getActiveHandles", GetActiveHandles); env->SetMethod(process, "_kill", Kill); - env->SetMethod(process, "cwd", Cwd); + env->SetMethodNoSideEffect(process, "cwd", Cwd); env->SetMethod(process, "dlopen", DLOpen); env->SetMethod(process, "reallyExit", Exit); - env->SetMethod(process, "uptime", Uptime); + env->SetMethodNoSideEffect(process, "uptime", Uptime); #if defined(__POSIX__) && !defined(__ANDROID__) && !defined(__CloudABI__) - env->SetMethod(process, "getuid", GetUid); - env->SetMethod(process, "geteuid", GetEUid); - env->SetMethod(process, "getgid", GetGid); - env->SetMethod(process, "getegid", GetEGid); - env->SetMethod(process, "getgroups", GetGroups); + env->SetMethodNoSideEffect(process, "getuid", GetUid); + env->SetMethodNoSideEffect(process, "geteuid", GetEUid); + env->SetMethodNoSideEffect(process, "getgid", GetGid); + env->SetMethodNoSideEffect(process, "getegid", GetEGid); + env->SetMethodNoSideEffect(process, "getgroups", GetGroups); #endif // __POSIX__ && !defined(__ANDROID__) && !defined(__CloudABI__) } diff --git a/src/node_buffer.cc b/src/node_buffer.cc index f6ebf82524d0a8..6e25889d6e4a25 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -1083,12 +1083,12 @@ void SetupBufferJS(const FunctionCallbackInfo<Value>& args) { Local<Object> proto = args[0].As<Object>(); env->set_buffer_prototype_object(proto); - env->SetMethod(proto, "asciiSlice", StringSlice<ASCII>); - env->SetMethod(proto, "base64Slice", StringSlice<BASE64>); - env->SetMethod(proto, "latin1Slice", StringSlice<LATIN1>); - env->SetMethod(proto, "hexSlice", StringSlice<HEX>); - env->SetMethod(proto, "ucs2Slice", StringSlice<UCS2>); - env->SetMethod(proto, "utf8Slice", StringSlice<UTF8>); + env->SetMethodNoSideEffect(proto, "asciiSlice", StringSlice<ASCII>); + env->SetMethodNoSideEffect(proto, "base64Slice", StringSlice<BASE64>); + env->SetMethodNoSideEffect(proto, "latin1Slice", StringSlice<LATIN1>); + env->SetMethodNoSideEffect(proto, "hexSlice", StringSlice<HEX>); + env->SetMethodNoSideEffect(proto, "ucs2Slice", StringSlice<UCS2>); + env->SetMethodNoSideEffect(proto, "utf8Slice", StringSlice<UTF8>); env->SetMethod(proto, "asciiWrite", StringWrite<ASCII>); env->SetMethod(proto, "base64Write", StringWrite<BASE64>); @@ -1116,22 +1116,22 @@ void Initialize(Local<Object> target, Environment* env = Environment::GetCurrent(context); env->SetMethod(target, "setupBufferJS", SetupBufferJS); - env->SetMethod(target, "createFromString", CreateFromString); + env->SetMethodNoSideEffect(target, "createFromString", CreateFromString); - env->SetMethod(target, "byteLengthUtf8", ByteLengthUtf8); + env->SetMethodNoSideEffect(target, "byteLengthUtf8", ByteLengthUtf8); env->SetMethod(target, "copy", Copy); - env->SetMethod(target, "compare", Compare); - env->SetMethod(target, "compareOffset", CompareOffset); + env->SetMethodNoSideEffect(target, "compare", Compare); + env->SetMethodNoSideEffect(target, "compareOffset", CompareOffset); env->SetMethod(target, "fill", Fill); - env->SetMethod(target, "indexOfBuffer", IndexOfBuffer); - env->SetMethod(target, "indexOfNumber", IndexOfNumber); - env->SetMethod(target, "indexOfString", IndexOfString); + env->SetMethodNoSideEffect(target, "indexOfBuffer", IndexOfBuffer); + env->SetMethodNoSideEffect(target, "indexOfNumber", IndexOfNumber); + env->SetMethodNoSideEffect(target, "indexOfString", IndexOfString); env->SetMethod(target, "swap16", Swap16); env->SetMethod(target, "swap32", Swap32); env->SetMethod(target, "swap64", Swap64); - env->SetMethod(target, "encodeUtf8String", EncodeUtf8String); + env->SetMethodNoSideEffect(target, "encodeUtf8String", EncodeUtf8String); target->Set(env->context(), FIXED_ONE_BYTE_STRING(env->isolate(), "kMaxLength"), diff --git a/src/node_crypto.cc b/src/node_crypto.cc index 2c3f983d39e9e5..3380961c8ebdfb 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -62,6 +62,7 @@ namespace crypto { using v8::Array; using v8::Boolean; +using v8::ConstructorBehavior; using v8::Context; using v8::DontDelete; using v8::EscapableHandleScope; @@ -83,6 +84,7 @@ using v8::Null; using v8::Object; using v8::PropertyAttribute; using v8::ReadOnly; +using v8::SideEffectType; using v8::Signature; using v8::String; using v8::Uint32; @@ -345,12 +347,12 @@ void SecureContext::Initialize(Environment* env, Local<Object> target) { #ifndef OPENSSL_NO_ENGINE env->SetProtoMethod(t, "setClientCertEngine", SetClientCertEngine); #endif // !OPENSSL_NO_ENGINE - env->SetProtoMethod(t, "getTicketKeys", GetTicketKeys); + env->SetProtoMethodNoSideEffect(t, "getTicketKeys", GetTicketKeys); env->SetProtoMethod(t, "setTicketKeys", SetTicketKeys); env->SetProtoMethod(t, "setFreeListLength", SetFreeListLength); env->SetProtoMethod(t, "enableTicketKeyCallback", EnableTicketKeyCallback); - env->SetProtoMethod(t, "getCertificate", GetCertificate<true>); - env->SetProtoMethod(t, "getIssuer", GetCertificate<false>); + env->SetProtoMethodNoSideEffect(t, "getCertificate", GetCertificate<true>); + env->SetProtoMethodNoSideEffect(t, "getIssuer", GetCertificate<false>); t->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "kTicketKeyReturnIndex"), Integer::NewFromUnsigned(env->isolate(), kTicketKeyReturnIndex)); @@ -1366,32 +1368,34 @@ template <class Base> void SSLWrap<Base>::AddMethods(Environment* env, Local<FunctionTemplate> t) { HandleScope scope(env->isolate()); - env->SetProtoMethod(t, "getPeerCertificate", GetPeerCertificate); - env->SetProtoMethod(t, "getFinished", GetFinished); - env->SetProtoMethod(t, "getPeerFinished", GetPeerFinished); - env->SetProtoMethod(t, "getSession", GetSession); + env->SetProtoMethodNoSideEffect(t, "getPeerCertificate", GetPeerCertificate); + env->SetProtoMethodNoSideEffect(t, "getFinished", GetFinished); + env->SetProtoMethodNoSideEffect(t, "getPeerFinished", GetPeerFinished); + env->SetProtoMethodNoSideEffect(t, "getSession", GetSession); env->SetProtoMethod(t, "setSession", SetSession); env->SetProtoMethod(t, "loadSession", LoadSession); - env->SetProtoMethod(t, "isSessionReused", IsSessionReused); - env->SetProtoMethod(t, "isInitFinished", IsInitFinished); - env->SetProtoMethod(t, "verifyError", VerifyError); - env->SetProtoMethod(t, "getCurrentCipher", GetCurrentCipher); + env->SetProtoMethodNoSideEffect(t, "isSessionReused", IsSessionReused); + env->SetProtoMethodNoSideEffect(t, "isInitFinished", IsInitFinished); + env->SetProtoMethodNoSideEffect(t, "verifyError", VerifyError); + env->SetProtoMethodNoSideEffect(t, "getCurrentCipher", GetCurrentCipher); env->SetProtoMethod(t, "endParser", EndParser); env->SetProtoMethod(t, "certCbDone", CertCbDone); env->SetProtoMethod(t, "renegotiate", Renegotiate); env->SetProtoMethod(t, "shutdownSSL", Shutdown); - env->SetProtoMethod(t, "getTLSTicket", GetTLSTicket); + env->SetProtoMethodNoSideEffect(t, "getTLSTicket", GetTLSTicket); env->SetProtoMethod(t, "newSessionDone", NewSessionDone); env->SetProtoMethod(t, "setOCSPResponse", SetOCSPResponse); env->SetProtoMethod(t, "requestOCSP", RequestOCSP); - env->SetProtoMethod(t, "getEphemeralKeyInfo", GetEphemeralKeyInfo); - env->SetProtoMethod(t, "getProtocol", GetProtocol); + env->SetProtoMethodNoSideEffect(t, "getEphemeralKeyInfo", + GetEphemeralKeyInfo); + env->SetProtoMethodNoSideEffect(t, "getProtocol", GetProtocol); #ifdef SSL_set_max_send_fragment env->SetProtoMethod(t, "setMaxSendFragment", SetMaxSendFragment); #endif // SSL_set_max_send_fragment - env->SetProtoMethod(t, "getALPNNegotiatedProtocol", GetALPNNegotiatedProto); + env->SetProtoMethodNoSideEffect(t, "getALPNNegotiatedProtocol", + GetALPNNegotiatedProto); env->SetProtoMethod(t, "setALPNProtocols", SetALPNProtocols); } @@ -2559,7 +2563,7 @@ void CipherBase::Initialize(Environment* env, Local<Object> target) { env->SetProtoMethod(t, "update", Update); env->SetProtoMethod(t, "final", Final); env->SetProtoMethod(t, "setAutoPadding", SetAutoPadding); - env->SetProtoMethod(t, "getAuthTag", GetAuthTag); + env->SetProtoMethodNoSideEffect(t, "getAuthTag", GetAuthTag); env->SetProtoMethod(t, "setAuthTag", SetAuthTag); env->SetProtoMethod(t, "setAAD", SetAAD); @@ -3916,10 +3920,10 @@ void DiffieHellman::Initialize(Environment* env, Local<Object> target) { env->SetProtoMethod(t, "generateKeys", GenerateKeys); env->SetProtoMethod(t, "computeSecret", ComputeSecret); - env->SetProtoMethod(t, "getPrime", GetPrime); - env->SetProtoMethod(t, "getGenerator", GetGenerator); - env->SetProtoMethod(t, "getPublicKey", GetPublicKey); - env->SetProtoMethod(t, "getPrivateKey", GetPrivateKey); + env->SetProtoMethodNoSideEffect(t, "getPrime", GetPrime); + env->SetProtoMethodNoSideEffect(t, "getGenerator", GetGenerator); + env->SetProtoMethodNoSideEffect(t, "getPublicKey", GetPublicKey); + env->SetProtoMethodNoSideEffect(t, "getPrivateKey", GetPrivateKey); env->SetProtoMethod(t, "setPublicKey", SetPublicKey); env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey); @@ -3927,7 +3931,11 @@ void DiffieHellman::Initialize(Environment* env, Local<Object> target) { FunctionTemplate::New(env->isolate(), DiffieHellman::VerifyErrorGetter, env->as_external(), - Signature::New(env->isolate(), t)); + Signature::New(env->isolate(), t), + /* length */ 0, + // TODO(TimothyGu): should be deny + ConstructorBehavior::kAllow, + SideEffectType::kHasNoSideEffect); t->InstanceTemplate()->SetAccessorProperty( env->verify_error_string(), @@ -3943,16 +3951,20 @@ void DiffieHellman::Initialize(Environment* env, Local<Object> target) { env->SetProtoMethod(t2, "generateKeys", GenerateKeys); env->SetProtoMethod(t2, "computeSecret", ComputeSecret); - env->SetProtoMethod(t2, "getPrime", GetPrime); - env->SetProtoMethod(t2, "getGenerator", GetGenerator); - env->SetProtoMethod(t2, "getPublicKey", GetPublicKey); - env->SetProtoMethod(t2, "getPrivateKey", GetPrivateKey); + env->SetProtoMethodNoSideEffect(t2, "getPrime", GetPrime); + env->SetProtoMethodNoSideEffect(t2, "getGenerator", GetGenerator); + env->SetProtoMethodNoSideEffect(t2, "getPublicKey", GetPublicKey); + env->SetProtoMethodNoSideEffect(t2, "getPrivateKey", GetPrivateKey); Local<FunctionTemplate> verify_error_getter_templ2 = FunctionTemplate::New(env->isolate(), DiffieHellman::VerifyErrorGetter, env->as_external(), - Signature::New(env->isolate(), t2)); + Signature::New(env->isolate(), t2), + /* length */ 0, + // TODO(TimothyGu): should be deny + ConstructorBehavior::kAllow, + SideEffectType::kHasNoSideEffect); t2->InstanceTemplate()->SetAccessorProperty( env->verify_error_string(), @@ -4304,8 +4316,8 @@ void ECDH::Initialize(Environment* env, Local<Object> target) { env->SetProtoMethod(t, "generateKeys", GenerateKeys); env->SetProtoMethod(t, "computeSecret", ComputeSecret); - env->SetProtoMethod(t, "getPublicKey", GetPublicKey); - env->SetProtoMethod(t, "getPrivateKey", GetPrivateKey); + env->SetProtoMethodNoSideEffect(t, "getPublicKey", GetPublicKey); + env->SetProtoMethodNoSideEffect(t, "getPrivateKey", GetPrivateKey); env->SetProtoMethod(t, "setPublicKey", SetPublicKey); env->SetProtoMethod(t, "setPrivateKey", SetPrivateKey); @@ -5198,27 +5210,27 @@ void Initialize(Local<Object> target, Sign::Initialize(env, target); Verify::Initialize(env, target); - env->SetMethod(target, "certVerifySpkac", VerifySpkac); - env->SetMethod(target, "certExportPublicKey", ExportPublicKey); - env->SetMethod(target, "certExportChallenge", ExportChallenge); + env->SetMethodNoSideEffect(target, "certVerifySpkac", VerifySpkac); + env->SetMethodNoSideEffect(target, "certExportPublicKey", ExportPublicKey); + env->SetMethodNoSideEffect(target, "certExportChallenge", ExportChallenge); - env->SetMethod(target, "ECDHConvertKey", ConvertKey); + env->SetMethodNoSideEffect(target, "ECDHConvertKey", ConvertKey); #ifndef OPENSSL_NO_ENGINE env->SetMethod(target, "setEngine", SetEngine); #endif // !OPENSSL_NO_ENGINE #ifdef NODE_FIPS_MODE - env->SetMethod(target, "getFipsCrypto", GetFipsCrypto); + env->SetMethodNoSideEffect(target, "getFipsCrypto", GetFipsCrypto); env->SetMethod(target, "setFipsCrypto", SetFipsCrypto); #endif env->SetMethod(target, "pbkdf2", PBKDF2); env->SetMethod(target, "randomBytes", RandomBytes); - env->SetMethod(target, "timingSafeEqual", TimingSafeEqual); - env->SetMethod(target, "getSSLCiphers", GetSSLCiphers); - env->SetMethod(target, "getCiphers", GetCiphers); - env->SetMethod(target, "getHashes", GetHashes); - env->SetMethod(target, "getCurves", GetCurves); + env->SetMethodNoSideEffect(target, "timingSafeEqual", TimingSafeEqual); + env->SetMethodNoSideEffect(target, "getSSLCiphers", GetSSLCiphers); + env->SetMethodNoSideEffect(target, "getCiphers", GetCiphers); + env->SetMethodNoSideEffect(target, "getHashes", GetHashes); + env->SetMethodNoSideEffect(target, "getCurves", GetCurves); env->SetMethod(target, "publicEncrypt", PublicKeyCipher::Cipher<PublicKeyCipher::kPublic, EVP_PKEY_encrypt_init, diff --git a/src/node_types.cc b/src/node_types.cc index 5dac1f6d275b63..4872491c924989 100644 --- a/src/node_types.cc +++ b/src/node_types.cc @@ -56,13 +56,13 @@ void InitializeTypes(Local<Object> target, Local<Context> context) { Environment* env = Environment::GetCurrent(context); -#define V(type) env->SetMethod(target, \ - "is" #type, \ - Is##type); +#define V(type) env->SetMethodNoSideEffect(target, \ + "is" #type, \ + Is##type); VALUE_METHOD_MAP(V) #undef V - env->SetMethod(target, "isAnyArrayBuffer", IsAnyArrayBuffer); + env->SetMethodNoSideEffect(target, "isAnyArrayBuffer", IsAnyArrayBuffer); } } // anonymous namespace diff --git a/src/node_url.cc b/src/node_url.cc index 82c093d516bc4a..1cdb179ed254f5 100644 --- a/src/node_url.cc +++ b/src/node_url.cc @@ -2334,10 +2334,10 @@ static void Initialize(Local<Object> target, void* priv) { Environment* env = Environment::GetCurrent(context); env->SetMethod(target, "parse", Parse); - env->SetMethod(target, "encodeAuth", EncodeAuthSet); - env->SetMethod(target, "toUSVString", ToUSVString); - env->SetMethod(target, "domainToASCII", DomainToASCII); - env->SetMethod(target, "domainToUnicode", DomainToUnicode); + env->SetMethodNoSideEffect(target, "encodeAuth", EncodeAuthSet); + env->SetMethodNoSideEffect(target, "toUSVString", ToUSVString); + env->SetMethodNoSideEffect(target, "domainToASCII", DomainToASCII); + env->SetMethodNoSideEffect(target, "domainToUnicode", DomainToUnicode); env->SetMethod(target, "setURLConstructor", SetURLConstructor); #define XX(name, _) NODE_DEFINE_CONSTANT(target, name); diff --git a/src/node_util.cc b/src/node_util.cc index 2db68586459ab2..724bb3603cfddd 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -212,18 +212,19 @@ void Initialize(Local<Object> target, V(kRejected); #undef V - env->SetMethod(target, "getHiddenValue", GetHiddenValue); + env->SetMethodNoSideEffect(target, "getHiddenValue", GetHiddenValue); env->SetMethod(target, "setHiddenValue", SetHiddenValue); - env->SetMethod(target, "getPromiseDetails", GetPromiseDetails); - env->SetMethod(target, "getProxyDetails", GetProxyDetails); - env->SetMethod(target, "safeToString", SafeToString); - env->SetMethod(target, "previewEntries", PreviewEntries); + env->SetMethodNoSideEffect(target, "getPromiseDetails", GetPromiseDetails); + env->SetMethodNoSideEffect(target, "getProxyDetails", GetProxyDetails); + env->SetMethodNoSideEffect(target, "safeToString", SafeToString); + env->SetMethodNoSideEffect(target, "previewEntries", PreviewEntries); env->SetMethod(target, "startSigintWatchdog", StartSigintWatchdog); env->SetMethod(target, "stopSigintWatchdog", StopSigintWatchdog); - env->SetMethod(target, "watchdogHasPendingSigint", WatchdogHasPendingSigint); + env->SetMethodNoSideEffect(target, "watchdogHasPendingSigint", + WatchdogHasPendingSigint); - env->SetMethod(target, "createPromise", CreatePromise); + env->SetMethodNoSideEffect(target, "createPromise", CreatePromise); env->SetMethod(target, "promiseResolve", PromiseResolve); env->SetMethod(target, "promiseReject", PromiseReject); diff --git a/src/node_v8.cc b/src/node_v8.cc index d546eeba93f4d9..fb0a9fea1e5d27 100644 --- a/src/node_v8.cc +++ b/src/node_v8.cc @@ -122,7 +122,8 @@ void Initialize(Local<Object> target, Local<Context> context) { Environment* env = Environment::GetCurrent(context); - env->SetMethod(target, "cachedDataVersionTag", CachedDataVersionTag); + env->SetMethodNoSideEffect(target, "cachedDataVersionTag", + CachedDataVersionTag); env->SetMethod(target, "updateHeapStatisticsArrayBuffer", diff --git a/src/stream_base-inl.h b/src/stream_base-inl.h index b53d3e5979c2a5..027b938d30df1c 100644 --- a/src/stream_base-inl.h +++ b/src/stream_base-inl.h @@ -275,29 +275,30 @@ void StreamBase::AddMethods(Environment* env, Local<FunctionTemplate> t) { Local<Signature> signature = Signature::New(env->isolate(), t); + // TODO(TimothyGu): None of these should have ConstructorBehavior::kAllow. Local<FunctionTemplate> get_fd_templ = - FunctionTemplate::New(env->isolate(), - GetFD<Base>, - env->as_external(), - signature); + env->NewFunctionTemplate(GetFD<Base>, + signature, + v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasNoSideEffect); Local<FunctionTemplate> get_external_templ = - FunctionTemplate::New(env->isolate(), - GetExternal<Base>, - env->as_external(), - signature); + env->NewFunctionTemplate(GetExternal<Base>, + signature, + v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasNoSideEffect); Local<FunctionTemplate> get_bytes_read_templ = - FunctionTemplate::New(env->isolate(), - GetBytesRead<Base>, - env->as_external(), - signature); + env->NewFunctionTemplate(GetBytesRead<Base>, + signature, + v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasNoSideEffect); Local<FunctionTemplate> get_bytes_written_templ = - FunctionTemplate::New(env->isolate(), - GetBytesWritten<Base>, - env->as_external(), - signature); + env->NewFunctionTemplate(GetBytesWritten<Base>, + signature, + v8::ConstructorBehavior::kAllow, + v8::SideEffectType::kHasNoSideEffect); t->PrototypeTemplate()->SetAccessorProperty(env->fd_string(), get_fd_templ, diff --git a/src/tty_wrap.cc b/src/tty_wrap.cc index 175b32879bbb83..83b6e34d630e73 100644 --- a/src/tty_wrap.cc +++ b/src/tty_wrap.cc @@ -58,15 +58,15 @@ void TTYWrap::Initialize(Local<Object> target, env->SetProtoMethod(t, "close", HandleWrap::Close); env->SetProtoMethod(t, "unref", HandleWrap::Unref); env->SetProtoMethod(t, "ref", HandleWrap::Ref); - env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef); + env->SetProtoMethodNoSideEffect(t, "hasRef", HandleWrap::HasRef); LibuvStreamWrap::AddMethods(env, t); - env->SetProtoMethod(t, "getWindowSize", TTYWrap::GetWindowSize); + env->SetProtoMethodNoSideEffect(t, "getWindowSize", TTYWrap::GetWindowSize); env->SetProtoMethod(t, "setRawMode", SetRawMode); - env->SetMethod(target, "isTTY", IsTTY); - env->SetMethod(target, "guessHandleType", GuessHandleType); + env->SetMethodNoSideEffect(target, "isTTY", IsTTY); + env->SetMethodNoSideEffect(target, "guessHandleType", GuessHandleType); target->Set(ttyString, t->GetFunction()); env->set_tty_constructor_template(t); From 4ed5d1a62399d64fef64adb1e64654500126fe12 Mon Sep 17 00:00:00 2001 From: Jon Moss <me@jonathanmoss.me> Date: Wed, 11 Jul 2018 13:53:05 -0400 Subject: [PATCH 081/116] src: add HandleWrap::AddWrapMethods Extracts common setters to a single location PR-URL: https://github.com/nodejs/node/pull/21769 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> --- src/handle_wrap.cc | 10 ++++++++++ src/handle_wrap.h | 3 +++ src/node_messaging.cc | 5 +---- src/node_stat_watcher.cc | 6 ++---- src/pipe_wrap.cc | 7 +------ src/process_wrap.cc | 7 +------ src/signal_wrap.cc | 6 ++---- src/tcp_wrap.cc | 8 +------- src/timer_wrap.cc | 6 +----- src/tty_wrap.cc | 7 +------ src/udp_wrap.cc | 6 +----- 11 files changed, 24 insertions(+), 47 deletions(-) diff --git a/src/handle_wrap.cc b/src/handle_wrap.cc index 4c2a33aa84459d..9281300146c4f3 100644 --- a/src/handle_wrap.cc +++ b/src/handle_wrap.cc @@ -29,6 +29,7 @@ namespace node { using v8::Context; using v8::FunctionCallbackInfo; +using v8::FunctionTemplate; using v8::HandleScope; using v8::Local; using v8::Object; @@ -130,4 +131,13 @@ void HandleWrap::OnClose(uv_handle_t* handle) { } +void HandleWrap::AddWrapMethods(Environment* env, + Local<FunctionTemplate> t) { + env->SetProtoMethod(t, "close", HandleWrap::Close); + env->SetProtoMethodNoSideEffect(t, "hasRef", HandleWrap::HasRef); + env->SetProtoMethod(t, "ref", HandleWrap::Ref); + env->SetProtoMethod(t, "unref", HandleWrap::Unref); +} + + } // namespace node diff --git a/src/handle_wrap.h b/src/handle_wrap.h index bd7ef4000bad6f..443d28bf523933 100644 --- a/src/handle_wrap.h +++ b/src/handle_wrap.h @@ -73,6 +73,9 @@ class HandleWrap : public AsyncWrap { virtual void Close( v8::Local<v8::Value> close_callback = v8::Local<v8::Value>()); + static void AddWrapMethods(Environment* env, + v8::Local<v8::FunctionTemplate> constructor); + protected: HandleWrap(Environment* env, v8::Local<v8::Object> object, diff --git a/src/node_messaging.cc b/src/node_messaging.cc index c28d0d4ea66560..20e0c7673b8fa9 100644 --- a/src/node_messaging.cc +++ b/src/node_messaging.cc @@ -724,15 +724,12 @@ MaybeLocal<Function> GetMessagePortConstructor( m->InstanceTemplate()->SetInternalFieldCount(1); AsyncWrap::AddWrapMethods(env, m); + HandleWrap::AddWrapMethods(env, m); env->SetProtoMethod(m, "postMessage", MessagePort::PostMessage); env->SetProtoMethod(m, "start", MessagePort::Start); env->SetProtoMethod(m, "stop", MessagePort::Stop); env->SetProtoMethod(m, "drain", MessagePort::Drain); - env->SetProtoMethod(m, "close", HandleWrap::Close); - env->SetProtoMethod(m, "unref", HandleWrap::Unref); - env->SetProtoMethod(m, "ref", HandleWrap::Ref); - env->SetProtoMethod(m, "hasRef", HandleWrap::HasRef); env->set_message_port_constructor_template(m); } diff --git a/src/node_stat_watcher.cc b/src/node_stat_watcher.cc index 767bc024e46be1..5e47476fd97a0c 100644 --- a/src/node_stat_watcher.cc +++ b/src/node_stat_watcher.cc @@ -52,11 +52,9 @@ void StatWatcher::Initialize(Environment* env, Local<Object> target) { t->SetClassName(statWatcherString); AsyncWrap::AddWrapMethods(env, t); + HandleWrap::AddWrapMethods(env, t); + env->SetProtoMethod(t, "start", StatWatcher::Start); - env->SetProtoMethod(t, "close", HandleWrap::Close); - env->SetProtoMethod(t, "ref", HandleWrap::Ref); - env->SetProtoMethod(t, "unref", HandleWrap::Unref); - env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef); target->Set(statWatcherString, t->GetFunction()); } diff --git a/src/pipe_wrap.cc b/src/pipe_wrap.cc index e2cc114479d5a1..a6044b6b267e47 100644 --- a/src/pipe_wrap.cc +++ b/src/pipe_wrap.cc @@ -77,12 +77,7 @@ void PipeWrap::Initialize(Local<Object> target, t->InstanceTemplate()->SetInternalFieldCount(1); AsyncWrap::AddWrapMethods(env, t); - - env->SetProtoMethod(t, "close", HandleWrap::Close); - env->SetProtoMethod(t, "unref", HandleWrap::Unref); - env->SetProtoMethod(t, "ref", HandleWrap::Ref); - env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef); - + HandleWrap::AddWrapMethods(env, t); LibuvStreamWrap::AddMethods(env, t); env->SetProtoMethod(t, "bind", Bind); diff --git a/src/process_wrap.cc b/src/process_wrap.cc index 3219b1a40b9c16..b9a20c34a770ba 100644 --- a/src/process_wrap.cc +++ b/src/process_wrap.cc @@ -58,16 +58,11 @@ class ProcessWrap : public HandleWrap { constructor->SetClassName(processString); AsyncWrap::AddWrapMethods(env, constructor); - - env->SetProtoMethod(constructor, "close", HandleWrap::Close); + HandleWrap::AddWrapMethods(env, constructor); env->SetProtoMethod(constructor, "spawn", Spawn); env->SetProtoMethod(constructor, "kill", Kill); - env->SetProtoMethod(constructor, "ref", HandleWrap::Ref); - env->SetProtoMethod(constructor, "unref", HandleWrap::Unref); - env->SetProtoMethod(constructor, "hasRef", HandleWrap::HasRef); - target->Set(processString, constructor->GetFunction()); } diff --git a/src/signal_wrap.cc b/src/signal_wrap.cc index f44b232a1a4ad2..5568d22e4c6228 100644 --- a/src/signal_wrap.cc +++ b/src/signal_wrap.cc @@ -52,10 +52,8 @@ class SignalWrap : public HandleWrap { constructor->SetClassName(signalString); AsyncWrap::AddWrapMethods(env, constructor); - env->SetProtoMethod(constructor, "close", HandleWrap::Close); - env->SetProtoMethod(constructor, "ref", HandleWrap::Ref); - env->SetProtoMethod(constructor, "unref", HandleWrap::Unref); - env->SetProtoMethod(constructor, "hasRef", HandleWrap::HasRef); + HandleWrap::AddWrapMethods(env, constructor); + env->SetProtoMethod(constructor, "start", Start); env->SetProtoMethod(constructor, "stop", Stop); diff --git a/src/tcp_wrap.cc b/src/tcp_wrap.cc index aa130d22e02689..805e566bfab08b 100644 --- a/src/tcp_wrap.cc +++ b/src/tcp_wrap.cc @@ -86,13 +86,7 @@ void TCPWrap::Initialize(Local<Object> target, t->InstanceTemplate()->Set(env->onconnection_string(), Null(env->isolate())); AsyncWrap::AddWrapMethods(env, t, AsyncWrap::kFlagHasReset); - - env->SetProtoMethod(t, "close", HandleWrap::Close); - - env->SetProtoMethod(t, "ref", HandleWrap::Ref); - env->SetProtoMethod(t, "unref", HandleWrap::Unref); - env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef); - + HandleWrap::AddWrapMethods(env, t); LibuvStreamWrap::AddMethods(env, t); env->SetProtoMethod(t, "open", Open); diff --git a/src/timer_wrap.cc b/src/timer_wrap.cc index 9bce195203d686..7af6d13c61f7d2 100644 --- a/src/timer_wrap.cc +++ b/src/timer_wrap.cc @@ -55,11 +55,7 @@ class TimerWrap : public HandleWrap { env->SetTemplateMethod(constructor, "now", Now); AsyncWrap::AddWrapMethods(env, constructor); - - env->SetProtoMethod(constructor, "close", HandleWrap::Close); - env->SetProtoMethod(constructor, "ref", HandleWrap::Ref); - env->SetProtoMethod(constructor, "unref", HandleWrap::Unref); - env->SetProtoMethod(constructor, "hasRef", HandleWrap::HasRef); + HandleWrap::AddWrapMethods(env, constructor); env->SetProtoMethod(constructor, "start", Start); env->SetProtoMethod(constructor, "stop", Stop); diff --git a/src/tty_wrap.cc b/src/tty_wrap.cc index 83b6e34d630e73..39d7ca1474ff8a 100644 --- a/src/tty_wrap.cc +++ b/src/tty_wrap.cc @@ -54,12 +54,7 @@ void TTYWrap::Initialize(Local<Object> target, t->InstanceTemplate()->SetInternalFieldCount(1); AsyncWrap::AddWrapMethods(env, t); - - env->SetProtoMethod(t, "close", HandleWrap::Close); - env->SetProtoMethod(t, "unref", HandleWrap::Unref); - env->SetProtoMethod(t, "ref", HandleWrap::Ref); - env->SetProtoMethodNoSideEffect(t, "hasRef", HandleWrap::HasRef); - + HandleWrap::AddWrapMethods(env, t); LibuvStreamWrap::AddMethods(env, t); env->SetProtoMethodNoSideEffect(t, "getWindowSize", TTYWrap::GetWindowSize); diff --git a/src/udp_wrap.cc b/src/udp_wrap.cc index 49f66914e2aa81..e5243319a553e7 100644 --- a/src/udp_wrap.cc +++ b/src/udp_wrap.cc @@ -119,7 +119,6 @@ void UDPWrap::Initialize(Local<Object> target, env->SetProtoMethod(t, "send", Send); env->SetProtoMethod(t, "bind6", Bind6); env->SetProtoMethod(t, "send6", Send6); - env->SetProtoMethod(t, "close", Close); env->SetProtoMethod(t, "recvStart", RecvStart); env->SetProtoMethod(t, "recvStop", RecvStop); env->SetProtoMethod(t, "getsockname", @@ -133,11 +132,8 @@ void UDPWrap::Initialize(Local<Object> target, env->SetProtoMethod(t, "setTTL", SetTTL); env->SetProtoMethod(t, "bufferSize", BufferSize); - env->SetProtoMethod(t, "ref", HandleWrap::Ref); - env->SetProtoMethod(t, "unref", HandleWrap::Unref); - env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef); - AsyncWrap::AddWrapMethods(env, t); + HandleWrap::AddWrapMethods(env, t); target->Set(udpString, t->GetFunction()); env->set_udp_constructor_function(t->GetFunction()); From d33281b36f9e4b5c8585209e501fc6c2e1f1b0c3 Mon Sep 17 00:00:00 2001 From: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Date: Sat, 14 Jul 2018 15:10:10 +0300 Subject: [PATCH 082/116] doc: prevent some redirections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace some redirected URLs with the final ones. PR-URL: https://github.com/nodejs/node/pull/21811 Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Jon Moss <me@jonathanmoss.me> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> --- doc/api/async_hooks.md | 2 +- doc/api/crypto.md | 6 +++--- doc/api/deprecations.md | 2 +- doc/api/fs.md | 8 ++++---- doc/api/index.md | 2 +- doc/api/intl.md | 2 +- doc/api/path.md | 2 +- doc/api/process.md | 2 +- doc/api/punycode.md | 2 +- doc/api/repl.md | 4 ++-- doc/api/timers.md | 2 +- doc/api/tls.md | 2 +- doc/api/util.md | 2 +- doc/api/v8.md | 2 +- 14 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/api/async_hooks.md b/doc/api/async_hooks.md index 6e5971c51d10c9..aa9e133b43e3f7 100644 --- a/doc/api/async_hooks.md +++ b/doc/api/async_hooks.md @@ -726,6 +726,6 @@ never be called. [`destroy` callback]: #async_hooks_destroy_asyncid [`init` callback]: #async_hooks_init_asyncid_type_triggerasyncid_resource [Hook Callbacks]: #async_hooks_hook_callbacks -[PromiseHooks]: https://docs.google.com/document/d/1rda3yKGHimKIhg5YeoAmCOtyURgsbTH_qaYR79FELlk +[PromiseHooks]: https://docs.google.com/document/d/1rda3yKGHimKIhg5YeoAmCOtyURgsbTH_qaYR79FELlk/edit [promise execution tracking]: #async_hooks_promise_execution_tracking [`Worker`]: worker_threads.html#worker_threads_class_worker diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 8482c80ce6110d..376a8c8a223d93 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -2762,9 +2762,9 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL. [Crypto Constants]: #crypto_crypto_constants_1 [HTML 5.2]: https://www.w3.org/TR/html52/changes.html#features-removed [HTML5's `keygen` element]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/keygen -[NIST SP 800-131A]: http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf -[NIST SP 800-132]: http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf -[NIST SP 800-38D]: http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +[NIST SP 800-131A]: https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf +[NIST SP 800-132]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf +[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf [Nonce-Disrespecting Adversaries]: https://github.com/nonce-disrespect/nonce-disrespect [OpenSSL's SPKAC implementation]: https://www.openssl.org/docs/man1.1.0/apps/openssl-spkac.html [RFC 2412]: https://www.rfc-editor.org/rfc/rfc2412.txt diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md index 5aee444a6d6883..d771a3e0e7a02d 100644 --- a/doc/api/deprecations.md +++ b/doc/api/deprecations.md @@ -1070,5 +1070,5 @@ The option `produceCachedData` has been deprecated. Use [alloc_unsafe_size]: buffer.html#buffer_class_method_buffer_allocunsafe_size [from_arraybuffer]: buffer.html#buffer_class_method_buffer_from_arraybuffer_byteoffset_length [from_string_encoding]: buffer.html#buffer_class_method_buffer_from_string_encoding -[NIST SP 800-38D]: http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf +[NIST SP 800-38D]: https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38d.pdf [`REPLServer.clearBufferedCommand()`]: repl.html#repl_replserver_clearbufferedcommand diff --git a/doc/api/fs.md b/doc/api/fs.md index 872820a991f170..d3f981cf79964d 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -4604,7 +4604,7 @@ the file contents. [`Buffer.byteLength`]: buffer.html#buffer_class_method_buffer_bytelength_string_encoding [`Buffer`]: buffer.html#buffer_buffer [`FSEvents`]: https://developer.apple.com/documentation/coreservices/file_system_events -[`ReadDirectoryChangesW`]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx +[`ReadDirectoryChangesW`]: https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-readdirectorychangesw [`ReadStream`]: #fs_class_fs_readstream [`URL`]: url.html#url_the_whatwg_url_api [`UV_THREADPOOL_SIZE`]: cli.html#cli_uv_threadpool_size_size @@ -4644,13 +4644,13 @@ the file contents. [Caveats]: #fs_caveats [Common System Errors]: errors.html#errors_common_system_errors [FS Constants]: #fs_fs_constants_1 -[MDN-Date]: https://developer.mozilla.org/en-US/JavaScript/Reference/Global_Objects/Date +[MDN-Date]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date [MDN-Number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type [MSDN-Rel-Path]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#fully-qualified-vs-relative-paths [Readable Streams]: stream.html#stream_class_stream_readable [Writable Stream]: stream.html#stream_class_stream_writable [inode]: https://en.wikipedia.org/wiki/Inode -[Naming Files, Paths, and Namespaces]: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx -[MSDN-Using-Streams]: https://msdn.microsoft.com/en-us/library/windows/desktop/bb540537.aspx +[Naming Files, Paths, and Namespaces]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file +[MSDN-Using-Streams]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/using-streams [support of file system `flags`]: #fs_file_system_flags [File Access Constants]: #fs_file_access_constants diff --git a/doc/api/index.md b/doc/api/index.md index 0b43097bf294de..5ba0da6a7fb5aa 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -61,4 +61,4 @@ <div class="line"></div> * [GitHub Repo & Issue Tracker](https://github.com/nodejs/node) -* [Mailing List](https://groups.google.com/group/nodejs) +* [Mailing List](https://groups.google.com/forum/#!forum/nodejs) diff --git a/doc/api/intl.md b/doc/api/intl.md index fc10bf9dda2e82..cce6661521c832 100644 --- a/doc/api/intl.md +++ b/doc/api/intl.md @@ -209,7 +209,7 @@ to be helpful: [BUILDING.md#full-icu]: https://github.com/nodejs/node/blob/master/BUILDING.md#build-with-full-icu-support-all-locales-supported-by-icu [ECMA-262]: https://tc39.github.io/ecma262/ [ECMA-402]: https://tc39.github.io/ecma402/ -[ICU]: http://icu-project.org/ +[ICU]: http://site.icu-project.org/ [REPL]: repl.html#repl_repl [Test262]: https://github.com/tc39/test262/tree/master/test/intl402 [WHATWG URL parser]: url.html#url_the_whatwg_url_api diff --git a/doc/api/path.md b/doc/api/path.md index c6960c7ee47109..887928dd1a46cb 100644 --- a/doc/api/path.md +++ b/doc/api/path.md @@ -564,4 +564,4 @@ of the `path` methods. [`path.sep`]: #path_path_sep [`path.win32`]: #path_path_win32 [MSDN-Rel-Path]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#fully-qualified-vs-relative-paths -[namespace-prefixed path]: https://msdn.microsoft.com/library/windows/desktop/aa365247(v=vs.85).aspx#namespaces +[namespace-prefixed path]: https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file#namespaces diff --git a/doc/api/process.md b/doc/api/process.md index df544e7f0f17b9..2c27409d809080 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -2080,7 +2080,7 @@ cases: [Cluster]: cluster.html [debugger]: debugger.html [Duplex]: stream.html#stream_duplex_and_transform_streams -[LTS]: https://github.com/nodejs/LTS/ +[LTS]: https://github.com/nodejs/Release [note on process I/O]: process.html#process_a_note_on_process_i_o [process_emit_warning]: #process_process_emitwarning_warning_type_code_ctor [process_warning]: #process_event_warning diff --git a/doc/api/punycode.md b/doc/api/punycode.md index fc67d55f85cd94..b8a3b7d629f9e5 100644 --- a/doc/api/punycode.md +++ b/doc/api/punycode.md @@ -149,5 +149,5 @@ added: v0.6.1 Returns a string identifying the current [Punycode.js][] version number. -[Punycode.js]: https://mths.be/punycode +[Punycode.js]: https://github.com/bestiejs/punycode.js [Punycode]: https://tools.ietf.org/html/rfc3492 diff --git a/doc/api/repl.md b/doc/api/repl.md index 356d5ef47e206f..cb2765eab44f8b 100644 --- a/doc/api/repl.md +++ b/doc/api/repl.md @@ -619,10 +619,10 @@ possible to connect to a long-running Node.js process without restarting it. For an example of running a "full-featured" (`terminal`) REPL over a `net.Server` and `net.Socket` instance, see: -[https://gist.github.com/2209310](https://gist.github.com/2209310). +[https://gist.github.com/TooTallNate/2209310](https://gist.github.com/TooTallNate/2209310). For an example of running a REPL instance over [curl(1)][], see: -[https://gist.github.com/2053342](https://gist.github.com/2053342). +[https://gist.github.com/TooTallNate/2053342](https://gist.github.com/TooTallNate/2053342). [`'uncaughtException'`]: process.html#process_event_uncaughtexception [`--experimental-repl-await`]: cli.html#cli_experimental_repl_await diff --git a/doc/api/timers.md b/doc/api/timers.md index 1eb511c3b92b48..d2029458e3d501 100644 --- a/doc/api/timers.md +++ b/doc/api/timers.md @@ -253,4 +253,4 @@ Cancels a `Timeout` object created by [`setTimeout()`][]. [`setInterval()`]: timers.html#timers_setinterval_callback_delay_args [`setTimeout()`]: timers.html#timers_settimeout_callback_delay_args [`util.promisify()`]: util.html#util_util_promisify_original -[the Node.js Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick +[the Node.js Event Loop]: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/ diff --git a/doc/api/tls.md b/doc/api/tls.md index 9d713088879927..5a9ee69ada4e59 100644 --- a/doc/api/tls.md +++ b/doc/api/tls.md @@ -1399,6 +1399,6 @@ where `secureSocket` has the same API as `pair.cleartext`. [Stream]: stream.html#stream_stream [TLS Session Tickets]: https://www.ietf.org/rfc/rfc5077.txt [TLS recommendations]: https://wiki.mozilla.org/Security/Server_Side_TLS -[asn1.js]: https://npmjs.org/package/asn1.js +[asn1.js]: https://www.npmjs.com/package/asn1.js [modifying the default cipher suite]: #tls_modifying_the_default_tls_cipher_suite [specific attacks affecting larger AES key sizes]: https://www.schneier.com/blog/archives/2009/07/another_new_aes.html diff --git a/doc/api/util.md b/doc/api/util.md index 3352a28a866289..e054c8a35bc7d7 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -2137,6 +2137,6 @@ Deprecated predecessor of `console.log`. [Module Namespace Object]: https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects [WHATWG Encoding Standard]: https://encoding.spec.whatwg.org/ [Common System Errors]: errors.html#errors_common_system_errors -[constructor]: https://developer.mozilla.org/en-US/JavaScript/Reference/Global_Objects/Object/constructor +[constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/constructor [list of deprecated APIS]: deprecations.html#deprecations_list_of_deprecated_apis [semantically incompatible]: https://github.com/nodejs/node/issues/4179 diff --git a/doc/api/v8.md b/doc/api/v8.md index c5d23af7a3154a..c9afa93f89d4ec 100644 --- a/doc/api/v8.md +++ b/doc/api/v8.md @@ -413,4 +413,4 @@ A subclass of [`Deserializer`][] corresponding to the format written by [V8]: https://developers.google.com/v8/ [`vm.Script`]: vm.html#vm_new_vm_script_code_options [here]: https://github.com/thlorenz/v8-flags/blob/master/flags-0.11.md -[`GetHeapSpaceStatistics`]: https://v8docs.nodesource.com/node-8.0/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4 +[`GetHeapSpaceStatistics`]: https://v8docs.nodesource.com/node-8.9/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4 From ca8c96035ae775d72605a7dbf2827ae4d086c23e Mon Sep 17 00:00:00 2001 From: Lance Ball <lball@redhat.com> Date: Sun, 15 Jul 2018 02:45:11 +0100 Subject: [PATCH 083/116] doc: update readme with my pronouns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/21818 Refs: https://github.com/nodejs/node/issues/21803 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Shelley Vohr <codebytere@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e81f6629ed342a..c899b1a9a9b400 100644 --- a/README.md +++ b/README.md @@ -422,7 +422,7 @@ For more information about the governance of the Node.js project, see * [kunalspathak](https://github.com/kunalspathak) - **Kunal Pathak** <kunal.pathak@microsoft.com> * [lance](https://github.com/lance) - -**Lance Ball** <lball@redhat.com> +**Lance Ball** <lball@redhat.com> (he/him) * [Leko](https://github.com/Leko) - **Shingo Inoue** <leko.noor@gmail.com> (he/him) * [lpinca](https://github.com/lpinca) - From 678313d18b77146573c2202078e76b3bd6d64a78 Mon Sep 17 00:00:00 2001 From: Masashi Hirano <cherrydog07@gmail.com> Date: Sat, 5 May 2018 11:38:09 +0900 Subject: [PATCH 084/116] test: add filehandle sync() and datasync() tests To increase test coverage for fs.promises, added tests for filehandle.sync and filehandle.datasync. PR-URL: https://github.com/nodejs/node/pull/20530 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> --- .../test-fs-promises-file-handle-sync.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 test/parallel/test-fs-promises-file-handle-sync.js diff --git a/test/parallel/test-fs-promises-file-handle-sync.js b/test/parallel/test-fs-promises-file-handle-sync.js new file mode 100644 index 00000000000000..cf28df31cb2e0f --- /dev/null +++ b/test/parallel/test-fs-promises-file-handle-sync.js @@ -0,0 +1,27 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const tmpdir = require('../common/tmpdir'); + +const { access, copyFile, open } = require('fs').promises; +const path = require('path'); + +common.crashOnUnhandledRejection(); + +async function validateSync() { + tmpdir.refresh(); + const dest = path.resolve(tmpdir.path, 'baz.js'); + await copyFile(fixtures.path('baz.js'), dest); + await access(dest, 'r'); + const handle = await open(dest, 'r+'); + await handle.datasync(); + await handle.sync(); + const buf = Buffer.from('hello world'); + await handle.write(buf); + const ret = await handle.read(Buffer.alloc(11), 0, 11, 0); + assert.strictEqual(ret.bytesRead, 11); + assert.deepStrictEqual(ret.buffer, buf); +} + +validateSync(); From 02982998db080ed808d3762c62994b775a94ced7 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater <ruben@bridgewater.de> Date: Sat, 14 Jul 2018 17:15:32 +0200 Subject: [PATCH 085/116] doc: add my pronoun MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/21813 Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Tobias Nießen <tniessen@tnie.de> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Jon Moss <me@jonathanmoss.me> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c899b1a9a9b400..ce03c7d4c04269 100644 --- a/README.md +++ b/README.md @@ -332,7 +332,7 @@ For more information about the governance of the Node.js project, see * [brendanashworth](https://github.com/brendanashworth) - **Brendan Ashworth** <brendan.ashworth@me.com> * [BridgeAR](https://github.com/BridgeAR) - -**Ruben Bridgewater** <ruben@bridgewater.de> +**Ruben Bridgewater** <ruben@bridgewater.de> (he/him) * [bzoz](https://github.com/bzoz) - **Bartosz Sosnowski** <bartosz@janeasystems.com> * [calvinmetcalf](https://github.com/calvinmetcalf) - From d42dbde1a8f1e373ea9ed24cf7e0addadac58af3 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Sun, 10 Jun 2018 15:57:46 +0200 Subject: [PATCH 086/116] src: add iteration over all base objects to Environment PR-URL: https://github.com/nodejs/node/pull/21741 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> --- src/env-inl.h | 16 ++++++++++++++++ src/env.h | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/src/env-inl.h b/src/env-inl.h index b436b23b678015..4ef9665cbddfed 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -809,6 +809,22 @@ bool Environment::CleanupHookCallback::Equal::operator()( return a.fn_ == b.fn_ && a.arg_ == b.arg_; } +BaseObject* Environment::CleanupHookCallback::GetBaseObject() const { + if (fn_ == BaseObject::DeleteMe) + return static_cast<BaseObject*>(arg_); + else + return nullptr; +} + +template <typename T> +void Environment::ForEachBaseObject(T&& iterator) { + for (const auto& hook : cleanup_hooks_) { + BaseObject* obj = hook.GetBaseObject(); + if (obj != nullptr) + iterator(obj); + } +} + #define VP(PropertyName, StringValue) V(v8::Private, PropertyName) #define VY(PropertyName, StringValue) V(v8::Symbol, PropertyName) #define VS(PropertyName, StringValue) V(v8::String, PropertyName) diff --git a/src/env.h b/src/env.h index ea50d019c2327f..3f4ea83b5611fd 100644 --- a/src/env.h +++ b/src/env.h @@ -976,6 +976,8 @@ class Environment { inline bool operator()(const CleanupHookCallback& a, const CleanupHookCallback& b) const; }; + + inline BaseObject* GetBaseObject() const; }; // Use an unordered_set, so that we have efficient insertion and removal. @@ -988,6 +990,9 @@ class Environment { v8::Local<v8::Promise> promise, v8::Local<v8::Value> parent); + template <typename T> + void ForEachBaseObject(T&& iterator); + #define V(PropertyName, TypeName) Persistent<TypeName> PropertyName ## _; ENVIRONMENT_STRONG_PERSISTENT_PROPERTIES(V) #undef V From 355c5e3c955cf9f5ad4a2cfcdebbc7db2f945c9a Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Sat, 30 Jun 2018 21:09:21 +0200 Subject: [PATCH 087/116] deps: cherry-pick 555c811 from upstream V8 Original commit message: [api] Switch from `SetBuildEmbedderGraphCallback` to `AddBuildEmbedderGraphCallback` `SetBuildEmbedderGraphCallback`, unlike `SetWrapperClassInfoProvider`, assumes a monolithic embedder that can provide all necessary information. That is not the case for e.g. Node.js, which can e.g. provide multiple Node.js instances per V8 Isolate, as well as native addons that may allocate resources on their own. Cq-Include-Trybots: luci.chromium.try:linux_chromium_rel_ng Change-Id: Ib53dfde82416dd69934b08623e27d674a483ac2d Reviewed-on: https://chromium-review.googlesource.com/1082441 Commit-Queue: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Ulan Degenbaev <ulan@chromium.org> Reviewed-by: Yang Guo <yangguo@chromium.org> Cr-Commit-Position: refs/heads/master@{#53545} Refs: https://github.com/v8/v8/commit/555c811c0d44d9aaaccf8e76059ed24537b3f012 PR-URL: https://github.com/nodejs/node/pull/21741 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> --- common.gypi | 2 +- deps/v8/include/v8-profiler.h | 22 +++-- deps/v8/src/api.cc | 22 ++++- deps/v8/src/profiler/heap-profiler.cc | 21 +++-- deps/v8/src/profiler/heap-profiler.h | 12 +-- deps/v8/test/cctest/test-heap-profiler.cc | 104 +++++++++++++++++++--- 6 files changed, 152 insertions(+), 31 deletions(-) diff --git a/common.gypi b/common.gypi index 9d74b46a8d5c70..a782cfbecb85e2 100644 --- a/common.gypi +++ b/common.gypi @@ -28,7 +28,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.14', + 'v8_embedder_string': '-node.15', # Enable disassembler for `--print-code` v8 options 'v8_enable_disassembler': 1, diff --git a/deps/v8/include/v8-profiler.h b/deps/v8/include/v8-profiler.h index 5df9b2a217ea8e..c61027b3b94e45 100644 --- a/deps/v8/include/v8-profiler.h +++ b/deps/v8/include/v8-profiler.h @@ -636,7 +636,7 @@ class V8_EXPORT AllocationProfile { * Usage: * 1) Define derived class of EmbedderGraph::Node for embedder objects. * 2) Set the build embedder graph callback on the heap profiler using - * HeapProfiler::SetBuildEmbedderGraphCallback. + * HeapProfiler::AddBuildEmbedderGraphCallback. * 3) In the callback use graph->AddEdge(node1, node2) to add an edge from * node1 to node2. * 4) To represent references from/to V8 object, construct V8 nodes using @@ -736,7 +736,12 @@ class V8_EXPORT HeapProfiler { * The callback must not trigger garbage collection in V8. */ typedef void (*BuildEmbedderGraphCallback)(v8::Isolate* isolate, - v8::EmbedderGraph* graph); + v8::EmbedderGraph* graph, + void* data); + + /** TODO(addaleax): Remove */ + typedef void (*LegacyBuildEmbedderGraphCallback)(v8::Isolate* isolate, + v8::EmbedderGraph* graph); /** Returns the number of snapshots taken. */ int GetSnapshotCount(); @@ -878,15 +883,22 @@ class V8_EXPORT HeapProfiler { /** Binds a callback to embedder's class ID. */ V8_DEPRECATED( - "Use SetBuildEmbedderGraphCallback to provide info about embedder nodes", + "Use AddBuildEmbedderGraphCallback to provide info about embedder nodes", void SetWrapperClassInfoProvider(uint16_t class_id, WrapperInfoCallback callback)); V8_DEPRECATED( - "Use SetBuildEmbedderGraphCallback to provide info about embedder nodes", + "Use AddBuildEmbedderGraphCallback to provide info about embedder nodes", void SetGetRetainerInfosCallback(GetRetainerInfosCallback callback)); - void SetBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback); + V8_DEPRECATE_SOON( + "Use AddBuildEmbedderGraphCallback to provide info about embedder nodes", + void SetBuildEmbedderGraphCallback( + LegacyBuildEmbedderGraphCallback callback)); + void AddBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback, + void* data); + void RemoveBuildEmbedderGraphCallback(BuildEmbedderGraphCallback callback, + void* data); /** * Default value of persistent handle class ID. Must not be used to diff --git a/deps/v8/src/api.cc b/deps/v8/src/api.cc index a7f6d00f6fabf7..192ad90f83e55c 100644 --- a/deps/v8/src/api.cc +++ b/deps/v8/src/api.cc @@ -10558,9 +10558,25 @@ void HeapProfiler::SetGetRetainerInfosCallback( } void HeapProfiler::SetBuildEmbedderGraphCallback( - BuildEmbedderGraphCallback callback) { - reinterpret_cast<i::HeapProfiler*>(this)->SetBuildEmbedderGraphCallback( - callback); + LegacyBuildEmbedderGraphCallback callback) { + reinterpret_cast<i::HeapProfiler*>(this)->AddBuildEmbedderGraphCallback( + [](v8::Isolate* isolate, v8::EmbedderGraph* graph, void* data) { + reinterpret_cast<LegacyBuildEmbedderGraphCallback>(data)(isolate, + graph); + }, + reinterpret_cast<void*>(callback)); +} + +void HeapProfiler::AddBuildEmbedderGraphCallback( + BuildEmbedderGraphCallback callback, void* data) { + reinterpret_cast<i::HeapProfiler*>(this)->AddBuildEmbedderGraphCallback( + callback, data); +} + +void HeapProfiler::RemoveBuildEmbedderGraphCallback( + BuildEmbedderGraphCallback callback, void* data) { + reinterpret_cast<i::HeapProfiler*>(this)->RemoveBuildEmbedderGraphCallback( + callback, data); } v8::Testing::StressType internal::Testing::stress_type_ = diff --git a/deps/v8/src/profiler/heap-profiler.cc b/deps/v8/src/profiler/heap-profiler.cc index 7e0bcec97ae75e..2496e24b91cf18 100644 --- a/deps/v8/src/profiler/heap-profiler.cc +++ b/deps/v8/src/profiler/heap-profiler.cc @@ -69,16 +69,25 @@ v8::HeapProfiler::RetainerInfos HeapProfiler::GetRetainerInfos( return infos; } -void HeapProfiler::SetBuildEmbedderGraphCallback( - v8::HeapProfiler::BuildEmbedderGraphCallback callback) { - build_embedder_graph_callback_ = callback; +void HeapProfiler::AddBuildEmbedderGraphCallback( + v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data) { + build_embedder_graph_callbacks_.push_back({callback, data}); +} + +void HeapProfiler::RemoveBuildEmbedderGraphCallback( + v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data) { + auto it = std::find(build_embedder_graph_callbacks_.begin(), + build_embedder_graph_callbacks_.end(), + std::make_pair(callback, data)); + if (it != build_embedder_graph_callbacks_.end()) + build_embedder_graph_callbacks_.erase(it); } void HeapProfiler::BuildEmbedderGraph(Isolate* isolate, v8::EmbedderGraph* graph) { - if (build_embedder_graph_callback_ != nullptr) - build_embedder_graph_callback_(reinterpret_cast<v8::Isolate*>(isolate), - graph); + for (const auto& cb : build_embedder_graph_callbacks_) { + cb.first(reinterpret_cast<v8::Isolate*>(isolate), graph, cb.second); + } } HeapSnapshot* HeapProfiler::TakeSnapshot( diff --git a/deps/v8/src/profiler/heap-profiler.h b/deps/v8/src/profiler/heap-profiler.h index 507dd579bffb65..fc0b005e1c67ce 100644 --- a/deps/v8/src/profiler/heap-profiler.h +++ b/deps/v8/src/profiler/heap-profiler.h @@ -71,11 +71,13 @@ class HeapProfiler : public HeapObjectAllocationTracker { v8::HeapProfiler::GetRetainerInfosCallback callback); v8::HeapProfiler::RetainerInfos GetRetainerInfos(Isolate* isolate); - void SetBuildEmbedderGraphCallback( - v8::HeapProfiler::BuildEmbedderGraphCallback callback); + void AddBuildEmbedderGraphCallback( + v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data); + void RemoveBuildEmbedderGraphCallback( + v8::HeapProfiler::BuildEmbedderGraphCallback callback, void* data); void BuildEmbedderGraph(Isolate* isolate, v8::EmbedderGraph* graph); bool HasBuildEmbedderGraphCallback() { - return build_embedder_graph_callback_ != nullptr; + return !build_embedder_graph_callbacks_.empty(); } bool is_tracking_object_moves() const { return is_tracking_object_moves_; } @@ -103,8 +105,8 @@ class HeapProfiler : public HeapObjectAllocationTracker { std::unique_ptr<SamplingHeapProfiler> sampling_heap_profiler_; v8::HeapProfiler::GetRetainerInfosCallback get_retainer_infos_callback_ = nullptr; - v8::HeapProfiler::BuildEmbedderGraphCallback build_embedder_graph_callback_ = - nullptr; + std::vector<std::pair<v8::HeapProfiler::BuildEmbedderGraphCallback, void*>> + build_embedder_graph_callbacks_; DISALLOW_COPY_AND_ASSIGN(HeapProfiler); }; diff --git a/deps/v8/test/cctest/test-heap-profiler.cc b/deps/v8/test/cctest/test-heap-profiler.cc index 4372aa3d2e5205..eec739d109dcf1 100644 --- a/deps/v8/test/cctest/test-heap-profiler.cc +++ b/deps/v8/test/cctest/test-heap-profiler.cc @@ -1541,8 +1541,8 @@ class EmbedderGraphBuilder : public v8::PersistentHandleVisitor { graph->AddNode(std::unique_ptr<Group>(new Group("ccc-group"))); } - static void BuildEmbedderGraph(v8::Isolate* isolate, - v8::EmbedderGraph* graph) { + static void BuildEmbedderGraph(v8::Isolate* isolate, v8::EmbedderGraph* graph, + void* data) { EmbedderGraphBuilder builder(isolate, graph); isolate->VisitHandlesWithClassIds(&builder); } @@ -1604,8 +1604,8 @@ TEST(HeapSnapshotRetainedObjectInfo) { v8::HandleScope scope(isolate); v8::HeapProfiler* heap_profiler = isolate->GetHeapProfiler(); - heap_profiler->SetBuildEmbedderGraphCallback( - EmbedderGraphBuilder::BuildEmbedderGraph); + heap_profiler->AddBuildEmbedderGraphCallback( + EmbedderGraphBuilder::BuildEmbedderGraph, nullptr); v8::Persistent<v8::String> p_AAA(isolate, v8_str("AAA")); p_AAA.SetWrapperClassId(1); v8::Persistent<v8::String> p_BBB(isolate, v8_str("BBB")); @@ -2932,7 +2932,8 @@ class EmbedderRootNode : public EmbedderNode { // global object. v8::Local<v8::Value>* global_object_pointer; -void BuildEmbedderGraph(v8::Isolate* v8_isolate, v8::EmbedderGraph* graph) { +void BuildEmbedderGraph(v8::Isolate* v8_isolate, v8::EmbedderGraph* graph, + void* data) { using Node = v8::EmbedderGraph::Node; Node* global_node = graph->V8Node(*global_object_pointer); Node* embedder_node_A = graph->AddNode( @@ -2979,12 +2980,92 @@ TEST(EmbedderGraph) { (isolate->context()->native_context()->global_object()))); global_object_pointer = &global_object; v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler(); - heap_profiler->SetBuildEmbedderGraphCallback(BuildEmbedderGraph); + heap_profiler->AddBuildEmbedderGraphCallback(BuildEmbedderGraph, nullptr); const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot(); CHECK(ValidateSnapshot(snapshot)); CheckEmbedderGraphSnapshot(env->GetIsolate(), snapshot); } +struct GraphBuildingContext { + int counter = 0; +}; + +void CheckEmbedderGraphSnapshotWithContext( + v8::Isolate* isolate, const v8::HeapSnapshot* snapshot, + const GraphBuildingContext* context) { + const v8::HeapGraphNode* global = GetGlobalObject(snapshot); + CHECK_GE(context->counter, 1); + CHECK_LE(context->counter, 2); + + const v8::HeapGraphNode* embedder_node_A = + GetChildByName(global, "EmbedderNodeA"); + CHECK_EQ(10, GetSize(embedder_node_A)); + + const v8::HeapGraphNode* embedder_node_B = + GetChildByName(global, "EmbedderNodeB"); + if (context->counter == 2) { + CHECK_NOT_NULL(embedder_node_B); + CHECK_EQ(20, GetSize(embedder_node_B)); + } else { + CHECK_NULL(embedder_node_B); + } +} + +void BuildEmbedderGraphWithContext(v8::Isolate* v8_isolate, + v8::EmbedderGraph* graph, void* data) { + using Node = v8::EmbedderGraph::Node; + GraphBuildingContext* context = static_cast<GraphBuildingContext*>(data); + Node* global_node = graph->V8Node(*global_object_pointer); + + CHECK_GE(context->counter, 0); + CHECK_LE(context->counter, 1); + switch (context->counter++) { + case 0: { + Node* embedder_node_A = graph->AddNode( + std::unique_ptr<Node>(new EmbedderNode("EmbedderNodeA", 10))); + graph->AddEdge(global_node, embedder_node_A); + break; + } + case 1: { + Node* embedder_node_B = graph->AddNode( + std::unique_ptr<Node>(new EmbedderNode("EmbedderNodeB", 20))); + graph->AddEdge(global_node, embedder_node_B); + break; + } + } +} + +TEST(EmbedderGraphMultipleCallbacks) { + i::FLAG_heap_profiler_use_embedder_graph = true; + LocalContext env; + v8::HandleScope scope(env->GetIsolate()); + i::Isolate* isolate = reinterpret_cast<i::Isolate*>(env->GetIsolate()); + v8::Local<v8::Value> global_object = + v8::Utils::ToLocal(i::Handle<i::JSObject>( + (isolate->context()->native_context()->global_object()))); + global_object_pointer = &global_object; + v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler(); + GraphBuildingContext context; + + heap_profiler->AddBuildEmbedderGraphCallback(BuildEmbedderGraphWithContext, + &context); + heap_profiler->AddBuildEmbedderGraphCallback(BuildEmbedderGraphWithContext, + &context); + const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot(); + CHECK_EQ(context.counter, 2); + CHECK(ValidateSnapshot(snapshot)); + CheckEmbedderGraphSnapshotWithContext(env->GetIsolate(), snapshot, &context); + + heap_profiler->RemoveBuildEmbedderGraphCallback(BuildEmbedderGraphWithContext, + &context); + context.counter = 0; + + snapshot = heap_profiler->TakeHeapSnapshot(); + CHECK_EQ(context.counter, 1); + CHECK(ValidateSnapshot(snapshot)); + CheckEmbedderGraphSnapshotWithContext(env->GetIsolate(), snapshot, &context); +} + TEST(StrongHandleAnnotation) { LocalContext env; v8::HandleScope scope(env->GetIsolate()); @@ -3010,7 +3091,7 @@ TEST(StrongHandleAnnotation) { } void BuildEmbedderGraphWithWrapperNode(v8::Isolate* v8_isolate, - v8::EmbedderGraph* graph) { + v8::EmbedderGraph* graph, void* data) { using Node = v8::EmbedderGraph::Node; Node* global_node = graph->V8Node(*global_object_pointer); Node* wrapper_node = graph->AddNode( @@ -3041,8 +3122,8 @@ TEST(EmbedderGraphWithWrapperNode) { (isolate->context()->native_context()->global_object()))); global_object_pointer = &global_object; v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler(); - heap_profiler->SetBuildEmbedderGraphCallback( - BuildEmbedderGraphWithWrapperNode); + heap_profiler->AddBuildEmbedderGraphCallback( + BuildEmbedderGraphWithWrapperNode, nullptr); const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot(); CHECK(ValidateSnapshot(snapshot)); const v8::HeapGraphNode* global = GetGlobalObject(snapshot); @@ -3080,7 +3161,7 @@ class EmbedderNodeWithPrefix : public v8::EmbedderGraph::Node { }; void BuildEmbedderGraphWithPrefix(v8::Isolate* v8_isolate, - v8::EmbedderGraph* graph) { + v8::EmbedderGraph* graph, void* data) { using Node = v8::EmbedderGraph::Node; Node* global_node = graph->V8Node(*global_object_pointer); Node* node = graph->AddNode( @@ -3098,7 +3179,8 @@ TEST(EmbedderGraphWithPrefix) { (isolate->context()->native_context()->global_object()))); global_object_pointer = &global_object; v8::HeapProfiler* heap_profiler = env->GetIsolate()->GetHeapProfiler(); - heap_profiler->SetBuildEmbedderGraphCallback(BuildEmbedderGraphWithPrefix); + heap_profiler->AddBuildEmbedderGraphCallback(BuildEmbedderGraphWithPrefix, + nullptr); const v8::HeapSnapshot* snapshot = heap_profiler->TakeHeapSnapshot(); CHECK(ValidateSnapshot(snapshot)); const v8::HeapGraphNode* global = GetGlobalObject(snapshot); From 5121278f5ce28b85d6f9b0d85fcfc42e89a13093 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Wed, 11 Jul 2018 00:59:53 +0200 Subject: [PATCH 088/116] src: use V8 graph heap snapshot API Transition to a newer, more flexible API for heap snapshot creation. This addresses a currently pending deprecation in the V8 API. PR-URL: https://github.com/nodejs/node/pull/21741 Fixes: https://github.com/nodejs/node/issues/21633 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> --- src/async_wrap.cc | 100 ++----------------------------------- src/async_wrap.h | 3 +- src/env.cc | 17 ++++++- src/env.h | 4 ++ src/memory_tracker-inl.h | 105 ++++++++++++++++++++++++++++++++++++--- src/memory_tracker.h | 35 ++++++++++--- src/tls_wrap.cc | 8 ++- src/tls_wrap.h | 4 +- 8 files changed, 160 insertions(+), 116 deletions(-) diff --git a/src/async_wrap.cc b/src/async_wrap.cc index e98dca3c56651b..7ef3dafdf992c5 100644 --- a/src/async_wrap.cc +++ b/src/async_wrap.cc @@ -32,7 +32,6 @@ using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; using v8::HandleScope; -using v8::HeapProfiler; using v8::Integer; using v8::Isolate; using v8::Local; @@ -43,7 +42,6 @@ using v8::ObjectTemplate; using v8::Promise; using v8::PromiseHookType; using v8::PropertyCallbackInfo; -using v8::RetainedObjectInfo; using v8::String; using v8::Uint32; using v8::Undefined; @@ -61,87 +59,6 @@ static const char* const provider_names[] = { }; -// Report correct information in a heapdump. - -class RetainedAsyncInfo: public RetainedObjectInfo { - public: - explicit RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap); - - void Dispose() override; - bool IsEquivalent(RetainedObjectInfo* other) override; - intptr_t GetHash() override; - const char* GetLabel() override; - intptr_t GetSizeInBytes() override; - - private: - const char* label_; - const AsyncWrap* wrap_; - const size_t length_; -}; - - -static int OwnMemory(AsyncWrap* async_wrap) { - MemoryTracker tracker; - tracker.set_track_only_self(true); - tracker.Track(async_wrap); - return tracker.accumulated_size(); -} - - -RetainedAsyncInfo::RetainedAsyncInfo(uint16_t class_id, AsyncWrap* wrap) - : label_(provider_names[class_id - NODE_ASYNC_ID_OFFSET]), - wrap_(wrap), - length_(OwnMemory(wrap)) { -} - - -void RetainedAsyncInfo::Dispose() { - delete this; -} - - -bool RetainedAsyncInfo::IsEquivalent(RetainedObjectInfo* other) { - return label_ == other->GetLabel() && - wrap_ == static_cast<RetainedAsyncInfo*>(other)->wrap_; -} - - -intptr_t RetainedAsyncInfo::GetHash() { - return reinterpret_cast<intptr_t>(wrap_); -} - - -const char* RetainedAsyncInfo::GetLabel() { - return label_; -} - - -intptr_t RetainedAsyncInfo::GetSizeInBytes() { - return length_; -} - - -RetainedObjectInfo* WrapperInfo(uint16_t class_id, Local<Value> wrapper) { - // No class_id should be the provider type of NONE. - CHECK_GT(class_id, NODE_ASYNC_ID_OFFSET); - // And make sure the class_id doesn't extend past the last provider. - CHECK_LE(class_id - NODE_ASYNC_ID_OFFSET, AsyncWrap::PROVIDERS_LENGTH); - CHECK(wrapper->IsObject()); - CHECK(!wrapper.IsEmpty()); - - Local<Object> object = wrapper.As<Object>(); - CHECK_GT(object->InternalFieldCount(), 0); - - AsyncWrap* wrap; - ASSIGN_OR_RETURN_UNWRAP(&wrap, object, nullptr); - - return new RetainedAsyncInfo(class_id, wrap); -} - - -// end RetainedAsyncInfo - - struct AsyncWrapObject : public AsyncWrap { static inline void New(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); @@ -616,16 +533,6 @@ void AsyncWrap::Initialize(Local<Object> target, } -void LoadAsyncWrapperInfo(Environment* env) { - HeapProfiler* heap_profiler = env->isolate()->GetHeapProfiler(); -#define V(PROVIDER) \ - heap_profiler->SetWrapperClassInfoProvider( \ - (NODE_ASYNC_ID_OFFSET + AsyncWrap::PROVIDER_ ## PROVIDER), WrapperInfo); - NODE_ASYNC_PROVIDER_TYPES(V) -#undef V -} - - AsyncWrap::AsyncWrap(Environment* env, Local<Object> object, ProviderType provider, @@ -814,9 +721,12 @@ void EmitAsyncDestroy(Isolate* isolate, async_context asyncContext) { Environment::GetCurrent(isolate), asyncContext.async_id); } +std::string AsyncWrap::MemoryInfoName() const { + return provider_names[provider_type()]; +} + std::string AsyncWrap::diagnostic_name() const { - return std::string(provider_names[provider_type()]) + - " (" + std::to_string(env()->thread_id()) + ":" + + return MemoryInfoName() + " (" + std::to_string(env()->thread_id()) + ":" + std::to_string(static_cast<int64_t>(async_id_)) + ")"; } diff --git a/src/async_wrap.h b/src/async_wrap.h index c1d90f8fad0541..e61837af61c242 100644 --- a/src/async_wrap.h +++ b/src/async_wrap.h @@ -175,6 +175,7 @@ class AsyncWrap : public BaseObject { v8::Local<v8::Value>* argv); virtual std::string diagnostic_name() const; + std::string MemoryInfoName() const override; static void WeakCallback(const v8::WeakCallbackInfo<DestroyParam> &info); @@ -205,8 +206,6 @@ class AsyncWrap : public BaseObject { double trigger_async_id_; }; -void LoadAsyncWrapperInfo(Environment* env); - } // namespace node #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS diff --git a/src/env.cc b/src/env.cc index 006173eaaeb847..3d1a9372c93c7d 100644 --- a/src/env.cc +++ b/src/env.cc @@ -142,9 +142,15 @@ Environment::Environment(IsolateData* isolate_data, std::string debug_cats; SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats); set_debug_categories(debug_cats, true); + + isolate()->GetHeapProfiler()->AddBuildEmbedderGraphCallback( + BuildEmbedderGraph, this); } Environment::~Environment() { + isolate()->GetHeapProfiler()->RemoveBuildEmbedderGraphCallback( + BuildEmbedderGraph, this); + // Make sure there are no re-used libuv wrapper objects. // CleanupHandles() should have removed all of them. CHECK(file_handle_read_wrap_freelist_.empty()); @@ -212,7 +218,6 @@ void Environment::Start(int argc, set_process_object(process_object); SetupProcessObject(this, argc, argv, exec_argc, exec_argv); - LoadAsyncWrapperInfo(this); static uv_once_t init_once = UV_ONCE_INIT; uv_once(&init_once, InitThreadLocalOnce); @@ -653,6 +658,16 @@ void Environment::stop_sub_worker_contexts() { } } +void Environment::BuildEmbedderGraph(v8::Isolate* isolate, + v8::EmbedderGraph* graph, + void* data) { + MemoryTracker tracker(isolate, graph); + static_cast<Environment*>(data)->ForEachBaseObject([&](BaseObject* obj) { + tracker.Track(obj); + }); +} + + // Not really any better place than env.cc at this moment. void BaseObject::DeleteMe(void* data) { BaseObject* self = static_cast<BaseObject*>(data); diff --git a/src/env.h b/src/env.h index 3f4ea83b5611fd..9c7f4d1ba8ae3f 100644 --- a/src/env.h +++ b/src/env.h @@ -859,6 +859,10 @@ class Environment { inline void RemoveCleanupHook(void (*fn)(void*), void* arg); void RunCleanup(); + static void BuildEmbedderGraph(v8::Isolate* isolate, + v8::EmbedderGraph* graph, + void* data); + private: inline void CreateImmediate(native_immediate_callback cb, void* data, diff --git a/src/memory_tracker-inl.h b/src/memory_tracker-inl.h index 758223492f6e71..568a4364f9c64d 100644 --- a/src/memory_tracker-inl.h +++ b/src/memory_tracker-inl.h @@ -7,13 +7,54 @@ namespace node { +class MemoryRetainerNode : public v8::EmbedderGraph::Node { + public: + explicit inline MemoryRetainerNode(MemoryTracker* tracker, + const MemoryRetainer* retainer, + const char* name) + : retainer_(retainer) { + if (retainer_ != nullptr) { + v8::HandleScope handle_scope(tracker->isolate()); + v8::Local<v8::Object> obj = retainer_->WrappedObject(); + if (!obj.IsEmpty()) + wrapper_node_ = tracker->graph()->V8Node(obj); + + name_ = retainer_->MemoryInfoName(); + } + if (name_.empty() && name != nullptr) { + name_ = name; + } + } + + const char* Name() override { return name_.c_str(); } + const char* NamePrefix() override { return "Node /"; } + size_t SizeInBytes() override { return size_; } + // TODO(addaleax): Merging this with the "official" WrapperNode() method + // seems to lose accuracy, e.g. SizeInBytes() is disregarded. + // Figure out whether to do anything about that. + Node* JSWrapperNode() { return wrapper_node_; } + + bool IsRootNode() override { + return retainer_ != nullptr && retainer_->IsRootNode(); + } + + private: + friend class MemoryTracker; + + Node* wrapper_node_ = nullptr; + const MemoryRetainer* retainer_; + std::string name_; + size_t size_ = 0; +}; + template <typename T> void MemoryTracker::TrackThis(const T* obj) { - accumulated_size_ += sizeof(T); + CurrentNode()->size_ = sizeof(T); } void MemoryTracker::TrackFieldWithSize(const char* name, size_t size) { - accumulated_size_ += size; + if (size > 0) + AddNode(name)->size_ = size; } void MemoryTracker::TrackField(const char* name, const MemoryRetainer& value) { @@ -21,9 +62,13 @@ void MemoryTracker::TrackField(const char* name, const MemoryRetainer& value) { } void MemoryTracker::TrackField(const char* name, const MemoryRetainer* value) { - if (track_only_self_ || value == nullptr || seen_.count(value) > 0) return; - seen_.insert(value); - Track(value); + if (track_only_self_ || value == nullptr) return; + auto it = seen_.find(value); + if (it != seen_.end()) { + graph_->AddEdge(CurrentNode(), it->second); + } else { + Track(value, name); + } } template <typename T> @@ -36,8 +81,10 @@ template <typename T, typename Iterator> void MemoryTracker::TrackField(const char* name, const T& value) { if (value.begin() == value.end()) return; size_t index = 0; + PushNode(name); for (Iterator it = value.begin(); it != value.end(); ++it) TrackField(std::to_string(index++).c_str(), *it); + PopNode(); } template <typename T> @@ -56,13 +103,15 @@ void MemoryTracker::TrackField(const char* name, const std::queue<T>& value) { template <typename T, typename test_for_number, typename dummy> void MemoryTracker::TrackField(const char* name, const T& value) { // For numbers, creating new nodes is not worth the overhead. - TrackFieldWithSize(name, sizeof(T)); + CurrentNode()->size_ += sizeof(T); } template <typename T, typename U> void MemoryTracker::TrackField(const char* name, const std::pair<T, U>& value) { + PushNode(name); TrackField("first", value.first); TrackField("second", value.second); + PopNode(); } template <typename T> @@ -74,10 +123,13 @@ void MemoryTracker::TrackField(const char* name, template <typename T, typename Traits> void MemoryTracker::TrackField(const char* name, const v8::Persistent<T, Traits>& value) { + TrackField(name, value.Get(isolate_)); } template <typename T> void MemoryTracker::TrackField(const char* name, const v8::Local<T>& value) { + if (!value.IsEmpty()) + graph_->AddEdge(CurrentNode(), graph_->V8Node(value)); } template <typename T> @@ -96,8 +148,47 @@ void MemoryTracker::TrackField(const char* name, TrackField(name, value.GetJSArray()); } -void MemoryTracker::Track(const MemoryRetainer* value) { +void MemoryTracker::Track(const MemoryRetainer* value, const char* name) { + v8::HandleScope handle_scope(isolate_); + MemoryRetainerNode* n = PushNode(name, value); value->MemoryInfo(this); + CHECK_EQ(CurrentNode(), n); + CHECK_NE(n->size_, 0); + PopNode(); +} + +MemoryRetainerNode* MemoryTracker::CurrentNode() const { + if (node_stack_.empty()) return nullptr; + return node_stack_.top(); +} + +MemoryRetainerNode* MemoryTracker::AddNode( + const char* name, const MemoryRetainer* retainer) { + MemoryRetainerNode* n = new MemoryRetainerNode(this, retainer, name); + graph_->AddNode(std::unique_ptr<v8::EmbedderGraph::Node>(n)); + if (retainer != nullptr) + seen_[retainer] = n; + + if (CurrentNode() != nullptr) + graph_->AddEdge(CurrentNode(), n); + + if (n->JSWrapperNode() != nullptr) { + graph_->AddEdge(n, n->JSWrapperNode()); + graph_->AddEdge(n->JSWrapperNode(), n); + } + + return n; +} + +MemoryRetainerNode* MemoryTracker::PushNode( + const char* name, const MemoryRetainer* retainer) { + MemoryRetainerNode* n = AddNode(name, retainer); + node_stack_.push(n); + return n; +} + +void MemoryTracker::PopNode() { + node_stack_.pop(); } } // namespace node diff --git a/src/memory_tracker.h b/src/memory_tracker.h index 18822651f67873..d0f9e0dcad8f1e 100644 --- a/src/memory_tracker.h +++ b/src/memory_tracker.h @@ -3,15 +3,19 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include <unordered_set> +#include <unordered_map> #include <queue> +#include <stack> +#include <string> #include <limits> #include <uv.h> -#include <aliased_buffer.h> +#include "aliased_buffer.h" +#include "v8-profiler.h" namespace node { class MemoryTracker; +class MemoryRetainerNode; namespace crypto { class NodeBIO; @@ -29,6 +33,8 @@ class MemoryRetainer { } virtual bool IsRootNode() const { return false; } + + virtual std::string MemoryInfoName() const { return std::string(); } }; class MemoryTracker { @@ -67,17 +73,32 @@ class MemoryTracker { inline void TrackField(const char* name, const AliasedBuffer<NativeT, V8T>& value); - inline void Track(const MemoryRetainer* value); - inline size_t accumulated_size() const { return accumulated_size_; } + inline void Track(const MemoryRetainer* value, const char* name = nullptr); inline void set_track_only_self(bool value) { track_only_self_ = value; } + inline v8::EmbedderGraph* graph() { return graph_; } + inline v8::Isolate* isolate() { return isolate_; } - inline MemoryTracker() {} + inline explicit MemoryTracker(v8::Isolate* isolate, + v8::EmbedderGraph* graph) + : isolate_(isolate), graph_(graph) {} private: + typedef std::unordered_map<const MemoryRetainer*, MemoryRetainerNode*> + NodeMap; + + inline MemoryRetainerNode* CurrentNode() const; + inline MemoryRetainerNode* AddNode(const char* name, + const MemoryRetainer* retainer = nullptr); + inline MemoryRetainerNode* PushNode(const char* name, + const MemoryRetainer* retainer = nullptr); + inline void PopNode(); + bool track_only_self_ = false; - size_t accumulated_size_ = 0; - std::unordered_set<const MemoryRetainer*> seen_; + v8::Isolate* isolate_; + v8::EmbedderGraph* graph_; + std::stack<MemoryRetainerNode*> node_stack_; + NodeMap seen_; }; } // namespace node diff --git a/src/tls_wrap.cc b/src/tls_wrap.cc index 0d0791b710c860..3efa6adb4edb0e 100644 --- a/src/tls_wrap.cc +++ b/src/tls_wrap.cc @@ -758,6 +758,8 @@ void TLSWrap::DestroySSL(const FunctionCallbackInfo<Value>& args) { // Destroy the SSL structure and friends wrap->SSLWrap<TLSWrap>::DestroySSL(); + wrap->enc_in_ = nullptr; + wrap->enc_out_ = nullptr; if (wrap->stream_ != nullptr) wrap->stream_->RemoveStreamListener(wrap); @@ -868,8 +870,10 @@ void TLSWrap::MemoryInfo(MemoryTracker* tracker) const { tracker->TrackThis(this); tracker->TrackField("error", error_); tracker->TrackField("pending_cleartext_input", pending_cleartext_input_); - tracker->TrackField("enc_in", crypto::NodeBIO::FromBIO(enc_in_)); - tracker->TrackField("enc_out", crypto::NodeBIO::FromBIO(enc_out_)); + if (enc_in_ != nullptr) + tracker->TrackField("enc_in", crypto::NodeBIO::FromBIO(enc_in_)); + if (enc_out_ != nullptr) + tracker->TrackField("enc_out", crypto::NodeBIO::FromBIO(enc_out_)); } diff --git a/src/tls_wrap.h b/src/tls_wrap.h index b45e379ca3f61c..5f4fd3f7073305 100644 --- a/src/tls_wrap.h +++ b/src/tls_wrap.h @@ -143,8 +143,8 @@ class TLSWrap : public AsyncWrap, static int SelectSNIContextCallback(SSL* s, int* ad, void* arg); crypto::SecureContext* sc_; - BIO* enc_in_; - BIO* enc_out_; + BIO* enc_in_ = nullptr; + BIO* enc_out_ = nullptr; std::vector<uv_buf_t> pending_cleartext_input_; size_t write_size_; WriteWrap* current_write_ = nullptr; From a9a718696e418d89fe3120ffac48db1a487eb074 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Sun, 1 Jul 2018 01:27:09 +0200 Subject: [PATCH 089/116] src: make heap snapshot & embedder graph accessible for tests Add methods that allow inspection of heap snapshots and a JS version of our own embedder graph. These can be used in tests and might also prove useful for ad-hoc debugging. Usage requires `--expose-internals` and prints a warning similar to our other modules whose primary purpose is test support. PR-URL: https://github.com/nodejs/node/pull/21741 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> --- lib/internal/test/heap.js | 87 ++++++++++++++ node.gyp | 2 + src/heap_utils.cc | 232 ++++++++++++++++++++++++++++++++++++++ src/node_internals.h | 1 + 4 files changed, 322 insertions(+) create mode 100644 lib/internal/test/heap.js create mode 100644 src/heap_utils.cc diff --git a/lib/internal/test/heap.js b/lib/internal/test/heap.js new file mode 100644 index 00000000000000..a9260f651b9c1a --- /dev/null +++ b/lib/internal/test/heap.js @@ -0,0 +1,87 @@ +'use strict'; + +process.emitWarning( + 'These APIs are exposed only for testing and are not ' + + 'tracked by any versioning system or deprecation process.', + 'internal/test/heap'); + +const { internalBinding } = require('internal/bootstrap/loaders'); +const { createHeapDump, buildEmbedderGraph } = internalBinding('heap_utils'); +const assert = require('assert'); + +// This is not suitable for production code. It creates a full V8 heap dump, +// parses it as JSON, and then creates complex objects from it, leading +// to significantly increased memory usage. +function createJSHeapDump() { + const dump = createHeapDump(); + const meta = dump.snapshot.meta; + + const nodes = + readHeapInfo(dump.nodes, meta.node_fields, meta.node_types, dump.strings); + const edges = + readHeapInfo(dump.edges, meta.edge_fields, meta.edge_types, dump.strings); + + for (const node of nodes) { + node.incomingEdges = []; + node.outgoingEdges = []; + } + + let fromNodeIndex = 0; + let edgeIndex = 0; + for (const { type, name_or_index, to_node } of edges) { + while (edgeIndex === nodes[fromNodeIndex].edge_count) { + edgeIndex = 0; + fromNodeIndex++; + } + const toNode = nodes[to_node / meta.node_fields.length]; + const fromNode = nodes[fromNodeIndex]; + const edge = { + type, + toNode, + fromNode, + name: typeof name_or_index === 'string' ? name_or_index : null + }; + toNode.incomingEdges.push(edge); + fromNode.outgoingEdges.push(edge); + edgeIndex++; + } + + for (const node of nodes) + assert.strictEqual(node.edge_count, node.outgoingEdges.length); + + return nodes; +} + +function readHeapInfo(raw, fields, types, strings) { + const items = []; + + for (var i = 0; i < raw.length; i += fields.length) { + const item = {}; + for (var j = 0; j < fields.length; j++) { + const name = fields[j]; + let type = types[j]; + if (Array.isArray(type)) { + item[name] = type[raw[i + j]]; + } else if (name === 'name_or_index') { // type === 'string_or_number' + if (item.type === 'element' || item.type === 'hidden') + type = 'number'; + else + type = 'string'; + } + + if (type === 'string') { + item[name] = strings[raw[i + j]]; + } else if (type === 'number' || type === 'node') { + item[name] = raw[i + j]; + } + } + items.push(item); + } + + return items; +} + +module.exports = { + createJSHeapDump, + buildEmbedderGraph +}; diff --git a/node.gyp b/node.gyp index 89ebb0fb95c945..04784715c34f86 100644 --- a/node.gyp +++ b/node.gyp @@ -146,6 +146,7 @@ 'lib/internal/repl/await.js', 'lib/internal/socket_list.js', 'lib/internal/test/binding.js', + 'lib/internal/test/heap.js', 'lib/internal/test/unicode.js', 'lib/internal/timers.js', 'lib/internal/tls.js', @@ -328,6 +329,7 @@ 'src/exceptions.cc', 'src/fs_event_wrap.cc', 'src/handle_wrap.cc', + 'src/heap_utils.cc', 'src/js_stream.cc', 'src/module_wrap.cc', 'src/node.cc', diff --git a/src/heap_utils.cc b/src/heap_utils.cc new file mode 100644 index 00000000000000..2d339c580fa076 --- /dev/null +++ b/src/heap_utils.cc @@ -0,0 +1,232 @@ +#include "node_internals.h" +#include "env.h" + +using v8::Array; +using v8::Boolean; +using v8::Context; +using v8::EmbedderGraph; +using v8::EscapableHandleScope; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::HeapSnapshot; +using v8::Isolate; +using v8::JSON; +using v8::Local; +using v8::MaybeLocal; +using v8::Number; +using v8::Object; +using v8::String; +using v8::Value; + +namespace node { +namespace heap { + +class JSGraphJSNode : public EmbedderGraph::Node { + public: + const char* Name() override { return "<JS Node>"; } + size_t SizeInBytes() override { return 0; } + bool IsEmbedderNode() override { return false; } + Local<Value> JSValue() { return StrongPersistentToLocal(persistent_); } + + int IdentityHash() { + Local<Value> v = JSValue(); + if (v->IsObject()) return v.As<Object>()->GetIdentityHash(); + if (v->IsName()) return v.As<v8::Name>()->GetIdentityHash(); + if (v->IsInt32()) return v.As<v8::Int32>()->Value(); + return 0; + } + + JSGraphJSNode(Isolate* isolate, Local<Value> val) + : persistent_(isolate, val) { + CHECK(!val.IsEmpty()); + } + + struct Hash { + inline size_t operator()(JSGraphJSNode* n) const { + return n->IdentityHash(); + } + }; + + struct Equal { + inline bool operator()(JSGraphJSNode* a, JSGraphJSNode* b) const { + return a->JSValue()->SameValue(b->JSValue()); + } + }; + + private: + Persistent<Value> persistent_; +}; + +class JSGraph : public EmbedderGraph { + public: + explicit JSGraph(Isolate* isolate) : isolate_(isolate) {} + + Node* V8Node(const Local<Value>& value) override { + std::unique_ptr<JSGraphJSNode> n { new JSGraphJSNode(isolate_, value) }; + auto it = engine_nodes_.find(n.get()); + if (it != engine_nodes_.end()) + return *it; + engine_nodes_.insert(n.get()); + return AddNode(std::unique_ptr<Node>(n.release())); + } + + Node* AddNode(std::unique_ptr<Node> node) override { + Node* n = node.get(); + nodes_.emplace(std::move(node)); + return n; + } + + void AddEdge(Node* from, Node* to) override { + edges_[from].insert(to); + } + + MaybeLocal<Array> CreateObject() const { + EscapableHandleScope handle_scope(isolate_); + Local<Context> context = isolate_->GetCurrentContext(); + + std::unordered_map<Node*, Local<Object>> info_objects; + Local<Array> nodes = Array::New(isolate_, nodes_.size()); + Local<String> edges_string = FIXED_ONE_BYTE_STRING(isolate_, "edges"); + Local<String> is_root_string = FIXED_ONE_BYTE_STRING(isolate_, "isRoot"); + Local<String> name_string = FIXED_ONE_BYTE_STRING(isolate_, "name"); + Local<String> size_string = FIXED_ONE_BYTE_STRING(isolate_, "size"); + Local<String> value_string = FIXED_ONE_BYTE_STRING(isolate_, "value"); + Local<String> wraps_string = FIXED_ONE_BYTE_STRING(isolate_, "wraps"); + + for (const std::unique_ptr<Node>& n : nodes_) + info_objects[n.get()] = Object::New(isolate_); + + { + HandleScope handle_scope(isolate_); + size_t i = 0; + for (const std::unique_ptr<Node>& n : nodes_) { + Local<Object> obj = info_objects[n.get()]; + Local<Value> value; + if (!String::NewFromUtf8(isolate_, n->Name(), + v8::NewStringType::kNormal).ToLocal(&value) || + obj->Set(context, name_string, value).IsNothing() || + obj->Set(context, is_root_string, + Boolean::New(isolate_, n->IsRootNode())).IsNothing() || + obj->Set(context, size_string, + Number::New(isolate_, n->SizeInBytes())).IsNothing() || + obj->Set(context, edges_string, + Array::New(isolate_)).IsNothing()) { + return MaybeLocal<Array>(); + } + if (nodes->Set(context, i++, obj).IsNothing()) + return MaybeLocal<Array>(); + if (!n->IsEmbedderNode()) { + value = static_cast<JSGraphJSNode*>(n.get())->JSValue(); + if (obj->Set(context, value_string, value).IsNothing()) + return MaybeLocal<Array>(); + } + } + } + + for (const std::unique_ptr<Node>& n : nodes_) { + Node* wraps = n->WrapperNode(); + if (wraps == nullptr) continue; + Local<Object> from = info_objects[n.get()]; + Local<Object> to = info_objects[wraps]; + if (from->Set(context, wraps_string, to).IsNothing()) + return MaybeLocal<Array>(); + } + + for (const auto& edge_info : edges_) { + Node* source = edge_info.first; + Local<Value> edges; + if (!info_objects[source]->Get(context, edges_string).ToLocal(&edges) || + !edges->IsArray()) { + return MaybeLocal<Array>(); + } + + size_t i = 0; + for (Node* target : edge_info.second) { + if (edges.As<Array>()->Set(context, + i++, + info_objects[target]).IsNothing()) { + return MaybeLocal<Array>(); + } + } + } + + return handle_scope.Escape(nodes); + } + + private: + Isolate* isolate_; + std::unordered_set<std::unique_ptr<Node>> nodes_; + std::unordered_set<JSGraphJSNode*, JSGraphJSNode::Hash, JSGraphJSNode::Equal> + engine_nodes_; + std::unordered_map<Node*, std::unordered_set<Node*>> edges_; +}; + +void BuildEmbedderGraph(const FunctionCallbackInfo<Value>& args) { + Environment* env = Environment::GetCurrent(args); + JSGraph graph(env->isolate()); + Environment::BuildEmbedderGraph(env->isolate(), &graph, env); + Local<Array> ret; + if (graph.CreateObject().ToLocal(&ret)) + args.GetReturnValue().Set(ret); +} + + +class BufferOutputStream : public v8::OutputStream { + public: + BufferOutputStream() : buffer_(new JSString()) {} + + void EndOfStream() override {} + int GetChunkSize() override { return 1024 * 1024; } + WriteResult WriteAsciiChunk(char* data, int size) override { + buffer_->Append(data, size); + return kContinue; + } + + Local<String> ToString(Isolate* isolate) { + return String::NewExternalOneByte(isolate, + buffer_.release()).ToLocalChecked(); + } + + private: + class JSString : public String::ExternalOneByteStringResource { + public: + void Append(char* data, size_t count) { + store_.append(data, count); + } + + const char* data() const override { return store_.data(); } + size_t length() const override { return store_.size(); } + + private: + std::string store_; + }; + + std::unique_ptr<JSString> buffer_; +}; + +void CreateHeapDump(const FunctionCallbackInfo<Value>& args) { + Isolate* isolate = args.GetIsolate(); + const HeapSnapshot* snapshot = isolate->GetHeapProfiler()->TakeHeapSnapshot(); + BufferOutputStream out; + snapshot->Serialize(&out, HeapSnapshot::kJSON); + const_cast<HeapSnapshot*>(snapshot)->Delete(); + Local<Value> ret; + if (JSON::Parse(isolate->GetCurrentContext(), + out.ToString(isolate)).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } +} + +void Initialize(Local<Object> target, + Local<Value> unused, + Local<Context> context) { + Environment* env = Environment::GetCurrent(context); + + env->SetMethodNoSideEffect(target, "buildEmbedderGraph", BuildEmbedderGraph); + env->SetMethodNoSideEffect(target, "createHeapDump", CreateHeapDump); +} + +} // namespace heap +} // namespace node + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(heap_utils, node::heap::Initialize) diff --git a/src/node_internals.h b/src/node_internals.h index cd791f8c059caf..d29400d4594569 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -111,6 +111,7 @@ struct sockaddr; V(domain) \ V(fs) \ V(fs_event_wrap) \ + V(heap_utils) \ V(http2) \ V(http_parser) \ V(inspector) \ From 7352b72fc9a740c21dc848a3bb7b0f67313f5776 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Tue, 10 Jul 2018 22:12:23 +0200 Subject: [PATCH 090/116] test: add heap snapshot tests Add a number of tests that validate that heap snapshots contain what we expect them to contain, and cross-check against a JS version of our own embedder graphs. PR-URL: https://github.com/nodejs/node/pull/21741 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> --- test/common/README.md | 37 +++++++++++ test/common/heap.js | 80 +++++++++++++++++++++++ test/parallel/test-heapdump-dns.js | 17 +++++ test/parallel/test-heapdump-fs-promise.js | 16 +++++ test/parallel/test-heapdump-http2.js | 76 +++++++++++++++++++++ test/parallel/test-heapdump-inspector.js | 21 ++++++ test/parallel/test-heapdump-tls.js | 33 ++++++++++ test/parallel/test-heapdump-worker.js | 27 ++++++++ test/parallel/test-heapdump-zlib.js | 17 +++++ 9 files changed, 324 insertions(+) create mode 100644 test/common/heap.js create mode 100644 test/parallel/test-heapdump-dns.js create mode 100644 test/parallel/test-heapdump-fs-promise.js create mode 100644 test/parallel/test-heapdump-http2.js create mode 100644 test/parallel/test-heapdump-inspector.js create mode 100644 test/parallel/test-heapdump-tls.js create mode 100644 test/parallel/test-heapdump-worker.js create mode 100644 test/parallel/test-heapdump-zlib.js diff --git a/test/common/README.md b/test/common/README.md index 111ce45b00360c..c0051ad9f7ca6e 100644 --- a/test/common/README.md +++ b/test/common/README.md @@ -10,6 +10,7 @@ This directory contains modules used to test the Node.js implementation. * [DNS module](#dns-module) * [Duplex pair helper](#duplex-pair-helper) * [Fixtures module](#fixtures-module) +* [Heap dump checker module](#heap-dump-checker-module) * [HTTP2 module](#http2-module) * [Internet module](#internet-module) * [tmpdir module](#tmpdir-module) @@ -538,6 +539,42 @@ Returns the result of Returns the result of `fs.readFileSync(path.join(fixtures.fixturesDir, 'keys', arg), 'enc')`. +## Heap dump checker module + +This provides utilities for checking the validity of heap dumps. +This requires the usage of `--expose-internals`. + +### heap.recordState() + +Create a heap dump and an embedder graph copy for inspection. +The returned object has a `validateSnapshotNodes` function similar to the +one listed below. (`heap.validateSnapshotNodes(...)` is a shortcut for +`heap.recordState().validateSnapshotNodes(...)`.) + +### heap.validateSnapshotNodes(name, expected, options) + +* `name` [<string>] Look for this string as the name of heap dump nodes. +* `expected` [<Array>] A list of objects, possibly with an `children` + property that points to expected other adjacent nodes. +* `options` [<Array>] + * `loose` [<boolean>] Do not expect an exact listing of occurrences + of nodes with name `name` in `expected`. + +Create a heap dump and an embedder graph copy and validate occurrences. + +<!-- eslint-disable no-undef, no-unused-vars, node-core/required-modules, strict --> +```js +validateSnapshotNodes('TLSWRAP', [ + { + children: [ + { name: 'enc_out' }, + { name: 'enc_in' }, + { name: 'TLSWrap' } + ] + } +]); +``` + ## HTTP/2 Module The http2.js module provides a handful of utilities for creating mock HTTP/2 diff --git a/test/common/heap.js b/test/common/heap.js new file mode 100644 index 00000000000000..a02de9a60651f4 --- /dev/null +++ b/test/common/heap.js @@ -0,0 +1,80 @@ +/* eslint-disable node-core/required-modules */ +'use strict'; +const assert = require('assert'); +const util = require('util'); + +let internalTestHeap; +try { + internalTestHeap = require('internal/test/heap'); +} catch (e) { + console.log('using `test/common/heap.js` requires `--expose-internals`'); + throw e; +} +const { createJSHeapDump, buildEmbedderGraph } = internalTestHeap; + +class State { + constructor() { + this.snapshot = createJSHeapDump(); + this.embedderGraph = buildEmbedderGraph(); + } + + validateSnapshotNodes(name, expected, { loose = false } = {}) { + const snapshot = this.snapshot.filter( + (node) => node.name === 'Node / ' + name && node.type !== 'string'); + if (loose) + assert(snapshot.length >= expected.length); + else + assert.strictEqual(snapshot.length, expected.length); + for (const expectedNode of expected) { + if (expectedNode.children) { + for (const expectedChild of expectedNode.children) { + const check = typeof expectedChild === 'function' ? + expectedChild : + (node) => [expectedChild.name, 'Node / ' + expectedChild.name] + .includes(node.name); + + assert(snapshot.some((node) => { + return node.outgoingEdges.map((edge) => edge.toNode).some(check); + }), `expected to find child ${util.inspect(expectedChild)} ` + + `in ${util.inspect(snapshot)}`); + } + } + } + + const graph = this.embedderGraph.filter((node) => node.name === name); + if (loose) + assert(graph.length >= expected.length); + else + assert.strictEqual(graph.length, expected.length); + for (const expectedNode of expected) { + if (expectedNode.edges) { + for (const expectedChild of expectedNode.children) { + const check = typeof expectedChild === 'function' ? + expectedChild : (node) => { + return node.name === expectedChild.name || + (node.value && + node.value.constructor && + node.value.constructor.name === expectedChild.name); + }; + + assert(graph.some((node) => node.edges.some(check)), + `expected to find child ${util.inspect(expectedChild)} ` + + `in ${util.inspect(snapshot)}`); + } + } + } + } +} + +function recordState() { + return new State(); +} + +function validateSnapshotNodes(...args) { + return recordState().validateSnapshotNodes(...args); +} + +module.exports = { + recordState, + validateSnapshotNodes +}; diff --git a/test/parallel/test-heapdump-dns.js b/test/parallel/test-heapdump-dns.js new file mode 100644 index 00000000000000..011503f5874d5a --- /dev/null +++ b/test/parallel/test-heapdump-dns.js @@ -0,0 +1,17 @@ +// Flags: --expose-internals +'use strict'; +require('../common'); +const { validateSnapshotNodes } = require('../common/heap'); + +validateSnapshotNodes('DNSCHANNEL', []); +const dns = require('dns'); +validateSnapshotNodes('DNSCHANNEL', [{}]); +dns.resolve('localhost', () => {}); +validateSnapshotNodes('DNSCHANNEL', [ + { + children: [ + { name: 'task list' }, + { name: 'ChannelWrap' } + ] + } +]); diff --git a/test/parallel/test-heapdump-fs-promise.js b/test/parallel/test-heapdump-fs-promise.js new file mode 100644 index 00000000000000..be44b3d8731bc1 --- /dev/null +++ b/test/parallel/test-heapdump-fs-promise.js @@ -0,0 +1,16 @@ +// Flags: --expose-internals +'use strict'; +require('../common'); +const { validateSnapshotNodes } = require('../common/heap'); +const fs = require('fs').promises; + +validateSnapshotNodes('FSREQPROMISE', []); +fs.stat(__filename); +validateSnapshotNodes('FSREQPROMISE', [ + { + children: [ + { name: 'FSReqPromise' }, + { name: 'Float64Array' } // Stat array + ] + } +]); diff --git a/test/parallel/test-heapdump-http2.js b/test/parallel/test-heapdump-http2.js new file mode 100644 index 00000000000000..19a70d8c44b15d --- /dev/null +++ b/test/parallel/test-heapdump-http2.js @@ -0,0 +1,76 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +const { recordState } = require('../common/heap'); +const http2 = require('http2'); +if (!common.hasCrypto) + common.skip('missing crypto'); + +{ + const state = recordState(); + state.validateSnapshotNodes('HTTP2SESSION', []); + state.validateSnapshotNodes('HTTP2STREAM', []); +} + +const server = http2.createServer(); +server.on('stream', (stream) => { + stream.respondWithFile(__filename); +}); +server.listen(0, () => { + const client = http2.connect(`http://localhost:${server.address().port}`); + const req = client.request(); + + req.on('response', common.mustCall(() => { + const state = recordState(); + state.validateSnapshotNodes('HTTP2STREAM', [ + { + children: [ + { name: 'Http2Stream' } + ] + }, + ], { loose: true }); + state.validateSnapshotNodes('FILEHANDLE', [ + { + children: [ + { name: 'FileHandle' } + ] + } + ]); + state.validateSnapshotNodes('TCPWRAP', [ + { + children: [ + { name: 'TCP' } + ] + } + ], { loose: true }); + state.validateSnapshotNodes('TCPSERVERWRAP', [ + { + children: [ + { name: 'TCP' } + ] + } + ], { loose: true }); + state.validateSnapshotNodes('STREAMPIPE', [ + { + children: [ + { name: 'StreamPipe' } + ] + } + ]); + state.validateSnapshotNodes('HTTP2SESSION', [ + { + children: [ + { name: 'Http2Session' }, + { name: 'streams' } + ] + } + ], { loose: true }); + })); + + req.resume(); + req.on('end', common.mustCall(() => { + client.close(); + server.close(); + })); + req.end(); +}); diff --git a/test/parallel/test-heapdump-inspector.js b/test/parallel/test-heapdump-inspector.js new file mode 100644 index 00000000000000..355b8d0d0a1d51 --- /dev/null +++ b/test/parallel/test-heapdump-inspector.js @@ -0,0 +1,21 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); + +common.skipIfInspectorDisabled(); + +const { validateSnapshotNodes } = require('../common/heap'); +const inspector = require('inspector'); + +const session = new inspector.Session(); +validateSnapshotNodes('INSPECTORJSBINDING', []); +session.connect(); +validateSnapshotNodes('INSPECTORJSBINDING', [ + { + children: [ + { name: 'session' }, + { name: 'Connection' }, + (node) => node.type === 'closure' || typeof node.value === 'function' + ] + } +]); diff --git a/test/parallel/test-heapdump-tls.js b/test/parallel/test-heapdump-tls.js new file mode 100644 index 00000000000000..be14b7b5f7ca64 --- /dev/null +++ b/test/parallel/test-heapdump-tls.js @@ -0,0 +1,33 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); + +if (!common.hasCrypto) + common.skip('missing crypto'); + +const { validateSnapshotNodes } = require('../common/heap'); +const net = require('net'); +const tls = require('tls'); + +validateSnapshotNodes('TLSWRAP', []); + +const server = net.createServer(common.mustCall((c) => { + c.end(); +})).listen(0, common.mustCall(() => { + const c = tls.connect({ port: server.address().port }); + + c.on('error', common.mustCall(() => { + server.close(); + })); + c.write('hello'); + + validateSnapshotNodes('TLSWRAP', [ + { + children: [ + { name: 'enc_out' }, + { name: 'enc_in' }, + { name: 'TLSWrap' } + ] + } + ]); +})); diff --git a/test/parallel/test-heapdump-worker.js b/test/parallel/test-heapdump-worker.js new file mode 100644 index 00000000000000..68d2ccd1abbc29 --- /dev/null +++ b/test/parallel/test-heapdump-worker.js @@ -0,0 +1,27 @@ +// Flags: --expose-internals --experimental-worker +'use strict'; +require('../common'); +const { validateSnapshotNodes } = require('../common/heap'); +const { Worker } = require('worker_threads'); + +validateSnapshotNodes('WORKER', []); +const worker = new Worker('setInterval(() => {}, 100);', { eval: true }); +validateSnapshotNodes('WORKER', [ + { + children: [ + { name: 'thread_exit_async' }, + { name: 'env' }, + { name: 'MESSAGEPORT' }, + { name: 'Worker' } + ] + } +]); +validateSnapshotNodes('MESSAGEPORT', [ + { + children: [ + { name: 'data' }, + { name: 'MessagePort' } + ] + } +], { loose: true }); +worker.terminate(); diff --git a/test/parallel/test-heapdump-zlib.js b/test/parallel/test-heapdump-zlib.js new file mode 100644 index 00000000000000..7a749902f5aaf6 --- /dev/null +++ b/test/parallel/test-heapdump-zlib.js @@ -0,0 +1,17 @@ +// Flags: --expose-internals +'use strict'; +require('../common'); +const { validateSnapshotNodes } = require('../common/heap'); +const zlib = require('zlib'); + +validateSnapshotNodes('ZLIB', []); +// eslint-disable-next-line no-unused-vars +const gunzip = zlib.createGunzip(); +validateSnapshotNodes('ZLIB', [ + { + children: [ + { name: 'Zlib' }, + { name: 'zlib memory' } + ] + } +]); From 10f9374ea302b84f831d28a23bfe83d666594bbd Mon Sep 17 00:00:00 2001 From: Sam Ruby <rubys@intertwingly.net> Date: Thu, 12 Jul 2018 13:28:42 -0400 Subject: [PATCH 091/116] doc: make markdown input compliant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/21780 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> --- doc/api/crypto.md | 12 ++++---- doc/api/fs.md | 4 +-- doc/api/http.md | 6 ++-- doc/api/os.md | 15 ++++----- doc/api/stream.md | 77 ++++------------------------------------------- 5 files changed, 25 insertions(+), 89 deletions(-) diff --git a/doc/api/crypto.md b/doc/api/crypto.md index 376a8c8a223d93..87f26716af3230 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -2674,18 +2674,18 @@ the `crypto`, `tls`, and `https` modules and are generally specific to OpenSSL. </tr> <tr> <td><code>RSA_PSS_SALTLEN_DIGEST</code></td> - <td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the digest size - when signing or verifying.</td> + <td>Sets the salt length for <code>RSA_PKCS1_PSS_PADDING</code> to the + digest size when signing or verifying.</td> </tr> <tr> <td><code>RSA_PSS_SALTLEN_MAX_SIGN</code></td> - <td>Sets the salt length for `RSA_PKCS1_PSS_PADDING` to the maximum - permissible value when signing data.</td> + <td>Sets the salt length for <code>RSA_PKCS1_PSS_PADDING</code> to the + maximum permissible value when signing data.</td> </tr> <tr> <td><code>RSA_PSS_SALTLEN_AUTO</code></td> - <td>Causes the salt length for `RSA_PKCS1_PSS_PADDING` to be determined - automatically when verifying a signature.</td> + <td>Causes the salt length for <code>RSA_PKCS1_PSS_PADDING</code> to be + determined automatically when verifying a signature.</td> </tr> <tr> <td><code>POINT_CONVERSION_COMPRESSED</code></td> diff --git a/doc/api/fs.md b/doc/api/fs.md index d3f981cf79964d..dafda61124ec00 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -4373,8 +4373,8 @@ The following constants are meant for use with `fs.open()`. <tr> <td><code>O_NOATIME</code></td> <td>Flag indicating reading accesses to the file system will no longer - result in an update to the `atime` information associated with the file. - This flag is available on Linux operating systems only.</td> + result in an update to the <code>atime</code> information associated with + the file. This flag is available on Linux operating systems only.</td> </tr> <tr> <td><code>O_NOFOLLOW</code></td> diff --git a/doc/api/http.md b/doc/api/http.md index 578f21c45e30ca..fd8bb02776d03f 100644 --- a/doc/api/http.md +++ b/doc/api/http.md @@ -1896,9 +1896,9 @@ changes: Authorization header. * `agent` {http.Agent | boolean} Controls [`Agent`][] behavior. Possible values: - * `undefined` (default): use [`http.globalAgent`][] for this host and port. - * `Agent` object: explicitly use the passed in `Agent`. - * `false`: causes a new `Agent` with default values to be used. + * `undefined` (default): use [`http.globalAgent`][] for this host and port. + * `Agent` object: explicitly use the passed in `Agent`. + * `false`: causes a new `Agent` with default values to be used. * `createConnection` {Function} A function that produces a socket/stream to use for the request when the `agent` option is not used. This can be used to avoid creating a custom `Agent` class just to override the default diff --git a/doc/api/os.md b/doc/api/os.md index 8db4904d29a489..b7bd246f97dbfd 100644 --- a/doc/api/os.md +++ b/doc/api/os.md @@ -442,7 +442,7 @@ The following signal constants are exported by `os.constants.signals`: <tr> <td><code>SIGINT</code></td> <td>Sent to indicate when a user wishes to interrupt a process - (`(Ctrl+C)`).</td> + (<code>(Ctrl+C)</code>).</td> </tr> <tr> <td><code>SIGQUIT</code></td> @@ -855,9 +855,9 @@ The following error constants are exported by `os.constants.errno`: </tr> <tr> <td><code>EOPNOTSUPP</code></td> - <td>Indicates that an operation is not supported on the socket. - Note that while `ENOTSUP` and `EOPNOTSUPP` have the same value on Linux, - according to POSIX.1 these error values should be distinct.)</td> + <td>Indicates that an operation is not supported on the socket. Note that + while <code>ENOTSUP</code> and <code>EOPNOTSUPP</code> have the same value + on Linux, according to POSIX.1 these error values should be distinct.)</td> </tr> <tr> <td><code>EOVERFLOW</code></td> @@ -1114,7 +1114,8 @@ The following error codes are specific to the Windows operating system: </tr> <tr> <td><code>WSAVERNOTSUPPORTED</code></td> - <td>Indicates that the `winsock.dll` version is out of range.</td> + <td>Indicates that the <code>winsock.dll</code> version is out of + range.</td> </tr> <tr> <td><code>WSANOTINITIALISED</code></td> @@ -1197,8 +1198,8 @@ information. </tr> <tr> <td><code>RTLD_LOCAL</code></td> - <td>The converse of `RTLD_GLOBAL`. This is the default behavior if neither - flag is specified.</td> + <td>The converse of <code>RTLD_GLOBAL</code>. This is the default behavior + if neither flag is specified.</td> </tr> <tr> <td><code>RTLD_DEEPBIND</code></td> diff --git a/doc/api/stream.md b/doc/api/stream.md index 7e7a3fb92a933d..5ca713a27b6e0f 100644 --- a/doc/api/stream.md +++ b/doc/api/stream.md @@ -1419,77 +1419,12 @@ class MyWritable extends Writable { The new stream class must then implement one or more specific methods, depending on the type of stream being created, as detailed in the chart below: -<table> - <thead> - <tr> - <th> - <p>Use-case</p> - </th> - <th> - <p>Class</p> - </th> - <th> - <p>Method(s) to implement</p> - </th> - </tr> - </thead> - <tr> - <td> - <p>Reading only</p> - </td> - <td> - <p>[`Readable`](#stream_class_stream_readable)</p> - </td> - <td> - <p><code>[_read][stream-_read]</code></p> - </td> - </tr> - <tr> - <td> - <p>Writing only</p> - </td> - <td> - <p>[`Writable`](#stream_class_stream_writable)</p> - </td> - <td> - <p> - <code>[_write][stream-_write]</code>, - <code>[_writev][stream-_writev]</code>, - <code>[_final][stream-_final]</code> - </p> - </td> - </tr> - <tr> - <td> - <p>Reading and writing</p> - </td> - <td> - <p>[`Duplex`](#stream_class_stream_duplex)</p> - </td> - <td> - <p> - <code>[_read][stream-_read]</code>, - <code>[_write][stream-_write]</code>, - <code>[_writev][stream-_writev]</code>, - <code>[_final][stream-_final]</code></p> - </td> - </tr> - <tr> - <td> - <p>Operate on written data, then read the result</p> - </td> - <td> - <p>[`Transform`](#stream_class_stream_transform)</p> - </td> - <td> - <p> - <code>[_transform][stream-_transform]</code>, - <code>[_flush][stream-_flush]</code>, - <code>[_final][stream-_final]</code> - </p> - </td> - </tr> -</table> +| Use-case | Class | Method(s) to implement | +| -------- | ----- | ---------------------- | +| Reading only | [`Readable`] | <code>[_read][stream-_read]</code> | +| Writing only | [`Writable`] | <code>[_write][stream-_write]</code>, <code>[_writev][stream-_writev]</code>, <code>[_final][stream-_final]</code> | +| Reading and writing | [`Duplex`] | <code>[_read][stream-_read]</code>, <code>[_write][stream-_write]</code>, <code>[_writev][stream-_writev]</code>, <code>[_final][stream-_final]</code> | +| Operate on written data, then read the result | [`Transform`] | <code>[_transform][stream-_transform]</code>, <code>[_flush][stream-_flush]</code>, <code>[_final][stream-_final]</code> | The implementation code for a stream should *never* call the "public" methods of a stream that are intended for use by consumers (as described in the From 51dfebf9acc30173f15b099cd0c378aaa892fd00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= <targos@protonmail.com> Date: Sun, 15 Jul 2018 17:27:07 +0200 Subject: [PATCH 092/116] doc: fix vm.runInNewContext signature The `options` parameter cannot be passed without `sandbox`. PR-URL: https://github.com/nodejs/node/pull/21824 Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net> --- doc/api/vm.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/vm.md b/doc/api/vm.md index d3be08f89b377a..7d14293ac4e45b 100644 --- a/doc/api/vm.md +++ b/doc/api/vm.md @@ -758,7 +758,7 @@ console.log(util.inspect(sandbox)); // { globalVar: 1024 } ``` -## vm.runInNewContext(code[, sandbox][, options]) +## vm.runInNewContext(code[, sandbox[, options]]) <!-- YAML added: v0.3.1 --> From 1019c2d317d39fd76fe92a33d82b5ccea608602e Mon Sep 17 00:00:00 2001 From: Javier Gonzalez <xaviergonz@gmail.com> Date: Sun, 4 Mar 2018 19:28:38 +0100 Subject: [PATCH 093/116] src: fix async hooks crashing when there is no node context PR-URL: https://github.com/nodejs/node/pull/19134 Fixes: https://github.com/nodejs/node/issues/19104 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> --- src/env-inl.h | 3 +++ src/env.cc | 20 +++++++++++++++++++- src/env.h | 2 ++ src/node_context_data.h | 10 ++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/env-inl.h b/src/env-inl.h index 4ef9665cbddfed..5c359c04d907cb 100644 --- a/src/env-inl.h +++ b/src/env-inl.h @@ -290,6 +290,9 @@ inline void Environment::AssignToContext(v8::Local<v8::Context> context, const ContextInfo& info) { context->SetAlignedPointerInEmbedderData( ContextEmbedderIndex::kEnvironment, this); + // Used by EnvPromiseHook to know that we are on a node context. + context->SetAlignedPointerInEmbedderData( + ContextEmbedderIndex::kContextTag, Environment::kNodeContextTagPtr); #if HAVE_INSPECTOR inspector_agent()->ContextCreated(context, info); #endif // HAVE_INSPECTOR diff --git a/src/env.cc b/src/env.cc index 3d1a9372c93c7d..fffe5d61d0685a 100644 --- a/src/env.cc +++ b/src/env.cc @@ -4,6 +4,7 @@ #include "node_buffer.h" #include "node_platform.h" #include "node_file.h" +#include "node_context_data.h" #include "node_worker.h" #include "tracing/agent.h" @@ -28,6 +29,10 @@ using v8::Symbol; using v8::Value; using worker::Worker; +int const Environment::kNodeContextTag = 0x6e6f64; +void* Environment::kNodeContextTagPtr = const_cast<void*>( + static_cast<const void*>(&Environment::kNodeContextTag)); + IsolateData::IsolateData(Isolate* isolate, uv_loop_t* event_loop, MultiIsolatePlatform* platform, @@ -430,7 +435,20 @@ bool Environment::RemovePromiseHook(promise_hook_func fn, void* arg) { void Environment::EnvPromiseHook(v8::PromiseHookType type, v8::Local<v8::Promise> promise, v8::Local<v8::Value> parent) { - Environment* env = Environment::GetCurrent(promise->CreationContext()); + Local<v8::Context> context = promise->CreationContext(); + + // Grow the embedder data if necessary to make sure we are not out of bounds + // when reading the magic number. + context->SetAlignedPointerInEmbedderData( + ContextEmbedderIndex::kContextTagBoundary, nullptr); + int* magicNumberPtr = reinterpret_cast<int*>( + context->GetAlignedPointerFromEmbedderData( + ContextEmbedderIndex::kContextTag)); + if (magicNumberPtr != Environment::kNodeContextTagPtr) { + return; + } + + Environment* env = Environment::GetCurrent(context); for (const PromiseHookCallback& hook : env->promise_hooks_) { hook.cb_(type, promise, parent, hook.arg_); } diff --git a/src/env.h b/src/env.h index 9c7f4d1ba8ae3f..ede221400b4965 100644 --- a/src/env.h +++ b/src/env.h @@ -902,6 +902,8 @@ class Environment { uint64_t thread_id_ = 0; std::unordered_set<worker::Worker*> sub_worker_contexts_; + static void* kNodeContextTagPtr; + static int const kNodeContextTag; #if HAVE_INSPECTOR std::unique_ptr<inspector::Agent> inspector_agent_; diff --git a/src/node_context_data.h b/src/node_context_data.h index 522ce292d21684..3892b31354027d 100644 --- a/src/node_context_data.h +++ b/src/node_context_data.h @@ -19,10 +19,20 @@ namespace node { #define NODE_CONTEXT_ALLOW_WASM_CODE_GENERATION_INDEX 34 #endif +#ifndef NODE_CONTEXT_TAG +#define NODE_CONTEXT_TAG 35 +#endif + +#ifndef NODE_CONTEXT_TAG_BOUNDARY +#define NODE_CONTEXT_TAG_BOUNDARY 36 +#endif + enum ContextEmbedderIndex { kEnvironment = NODE_CONTEXT_EMBEDDER_DATA_INDEX, kSandboxObject = NODE_CONTEXT_SANDBOX_OBJECT_INDEX, kAllowWasmCodeGeneration = NODE_CONTEXT_ALLOW_WASM_CODE_GENERATION_INDEX, + kContextTag = NODE_CONTEXT_TAG, + kContextTagBoundary = NODE_CONTEXT_TAG_BOUNDARY, }; } // namespace node From 238ef58841fef61cda438589705c2af9d2fb4b91 Mon Sep 17 00:00:00 2001 From: RidgeA <antongriadchenko@gmail.com> Date: Wed, 11 Jul 2018 17:29:42 +0300 Subject: [PATCH 094/116] http2: remove `waitTrailers` listener after closing a stream When `writeHeader` of `Http2ServerResponse` instance are called with 204, 205 and 304 status codes an underlying stream closes. If call `end` method after sending any of these status codes it will cause an error `TypeError: Cannot read property 'Symbol(trailers)' of undefined` because a reference to `Http2ServerResponse` instance associated with Http2Stream already was deleted. The closing of stream causes emitting `waitTrailers` event and, when this event handles inside `onStreamTrailerReady` handler, there is no reference to Http2ServerResponse instance. Fixes: https://github.com/nodejs/node/issues/21740 PR-URL: https://github.com/nodejs/node/pull/21764 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> --- lib/internal/http2/compat.js | 2 + ...esponse-end-after-statuses-without-body.js | 47 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 test/parallel/test-http2-compat-serverresponse-end-after-statuses-without-body.js diff --git a/lib/internal/http2/compat.js b/lib/internal/http2/compat.js index 88879484ebaab7..4a006107b2fcea 100644 --- a/lib/internal/http2/compat.js +++ b/lib/internal/http2/compat.js @@ -391,6 +391,8 @@ function onStreamCloseResponse() { state.closed = true; this[kProxySocket] = null; + + this.removeListener('wantTrailers', onStreamTrailersReady); this[kResponse] = undefined; res.emit('finish'); diff --git a/test/parallel/test-http2-compat-serverresponse-end-after-statuses-without-body.js b/test/parallel/test-http2-compat-serverresponse-end-after-statuses-without-body.js new file mode 100644 index 00000000000000..83d5521bf2473f --- /dev/null +++ b/test/parallel/test-http2-compat-serverresponse-end-after-statuses-without-body.js @@ -0,0 +1,47 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const h2 = require('http2'); + +// This test case ensures that calling of res.end after sending +// 204, 205 and 304 HTTP statuses will not cause an error +// See issue: https://github.com/nodejs/node/issues/21740 + +const { + HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_RESET_CONTENT, + HTTP_STATUS_NOT_MODIFIED +} = h2.constants; + +const statusWithouBody = [ + HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_RESET_CONTENT, + HTTP_STATUS_NOT_MODIFIED, +]; +const STATUS_CODES_COUNT = statusWithouBody.length; + +const server = h2.createServer(common.mustCall(function(req, res) { + res.writeHead(statusWithouBody.pop()); + res.end(); +}, STATUS_CODES_COUNT)); + +server.listen(0, common.mustCall(function() { + const url = `http://localhost:${server.address().port}`; + const client = h2.connect(url, common.mustCall(() => { + let responseCount = 0; + const closeAfterResponse = () => { + if (STATUS_CODES_COUNT === ++responseCount) { + client.destroy(); + server.close(); + } + }; + + for (let i = 0; i < STATUS_CODES_COUNT; i++) { + const request = client.request(); + request.on('response', common.mustCall(closeAfterResponse)); + } + + })); +})); From 32ad163038100a4a5c20ddf5c3b5cf2aea93a8eb Mon Sep 17 00:00:00 2001 From: Ryuichi Sakagami <shagamiiiii@gmail.com> Date: Sun, 8 Jul 2018 19:09:18 +0900 Subject: [PATCH 095/116] test: add test of fs.promises write for non-string buffers PR-URL: https://github.com/nodejs/node/pull/21708 Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Shingo Inoue <leko.noor@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- .../test-fs-promises-file-handle-write.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-fs-promises-file-handle-write.js b/test/parallel/test-fs-promises-file-handle-write.js index d7812745c5439e..3cff69f48dfb72 100644 --- a/test/parallel/test-fs-promises-file-handle-write.js +++ b/test/parallel/test-fs-promises-file-handle-write.js @@ -45,8 +45,22 @@ async function validateNonUint8ArrayWrite() { assert.deepStrictEqual(Buffer.from(buffer, 'utf8'), readFileData); } +async function validateNonStringValuesWrite() { + const filePathForHandle = path.resolve(tmpDir, 'tmp-non-string-write.txt'); + const fileHandle = await open(filePathForHandle, 'w+'); + const nonStringValues = [123, {}, new Map()]; + for (const nonStringValue of nonStringValues) { + await fileHandle.write(nonStringValue); + } + + const readFileData = fs.readFileSync(filePathForHandle); + const expected = ['123', '[object Object]', '[object Map]'].join(''); + assert.deepStrictEqual(Buffer.from(expected, 'utf8'), readFileData); +} + Promise.all([ validateWrite(), validateEmptyWrite(), - validateNonUint8ArrayWrite() + validateNonUint8ArrayWrite(), + validateNonStringValuesWrite() ]).then(common.mustCall()); From a2edb5987060ed101b08db18fe6b9cd930dae254 Mon Sep 17 00:00:00 2001 From: Ryuichi Sakagami <shagamiiiii@gmail.com> Date: Sun, 8 Jul 2018 19:10:12 +0900 Subject: [PATCH 096/116] test: fix comment of fs.promises write PR-URL: https://github.com/nodejs/node/pull/21708 Reviewed-By: Franziska Hinkelmann <franziska.hinkelmann@gmail.com> Reviewed-By: Shingo Inoue <leko.noor@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- test/parallel/test-fs-promises-file-handle-write.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/parallel/test-fs-promises-file-handle-write.js b/test/parallel/test-fs-promises-file-handle-write.js index 3cff69f48dfb72..49df2bf54f45ed 100644 --- a/test/parallel/test-fs-promises-file-handle-write.js +++ b/test/parallel/test-fs-promises-file-handle-write.js @@ -3,7 +3,7 @@ const common = require('../common'); // The following tests validate base functionality for the fs.promises -// FileHandle.read method. +// FileHandle.write method. const fs = require('fs'); const { open } = fs.promises; From 4e60ce8f87fef6307bc2ca947b69da5c08ccdacd Mon Sep 17 00:00:00 2001 From: Rich Trott <rtrott@gmail.com> Date: Sun, 15 Jul 2018 10:52:36 -0700 Subject: [PATCH 097/116] test: fix flaky test-debug-prompt Be sure to send `.exit` only once to avoid spurious EPIPE and possibly other errors. Fixes: https://github.com/nodejs/node/issues/21724 PR-URL: https://github.com/nodejs/node/pull/21826 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: Jon Moss <me@jonathanmoss.me> --- test/sequential/test-debug-prompt.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/sequential/test-debug-prompt.js b/test/sequential/test-debug-prompt.js index 902bce34fc46f4..e32f4646900536 100644 --- a/test/sequential/test-debug-prompt.js +++ b/test/sequential/test-debug-prompt.js @@ -7,9 +7,12 @@ const spawn = require('child_process').spawn; const proc = spawn(process.execPath, ['inspect', 'foo']); proc.stdout.setEncoding('utf8'); +let needToSendExit = true; let output = ''; proc.stdout.on('data', (data) => { output += data; - if (output.includes('debug> ')) + if (output.includes('debug> ') && needToSendExit) { proc.stdin.write('.exit\n'); + needToSendExit = false; + } }); From 73cafd853cc338a34c8dcfe043feb37ec9d32b98 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Sat, 19 May 2018 00:55:54 +0200 Subject: [PATCH 098/116] console,util: avoid pair array generation in C++ Use a plain `[key, value, key, value]`-style list instead of an array of pairs for inspecting collections. This also fixes a bug with `console.table()` where inspecting a non-key-value `MapIterator` would have led to odd results. PR-URL: https://github.com/nodejs/node/pull/20831 Refs: https://github.com/nodejs/node/pull/20719#discussion_r189342513 Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> --- lib/console.js | 28 +++++++++++++++++++--------- lib/util.js | 28 ++++++++++++++++++++-------- src/node_util.cc | 25 ++++++------------------- test/parallel/test-console-table.js | 20 ++++++++++++++++++++ test/parallel/test-util-inspect.js | 4 ++-- 5 files changed, 67 insertions(+), 38 deletions(-) diff --git a/lib/console.js b/lib/console.js index fee3f8ac0b155d..7d6f9c97299e64 100644 --- a/lib/console.js +++ b/lib/console.js @@ -356,17 +356,27 @@ Console.prototype.table = function(tabularData, properties) { const getIndexArray = (length) => ArrayFrom({ length }, (_, i) => inspect(i)); const mapIter = isMapIterator(tabularData); + let isKeyValue = false; + let i = 0; if (mapIter) - tabularData = previewEntries(tabularData); + [ tabularData, isKeyValue ] = previewEntries(tabularData); - if (mapIter || isMap(tabularData)) { + if (isKeyValue || isMap(tabularData)) { const keys = []; const values = []; let length = 0; - for (const [k, v] of tabularData) { - keys.push(inspect(k)); - values.push(inspect(v)); - length++; + if (mapIter) { + for (; i < tabularData.length / 2; ++i) { + keys.push(inspect(tabularData[i * 2])); + values.push(inspect(tabularData[i * 2 + 1])); + length++; + } + } else { + for (const [k, v] of tabularData) { + keys.push(inspect(k)); + values.push(inspect(v)); + length++; + } } return final([ iterKey, keyKey, valuesKey @@ -379,9 +389,9 @@ Console.prototype.table = function(tabularData, properties) { const setIter = isSetIterator(tabularData); if (setIter) - tabularData = previewEntries(tabularData); + [ tabularData ] = previewEntries(tabularData); - const setlike = setIter || isSet(tabularData); + const setlike = setIter || (mapIter && !isKeyValue) || isSet(tabularData); if (setlike) { const values = []; let length = 0; @@ -400,7 +410,7 @@ Console.prototype.table = function(tabularData, properties) { const valuesKeyArray = []; const indexKeyArray = ObjectKeys(tabularData); - for (var i = 0; i < indexKeyArray.length; i++) { + for (; i < indexKeyArray.length; i++) { const item = tabularData[indexKeyArray[i]]; const primitive = item === null || (typeof item !== 'function' && typeof item !== 'object'); diff --git a/lib/util.js b/lib/util.js index 15611fdae108df..8160d802b0332f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -984,7 +984,7 @@ function formatMap(ctx, value, recurseTimes, keys) { function formatWeakSet(ctx, value, recurseTimes, keys) { const maxArrayLength = Math.max(ctx.maxArrayLength, 0); - const entries = previewEntries(value).slice(0, maxArrayLength + 1); + const [ entries ] = previewEntries(value).slice(0, maxArrayLength + 1); const maxLength = Math.min(maxArrayLength, entries.length); let output = new Array(maxLength); for (var i = 0; i < maxLength; ++i) @@ -1001,14 +1001,16 @@ function formatWeakSet(ctx, value, recurseTimes, keys) { function formatWeakMap(ctx, value, recurseTimes, keys) { const maxArrayLength = Math.max(ctx.maxArrayLength, 0); - const entries = previewEntries(value).slice(0, maxArrayLength + 1); - const remainder = entries.length > maxArrayLength; - const len = entries.length - (remainder ? 1 : 0); + const [ entries ] = previewEntries(value).slice(0, (maxArrayLength + 1) * 2); + // Entries exist as [key1, val1, key2, val2, ...] + const remainder = entries.length / 2 > maxArrayLength; + const len = entries.length / 2 - (remainder ? 1 : 0); const maxLength = Math.min(maxArrayLength, len); let output = new Array(maxLength); - for (var i = 0; i < len; i++) { - output[i] = `${formatValue(ctx, entries[i][0], recurseTimes)} => ` + - formatValue(ctx, entries[i][1], recurseTimes); + for (var i = 0; i < maxLength; i++) { + const pos = i * 2; + output[i] = `${formatValue(ctx, entries[pos], recurseTimes)} => ` + + formatValue(ctx, entries[pos + 1], recurseTimes); } // Sort all entries to have a halfway reliable output (if more entries than // retrieved ones exist, we can not reliably return the same output). @@ -1020,9 +1022,19 @@ function formatWeakMap(ctx, value, recurseTimes, keys) { return output; } +function zip2(list) { + const ret = Array(list.length / 2); + for (var i = 0; i < ret.length; ++i) + ret[i] = [list[2 * i], list[2 * i + 1]]; + return ret; +} + function formatCollectionIterator(ctx, value, recurseTimes, keys) { const output = []; - for (const entry of previewEntries(value)) { + var [ entries, isKeyValue ] = previewEntries(value); + if (isKeyValue) + entries = zip2(entries); + for (const entry of entries) { if (ctx.maxArrayLength === output.length) { output.push('... more items'); break; diff --git a/src/node_util.cc b/src/node_util.cc index 724bb3603cfddd..ef7dc8a818bf11 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -53,29 +53,16 @@ static void PreviewEntries(const FunctionCallbackInfo<Value>& args) { if (!args[0]->IsObject()) return; + Environment* env = Environment::GetCurrent(args); bool is_key_value; Local<Array> entries; if (!args[0].As<Object>()->PreviewEntries(&is_key_value).ToLocal(&entries)) return; - if (!is_key_value) - return args.GetReturnValue().Set(entries); - - uint32_t length = entries->Length(); - CHECK_EQ(length % 2, 0); - - Environment* env = Environment::GetCurrent(args); - Local<Context> context = env->context(); - - Local<Array> pairs = Array::New(env->isolate(), length / 2); - for (uint32_t i = 0; i < length / 2; i++) { - Local<Array> pair = Array::New(env->isolate(), 2); - pair->Set(context, 0, entries->Get(context, i * 2).ToLocalChecked()) - .FromJust(); - pair->Set(context, 1, entries->Get(context, i * 2 + 1).ToLocalChecked()) - .FromJust(); - pairs->Set(context, i, pair).FromJust(); - } - args.GetReturnValue().Set(pairs); + Local<Array> ret = Array::New(env->isolate(), 2); + ret->Set(env->context(), 0, entries).FromJust(); + ret->Set(env->context(), 1, v8::Boolean::New(env->isolate(), is_key_value)) + .FromJust(); + return args.GetReturnValue().Set(ret); } // Side effect-free stringification that will never throw exceptions. diff --git a/test/parallel/test-console-table.js b/test/parallel/test-console-table.js index 9ff4641d65923c..844d2e01e4180b 100644 --- a/test/parallel/test-console-table.js +++ b/test/parallel/test-console-table.js @@ -120,6 +120,26 @@ test(new Map([[1, 1], [2, 2], [3, 3]]).entries(), ` └───────────────────┴─────┴────────┘ `); +test(new Map([[1, 1], [2, 2], [3, 3]]).values(), ` +┌───────────────────┬────────┐ +│ (iteration index) │ Values │ +├───────────────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +└───────────────────┴────────┘ +`); + +test(new Map([[1, 1], [2, 2], [3, 3]]).keys(), ` +┌───────────────────┬────────┐ +│ (iteration index) │ Values │ +├───────────────────┼────────┤ +│ 0 │ 1 │ +│ 1 │ 2 │ +│ 2 │ 3 │ +└───────────────────┴────────┘ +`); + test(new Set([1, 2, 3]).values(), ` ┌───────────────────┬────────┐ │ (iteration index) │ Values │ diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 874797665dc415..826e965fd0044d 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -447,13 +447,13 @@ assert.strictEqual(util.inspect(-5e-324), '-5e-324'); { const map = new Map(); map.set(1, 2); - const vals = previewEntries(map.entries()); + const [ vals ] = previewEntries(map.entries()); const valsOutput = []; for (const o of vals) { valsOutput.push(o); } - assert.strictEqual(util.inspect(valsOutput), '[ [ 1, 2 ] ]'); + assert.strictEqual(util.inspect(valsOutput), '[ 1, 2 ]'); } // Test for other constructors in different context. From 2a0862cec9698cea86c8908379cb880d58f6b871 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater <ruben@bridgewater.de> Date: Thu, 12 Jul 2018 16:56:15 +0200 Subject: [PATCH 099/116] console: fix timeEnd() not coercing the input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The input of console.timeEnd() was not coerced to a string. That way labels were not found and the entry was not removed anymore. PR-URL: https://github.com/nodejs/node/pull/21779 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> --- lib/console.js | 6 ++++-- test/parallel/test-console.js | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/console.js b/lib/console.js index 7d6f9c97299e64..98ab0c35712d97 100644 --- a/lib/console.js +++ b/lib/console.js @@ -231,6 +231,8 @@ Console.prototype.time = function time(label = 'default') { }; Console.prototype.timeEnd = function timeEnd(label = 'default') { + // Coerces everything other than Symbol to a string + label = `${label}`; const hasWarned = timeLogImpl(this, 'timeEnd', label); if (!hasWarned) { this._times.delete(label); @@ -238,13 +240,13 @@ Console.prototype.timeEnd = function timeEnd(label = 'default') { }; Console.prototype.timeLog = function timeLog(label, ...data) { + // Coerces everything other than Symbol to a string + label = `${label}`; timeLogImpl(this, 'timeLog', label, data); }; // Returns true if label was not found function timeLogImpl(self, name, label = 'default', data) { - // Coerces everything other than Symbol to a string - label = `${label}`; const time = self._times.get(label); if (!time) { process.emitWarning(`No such label '${label}' for console.${name}()`); diff --git a/test/parallel/test-console.js b/test/parallel/test-console.js index b8dfac2f29888a..a0e6e322a5e42a 100644 --- a/test/parallel/test-console.js +++ b/test/parallel/test-console.js @@ -132,11 +132,14 @@ console.timeEnd('constructor'); console.time('hasOwnProperty'); console.timeEnd('hasOwnProperty'); -// verify that values are coerced to strings +// Verify that values are coerced to strings. console.time([]); console.timeEnd([]); console.time({}); console.timeEnd({}); +// Repeat the object call to verify that everything really worked. +console.time({}); +console.timeEnd({}); console.time(null); console.timeEnd(null); console.time(undefined); @@ -212,6 +215,7 @@ assert.ok(/^hasOwnProperty: \d+\.\d{3}ms$/.test(strings.shift().trim())); // verify that console.time() coerces label values to strings as expected assert.ok(/^: \d+\.\d{3}ms$/.test(strings.shift().trim())); assert.ok(/^\[object Object\]: \d+\.\d{3}ms$/.test(strings.shift().trim())); +assert.ok(/^\[object Object\]: \d+\.\d{3}ms$/.test(strings.shift().trim())); assert.ok(/^null: \d+\.\d{3}ms$/.test(strings.shift().trim())); assert.ok(/^default: \d+\.\d{3}ms$/.test(strings.shift().trim())); assert.ok(/^default: \d+\.\d{3}ms$/.test(strings.shift().trim())); From 8c97ffb2f5664108a066b63f777565419b9dd983 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater <ruben@bridgewater.de> Date: Mon, 2 Jul 2018 20:29:57 +0200 Subject: [PATCH 100/116] assert: improve simple assert 1) If simple assert is called in the very first line of a file and it causes an error, it used to report the wrong code. The reason is that the column that is reported is faulty. This is fixed by subtracting the offset from now on in such cases. 2) The actual code read is now limited to the part that is actually required to visualize the call site. All other code in e.g. minified files will not cause a significant overhead anymore. 3) The number of allocations is now significantly lower than it used to be. The buffer is reused until the correct line in the code is found. In general the algorithm tries to safe operations where possible. 4) The indentation is now corrected depending on where the statement actually beginns. 5) It is now possible to handle `.call()` and `.apply()` properly. 6) The user defined function name will now always be used instead of only choosing either `assert.ok()` or `assert()`. PR-URL: https://github.com/nodejs/node/pull/21626 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ben Coe <bencoe@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> --- lib/assert.js | 224 +++++++++++++++--------- test/fixtures/assert-first-line.js | 2 + test/fixtures/assert-long-line.js | 1 + test/parallel/test-assert-first-line.js | 23 +++ test/parallel/test-assert.js | 63 +++++-- 5 files changed, 221 insertions(+), 92 deletions(-) create mode 100644 test/fixtures/assert-first-line.js create mode 100644 test/fixtures/assert-long-line.js create mode 100644 test/parallel/test-assert-first-line.js diff --git a/lib/assert.js b/lib/assert.js index 47c0e3c1402bdd..5a25e73a9dc1bc 100644 --- a/lib/assert.js +++ b/lib/assert.js @@ -34,6 +34,10 @@ const { NativeModule } = require('internal/bootstrap/loaders'); let isDeepEqual; let isDeepStrictEqual; +let parseExpressionAt; +let findNodeAround; +let columnOffset = 0; +let decoder; function lazyLoadComparison() { const comparison = require('internal/util/comparisons'); @@ -113,52 +117,141 @@ function fail(actual, expected, message, operator, stackStartFn) { assert.fail = fail; // The AssertionError is defined in internal/error. -// new assert.AssertionError({ message: message, -// actual: actual, -// expected: expected }); assert.AssertionError = AssertionError; -function getBuffer(fd, assertLine) { +function findColumn(fd, column, code) { + if (code.length > column + 100) { + try { + return parseCode(code, column); + } catch { + // End recursion in case no code could be parsed. The expression should + // have been found after 2500 characters, so stop trying. + if (code.length - column > 2500) { + // eslint-disable-next-line no-throw-literal + throw null; + } + } + } + // Read up to 2500 bytes more than necessary in columns. That way we address + // multi byte characters and read enough data to parse the code. + const bytesToRead = column - code.length + 2500; + const buffer = Buffer.allocUnsafe(bytesToRead); + const bytesRead = readSync(fd, buffer, 0, bytesToRead); + code += decoder.write(buffer.slice(0, bytesRead)); + // EOF: fast path. + if (bytesRead < bytesToRead) { + return parseCode(code, column); + } + // Read potentially missing code. + return findColumn(fd, column, code); +} + +function getCode(fd, line, column) { + let bytesRead = 0; + if (line === 0) { + // Special handle line number one. This is more efficient and simplifies the + // rest of the algorithm. Read more than the regular column number in bytes + // to prevent multiple reads in case multi byte characters are used. + return findColumn(fd, column, ''); + } let lines = 0; // Prevent blocking the event loop by limiting the maximum amount of // data that may be read. let maxReads = 64; // bytesPerRead * maxReads = 512 kb - let bytesRead = 0; - let startBuffer = 0; // Start reading from that char on const bytesPerRead = 8192; - const buffers = []; - do { - const buffer = Buffer.allocUnsafe(bytesPerRead); + // Use a single buffer up front that is reused until the call site is found. + let buffer = Buffer.allocUnsafe(bytesPerRead); + while (maxReads-- !== 0) { + // Only allocate a new buffer in case the needed line is found. All data + // before that can be discarded. + buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead); bytesRead = readSync(fd, buffer, 0, bytesPerRead); + // Read the buffer until the required code line is found. for (var i = 0; i < bytesRead; i++) { - if (buffer[i] === 10) { - lines++; - if (lines === assertLine) { - startBuffer = i + 1; - // Read up to 15 more lines to make sure all code gets matched - } else if (lines === assertLine + 16) { - buffers.push(buffer.slice(startBuffer, i)); - return buffers; + if (buffer[i] === 10 && ++lines === line) { + // If the end of file is reached, directly parse the code and return. + if (bytesRead < bytesPerRead) { + return parseCode(buffer.toString('utf8', i + 1, bytesRead), column); } + // Check if the read code is sufficient or read more until the whole + // expression is read. Make sure multi byte characters are preserved + // properly by using the decoder. + const code = decoder.write(buffer.slice(i + 1, bytesRead)); + return findColumn(fd, column, code); } } - if (lines >= assertLine) { - buffers.push(buffer.slice(startBuffer, bytesRead)); - // Reset the startBuffer in case we need more than one chunk - startBuffer = 0; + } +} + +function parseCode(code, offset) { + // Lazy load acorn. + if (parseExpressionAt === undefined) { + ({ parseExpressionAt } = require('internal/deps/acorn/dist/acorn')); + ({ findNodeAround } = require('internal/deps/acorn/dist/walk')); + } + let node; + let start = 0; + // Parse the read code until the correct expression is found. + do { + try { + node = parseExpressionAt(code, start); + start = node.end + 1 || start; + // Find the CallExpression in the tree. + node = findNodeAround(node, offset, 'CallExpression'); + } catch (err) { + // Unexpected token error and the like. + start += err.raisedAt || 1; + if (start > offset) { + // No matching expression found. This could happen if the assert + // expression is bigger than the provided buffer. + // eslint-disable-next-line no-throw-literal + throw null; + } } - } while (--maxReads !== 0 && bytesRead !== 0); - return buffers; + } while (node === undefined || node.node.end < offset); + + return [ + node.node.start, + code.slice(node.node.start, node.node.end) + .replace(escapeSequencesRegExp, escapeFn) + ]; } -function getErrMessage(call) { +function getErrMessage(message, fn) { + const tmpLimit = Error.stackTraceLimit; + // Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it + // does to much work. + Error.stackTraceLimit = 1; + // We only need the stack trace. To minimize the overhead use an object + // instead of an error. + const err = {}; + Error.captureStackTrace(err, fn); + Error.stackTraceLimit = tmpLimit; + + const tmpPrepare = Error.prepareStackTrace; + Error.prepareStackTrace = (_, stack) => stack; + const call = err.stack[0]; + Error.prepareStackTrace = tmpPrepare; + const filename = call.getFileName(); + if (!filename) { - return; + return message; } const line = call.getLineNumber() - 1; - const column = call.getColumnNumber() - 1; + let column = call.getColumnNumber() - 1; + + // Line number one reports the wrong column due to being wrapped in a + // function. Remove that offset to get the actual call. + if (line === 0) { + if (columnOffset === 0) { + const { wrapper } = require('internal/modules/cjs/loader'); + columnOffset = wrapper[0].length; + } + column -= columnOffset; + } + const identifier = `${filename}${line}${column}`; if (errorCache.has(identifier)) { @@ -171,57 +264,49 @@ function getErrMessage(call) { return; } - let fd, message; + let fd; try { + // Set the stack trace limit to zero. This makes sure unexpected token + // errors are handled faster. + Error.stackTraceLimit = 0; + + if (decoder === undefined) { + const { StringDecoder } = require('string_decoder'); + decoder = new StringDecoder('utf8'); + } + fd = openSync(filename, 'r', 0o666); - const buffers = getBuffer(fd, line); - const code = Buffer.concat(buffers).toString('utf8'); - // Lazy load acorn. - const { parseExpressionAt } = require('internal/deps/acorn/dist/acorn'); - const nodes = parseExpressionAt(code, column); - // Node type should be "CallExpression" and some times - // "SequenceExpression". - const node = nodes.type === 'CallExpression' ? nodes : nodes.expressions[0]; - const name = node.callee.name; - // Calling `ok` with .apply or .call is uncommon but we use a simple - // safeguard nevertheless. - if (name !== 'apply' && name !== 'call') { - // Only use `assert` and `assert.ok` to reference the "real API" and - // not user defined function names. - const ok = name === 'ok' ? '.ok' : ''; - const args = node.arguments; - message = code - .slice(args[0].start, args[args.length - 1].end) - .replace(escapeSequencesRegExp, escapeFn); + // Reset column and message. + [column, message] = getCode(fd, line, column); + // Flush unfinished multi byte characters. + decoder.end(); + // Always normalize indentation, otherwise the message could look weird. + if (message.indexOf('\n') !== -1) { if (EOL === '\r\n') { message = message.replace(/\r\n/g, '\n'); } - // Always normalize indentation, otherwise the message could look weird. - if (message.indexOf('\n') !== -1) { - const tmp = message.split('\n'); - message = tmp[0]; - for (var i = 1; i < tmp.length; i++) { - let pos = 0; - while (pos < column && - (tmp[i][pos] === ' ' || tmp[i][pos] === '\t')) { - pos++; - } - message += `\n ${tmp[i].slice(pos)}`; + const frames = message.split('\n'); + message = frames.shift(); + for (const frame of frames) { + let pos = 0; + while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) { + pos++; } + message += `\n ${frame.slice(pos)}`; } - message = 'The expression evaluated to a falsy value:' + - `\n\n assert${ok}(${message})\n`; } + message = `The expression evaluated to a falsy value:\n\n ${message}\n`; // Make sure to always set the cache! No matter if the message is // undefined or not errorCache.set(identifier, message); return message; - } catch (e) { // Invalidate cache to prevent trying to read this part again. errorCache.set(identifier, undefined); } finally { + // Reset limit. + Error.stackTraceLimit = tmpLimit; if (fd !== undefined) closeSync(fd); } @@ -235,25 +320,8 @@ function innerOk(fn, argLen, value, message) { generatedMessage = true; message = 'No value argument passed to `assert.ok()`'; } else if (message == null) { - // Use the call as error message if possible. - // This does not work with e.g. the repl. - // eslint-disable-next-line no-restricted-syntax - const err = new Error(); - // Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it - // does to much work. - const tmpLimit = Error.stackTraceLimit; - Error.stackTraceLimit = 1; - Error.captureStackTrace(err, fn); - Error.stackTraceLimit = tmpLimit; - - const tmpPrepare = Error.prepareStackTrace; - Error.prepareStackTrace = (_, stack) => stack; - const call = err.stack[0]; - Error.prepareStackTrace = tmpPrepare; - - // Make sure it would be "null" in case that is used. - message = getErrMessage(call) || message; generatedMessage = true; + message = getErrMessage(message, fn); } else if (message instanceof Error) { throw message; } diff --git a/test/fixtures/assert-first-line.js b/test/fixtures/assert-first-line.js new file mode 100644 index 00000000000000..8a65113559c981 --- /dev/null +++ b/test/fixtures/assert-first-line.js @@ -0,0 +1,2 @@ +'use strict'; const ässört = require('assert'); ässört(true); ässört.ok(''); ässört(null); +// aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa(false); diff --git a/test/fixtures/assert-long-line.js b/test/fixtures/assert-long-line.js new file mode 100644 index 00000000000000..cab3507ac8d779 --- /dev/null +++ b/test/fixtures/assert-long-line.js @@ -0,0 +1 @@ +'use strict'; /* aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa */ const assert = require('assert'); assert(true); assert.ok(''); assert(null); diff --git a/test/parallel/test-assert-first-line.js b/test/parallel/test-assert-first-line.js new file mode 100644 index 00000000000000..32eadbf74156c3 --- /dev/null +++ b/test/parallel/test-assert-first-line.js @@ -0,0 +1,23 @@ +'use strict'; + +// Verify that asserting in the very first line produces the expected result. + +require('../common'); +const assert = require('assert'); +const { path } = require('../common/fixtures'); + +assert.throws( + () => require(path('assert-first-line')), + { + name: 'AssertionError [ERR_ASSERTION]', + message: "The expression evaluated to a falsy value:\n\n ässört.ok('')\n" + } +); + +assert.throws( + () => require(path('assert-long-line')), + { + name: 'AssertionError [ERR_ASSERTION]', + message: "The expression evaluated to a falsy value:\n\n assert.ok('')\n" + } +); diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index 4b4b802701b778..ec7fe58d05c6c4 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -457,7 +457,7 @@ assert.throws( code: 'ERR_ASSERTION', type: assert.AssertionError, message: 'The expression evaluated to a falsy value:\n\n ' + - "assert.ok(typeof 123 === 'string')\n" + "assert.ok(\n typeof 123 === 'string'\n )\n" } ); Error.stackTraceLimit = tmpLimit; @@ -661,7 +661,7 @@ common.expectsError( code: 'ERR_ASSERTION', type: assert.AssertionError, message: 'The expression evaluated to a falsy value:\n\n ' + - "assert(Buffer.from('test') instanceof Error)\n" + "assert(\n (Buffer.from('test') instanceof Error)\n )\n" } ); common.expectsError( @@ -670,7 +670,7 @@ common.expectsError( code: 'ERR_ASSERTION', type: assert.AssertionError, message: 'The expression evaluated to a falsy value:\n\n ' + - "assert(Buffer.from('test') instanceof Error)\n" + "assert(\n (Buffer.from('test') instanceof Error)\n )\n" } ); fs.close = tmp; @@ -690,11 +690,13 @@ common.expectsError( code: 'ERR_ASSERTION', type: assert.AssertionError, message: 'The expression evaluated to a falsy value:\n\n' + - ' assert((() => \'string\')()\n' + + ' a(\n' + + ' (() => \'string\')()\n' + ' // eslint-disable-next-line\n' + ' ===\n' + ' 123 instanceof\n' + - ' Buffer)\n' + ' Buffer\n' + + ' )\n' } ); @@ -712,11 +714,13 @@ common.expectsError( code: 'ERR_ASSERTION', type: assert.AssertionError, message: 'The expression evaluated to a falsy value:\n\n' + - ' assert((() => \'string\')()\n' + + ' a(\n' + + ' (() => \'string\')()\n' + ' // eslint-disable-next-line\n' + ' ===\n' + ' 123 instanceof\n' + - ' Buffer)\n' + ' Buffer\n' + + ' )\n' } ); @@ -731,16 +735,19 @@ Buffer code: 'ERR_ASSERTION', type: assert.AssertionError, message: 'The expression evaluated to a falsy value:\n\n' + - ' assert((\n' + + ' a((\n' + ' () => \'string\')() ===\n' + ' 123 instanceof\n' + - ' Buffer)\n' + ' Buffer\n' + + ' )\n' } ); /* eslint-enable indent */ common.expectsError( - () => assert(null, undefined), + () => { + assert(true); assert(null, undefined); + }, { code: 'ERR_ASSERTION', type: assert.AssertionError, @@ -750,11 +757,38 @@ common.expectsError( ); common.expectsError( - () => assert.ok.apply(null, [0]), + () => { + assert + .ok(null, undefined); + }, { code: 'ERR_ASSERTION', type: assert.AssertionError, - message: '0 == true' + message: 'The expression evaluated to a falsy value:\n\n ' + + 'ok(null, undefined)\n' + } +); + +common.expectsError( + // eslint-disable-next-line dot-notation, quotes + () => assert['ok']["apply"](null, [0]), + { + code: 'ERR_ASSERTION', + type: assert.AssertionError, + message: 'The expression evaluated to a falsy value:\n\n ' + + 'assert[\'ok\']["apply"](null, [0])\n' + } +); + +common.expectsError( + () => { + const wrapper = (fn, value) => fn(value); + wrapper(assert, false); + }, + { + code: 'ERR_ASSERTION', + type: assert.AssertionError, + message: 'The expression evaluated to a falsy value:\n\n fn(value)\n' } ); @@ -763,7 +797,8 @@ common.expectsError( { code: 'ERR_ASSERTION', type: assert.AssertionError, - message: '0 == true', + message: 'The expression evaluated to a falsy value:\n\n ' + + 'assert.ok.call(null, 0)\n', generatedMessage: true } ); @@ -778,7 +813,7 @@ common.expectsError( } ); -// works in eval +// Works in eval. common.expectsError( () => new Function('assert', 'assert(1 === 2);')(assert), { From 53b587a5afa3811e1e8c3b052a04380858dd3f30 Mon Sep 17 00:00:00 2001 From: Andreas Madsen <amwebdk@gmail.com> Date: Mon, 9 Jul 2018 15:31:18 +0200 Subject: [PATCH 101/116] doc: add documentation for buffer.byteOffset Also document a common issue when casting a Buffer object to a TypedArray object. Fixes: https://github.com/nodejs/node/issues/19301 PR-URL: https://github.com/nodejs/node/pull/21718 Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> --- doc/api/buffer.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/doc/api/buffer.md b/doc/api/buffer.md index c219f00e4a1f48..8e1d1c325b6ca7 100644 --- a/doc/api/buffer.md +++ b/doc/api/buffer.md @@ -992,6 +992,31 @@ console.log(buffer.buffer === arrayBuffer); // Prints: true ``` +### buf.byteOffset + +* {integer} The `byteOffset` on the underlying `ArrayBuffer` object based on + which this `Buffer` object is created. + +When setting `byteOffset` in `Buffer.from(ArrayBuffer, byteOffset, length)` +or sometimes when allocating a buffer smaller than `Buffer.poolSize` the +buffer doesn't start from a zero offset on the underlying `ArrayBuffer`. + +This can cause problems when accessing the underlying `ArrayBuffer` directly +using `buf.buffer`, as the first bytes in this `ArrayBuffer` may be unrelated +to the `buf` object itself. + +A common issue is when casting a `Buffer` object to a `TypedArray` object, +in this case one needs to specify the `byteOffset` correctly: + +```js +// Create a buffer smaller than `Buffer.poolSize`. +const nodeBuffer = new Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + +// When casting the Node.js Buffer to an Int8 TypedArray remember to use the +// byteOffset. +new Int8Array(nodeBuffer.buffer, nodeBuffer.byteOffset, nodeBuffer.length); +``` + ### buf.compare(target[, targetStart[, targetEnd[, sourceStart[, sourceEnd]]]]) <!-- YAML added: v0.11.13 From 6bb2b5a51d5686be252ead277eaa76fa16ce0fb5 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Fri, 13 Jul 2018 20:05:24 +0200 Subject: [PATCH 102/116] build: account for pure C sources in `build-addons-napi` PR-URL: https://github.com/nodejs/node/pull/21797 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Jon Moss <me@jonathanmoss.me> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 03285c9d42cee3..168dd27ea03d6d 100644 --- a/Makefile +++ b/Makefile @@ -370,6 +370,7 @@ ADDONS_NAPI_BINDING_GYPS := \ $(wildcard test/addons-napi/*/binding.gyp)) ADDONS_NAPI_BINDING_SOURCES := \ + $(filter-out test/addons-napi/??_*/*.c, $(wildcard test/addons-napi/*/*.c)) \ $(filter-out test/addons-napi/??_*/*.cc, $(wildcard test/addons-napi/*/*.cc)) \ $(filter-out test/addons-napi/??_*/*.h, $(wildcard test/addons-napi/*/*.h)) From 0b3c80ca31db5bbf79e0439304653e14e6302e30 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Wed, 27 Jun 2018 12:55:33 +0200 Subject: [PATCH 103/116] http2: fix issues with aborted `respondWithFile()`s PR-URL: https://github.com/nodejs/node/pull/21561 Fixes: https://github.com/nodejs/node/issues/20824 Fixes: https://github.com/nodejs/node/issues/21560 Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> --- src/node_http2.cc | 3 +- src/stream_pipe.cc | 11 ++++-- src/stream_pipe.h | 10 +++-- ...ttp2-respond-with-file-connection-abort.js | 37 +++++++++++++++++++ 4 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 test/parallel/test-http2-respond-with-file-connection-abort.js diff --git a/src/node_http2.cc b/src/node_http2.cc index dfe0e4e7ae3e9a..d1319c9d82fd97 100644 --- a/src/node_http2.cc +++ b/src/node_http2.cc @@ -2060,10 +2060,9 @@ int Http2Stream::DoWrite(WriteWrap* req_wrap, uv_buf_t* bufs, size_t nbufs, uv_stream_t* send_handle) { - CHECK(!this->IsDestroyed()); CHECK_NULL(send_handle); Http2Scope h2scope(this); - if (!IsWritable()) { + if (!IsWritable() || IsDestroyed()) { req_wrap->Done(UV_EOF); return 0; } diff --git a/src/stream_pipe.cc b/src/stream_pipe.cc index bfe7d4297257a0..e19f98e35d2821 100644 --- a/src/stream_pipe.cc +++ b/src/stream_pipe.cc @@ -57,9 +57,11 @@ void StreamPipe::Unpipe() { if (is_closed_) return; - // Note that we cannot use virtual methods on `source` and `sink` here, - // because this function can be called from their destructors via + // Note that we possibly cannot use virtual methods on `source` and `sink` + // here, because this function can be called from their destructors via // `OnStreamDestroy()`. + if (!source_destroyed_) + source()->ReadStop(); is_closed_ = true; is_reading_ = false; @@ -144,7 +146,8 @@ void StreamPipe::ProcessData(size_t nread, const uv_buf_t& buf) { is_writing_ = true; is_reading_ = false; res.wrap->SetAllocatedStorage(buf.base, buf.len); - source()->ReadStop(); + if (source() != nullptr) + source()->ReadStop(); } } @@ -183,6 +186,7 @@ void StreamPipe::WritableListener::OnStreamAfterShutdown(ShutdownWrap* w, void StreamPipe::ReadableListener::OnStreamDestroy() { StreamPipe* pipe = ContainerOf(&StreamPipe::readable_listener_, this); + pipe->source_destroyed_ = true; if (!pipe->is_eof_) { OnStreamRead(UV_EPIPE, uv_buf_init(nullptr, 0)); } @@ -190,6 +194,7 @@ void StreamPipe::ReadableListener::OnStreamDestroy() { void StreamPipe::WritableListener::OnStreamDestroy() { StreamPipe* pipe = ContainerOf(&StreamPipe::writable_listener_, this); + pipe->sink_destroyed_ = true; pipe->is_eof_ = true; pipe->Unpipe(); } diff --git a/src/stream_pipe.h b/src/stream_pipe.h index b72a60941b610a..36a0b1dc08106b 100644 --- a/src/stream_pipe.h +++ b/src/stream_pipe.h @@ -23,16 +23,18 @@ class StreamPipe : public AsyncWrap { } private: - StreamBase* source(); - StreamBase* sink(); + inline StreamBase* source(); + inline StreamBase* sink(); - void ShutdownWritable(); - void FlushToWritable(); + inline void ShutdownWritable(); + inline void FlushToWritable(); bool is_reading_ = false; bool is_writing_ = false; bool is_eof_ = false; bool is_closed_ = true; + bool sink_destroyed_ = false; + bool source_destroyed_ = false; // Set a default value so that when we’re coming from Start(), we know // that we don’t want to read just yet. diff --git a/test/parallel/test-http2-respond-with-file-connection-abort.js b/test/parallel/test-http2-respond-with-file-connection-abort.js new file mode 100644 index 00000000000000..25926b2c9805a3 --- /dev/null +++ b/test/parallel/test-http2-respond-with-file-connection-abort.js @@ -0,0 +1,37 @@ +'use strict'; + +const common = require('../common'); +if (!common.hasCrypto) + common.skip('missing crypto'); +const http2 = require('http2'); +const net = require('net'); + +const { + HTTP2_HEADER_CONTENT_TYPE +} = http2.constants; + +const server = http2.createServer(); +server.on('stream', common.mustCall((stream) => { + stream.respondWithFile(process.execPath, { + [HTTP2_HEADER_CONTENT_TYPE]: 'application/octet-stream' + }); +})); + +server.listen(0, common.mustCall(() => { + const client = http2.connect(`http://localhost:${server.address().port}`); + const req = client.request(); + + req.on('response', common.mustCall(() => {})); + req.on('data', common.mustCall(() => { + net.Socket.prototype.destroy.call(client.socket); + server.close(); + })); + req.end(); +})); + +// TODO(addaleax): This is a *hack*. HTTP/2 needs to have a proper way of +// dealing with this kind of issue. +process.once('uncaughtException', (err) => { + if (err.code === 'ECONNRESET') return; + throw err; +}); From b338ff54bbad0f34400ece8e5587cec59c0df0cf Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Fri, 13 Jul 2018 18:32:17 +0200 Subject: [PATCH 104/116] test: add gc tracking to common API PR-URL: https://github.com/nodejs/node/pull/21794 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> --- test/common/README.md | 15 +++++++++++++++ test/common/index.js | 27 +++++++++++++++++++++++++++ test/parallel/test-common-gc.js | 15 +++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 test/parallel/test-common-gc.js diff --git a/test/common/README.md b/test/common/README.md index c0051ad9f7ca6e..41cc88aca10f59 100644 --- a/test/common/README.md +++ b/test/common/README.md @@ -322,6 +322,21 @@ otherwise. ### noWarnCode See `common.expectWarning()` for usage. +### onGC(target, listener) +* `target` [<Object>] +* `listener` [<Object>] + * `ongc` [<Function>] + +Installs a GC listener for the collection of `target`. + +This uses `async_hooks` for GC tracking. This means that it enables +`async_hooks` tracking, which may affect the test functionality. It also +means that between a `global.gc()` call and the listener being invoked +a full `setImmediate()` invocation passes. + +`listener` is an object to make it easier to use a closure; the target object +should not be in scope when `listener.ongc()` is created. + ### opensslCli * [<boolean>] diff --git a/test/common/index.js b/test/common/index.js index bf6b1077d1859b..cca289337ef4a2 100644 --- a/test/common/index.js +++ b/test/common/index.js @@ -883,3 +883,30 @@ exports.isCPPSymbolsNotMapped = exports.isWindows || exports.isAIX || exports.isLinuxPPCBE || exports.isFreeBSD; + +const gcTrackerMap = new WeakMap(); +const gcTrackerTag = 'NODE_TEST_COMMON_GC_TRACKER'; + +exports.onGC = function(obj, gcListener) { + const async_hooks = require('async_hooks'); + + const onGcAsyncHook = async_hooks.createHook({ + init: exports.mustCallAtLeast(function(id, type, trigger, resource) { + if (this.trackedId === undefined) { + assert.strictEqual(type, gcTrackerTag); + this.trackedId = id; + } + }), + destroy(id) { + assert.notStrictEqual(this.trackedId, -1); + if (id === this.trackedId) { + this.gcListener.ongc(); + onGcAsyncHook.disable(); + } + } + }).enable(); + onGcAsyncHook.gcListener = gcListener; + + gcTrackerMap.set(obj, new async_hooks.AsyncResource(gcTrackerTag)); + obj = null; +}; diff --git a/test/parallel/test-common-gc.js b/test/parallel/test-common-gc.js new file mode 100644 index 00000000000000..210b1d6d5f49ea --- /dev/null +++ b/test/parallel/test-common-gc.js @@ -0,0 +1,15 @@ +'use strict'; +// Flags: --expose-gc +const common = require('../common'); + +{ + const gcListener = { ongc: common.mustCall() }; + common.onGC({}, gcListener); + global.gc(); +} + +{ + const gcListener = { ongc: common.mustNotCall() }; + common.onGC(process, gcListener); + global.gc(); +} From 174a9db51a96a9cb7ce4216eddd9588147f890e9 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Fri, 13 Jul 2018 18:32:35 +0200 Subject: [PATCH 105/116] test: refactor test-net-connect-memleak, move to parallel This enables the test to run as part of the regular test suite. PR-URL: https://github.com/nodejs/node/pull/21794 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> --- .../test-net-connect-memleak.js | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) rename test/{pummel => parallel}/test-net-connect-memleak.js (64%) diff --git a/test/pummel/test-net-connect-memleak.js b/test/parallel/test-net-connect-memleak.js similarity index 64% rename from test/pummel/test-net-connect-memleak.js rename to test/parallel/test-net-connect-memleak.js index 7546a6caeb62ea..afcc61f173509f 100644 --- a/test/pummel/test-net-connect-memleak.js +++ b/test/parallel/test-net-connect-memleak.js @@ -26,32 +26,32 @@ const common = require('../common'); const assert = require('assert'); const net = require('net'); -console.log('Run this test with --expose-gc'); -assert.strictEqual( - typeof global.gc, - 'function' -); -net.createServer(function() {}).listen(common.PORT); - -let before = 0; +// Test that the implicit listener for an 'connect' event on net.Sockets is +// added using `once()`, i.e. can be gc'ed once that event has occurred. + +const server = net.createServer(common.mustCall()).listen(0); + +let collected = false; +const gcListener = { ongc() { collected = true; } }; + { - // 2**26 == 64M entries - global.gc(); - const junk = new Array(2 ** 26).fill(0); - before = process.memoryUsage().rss; + const gcObject = {}; + common.onGC(gcObject, gcListener); - net.createConnection(common.PORT, '127.0.0.1', function() { - assert.notStrictEqual(junk.length, 0); // keep reference alive - setTimeout(done, 10); - global.gc(); - }); + const sock = net.createConnection( + server.address().port, + common.mustCall(() => { + assert.strictEqual(gcObject, gcObject); // keep reference alive + assert.strictEqual(collected, false); + setImmediate(done, sock); + })); } -function done() { +function done(sock) { global.gc(); - const after = process.memoryUsage().rss; - const reclaimed = (before - after) / 1024; - console.log('%d kB reclaimed', reclaimed); - assert(reclaimed > 128 * 1024); // It's around 256 MB on x64. - process.exit(); + setImmediate(() => { + assert.strictEqual(collected, true); + sock.end(); + server.close(); + }); } From 6b72583bf8b5d0cfe96b619bccd94e537b0f9639 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Fri, 13 Jul 2018 18:32:35 +0200 Subject: [PATCH 106/116] test: refactor test-tls-connect-memleak, move to parallel This enables the test to run as part of the regular test suite. PR-URL: https://github.com/nodejs/node/pull/21794 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> --- .../test-tls-connect-memleak.js | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) rename test/{pummel => parallel}/test-tls-connect-memleak.js (65%) diff --git a/test/pummel/test-tls-connect-memleak.js b/test/parallel/test-tls-connect-memleak.js similarity index 65% rename from test/pummel/test-tls-connect-memleak.js rename to test/parallel/test-tls-connect-memleak.js index 6809b23baf3926..95f71acdc3b57b 100644 --- a/test/pummel/test-tls-connect-memleak.js +++ b/test/parallel/test-tls-connect-memleak.js @@ -30,36 +30,36 @@ const assert = require('assert'); const tls = require('tls'); const fixtures = require('../common/fixtures'); -assert.strictEqual( - typeof global.gc, - 'function', - `Type of global.gc is not a function. Type: ${typeof global.gc}.` + - ' Run this test with --expose-gc' -); +// Test that the implicit listener for an 'connect' event on tls.Sockets is +// added using `once()`, i.e. can be gc'ed once that event has occurred. -tls.createServer({ +const server = tls.createServer({ cert: fixtures.readSync('test_cert.pem'), key: fixtures.readSync('test_key.pem') -}).listen(common.PORT); +}).listen(0); + +let collected = false; +const gcListener = { ongc() { collected = true; } }; { - // 2**26 == 64M entries - const junk = new Array(2 ** 26).fill(0); + const gcObject = {}; + common.onGC(gcObject, gcListener); - const options = { rejectUnauthorized: false }; - tls.connect(common.PORT, '127.0.0.1', options, function() { - assert.notStrictEqual(junk.length, 0); // keep reference alive - setTimeout(done, 10); - global.gc(); - }); + const sock = tls.connect( + server.address().port, + { rejectUnauthorized: false }, + common.mustCall(() => { + assert.strictEqual(gcObject, gcObject); // keep reference alive + assert.strictEqual(collected, false); + setImmediate(done, sock); + })); } -function done() { - const before = process.memoryUsage().rss; +function done(sock) { global.gc(); - const after = process.memoryUsage().rss; - const reclaimed = (before - after) / 1024; - console.log('%d kB reclaimed', reclaimed); - assert(reclaimed > 256 * 1024); // it's more like 512M on x64 - process.exit(); + setImmediate(() => { + assert.strictEqual(collected, true); + sock.end(); + server.close(); + }); } From 67908e993302807bb1c7986a090648b56ac2d7ae Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Fri, 13 Jul 2018 20:03:17 +0200 Subject: [PATCH 107/116] test: fix build warnings in bigint N-API test PR-URL: https://github.com/nodejs/node/pull/21796 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Jon Moss <me@jonathanmoss.me> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Kyle Farnung <kfarnung@microsoft.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> --- test/addons-napi/test_bigint/test_bigint.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/addons-napi/test_bigint/test_bigint.c b/test/addons-napi/test_bigint/test_bigint.c index e3516628e88b35..b1e6c359db6add 100644 --- a/test/addons-napi/test_bigint/test_bigint.c +++ b/test/addons-napi/test_bigint/test_bigint.c @@ -8,7 +8,7 @@ static napi_value IsLossless(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value args[2]; - NAPI_CALL(env, napi_get_cb_info(env, info, &argc, &args, NULL, NULL)); + NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, NULL, NULL)); bool is_signed; NAPI_CALL(env, napi_get_value_bool(env, args[1], &is_signed)); @@ -98,7 +98,7 @@ static napi_value TestWords(napi_env env, napi_callback_info info) { uint64_t words[10]; NAPI_CALL(env, napi_get_value_bigint_words( - env, args[0], &sign_bit, &word_count, &words)); + env, args[0], &sign_bit, &word_count, words)); NAPI_ASSERT(env, word_count == expected_word_count, "word counts do not match"); From 4ed4bf3bdd2e8ce11b89aa07c0358ada58ba653d Mon Sep 17 00:00:00 2001 From: Rich Trott <rtrott@gmail.com> Date: Wed, 11 Jul 2018 10:57:46 -0700 Subject: [PATCH 108/116] lib: update punycode to 2.1.1 PR-URL: https://github.com/nodejs/node/pull/21768 Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de> Reviewed-By: Yuta Hiroto <hello@hiroppy.me> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> --- lib/punycode.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/punycode.js b/lib/punycode.js index 34da3ca5ad13b6..ea61fd0d39a39d 100644 --- a/lib/punycode.js +++ b/lib/punycode.js @@ -15,7 +15,7 @@ const delimiter = '-'; // '\x2D' /** Regular expressions */ const regexPunycode = /^xn--/; -const regexNonASCII = /[^\x20-\x7E]/; // unprintable ASCII chars + non-ASCII chars +const regexNonASCII = /[^\0-\x7E]/; // non-ASCII chars const regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators /** Error messages */ @@ -210,7 +210,7 @@ const decode = function(input) { basic = 0; } - for (var j = 0; j < basic; ++j) { + for (let j = 0; j < basic; ++j) { // if it's not a basic code point if (input.charCodeAt(j) >= 0x80) { error('not-basic'); @@ -221,7 +221,7 @@ const decode = function(input) { // Main decoding loop: start just after the last delimiter if any basic code // points were copied; start at the beginning otherwise. - for (var index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { + for (let index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) { // `index` is the index of the next character to be consumed. // Decode a generalized variable-length integer into `delta`, @@ -229,7 +229,7 @@ const decode = function(input) { // if we increase `i` as we go, then subtract off its starting // value at the end to obtain `delta`. let oldi = i; - for (var w = 1, k = base; /* no condition */; k += base) { + for (let w = 1, k = base; /* no condition */; k += base) { if (index >= inputLength) { error('invalid-input'); @@ -345,7 +345,7 @@ const encode = function(input) { if (currentValue == n) { // Represent delta as a generalized variable-length integer. let q = delta; - for (var k = base; /* no condition */; k += base) { + for (let k = base; /* no condition */; k += base) { const t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); if (q < t) { break; @@ -419,7 +419,7 @@ const punycode = { * @memberOf punycode * @type String */ - 'version': '2.0.0', + 'version': '2.1.0', /** * An object of methods to convert from JavaScript's internal character * representation (UCS-2) to Unicode code points, and back. From eef975ebae7a166b41f8dd2e65aec9aa0b5a5fc4 Mon Sep 17 00:00:00 2001 From: Anna Henningsen <anna@addaleax.net> Date: Fri, 13 Jul 2018 20:49:16 +0200 Subject: [PATCH 109/116] test: move inspector test back to parallel, unmark flaky MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After https://github.com/nodejs/node/pull/21182, the test is no longer flaky. Since it doesn’t use a static port, moving it to parallel seems justified. PR-URL: https://github.com/nodejs/node/pull/21806 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Jon Moss <me@jonathanmoss.me> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com> --- .../{sequential => parallel}/test-inspector-port-zero-cluster.js | 0 test/sequential/sequential.status | 1 - 2 files changed, 1 deletion(-) rename test/{sequential => parallel}/test-inspector-port-zero-cluster.js (100%) diff --git a/test/sequential/test-inspector-port-zero-cluster.js b/test/parallel/test-inspector-port-zero-cluster.js similarity index 100% rename from test/sequential/test-inspector-port-zero-cluster.js rename to test/parallel/test-inspector-port-zero-cluster.js diff --git a/test/sequential/sequential.status b/test/sequential/sequential.status index 59cc2bc7f77881..c5873b9060f8fe 100644 --- a/test/sequential/sequential.status +++ b/test/sequential/sequential.status @@ -16,7 +16,6 @@ test-inspector-bindings : PASS, FLAKY test-inspector-debug-end : PASS, FLAKY [$system==linux] -test-inspector-port-zero-cluster : PASS, FLAKY [$system==macos] From 581390c59d2a003bc7fcc7d3727dce2dafde21fd Mon Sep 17 00:00:00 2001 From: Joyee Cheung <joyeec9h3@gmail.com> Date: Mon, 18 Jun 2018 00:40:58 +0800 Subject: [PATCH 110/116] process: split bootstrappers by threads that can run them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch split part of the bootstrappers into three files: - `lib/internal/process/main_thread_only.js`: contains bootstrappers that can only be run in the main thread, including - `setupStdio` for the main thread that sets up `process.stdin`, `process.stdout`, `process.error` that may interact with external resources, e.g. TTY/File/Pipe/TCP sockets - `setupProcessMethods` that setup methods changing process-global states, e.g. `process.chdir`, `process.umask`, `process.setuid` - `setupSignalHandlers` - `setupChildProcessIpcChannel` that setup `process.send` for child processes. - `lib/internal/process/worker_thread_only.js`: contains bootstrappers that can only be run in the worker threads, including - `setupStdio` for the worker thread that are streams to be manipulated or piped to the parent thread - `lib/internal/process/per_thread.js`: contains bootstrappers that can be run in all threads, including: - `setupAssert` for `process.assert` - `setupCpuUsage` for `process.cpuUsage` - `setupHrtime` for `process.hrtime` and `process.hrtime.bigint` - `setupMemoryUsage` for `process.memoryUsage` - `setupConfig` for `process.config` - `setupKillAndExit` for `process.kill` and `process.exit` - `setupRawDebug` for `process._rawDebug` - `setupUncaughtExceptionCapture` for `process.setUncaughtExceptionCaptureCallback` and `process.hasUncaughtExceptionCaptureCallback` Hopefully in the future we can sort more bootstrappers in `boostrap/node.js` into these three files and further group them into functions that can be run before creating the snapshot / after loading the snapshot. This patch also moves most of the `isMainThread` conditionals into the main bootstrapper instead of letting them scattered around special-casing different implementations. PR-URL: https://github.com/nodejs/node/pull/21378 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Jeremiah Senkpiel <fishrock123@rocketmail.com> Backport-PR-URL: https://github.com/nodejs/node/pull/21866 Reviewed-By: Michaël Zasso <targos@protonmail.com> --- lib/internal/bootstrap/node.js | 67 ++++++--- lib/internal/process/main_thread_only.js | 127 ++++++++++++++++++ lib/internal/process/methods.js | 56 -------- .../{process.js => process/per_thread.js} | 92 +++---------- lib/internal/process/stdio.js | 21 +-- lib/internal/process/worker_thread_only.js | 24 ++++ node.gyp | 5 +- 7 files changed, 227 insertions(+), 165 deletions(-) create mode 100644 lib/internal/process/main_thread_only.js delete mode 100644 lib/internal/process/methods.js rename lib/internal/{process.js => process/per_thread.js} (75%) create mode 100644 lib/internal/process/worker_thread_only.js diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 61146a32aad010..ab1695125df6bc 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -44,33 +44,57 @@ setupGlobalVariables(); - const _process = NativeModule.require('internal/process'); - _process.setupConfig(NativeModule._source); - _process.setupSignalHandlers(); - _process.setupUncaughtExceptionCapture(exceptionHandlerState, - _shouldAbortOnUncaughtToggle); + // Bootstrappers for all threads, including worker threads and main thread + const perThreadSetup = NativeModule.require('internal/process/per_thread'); + // Bootstrappers for the main thread only + let mainThreadSetup; + // Bootstrappers for the worker threads only + let workerThreadSetup; + if (isMainThread) { + mainThreadSetup = NativeModule.require( + 'internal/process/main_thread_only' + ); + } else { + workerThreadSetup = NativeModule.require( + 'internal/process/worker_thread_only' + ); + } + + perThreadSetup.setupAssert(); + perThreadSetup.setupConfig(NativeModule._source); + + if (isMainThread) { + mainThreadSetup.setupSignalHandlers(); + } + + perThreadSetup.setupUncaughtExceptionCapture(exceptionHandlerState, + _shouldAbortOnUncaughtToggle); + NativeModule.require('internal/process/warning').setup(); NativeModule.require('internal/process/next_tick').setup(_setupNextTick, _setupPromises); - NativeModule.require('internal/process/stdio').setup(); - NativeModule.require('internal/process/methods').setup(_chdir, - _umask, - _initgroups, - _setegid, - _seteuid, - _setgid, - _setuid, - _setgroups); + + if (isMainThread) { + mainThreadSetup.setupStdio(); + mainThreadSetup.setupProcessMethods( + _chdir, _umask, _initgroups, _setegid, _seteuid, + _setgid, _setuid, _setgroups + ); + } else { + workerThreadSetup.setupStdio(); + } const perf = process.binding('performance'); const { NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE, } = perf.constants; - _process.setup_hrtime(_hrtime, _hrtimeBigInt); - _process.setup_cpuUsage(_cpuUsage); - _process.setupMemoryUsage(_memoryUsage); - _process.setupKillAndExit(); + perThreadSetup.setupRawDebug(_rawDebug); + perThreadSetup.setupHrtime(_hrtime, _hrtimeBigInt); + perThreadSetup.setupCpuUsage(_cpuUsage); + perThreadSetup.setupMemoryUsage(_memoryUsage); + perThreadSetup.setupKillAndExit(); + if (global.__coverage__) NativeModule.require('internal/process/write-coverage').setup(); @@ -90,10 +114,9 @@ NativeModule.require('internal/inspector_async_hook').setup(); } - if (isMainThread) - _process.setupChannel(); - - _process.setupRawDebug(_rawDebug); + if (isMainThread) { + mainThreadSetup.setupChildProcessIpcChannel(); + } const browserGlobals = !process._noBrowserGlobals; if (browserGlobals) { diff --git a/lib/internal/process/main_thread_only.js b/lib/internal/process/main_thread_only.js new file mode 100644 index 00000000000000..814cca266d6675 --- /dev/null +++ b/lib/internal/process/main_thread_only.js @@ -0,0 +1,127 @@ +'use strict'; + +// This file contains process bootstrappers that can only be +// run in the main thread + +const { + errnoException +} = require('internal/errors'); + +const { + setupProcessStdio, + getMainThreadStdio +} = require('internal/process/stdio'); + +const assert = require('assert').strict; + +function setupStdio() { + setupProcessStdio(getMainThreadStdio()); +} + +// Non-POSIX platforms like Windows don't have certain methods. +// Workers also lack these methods since they change process-global state. +function setupProcessMethods(_chdir, _umask, _initgroups, _setegid, + _seteuid, _setgid, _setuid, _setgroups) { + if (_setgid !== undefined) { + setupPosixMethods(_initgroups, _setegid, _seteuid, + _setgid, _setuid, _setgroups); + } + + process.chdir = function chdir(...args) { + return _chdir(...args); + }; + + process.umask = function umask(...args) { + return _umask(...args); + }; +} + +function setupPosixMethods(_initgroups, _setegid, _seteuid, + _setgid, _setuid, _setgroups) { + + process.initgroups = function initgroups(...args) { + return _initgroups(...args); + }; + + process.setegid = function setegid(...args) { + return _setegid(...args); + }; + + process.seteuid = function seteuid(...args) { + return _seteuid(...args); + }; + + process.setgid = function setgid(...args) { + return _setgid(...args); + }; + + process.setuid = function setuid(...args) { + return _setuid(...args); + }; + + process.setgroups = function setgroups(...args) { + return _setgroups(...args); + }; +} + +// Worker threads don't receive signals. +function setupSignalHandlers() { + const constants = process.binding('constants').os.signals; + const signalWraps = Object.create(null); + let Signal; + + function isSignal(event) { + return typeof event === 'string' && constants[event] !== undefined; + } + + // Detect presence of a listener for the special signal types + process.on('newListener', function(type) { + if (isSignal(type) && signalWraps[type] === undefined) { + if (Signal === undefined) + Signal = process.binding('signal_wrap').Signal; + const wrap = new Signal(); + + wrap.unref(); + + wrap.onsignal = process.emit.bind(process, type, type); + + const signum = constants[type]; + const err = wrap.start(signum); + if (err) { + wrap.close(); + throw errnoException(err, 'uv_signal_start'); + } + + signalWraps[type] = wrap; + } + }); + + process.on('removeListener', function(type) { + if (signalWraps[type] !== undefined && this.listenerCount(type) === 0) { + signalWraps[type].close(); + delete signalWraps[type]; + } + }); +} + +function setupChildProcessIpcChannel() { + // If we were spawned with env NODE_CHANNEL_FD then load that up and + // start parsing data from that stream. + if (process.env.NODE_CHANNEL_FD) { + const fd = parseInt(process.env.NODE_CHANNEL_FD, 10); + assert(fd >= 0); + + // Make sure it's not accidentally inherited by child processes. + delete process.env.NODE_CHANNEL_FD; + + require('child_process')._forkChild(fd); + assert(process.send); + } +} + +module.exports = { + setupStdio, + setupProcessMethods, + setupSignalHandlers, + setupChildProcessIpcChannel +}; diff --git a/lib/internal/process/methods.js b/lib/internal/process/methods.js deleted file mode 100644 index 38ba5f84b5a30a..00000000000000 --- a/lib/internal/process/methods.js +++ /dev/null @@ -1,56 +0,0 @@ -'use strict'; - -const { - isMainThread -} = require('internal/worker'); - -function setupProcessMethods(_chdir, _umask, _initgroups, _setegid, - _seteuid, _setgid, _setuid, _setgroups) { - // Non-POSIX platforms like Windows don't have certain methods. - // Workers also lack these methods since they change process-global state. - if (!isMainThread) - return; - - if (_setgid !== undefined) { - setupPosixMethods(_initgroups, _setegid, _seteuid, - _setgid, _setuid, _setgroups); - } - - process.chdir = function chdir(...args) { - return _chdir(...args); - }; - - process.umask = function umask(...args) { - return _umask(...args); - }; -} - -function setupPosixMethods(_initgroups, _setegid, _seteuid, - _setgid, _setuid, _setgroups) { - - process.initgroups = function initgroups(...args) { - return _initgroups(...args); - }; - - process.setegid = function setegid(...args) { - return _setegid(...args); - }; - - process.seteuid = function seteuid(...args) { - return _seteuid(...args); - }; - - process.setgid = function setgid(...args) { - return _setgid(...args); - }; - - process.setuid = function setuid(...args) { - return _setuid(...args); - }; - - process.setgroups = function setgroups(...args) { - return _setgroups(...args); - }; -} - -exports.setup = setupProcessMethods; diff --git a/lib/internal/process.js b/lib/internal/process/per_thread.js similarity index 75% rename from lib/internal/process.js rename to lib/internal/process/per_thread.js index 098edaeaaff183..fbf60bac6e9fff 100644 --- a/lib/internal/process.js +++ b/lib/internal/process/per_thread.js @@ -1,5 +1,9 @@ 'use strict'; +// This files contains process bootstrappers that can be +// run when setting up each thread, including the main +// thread and the worker threads. + const { errnoException, codes: { @@ -14,19 +18,19 @@ const { } = require('internal/errors'); const util = require('util'); const constants = process.binding('constants').os.signals; -const assert = require('assert').strict; const { deprecate } = require('internal/util'); -const { isMainThread } = require('internal/worker'); -process.assert = deprecate( - function(x, msg) { - if (!x) throw new ERR_ASSERTION(msg || 'assertion error'); - }, - 'process.assert() is deprecated. Please use the `assert` module instead.', - 'DEP0100'); +function setupAssert() { + process.assert = deprecate( + function(x, msg) { + if (!x) throw new ERR_ASSERTION(msg || 'assertion error'); + }, + 'process.assert() is deprecated. Please use the `assert` module instead.', + 'DEP0100'); +} // Set up the process.cpuUsage() function. -function setup_cpuUsage(_cpuUsage) { +function setupCpuUsage(_cpuUsage) { // Create the argument array that will be passed to the native function. const cpuValues = new Float64Array(2); @@ -90,7 +94,7 @@ function setup_cpuUsage(_cpuUsage) { // The 3 entries filled in by the original process.hrtime contains // the upper/lower 32 bits of the second part of the value, // and the remaining nanoseconds of the value. -function setup_hrtime(_hrtime, _hrtimeBigInt) { +function setupHrtime(_hrtime, _hrtimeBigInt) { const hrValues = new Uint32Array(3); process.hrtime = function hrtime(time) { @@ -193,67 +197,6 @@ function setupKillAndExit() { }; } - -function setupSignalHandlers() { - if (!isMainThread) { - // Worker threads don't receive signals. - return; - } - - const signalWraps = Object.create(null); - let Signal; - - function isSignal(event) { - return typeof event === 'string' && constants[event] !== undefined; - } - - // Detect presence of a listener for the special signal types - process.on('newListener', function(type) { - if (isSignal(type) && signalWraps[type] === undefined) { - if (Signal === undefined) - Signal = process.binding('signal_wrap').Signal; - const wrap = new Signal(); - - wrap.unref(); - - wrap.onsignal = process.emit.bind(process, type, type); - - const signum = constants[type]; - const err = wrap.start(signum); - if (err) { - wrap.close(); - throw errnoException(err, 'uv_signal_start'); - } - - signalWraps[type] = wrap; - } - }); - - process.on('removeListener', function(type) { - if (signalWraps[type] !== undefined && this.listenerCount(type) === 0) { - signalWraps[type].close(); - delete signalWraps[type]; - } - }); -} - - -function setupChannel() { - // If we were spawned with env NODE_CHANNEL_FD then load that up and - // start parsing data from that stream. - if (process.env.NODE_CHANNEL_FD) { - const fd = parseInt(process.env.NODE_CHANNEL_FD, 10); - assert(fd >= 0); - - // Make sure it's not accidentally inherited by child processes. - delete process.env.NODE_CHANNEL_FD; - - require('child_process')._forkChild(fd); - assert(process.send); - } -} - - function setupRawDebug(_rawDebug) { process._rawDebug = function() { _rawDebug(util.format.apply(null, arguments)); @@ -288,13 +231,12 @@ function setupUncaughtExceptionCapture(exceptionHandlerState, } module.exports = { - setup_cpuUsage, - setup_hrtime, + setupAssert, + setupCpuUsage, + setupHrtime, setupMemoryUsage, setupConfig, setupKillAndExit, - setupSignalHandlers, - setupChannel, setupRawDebug, setupUncaughtExceptionCapture }; diff --git a/lib/internal/process/stdio.js b/lib/internal/process/stdio.js index f4a07cd06d09d9..e4207a1aa768bd 100644 --- a/lib/internal/process/stdio.js +++ b/lib/internal/process/stdio.js @@ -6,21 +6,17 @@ const { ERR_UNKNOWN_STDIN_TYPE, ERR_UNKNOWN_STREAM_TYPE } = require('internal/errors').codes; -const { - isMainThread, - workerStdio -} = require('internal/worker'); -exports.setup = setupStdio; +exports.setupProcessStdio = setupProcessStdio; +exports.getMainThreadStdio = getMainThreadStdio; -function setupStdio() { +function getMainThreadStdio() { var stdin; var stdout; var stderr; function getStdout() { if (stdout) return stdout; - if (!isMainThread) return workerStdio.stdout; stdout = createWritableStdioStream(1); stdout.destroySoon = stdout.destroy; stdout._destroy = function(er, cb) { @@ -36,7 +32,6 @@ function setupStdio() { function getStderr() { if (stderr) return stderr; - if (!isMainThread) return workerStdio.stderr; stderr = createWritableStdioStream(2); stderr.destroySoon = stderr.destroy; stderr._destroy = function(er, cb) { @@ -52,8 +47,6 @@ function setupStdio() { function getStdin() { if (stdin) return stdin; - if (!isMainThread) return workerStdio.stdin; - const tty_wrap = process.binding('tty_wrap'); const fd = 0; @@ -136,6 +129,14 @@ function setupStdio() { return stdin; } + return { + getStdout, + getStderr, + getStdin + }; +} + +function setupProcessStdio({ getStdout, getStdin, getStderr }) { Object.defineProperty(process, 'stdout', { configurable: true, enumerable: true, diff --git a/lib/internal/process/worker_thread_only.js b/lib/internal/process/worker_thread_only.js new file mode 100644 index 00000000000000..834ba6078fca44 --- /dev/null +++ b/lib/internal/process/worker_thread_only.js @@ -0,0 +1,24 @@ +'use strict'; + +// This file contains process bootstrappers that can only be +// run in the worker thread. + +const { + setupProcessStdio +} = require('internal/process/stdio'); + +const { + workerStdio +} = require('internal/worker'); + +function setupStdio() { + setupProcessStdio({ + getStdout: () => workerStdio.stdout, + getStderr: () => workerStdio.stderr, + getStdin: () => workerStdio.stdin + }); +} + +module.exports = { + setupStdio +}; diff --git a/node.gyp b/node.gyp index 04784715c34f86..56315baf7006b3 100644 --- a/node.gyp +++ b/node.gyp @@ -133,12 +133,13 @@ 'lib/internal/net.js', 'lib/internal/os.js', 'lib/internal/process/esm_loader.js', - 'lib/internal/process/methods.js', + 'lib/internal/process/main_thread_only.js', 'lib/internal/process/next_tick.js', + 'lib/internal/process/per_thread.js', 'lib/internal/process/promises.js', 'lib/internal/process/stdio.js', 'lib/internal/process/warning.js', - 'lib/internal/process.js', + 'lib/internal/process/worker_thread_only.js', 'lib/internal/querystring.js', 'lib/internal/process/write-coverage.js', 'lib/internal/readline.js', From 576f1ea9782bbd8594e88b21dd0f168275c80e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= <tniessen@tnie.de> Date: Tue, 17 Jul 2018 02:38:34 +0200 Subject: [PATCH 111/116] buffer: remove superfluous assignment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR-URL: https://github.com/nodejs/node/pull/21844 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: Yuta Hiroto <hello@hiroppy.me> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> --- src/node_buffer.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/node_buffer.cc b/src/node_buffer.cc index 6e25889d6e4a25..858852e748fe03 100644 --- a/src/node_buffer.cc +++ b/src/node_buffer.cc @@ -612,7 +612,6 @@ void Fill(const FunctionCallbackInfo<Value>& args) { memcpy(ts_obj_data + start, *str, MIN(str_length, fill_length)); } else { - str_length = str_obj->Length(); // Write initial String to Buffer, then use that memory to copy remainder // of string. Correct the string length for cases like HEX where less than // the total string length is written. From d9825c7a16cae44e3efb2b3d7891a7f05e4c093e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Nie=C3=9Fen?= <tniessen@tnie.de> Date: Sat, 14 Jul 2018 18:59:39 +0200 Subject: [PATCH 112/116] crypto: prevent Sign::SignFinal from crashing The validation logic could be tricked into assuming an option was valid using malicious getters, leading to an invalid value being passed to the C++ layer, thus crashing the process. PR-URL: https://github.com/nodejs/node/pull/21815 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Ujjwal Sharma <usharma1998@gmail.com> --- lib/internal/crypto/sig.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/internal/crypto/sig.js b/lib/internal/crypto/sig.js index b6f8ceb50186d1..8aff7354735404 100644 --- a/lib/internal/crypto/sig.js +++ b/lib/internal/crypto/sig.js @@ -57,10 +57,11 @@ function getSaltLength(options) { function getIntOption(name, defaultValue, options) { if (options.hasOwnProperty(name)) { - if (options[name] === options[name] >> 0) { - return options[name]; + const value = options[name]; + if (value === value >> 0) { + return value; } else { - throw new ERR_INVALID_OPT_VALUE(name, options[name]); + throw new ERR_INVALID_OPT_VALUE(name, value); } } return defaultValue; From 0108ff6b51e7d46f1d4126f3a334d9bdbe31d8f4 Mon Sep 17 00:00:00 2001 From: Antoine du HAMEL <duhamelantoine1995@gmail.com> Date: Wed, 27 Jun 2018 00:51:21 +0200 Subject: [PATCH 113/116] test: add support for NODE_TEST_DIR on a separate mount point Linux permits a filesystem to be mounted at multiple points, but `fs.renameSync` does not work across different mount points, even if the same filesystem is mounted on both. This fixes failing tests when NODE_TEST_DIR mount point is different from the one on which the tests are executed (E.G. on a separate partition). Ref: http://man7.org/linux/man-pages/man2/rename.2.html PR-URL: https://github.com/nodejs/node/pull/21552 Refs: http://man7.org/linux/man-pages/man2/rename.2.html Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Ben Coe <bencoe@gmail.com> --- test/parallel/test-fs-copyfile.js | 2 +- test/parallel/test-fs-error-messages.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/test/parallel/test-fs-copyfile.js b/test/parallel/test-fs-copyfile.js index 6e5d6c9403e795..f001535598bbeb 100644 --- a/test/parallel/test-fs-copyfile.js +++ b/test/parallel/test-fs-copyfile.js @@ -64,7 +64,7 @@ try { } catch (err) { assert.strictEqual(err.syscall, 'copyfile'); assert(err.code === 'ENOTSUP' || err.code === 'ENOTTY' || - err.code === 'ENOSYS'); + err.code === 'ENOSYS' || err.code === 'EXDEV'); assert.strictEqual(err.path, src); assert.strictEqual(err.dest, dest); } diff --git a/test/parallel/test-fs-error-messages.js b/test/parallel/test-fs-error-messages.js index f406e8743ed120..8ef009e8128b0d 100644 --- a/test/parallel/test-fs-error-messages.js +++ b/test/parallel/test-fs-error-messages.js @@ -303,6 +303,11 @@ function re(literals, ...values) { `ENOTEMPTY: directory not empty, rename '${existingDir}' -> ` + `'${existingDir2}'`); assert.strictEqual(err.errno, uv.UV_ENOTEMPTY); + } else if (err.code === 'EXDEV') { // not on the same mounted filesystem + assert.strictEqual( + err.message, + `EXDEV: cross-device link not permitted, rename '${existingDir}' -> ` + + `'${existingDir2}'`); } else if (err.code === 'EEXIST') { // smartos and aix assert.strictEqual( err.message, From 292202836247f5ff1807a3fb72950d591a0e5cae Mon Sep 17 00:00:00 2001 From: Matteo Collina <hello@matteocollina.com> Date: Wed, 4 Jul 2018 19:55:12 +0200 Subject: [PATCH 114/116] inspector: expose original console MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds require('inspector').console, mapping it to the original global.console of V8. This enables applications to send messages to the inspector console programmatically. Fixes: https://github.com/nodejs/node/issues/21651 PR-URL: https://github.com/nodejs/node/pull/21659 Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Michaël Zasso <targos@protonmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> --- doc/api/inspector.md | 11 +++++++ lib/inspector.js | 2 ++ lib/internal/bootstrap/node.js | 2 ++ test/common/inspector-helper.js | 10 ++++-- test/sequential/test-inspector-console.js | 39 +++++++++++++++++++++++ 5 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 test/sequential/test-inspector-console.js diff --git a/doc/api/inspector.md b/doc/api/inspector.md index 7b1adcc47b32a0..8ab827bd1f9145 100644 --- a/doc/api/inspector.md +++ b/doc/api/inspector.md @@ -28,6 +28,17 @@ started. If wait is `true`, will block until a client has connected to the inspect port and flow control has been passed to the debugger client. +### inspector.console + +An object to send messages to the remote inspector console. + +```js +require('inspector').console.log('a message'); +``` + +The inspector console does not have API parity with Node.js +console. + ### inspector.close() Deactivate the inspector. Blocks until there are no active connections. diff --git a/lib/inspector.js b/lib/inspector.js index f4ec71fd6c2105..f4e365b774be79 100644 --- a/lib/inspector.js +++ b/lib/inspector.js @@ -11,6 +11,7 @@ const { } = require('internal/errors').codes; const util = require('util'); const { Connection, open, url } = process.binding('inspector'); +const { originalConsole } = require('internal/process/per_thread'); if (!Connection || !require('internal/worker').isMainThread) throw new ERR_INSPECTOR_NOT_AVAILABLE(); @@ -103,5 +104,6 @@ module.exports = { open: (port, host, wait) => open(port, host, !!wait), close: process._debugEnd, url: url, + console: originalConsole, Session }; diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index ab1695125df6bc..016c0c5e2304bc 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -120,6 +120,8 @@ const browserGlobals = !process._noBrowserGlobals; if (browserGlobals) { + // we are setting this here to foward it to the inspector later + perThreadSetup.originalConsole = global.console; setupGlobalTimeouts(); setupGlobalConsole(); setupGlobalURL(); diff --git a/test/common/inspector-helper.js b/test/common/inspector-helper.js index e590349f9c2b71..13726049798f0e 100644 --- a/test/common/inspector-helper.js +++ b/test/common/inspector-helper.js @@ -7,6 +7,7 @@ const fixtures = require('../common/fixtures'); const { spawn } = require('child_process'); const { parse: parseURL } = require('url'); const { getURLFromFilePath } = require('internal/url'); +const { EventEmitter } = require('events'); const _MAINSCRIPT = fixtures.path('loop.js'); const DEBUG = false; @@ -311,10 +312,12 @@ class InspectorSession { } } -class NodeInstance { +class NodeInstance extends EventEmitter { constructor(inspectorFlags = ['--inspect-brk=0'], scriptContents = '', scriptFile = _MAINSCRIPT) { + super(); + this._scriptPath = scriptFile; this._script = scriptFile ? null : scriptContents; this._portCallback = null; @@ -326,7 +329,10 @@ class NodeInstance { this._unprocessedStderrLines = []; this._process.stdout.on('data', makeBufferingDataCallback( - (line) => console.log('[out]', line))); + (line) => { + this.emit('stdout', line); + console.log('[out]', line); + })); this._process.stderr.on('data', makeBufferingDataCallback( (message) => this.onStderrLine(message))); diff --git a/test/sequential/test-inspector-console.js b/test/sequential/test-inspector-console.js new file mode 100644 index 00000000000000..6a06c798881654 --- /dev/null +++ b/test/sequential/test-inspector-console.js @@ -0,0 +1,39 @@ +// Flags: --expose-internals +'use strict'; + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const { NodeInstance } = require('../common/inspector-helper.js'); +const assert = require('assert'); + +async function runTest() { + const script = 'require(\'inspector\').console.log(\'hello world\');'; + const child = new NodeInstance('--inspect-brk=0', script, ''); + + let out = ''; + child.on('stdout', (line) => out += line); + + const session = await child.connectInspectorSession(); + + const commands = [ + { 'method': 'Runtime.enable' }, + { 'method': 'Runtime.runIfWaitingForDebugger' } + ]; + + session.send(commands); + + const msg = await session.waitForNotification('Runtime.consoleAPICalled'); + + assert.strictEqual(msg.params.type, 'log'); + assert.deepStrictEqual(msg.params.args, [{ + type: 'string', + value: 'hello world' + }]); + assert.strictEqual(out, ''); + + session.disconnect(); +} + +common.crashOnUnhandledRejection(); +runTest(); From 506631a9f92fa7a519db172f7bc8de0d7e58f596 Mon Sep 17 00:00:00 2001 From: Vse Mozhet Byt <vsemozhetbyt@gmail.com> Date: Sun, 8 Jul 2018 16:58:40 +0300 Subject: [PATCH 115/116] doc: fix structure and formatting in inspector.md 1. Reorder some sections alphabetically. 2. Fix some heading levels. 3. Express a type of a property more formally. PR-URL: https://github.com/nodejs/node/pull/21709 Reviewed-By: Rich Trott <rtrott@gmail.com> Reviewed-By: Claudio Rodriguez <cjrodr@yahoo.com> Reviewed-By: Anatoli Papirovski <apapirovski@mac.com> --- doc/api/inspector.md | 52 ++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/doc/api/inspector.md b/doc/api/inspector.md index 8ab827bd1f9145..92fcb0e03f89e4 100644 --- a/doc/api/inspector.md +++ b/doc/api/inspector.md @@ -12,6 +12,21 @@ It can be accessed using: const inspector = require('inspector'); ``` +## inspector.close() + +Deactivate the inspector. Blocks until there are no active connections. + +## inspector.console + +* {Object} An object to send messages to the remote inspector console. + +```js +require('inspector').console.log('a message'); +``` + +The inspector console does not have API parity with Node.js +console. + ## inspector.open([port[, host[, wait]]]) * `port` {number} Port to listen on for inspector connections. Optional. @@ -28,22 +43,7 @@ started. If wait is `true`, will block until a client has connected to the inspect port and flow control has been passed to the debugger client. -### inspector.console - -An object to send messages to the remote inspector console. - -```js -require('inspector').console.log('a message'); -``` - -The inspector console does not have API parity with Node.js -console. - -### inspector.close() - -Deactivate the inspector. Blocks until there are no active connections. - -### inspector.url() +## inspector.url() * Returns: {string|undefined} @@ -112,6 +112,16 @@ Connects a session to the inspector back-end. An exception will be thrown if there is already a connected session established either through the API or by a front-end connected to the Inspector WebSocket port. +### session.disconnect() +<!-- YAML +added: v8.0.0 +--> + +Immediately close the session. All pending message callbacks will be called +with an error. [`session.connect()`] will need to be called to be able to send +messages again. Reconnected session will lose all inspector state, such as +enabled agents or configured breakpoints. + ### session.post(method[, params][, callback]) <!-- YAML added: v8.0.0 @@ -139,16 +149,6 @@ by V8. Chrome DevTools Protocol domain provides an interface for interacting with one of the runtime agents used to inspect the application state and listen to the run-time events. -### session.disconnect() -<!-- YAML -added: v8.0.0 ---> - -Immediately close the session. All pending message callbacks will be called -with an error. [`session.connect()`] will need to be called to be able to send -messages again. Reconnected session will lose all inspector state, such as -enabled agents or configured breakpoints. - ## Example usage ### CPU Profiler From 991bb95384214639a1afe2dc4599768ebceab3ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Zasso?= <targos@protonmail.com> Date: Tue, 17 Jul 2018 16:33:02 +0200 Subject: [PATCH 116/116] 2018-07-18, Version 10.7.0 (Current) Notable changes: * console: * The `console.timeLog()` method has been implemented. (https://github.com/nodejs/node/pull/21312) * deps: * Upgrade to libuv 1.22.0. (https://github.com/nodejs/node/pull/21731) * Upgrade to ICU 62.1 (Unicode 11, CLDR 33.1). (https://github.com/nodejs/node/pull/21728) * http: * Added support for passing both `timeout` and `agent` options to `http.request`. (https://github.com/nodejs/node/pull/21204) * inspector: * Expose the original console API in `require('inspector').console`. (https://github.com/nodejs/node/pull/21659) * napi: * Added experimental support for functions dealing with bigint numbers. (https://github.com/nodejs/node/pull/21226) * process: * The `process.hrtime.bigint()` method has been implemented. (https://github.com/nodejs/node/pull/21256) * Added the `--title` command line argument to set the process title on startup. (https://github.com/nodejs/node/pull/21477) * trace_events: * Added process_name metadata. (https://github.com/nodejs/node/pull/21477) * Added new collaborators * codebytere - Shelley Vohr PR-URL: https://github.com/nodejs/node/pull/21851 --- CHANGELOG.md | 3 +- doc/api/cli.md | 2 +- doc/api/console.md | 2 +- doc/api/n-api.md | 12 +-- doc/api/process.md | 2 +- doc/changelogs/CHANGELOG_V10.md | 145 ++++++++++++++++++++++++++++++++ src/node_version.h | 6 +- 7 files changed, 159 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af54237dfa74d1..b93976dde5d728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,8 @@ release. </tr> <tr> <td valign="top"> -<b><a href="doc/changelogs/CHANGELOG_V10.md#10.6.0">10.6.0</a></b><br/> +<b><a href="doc/changelogs/CHANGELOG_V10.md#10.7.0">10.7.0</a></b><br/> +<a href="doc/changelogs/CHANGELOG_V10.md#10.6.0">10.6.0</a><br/> <a href="doc/changelogs/CHANGELOG_V10.md#10.5.0">10.5.0</a><br/> <a href="doc/changelogs/CHANGELOG_V10.md#10.4.1">10.4.1</a><br/> <a href="doc/changelogs/CHANGELOG_V10.md#10.4.0">10.4.0</a><br/> diff --git a/doc/api/cli.md b/doc/api/cli.md index 7ebfb084d76abb..4986d019129b4e 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -280,7 +280,7 @@ Throw errors for deprecations. ### `--title=title` <!-- YAML -added: REPLACEME +added: v10.7.0 --> Set `process.title` on startup. diff --git a/doc/api/console.md b/doc/api/console.md index 95801c31fed63c..efc06eacd313b6 100644 --- a/doc/api/console.md +++ b/doc/api/console.md @@ -415,7 +415,7 @@ console.timeEnd('100-elements'); ### console.timeLog([label][, ...data]) <!-- YAML -added: REPLACEME +added: v10.7.0 --> * `label` {string} **Default:** `'default'` * `...data` {any} diff --git a/doc/api/n-api.md b/doc/api/n-api.md index 5fbe4bb07469c2..44223e19026c52 100644 --- a/doc/api/n-api.md +++ b/doc/api/n-api.md @@ -1695,7 +1695,7 @@ The JavaScript `Number` type is described in #### napi_create_bigint_int64 <!-- YAML -added: REPLACEME +added: v10.7.0 --> > Stability: 1 - Experimental @@ -1716,7 +1716,7 @@ This API converts the C `int64_t` type to the JavaScript `BigInt` type. #### napi_create_bigint_uint64 <!-- YAML -added: REPLACEME +added: v10.7.0 --> > Stability: 1 - Experimental @@ -1737,7 +1737,7 @@ This API converts the C `uint64_t` type to the JavaScript `BigInt` type. #### napi_create_bigint_words <!-- YAML -added: REPLACEME +added: v10.7.0 --> > Stability: 1 - Experimental @@ -2051,7 +2051,7 @@ This API returns the C double primitive equivalent of the given JavaScript #### napi_get_value_bigint_int64 <!-- YAML -added: REPLACEME +added: v10.7.0 --> > Stability: 1 - Experimental @@ -2079,7 +2079,7 @@ This API returns the C `int64_t` primitive equivalent of the given JavaScript #### napi_get_value_bigint_uint64 <!-- YAML -added: REPLACEME +added: v10.7.0 --> > Stability: 1 - Experimental @@ -2107,7 +2107,7 @@ This API returns the C `uint64_t` primitive equivalent of the given JavaScript #### napi_get_value_bigint_words <!-- YAML -added: REPLACEME +added: v10.7.0 --> > Stability: 1 - Experimental diff --git a/doc/api/process.md b/doc/api/process.md index 2c27409d809080..dd0314cf6c3c1d 100644 --- a/doc/api/process.md +++ b/doc/api/process.md @@ -1196,7 +1196,7 @@ setTimeout(() => { ## process.hrtime.bigint() <!-- YAML -added: REPLACEME +added: v10.7.0 --> * Returns: {bigint} diff --git a/doc/changelogs/CHANGELOG_V10.md b/doc/changelogs/CHANGELOG_V10.md index 1973dfd3f3ee97..bf90f2a09d8e40 100644 --- a/doc/changelogs/CHANGELOG_V10.md +++ b/doc/changelogs/CHANGELOG_V10.md @@ -9,6 +9,7 @@ </tr> <tr> <td> +<a href="#10.7.0">10.7.0</a><br/> <a href="#10.6.0">10.6.0</a><br/> <a href="#10.5.0">10.5.0</a><br/> <a href="#10.4.1">10.4.1</a><br/> @@ -34,6 +35,150 @@ * [io.js](CHANGELOG_IOJS.md) * [Archive](CHANGELOG_ARCHIVE.md) +<a id="10.7.0"></a> +## 2018-07-18, Version 10.7.0 (Current), @targos + +### Notable Changes + +* **console**: + * The `console.timeLog()` method has been implemented. [#21312](https://github.com/nodejs/node/pull/21312) +* **deps**: + * Upgrade to libuv 1.22.0. [#21731](https://github.com/nodejs/node/pull/21731) + * Upgrade to ICU 62.1 (Unicode 11, CLDR 33.1). [#21728](https://github.com/nodejs/node/pull/21728) +* **http**: + * Added support for passing both `timeout` and `agent` options to + `http.request`. [#21204](https://github.com/nodejs/node/pull/21204) +* **inspector**: + * Expose the original console API in `require('inspector').console`. [#21659](https://github.com/nodejs/node/pull/21659) +* **napi**: + * Added experimental support for functions dealing with bigint numbers. [#21226](https://github.com/nodejs/node/pull/21226) +* **process**: + * The `process.hrtime.bigint()` method has been implemented. [#21256](https://github.com/nodejs/node/pull/21256) + * Added the `--title` command line argument to set the process title on + startup. [#21477](https://github.com/nodejs/node/pull/21477) +* **trace_events**: + * Added process\_name metadata. [#21477](https://github.com/nodejs/node/pull/21477) +* **Added new collaborators** + * [codebytere](https://github.com/codebytere) - Shelley Vohr + +### Commits + +* [[`8c97ffb2f5`](https://github.com/nodejs/node/commit/8c97ffb2f5)] - **assert**: improve simple assert (Ruben Bridgewater) [#21626](https://github.com/nodejs/node/pull/21626) +* [[`9776f1cbef`](https://github.com/nodejs/node/commit/9776f1cbef)] - **benchmark**: add n-api function args benchmark (Kenny Yuan) [#21555](https://github.com/nodejs/node/pull/21555) +* [[`576f1ea978`](https://github.com/nodejs/node/commit/576f1ea978)] - **buffer**: remove superfluous assignment (Tobias Nießen) [#21844](https://github.com/nodejs/node/pull/21844) +* [[`6bb2b5a51d`](https://github.com/nodejs/node/commit/6bb2b5a51d)] - **build**: account for pure C sources in `build-addons-napi` (Anna Henningsen) [#21797](https://github.com/nodejs/node/pull/21797) +* [[`c02fb88936`](https://github.com/nodejs/node/commit/c02fb88936)] - **build**: enabling lto at configure (Octavian Soldea) [#21677](https://github.com/nodejs/node/pull/21677) +* [[`2a0862cec9`](https://github.com/nodejs/node/commit/2a0862cec9)] - **console**: fix timeEnd() not coercing the input (Ruben Bridgewater) [#21779](https://github.com/nodejs/node/pull/21779) +* [[`f3c397cd21`](https://github.com/nodejs/node/commit/f3c397cd21)] - **(SEMVER-MINOR)** **console**: implement timeLog method (Michaël Zasso) [#21312](https://github.com/nodejs/node/pull/21312) +* [[`73cafd853c`](https://github.com/nodejs/node/commit/73cafd853c)] - **console,util**: avoid pair array generation in C++ (Anna Henningsen) [#20831](https://github.com/nodejs/node/pull/20831) +* [[`d9825c7a16`](https://github.com/nodejs/node/commit/d9825c7a16)] - **crypto**: prevent Sign::SignFinal from crashing (Tobias Nießen) [#21815](https://github.com/nodejs/node/pull/21815) +* [[`07cce880bf`](https://github.com/nodejs/node/commit/07cce880bf)] - **crypto**: handle OpenSSL error queue in CipherBase (Tobias Nießen) [#21288](https://github.com/nodejs/node/pull/21288) +* [[`355c5e3c95`](https://github.com/nodejs/node/commit/355c5e3c95)] - **deps**: cherry-pick 555c811 from upstream V8 (Anna Henningsen) [#21741](https://github.com/nodejs/node/pull/21741) +* [[`42d75392c5`](https://github.com/nodejs/node/commit/42d75392c5)] - **deps**: patch V8 to 6.7.288.49 (Myles Borins) [#21727](https://github.com/nodejs/node/pull/21727) +* [[`6920091488`](https://github.com/nodejs/node/commit/6920091488)] - **deps**: upgrade to libuv 1.22.0 (cjihrig) [#21731](https://github.com/nodejs/node/pull/21731) +* [[`122ae24f62`](https://github.com/nodejs/node/commit/122ae24f62)] - **deps**: icu 62.1 bump (Unicode 11, CLDR 33.1) (Steven R. Loomis) [#21728](https://github.com/nodejs/node/pull/21728) +* [[`a5233c7e17`](https://github.com/nodejs/node/commit/a5233c7e17)] - **deps**: cherry-pick 477df06 from upstream v8 (Gus Caplan) [#21644](https://github.com/nodejs/node/pull/21644) +* [[`506631a9f9`](https://github.com/nodejs/node/commit/506631a9f9)] - **doc**: fix structure and formatting in inspector.md (Vse Mozhet Byt) [#21709](https://github.com/nodejs/node/pull/21709) +* [[`53b587a5af`](https://github.com/nodejs/node/commit/53b587a5af)] - **doc**: add documentation for buffer.byteOffset (Andreas Madsen) [#21718](https://github.com/nodejs/node/pull/21718) +* [[`51dfebf9ac`](https://github.com/nodejs/node/commit/51dfebf9ac)] - **doc**: fix vm.runInNewContext signature (Michaël Zasso) [#21824](https://github.com/nodejs/node/pull/21824) +* [[`10f9374ea3`](https://github.com/nodejs/node/commit/10f9374ea3)] - **doc**: make markdown input compliant (Sam Ruby) [#21780](https://github.com/nodejs/node/pull/21780) +* [[`02982998db`](https://github.com/nodejs/node/commit/02982998db)] - **doc**: add my pronoun (Ruben Bridgewater) [#21813](https://github.com/nodejs/node/pull/21813) +* [[`ca8c96035a`](https://github.com/nodejs/node/commit/ca8c96035a)] - **doc**: update readme with my pronouns (Lance Ball) [#21818](https://github.com/nodejs/node/pull/21818) +* [[`d33281b36f`](https://github.com/nodejs/node/commit/d33281b36f)] - **doc**: prevent some redirections (Vse Mozhet Byt) [#21811](https://github.com/nodejs/node/pull/21811) +* [[`0de0f89d0c`](https://github.com/nodejs/node/commit/0de0f89d0c)] - **doc**: add "Edit on GitHub" link (Rich Trott) [#21703](https://github.com/nodejs/node/pull/21703) +* [[`7ab6efdb94`](https://github.com/nodejs/node/commit/7ab6efdb94)] - **doc**: add policy for landing new npm releases (Myles Borins) [#21594](https://github.com/nodejs/node/pull/21594) +* [[`3d93273bf7`](https://github.com/nodejs/node/commit/3d93273bf7)] - **doc**: add OS X to instead of only macOS (XadillaX) [#21033](https://github.com/nodejs/node/pull/21033) +* [[`577d24baa4`](https://github.com/nodejs/node/commit/577d24baa4)] - **doc**: fix module.children description (Travis Fischer) [#21672](https://github.com/nodejs/node/pull/21672) +* [[`cd6601b87a`](https://github.com/nodejs/node/commit/cd6601b87a)] - **doc**: fix HTTP res 'finish' description (Sergey Zelenov) [#21670](https://github.com/nodejs/node/pull/21670) +* [[`51db88b0f1`](https://github.com/nodejs/node/commit/51db88b0f1)] - **doc**: fix http2stream.pushStream error doc (Сковорода Никита Андреевич) [#21487](https://github.com/nodejs/node/pull/21487) +* [[`6e1917a596`](https://github.com/nodejs/node/commit/6e1917a596)] - **doc**: update changelog with 9.x EOL (Сковорода Никита Андреевич) [#21612](https://github.com/nodejs/node/pull/21612) +* [[`cd77d8782a`](https://github.com/nodejs/node/commit/cd77d8782a)] - **doc**: improve documentation of fs sync methods (iwko) [#21243](https://github.com/nodejs/node/pull/21243) +* [[`1044bafec4`](https://github.com/nodejs/node/commit/1044bafec4)] - **doc**: remove \_Node.js style callback\_ (Rich Trott) [#21701](https://github.com/nodejs/node/pull/21701) +* [[`971679328e`](https://github.com/nodejs/node/commit/971679328e)] - **doc**: add codebytere as collaborator (Shelley Vohr) [#21700](https://github.com/nodejs/node/pull/21700) +* [[`034fe19862`](https://github.com/nodejs/node/commit/034fe19862)] - **doc**: add links to inline HTML table (Rich Trott) [#21678](https://github.com/nodejs/node/pull/21678) +* [[`04eed2342d`](https://github.com/nodejs/node/commit/04eed2342d)] - **doc**: remove "note that" from fs doc (Rich Trott) [#21646](https://github.com/nodejs/node/pull/21646) +* [[`c8d5bab022`](https://github.com/nodejs/node/commit/c8d5bab022)] - **doc**: fix doc for napi\_create\_function (Gabriel Schulhof) +* [[`f7aa22a0eb`](https://github.com/nodejs/node/commit/f7aa22a0eb)] - **doc**: improve guide text for CI runs (Rich Trott) [#21645](https://github.com/nodejs/node/pull/21645) +* [[`6f8ebc08b9`](https://github.com/nodejs/node/commit/6f8ebc08b9)] - **doc**: unify spelling of backpressure (Thomas Watson) [#21630](https://github.com/nodejs/node/pull/21630) +* [[`3fffc7e95f`](https://github.com/nodejs/node/commit/3fffc7e95f)] - **errors**: fix undefined HTTP2 and tls errors (Shailesh Shekhawat) [#21564](https://github.com/nodejs/node/pull/21564) +* [[`b758006c23`](https://github.com/nodejs/node/commit/b758006c23)] - **fs**: fix fsPromises.lchmod error on non-Mac (Masashi Hirano) [#21435](https://github.com/nodejs/node/pull/21435) +* [[`4fa7150962`](https://github.com/nodejs/node/commit/4fa7150962)] - **fs**: support pseudofiles in promises.readFile (Timothy Gu) [#21497](https://github.com/nodejs/node/pull/21497) +* [[`bba500d0ea`](https://github.com/nodejs/node/commit/bba500d0ea)] - **(SEMVER-MINOR)** **http**: fix request with option timeout and agent (killagu) [#21204](https://github.com/nodejs/node/pull/21204) +* [[`0b3c80ca31`](https://github.com/nodejs/node/commit/0b3c80ca31)] - **http2**: fix issues with aborted `respondWithFile()`s (Anna Henningsen) [#21561](https://github.com/nodejs/node/pull/21561) +* [[`238ef58841`](https://github.com/nodejs/node/commit/238ef58841)] - **http2**: remove `waitTrailers` listener after closing a stream (RidgeA) [#21764](https://github.com/nodejs/node/pull/21764) +* [[`07160cd2fd`](https://github.com/nodejs/node/commit/07160cd2fd)] - **http2**: order declarations in core.js (Rich Trott) [#21689](https://github.com/nodejs/node/pull/21689) +* [[`c88af232c8`](https://github.com/nodejs/node/commit/c88af232c8)] - **http2**: pass incoming set-cookie header as array (Gerhard Stoebich) [#21360](https://github.com/nodejs/node/pull/21360) +* [[`2922028362`](https://github.com/nodejs/node/commit/2922028362)] - **(SEMVER-MINOR)** **inspector**: expose original console (Matteo Collina) [#21659](https://github.com/nodejs/node/pull/21659) +* [[`b2291296ef`](https://github.com/nodejs/node/commit/b2291296ef)] - **inspector**: split main thread interface from transport (Eugene Ostroukhov) [#21182](https://github.com/nodejs/node/pull/21182) +* [[`4ed4bf3bdd`](https://github.com/nodejs/node/commit/4ed4bf3bdd)] - **lib**: update punycode to 2.1.1 (Rich Trott) [#21768](https://github.com/nodejs/node/pull/21768) +* [[`4433ecbf30`](https://github.com/nodejs/node/commit/4433ecbf30)] - **lib**: refactor cli table (Ruben Bridgewater) [#20960](https://github.com/nodejs/node/pull/20960) +* [[`92d79212ec`](https://github.com/nodejs/node/commit/92d79212ec)] - **lib**: consolidate redundant require() calls (cjihrig) [#21699](https://github.com/nodejs/node/pull/21699) +* [[`ed774b7930`](https://github.com/nodejs/node/commit/ed774b7930)] - **messaging**: fix edge cases with transferring ports (Timothy Gu) [#21540](https://github.com/nodejs/node/pull/21540) +* [[`221c8bd58f`](https://github.com/nodejs/node/commit/221c8bd58f)] - **messaging**: use actual DOMException for DataCloneError (Timothy Gu) [#21540](https://github.com/nodejs/node/pull/21540) +* [[`4f3bbfaaca`](https://github.com/nodejs/node/commit/4f3bbfaaca)] - **n-api**: test uint32 truncation (Gabriel Schulhof) [#21722](https://github.com/nodejs/node/pull/21722) +* [[`b8ba003fbf`](https://github.com/nodejs/node/commit/b8ba003fbf)] - **n-api**: remove experimental gate from status codes (Gabriel Schulhof) [#21680](https://github.com/nodejs/node/pull/21680) +* [[`109c59971a`](https://github.com/nodejs/node/commit/109c59971a)] - **n-api**: create functions directly (Gabriel Schulhof) [#21688](https://github.com/nodejs/node/pull/21688) +* [[`cec166e85f`](https://github.com/nodejs/node/commit/cec166e85f)] - **n-api**: restrict exports by version (Kyle Farnung) [#19962](https://github.com/nodejs/node/pull/19962) +* [[`3096ee5a4b`](https://github.com/nodejs/node/commit/3096ee5a4b)] - **(SEMVER-MINOR)** **napi**: add bigint support (Gus Caplan) [#21226](https://github.com/nodejs/node/pull/21226) +* [[`581390c59d`](https://github.com/nodejs/node/commit/581390c59d)] - **process**: split bootstrappers by threads that can run them (Joyee Cheung) [#21378](https://github.com/nodejs/node/pull/21378) +* [[`f1b18ba412`](https://github.com/nodejs/node/commit/f1b18ba412)] - **(SEMVER-MINOR)** **process**: implement process.hrtime.bigint() (Joyee Cheung) [#21256](https://github.com/nodejs/node/pull/21256) +* [[`961f6e8623`](https://github.com/nodejs/node/commit/961f6e8623)] - **process**: fix process.exitCode handling for fatalException (Denys Otrishko) [#21739](https://github.com/nodejs/node/pull/21739) +* [[`4b613d3976`](https://github.com/nodejs/node/commit/4b613d3976)] - **repl**: make own properties shadow prototype properties (Sam Ruby) [#21588](https://github.com/nodejs/node/pull/21588) +* [[`1019c2d317`](https://github.com/nodejs/node/commit/1019c2d317)] - **src**: fix async hooks crashing when there is no node context (Javier Gonzalez) [#19134](https://github.com/nodejs/node/pull/19134) +* [[`a9a718696e`](https://github.com/nodejs/node/commit/a9a718696e)] - **src**: make heap snapshot & embedder graph accessible for tests (Anna Henningsen) [#21741](https://github.com/nodejs/node/pull/21741) +* [[`5121278f5c`](https://github.com/nodejs/node/commit/5121278f5c)] - **src**: use V8 graph heap snapshot API (Anna Henningsen) [#21741](https://github.com/nodejs/node/pull/21741) +* [[`d42dbde1a8`](https://github.com/nodejs/node/commit/d42dbde1a8)] - **src**: add iteration over all base objects to Environment (Anna Henningsen) [#21741](https://github.com/nodejs/node/pull/21741) +* [[`4ed5d1a623`](https://github.com/nodejs/node/commit/4ed5d1a623)] - **src**: add HandleWrap::AddWrapMethods (Jon Moss) [#21769](https://github.com/nodejs/node/pull/21769) +* [[`51d613db2d`](https://github.com/nodejs/node/commit/51d613db2d)] - **src**: start annotating native code side effect (Timothy Gu) [#21458](https://github.com/nodejs/node/pull/21458) +* [[`466601f47f`](https://github.com/nodejs/node/commit/466601f47f)] - **src**: remove .h if -inl.h is already included (Daniel Bevenius) [#21381](https://github.com/nodejs/node/pull/21381) +* [[`a68b7dda5f`](https://github.com/nodejs/node/commit/a68b7dda5f)] - **src**: add node\_process.cc (James M Snell) [#21105](https://github.com/nodejs/node/pull/21105) +* [[`cb698111c4`](https://github.com/nodejs/node/commit/cb698111c4)] - **src**: add comment on CallbackScope exception behaviour (Anna Henningsen) [#21743](https://github.com/nodejs/node/pull/21743) +* [[`712809eb1b`](https://github.com/nodejs/node/commit/712809eb1b)] - **src**: enable more detailed memory tracking (Anna Henningsen) [#21742](https://github.com/nodejs/node/pull/21742) +* [[`277077853f`](https://github.com/nodejs/node/commit/277077853f)] - **src**: make Environment::is\_stopping\_worker inline (Jon Moss) [#21720](https://github.com/nodejs/node/pull/21720) +* [[`d06305635d`](https://github.com/nodejs/node/commit/d06305635d)] - **(SEMVER-MINOR)** **src**: add --title command line argument (James M Snell) [#21477](https://github.com/nodejs/node/pull/21477) +* [[`ceec23e6e4`](https://github.com/nodejs/node/commit/ceec23e6e4)] - **src**: remove using directives from spawn\_sync.h (Daniel Bevenius) [#21634](https://github.com/nodejs/node/pull/21634) +* [[`3a627c830b`](https://github.com/nodejs/node/commit/3a627c830b)] - **src**: add context-aware init macro and doc (Gabriel Schulhof) [#21318](https://github.com/nodejs/node/pull/21318) +* [[`aa5994f2b9`](https://github.com/nodejs/node/commit/aa5994f2b9)] - **src,tools**: use https://nodejs.org URL when possible. (XhmikosR) [#21719](https://github.com/nodejs/node/pull/21719) +* [[`0108ff6b51`](https://github.com/nodejs/node/commit/0108ff6b51)] - **test**: add support for NODE\_TEST\_DIR on a separate mount point (Antoine du HAMEL) [#21552](https://github.com/nodejs/node/pull/21552) +* [[`eef975ebae`](https://github.com/nodejs/node/commit/eef975ebae)] - **test**: move inspector test back to parallel, unmark flaky (Anna Henningsen) [#21806](https://github.com/nodejs/node/pull/21806) +* [[`67908e9933`](https://github.com/nodejs/node/commit/67908e9933)] - **test**: fix build warnings in bigint N-API test (Anna Henningsen) [#21796](https://github.com/nodejs/node/pull/21796) +* [[`6b72583bf8`](https://github.com/nodejs/node/commit/6b72583bf8)] - **test**: refactor test-tls-connect-memleak, move to parallel (Anna Henningsen) [#21794](https://github.com/nodejs/node/pull/21794) +* [[`174a9db51a`](https://github.com/nodejs/node/commit/174a9db51a)] - **test**: refactor test-net-connect-memleak, move to parallel (Anna Henningsen) [#21794](https://github.com/nodejs/node/pull/21794) +* [[`b338ff54bb`](https://github.com/nodejs/node/commit/b338ff54bb)] - **test**: add gc tracking to common API (Anna Henningsen) [#21794](https://github.com/nodejs/node/pull/21794) +* [[`4e60ce8f87`](https://github.com/nodejs/node/commit/4e60ce8f87)] - **test**: fix flaky test-debug-prompt (Rich Trott) [#21826](https://github.com/nodejs/node/pull/21826) +* [[`a2edb59870`](https://github.com/nodejs/node/commit/a2edb59870)] - **test**: fix comment of fs.promises write (Ryuichi Sakagami) [#21708](https://github.com/nodejs/node/pull/21708) +* [[`32ad163038`](https://github.com/nodejs/node/commit/32ad163038)] - **test**: add test of fs.promises write for non-string buffers (Ryuichi Sakagami) [#21708](https://github.com/nodejs/node/pull/21708) +* [[`7352b72fc9`](https://github.com/nodejs/node/commit/7352b72fc9)] - **test**: add heap snapshot tests (Anna Henningsen) [#21741](https://github.com/nodejs/node/pull/21741) +* [[`678313d18b`](https://github.com/nodejs/node/commit/678313d18b)] - **test**: add filehandle sync() and datasync() tests (Masashi Hirano) [#20530](https://github.com/nodejs/node/pull/20530) +* [[`a09bdb5847`](https://github.com/nodejs/node/commit/a09bdb5847)] - **test**: improve console table error output (Ruben Bridgewater) [#20960](https://github.com/nodejs/node/pull/20960) +* [[`600349aaba`](https://github.com/nodejs/node/commit/600349aaba)] - **test**: refactor process/worker exitCode tests (Denys Otrishko) [#21739](https://github.com/nodejs/node/pull/21739) +* [[`15026511b8`](https://github.com/nodejs/node/commit/15026511b8)] - **test**: remove timer in fs.watchFile() test (Rich Trott) [#21694](https://github.com/nodejs/node/pull/21694) +* [[`ae5d5658b9`](https://github.com/nodejs/node/commit/ae5d5658b9)] - **test**: fix flaky watchFile() (Rich Trott) [#21694](https://github.com/nodejs/node/pull/21694) +* [[`ada3f34cd4`](https://github.com/nodejs/node/commit/ada3f34cd4)] - **test**: fix weird string error (Jon Moss) [#21793](https://github.com/nodejs/node/pull/21793) +* [[`f46536be23`](https://github.com/nodejs/node/commit/f46536be23)] - **test**: fix timeouts when running worker tests with `--worker` (Anna Henningsen) [#21791](https://github.com/nodejs/node/pull/21791) +* [[`f386c0a517`](https://github.com/nodejs/node/commit/f386c0a517)] - **test**: add test for dns.promises.resolve . (Keita Akutsu) [#21691](https://github.com/nodejs/node/pull/21691) +* [[`11e9b4ecee`](https://github.com/nodejs/node/commit/11e9b4ecee)] - **test**: fix parallel/test-tls-env-extra-ca.js (Niicck) [#21647](https://github.com/nodejs/node/pull/21647) +* [[`eda7fffba4`](https://github.com/nodejs/node/commit/eda7fffba4)] - **test**: swap arguments in strictEqual() (Sohail Rajdev) [#21660](https://github.com/nodejs/node/pull/21660) +* [[`194d1955a7`](https://github.com/nodejs/node/commit/194d1955a7)] - **test**: fix test-tls-connect-memleak (Rich Trott) [#21681](https://github.com/nodejs/node/pull/21681) +* [[`24f649c8cf`](https://github.com/nodejs/node/commit/24f649c8cf)] - **test**: fix pummel/test-net-connect-memleak (Rich Trott) [#21658](https://github.com/nodejs/node/pull/21658) +* [[`021dd5404c`](https://github.com/nodejs/node/commit/021dd5404c)] - **test**: remove unnecessary string literals (Jacek Pospychała) [#21638](https://github.com/nodejs/node/pull/21638) +* [[`47b10e30c0`](https://github.com/nodejs/node/commit/47b10e30c0)] - **test**: replace third argument with comment in strict equals (Developer Davo) [#21603](https://github.com/nodejs/node/pull/21603) +* [[`25dac95164`](https://github.com/nodejs/node/commit/25dac95164)] - **test**: fix args passed to strictEqual (Haroon Khan) [#21584](https://github.com/nodejs/node/pull/21584) +* [[`fe9888a34a`](https://github.com/nodejs/node/commit/fe9888a34a)] - **test**: check type for Worker filename argument (Masashi Hirano) [#21620](https://github.com/nodejs/node/pull/21620) +* [[`9cd5c0ec79`](https://github.com/nodejs/node/commit/9cd5c0ec79)] - **test**: add test for missing dynamic instantiate hook (Michaël Zasso) [#21506](https://github.com/nodejs/node/pull/21506) +* [[`dc84858787`](https://github.com/nodejs/node/commit/dc84858787)] - **test,util**: add missing tests and conditions (MaleDong) [#21455](https://github.com/nodejs/node/pull/21455) +* [[`c26ba082ae`](https://github.com/nodejs/node/commit/c26ba082ae)] - **tools**: avoid global install of dmn for lint update (Rich Trott) [#21744](https://github.com/nodejs/node/pull/21744) +* [[`e030dd7d65`](https://github.com/nodejs/node/commit/e030dd7d65)] - **tools**: add no-duplicate-requires rule (Gus Caplan) [#21712](https://github.com/nodejs/node/pull/21712) +* [[`b9bbbbe5d1`](https://github.com/nodejs/node/commit/b9bbbbe5d1)] - **tools**: build all.json by combining generated JSON (Sam Ruby) [#21637](https://github.com/nodejs/node/pull/21637) +* [[`214c608208`](https://github.com/nodejs/node/commit/214c608208)] - **tools**: lint doc code examples in strict mode (Vse Mozhet Byt) [#21615](https://github.com/nodejs/node/pull/21615) +* [[`27d17d4600`](https://github.com/nodejs/node/commit/27d17d4600)] - **trace_events**: add traced\_value.cc/traced\_value.h (James M Snell) [#21475](https://github.com/nodejs/node/pull/21475) +* [[`c4d7413a15`](https://github.com/nodejs/node/commit/c4d7413a15)] - **(SEMVER-MINOR)** **trace_events**: add process\_name metadata (James M Snell) [#21477](https://github.com/nodejs/node/pull/21477) +* [[`b0943a655e`](https://github.com/nodejs/node/commit/b0943a655e)] - **worker**: exit after uncaught exception (Denys Otrishko) [#21739](https://github.com/nodejs/node/pull/21739) +* [[`25fef3d8d4`](https://github.com/nodejs/node/commit/25fef3d8d4)] - **workers**: fix invalid exit code in parent upon uncaught exception (Denys Otrishko) [#21713](https://github.com/nodejs/node/pull/21713) +* [[`48b16aad47`](https://github.com/nodejs/node/commit/48b16aad47)] - **zlib**: instance-ify two methods (Jon Moss) [#21702](https://github.com/nodejs/node/pull/21702) +* [[`dae7130929`](https://github.com/nodejs/node/commit/dae7130929)] - **zlib**: track memory allocated by zlib (Anna Henningsen) [#21608](https://github.com/nodejs/node/pull/21608) +* [[`96dae83713`](https://github.com/nodejs/node/commit/96dae83713)] - **zlib**: fix memory leak for unused zlib instances (Anna Henningsen) [#21607](https://github.com/nodejs/node/pull/21607) + <a id="10.6.0"></a> ## 2018-07-04, Version 10.6.0 (Current), @targos diff --git a/src/node_version.h b/src/node_version.h index 0a7d134d9e25e4..8370b4c86f44db 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -23,13 +23,13 @@ #define SRC_NODE_VERSION_H_ #define NODE_MAJOR_VERSION 10 -#define NODE_MINOR_VERSION 6 -#define NODE_PATCH_VERSION 1 +#define NODE_MINOR_VERSION 7 +#define NODE_PATCH_VERSION 0 #define NODE_VERSION_IS_LTS 0 #define NODE_VERSION_LTS_CODENAME "" -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n)