@lango-project/lango-js 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/compiler.mjs +189 -12
- package/src/runtime.mjs +10 -1
- package/src/vendor/bignumber.LICENCE.md +26 -0
- package/src/vendor/bignumber.mjs +3119 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lango-project/lango-js",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"src/*.mjs",
|
|
19
|
+
"src/vendor/**",
|
|
19
20
|
"src/template/**",
|
|
20
21
|
"README.md",
|
|
21
22
|
"LICENSE",
|
package/src/compiler.mjs
CHANGED
|
@@ -231,7 +231,7 @@ function formatLine(line) {
|
|
|
231
231
|
|
|
232
232
|
class Parser {
|
|
233
233
|
constructor(source, name) {
|
|
234
|
-
this.lines = source.split(/\r?\n/).map((line) => stripComment(line).trimEnd());
|
|
234
|
+
this.lines = normalizeKebabIdentifiers(source).split(/\r?\n/).map((line) => stripComment(line).trimEnd());
|
|
235
235
|
this.name = name;
|
|
236
236
|
this.i = 0;
|
|
237
237
|
}
|
|
@@ -518,7 +518,16 @@ class Parser {
|
|
|
518
518
|
`expected ${indent} spaces`,
|
|
519
519
|
);
|
|
520
520
|
const line = this.line();
|
|
521
|
-
const
|
|
521
|
+
const initialText = line.text.slice(indent);
|
|
522
|
+
const text = /^([A-Za-z_]\w*) = \{$/.test(initialText)
|
|
523
|
+
? initialText
|
|
524
|
+
: this.continuedStatement(initialText, line, indent);
|
|
525
|
+
if (text === "else")
|
|
526
|
+
this.fail(
|
|
527
|
+
line,
|
|
528
|
+
"LNG1259",
|
|
529
|
+
"else must be indented 2 spaces more than its if",
|
|
530
|
+
);
|
|
522
531
|
if (text.startsWith("assert ")) {
|
|
523
532
|
body.push({
|
|
524
533
|
type: "assert",
|
|
@@ -540,7 +549,16 @@ class Parser {
|
|
|
540
549
|
this.blank();
|
|
541
550
|
let elseBody = null;
|
|
542
551
|
if (this.peek() === `${" ".repeat(indent + 2)}else`) {
|
|
552
|
+
const elseLine = { text: this.peek(), number: this.i + 1 };
|
|
543
553
|
this.i += 1;
|
|
554
|
+
this.blank();
|
|
555
|
+
const elseStart = this.peek();
|
|
556
|
+
if (!elseStart || spaces(elseStart) !== indent + 6)
|
|
557
|
+
this.fail(
|
|
558
|
+
elseStart ? { text: elseStart, number: this.i + 1 } : elseLine,
|
|
559
|
+
"LNG1260",
|
|
560
|
+
"else body must be indented 4 spaces more than else",
|
|
561
|
+
);
|
|
544
562
|
elseBody = this.block(indent + 6);
|
|
545
563
|
}
|
|
546
564
|
body.push({
|
|
@@ -739,6 +757,52 @@ class Parser {
|
|
|
739
757
|
});
|
|
740
758
|
else if (/^[A-Za-z_]\w*\+\+$/.test(text))
|
|
741
759
|
body.push({ type: "increment", name: text.slice(0, -2) });
|
|
760
|
+
else if (/^(\$?[A-Za-z_]\w*) = new ([A-Za-z_]\w*(?:[./][A-Za-z_]\w*)*)\(\(([^)]*)\)$/.test(text)) {
|
|
761
|
+
const [, name, constructor, parameters] =
|
|
762
|
+
/^(\$?[A-Za-z_]\w*) = new ([A-Za-z_]\w*(?:[./][A-Za-z_]\w*)*)\(\(([^)]*)\)$/.exec(text);
|
|
763
|
+
const closure = {
|
|
764
|
+
type: "closure",
|
|
765
|
+
parameters: this.parameters(parameters, line),
|
|
766
|
+
captures: this.captures(),
|
|
767
|
+
line: line.number,
|
|
768
|
+
body: this.block(indent + 4),
|
|
769
|
+
};
|
|
770
|
+
this.blank();
|
|
771
|
+
if (this.peek() !== `${" ".repeat(indent)})`)
|
|
772
|
+
this.fail(line, "LNG1263", "constructor callback requires a closing parenthesis");
|
|
773
|
+
this.i += 1;
|
|
774
|
+
body.push({
|
|
775
|
+
type: "assign",
|
|
776
|
+
name,
|
|
777
|
+
value: { type: "new", name: constructor, arguments: [closure], line: line.number },
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
else if (/^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.test(text)) {
|
|
781
|
+
const [, receiver, name, argumentsText, parameters] =
|
|
782
|
+
/^(.+)\.([A-Za-z_]\w*)\((.*), \(([^)]*)\)$/.exec(text);
|
|
783
|
+
const closure = {
|
|
784
|
+
type: "closure",
|
|
785
|
+
parameters: this.parameters(parameters, line),
|
|
786
|
+
captures: this.captures(),
|
|
787
|
+
line: line.number,
|
|
788
|
+
body: this.block(indent + 4),
|
|
789
|
+
};
|
|
790
|
+
this.blank();
|
|
791
|
+
if (this.peek() !== `${" ".repeat(indent)})`)
|
|
792
|
+
this.fail(line, "LNG1264", "method callback requires a closing parenthesis");
|
|
793
|
+
this.i += 1;
|
|
794
|
+
body.push({
|
|
795
|
+
type: "expression",
|
|
796
|
+
value: {
|
|
797
|
+
type: "methodCall",
|
|
798
|
+
receiver,
|
|
799
|
+
optional: false,
|
|
800
|
+
name,
|
|
801
|
+
arguments: [...splitTopLevel(argumentsText).map((item) => expr(item, line, this)), closure],
|
|
802
|
+
line: line.number,
|
|
803
|
+
},
|
|
804
|
+
});
|
|
805
|
+
}
|
|
742
806
|
else if (/^(.+)\.([A-Za-z_]\w*)\(\(([^)]*)\)$/.test(text)) {
|
|
743
807
|
const [, first, name, parameters] = /^(.+)\.([A-Za-z_]\w*)\(\(([^)]*)\)$/.exec(text);
|
|
744
808
|
const steps = [];
|
|
@@ -937,6 +1001,17 @@ class Parser {
|
|
|
937
1001
|
xml += `\n${this.lines[this.i++]}`;
|
|
938
1002
|
}
|
|
939
1003
|
}
|
|
1004
|
+
continuedStatement(source, line, indent) {
|
|
1005
|
+
let depth = curlyDelimiterDepth(source);
|
|
1006
|
+
while (depth > 0) {
|
|
1007
|
+
const next = this.peek();
|
|
1008
|
+
if (next === undefined || spaces(next) < indent)
|
|
1009
|
+
this.fail(line, "LNG1262", "unterminated multi-line expression");
|
|
1010
|
+
source += ` ${this.line().text.trim()}`;
|
|
1011
|
+
depth = curlyDelimiterDepth(source);
|
|
1012
|
+
}
|
|
1013
|
+
return source;
|
|
1014
|
+
}
|
|
940
1015
|
captures() {
|
|
941
1016
|
if (!(this.peek() ?? "").trimStart().startsWith("capture ")) return [];
|
|
942
1017
|
const line = this.line();
|
|
@@ -1072,6 +1147,66 @@ function topLevelBinary(text, operators, rightAssociative = false) {
|
|
|
1072
1147
|
return match;
|
|
1073
1148
|
}
|
|
1074
1149
|
|
|
1150
|
+
function curlyDelimiterDepth(text) {
|
|
1151
|
+
let quote = null;
|
|
1152
|
+
let depth = 0;
|
|
1153
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
1154
|
+
const character = text[index];
|
|
1155
|
+
if (quote !== null) {
|
|
1156
|
+
if (character === "\\") { index += 1; continue; }
|
|
1157
|
+
if (character === quote) quote = null;
|
|
1158
|
+
continue;
|
|
1159
|
+
}
|
|
1160
|
+
if (character === "'" || character === '"') { quote = character; continue; }
|
|
1161
|
+
if (character === "{") depth += 1;
|
|
1162
|
+
if (character === "}") depth -= 1;
|
|
1163
|
+
}
|
|
1164
|
+
return depth;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const KEBAB_MARKER = "__lango_dash__";
|
|
1168
|
+
|
|
1169
|
+
function normalizeKebabIdentifiers(source) {
|
|
1170
|
+
let result = "";
|
|
1171
|
+
let quote = null;
|
|
1172
|
+
for (let index = 0; index < source.length; index += 1) {
|
|
1173
|
+
const character = source[index];
|
|
1174
|
+
if (quote !== null) {
|
|
1175
|
+
result += character;
|
|
1176
|
+
if (character === "\\") result += source[++index] ?? "";
|
|
1177
|
+
else if (character === quote) quote = null;
|
|
1178
|
+
continue;
|
|
1179
|
+
}
|
|
1180
|
+
if (character === "'" || character === '"') { quote = character; result += character; continue; }
|
|
1181
|
+
const match = /^[A-Za-z_]\w*(?:-[A-Za-z_]\w*)+/.exec(source.slice(index));
|
|
1182
|
+
if (match) {
|
|
1183
|
+
result += match[0].replaceAll("-", KEBAB_MARKER);
|
|
1184
|
+
index += match[0].length - 1;
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
result += character;
|
|
1188
|
+
}
|
|
1189
|
+
return result;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function kebabName(name) {
|
|
1193
|
+
return name.replaceAll(KEBAB_MARKER, "-");
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
function emitKebabMember(receiver, operator, name) {
|
|
1197
|
+
return name.includes(KEBAB_MARKER)
|
|
1198
|
+
? `${receiver}${operator === "?." ? "?." : ""}[${JSON.stringify(kebabName(name))}]`
|
|
1199
|
+
: `${receiver}${operator}${name}`;
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
function emitKebabPath(value) {
|
|
1203
|
+
return value.replace(/(\?\.|\.)([A-Za-z_]\w*)/g, (all, operator, name) =>
|
|
1204
|
+
name.includes(KEBAB_MARKER)
|
|
1205
|
+
? emitKebabMember("", operator, name)
|
|
1206
|
+
: all,
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1075
1210
|
function splitTopLevel(text, separator = ", ") {
|
|
1076
1211
|
const parts = [];
|
|
1077
1212
|
let start = 0;
|
|
@@ -1200,6 +1335,13 @@ function expr(source, line, parser) {
|
|
|
1200
1335
|
const localize = /^_\((.*)\)$/.exec(text);
|
|
1201
1336
|
if (localize)
|
|
1202
1337
|
return { type: "translate", literal: literal(localize[1], line, parser) };
|
|
1338
|
+
const decimal = /^\$\((.*)\)$/.exec(text);
|
|
1339
|
+
if (decimal) {
|
|
1340
|
+
const arguments_ = decimal[1] === "" ? [] : splitTopLevel(decimal[1]);
|
|
1341
|
+
if (arguments_.length !== 1)
|
|
1342
|
+
parser.fail(line, "LNG1261", "$() requires exactly one value");
|
|
1343
|
+
return { type: "decimal", value: expr(arguments_[0], line, parser) };
|
|
1344
|
+
}
|
|
1203
1345
|
const construct =
|
|
1204
1346
|
/^new ([A-Za-z_]\w*(?:[./][A-Za-z_]\w*)*)\((.*)\)$/.exec(text);
|
|
1205
1347
|
if (construct)
|
|
@@ -1623,7 +1765,8 @@ function validate(program, imports = []) {
|
|
|
1623
1765
|
const hostFunctions = new Set(["fetch"]);
|
|
1624
1766
|
const isDecimalValue = (value) =>
|
|
1625
1767
|
value &&
|
|
1626
|
-
(
|
|
1768
|
+
(value.type === "decimal" ||
|
|
1769
|
+
(value.type === "raw" && (value.value.startsWith("$") || value.value.includes(".$"))) ||
|
|
1627
1770
|
(value.type === "unary" &&
|
|
1628
1771
|
value.operator === "negate" &&
|
|
1629
1772
|
isDecimalValue(value.value)) ||
|
|
@@ -1635,6 +1778,21 @@ function validate(program, imports = []) {
|
|
|
1635
1778
|
((value.left.type === "raw" && value.left.value === "null" && isDecimalValue(value.right)) ||
|
|
1636
1779
|
(value.right.type === "raw" && value.right.value === "null" && isDecimalValue(value.left)) ||
|
|
1637
1780
|
(isDecimalValue(value.left) && isDecimalValue(value.right)))));
|
|
1781
|
+
const decimalParameters = (fn) => {
|
|
1782
|
+
const parameterNames = new Set(fn.parameters.map((parameter) => parameter.name));
|
|
1783
|
+
const accepted = new Set();
|
|
1784
|
+
const visit = (node) => {
|
|
1785
|
+
if (!node || typeof node !== "object") return;
|
|
1786
|
+
if (node.type === "decimal" && node.value?.type === "raw" && parameterNames.has(node.value.value))
|
|
1787
|
+
accepted.add(node.value.value);
|
|
1788
|
+
Object.values(node).forEach((child) => {
|
|
1789
|
+
if (Array.isArray(child)) child.forEach(visit);
|
|
1790
|
+
else visit(child);
|
|
1791
|
+
});
|
|
1792
|
+
};
|
|
1793
|
+
visit(fn.body);
|
|
1794
|
+
return accepted;
|
|
1795
|
+
};
|
|
1638
1796
|
const returns = (body, result = []) => {
|
|
1639
1797
|
for (const node of body) {
|
|
1640
1798
|
if (node.type === "return") result.push(node.value);
|
|
@@ -2057,11 +2215,13 @@ function validate(program, imports = []) {
|
|
|
2057
2215
|
);
|
|
2058
2216
|
if (target) {
|
|
2059
2217
|
validateRequiredArguments(target, value, value.name);
|
|
2218
|
+
const decimalArguments = decimalParameters(target);
|
|
2060
2219
|
value.arguments.forEach((argument, index) => {
|
|
2061
2220
|
const parameter = target.parameters[index];
|
|
2062
2221
|
if (
|
|
2063
2222
|
parameter &&
|
|
2064
2223
|
!parameter.name.startsWith("$") &&
|
|
2224
|
+
!decimalArguments.has(parameter.name) &&
|
|
2065
2225
|
isDecimalValue(argument)
|
|
2066
2226
|
)
|
|
2067
2227
|
throw new LangoError(
|
|
@@ -2543,6 +2703,10 @@ function validate(program, imports = []) {
|
|
|
2543
2703
|
? node
|
|
2544
2704
|
: node.type === "return" && node.value?.type === "closure"
|
|
2545
2705
|
? node.value
|
|
2706
|
+
: node.type === "assign" && node.value?.type === "new" && node.value.arguments.length === 1 && node.value.arguments[0].type === "closure"
|
|
2707
|
+
? node.value.arguments[0]
|
|
2708
|
+
: node.type === "expression" && node.value?.type === "methodCall" && node.value.arguments.at(-1)?.type === "closure"
|
|
2709
|
+
? node.value.arguments.at(-1)
|
|
2546
2710
|
: null;
|
|
2547
2711
|
if (closure) {
|
|
2548
2712
|
const localBindings = new Set([
|
|
@@ -2615,7 +2779,7 @@ function validate(program, imports = []) {
|
|
|
2615
2779
|
classScope,
|
|
2616
2780
|
expiredCatchBindings,
|
|
2617
2781
|
);
|
|
2618
|
-
if (node.type === "closureAssign") available.add(node.name);
|
|
2782
|
+
if (node.type === "closureAssign" || node.type === "assign") available.add(node.name);
|
|
2619
2783
|
continue;
|
|
2620
2784
|
}
|
|
2621
2785
|
if (node.type === "assign" && !node.name.startsWith("@"))
|
|
@@ -3442,6 +3606,7 @@ function collectEmitLocals(body, names = new Set(), nested = false) {
|
|
|
3442
3606
|
}
|
|
3443
3607
|
function emitDecimalExpression(node, flags) {
|
|
3444
3608
|
const operand = (value) => {
|
|
3609
|
+
if (value.type === "decimal") return emitExpr(value, flags);
|
|
3445
3610
|
if (value.type === "binary" && ["+", "-", "*", "/", "%", "**"].includes(value.operator))
|
|
3446
3611
|
return emitDecimalExpression(value, flags);
|
|
3447
3612
|
if (value.type === "raw" && /^\d+(?:\.\d+)?$/.test(value.value)) {
|
|
@@ -3700,6 +3865,7 @@ function emitExpr(node, flags) {
|
|
|
3700
3865
|
value?.type === "raw" &&
|
|
3701
3866
|
(value.value.startsWith("$") || value.value.includes(".$"));
|
|
3702
3867
|
const decimalValue = (value) =>
|
|
3868
|
+
value?.type === "decimal" ||
|
|
3703
3869
|
decimalRaw(value) ||
|
|
3704
3870
|
(value?.type === "binary" && (decimalValue(value.left) || decimalValue(value.right))) ||
|
|
3705
3871
|
(value?.type === "unary" && decimalValue(value.value));
|
|
@@ -3731,9 +3897,13 @@ function emitExpr(node, flags) {
|
|
|
3731
3897
|
: normalize(`this.#${node.value.slice(1)}`)
|
|
3732
3898
|
: flags.className && node.value.startsWith("_")
|
|
3733
3899
|
? normalize(`this.${node.value}`)
|
|
3734
|
-
: (node.value.includes("?.") || /^(?:[A-Za-z_]\w*\.[A-Za-z_]\w*|[A-Za-z_]\w*\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\])$/.test(node.value))
|
|
3735
|
-
? normalize(node.value)
|
|
3736
|
-
: node.value;
|
|
3900
|
+
: (node.value.includes("?.") || node.value.includes(KEBAB_MARKER) || /^(?:[A-Za-z_]\w*\.[A-Za-z_]\w*|[A-Za-z_]\w*\[(?:\d+|[A-Za-z_]\w*|['"][^'"]*['"])\])$/.test(node.value))
|
|
3901
|
+
? normalize(emitKebabPath(node.value))
|
|
3902
|
+
: emitKebabPath(node.value);
|
|
3903
|
+
if (node.type === "decimal") {
|
|
3904
|
+
flags.decimal = true;
|
|
3905
|
+
return `__decimal(${emitExpr(node.value, flags)})`;
|
|
3906
|
+
}
|
|
3737
3907
|
if (node.type === "sql") {
|
|
3738
3908
|
flags.sql = true;
|
|
3739
3909
|
const text = node.parts
|
|
@@ -3758,7 +3928,9 @@ function emitExpr(node, flags) {
|
|
|
3758
3928
|
return `__finite(-${numericLiteral ? emitExpr(node.value, flags) : `__requireNumber(${emitExpr(node.value, flags)})`})`;
|
|
3759
3929
|
}
|
|
3760
3930
|
if (node.type === "new")
|
|
3761
|
-
return `new ${node.name.replaceAll("/", ".")}(${node.arguments.map((item) =>
|
|
3931
|
+
return `new ${node.name.replaceAll("/", ".")}(${node.arguments.map((item) =>
|
|
3932
|
+
item.type === "closure" ? emitClosure(item, 0, flags) : emitExpr(item, flags),
|
|
3933
|
+
).join(", ")})`;
|
|
3762
3934
|
if (node.type === "superMethodCall")
|
|
3763
3935
|
return `super.${node.name}(${node.arguments.map((item) => emitExpr(item, flags)).join(", ")})`;
|
|
3764
3936
|
if (node.type === "index")
|
|
@@ -3767,7 +3939,7 @@ function emitExpr(node, flags) {
|
|
|
3767
3939
|
);
|
|
3768
3940
|
if (node.type === "property")
|
|
3769
3941
|
return normalize(
|
|
3770
|
-
|
|
3942
|
+
emitKebabMember(emitExpr(node.receiver, flags), node.optional ? "?." : ".", node.name),
|
|
3771
3943
|
);
|
|
3772
3944
|
if (node.type === "methodCall" || node.type === "namespaceMethodCall") {
|
|
3773
3945
|
const namespace = node.type === "namespaceMethodCall";
|
|
@@ -3793,7 +3965,9 @@ function emitExpr(node, flags) {
|
|
|
3793
3965
|
flags.arrayBindings?.has(node.receiver)
|
|
3794
3966
|
? "push"
|
|
3795
3967
|
: node.name;
|
|
3796
|
-
const arguments_ = node.arguments.map((item) =>
|
|
3968
|
+
const arguments_ = node.arguments.map((item) =>
|
|
3969
|
+
item.type === "closure" ? emitClosure(item, 0, flags) : emitExpr(item, flags),
|
|
3970
|
+
).join(", ");
|
|
3797
3971
|
const helperCall = (helper) => {
|
|
3798
3972
|
const call = `${helper}(${receiver}${arguments_ ? `, ${arguments_}` : ""})`;
|
|
3799
3973
|
if (!node.optional) return call;
|
|
@@ -3824,7 +3998,9 @@ function emitExpr(node, flags) {
|
|
|
3824
3998
|
flags.insertAt = true;
|
|
3825
3999
|
return helperCall("__insertAt");
|
|
3826
4000
|
}
|
|
3827
|
-
const call =
|
|
4001
|
+
const call = visibility === false
|
|
4002
|
+
? `${receiver}.#${method}(${arguments_})`
|
|
4003
|
+
: emitKebabMember(receiver, node.optional ? "?." : ".", method) + `(${arguments_})`;
|
|
3828
4004
|
if (node.optional) return normalize(call);
|
|
3829
4005
|
if (
|
|
3830
4006
|
visibility === undefined &&
|
|
@@ -3840,7 +4016,8 @@ function emitExpr(node, flags) {
|
|
|
3840
4016
|
if (node.type === "object")
|
|
3841
4017
|
return `{ ${node.fields.map((item) => {
|
|
3842
4018
|
const value = emitExpr(item.value, flags);
|
|
3843
|
-
if (!item.key.startsWith("$"))
|
|
4019
|
+
if (!item.key.startsWith("$"))
|
|
4020
|
+
return `${item.key.includes(KEBAB_MARKER) ? JSON.stringify(kebabName(item.key)) : item.key}: ${value}`;
|
|
3844
4021
|
flags.decimal = true;
|
|
3845
4022
|
return `${JSON.stringify(item.key)}: __decimal(${item.value.type === "raw" ? JSON.stringify(item.value.value) : value})`;
|
|
3846
4023
|
}).join(", ")} }`;
|
package/src/runtime.mjs
CHANGED
|
@@ -31,7 +31,16 @@ Decimal.Rounding = Object.freeze(${JSON.stringify(Decimal.Rounding)});
|
|
|
31
31
|
export { Decimal };
|
|
32
32
|
|
|
33
33
|
export function decimal(value) {
|
|
34
|
-
|
|
34
|
+
if (value instanceof Decimal) return value;
|
|
35
|
+
let result;
|
|
36
|
+
try {
|
|
37
|
+
result = new Decimal(value);
|
|
38
|
+
} catch {
|
|
39
|
+
throw new TypeError("Decimal value must be finite");
|
|
40
|
+
}
|
|
41
|
+
if (!result.isFinite())
|
|
42
|
+
throw new TypeError("Decimal value must be finite");
|
|
43
|
+
return result;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
export function isDecimal(value) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
=====================
|
|
3
|
+
|
|
4
|
+
Copyright © `<2026>` `Michael Mclaughlin`
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person
|
|
7
|
+
obtaining a copy of this software and associated documentation
|
|
8
|
+
files (the “Software”), to deal in the Software without
|
|
9
|
+
restriction, including without limitation the rights to use,
|
|
10
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the
|
|
12
|
+
Software is furnished to do so, subject to the following
|
|
13
|
+
conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be
|
|
16
|
+
included in all copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
19
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
20
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
21
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
22
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
23
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
24
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
25
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
26
|
+
|