Skip to content

Commit

Permalink
Add Dart Web NumberFormat
Browse files Browse the repository at this point in the history
  • Loading branch information
mosuem committed Aug 16, 2023
1 parent 561e549 commit 88b9cdc
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 21 deletions.
6 changes: 3 additions & 3 deletions executors/dart_web/.gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
out/collatorDart.js
out/collatorDart.js.deps
out/collatorDart.js.map
out/*Dart.js
out/*Dart.js.deps
out/*Dart.js.map
pubspec.lock
File renamed without changes.
41 changes: 32 additions & 9 deletions executors/dart_web/bin/make_runnable_by_node.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,43 @@ import 'dart:io';

import 'package:pubspec_lock_parse/pubspec_lock_parse.dart';

class ExportFunction {
final String name;
final List<String> argNames;

ExportFunction({required this.name, required this.argNames});
}

Future<void> main(List<String> args) async {
var name = 'collatorDart';
var names = {
'collator': ExportFunction(
name: 'testCollationShort',
argNames: ['encoded'],
),
'numberformat': ExportFunction(
name: 'testDecimalFormat',
argNames: ['encoded', 'log', 'version'],
),
};
for (var name in names.entries) {
await prepare(name.key, name.value);
}

setVersionFile();
}

Future<void> prepare(String name, ExportFunction function) async {
var outFile = '${name}Dart';
var compile = await Process.run('dart', [
'compile',
'js',
'bin/all_executors.dart',
'bin/${name}Executor.dart',
'-o',
'out/$name.js',
'out/$outFile.js',
]);
print(compile.stderr);

prepareOutFile(name, ['testCollationShort']);

setVersionFile();
prepareOutFile(outFile, [function]);
}

void setVersionFile() {
Expand All @@ -33,7 +56,7 @@ module.exports = { dartVersion };
}

/// Prepare the file to export `testCollationShort`
void prepareOutFile(String name, List<String> functions) {
void prepareOutFile(String name, List<ExportFunction> functions) {
var outFile = File('out/$name.js');
var s = outFile.readAsStringSync();
s = s.replaceAll('self.', '');
Expand All @@ -45,8 +68,8 @@ void prepareOutFile(String name, List<String> functions) {

var exportFunctions = functions
.map(
(e) => '''$e: function(arg) {
return A.$e(arg);
(e) => '''${e.name}: function(${e.argNames.join(',')}) {
return A.${e.name}(${e.argNames.join(',')});
}''',
)
.join(',\n');
Expand Down
251 changes: 251 additions & 0 deletions executors/dart_web/bin/numberformat.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
import 'dart:convert';

import 'package:intl4x/intl4x.dart';
import 'package:intl4x/number_format.dart';

final patternsToOptions = <String, NumberFormatOptions>{
"0.0":
NumberFormatOptions.custom(digits: Digits.withFractionDigits(minimum: 1)),
"00": NumberFormatOptions.custom(
digits: Digits.withSignificantDigits(minimum: 1)),
"@@@": NumberFormatOptions.custom(
digits: Digits.withSignificantDigits(minimum: 3)),
"@@###": NumberFormatOptions.custom(
digits: Digits.withSignificantDigits(minimum: 2, maximum: 5)),
"0.0000E0": NumberFormatOptions.custom(
notation: ScientificNotation(),
digits: Digits.withFractionDigits(minimum: 4)),
};

// The nodejs version that first supported advance rounding options
const first_v3_version = 'v20.1.0';

enum NodeVersion {
v3,
preV3,
}

const unsupported_skeleton_terms = [
"scientific/+ee/sign-always",
"decimal-always",
];

const unsupported_rounding_modes = ["unnecessary"];

// Use this
const supported_options_by_version = {
NodeVersion.v3: [
"compactDisplay",
"currency",
"currencyDisplay",
"currencySign",
"localeMatcher",
"notation",
"numberingSystem",
"signDisplay",
"style",
"unit",
"unitDisplay",
"useGrouping",
"roundingMode",
"roundingPriority",
"roundingIncrement",
"trailingZeroDisplay",
"minimumIntegerDigits",
"minimumFractionDigits",
"maximumFractionDigits",
"minimumSignificantDigits",
"maximumSignificantDigits"
],
NodeVersion.preV3: [
"compactDisplay",
"currency",
"currencyDisplay",
"currencySign",
"localeMatcher",
"notation",
"numberingSystem",
"signDisplay",
"style",
"unit",
"unitDisplay",
"useGrouping",
"roundingMode",
"minimumIntegerDigits",
"minimumFractionDigits",
"maximumFractionDigits",
"minimumSignificantDigits",
"maximumSignificantDigits"
]
// TODO: Add older version support.
};

String testDecimalFormat(
String encoded,
bool doLogInput,
String node_version,
) {
final json = jsonDecode(encoded);
final label = json['label'] as String?;
final skeleton = json['skeleton'] as String?;
final pattern = json['pattern'] as String?;
final rounding = json['rounding'] as String?;
var input =
double.parse(json['input'] as String); // May be changed with some options

var unsupported_options = [];

// If options are in the JSON, use them...
NumberFormatOptions options;
var jsonOptions = json['options'] as Map<String, dynamic>;
if (json.containsKey('options')) {
options = fromJson(jsonOptions);
} else {
try {
options = decimalPatternToOptions(pattern, rounding);
} catch (error) {
// Some error - to return this message
return jsonEncode({
'error': "Can't convert pattern",
'label': label,
});
}
}
// Default maximumFractionDigits and rounding modes are set in test generation
final roundingMode = options.roundingMode;

// Check each option for implementation.
// Handle percent - input value is the basis of the actual percent
// expected, e.g., input='0.25' should be interpreted '0.25%'
if (options.style is PercentStyle) {
input = input / 100.0;
}

// Handle scale in the skeleton
var skeleton_terms;
if (skeleton != null) {
skeleton_terms = skeleton.split(" "); // all the components
if (doLogInput) {
print("# SKEL: " + skeleton_terms);
}
final scale_regex = RegExp(r'/scale\/(\d+\.\d*)/');
final match_scale = scale_regex.firstMatch(skeleton);
if (match_scale != null) {
// Get the value and use it
final scale_value = double.parse(match_scale.group(1)!);
input = input * scale_value;
}
}

// Supported options depends on the nodejs version
if (doLogInput) {
print("#NNNN " + node_version);
}
List<String> version_supported_options;
if (node_version.compareTo(first_v3_version) >= 0) {
if (doLogInput) {
print("#V3 !!!! " + node_version);
}
version_supported_options = supported_options_by_version[NodeVersion.v3]!;
} else {
if (doLogInput) {
print("#pre_v3 !!!! " + node_version);
}
version_supported_options =
supported_options_by_version[NodeVersion.preV3]!;
}
if (doLogInput) {
print("#NNNN $version_supported_options");
}
// Check for option items that are not supported
for (var key in jsonOptions.keys) {
if (!version_supported_options.contains(key)) {
unsupported_options.add((key + ":" + jsonOptions[key]));
}
}

// Check for skelection terms that are not supported
for (var skel_index in skeleton_terms) {
final skel_term = skeleton_terms[skel_index];
if (doLogInput) {
print("# SKEL_TERM: " + skel_term);
}
if (unsupported_skeleton_terms.contains(skel_term)) {
unsupported_options.add(skel_term);
if (doLogInput) {
print("# UNSUPPORTED SKEL_TERM: " + skel_term);
}
}
}

if (unsupported_rounding_modes.contains(roundingMode.name)) {
unsupported_options.add(roundingMode.name);
}
if (unsupported_options.length > 0) {
return jsonEncode({
'label': label,
"unsupported": "unsupported_options",
"error_detail": {'unsupported_options': unsupported_options}
});
}

var testLocale = json['locale'];

NumberFormat nf;
Map<String, dynamic> outputLine;
try {
if (testLocale) {
nf = Intl(locale: testLocale).numberFormat(options);
} else {
nf = Intl(locale: Locale(language: 'und')).numberFormat(options);
}

var result = 'NOT IMPLEMENTED';
result = nf.format(input);

// TODO: Catch unsupported units, e.g., furlongs.
// Formatting as JSON
var resultString = result;

outputLine = {
"label": json['label'],
"result": resultString,
"actual_options": options
};
} catch (error) {
if (error.toString().contains('furlong')) {
// This is a special kind of unsupported.
return jsonEncode({
'label': label,
"unsupported": "unsupported_options",
"error_detail": {'unsupported_options': error.toString()}
});
}
// Handle type of the error
outputLine = {
"label": json['label'],
"error": "formatting error",
};
if (error is RangeError) {
outputLine['error_detail'] = error.message;
outputLine['actual_options'] = options;
}
}
return jsonEncode(outputLine);
}

NumberFormatOptions decimalPatternToOptions(String? pattern, String? rounding) {
final numberFormatOptions =
patternsToOptions[pattern] ?? NumberFormatOptions.custom();
if (rounding != null) {
var roundingMode =
RoundingMode.values.firstWhere((mode) => mode.name == rounding);
return numberFormatOptions.copyWith(roundingMode: roundingMode);
} else {
return numberFormatOptions;
}
}

NumberFormatOptions fromJson(Map<String, dynamic> options) {
return NumberFormatOptions.custom();
}
6 changes: 6 additions & 0 deletions executors/dart_web/bin/numberformatExecutor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'numberformat.dart';

void main(List<String> args) {
//just some call to not treeshake the function
testDecimalFormat(args.first, bool.parse(args[2]), args[3]);
}
24 changes: 18 additions & 6 deletions executors/dart_web/out/executor.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@


let collator = require('./collator.js')

let numberformatter = require('./numberformat.js')

const { dartVersion } = require('./version.js')

/**
Expand Down Expand Up @@ -168,12 +171,21 @@ rl.on('line', function (line) {
const test_type = parsedJson["test_type"];
if (test_type == "coll_shift_short") {
outputLine = collator.testCollationShort(parsedJson);
} else {
outputLine = {
'error': 'unknown test type', 'testId': testId,
'unsupported_test': testId
};
}
} else
if (test_type == "decimal_fmt" || test_type == "number_fmt") {
outputLine = numberformatter.testDecimalFormat(parsedJson, doLogInput > 0, process.version);
} else
if (test_type == "display_names") {
outputLine = displaynames.testDisplayNames(parsedJson);
} else
if (test_type == "language_display_name") {
outputLine = langnames.testLangNames(parsedJson);
} else {
outputLine = {
'error': 'unknown test type', 'testId': testId,
'unsupported_test': testId
};
}

if ('error' in outputLine) {
// To get the attention of the driver
Expand Down
10 changes: 10 additions & 0 deletions executors/dart_web/out/numberformat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
var tools = require('./numberformatDart');
// The Collator used for the actual testing.

// !!! TODO: Collation: determine the sensitivity that corresponds
// to the strength.
module.exports = {
testDecimalFormat: function (json) {
return JSON.parse(tools.testDecimalFormat(JSON.stringify(json)));
}
};
2 changes: 1 addition & 1 deletion executors/dart_web/out/version.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
const dartVersion = "0.4.0";
const dartVersion = "0.5.0";
module.exports = { dartVersion };
Loading

0 comments on commit 88b9cdc

Please sign in to comment.