mirror of
https://github.com/PostHog/posthog.git
synced 2024-11-24 00:47:50 +01:00
feat(cdp): hog to JS transpiler (#26143)
This commit is contained in:
parent
fc6038118f
commit
0fd91f4743
2
.vscode/launch.json
vendored
2
.vscode/launch.json
vendored
@ -97,7 +97,7 @@
|
|||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"cwd": "${workspaceFolder}",
|
"cwd": "${workspaceFolder}",
|
||||||
"env": {
|
"env": {
|
||||||
"SKIP_ASYNC_MIGRATIONS_SETUP": "0",
|
"SKIP_ASYNC_MIGRATIONS_SETUP": "1",
|
||||||
"DEBUG": "1",
|
"DEBUG": "1",
|
||||||
"BILLING_SERVICE_URL": "https://billing.dev.posthog.dev",
|
"BILLING_SERVICE_URL": "https://billing.dev.posthog.dev",
|
||||||
"SKIP_SERVICE_VERSION_REQUIREMENTS": "1"
|
"SKIP_SERVICE_VERSION_REQUIREMENTS": "1"
|
||||||
|
3
bin/hoge
3
bin/hoge
@ -3,10 +3,13 @@ set -e
|
|||||||
|
|
||||||
if [[ "$@" == *".hog"* ]]; then
|
if [[ "$@" == *".hog"* ]]; then
|
||||||
exec python3 -m posthog.hogql.cli --compile "$@"
|
exec python3 -m posthog.hogql.cli --compile "$@"
|
||||||
|
elif [[ "$@" == *".js"* ]]; then
|
||||||
|
exec python3 -m posthog.hogql.cli --compile "$@"
|
||||||
else
|
else
|
||||||
echo "$0 - the Hog compilër! 🦔+🕶️= Hoge"
|
echo "$0 - the Hog compilër! 🦔+🕶️= Hoge"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Usage: bin/hoge <file.hog> [output.hoge] compile .hog into .hoge"
|
echo "Usage: bin/hoge <file.hog> [output.hoge] compile .hog into .hoge"
|
||||||
|
echo " bin/hoge <file.hog> <output.js> compile .hog into .js"
|
||||||
echo " bin/hog <file.hog> run .hog source code"
|
echo " bin/hog <file.hog> run .hog source code"
|
||||||
echo " bin/hog <file.hoge> run compiled .hoge bytecode"
|
echo " bin/hog <file.hoge> run compiled .hoge bytecode"
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
|
|
||||||
from posthog.client import sync_execute
|
from posthog.client import sync_execute
|
||||||
from posthog.hogql.bytecode import create_bytecode
|
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||||
from posthog.hogql.hogql import HogQLContext
|
from posthog.hogql.hogql import HogQLContext
|
||||||
from posthog.hogql.property import action_to_expr
|
from posthog.hogql.property import action_to_expr
|
||||||
from posthog.models.action import Action
|
from posthog.models.action import Action
|
||||||
|
@ -29,4 +29,6 @@
|
|||||||
33, 2, 33, 3, 43, 3, 33, 4, 2, "indexOf", 2, 2, "print", 1, 35, 52, "lambda", 1, 0, 6, 33, 2, 36, 0, 13, 38, 53, 0, 33,
|
33, 2, 33, 3, 43, 3, 33, 4, 2, "indexOf", 2, 2, "print", 1, 35, 52, "lambda", 1, 0, 6, 33, 2, 36, 0, 13, 38, 53, 0, 33,
|
||||||
1, 33, 2, 33, 3, 33, 4, 33, 5, 43, 5, 2, "arrayCount", 2, 2, "print", 1, 35, 32, "------", 2, "print", 1, 35, 33, 1, 33,
|
1, 33, 2, 33, 3, 33, 4, 33, 5, 43, 5, 2, "arrayCount", 2, 2, "print", 1, 35, 32, "------", 2, "print", 1, 35, 33, 1, 33,
|
||||||
2, 33, 3, 43, 3, 36, 3, 33, 1, 45, 36, 3, 33, 2, 45, 36, 3, 33, 3, 45, 36, 3, 33, 4, 45, 2, "print", 4, 35, 36, 3, 33,
|
2, 33, 3, 43, 3, 36, 3, 33, 1, 45, 36, 3, 33, 2, 45, 36, 3, 33, 3, 45, 36, 3, 33, 4, 45, 2, "print", 4, 35, 36, 3, 33,
|
||||||
-1, 45, 36, 3, 33, -2, 45, 36, 3, 33, -3, 45, 36, 3, 33, -4, 45, 2, "print", 4, 35, 35, 35, 35, 35]
|
-1, 45, 36, 3, 33, -2, 45, 36, 3, 33, -3, 45, 36, 3, 33, -4, 45, 2, "print", 4, 35, 32, "------", 2, "print", 1, 35, 32,
|
||||||
|
"a", 32, "b", 32, "c", 43, 3, 32, "a", 21, 2, "print", 1, 35, 32, "a", 32, "b", 32, "c", 43, 3, 32, "d", 21, 2, "print",
|
||||||
|
1, 35, 43, 0, 32, "a", 21, 2, "print", 1, 35, 35, 35, 35, 35]
|
||||||
|
144
hogvm/__tests__/__snapshots__/arrays.js
Normal file
144
hogvm/__tests__/__snapshots__/arrays.js
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function indexOf (arrOrString, elem) { if (Array.isArray(arrOrString)) { return arrOrString.indexOf(elem) + 1 } else { return 0 } }
|
||||||
|
function has (arr, elem) { if (!Array.isArray(arr) || arr.length === 0) { return false } return arr.includes(elem) }
|
||||||
|
function arrayStringConcat (arr, separator = '') { if (!Array.isArray(arr)) { return '' } return arr.join(separator) }
|
||||||
|
function arraySort (arr) { if (!Array.isArray(arr)) { return [] } return [...arr].sort() }
|
||||||
|
function arrayReverseSort (arr) { if (!Array.isArray(arr)) { return [] } return [...arr].sort().reverse() }
|
||||||
|
function arrayReverse (arr) { if (!Array.isArray(arr)) { return [] } return [...arr].reverse() }
|
||||||
|
function arrayPushFront (arr, item) { if (!Array.isArray(arr)) { return [item] } return [item, ...arr] }
|
||||||
|
function arrayPushBack (arr, item) { if (!Array.isArray(arr)) { return [item] } return [...arr, item] }
|
||||||
|
function arrayPopFront (arr) { if (!Array.isArray(arr)) { return [] } return arr.slice(1) }
|
||||||
|
function arrayPopBack (arr) { if (!Array.isArray(arr)) { return [] } return arr.slice(0, arr.length - 1) }
|
||||||
|
function arrayCount (func, arr) { let count = 0; for (let i = 0; i < arr.length; i++) { if (func(arr[i])) { count = count + 1 } } return count }
|
||||||
|
function __setProperty(objectOrArray, key, value) {
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
if (key > 0) {
|
||||||
|
objectOrArray[key - 1] = value
|
||||||
|
} else {
|
||||||
|
objectOrArray[objectOrArray.length + key] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
objectOrArray[key] = value
|
||||||
|
}
|
||||||
|
return objectOrArray
|
||||||
|
}
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print([]);
|
||||||
|
print([1, 2, 3]);
|
||||||
|
print([1, "2", 3]);
|
||||||
|
print([1, [2, 3], 4]);
|
||||||
|
print([1, [2, [3, 4]], 5]);
|
||||||
|
let a = [1, 2, 3];
|
||||||
|
print(__getProperty(a, 2, false));
|
||||||
|
print(__getProperty(a, 2, true));
|
||||||
|
print(__getProperty(a, 2, true));
|
||||||
|
print(__getProperty(a, 7, true));
|
||||||
|
print(__getProperty(a, 7, true));
|
||||||
|
print(__getProperty([1, 2, 3], 2, false));
|
||||||
|
print(__getProperty(__getProperty(__getProperty([1, [2, [3, 4]], 5], 2, false), 2, false), 2, false));
|
||||||
|
print(__getProperty(__getProperty(__getProperty([1, [2, [3, 4]], 5], 2, true), 2, true), 2, true));
|
||||||
|
print(__getProperty(__getProperty(__getProperty([1, [2, [3, 4]], 5], 2, true), 2, true), 2, true));
|
||||||
|
print(__getProperty(__getProperty(__getProperty([1, [2, [3, 4]], 5], 7, true), 4, true), 2, true));
|
||||||
|
print(__getProperty(__getProperty(__getProperty([1, [2, [3, 4]], 5], 7, true), 4, true), 2, true));
|
||||||
|
print((__getProperty(__getProperty(__getProperty([1, [2, [3, 4]], 5], 2, false), 2, false), 2, false) + 1));
|
||||||
|
print(__getProperty(__getProperty(__getProperty([1, [2, [3, 4]], 5], 2, false), 2, false), 2, false));
|
||||||
|
print("------");
|
||||||
|
let b = [1, 2, [1, 2, 3]];
|
||||||
|
__setProperty(b, 2, 4);
|
||||||
|
print(__getProperty(b, 1, false));
|
||||||
|
print(__getProperty(b, 2, false));
|
||||||
|
print(__getProperty(b, 3, false));
|
||||||
|
__setProperty(__getProperty(b, 3, false), 3, 8);
|
||||||
|
print(b);
|
||||||
|
print("------");
|
||||||
|
print(arrayPushBack([1, 2, 3], 4));
|
||||||
|
print(arrayPushFront([1, 2, 3], 0));
|
||||||
|
print(arrayPopBack([1, 2, 3]));
|
||||||
|
print(arrayPopFront([1, 2, 3]));
|
||||||
|
print(arraySort([3, 2, 1]));
|
||||||
|
print(arrayReverse([1, 2, 3]));
|
||||||
|
print(arrayReverseSort([3, 2, 1]));
|
||||||
|
print(arrayStringConcat([1, 2, 3], ","));
|
||||||
|
print("-----");
|
||||||
|
let arr = [1, 2, 3, 4];
|
||||||
|
print(arr);
|
||||||
|
arrayPushBack(arr, 5);
|
||||||
|
print(arr);
|
||||||
|
arrayPushFront(arr, 0);
|
||||||
|
print(arr);
|
||||||
|
arrayPopBack(arr);
|
||||||
|
print(arr);
|
||||||
|
arrayPopFront(arr);
|
||||||
|
print(arr);
|
||||||
|
arraySort(arr);
|
||||||
|
print(arr);
|
||||||
|
arrayReverse(arr);
|
||||||
|
print(arr);
|
||||||
|
arrayReverseSort(arr);
|
||||||
|
print(arr);
|
||||||
|
print("------");
|
||||||
|
print(has(arr, 0));
|
||||||
|
print(has(arr, 2));
|
||||||
|
print(has(arr, "banana"));
|
||||||
|
print(has("banananas", "banana"));
|
||||||
|
print(has("banananas", "foo"));
|
||||||
|
print(has(["1", "2"], "1"));
|
||||||
|
print(indexOf([1, 2, 3], 1));
|
||||||
|
print(indexOf([1, 2, 3], 2));
|
||||||
|
print(indexOf([1, 2, 3], 3));
|
||||||
|
print(indexOf([1, 2, 3], 4));
|
||||||
|
print(arrayCount(__lambda((x) => (x > 2)), [1, 2, 3, 4, 5]));
|
||||||
|
print("------");
|
||||||
|
let c = [1, 2, 3];
|
||||||
|
print(__getProperty(c, 1, false), __getProperty(c, 2, false), __getProperty(c, 3, false), __getProperty(c, 4, false));
|
||||||
|
print(__getProperty(c, -1, false), __getProperty(c, -2, false), __getProperty(c, -3, false), __getProperty(c, -4, false));
|
||||||
|
print("------");
|
||||||
|
print((["a", "b", "c"].includes("a")));
|
||||||
|
print((["a", "b", "c"].includes("d")));
|
||||||
|
print(([].includes("a")));
|
@ -54,3 +54,7 @@ true
|
|||||||
------
|
------
|
||||||
1 2 3 null
|
1 2 3 null
|
||||||
3 2 1 null
|
3 2 1 null
|
||||||
|
------
|
||||||
|
true
|
||||||
|
false
|
||||||
|
false
|
||||||
|
66
hogvm/__tests__/__snapshots__/bytecodeStl.js
Normal file
66
hogvm/__tests__/__snapshots__/bytecodeStl.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function like (str, pattern) { return __like(str, pattern, false) }
|
||||||
|
function arrayMap (func, arr) { let result = []; for (let i = 0; i < arr.length; i++) { result = arrayPushBack(result, func(arr[i])) } return result }
|
||||||
|
function arrayFilter (func, arr) { let result = []; for (let i = 0; i < arr.length; i++) { if (func(arr[i])) { result = arrayPushBack(result, arr[i]) } } return result}
|
||||||
|
function arrayPushBack (arr, item) { if (!Array.isArray(arr)) { return [item] } return [...arr, item] }
|
||||||
|
function arrayExists (func, arr) { for (let i = 0; i < arr.length; i++) { if (func(arr[i])) { return true } } return false }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __like(str, pattern, caseInsensitive = false) {
|
||||||
|
if (caseInsensitive) {
|
||||||
|
str = str.toLowerCase()
|
||||||
|
pattern = pattern.toLowerCase()
|
||||||
|
}
|
||||||
|
pattern = String(pattern)
|
||||||
|
.replaceAll(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||||
|
.replaceAll('%', '.*')
|
||||||
|
.replaceAll('_', '.')
|
||||||
|
return new RegExp(pattern).test(str)
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("--- arrayMap ----");
|
||||||
|
print(arrayMap(__lambda((x) => (x * 2)), [1, 2, 3]));
|
||||||
|
print("--- arrayExists ----");
|
||||||
|
print(arrayExists(__lambda((x) => like(x, "%nana%")), ["apple", "banana", "cherry"]));
|
||||||
|
print(arrayExists(__lambda((x) => like(x, "%boom%")), ["apple", "banana", "cherry"]));
|
||||||
|
print(arrayExists(__lambda((x) => like(x, "%boom%")), []));
|
||||||
|
print("--- arrayFilter ----");
|
||||||
|
print(arrayFilter(__lambda((x) => like(x, "%nana%")), ["apple", "banana", "cherry"]));
|
||||||
|
print(arrayFilter(__lambda((x) => like(x, "%e%")), ["apple", "banana", "cherry"]));
|
||||||
|
print(arrayFilter(__lambda((x) => like(x, "%boom%")), []));
|
134
hogvm/__tests__/__snapshots__/catch.js
Normal file
134
hogvm/__tests__/__snapshots__/catch.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function NotImplementedError (message, payload) { return __newHogError('NotImplementedError', message, payload) }
|
||||||
|
function HogError (type, message, payload) { return __newHogError(type, message, payload) }
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
function FishError(message) {
|
||||||
|
return HogError("FishError", message);
|
||||||
|
}
|
||||||
|
function FoodError(message) {
|
||||||
|
return HogError("FoodError", message);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
throw FishError("You forgot to feed your fish");
|
||||||
|
} catch (__error) { if (__error.type === "FoodError") { let e = __error;
|
||||||
|
print(concat("Problem with your food: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else if (__error.type === "FishError") { let e = __error;
|
||||||
|
print(concat("Problem with your fish: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else { throw __error; }}
|
||||||
|
try {
|
||||||
|
throw FoodError("Your fish are hungry");
|
||||||
|
} catch (__error) { if (__error.type === "FoodError") { let e = __error;
|
||||||
|
print(concat("Problem with your food: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else if (__error.type === "FishError") { let e = __error;
|
||||||
|
print(concat("Problem with your fish: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else { throw __error; }}
|
||||||
|
try {
|
||||||
|
throw NotImplementedError("Your fish are hungry");
|
||||||
|
} catch (__error) { if (__error.type === "FoodError") { let e = __error;
|
||||||
|
print(concat("Problem with your food: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else if (true) { let e = __error;
|
||||||
|
print(concat("Unknown problem: ", e));
|
||||||
|
}
|
||||||
|
}
|
@ -6,10 +6,10 @@
|
|||||||
"You forgot to feed your fish", 2, "HogError", 2, 49, 51, 39, 32, 36, 0, 32, "type", 45, 32, "FoodError", 36, 1, 11, 40,
|
"You forgot to feed your fish", 2, "HogError", 2, 49, 51, 39, 32, 36, 0, 32, "type", 45, 32, "FoodError", 36, 1, 11, 40,
|
||||||
16, 32, "Problem with your food: ", 36, 0, 32, "message", 45, 2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35,
|
16, 32, "Problem with your food: ", 36, 0, 32, "message", 45, 2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35,
|
||||||
51, 39, 55, 36, 0, 32, "type", 45, 32, "FishError", 36, 1, 11, 40, 16, 32, "FishError: ", 36, 0, 32, "message", 45, 2,
|
51, 39, 55, 36, 0, 32, "type", 45, 32, "FishError", 36, 1, 11, 40, 16, 32, "FishError: ", 36, 0, 32, "message", 45, 2,
|
||||||
"concat", 2, 2, "print", 1, 35, 39, 25, 32, "Error of type ", 36, 0, 32, "name", 45, 32, ": ", 36, 0, 32, "message", 45,
|
"concat", 2, 2, "print", 1, 35, 39, 25, 32, "Error of type ", 36, 0, 32, "type", 45, 32, ": ", 36, 0, 32, "message", 45,
|
||||||
2, "concat", 4, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35, 50, 49, 50, 12, 32, "FishError", 32,
|
2, "concat", 4, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35, 50, 49, 50, 12, 32, "FishError", 32,
|
||||||
"You forgot to feed your fish", 2, "HogError", 2, 49, 51, 39, 32, 36, 0, 32, "type", 45, 32, "FoodError", 36, 1, 11, 40,
|
"You forgot to feed your fish", 2, "HogError", 2, 49, 51, 39, 32, 36, 0, 32, "type", 45, 32, "FoodError", 36, 1, 11, 40,
|
||||||
16, 32, "Problem with your food: ", 36, 0, 32, "message", 45, 2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35,
|
16, 32, "Problem with your food: ", 36, 0, 32, "message", 45, 2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35,
|
||||||
51, 39, 55, 36, 0, 32, "type", 45, 32, "Error of type ", 36, 0, 32, "name", 45, 32, ": ", 36, 0, 32, "message", 45, 2,
|
51, 39, 55, 36, 0, 32, "type", 45, 32, "Error of type ", 36, 0, 32, "type", 45, 32, ": ", 36, 0, 32, "message", 45, 2,
|
||||||
"concat", 4, 2, "print", 1, 35, 39, 25, 32, "FishError", 36, 1, 11, 40, 16, 32, "FishError: ", 36, 0, 32, "message", 45,
|
"concat", 4, 2, "print", 1, 35, 39, 25, 32, "FishError", 36, 1, 11, 40, 16, 32, "FishError: ", 36, 0, 32, "message", 45,
|
||||||
2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35]
|
2, "concat", 2, 2, "print", 1, 35, 39, 2, 35, 49, 35, 35]
|
||||||
|
142
hogvm/__tests__/__snapshots__/catch2.js
Normal file
142
hogvm/__tests__/__snapshots__/catch2.js
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function HogError (type, message, payload) { return __newHogError(type, message, payload) }
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
throw HogError("FishError", "You forgot to feed your fish");
|
||||||
|
} catch (__error) { if (__error.type === "FoodError") { let e = __error;
|
||||||
|
print(concat("Problem with your food: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else { throw __error; }}
|
||||||
|
} catch (__error) { if (__error.type === "FishError") { let e = __error;
|
||||||
|
print(concat("FishError: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else if (true) { let e = __error;
|
||||||
|
print(concat("Error: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
throw HogError("FunkyError", "You forgot to feed your fish");
|
||||||
|
} catch (__error) { if (__error.type === "FoodError") { let e = __error;
|
||||||
|
print(concat("Problem with your food: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else { throw __error; }}
|
||||||
|
} catch (__error) { if (__error.type === "FishError") { let e = __error;
|
||||||
|
print(concat("FishError: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else if (true) { let e = __error;
|
||||||
|
print(concat("Error of type ", __getProperty(e, "type", true), ": ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
throw HogError("FishError", "You forgot to feed your fish");
|
||||||
|
} catch (__error) { if (__error.type === "FoodError") { let e = __error;
|
||||||
|
print(concat("Problem with your food: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else { throw __error; }}
|
||||||
|
} catch (__error) { if (true) { let e = __error;
|
||||||
|
print(concat("Error of type ", __getProperty(e, "type", true), ": ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
else if (__error.type === "FishError") { let e = __error;
|
||||||
|
print(concat("FishError: ", __getProperty(e, "message", true)));
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,3 @@
|
|||||||
FishError: You forgot to feed your fish
|
FishError: You forgot to feed your fish
|
||||||
Error of type : You forgot to feed your fish
|
Error of type FunkyError: You forgot to feed your fish
|
||||||
Error of type : You forgot to feed your fish
|
Error of type FishError: You forgot to feed your fish
|
||||||
|
49
hogvm/__tests__/__snapshots__/crypto.js
Normal file
49
hogvm/__tests__/__snapshots__/crypto.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
function sha256HmacChainHex (data, options) { return 'sha256HmacChainHex not implemented' }
|
||||||
|
function sha256Hex (str, options) { return 'SHA256 is not implemented' }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function md5Hex(string) { return 'MD5 is not implemented' }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
let string = "this is a secure string";
|
||||||
|
print("string:", string);
|
||||||
|
print("md5Hex(string):", md5Hex(string));
|
||||||
|
print("sha256Hex(string):", sha256Hex(string));
|
||||||
|
let data = ["1", "string", "more", "keys"];
|
||||||
|
print("data:", data);
|
||||||
|
print("sha256HmacChainHex(data):", sha256HmacChainHex(data));
|
171
hogvm/__tests__/__snapshots__/date.js
Normal file
171
hogvm/__tests__/__snapshots__/date.js
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
function toUnixTimestampMilli (input, zone) { return __toUnixTimestampMilli(input, zone) }
|
||||||
|
function toUnixTimestamp (input, zone) { return __toUnixTimestamp(input, zone) }
|
||||||
|
function toTimeZone (input, zone) { return __toTimeZone(input, zone) }
|
||||||
|
function toString (value) { return __STLToString(value) }
|
||||||
|
function toInt(value) {
|
||||||
|
if (__isHogDateTime(value)) { return Math.floor(value.dt); }
|
||||||
|
else if (__isHogDate(value)) { const date = new Date(Date.UTC(value.year, value.month - 1, value.day)); const epoch = new Date(Date.UTC(1970, 0, 1)); const diffInDays = Math.floor((date - epoch) / (1000 * 60 * 60 * 24)); return diffInDays; }
|
||||||
|
return !isNaN(parseInt(value)) ? parseInt(value) : null; }
|
||||||
|
function toFloat(value) {
|
||||||
|
if (__isHogDateTime(value)) { return value.dt; }
|
||||||
|
else if (__isHogDate(value)) { const date = new Date(Date.UTC(value.year, value.month - 1, value.day)); const epoch = new Date(Date.UTC(1970, 0, 1)); const diffInDays = (date - epoch) / (1000 * 60 * 60 * 24); return diffInDays; }
|
||||||
|
return !isNaN(parseFloat(value)) ? parseFloat(value) : null; }
|
||||||
|
function toDateTime (input, zone) { return __toDateTime(input, zone) }
|
||||||
|
function toDate (input) { return __toDate(input) }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function fromUnixTimestampMilli (input) { return __fromUnixTimestampMilli(input) }
|
||||||
|
function fromUnixTimestamp (input) { return __fromUnixTimestamp(input) }
|
||||||
|
function __toUnixTimestampMilli(input, zone) { return __toUnixTimestamp(input, zone) * 1000 }
|
||||||
|
function __toUnixTimestamp(input, zone) {
|
||||||
|
if (__isHogDateTime(input)) { return input.dt; }
|
||||||
|
if (__isHogDate(input)) { return __toHogDateTime(input).dt; }
|
||||||
|
const date = new Date(input);
|
||||||
|
if (isNaN(date.getTime())) { throw new Error('Invalid date input'); }
|
||||||
|
return Math.floor(date.getTime() / 1000);}
|
||||||
|
function __toTimeZone(input, zone) { if (!__isHogDateTime(input)) { throw new Error('Expected a DateTime') }; return { ...input, zone }}
|
||||||
|
function __toDateTime(input, zone) { let dt;
|
||||||
|
if (typeof input === 'number') { dt = input; }
|
||||||
|
else { const date = new Date(input); if (isNaN(date.getTime())) { throw new Error('Invalid date input'); } dt = date.getTime() / 1000; }
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' }; }
|
||||||
|
function __toDate(input) { let date;
|
||||||
|
if (typeof input === 'number') { date = new Date(input * 1000); } else { date = new Date(input); }
|
||||||
|
if (isNaN(date.getTime())) { throw new Error('Invalid date input'); }
|
||||||
|
return { __hogDate__: true, year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate() }; }
|
||||||
|
function __fromUnixTimestampMilli(input) { return __toHogDateTime(input / 1000) }
|
||||||
|
function __fromUnixTimestamp(input) { return __toHogDateTime(input) }
|
||||||
|
function __toHogDateTime(timestamp, zone) {
|
||||||
|
if (__isHogDate(timestamp)) {
|
||||||
|
const date = new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day));
|
||||||
|
const dt = date.getTime() / 1000;
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' };
|
||||||
|
}
|
||||||
|
return { __hogDateTime__: true, dt: timestamp, zone: zone || 'UTC' }; }
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dt = fromUnixTimestamp(1234334543);
|
||||||
|
print(dt);
|
||||||
|
print(toString(dt));
|
||||||
|
print(toInt(toUnixTimestamp(dt)));
|
||||||
|
print("-");
|
||||||
|
let dt2 = toDate("2024-05-03");
|
||||||
|
print(dt2);
|
||||||
|
print(toString(dt2));
|
||||||
|
print(toInt(toUnixTimestamp(dt2)));
|
||||||
|
print("-");
|
||||||
|
let dt3 = toDateTime("2024-05-03T12:34:56Z");
|
||||||
|
print(dt3);
|
||||||
|
print(toString(dt3));
|
||||||
|
print(toInt(toUnixTimestamp(dt3)));
|
||||||
|
print("------");
|
||||||
|
print(toTimeZone(dt3, "Europe/Brussels"));
|
||||||
|
print(toString(toTimeZone(dt3, "Europe/Brussels")));
|
||||||
|
print("-");
|
||||||
|
print(toTimeZone(dt3, "Europe/Tallinn"));
|
||||||
|
print(toString(toTimeZone(dt3, "Europe/Tallinn")));
|
||||||
|
print("-");
|
||||||
|
print(toTimeZone(dt3, "America/New_York"));
|
||||||
|
print(toString(toTimeZone(dt3, "America/New_York")));
|
||||||
|
print("------");
|
||||||
|
let timestamp = fromUnixTimestamp(1234334543.123);
|
||||||
|
print("timestamp: ", timestamp);
|
||||||
|
print("toString(timestamp): ", toString(timestamp));
|
||||||
|
print("toInt(timestamp): ", toInt(timestamp));
|
||||||
|
print("toDateTime(toInt(timestamp)): ", toDateTime(toInt(timestamp)));
|
||||||
|
print("toInt(toDateTime(toInt(timestamp))): ", toInt(toDateTime(toInt(timestamp))));
|
||||||
|
print("toString(toDateTime(toInt(timestamp))): ", toString(toDateTime(toInt(timestamp))));
|
||||||
|
print("toFloat(timestamp): ", toFloat(timestamp));
|
||||||
|
print("toDateTime(toFloat(timestamp)): ", toDateTime(toFloat(timestamp)));
|
||||||
|
print("toFloat(toDateTime(toFloat(timestamp))): ", toFloat(toDateTime(toFloat(timestamp))));
|
||||||
|
print("toString(toDateTime(toFloat(timestamp))): ", toString(toDateTime(toFloat(timestamp))));
|
||||||
|
print("------");
|
||||||
|
let millisTs = fromUnixTimestampMilli(1234334543123);
|
||||||
|
print("millisTs: ", toString(millisTs));
|
||||||
|
print("toString(millisTs): ", toString(millisTs));
|
||||||
|
print("toInt(millisTs): ", toInt(millisTs));
|
||||||
|
print("toFloat(millisTs): ", toFloat(millisTs));
|
||||||
|
print("toUnixTimestampMilli(millisTs): ", toUnixTimestampMilli(millisTs));
|
||||||
|
print("------");
|
||||||
|
let date = toDate("2024-05-03");
|
||||||
|
print(date);
|
||||||
|
print(toString(date));
|
||||||
|
print(toInt(date));
|
287
hogvm/__tests__/__snapshots__/dateFormat.js
Normal file
287
hogvm/__tests__/__snapshots__/dateFormat.js
Normal file
@ -0,0 +1,287 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function fromUnixTimestamp (input) { return __fromUnixTimestamp(input) }
|
||||||
|
function formatDateTime (input, format, zone) { return __formatDateTime(input, format, zone) }
|
||||||
|
function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }
|
||||||
|
function __fromUnixTimestamp(input) { return __toHogDateTime(input) }
|
||||||
|
function __toHogDateTime(timestamp, zone) {
|
||||||
|
if (__isHogDate(timestamp)) {
|
||||||
|
const date = new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day));
|
||||||
|
const dt = date.getTime() / 1000;
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' };
|
||||||
|
}
|
||||||
|
return { __hogDateTime__: true, dt: timestamp, zone: zone || 'UTC' }; }
|
||||||
|
function __formatDateTime(input, format, zone) {
|
||||||
|
if (!__isHogDateTime(input)) { throw new Error('Expected a DateTime'); }
|
||||||
|
if (!format) { throw new Error('formatDateTime requires at least 2 arguments'); }
|
||||||
|
const timestamp = input.dt * 1000;
|
||||||
|
let date = new Date(timestamp);
|
||||||
|
if (!zone) { zone = 'UTC'; }
|
||||||
|
const padZero = (num, len = 2) => String(num).padStart(len, '0');
|
||||||
|
const padSpace = (num, len = 2) => String(num).padStart(len, ' ');
|
||||||
|
const getDateComponent = (type, options = {}) => {
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', { ...options, timeZone: zone });
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const part = parts.find(p => p.type === type);
|
||||||
|
return part ? part.value : '';
|
||||||
|
};
|
||||||
|
const getNumericComponent = (type, options = {}) => {
|
||||||
|
const value = getDateComponent(type, options);
|
||||||
|
return parseInt(value, 10);
|
||||||
|
};
|
||||||
|
const getWeekNumber = (d) => {
|
||||||
|
const dateInZone = new Date(d.toLocaleString('en-US', { timeZone: zone }));
|
||||||
|
const target = new Date(Date.UTC(dateInZone.getFullYear(), dateInZone.getMonth(), dateInZone.getDate()));
|
||||||
|
const dayNr = (target.getUTCDay() + 6) % 7;
|
||||||
|
target.setUTCDate(target.getUTCDate() - dayNr + 3);
|
||||||
|
const firstThursday = new Date(Date.UTC(target.getUTCFullYear(), 0, 4));
|
||||||
|
const weekNumber = 1 + Math.round(((target - firstThursday) / 86400000 - 3 + ((firstThursday.getUTCDay() + 6) % 7)) / 7);
|
||||||
|
return weekNumber;
|
||||||
|
};
|
||||||
|
const getDayOfYear = (d) => {
|
||||||
|
const startOfYear = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||||
|
const dateInZone = new Date(d.toLocaleString('en-US', { timeZone: zone }));
|
||||||
|
const diff = dateInZone - startOfYear;
|
||||||
|
return Math.floor(diff / 86400000) + 1;
|
||||||
|
};
|
||||||
|
// Token mapping with corrections
|
||||||
|
const tokens = {
|
||||||
|
'%a': () => getDateComponent('weekday', { weekday: 'short' }),
|
||||||
|
'%b': () => getDateComponent('month', { month: 'short' }),
|
||||||
|
'%c': () => padZero(getNumericComponent('month', { month: '2-digit' })),
|
||||||
|
'%C': () => getDateComponent('year', { year: '2-digit' }),
|
||||||
|
'%d': () => padZero(getNumericComponent('day', { day: '2-digit' })),
|
||||||
|
'%D': () => {
|
||||||
|
const month = padZero(getNumericComponent('month', { month: '2-digit' }));
|
||||||
|
const day = padZero(getNumericComponent('day', { day: '2-digit' }));
|
||||||
|
const year = getDateComponent('year', { year: '2-digit' });
|
||||||
|
return `${month}/${day}/${year}`;
|
||||||
|
},
|
||||||
|
'%e': () => padSpace(getNumericComponent('day', { day: 'numeric' })),
|
||||||
|
'%F': () => {
|
||||||
|
const year = getNumericComponent('year', { year: 'numeric' });
|
||||||
|
const month = padZero(getNumericComponent('month', { month: '2-digit' }));
|
||||||
|
const day = padZero(getNumericComponent('day', { day: '2-digit' }));
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
},
|
||||||
|
'%g': () => getDateComponent('year', { year: '2-digit' }),
|
||||||
|
'%G': () => getNumericComponent('year', { year: 'numeric' }),
|
||||||
|
'%h': () => padZero(getNumericComponent('hour', { hour: '2-digit', hour12: true })),
|
||||||
|
'%H': () => padZero(getNumericComponent('hour', { hour: '2-digit', hour12: false })),
|
||||||
|
'%i': () => padZero(getNumericComponent('minute', { minute: '2-digit' })),
|
||||||
|
'%I': () => padZero(getNumericComponent('hour', { hour: '2-digit', hour12: true })),
|
||||||
|
'%j': () => padZero(getDayOfYear(date), 3),
|
||||||
|
'%k': () => padSpace(getNumericComponent('hour', { hour: 'numeric', hour12: false })),
|
||||||
|
'%l': () => padZero(getNumericComponent('hour', { hour: '2-digit', hour12: true })),
|
||||||
|
'%m': () => padZero(getNumericComponent('month', { month: '2-digit' })),
|
||||||
|
'%M': () => getDateComponent('month', { month: 'long' }),
|
||||||
|
'%n': () => '\n',
|
||||||
|
'%p': () => getDateComponent('dayPeriod', { hour: 'numeric', hour12: true }),
|
||||||
|
'%r': () => {
|
||||||
|
const hour = padZero(getNumericComponent('hour', { hour: '2-digit', hour12: true }));
|
||||||
|
const minute = padZero(getNumericComponent('minute', { minute: '2-digit' }));
|
||||||
|
const second = padZero(getNumericComponent('second', { second: '2-digit' }));
|
||||||
|
const period = getDateComponent('dayPeriod', { hour: 'numeric', hour12: true });
|
||||||
|
return `${hour}:${minute} ${period}`;
|
||||||
|
},
|
||||||
|
'%R': () => {
|
||||||
|
const hour = padZero(getNumericComponent('hour', { hour: '2-digit', hour12: false }));
|
||||||
|
const minute = padZero(getNumericComponent('minute', { minute: '2-digit' }));
|
||||||
|
return `${hour}:${minute}`;
|
||||||
|
},
|
||||||
|
'%s': () => padZero(getNumericComponent('second', { second: '2-digit' })),
|
||||||
|
'%S': () => padZero(getNumericComponent('second', { second: '2-digit' })),
|
||||||
|
'%t': () => '\t',
|
||||||
|
'%T': () => {
|
||||||
|
const hour = padZero(getNumericComponent('hour', { hour: '2-digit', hour12: false }));
|
||||||
|
const minute = padZero(getNumericComponent('minute', { minute: '2-digit' }));
|
||||||
|
const second = padZero(getNumericComponent('second', { second: '2-digit' }));
|
||||||
|
return `${hour}:${minute}:${second}`;
|
||||||
|
},
|
||||||
|
'%u': () => {
|
||||||
|
let day = getDateComponent('weekday', { weekday: 'short' });
|
||||||
|
const dayMap = { 'Mon': '1', 'Tue': '2', 'Wed': '3', 'Thu': '4', 'Fri': '5', 'Sat': '6', 'Sun': '7' };
|
||||||
|
return dayMap[day];
|
||||||
|
},
|
||||||
|
'%V': () => padZero(getWeekNumber(date)),
|
||||||
|
'%w': () => {
|
||||||
|
let day = getDateComponent('weekday', { weekday: 'short' });
|
||||||
|
const dayMap = { 'Sun': '0', 'Mon': '1', 'Tue': '2', 'Wed': '3', 'Thu': '4', 'Fri': '5', 'Sat': '6' };
|
||||||
|
return dayMap[day];
|
||||||
|
},
|
||||||
|
'%W': () => getDateComponent('weekday', { weekday: 'long' }),
|
||||||
|
'%y': () => getDateComponent('year', { year: '2-digit' }),
|
||||||
|
'%Y': () => getNumericComponent('year', { year: 'numeric' }),
|
||||||
|
'%z': () => {
|
||||||
|
if (zone === 'UTC') {
|
||||||
|
return '+0000';
|
||||||
|
} else {
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
|
timeZone: zone,
|
||||||
|
timeZoneName: 'shortOffset',
|
||||||
|
});
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const offsetPart = parts.find(part => part.type === 'timeZoneName');
|
||||||
|
if (offsetPart && offsetPart.value) {
|
||||||
|
const offsetValue = offsetPart.value;
|
||||||
|
const match = offsetValue.match(/GMT([+-]\d{1,2})(?::(\d{2}))?/);
|
||||||
|
if (match) {
|
||||||
|
const sign = match[1][0];
|
||||||
|
const hours = padZero(Math.abs(parseInt(match[1], 10)));
|
||||||
|
const minutes = padZero(match[2] ? parseInt(match[2], 10) : 0);
|
||||||
|
return `${sign}${hours}${minutes}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'%%': () => '%',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace tokens in the format string
|
||||||
|
let result = '';
|
||||||
|
let i = 0;
|
||||||
|
while (i < format.length) {
|
||||||
|
if (format[i] === '%') {
|
||||||
|
const token = format.substring(i, i + 2);
|
||||||
|
if (tokens[token]) {
|
||||||
|
result += tokens[token]();
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
// If token not found, include '%' and move to next character
|
||||||
|
result += format[i];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += format[i];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let dt = fromUnixTimestamp(1234377543.123456);
|
||||||
|
print(formatDateTime(dt, "%Y-%m-%d %H:%i:%S"));
|
||||||
|
print(formatDateTime(dt, "%Y-%m-%d %H:%i:%S", "Europe/Brussels"));
|
||||||
|
print(formatDateTime(dt, "%Y-%m-%d %H:%i:%S", "America/New_York"));
|
||||||
|
print(formatDateTime(dt, "%Y%m%dT%H%i%sZ"));
|
||||||
|
print("-----");
|
||||||
|
print(concat("%a: ", formatDateTime(dt, "%a")));
|
||||||
|
print(concat("%b: ", formatDateTime(dt, "%b")));
|
||||||
|
print(concat("%c: ", formatDateTime(dt, "%c")));
|
||||||
|
print(concat("%C: ", formatDateTime(dt, "%C")));
|
||||||
|
print(concat("%d: ", formatDateTime(dt, "%d")));
|
||||||
|
print(concat("%D: ", formatDateTime(dt, "%D")));
|
||||||
|
print(concat("%e: ", formatDateTime(dt, "%e")));
|
||||||
|
print(concat("%F: ", formatDateTime(dt, "%F")));
|
||||||
|
print(concat("%g: ", formatDateTime(dt, "%g")));
|
||||||
|
print(concat("%G: ", formatDateTime(dt, "%G")));
|
||||||
|
print(concat("%h: ", formatDateTime(dt, "%h")));
|
||||||
|
print(concat("%H: ", formatDateTime(dt, "%H")));
|
||||||
|
print(concat("%i: ", formatDateTime(dt, "%i")));
|
||||||
|
print(concat("%I: ", formatDateTime(dt, "%I")));
|
||||||
|
print(concat("%j: ", formatDateTime(dt, "%j")));
|
||||||
|
print(concat("%k: ", formatDateTime(dt, "%k")));
|
||||||
|
print(concat("%l: ", formatDateTime(dt, "%l")));
|
||||||
|
print(concat("%m: ", formatDateTime(dt, "%m")));
|
||||||
|
print(concat("%M: ", formatDateTime(dt, "%M")));
|
||||||
|
print(concat("%n: ", formatDateTime(dt, "%n")));
|
||||||
|
print(concat("%p: ", formatDateTime(dt, "%p")));
|
||||||
|
print(concat("%r: ", formatDateTime(dt, "%r")));
|
||||||
|
print(concat("%R: ", formatDateTime(dt, "%R")));
|
||||||
|
print(concat("%s: ", formatDateTime(dt, "%s")));
|
||||||
|
print(concat("%S: ", formatDateTime(dt, "%S")));
|
||||||
|
print(concat("%t: ", formatDateTime(dt, "%t")));
|
||||||
|
print(concat("%T: ", formatDateTime(dt, "%T")));
|
||||||
|
print(concat("%u: ", formatDateTime(dt, "%u")));
|
||||||
|
print(concat("%V: ", formatDateTime(dt, "%V")));
|
||||||
|
print(concat("%w: ", formatDateTime(dt, "%w")));
|
||||||
|
print(concat("%W: ", formatDateTime(dt, "%W")));
|
||||||
|
print(concat("%y: ", formatDateTime(dt, "%y")));
|
||||||
|
print(concat("%Y: ", formatDateTime(dt, "%Y")));
|
||||||
|
print(concat("%z: ", formatDateTime(dt, "%z")));
|
||||||
|
print(concat("%%: ", formatDateTime(dt, "%%")));
|
||||||
|
print("-----");
|
||||||
|
print(formatDateTime(dt, "one banana"));
|
||||||
|
print(formatDateTime(dt, "%Y no way %m is this %d a %H real %i time %S"));
|
@ -1,6 +1,6 @@
|
|||||||
["_H", 1, 42, 0, 2, "print", 1, 35, 32, "key", 32, "value", 42, 1, 2, "print", 1, 35, 32, "key", 32, "value", 32,
|
["_H", 1, 42, 0, 2, "print", 1, 35, 32, "key", 32, "value", 42, 1, 2, "print", 1, 35, 32, "key", 32, "value", 32,
|
||||||
"other", 32, "thing", 42, 2, 2, "print", 1, 35, 32, "key", 32, "otherKey", 32, "value", 42, 1, 42, 1, 2, "print", 1, 35,
|
"other", 32, "thing", 42, 2, 2, "print", 1, 35, 32, "key", 32, "otherKey", 32, "value", 42, 1, 42, 1, 2, "print", 1, 35,
|
||||||
33, 3, 36, 0, 32, "value", 42, 1, 2, "print", 1, 35, 32, "key", 32, "value", 42, 1, 32, "key", 45, 2, "print", 1, 35,
|
32, "kk", 36, 0, 32, "value", 42, 1, 2, "print", 1, 35, 32, "key", 32, "value", 42, 1, 32, "key", 45, 2, "print", 1, 35,
|
||||||
32, "key", 32, "value", 42, 1, 32, "key", 45, 2, "print", 1, 35, 32, "key", 32, "otherKey", 32, "value", 42, 1, 42, 1,
|
32, "key", 32, "value", 42, 1, 32, "key", 45, 2, "print", 1, 35, 32, "key", 32, "otherKey", 32, "value", 42, 1, 42, 1,
|
||||||
32, "key", 45, 32, "otherKey", 45, 2, "print", 1, 35, 32, "key", 32, "otherKey", 32, "value", 42, 1, 42, 1, 32, "key",
|
32, "key", 45, 32, "otherKey", 45, 2, "print", 1, 35, 32, "key", 32, "otherKey", 32, "value", 42, 1, 42, 1, 32, "key",
|
||||||
45, 32, "otherKey", 45, 2, "print", 1, 35, 35]
|
45, 32, "otherKey", 45, 2, "print", 1, 35, 35]
|
||||||
|
57
hogvm/__tests__/__snapshots__/dicts.js
Normal file
57
hogvm/__tests__/__snapshots__/dicts.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print({});
|
||||||
|
print({"key": "value"});
|
||||||
|
print({"key": "value", "other": "thing"});
|
||||||
|
print({"key": {"otherKey": "value"}});
|
||||||
|
let key = "kk";
|
||||||
|
print({[key]: "value"});
|
||||||
|
print(__getProperty({"key": "value"}, "key", false));
|
||||||
|
print(__getProperty({"key": "value"}, "key", false));
|
||||||
|
print(__getProperty(__getProperty({"key": {"otherKey": "value"}}, "key", false), "otherKey", false));
|
||||||
|
print(__getProperty(__getProperty({"key": {"otherKey": "value"}}, "key", false), "otherKey", false));
|
@ -2,7 +2,7 @@
|
|||||||
{'key': 'value'}
|
{'key': 'value'}
|
||||||
{'key': 'value', 'other': 'thing'}
|
{'key': 'value', 'other': 'thing'}
|
||||||
{'key': {'otherKey': 'value'}}
|
{'key': {'otherKey': 'value'}}
|
||||||
{3: 'value'}
|
{'kk': 'value'}
|
||||||
value
|
value
|
||||||
value
|
value
|
||||||
value
|
value
|
||||||
|
152
hogvm/__tests__/__snapshots__/exceptions.js
Normal file
152
hogvm/__tests__/__snapshots__/exceptions.js
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __x_Error (message, payload) { return __newHogError('Error', message, payload) }
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
print("start");
|
||||||
|
try {
|
||||||
|
print("try");
|
||||||
|
} catch (__error) { if (true) { let e = __error;
|
||||||
|
print(concat(e, " was the exception"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("------------------");
|
||||||
|
print("start");
|
||||||
|
try {
|
||||||
|
print("try");
|
||||||
|
} catch (__error) { if (true) { let e = __error;
|
||||||
|
print("No var for error, but no error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("------------------");
|
||||||
|
try {
|
||||||
|
print("try again");
|
||||||
|
throw __x_Error();
|
||||||
|
} catch (__error) { if (true) { let e = __error;
|
||||||
|
print(concat(e, " was the exception"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("------------------");
|
||||||
|
try {
|
||||||
|
print("try again");
|
||||||
|
throw __x_Error();
|
||||||
|
} catch (__error) { if (true) { let e = __error;
|
||||||
|
print("No var for error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("------------------");
|
||||||
|
function third() {
|
||||||
|
print("Throwing in third");
|
||||||
|
throw __x_Error("Threw in third");
|
||||||
|
}
|
||||||
|
function second() {
|
||||||
|
print("second");
|
||||||
|
third();
|
||||||
|
}
|
||||||
|
function first() {
|
||||||
|
print("first");
|
||||||
|
second();
|
||||||
|
}
|
||||||
|
function base() {
|
||||||
|
print("base");
|
||||||
|
try {
|
||||||
|
first();
|
||||||
|
} catch (__error) { if (true) { let e = __error;
|
||||||
|
print(concat("Caught in base: ", e)); throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
base();
|
||||||
|
} catch (__error) { if (true) { let e = __error;
|
||||||
|
print(concat("Caught in root: ", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("The end");
|
72
hogvm/__tests__/__snapshots__/functionVars.js
Normal file
72
hogvm/__tests__/__snapshots__/functionVars.js
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function base64Encode (str) { return Buffer.from(str).toString('base64') }
|
||||||
|
function base64Decode (str) { return Buffer.from(str, 'base64').toString() }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
function execFunction() {
|
||||||
|
print("execFunction");
|
||||||
|
}
|
||||||
|
function execFunctionNested() {
|
||||||
|
function execFunction() {
|
||||||
|
print("execFunctionNew");
|
||||||
|
}
|
||||||
|
print("execFunctionNested");
|
||||||
|
execFunction();
|
||||||
|
}
|
||||||
|
execFunction();
|
||||||
|
execFunctionNested();
|
||||||
|
execFunction();
|
||||||
|
print("--------");
|
||||||
|
function secondExecFunction() {
|
||||||
|
print("secondExecFunction");
|
||||||
|
}
|
||||||
|
function secondExecFunctionNested() {
|
||||||
|
print("secondExecFunctionNested");
|
||||||
|
secondExecFunction();
|
||||||
|
}
|
||||||
|
secondExecFunction();
|
||||||
|
secondExecFunctionNested();
|
||||||
|
secondExecFunction();
|
||||||
|
print("--------");
|
||||||
|
let decode = __lambda(() => base64Decode);
|
||||||
|
let sixtyFour = base64Encode;
|
||||||
|
print(sixtyFour("http://www.google.com"));
|
||||||
|
print(decode()(sixtyFour("http://www.google.com")));
|
||||||
|
print(decode()(sixtyFour("http://www.google.com")));
|
124
hogvm/__tests__/__snapshots__/functions.js
Normal file
124
hogvm/__tests__/__snapshots__/functions.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function empty (value) {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if (Array.isArray(value)) { return value.length === 0 } else if (value === null) { return true } else if (value instanceof Map) { return value.size === 0 }
|
||||||
|
return Object.keys(value).length === 0
|
||||||
|
} else if (typeof value === 'number' || typeof value === 'boolean') { return false }
|
||||||
|
return !value }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("-- test functions --");
|
||||||
|
function add(a, b) {
|
||||||
|
return (a + b);
|
||||||
|
}
|
||||||
|
print(add);
|
||||||
|
function add2(a, b) {
|
||||||
|
let c = (a + b);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
print(add2);
|
||||||
|
function mult(a, b) {
|
||||||
|
return (a * b);
|
||||||
|
}
|
||||||
|
print(mult);
|
||||||
|
function noArgs() {
|
||||||
|
let url = "basdfasdf";
|
||||||
|
let second = (2 + 3);
|
||||||
|
return second;
|
||||||
|
}
|
||||||
|
print(noArgs);
|
||||||
|
function empty() {
|
||||||
|
|
||||||
|
}
|
||||||
|
function empty2() {
|
||||||
|
|
||||||
|
}
|
||||||
|
function empty3() {
|
||||||
|
|
||||||
|
}
|
||||||
|
function noReturn() {
|
||||||
|
let a = 1;
|
||||||
|
let b = 2;
|
||||||
|
let c = (a + b);
|
||||||
|
}
|
||||||
|
function emptyReturn() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function emptyReturnBeforeOtherStuff() {
|
||||||
|
return null;
|
||||||
|
(2 + 2);
|
||||||
|
}
|
||||||
|
function emptyReturnBeforeOtherStuffNoSemicolon() {
|
||||||
|
return (2 + 2);
|
||||||
|
}
|
||||||
|
function ifThenReturn() {
|
||||||
|
if (false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
print(add(3, 4));
|
||||||
|
print(((add(3, 4) + 100) + add(1, 1)));
|
||||||
|
print((noArgs() ?? -1));
|
||||||
|
print((empty() ?? -1));
|
||||||
|
print((empty2() ?? -1));
|
||||||
|
print((empty3() ?? -1));
|
||||||
|
print((noReturn() ?? -1));
|
||||||
|
print((emptyReturn() ?? -1));
|
||||||
|
print((emptyReturnBeforeOtherStuff() ?? -1));
|
||||||
|
print((emptyReturnBeforeOtherStuffNoSemicolon() ?? -1));
|
||||||
|
print((ifThenReturn() ?? -1));
|
||||||
|
print(mult(((add(3, 4) + 100) + add(2, 1)), 2));
|
||||||
|
print(mult(((add2(3, 4) + 100) + add2(2, 1)), 10));
|
||||||
|
function printArgs(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
|
||||||
|
print(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
||||||
|
}
|
||||||
|
let printArgs2 = __lambda((arg1, arg2, arg3, arg4, arg5, arg6, arg7) => {
|
||||||
|
print(arg1, arg2, arg3, arg4, arg5, arg6, arg7);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
printArgs(1, 2, 3, 4, 5, 6, 7);
|
||||||
|
printArgs2(1, 2, 3, 4, 5, 6, 7);
|
||||||
|
printArgs(1, 2, 3, 4, 5, 6);
|
||||||
|
printArgs2(1, 2, 3, 4, 5, 6);
|
||||||
|
printArgs(1, 2, 3, 4, 5);
|
||||||
|
printArgs2(1, 2, 3, 4, 5);
|
||||||
|
printArgs();
|
||||||
|
printArgs2();
|
69
hogvm/__tests__/__snapshots__/ifElse.js
Normal file
69
hogvm/__tests__/__snapshots__/ifElse.js
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("-- test if else --");
|
||||||
|
{
|
||||||
|
if (true) {
|
||||||
|
print(1);
|
||||||
|
} else {
|
||||||
|
print(2);
|
||||||
|
}
|
||||||
|
if (true) {
|
||||||
|
print(1);
|
||||||
|
} else {
|
||||||
|
print(2);
|
||||||
|
}
|
||||||
|
if (false) {
|
||||||
|
print(1);
|
||||||
|
} else {
|
||||||
|
print(2);
|
||||||
|
}
|
||||||
|
if (true) {
|
||||||
|
print(1);
|
||||||
|
} else {
|
||||||
|
print(2);
|
||||||
|
}
|
||||||
|
let a = true;
|
||||||
|
if (a) {
|
||||||
|
let a = 3;
|
||||||
|
print((a + 2));
|
||||||
|
} else {
|
||||||
|
print(2);
|
||||||
|
}
|
||||||
|
}
|
61
hogvm/__tests__/__snapshots__/ifJump.js
Normal file
61
hogvm/__tests__/__snapshots__/ifJump.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
let props = {};
|
||||||
|
let email = __getProperty(props, "email", true);
|
||||||
|
if ((email == "")) {
|
||||||
|
print("ERROR - Email not found!");
|
||||||
|
print("3");
|
||||||
|
}
|
||||||
|
print("1");
|
||||||
|
if ((email == "")) {
|
||||||
|
print("ERROR - Email not found!");
|
||||||
|
print("3");
|
||||||
|
} else {
|
||||||
|
print("else");
|
||||||
|
}
|
||||||
|
print("1");
|
197
hogvm/__tests__/__snapshots__/json.js
Normal file
197
hogvm/__tests__/__snapshots__/json.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function jsonStringify (value, spacing) {
|
||||||
|
function convert(x, marked) {
|
||||||
|
if (!marked) { marked = new Set() }
|
||||||
|
if (typeof x === 'object' && x !== null) {
|
||||||
|
if (marked.has(x)) { return null }
|
||||||
|
marked.add(x)
|
||||||
|
try {
|
||||||
|
if (x instanceof Map) {
|
||||||
|
const obj = {}
|
||||||
|
x.forEach((value, key) => { obj[convert(key, marked)] = convert(value, marked) })
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if (Array.isArray(x)) { return x.map((v) => convert(v, marked)) }
|
||||||
|
if (__isHogDateTime(x) || __isHogDate(x) || __isHogError(x)) { return x }
|
||||||
|
if (typeof x === 'function') { return `fn<${x.name || 'lambda'}(${x.length})>` }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key], marked) }
|
||||||
|
return obj
|
||||||
|
} finally {
|
||||||
|
marked.delete(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
if (spacing && typeof spacing === 'number' && spacing > 0) {
|
||||||
|
return JSON.stringify(convert(value), null, spacing)
|
||||||
|
}
|
||||||
|
return JSON.stringify(convert(value), (key, val) => typeof val === 'function' ? `fn<${val.name || 'lambda'}(${val.length})>` : val)
|
||||||
|
}
|
||||||
|
function jsonParse (str) {
|
||||||
|
function convert(x) {
|
||||||
|
if (Array.isArray(x)) { return x.map(convert) }
|
||||||
|
else if (typeof x === 'object' && x !== null) {
|
||||||
|
if (x.__hogDateTime__) { return __toHogDateTime(x.dt, x.zone)
|
||||||
|
} else if (x.__hogDate__) { return __toHogDate(x.year, x.month, x.day)
|
||||||
|
} else if (x.__hogError__) { return __newHogError(x.type, x.message, x.payload) }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key]) }; return obj }
|
||||||
|
return x }
|
||||||
|
return convert(JSON.parse(str)) }
|
||||||
|
function isValidJSON (str) { try { JSON.parse(str); return true } catch (e) { return false } }
|
||||||
|
function __toHogDateTime(timestamp, zone) {
|
||||||
|
if (__isHogDate(timestamp)) {
|
||||||
|
const date = new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day));
|
||||||
|
const dt = date.getTime() / 1000;
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' };
|
||||||
|
}
|
||||||
|
return { __hogDateTime__: true, dt: timestamp, zone: zone || 'UTC' }; }
|
||||||
|
function __toHogDate(year, month, day) { return { __hogDate__: true, year: year, month: month, day: day, } }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function JSONLength (obj, ...path) {
|
||||||
|
try { if (typeof obj === 'string') { obj = JSON.parse(obj) } } catch (e) { return 0 }
|
||||||
|
if (typeof obj === 'object' && obj !== null) {
|
||||||
|
const value = __getNestedValue(obj, path, true)
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length
|
||||||
|
} else if (value instanceof Map) {
|
||||||
|
return value.size
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
return Object.keys(value).length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0 }
|
||||||
|
function JSONHas (obj, ...path) {
|
||||||
|
let current = obj
|
||||||
|
for (const key of path) {
|
||||||
|
let currentParsed = current
|
||||||
|
if (typeof current === 'string') { try { currentParsed = JSON.parse(current) } catch (e) { return false } }
|
||||||
|
if (currentParsed instanceof Map) { if (!currentParsed.has(key)) { return false }; current = currentParsed.get(key) }
|
||||||
|
else if (typeof currentParsed === 'object' && currentParsed !== null) {
|
||||||
|
if (typeof key === 'number') {
|
||||||
|
if (Array.isArray(currentParsed)) {
|
||||||
|
if (key < 0) { if (key < -currentParsed.length) { return false }; current = currentParsed[currentParsed.length + key] }
|
||||||
|
else if (key === 0) { return false }
|
||||||
|
else { if (key > currentParsed.length) { return false }; current = currentParsed[key - 1] }
|
||||||
|
} else { return false }
|
||||||
|
} else {
|
||||||
|
if (!(key in currentParsed)) { return false }
|
||||||
|
current = currentParsed[key]
|
||||||
|
}
|
||||||
|
} else { return false }
|
||||||
|
}
|
||||||
|
return true }
|
||||||
|
function JSONExtractBool (obj, ...path) {
|
||||||
|
try {
|
||||||
|
if (typeof obj === 'string') {
|
||||||
|
obj = JSON.parse(obj)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (path.length > 0) {
|
||||||
|
obj = __getNestedValue(obj, path, true)
|
||||||
|
}
|
||||||
|
if (typeof obj === 'boolean') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
function __getNestedValue(obj, path, allowNull = false) {
|
||||||
|
let current = obj
|
||||||
|
for (const key of path) {
|
||||||
|
if (current == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (current instanceof Map) {
|
||||||
|
current = current.get(key)
|
||||||
|
} else if (typeof current === 'object' && current !== null) {
|
||||||
|
current = current[key]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current === null && !allowNull) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
|
||||||
|
print(jsonParse("[1,2,3]"));
|
||||||
|
let event = {"event": "$pageview", "properties": {"$browser": "Chrome", "$os": "Windows"}};
|
||||||
|
let json = jsonStringify(event);
|
||||||
|
print(jsonParse(json));
|
||||||
|
print("-- JSONHas --");
|
||||||
|
print(JSONHas("{\"a\": \"hello\", \"b\": [-100, 200.0, 300]}", "b"));
|
||||||
|
print(JSONHas("{\"a\": \"hello\", \"b\": [-100, 200.0, 300]}", "b", 4));
|
||||||
|
print(JSONHas({"a": "hello", "b": [-100, 200.0, 300]}, "b"));
|
||||||
|
print(JSONHas({"a": "hello", "b": [-100, 200.0, 300]}, "b", 4));
|
||||||
|
print(JSONHas({"a": "hello", "b": [-100, 200.0, 300]}, "b", -2));
|
||||||
|
print(JSONHas({"a": "hello", "b": [-100, 200.0, 300]}, "b", -4));
|
||||||
|
print(JSONHas("[1,2,3]", 0));
|
||||||
|
print(JSONHas("[1,2,[1,2]]", -1, 1));
|
||||||
|
print(JSONHas("[1,2,[1,2]]", -1, -3));
|
||||||
|
print(JSONHas("[1,2,[1,2]]", 1, 1));
|
||||||
|
print("-- isValidJSON --");
|
||||||
|
print(isValidJSON("{\"a\": \"hello\", \"b\": [-100, 200.0, 300]}"));
|
||||||
|
print(isValidJSON("not a json"));
|
||||||
|
print("-- JSONLength --");
|
||||||
|
print(JSONLength("{\"a\": \"hello\", \"b\": [-100, 200.0, 300]}", "b"));
|
||||||
|
print(JSONLength("{\"a\": \"hello\", \"b\": [-100, 200.0, 300]}"));
|
||||||
|
print(JSONLength({"a": "hello", "b": [-100, 200.0, 300]}, "b"));
|
||||||
|
print(JSONLength({"a": "hello", "b": [-100, 200.0, 300]}));
|
||||||
|
print("-- JSONExtractBool --");
|
||||||
|
print(JSONExtractBool("{\"a\": \"hello\", \"b\": true}", "b"));
|
||||||
|
print(JSONExtractBool("{\"a\": \"hello\", \"b\": false}", "b"));
|
||||||
|
print(JSONExtractBool("{\"a\": \"hello\", \"b\": 1}", "b"));
|
||||||
|
print(JSONExtractBool("{\"a\": \"hello\", \"b\": 0}", "b"));
|
||||||
|
print(JSONExtractBool("{\"a\": \"hello\", \"b\": \"true\"}", "b"));
|
||||||
|
print(JSONExtractBool("{\"a\": \"hello\", \"b\": \"false\"}", "b"));
|
||||||
|
print(JSONExtractBool(true));
|
||||||
|
print(JSONExtractBool(false));
|
||||||
|
print(JSONExtractBool(1));
|
||||||
|
print(JSONExtractBool(0));
|
||||||
|
print(JSONExtractBool("true"));
|
||||||
|
print(JSONExtractBool("false"));
|
54
hogvm/__tests__/__snapshots__/keysValues.js
Normal file
54
hogvm/__tests__/__snapshots__/keysValues.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
function values (obj) { if (typeof obj === 'object' && obj !== null) { if (Array.isArray(obj)) { return [...obj] } else if (obj instanceof Map) { return Array.from(obj.values()) } return Object.values(obj) } return [] }
|
||||||
|
function tuple (...args) { const tuple = args.slice(); tuple.__isHogTuple = true; return tuple; }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function keys (obj) { if (typeof obj === 'object' && obj !== null) { if (Array.isArray(obj)) { return Array.from(obj.keys()) } else if (obj instanceof Map) { return Array.from(obj.keys()) } return Object.keys(obj) } return [] }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
let a = [3, 4, 5];
|
||||||
|
let b = tuple(3, 4, 5);
|
||||||
|
let c = {"key": "value", "other": "val"};
|
||||||
|
print(">> A");
|
||||||
|
print(keys(a));
|
||||||
|
print(values(a));
|
||||||
|
print(">> B");
|
||||||
|
print(keys(b));
|
||||||
|
print(values(b));
|
||||||
|
print(">> C");
|
||||||
|
print(keys(c));
|
||||||
|
print(values(c));
|
129
hogvm/__tests__/__snapshots__/lambdas.js
Normal file
129
hogvm/__tests__/__snapshots__/lambdas.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function jsonStringify (value, spacing) {
|
||||||
|
function convert(x, marked) {
|
||||||
|
if (!marked) { marked = new Set() }
|
||||||
|
if (typeof x === 'object' && x !== null) {
|
||||||
|
if (marked.has(x)) { return null }
|
||||||
|
marked.add(x)
|
||||||
|
try {
|
||||||
|
if (x instanceof Map) {
|
||||||
|
const obj = {}
|
||||||
|
x.forEach((value, key) => { obj[convert(key, marked)] = convert(value, marked) })
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if (Array.isArray(x)) { return x.map((v) => convert(v, marked)) }
|
||||||
|
if (__isHogDateTime(x) || __isHogDate(x) || __isHogError(x)) { return x }
|
||||||
|
if (typeof x === 'function') { return `fn<${x.name || 'lambda'}(${x.length})>` }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key], marked) }
|
||||||
|
return obj
|
||||||
|
} finally {
|
||||||
|
marked.delete(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
if (spacing && typeof spacing === 'number' && spacing > 0) {
|
||||||
|
return JSON.stringify(convert(value), null, spacing)
|
||||||
|
}
|
||||||
|
return JSON.stringify(convert(value), (key, val) => typeof val === 'function' ? `fn<${val.name || 'lambda'}(${val.length})>` : val)
|
||||||
|
}
|
||||||
|
function jsonParse (str) {
|
||||||
|
function convert(x) {
|
||||||
|
if (Array.isArray(x)) { return x.map(convert) }
|
||||||
|
else if (typeof x === 'object' && x !== null) {
|
||||||
|
if (x.__hogDateTime__) { return __toHogDateTime(x.dt, x.zone)
|
||||||
|
} else if (x.__hogDate__) { return __toHogDate(x.year, x.month, x.day)
|
||||||
|
} else if (x.__hogError__) { return __newHogError(x.type, x.message, x.payload) }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key]) }; return obj }
|
||||||
|
return x }
|
||||||
|
return convert(JSON.parse(str)) }
|
||||||
|
function __toHogDateTime(timestamp, zone) {
|
||||||
|
if (__isHogDate(timestamp)) {
|
||||||
|
const date = new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day));
|
||||||
|
const dt = date.getTime() / 1000;
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' };
|
||||||
|
}
|
||||||
|
return { __hogDateTime__: true, dt: timestamp, zone: zone || 'UTC' }; }
|
||||||
|
function __toHogDate(year, month, day) { return { __hogDate__: true, year: year, month: month, day: day, } }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
let b = __lambda((x) => (x * 2));
|
||||||
|
print(b);
|
||||||
|
print(b(2));
|
||||||
|
print(b(8));
|
||||||
|
print("--------");
|
||||||
|
let func = __lambda((x) => (x * 2));
|
||||||
|
let arr = [func];
|
||||||
|
print(func(2));
|
||||||
|
print(__getProperty(arr, 1, false)(2));
|
||||||
|
print(__lambda((x) => (x * 2))(2));
|
||||||
|
print("--------");
|
||||||
|
let withArg = __lambda((x) => {
|
||||||
|
print(x);
|
||||||
|
print("moo");
|
||||||
|
print("cow");
|
||||||
|
});
|
||||||
|
withArg(2);
|
||||||
|
print("--------");
|
||||||
|
let noArg = __lambda(() => {
|
||||||
|
print("moo");
|
||||||
|
print("cow");
|
||||||
|
});
|
||||||
|
noArg();
|
||||||
|
print("-------- lambdas do not survive json --------");
|
||||||
|
print(b);
|
||||||
|
print(jsonStringify(b));
|
||||||
|
let c = jsonParse(jsonStringify(b));
|
||||||
|
print(c);
|
108
hogvm/__tests__/__snapshots__/loops.js
Normal file
108
hogvm/__tests__/__snapshots__/loops.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
function values (obj) { if (typeof obj === 'object' && obj !== null) { if (Array.isArray(obj)) { return [...obj] } else if (obj instanceof Map) { return Array.from(obj.values()) } return Object.values(obj) } return [] }
|
||||||
|
function tuple (...args) { const tuple = args.slice(); tuple.__isHogTuple = true; return tuple; }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function keys (obj) { if (typeof obj === 'object' && obj !== null) { if (Array.isArray(obj)) { return Array.from(obj.keys()) } else if (obj instanceof Map) { return Array.from(obj.keys()) } return Object.keys(obj) } return [] }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("-- test while loop --");
|
||||||
|
{
|
||||||
|
let i = 0;
|
||||||
|
while ((i < 3)) {
|
||||||
|
i = (i + 1)
|
||||||
|
print(i);
|
||||||
|
}
|
||||||
|
print(i);
|
||||||
|
}
|
||||||
|
print("-- test for loop --");
|
||||||
|
{
|
||||||
|
for (let i = 0; (i < 3); i = (i + 1)) {
|
||||||
|
print(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("-- test emptier for loop --");
|
||||||
|
{
|
||||||
|
let i = 0;
|
||||||
|
for (; (i < 3); ) {
|
||||||
|
print("woo");
|
||||||
|
i = (i + 1)
|
||||||
|
}
|
||||||
|
print("hoo");
|
||||||
|
}
|
||||||
|
print("-- for in loop with arrays --");
|
||||||
|
{
|
||||||
|
let arr = [1, 2, 3];
|
||||||
|
for (let i of values(arr)) {
|
||||||
|
print(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("-- for in loop with arrays and keys --");
|
||||||
|
{
|
||||||
|
let arr = [1, 2, 3];
|
||||||
|
for (let k of keys(arr)) { let v = arr[k]; {
|
||||||
|
print(k, v);
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
print("-- for in loop with tuples --");
|
||||||
|
{
|
||||||
|
let tup = tuple(1, 2, 3);
|
||||||
|
for (let i of values(tup)) {
|
||||||
|
print(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("-- for in loop with tuples and keys --");
|
||||||
|
{
|
||||||
|
let tup = tuple(1, 2, 3);
|
||||||
|
for (let k of keys(tup)) { let v = tup[k]; {
|
||||||
|
print(k, v);
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
print("-- for in loop with dicts --");
|
||||||
|
{
|
||||||
|
let obj = {"first": "v1", "second": "v2", "third": "v3"};
|
||||||
|
for (let i of values(obj)) {
|
||||||
|
print(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print("-- for in loop with dicts and keys --");
|
||||||
|
{
|
||||||
|
let obj = {"first": "v1", "second": "v2", "third": "v3"};
|
||||||
|
for (let k of keys(obj)) { let v = obj[k]; {
|
||||||
|
print(k, v);
|
||||||
|
} }
|
||||||
|
}
|
125
hogvm/__tests__/__snapshots__/mandelbrot.js
Normal file
125
hogvm/__tests__/__snapshots__/mandelbrot.js
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mandelbrot(re, im, max_iter) {
|
||||||
|
let z_re = 0.0;
|
||||||
|
let z_im = 0.0;
|
||||||
|
let n = 0;
|
||||||
|
while (!!((((z_re * z_re) + (z_im * z_im)) <= 4) && (n < max_iter))) {
|
||||||
|
let temp_re = (((z_re * z_re) - (z_im * z_im)) + re);
|
||||||
|
let temp_im = (((2 * z_re) * z_im) + im);
|
||||||
|
z_re = temp_re
|
||||||
|
z_im = temp_im
|
||||||
|
n = (n + 1)
|
||||||
|
}
|
||||||
|
if ((n == max_iter)) {
|
||||||
|
return " ";
|
||||||
|
} else {
|
||||||
|
return "#";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function main() {
|
||||||
|
let width = 80;
|
||||||
|
let height = 24;
|
||||||
|
let xmin = -2.0;
|
||||||
|
let xmax = 1.0;
|
||||||
|
let ymin = -1.0;
|
||||||
|
let ymax = 1.0;
|
||||||
|
let max_iter = 30;
|
||||||
|
let y = 0;
|
||||||
|
while ((y < height)) {
|
||||||
|
let row = "";
|
||||||
|
let x = 0;
|
||||||
|
while ((x < width)) {
|
||||||
|
let re = (((x / width) * (xmax - xmin)) + xmin);
|
||||||
|
let im = (((y / height) * (ymax - ymin)) + ymin);
|
||||||
|
let letter = mandelbrot(re, im, max_iter);
|
||||||
|
row = concat(row, letter)
|
||||||
|
x = (x + 1)
|
||||||
|
}
|
||||||
|
print(row);
|
||||||
|
y = (y + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main();
|
@ -10,20 +10,22 @@
|
|||||||
54, 1, 35, 32, "%x%", 32, "baa", 17, 36, 0, 54, 1, 35, 32, "%A%", 32, "baa", 18, 36, 0, 54, 1, 35, 32, "%C%", 32, "baa",
|
54, 1, 35, 32, "%x%", 32, "baa", 17, 36, 0, 54, 1, 35, 32, "%A%", 32, "baa", 18, 36, 0, 54, 1, 35, 32, "%C%", 32, "baa",
|
||||||
18, 36, 0, 54, 1, 35, 32, "b", 32, "a", 18, 36, 0, 54, 1, 35, 32, "b", 32, "a", 19, 36, 0, 54, 1, 35, 32, "b", 32, "a",
|
18, 36, 0, 54, 1, 35, 32, "b", 32, "a", 18, 36, 0, 54, 1, 35, 32, "b", 32, "a", 19, 36, 0, 54, 1, 35, 32, "b", 32, "a",
|
||||||
20, 36, 0, 54, 1, 35, 32, "car", 32, "a", 21, 36, 0, 54, 1, 35, 32, "foo", 32, "a", 21, 36, 0, 54, 1, 35, 32, "car", 32,
|
20, 36, 0, 54, 1, 35, 32, "car", 32, "a", 21, 36, 0, 54, 1, 35, 32, "foo", 32, "a", 21, 36, 0, 54, 1, 35, 32, "car", 32,
|
||||||
"a", 22, 36, 0, 54, 1, 35, 32, "arg", 32, "another", 2, "concat", 2, 36, 0, 54, 1, 35, 33, 1, 31, 2, "concat", 2, 36, 0,
|
"a", 22, 36, 0, 54, 1, 35, 32, "b_x", 32, "bax", 17, 36, 0, 54, 1, 35, 32, "b_x", 32, "baax", 19, 36, 0, 54, 1, 35, 32,
|
||||||
54, 1, 35, 29, 30, 2, "concat", 2, 36, 0, 54, 1, 35, 32, "test", 32, "e.*", 2, "match", 2, 36, 0, 54, 1, 35, 32, "test",
|
"b%x", 32, "baax", 17, 36, 0, 54, 1, 35, 32, "arg", 32, "another", 2, "concat", 2, 36, 0, 54, 1, 35, 33, 1, 31, 2,
|
||||||
32, "^e.*", 2, "match", 2, 36, 0, 54, 1, 35, 32, "test", 32, "x.*", 2, "match", 2, 36, 0, 54, 1, 35, 32, "e.*", 32,
|
"concat", 2, 36, 0, 54, 1, 35, 29, 30, 2, "concat", 2, 36, 0, 54, 1, 35, 32, "test", 32, "e.*", 2, "match", 2, 36, 0,
|
||||||
"test", 23, 36, 0, 54, 1, 35, 32, "e.*", 32, "test", 24, 36, 0, 54, 1, 35, 32, "^e.*", 32, "test", 23, 36, 0, 54, 1, 35,
|
54, 1, 35, 32, "test", 32, "^e.*", 2, "match", 2, 36, 0, 54, 1, 35, 32, "test", 32, "x.*", 2, "match", 2, 36, 0, 54, 1,
|
||||||
32, "^e.*", 32, "test", 24, 36, 0, 54, 1, 35, 32, "x.*", 32, "test", 23, 36, 0, 54, 1, 35, 32, "x.*", 32, "test", 24,
|
35, 32, "e.*", 32, "test", 23, 36, 0, 54, 1, 35, 32, "e.*", 32, "test", 24, 36, 0, 54, 1, 35, 32, "^e.*", 32, "test",
|
||||||
36, 0, 54, 1, 35, 32, "EST", 32, "test", 25, 36, 0, 54, 1, 35, 32, "EST", 32, "test", 25, 36, 0, 54, 1, 35, 32, "EST",
|
23, 36, 0, 54, 1, 35, 32, "^e.*", 32, "test", 24, 36, 0, 54, 1, 35, 32, "x.*", 32, "test", 23, 36, 0, 54, 1, 35, 32,
|
||||||
32, "test", 26, 36, 0, 54, 1, 35, 33, 1, 2, "toString", 1, 36, 0, 54, 1, 35, 34, 1.5, 2, "toString", 1, 36, 0, 54, 1,
|
"x.*", 32, "test", 24, 36, 0, 54, 1, 35, 32, "EST", 32, "test", 25, 36, 0, 54, 1, 35, 32, "EST", 32, "test", 25, 36, 0,
|
||||||
35, 29, 2, "toString", 1, 36, 0, 54, 1, 35, 31, 2, "toString", 1, 36, 0, 54, 1, 35, 32, "string", 2, "toString", 1, 36,
|
54, 1, 35, 32, "EST", 32, "test", 26, 36, 0, 54, 1, 35, 33, 1, 2, "toString", 1, 36, 0, 54, 1, 35, 34, 1.5, 2,
|
||||||
0, 54, 1, 35, 32, "1", 2, "toInt", 1, 36, 0, 54, 1, 35, 32, "bla", 2, "toInt", 1, 36, 0, 54, 1, 35, 32, "1.2", 2,
|
"toString", 1, 36, 0, 54, 1, 35, 29, 2, "toString", 1, 36, 0, 54, 1, 35, 31, 2, "toString", 1, 36, 0, 54, 1, 35, 32,
|
||||||
"toFloat", 1, 36, 0, 54, 1, 35, 32, "bla", 2, "toFloat", 1, 36, 0, 54, 1, 35, 32, "asd", 2, "toUUID", 1, 36, 0, 54, 1,
|
"string", 2, "toString", 1, 36, 0, 54, 1, 35, 32, "1", 2, "toInt", 1, 36, 0, 54, 1, 35, 32, "bla", 2, "toInt", 1, 36, 0,
|
||||||
35, 31, 33, 1, 11, 36, 0, 54, 1, 35, 31, 33, 1, 12, 36, 0, 54, 1, 35, 33, 1, 32, "1", 11, 36, 0, 54, 1, 35, 32, "1", 33,
|
54, 1, 35, 32, "1.2", 2, "toFloat", 1, 36, 0, 54, 1, 35, 32, "bla", 2, "toFloat", 1, 36, 0, 54, 1, 35, 32, "asd", 2,
|
||||||
1, 11, 36, 0, 54, 1, 35, 29, 33, 1, 11, 36, 0, 54, 1, 35, 29, 33, 0, 11, 36, 0, 54, 1, 35, 29, 33, 2, 11, 36, 0, 54, 1,
|
"toUUID", 1, 36, 0, 54, 1, 35, 31, 33, 1, 11, 36, 0, 54, 1, 35, 31, 33, 1, 12, 36, 0, 54, 1, 35, 33, 1, 32, "1", 11, 36,
|
||||||
35, 30, 33, 1, 12, 36, 0, 54, 1, 35, 32, "2", 33, 1, 11, 36, 0, 54, 1, 35, 32, "2", 33, 1, 11, 36, 0, 54, 1, 35, 32,
|
0, 54, 1, 35, 32, "1", 33, 1, 11, 36, 0, 54, 1, 35, 29, 33, 1, 11, 36, 0, 54, 1, 35, 29, 33, 0, 11, 36, 0, 54, 1, 35,
|
||||||
"2", 33, 1, 12, 36, 0, 54, 1, 35, 32, "2", 33, 1, 15, 36, 0, 54, 1, 35, 32, "2", 33, 1, 16, 36, 0, 54, 1, 35, 32, "2",
|
29, 33, 2, 11, 36, 0, 54, 1, 35, 30, 33, 1, 12, 36, 0, 54, 1, 35, 32, "2", 33, 1, 11, 36, 0, 54, 1, 35, 32, "2", 33, 1,
|
||||||
33, 1, 13, 36, 0, 54, 1, 35, 32, "2", 33, 1, 14, 36, 0, 54, 1, 35, 33, 2, 32, "1", 11, 36, 0, 54, 1, 35, 33, 2, 32, "1",
|
11, 36, 0, 54, 1, 35, 32, "2", 33, 1, 12, 36, 0, 54, 1, 35, 32, "2", 33, 1, 15, 36, 0, 54, 1, 35, 32, "2", 33, 1, 16,
|
||||||
11, 36, 0, 54, 1, 35, 33, 2, 32, "1", 12, 36, 0, 54, 1, 35, 33, 2, 32, "1", 15, 36, 0, 54, 1, 35, 33, 2, 32, "1", 16,
|
36, 0, 54, 1, 35, 32, "2", 33, 1, 13, 36, 0, 54, 1, 35, 32, "2", 33, 1, 14, 36, 0, 54, 1, 35, 33, 2, 32, "1", 11, 36, 0,
|
||||||
36, 0, 54, 1, 35, 33, 2, 32, "1", 13, 36, 0, 54, 1, 35, 33, 2, 32, "1", 14, 36, 0, 54, 1, 35, 35]
|
54, 1, 35, 33, 2, 32, "1", 11, 36, 0, 54, 1, 35, 33, 2, 32, "1", 12, 36, 0, 54, 1, 35, 33, 2, 32, "1", 15, 36, 0, 54, 1,
|
||||||
|
35, 33, 2, 32, "1", 16, 36, 0, 54, 1, 35, 33, 2, 32, "1", 13, 36, 0, 54, 1, 35, 33, 2, 32, "1", 14, 36, 0, 54, 1, 35,
|
||||||
|
35]
|
||||||
|
224
hogvm/__tests__/__snapshots__/operations.js
Normal file
224
hogvm/__tests__/__snapshots__/operations.js
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
function toUUID (value) { return __STLToString(value) }
|
||||||
|
function toString (value) { return __STLToString(value) }
|
||||||
|
function toInt(value) {
|
||||||
|
if (__isHogDateTime(value)) { return Math.floor(value.dt); }
|
||||||
|
else if (__isHogDate(value)) { const date = new Date(Date.UTC(value.year, value.month - 1, value.day)); const epoch = new Date(Date.UTC(1970, 0, 1)); const diffInDays = Math.floor((date - epoch) / (1000 * 60 * 60 * 24)); return diffInDays; }
|
||||||
|
return !isNaN(parseInt(value)) ? parseInt(value) : null; }
|
||||||
|
function toFloat(value) {
|
||||||
|
if (__isHogDateTime(value)) { return value.dt; }
|
||||||
|
else if (__isHogDate(value)) { const date = new Date(Date.UTC(value.year, value.month - 1, value.day)); const epoch = new Date(Date.UTC(1970, 0, 1)); const diffInDays = (date - epoch) / (1000 * 60 * 60 * 24); return diffInDays; }
|
||||||
|
return !isNaN(parseFloat(value)) ? parseFloat(value) : null; }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function match (str, pattern) { return new RegExp(pattern).test(str) }
|
||||||
|
function like (str, pattern) { return __like(str, pattern, false) }
|
||||||
|
function jsonStringify (value, spacing) {
|
||||||
|
function convert(x, marked) {
|
||||||
|
if (!marked) { marked = new Set() }
|
||||||
|
if (typeof x === 'object' && x !== null) {
|
||||||
|
if (marked.has(x)) { return null }
|
||||||
|
marked.add(x)
|
||||||
|
try {
|
||||||
|
if (x instanceof Map) {
|
||||||
|
const obj = {}
|
||||||
|
x.forEach((value, key) => { obj[convert(key, marked)] = convert(value, marked) })
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if (Array.isArray(x)) { return x.map((v) => convert(v, marked)) }
|
||||||
|
if (__isHogDateTime(x) || __isHogDate(x) || __isHogError(x)) { return x }
|
||||||
|
if (typeof x === 'function') { return `fn<${x.name || 'lambda'}(${x.length})>` }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key], marked) }
|
||||||
|
return obj
|
||||||
|
} finally {
|
||||||
|
marked.delete(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
if (spacing && typeof spacing === 'number' && spacing > 0) {
|
||||||
|
return JSON.stringify(convert(value), null, spacing)
|
||||||
|
}
|
||||||
|
return JSON.stringify(convert(value), (key, val) => typeof val === 'function' ? `fn<${val.name || 'lambda'}(${val.length})>` : val)
|
||||||
|
}
|
||||||
|
function ilike (str, pattern) { return __like(str, pattern, true) }
|
||||||
|
function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }
|
||||||
|
function __like(str, pattern, caseInsensitive = false) {
|
||||||
|
if (caseInsensitive) {
|
||||||
|
str = str.toLowerCase()
|
||||||
|
pattern = pattern.toLowerCase()
|
||||||
|
}
|
||||||
|
pattern = String(pattern)
|
||||||
|
.replaceAll(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||||
|
.replaceAll('%', '.*')
|
||||||
|
.replaceAll('_', '.')
|
||||||
|
return new RegExp(pattern).test(str)
|
||||||
|
}
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(val) {
|
||||||
|
print(jsonStringify(val));
|
||||||
|
}
|
||||||
|
print("-- test the most common expressions --");
|
||||||
|
test((1 + 2));
|
||||||
|
test((1 - 2));
|
||||||
|
test((3 * 2));
|
||||||
|
test((3 / 2));
|
||||||
|
test((3 % 2));
|
||||||
|
test(!!(1 && 2));
|
||||||
|
test(!!(1 || 0));
|
||||||
|
test(!!(1 && 0));
|
||||||
|
test(!!(1 || !!(0 && 1) || 2));
|
||||||
|
test(!!(1 && 0 && 1));
|
||||||
|
test(!!(!!(1 || 2) && !!(1 || 2)));
|
||||||
|
test(true);
|
||||||
|
test((!true));
|
||||||
|
test(false);
|
||||||
|
test(null);
|
||||||
|
test(3.14);
|
||||||
|
test((1 == 2));
|
||||||
|
test((1 == 2));
|
||||||
|
test((1 != 2));
|
||||||
|
test((1 < 2));
|
||||||
|
test((1 <= 2));
|
||||||
|
test((1 > 2));
|
||||||
|
test((1 >= 2));
|
||||||
|
test(like("a", "b"));
|
||||||
|
test(like("baa", "%a%"));
|
||||||
|
test(like("baa", "%x%"));
|
||||||
|
test(ilike("baa", "%A%"));
|
||||||
|
test(ilike("baa", "%C%"));
|
||||||
|
test(ilike("a", "b"));
|
||||||
|
test(!like("a", "b"));
|
||||||
|
test(!ilike("a", "b"));
|
||||||
|
test(("car".includes("a")));
|
||||||
|
test(("foo".includes("a")));
|
||||||
|
test((!"car".includes("a")));
|
||||||
|
test(like("bax", "b_x"));
|
||||||
|
test(!like("baax", "b_x"));
|
||||||
|
test(like("baax", "b%x"));
|
||||||
|
test(concat("arg", "another"));
|
||||||
|
test(concat(1, null));
|
||||||
|
test(concat(true, false));
|
||||||
|
test(match("test", "e.*"));
|
||||||
|
test(match("test", "^e.*"));
|
||||||
|
test(match("test", "x.*"));
|
||||||
|
test(new RegExp("e.*").test("test"));
|
||||||
|
test(!(new RegExp("e.*").test("test")));
|
||||||
|
test(new RegExp("^e.*").test("test"));
|
||||||
|
test(!(new RegExp("^e.*").test("test")));
|
||||||
|
test(new RegExp("x.*").test("test"));
|
||||||
|
test(!(new RegExp("x.*").test("test")));
|
||||||
|
test(new RegExp("EST", "i").test("test"));
|
||||||
|
test(new RegExp("EST", "i").test("test"));
|
||||||
|
test(!(new RegExp("EST", "i").test("test")));
|
||||||
|
test(toString(1));
|
||||||
|
test(toString(1.5));
|
||||||
|
test(toString(true));
|
||||||
|
test(toString(null));
|
||||||
|
test(toString("string"));
|
||||||
|
test(toInt("1"));
|
||||||
|
test(toInt("bla"));
|
||||||
|
test(toFloat("1.2"));
|
||||||
|
test(toFloat("bla"));
|
||||||
|
test(toUUID("asd"));
|
||||||
|
test((1 == null));
|
||||||
|
test((1 != null));
|
||||||
|
test(("1" == 1));
|
||||||
|
test((1 == "1"));
|
||||||
|
test((1 == true));
|
||||||
|
test((0 == true));
|
||||||
|
test((2 == true));
|
||||||
|
test((1 != false));
|
||||||
|
test((1 == "2"));
|
||||||
|
test((1 == "2"));
|
||||||
|
test((1 != "2"));
|
||||||
|
test((1 < "2"));
|
||||||
|
test((1 <= "2"));
|
||||||
|
test((1 > "2"));
|
||||||
|
test((1 >= "2"));
|
||||||
|
test(("1" == 2));
|
||||||
|
test(("1" == 2));
|
||||||
|
test(("1" != 2));
|
||||||
|
test(("1" < 2));
|
||||||
|
test(("1" <= 2));
|
||||||
|
test(("1" > 2));
|
||||||
|
test(("1" >= 2));
|
@ -33,6 +33,9 @@ true
|
|||||||
true
|
true
|
||||||
false
|
false
|
||||||
false
|
false
|
||||||
|
true
|
||||||
|
true
|
||||||
|
true
|
||||||
"arganother"
|
"arganother"
|
||||||
"1"
|
"1"
|
||||||
"truefalse"
|
"truefalse"
|
||||||
|
158
hogvm/__tests__/__snapshots__/printLoops.js
Normal file
158
hogvm/__tests__/__snapshots__/printLoops.js
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function jsonStringify (value, spacing) {
|
||||||
|
function convert(x, marked) {
|
||||||
|
if (!marked) { marked = new Set() }
|
||||||
|
if (typeof x === 'object' && x !== null) {
|
||||||
|
if (marked.has(x)) { return null }
|
||||||
|
marked.add(x)
|
||||||
|
try {
|
||||||
|
if (x instanceof Map) {
|
||||||
|
const obj = {}
|
||||||
|
x.forEach((value, key) => { obj[convert(key, marked)] = convert(value, marked) })
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if (Array.isArray(x)) { return x.map((v) => convert(v, marked)) }
|
||||||
|
if (__isHogDateTime(x) || __isHogDate(x) || __isHogError(x)) { return x }
|
||||||
|
if (typeof x === 'function') { return `fn<${x.name || 'lambda'}(${x.length})>` }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key], marked) }
|
||||||
|
return obj
|
||||||
|
} finally {
|
||||||
|
marked.delete(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
if (spacing && typeof spacing === 'number' && spacing > 0) {
|
||||||
|
return JSON.stringify(convert(value), null, spacing)
|
||||||
|
}
|
||||||
|
return JSON.stringify(convert(value), (key, val) => typeof val === 'function' ? `fn<${val.name || 'lambda'}(${val.length})>` : val)
|
||||||
|
}
|
||||||
|
function jsonParse (str) {
|
||||||
|
function convert(x) {
|
||||||
|
if (Array.isArray(x)) { return x.map(convert) }
|
||||||
|
else if (typeof x === 'object' && x !== null) {
|
||||||
|
if (x.__hogDateTime__) { return __toHogDateTime(x.dt, x.zone)
|
||||||
|
} else if (x.__hogDate__) { return __toHogDate(x.year, x.month, x.day)
|
||||||
|
} else if (x.__hogError__) { return __newHogError(x.type, x.message, x.payload) }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key]) }; return obj }
|
||||||
|
return x }
|
||||||
|
return convert(JSON.parse(str)) }
|
||||||
|
function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }
|
||||||
|
function __toHogDateTime(timestamp, zone) {
|
||||||
|
if (__isHogDate(timestamp)) {
|
||||||
|
const date = new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day));
|
||||||
|
const dt = date.getTime() / 1000;
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' };
|
||||||
|
}
|
||||||
|
return { __hogDateTime__: true, dt: timestamp, zone: zone || 'UTC' }; }
|
||||||
|
function __toHogDate(year, month, day) { return { __hogDate__: true, year: year, month: month, day: day, } }
|
||||||
|
function __setProperty(objectOrArray, key, value) {
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
if (key > 0) {
|
||||||
|
objectOrArray[key - 1] = value
|
||||||
|
} else {
|
||||||
|
objectOrArray[objectOrArray.length + key] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
objectOrArray[key] = value
|
||||||
|
}
|
||||||
|
return objectOrArray
|
||||||
|
}
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj = {"key": "value", "key2": "value2"};
|
||||||
|
let str = "na";
|
||||||
|
for (let i = 0; (i < 100); i = (i + 1)) {
|
||||||
|
str = concat(str, "na")
|
||||||
|
__setProperty(obj, concat("key_", i), {"wasted": concat("memory: ", str, " batman!"), "something": obj});
|
||||||
|
}
|
||||||
|
print(obj);
|
||||||
|
let json = jsonStringify(obj);
|
||||||
|
print(jsonParse(json));
|
156
hogvm/__tests__/__snapshots__/printLoops2.js
Normal file
156
hogvm/__tests__/__snapshots__/printLoops2.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function jsonStringify (value, spacing) {
|
||||||
|
function convert(x, marked) {
|
||||||
|
if (!marked) { marked = new Set() }
|
||||||
|
if (typeof x === 'object' && x !== null) {
|
||||||
|
if (marked.has(x)) { return null }
|
||||||
|
marked.add(x)
|
||||||
|
try {
|
||||||
|
if (x instanceof Map) {
|
||||||
|
const obj = {}
|
||||||
|
x.forEach((value, key) => { obj[convert(key, marked)] = convert(value, marked) })
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if (Array.isArray(x)) { return x.map((v) => convert(v, marked)) }
|
||||||
|
if (__isHogDateTime(x) || __isHogDate(x) || __isHogError(x)) { return x }
|
||||||
|
if (typeof x === 'function') { return `fn<${x.name || 'lambda'}(${x.length})>` }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key], marked) }
|
||||||
|
return obj
|
||||||
|
} finally {
|
||||||
|
marked.delete(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
if (spacing && typeof spacing === 'number' && spacing > 0) {
|
||||||
|
return JSON.stringify(convert(value), null, spacing)
|
||||||
|
}
|
||||||
|
return JSON.stringify(convert(value), (key, val) => typeof val === 'function' ? `fn<${val.name || 'lambda'}(${val.length})>` : val)
|
||||||
|
}
|
||||||
|
function jsonParse (str) {
|
||||||
|
function convert(x) {
|
||||||
|
if (Array.isArray(x)) { return x.map(convert) }
|
||||||
|
else if (typeof x === 'object' && x !== null) {
|
||||||
|
if (x.__hogDateTime__) { return __toHogDateTime(x.dt, x.zone)
|
||||||
|
} else if (x.__hogDate__) { return __toHogDate(x.year, x.month, x.day)
|
||||||
|
} else if (x.__hogError__) { return __newHogError(x.type, x.message, x.payload) }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key]) }; return obj }
|
||||||
|
return x }
|
||||||
|
return convert(JSON.parse(str)) }
|
||||||
|
function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }
|
||||||
|
function __toHogDateTime(timestamp, zone) {
|
||||||
|
if (__isHogDate(timestamp)) {
|
||||||
|
const date = new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day));
|
||||||
|
const dt = date.getTime() / 1000;
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' };
|
||||||
|
}
|
||||||
|
return { __hogDateTime__: true, dt: timestamp, zone: zone || 'UTC' }; }
|
||||||
|
function __toHogDate(year, month, day) { return { __hogDate__: true, year: year, month: month, day: day, } }
|
||||||
|
function __setProperty(objectOrArray, key, value) {
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
if (key > 0) {
|
||||||
|
objectOrArray[key - 1] = value
|
||||||
|
} else {
|
||||||
|
objectOrArray[objectOrArray.length + key] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
objectOrArray[key] = value
|
||||||
|
}
|
||||||
|
return objectOrArray
|
||||||
|
}
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let root = {"key": "value", "key2": "value2"};
|
||||||
|
let leaf = {"key": "value", "key2": "value2"};
|
||||||
|
for (let i = 0; (i < 30); i = (i + 1)) {
|
||||||
|
__setProperty(root, concat("key_", i), {"something": leaf});
|
||||||
|
}
|
||||||
|
print(root);
|
||||||
|
print(jsonParse(jsonStringify(root)));
|
122
hogvm/__tests__/__snapshots__/properties.js
Normal file
122
hogvm/__tests__/__snapshots__/properties.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
function tuple (...args) { const tuple = args.slice(); tuple.__isHogTuple = true; return tuple; }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __setProperty(objectOrArray, key, value) {
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
if (key > 0) {
|
||||||
|
objectOrArray[key - 1] = value
|
||||||
|
} else {
|
||||||
|
objectOrArray[objectOrArray.length + key] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
objectOrArray[key] = value
|
||||||
|
}
|
||||||
|
return objectOrArray
|
||||||
|
}
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": tuple(1, 3, 42, 6)}];
|
||||||
|
print(__getProperty(__getProperty(__getProperty(r, 3, false), "d", false), 2, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": tuple(1, 3, 42, 6)}];
|
||||||
|
print(__getProperty(__getProperty(__getProperty(r, 3, false), "d", false), 3, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": tuple(1, 3, 42, 6)}];
|
||||||
|
print(__getProperty(__getProperty(__getProperty(r, 3, false), "d", false), 4, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = {"d": tuple(1, 3, 42, 6)};
|
||||||
|
print(__getProperty(__getProperty(r, "d", true), 2, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": [1, 3, 42, 3]}];
|
||||||
|
__setProperty(__getProperty(__getProperty(r, 3, false), "d", false), 3, 3);
|
||||||
|
print(__getProperty(__getProperty(__getProperty(r, 3, false), "d", false), 3, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": [1, 3, 42, 3]}];
|
||||||
|
__setProperty(__getProperty(__getProperty(r, 3, false), "d", false), 3, 3);
|
||||||
|
print(__getProperty(__getProperty(__getProperty(r, 3, false), "d", false), 3, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": [1, 3, 42, 3]}];
|
||||||
|
__setProperty(__getProperty(r, 3, false), "c", [666]);
|
||||||
|
print(__getProperty(r, 3, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": [1, 3, 42, 3]}];
|
||||||
|
__setProperty(__getProperty(__getProperty(r, 3, false), "d", false), 3, 3);
|
||||||
|
print(__getProperty(__getProperty(r, 3, false), "d", false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": [1, 3, 42, 3]}];
|
||||||
|
__setProperty(__getProperty(r, 3, false), "d", ["a", "b", "c", "d"]);
|
||||||
|
print(__getProperty(__getProperty(__getProperty(r, 3, false), "d", false), 3, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = [1, 2, {"d": [1, 3, 42, 3]}];
|
||||||
|
let g = "d";
|
||||||
|
__setProperty(__getProperty(r, 3, false), g, ["a", "b", "c", "d"]);
|
||||||
|
print(__getProperty(__getProperty(__getProperty(r, 3, false), "d", false), 3, false));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let event = {"event": "$pageview", "properties": {"$browser": "Chrome", "$os": "Windows"}};
|
||||||
|
__setProperty(__getProperty(event, "properties", false), "$browser", "Firefox");
|
||||||
|
print(event);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let event = {"event": "$pageview", "properties": {"$browser": "Chrome", "$os": "Windows"}};
|
||||||
|
__setProperty(__getProperty(event, "properties", true), "$browser", "Firefox")
|
||||||
|
print(event);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let event = {"event": "$pageview", "properties": {"$browser": "Chrome", "$os": "Windows"}};
|
||||||
|
let config = {};
|
||||||
|
print(event);
|
||||||
|
}
|
56
hogvm/__tests__/__snapshots__/recursion.js
Normal file
56
hogvm/__tests__/__snapshots__/recursion.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fibonacci = __lambda((number) => {
|
||||||
|
if ((number < 2)) {
|
||||||
|
return number;
|
||||||
|
} else {
|
||||||
|
return (fibonacci((number - 1)) + fibonacci((number - 2)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
print(fibonacci(6));
|
||||||
|
function hogonacci(number) {
|
||||||
|
if ((number < 2)) {
|
||||||
|
return number;
|
||||||
|
} else {
|
||||||
|
return (hogonacci((number - 1)) + hogonacci((number - 2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print(hogonacci(6));
|
122
hogvm/__tests__/__snapshots__/scope.js
Normal file
122
hogvm/__tests__/__snapshots__/scope.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __setProperty(objectOrArray, key, value) {
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
if (key > 0) {
|
||||||
|
objectOrArray[key - 1] = value
|
||||||
|
} else {
|
||||||
|
objectOrArray[objectOrArray.length + key] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
objectOrArray[key] = value
|
||||||
|
}
|
||||||
|
return objectOrArray
|
||||||
|
}
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dbl = __lambda((x) => (x * 2));
|
||||||
|
print(dbl);
|
||||||
|
print(dbl(2));
|
||||||
|
print(dbl(8));
|
||||||
|
print("--------");
|
||||||
|
let __x_var = 5;
|
||||||
|
let varify = __lambda((x) => (x * __x_var));
|
||||||
|
print(varify(2));
|
||||||
|
__x_var = 10
|
||||||
|
print(varify(2));
|
||||||
|
print(varify(8));
|
||||||
|
print("--------");
|
||||||
|
function bigVar() {
|
||||||
|
let __x_var = 5;
|
||||||
|
let varify = __lambda((x) => (x * __x_var));
|
||||||
|
return varify;
|
||||||
|
}
|
||||||
|
let bigVarify = bigVar();
|
||||||
|
print(bigVarify(2));
|
||||||
|
print(bigVarify(8));
|
||||||
|
print("--------");
|
||||||
|
let a = 3;
|
||||||
|
function outerA() {
|
||||||
|
print(a);
|
||||||
|
a = 4
|
||||||
|
print(a);
|
||||||
|
}
|
||||||
|
function innerA() {
|
||||||
|
print(a);
|
||||||
|
outerA();
|
||||||
|
print(a);
|
||||||
|
}
|
||||||
|
print(a);
|
||||||
|
innerA();
|
||||||
|
print(a);
|
||||||
|
print("--------");
|
||||||
|
let b = {"key": 3};
|
||||||
|
function outerB() {
|
||||||
|
print(b);
|
||||||
|
__setProperty(b, "key", 4)
|
||||||
|
print(b);
|
||||||
|
}
|
||||||
|
function innerB() {
|
||||||
|
print(b);
|
||||||
|
outerB();
|
||||||
|
print(b);
|
||||||
|
}
|
||||||
|
print(b);
|
||||||
|
innerB();
|
||||||
|
print(b);
|
||||||
|
print("--------");
|
||||||
|
function outerC() {
|
||||||
|
let x = "outside";
|
||||||
|
function innerC() {
|
||||||
|
print(x);
|
||||||
|
}
|
||||||
|
innerC();
|
||||||
|
}
|
||||||
|
outerC();
|
||||||
|
print("--------");
|
||||||
|
function myFunctionOuter() {
|
||||||
|
let b = 3;
|
||||||
|
function myFunction(a) {
|
||||||
|
return (a + b);
|
||||||
|
}
|
||||||
|
print(myFunction(2));
|
||||||
|
print(myFunction(3));
|
||||||
|
}
|
||||||
|
myFunctionOuter();
|
||||||
|
print("--------");
|
121
hogvm/__tests__/__snapshots__/stl.js
Normal file
121
hogvm/__tests__/__snapshots__/stl.js
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
function upper (value) { return value.toUpperCase() }
|
||||||
|
function tuple (...args) { const tuple = args.slice(); tuple.__isHogTuple = true; return tuple; }
|
||||||
|
function reverse (value) { return value.split('').reverse().join('') }
|
||||||
|
function replaceOne (str, searchValue, replaceValue) { return str.replace(searchValue, replaceValue) }
|
||||||
|
function replaceAll (str, searchValue, replaceValue) { return str.replaceAll(searchValue, replaceValue) }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function notEmpty (value) { return !empty(value) }
|
||||||
|
function lower (value) { return value.toLowerCase() }
|
||||||
|
function length (value) { return value.length }
|
||||||
|
function generateUUIDv4 () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16) })}
|
||||||
|
function encodeURLComponent (str) { return encodeURIComponent(str) }
|
||||||
|
function empty (value) {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if (Array.isArray(value)) { return value.length === 0 } else if (value === null) { return true } else if (value instanceof Map) { return value.size === 0 }
|
||||||
|
return Object.keys(value).length === 0
|
||||||
|
} else if (typeof value === 'number' || typeof value === 'boolean') { return false }
|
||||||
|
return !value }
|
||||||
|
function decodeURLComponent (str) { return decodeURIComponent(str) }
|
||||||
|
function base64Encode (str) { return Buffer.from(str).toString('base64') }
|
||||||
|
function base64Decode (str) { return Buffer.from(str, 'base64').toString() }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("-- empty, notEmpty, length, lower, upper, reverse --");
|
||||||
|
if (!!(empty("") && notEmpty("234"))) {
|
||||||
|
print(length("123"));
|
||||||
|
}
|
||||||
|
if ((lower("Tdd4gh") == "tdd4gh")) {
|
||||||
|
print(upper("test"));
|
||||||
|
}
|
||||||
|
print(reverse("spinner"));
|
||||||
|
print("");
|
||||||
|
print("-- encodeURLComponent, decodeURLComponent --");
|
||||||
|
print(encodeURLComponent("http://www.google.com"));
|
||||||
|
print(encodeURLComponent("tom & jerry"));
|
||||||
|
print(decodeURLComponent(encodeURLComponent("http://www.google.com")));
|
||||||
|
print(decodeURLComponent(encodeURLComponent("tom & jerry")));
|
||||||
|
print("");
|
||||||
|
print("-- base64Encode, base64Decode --");
|
||||||
|
print(base64Encode("http://www.google.com"));
|
||||||
|
print(base64Encode("tom & jerry"));
|
||||||
|
print(base64Decode(base64Encode("http://www.google.com")));
|
||||||
|
print(base64Decode(base64Encode("tom & jerry")));
|
||||||
|
print("");
|
||||||
|
print("-- empty --");
|
||||||
|
print(empty(null));
|
||||||
|
print(empty(0));
|
||||||
|
print(empty(1));
|
||||||
|
print(empty(-1));
|
||||||
|
print(empty(0.0));
|
||||||
|
print(empty(0.01));
|
||||||
|
print(empty(""));
|
||||||
|
print(empty("string"));
|
||||||
|
print(empty("0"));
|
||||||
|
print(empty([]));
|
||||||
|
print(empty({}));
|
||||||
|
print(empty(tuple()));
|
||||||
|
print(empty(tuple(0)));
|
||||||
|
print(empty(tuple(1, 2)));
|
||||||
|
print(empty(true));
|
||||||
|
print(empty(false));
|
||||||
|
print("");
|
||||||
|
print("-- notEmpty --");
|
||||||
|
print(notEmpty(null));
|
||||||
|
print(notEmpty(0));
|
||||||
|
print(notEmpty(1));
|
||||||
|
print(notEmpty(-1));
|
||||||
|
print(notEmpty(0.0));
|
||||||
|
print(notEmpty(0.01));
|
||||||
|
print(notEmpty(""));
|
||||||
|
print(notEmpty("string"));
|
||||||
|
print(notEmpty("0"));
|
||||||
|
print(notEmpty([]));
|
||||||
|
print(notEmpty({}));
|
||||||
|
print(notEmpty(tuple()));
|
||||||
|
print(notEmpty(tuple(0)));
|
||||||
|
print(notEmpty(tuple(1, 2)));
|
||||||
|
print(notEmpty(true));
|
||||||
|
print(notEmpty(false));
|
||||||
|
print("");
|
||||||
|
print("-- replaceAll, replaceOne --");
|
||||||
|
print(replaceAll("hello world", "l", "L"));
|
||||||
|
print(replaceOne("hello world", "l", "L"));
|
||||||
|
print("");
|
||||||
|
print("-- generateUUIDv4 --");
|
||||||
|
print(length(generateUUIDv4()));
|
129
hogvm/__tests__/__snapshots__/strings.js
Normal file
129
hogvm/__tests__/__snapshots__/strings.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
function trimRight (str, char) {
|
||||||
|
if (char === null || char === undefined) {
|
||||||
|
char = ' '
|
||||||
|
}
|
||||||
|
if (char.length !== 1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
let end = str.length
|
||||||
|
while (str[end - 1] === char) {
|
||||||
|
end--
|
||||||
|
}
|
||||||
|
return str.slice(0, end)
|
||||||
|
}
|
||||||
|
function trimLeft (str, char) {
|
||||||
|
if (char === null || char === undefined) {
|
||||||
|
char = ' '
|
||||||
|
}
|
||||||
|
if (char.length !== 1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
let start = 0
|
||||||
|
while (str[start] === char) {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
return str.slice(start)
|
||||||
|
}
|
||||||
|
function trim (str, char) {
|
||||||
|
if (char === null || char === undefined) {
|
||||||
|
char = ' '
|
||||||
|
}
|
||||||
|
if (char.length !== 1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
let start = 0
|
||||||
|
while (str[start] === char) {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
let end = str.length
|
||||||
|
while (str[end - 1] === char) {
|
||||||
|
end--
|
||||||
|
}
|
||||||
|
if (start >= end) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return str.slice(start, end)
|
||||||
|
}
|
||||||
|
function splitByString (separator, str, maxSplits) { if (maxSplits === undefined || maxSplits === null) { return str.split(separator) } return str.split(separator, maxSplits) }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function positionCaseInsensitive (str, elem) { if (typeof str === 'string') { return str.toLowerCase().indexOf(String(elem).toLowerCase()) + 1 } else { return 0 } }
|
||||||
|
function position (str, elem) { if (typeof str === 'string') { return str.indexOf(String(elem)) + 1 } else { return 0 } }
|
||||||
|
function notLike (str, pattern) { return !__like(str, pattern, false) }
|
||||||
|
function notILike (str, pattern) { return !__like(str, pattern, true) }
|
||||||
|
function like (str, pattern) { return __like(str, pattern, false) }
|
||||||
|
function ilike (str, pattern) { return __like(str, pattern, true) }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __like(str, pattern, caseInsensitive = false) {
|
||||||
|
if (caseInsensitive) {
|
||||||
|
str = str.toLowerCase()
|
||||||
|
pattern = pattern.toLowerCase()
|
||||||
|
}
|
||||||
|
pattern = String(pattern)
|
||||||
|
.replaceAll(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||||
|
.replaceAll('%', '.*')
|
||||||
|
.replaceAll('_', '.')
|
||||||
|
return new RegExp(pattern).test(str)
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print(trim(" hello world "));
|
||||||
|
print(trimLeft(" hello world "));
|
||||||
|
print(trimRight(" hello world "));
|
||||||
|
print(trim("xxxx hello world xx", "x"));
|
||||||
|
print(trimLeft("xxxx hello world xx", "x"));
|
||||||
|
print(trimRight("xxxx hello world xx", "x"));
|
||||||
|
print(splitByString(" ", "hello world and more"));
|
||||||
|
print(splitByString(" ", "hello world and more", 1));
|
||||||
|
print(splitByString(" ", "hello world and more", 2));
|
||||||
|
print(splitByString(" ", "hello world and more", 10));
|
||||||
|
print(like("banana", "N"));
|
||||||
|
print(like("banana", "n"));
|
||||||
|
print(like("banana", "naan"));
|
||||||
|
print(ilike("banana", "N"));
|
||||||
|
print(ilike("banana", "n"));
|
||||||
|
print(ilike("banana", "naan"));
|
||||||
|
print(notLike("banana", "N"));
|
||||||
|
print(notILike("banana", "NO"));
|
||||||
|
print(position("abc", "a"));
|
||||||
|
print(position("abc", "b"));
|
||||||
|
print(position("abc", "c"));
|
||||||
|
print(position("abc", "d"));
|
||||||
|
print(positionCaseInsensitive("AbC", "a"));
|
||||||
|
print(positionCaseInsensitive("AbC", "b"));
|
||||||
|
print(positionCaseInsensitive("AbC", "c"));
|
||||||
|
print(positionCaseInsensitive("AbC", "d"));
|
67
hogvm/__tests__/__snapshots__/tuples.js
Normal file
67
hogvm/__tests__/__snapshots__/tuples.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
function tuple (...args) { const tuple = args.slice(); tuple.__isHogTuple = true; return tuple; }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print(tuple());
|
||||||
|
print(tuple(1));
|
||||||
|
print(tuple(1, 2));
|
||||||
|
print(tuple(1, 2));
|
||||||
|
print(tuple(1, 2, 3));
|
||||||
|
print(tuple(1, "2", 3));
|
||||||
|
print(tuple(1, tuple(2, 3), 4));
|
||||||
|
print(tuple(1, tuple(2, tuple(3, 4)), 5));
|
||||||
|
let a = tuple(1, 2, 3);
|
||||||
|
print(__getProperty(a, 2, false));
|
||||||
|
print(__getProperty(a, 2, true));
|
||||||
|
print(__getProperty(a, 8, true));
|
||||||
|
print(__getProperty(__getProperty(__getProperty(tuple(1, tuple(2, tuple(3, 4)), 5), 2, false), 2, false), 2, false));
|
||||||
|
print(__getProperty(__getProperty(__getProperty(tuple(1, tuple(2, tuple(3, 4)), 5), 2, true), 2, true), 2, true));
|
||||||
|
print(__getProperty(__getProperty(__getProperty(tuple(1, tuple(2, tuple(3, 4)), 5), 2, true), 2, true), 2, true));
|
||||||
|
print(__getProperty(__getProperty(__getProperty(tuple(1, tuple(2, tuple(3, 4)), 5), 4, true), 7, true), 2, true));
|
||||||
|
print(__getProperty(__getProperty(__getProperty(tuple(1, tuple(2, tuple(3, 4)), 5), 4, true), 7, true), 2, true));
|
||||||
|
print(__getProperty(__getProperty(__getProperty(tuple(1, tuple(2, tuple(3, 4)), 5), 2, false), 2, false), 2, false));
|
||||||
|
print((__getProperty(__getProperty(__getProperty(tuple(1, tuple(2, tuple(3, 4)), 5), 2, false), 2, false), 2, false) + 1));
|
88
hogvm/__tests__/__snapshots__/typeof.js
Normal file
88
hogvm/__tests__/__snapshots__/typeof.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
function __x_typeof (value) {
|
||||||
|
if (value === null || value === undefined) { return 'null'
|
||||||
|
} else if (__isHogDateTime(value)) { return 'datetime'
|
||||||
|
} else if (__isHogDate(value)) { return 'date'
|
||||||
|
} else if (__isHogError(value)) { return 'error'
|
||||||
|
} else if (typeof value === 'function') { return 'function'
|
||||||
|
} else if (Array.isArray(value)) { if (value.__isHogTuple) { return 'tuple' } return 'array'
|
||||||
|
} else if (typeof value === 'object') { return 'object'
|
||||||
|
} else if (typeof value === 'number') { return Number.isInteger(value) ? 'integer' : 'float'
|
||||||
|
} else if (typeof value === 'string') { return 'string'
|
||||||
|
} else if (typeof value === 'boolean') { return 'boolean' }
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
function tuple (...args) { const tuple = args.slice(); tuple.__isHogTuple = true; return tuple; }
|
||||||
|
function toDateTime (input, zone) { return __toDateTime(input, zone) }
|
||||||
|
function toDate (input) { return __toDate(input) }
|
||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __toDateTime(input, zone) { let dt;
|
||||||
|
if (typeof input === 'number') { dt = input; }
|
||||||
|
else { const date = new Date(input); if (isNaN(date.getTime())) { throw new Error('Invalid date input'); } dt = date.getTime() / 1000; }
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' }; }
|
||||||
|
function __toDate(input) { let date;
|
||||||
|
if (typeof input === 'number') { date = new Date(input * 1000); } else { date = new Date(input); }
|
||||||
|
if (isNaN(date.getTime())) { throw new Error('Invalid date input'); }
|
||||||
|
return { __hogDate__: true, year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate() }; }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
function __x_Error (message, payload) { return __newHogError('Error', message, payload) }
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
|
||||||
|
function test(obj) {
|
||||||
|
print(__x_typeof(obj));
|
||||||
|
}
|
||||||
|
test("hello world");
|
||||||
|
test(123);
|
||||||
|
test(1.23);
|
||||||
|
test(true);
|
||||||
|
test(false);
|
||||||
|
test(null);
|
||||||
|
test({});
|
||||||
|
test([]);
|
||||||
|
test(tuple(1, 2, 3));
|
||||||
|
test(__lambda(() => (1 + 2)));
|
||||||
|
test(toDateTime("2021-01-01T00:00:00Z"));
|
||||||
|
test(toDate("2021-01-01"));
|
||||||
|
test(__x_Error("BigError", "message"));
|
57
hogvm/__tests__/__snapshots__/upvalues.js
Normal file
57
hogvm/__tests__/__snapshots__/upvalues.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __lambda (fn) { return fn }
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
function returnCallable(a) {
|
||||||
|
return __lambda((x) => (x * a));
|
||||||
|
}
|
||||||
|
let double = returnCallable(2);
|
||||||
|
let triple = returnCallable(3);
|
||||||
|
print(double(2));
|
||||||
|
print(triple(2));
|
||||||
|
print("----------");
|
||||||
|
function outer() {
|
||||||
|
let x = "outside";
|
||||||
|
function inner() {
|
||||||
|
print(x);
|
||||||
|
}
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
let closure = outer();
|
||||||
|
closure();
|
53
hogvm/__tests__/__snapshots__/variables.js
Normal file
53
hogvm/__tests__/__snapshots__/variables.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
function print (...args) { console.log(...args.map(__printHogStringOutput)) }
|
||||||
|
function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) }
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
function __isHogError(obj) {return obj && obj.__hogError__ === true}
|
||||||
|
function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }
|
||||||
|
function __isHogDate(obj) { return obj && obj.__hogDate__ === true }
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', "'": "\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\b': '\\b', '\f': '\\f', '\r': '\\r', '\n': '\\n', '\t': '\\t', '\0': '\\0', '\v': '\\v', '\\': '\\\\', '`': '\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
print("-- test variables --");
|
||||||
|
{
|
||||||
|
let a = (1 + 2);
|
||||||
|
print(a);
|
||||||
|
let b = (a + 4);
|
||||||
|
print(b);
|
||||||
|
}
|
||||||
|
print("-- test variable reassignment --");
|
||||||
|
{
|
||||||
|
let a = 1;
|
||||||
|
a = (a + 3)
|
||||||
|
a = (a * 2)
|
||||||
|
print(a);
|
||||||
|
}
|
@ -76,3 +76,8 @@ print('------')
|
|||||||
let c := [1,2,3]
|
let c := [1,2,3]
|
||||||
print(c[1], c[2], c[3], c[4])
|
print(c[1], c[2], c[3], c[4])
|
||||||
print(c[-1], c[-2], c[-3], c[-4])
|
print(c[-1], c[-2], c[-3], c[-4])
|
||||||
|
|
||||||
|
print('------')
|
||||||
|
print('a' in ['a', 'b', 'c'])
|
||||||
|
print('d' in ['a', 'b', 'c'])
|
||||||
|
print('a' in [])
|
||||||
|
@ -19,7 +19,7 @@ try {
|
|||||||
} catch (e: FishError) {
|
} catch (e: FishError) {
|
||||||
print(f'FishError: {e.message}')
|
print(f'FishError: {e.message}')
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
print(f'Error of type {e.name}: {e.message}')
|
print(f'Error of type {e.type}: {e.message}')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -29,7 +29,7 @@ try {
|
|||||||
print(f'Problem with your food: {e.message}')
|
print(f'Problem with your food: {e.message}')
|
||||||
}
|
}
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
print(f'Error of type {e.name}: {e.message}')
|
print(f'Error of type {e.type}: {e.message}')
|
||||||
} catch (e: FishError) {
|
} catch (e: FishError) {
|
||||||
print(f'FishError: {e.message}')
|
print(f'FishError: {e.message}')
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,10 @@ print({'key': 'value'})
|
|||||||
print({'key': 'value', 'other': 'thing'})
|
print({'key': 'value', 'other': 'thing'})
|
||||||
print({'key': {'otherKey': 'value'}})
|
print({'key': {'otherKey': 'value'}})
|
||||||
|
|
||||||
let key := 3
|
// We support non-string keys... in the HogVM.
|
||||||
|
// Keys are always converted to a string in the transpiled JS version.
|
||||||
|
// TODO: this might be worth revisiting
|
||||||
|
let key := 'kk'
|
||||||
print({key: 'value'})
|
print({key: 'value'})
|
||||||
|
|
||||||
print({'key': 'value', }.key)
|
print({'key': 'value', }.key)
|
||||||
|
@ -37,6 +37,9 @@ test('a' not ilike 'b') // true
|
|||||||
test('a' in 'car') // true
|
test('a' in 'car') // true
|
||||||
test('a' in 'foo') // false
|
test('a' in 'foo') // false
|
||||||
test('a' not in 'car') // false
|
test('a' not in 'car') // false
|
||||||
|
test('bax' like 'b_x')
|
||||||
|
test('baax' not like 'b_x')
|
||||||
|
test('baax' like 'b%x')
|
||||||
test(concat('arg', 'another')) // 'arganother'
|
test(concat('arg', 'another')) // 'arganother'
|
||||||
test(concat(1, NULL)) // '1'
|
test(concat(1, NULL)) // '1'
|
||||||
test(concat(true, false)) // 'truefalse'
|
test(concat(true, false)) // 'truefalse'
|
||||||
|
@ -10,7 +10,7 @@ from hogvm.python.operation import (
|
|||||||
HOGQL_BYTECODE_VERSION as VERSION,
|
HOGQL_BYTECODE_VERSION as VERSION,
|
||||||
)
|
)
|
||||||
from hogvm.python.utils import UncaughtHogVMException
|
from hogvm.python.utils import UncaughtHogVMException
|
||||||
from posthog.hogql.bytecode import create_bytecode
|
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||||
from posthog.hogql.parser import parse_expr, parse_program
|
from posthog.hogql.parser import parse_expr, parse_program
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ class UncaughtHogVMException(HogVMException):
|
|||||||
|
|
||||||
|
|
||||||
def like(string, pattern, flags=0):
|
def like(string, pattern, flags=0):
|
||||||
pattern = re.escape(pattern).replace("%", ".*")
|
pattern = re.escape(pattern).replace("%", ".*").replace("_", ".")
|
||||||
re_pattern = re.compile(pattern, flags)
|
re_pattern = re.compile(pattern, flags)
|
||||||
return re_pattern.search(string) is not None
|
return re_pattern.search(string) is not None
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import glob
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from posthog.hogql import ast
|
from posthog.hogql import ast
|
||||||
from posthog.hogql.bytecode import create_bytecode, parse_program
|
from posthog.hogql.compiler.bytecode import create_bytecode, parse_program
|
||||||
|
|
||||||
source = "hogvm/stl/src/*.hog"
|
source = "hogvm/stl/src/*.hog"
|
||||||
target_ts = "hogvm/typescript/src/stl/bytecode.ts"
|
target_ts = "hogvm/typescript/src/stl/bytecode.ts"
|
||||||
|
121
hogvm/test.sh
121
hogvm/test.sh
@ -1,32 +1,119 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# List of test files to skip the compiledjs tests
|
||||||
|
SKIP_COMPILEDJS_FILES=("crypto.hog")
|
||||||
|
|
||||||
|
# Navigate to the script's directory
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# Build the project
|
||||||
cd typescript
|
cd typescript
|
||||||
pnpm run build
|
pnpm run build
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
# Navigate to the project root (parent directory of 'hogvm')
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
rm -f hogvm/__tests__/__snapshots__/*.stdout.nodejs
|
# Function to compute the basename for a given file
|
||||||
rm -f hogvm/__tests__/__snapshots__/*.stdout.python
|
get_basename() {
|
||||||
|
local file="$1"
|
||||||
|
local base="${file%.hog}"
|
||||||
|
base="${base##*/}"
|
||||||
|
echo "hogvm/__tests__/__snapshots__/$base"
|
||||||
|
}
|
||||||
|
|
||||||
for file in hogvm/__tests__/*.hog; do
|
# Function to check if a value is in an array
|
||||||
|
is_in_array() {
|
||||||
|
local val="$1"
|
||||||
|
shift
|
||||||
|
local arr=("$@")
|
||||||
|
for item in "${arr[@]}"; do
|
||||||
|
if [ "$item" == "$val" ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if an argument is provided
|
||||||
|
if [ "$#" -eq 1 ]; then
|
||||||
|
test_file="$1"
|
||||||
|
# Adjust the test file path if it doesn't start with 'hogvm/'
|
||||||
|
if [[ ! "$test_file" == hogvm/* ]]; then
|
||||||
|
test_file="hogvm/__tests__/$test_file"
|
||||||
|
fi
|
||||||
|
# Check if the test file exists
|
||||||
|
if [ ! -f "$test_file" ]; then
|
||||||
|
echo "Test file $test_file does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
test_files=("$test_file")
|
||||||
|
# Remove previous outputs for this test file only
|
||||||
|
basename=$(get_basename "$test_file")
|
||||||
|
rm -f "$basename.stdout.nodejs" "$basename.stdout.python" "$basename.stdout.compiledjs"
|
||||||
|
else
|
||||||
|
shopt -s nullglob
|
||||||
|
test_files=(hogvm/__tests__/*.hog)
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if [ ${#test_files[@]} -eq 0 ]; then
|
||||||
|
echo "No test files found in hogvm/__tests__/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Remove all previous outputs
|
||||||
|
rm -f hogvm/__tests__/__snapshots__/*.stdout.nodejs
|
||||||
|
rm -f hogvm/__tests__/__snapshots__/*.stdout.python
|
||||||
|
rm -f hogvm/__tests__/__snapshots__/*.stdout.compiledjs
|
||||||
|
fi
|
||||||
|
|
||||||
|
for file in "${test_files[@]}"; do
|
||||||
echo "Testing $file"
|
echo "Testing $file"
|
||||||
|
|
||||||
# from hogvm/__tests__/*.hog get hogvm/__tests__/__snapshots__/*
|
basename=$(get_basename "$file")
|
||||||
basename="${file%.hog}"
|
filename=$(basename "$file")
|
||||||
basename="${basename##*/}"
|
|
||||||
basename="hogvm/__tests__/__snapshots__/$basename"
|
|
||||||
|
|
||||||
./bin/hoge $file $basename.hoge
|
./bin/hoge "$file" "$basename.hoge"
|
||||||
./bin/hog --nodejs $basename.hoge > $basename.stdout.nodejs
|
./bin/hog --nodejs "$basename.hoge" > "$basename.stdout.nodejs"
|
||||||
./bin/hog --python $basename.hoge > $basename.stdout.python
|
./bin/hog --python "$basename.hoge" > "$basename.stdout.python"
|
||||||
set +e
|
|
||||||
diff $basename.stdout.nodejs $basename.stdout.python
|
# Check if the current file should skip the compiledjs tests
|
||||||
if [ $? -eq 0 ]; then
|
if is_in_array "$filename" "${SKIP_COMPILEDJS_FILES[@]}"; then
|
||||||
mv $basename.stdout.nodejs $basename.stdout
|
# Skip compiledjs steps for this file
|
||||||
rm $basename.stdout.python
|
echo "Skipping compiledjs tests for $filename"
|
||||||
|
set +e
|
||||||
|
diff "$basename.stdout.nodejs" "$basename.stdout.python"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
mv "$basename.stdout.nodejs" "$basename.stdout"
|
||||||
|
rm "$basename.stdout.python"
|
||||||
|
echo "Test passed"
|
||||||
|
else
|
||||||
|
echo "Test failed: Output differs between Node.js and Python interpreters."
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
else
|
else
|
||||||
echo "Test failed"
|
# Proceed with compiledjs tests
|
||||||
|
set +e
|
||||||
|
./bin/hoge "$file" "$basename.js"
|
||||||
|
node "$basename.js" > "$basename.stdout.compiledjs" 2>&1
|
||||||
|
set -e
|
||||||
|
|
||||||
|
set +e
|
||||||
|
diff "$basename.stdout.nodejs" "$basename.stdout.compiledjs"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
diff "$basename.stdout.nodejs" "$basename.stdout.python"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
mv "$basename.stdout.nodejs" "$basename.stdout"
|
||||||
|
rm "$basename.stdout.python"
|
||||||
|
rm "$basename.stdout.compiledjs"
|
||||||
|
echo "Test passed"
|
||||||
|
else
|
||||||
|
echo "Test failed: Output differs between Node.js and Python interpreters."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Test failed: Output differs between Node.js interpreter and compiled JavaScript."
|
||||||
|
fi
|
||||||
|
set -e
|
||||||
fi
|
fi
|
||||||
set -e
|
|
||||||
done
|
done
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@posthog/hogvm",
|
"name": "@posthog/hogvm",
|
||||||
"version": "1.0.58",
|
"version": "1.0.59",
|
||||||
"description": "PostHog Hog Virtual Machine",
|
"description": "PostHog Hog Virtual Machine",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"source": "src/index.ts",
|
"source": "src/index.ts",
|
||||||
|
@ -19,6 +19,8 @@ import {
|
|||||||
} from './date'
|
} from './date'
|
||||||
import { printHogStringOutput } from './print'
|
import { printHogStringOutput } from './print'
|
||||||
|
|
||||||
|
// TODO: this file should be generated from or mergred with posthog/hogql/compiler/javascript_stl.py
|
||||||
|
|
||||||
function STLToString(args: any[]): string {
|
function STLToString(args: any[]): string {
|
||||||
if (isHogDate(args[0])) {
|
if (isHogDate(args[0])) {
|
||||||
const month = args[0].month
|
const month = args[0].month
|
||||||
@ -71,9 +73,7 @@ export const STL: Record<string, STLFunction> = {
|
|||||||
},
|
},
|
||||||
toString: { fn: STLToString, minArgs: 1, maxArgs: 1 },
|
toString: { fn: STLToString, minArgs: 1, maxArgs: 1 },
|
||||||
toUUID: {
|
toUUID: {
|
||||||
fn: (args) => {
|
fn: STLToString,
|
||||||
return String(args[0])
|
|
||||||
},
|
|
||||||
minArgs: 1,
|
minArgs: 1,
|
||||||
maxArgs: 1,
|
maxArgs: 1,
|
||||||
},
|
},
|
||||||
@ -148,8 +148,8 @@ export const STL: Record<string, STLFunction> = {
|
|||||||
},
|
},
|
||||||
tuple: {
|
tuple: {
|
||||||
fn: (args) => {
|
fn: (args) => {
|
||||||
const tuple = args.slice()
|
const tuple = args.slice();
|
||||||
;(tuple as any).__isHogTuple = true
|
(tuple as any).__isHogTuple = true
|
||||||
return tuple
|
return tuple
|
||||||
},
|
},
|
||||||
minArgs: 0,
|
minArgs: 0,
|
||||||
|
@ -36,6 +36,7 @@ export function like(
|
|||||||
pattern = String(pattern)
|
pattern = String(pattern)
|
||||||
.replaceAll(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
.replaceAll(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||||
.replaceAll('%', '.*')
|
.replaceAll('%', '.*')
|
||||||
|
.replaceAll('_', '.')
|
||||||
if (match) {
|
if (match) {
|
||||||
return match((caseInsensitive ? '(?i)' : '') + pattern, string)
|
return match((caseInsensitive ? '(?i)' : '') + pattern, string)
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@
|
|||||||
"@medv/finder": "^3.1.0",
|
"@medv/finder": "^3.1.0",
|
||||||
"@microlink/react-json-view": "^1.21.3",
|
"@microlink/react-json-view": "^1.21.3",
|
||||||
"@monaco-editor/react": "4.6.0",
|
"@monaco-editor/react": "4.6.0",
|
||||||
"@posthog/hogvm": "^1.0.58",
|
"@posthog/hogvm": "^1.0.59",
|
||||||
"@posthog/icons": "0.9.1",
|
"@posthog/icons": "0.9.1",
|
||||||
"@posthog/plugin-scaffold": "^1.4.4",
|
"@posthog/plugin-scaffold": "^1.4.4",
|
||||||
"@react-hook/size": "^2.1.2",
|
"@react-hook/size": "^2.1.2",
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
"start:dev": "NODE_ENV=dev BASE_DIR=.. nodemon --watch src/ --exec node -r @swc-node/register src/index.ts",
|
"start:dev": "NODE_ENV=dev BASE_DIR=.. nodemon --watch src/ --exec node -r @swc-node/register src/index.ts",
|
||||||
"start:devNoWatch": "NODE_ENV=dev BASE_DIR=.. node -r @swc-node/register src/index.ts",
|
"start:devNoWatch": "NODE_ENV=dev BASE_DIR=.. node -r @swc-node/register src/index.ts",
|
||||||
"build": "pnpm clean && pnpm compile",
|
"build": "pnpm clean && pnpm compile",
|
||||||
"clean": "rm -rf dist/*",
|
"clean": "rm -rf dist/* && rm -rf ../rust/cyclotron-node/index.node",
|
||||||
"typescript:compile": "tsc -b",
|
"typescript:compile": "tsc -b",
|
||||||
"typescript:check": "tsc --noEmit -p .",
|
"typescript:check": "tsc --noEmit -p .",
|
||||||
"compile": "pnpm typescript:compile",
|
"compile": "pnpm typescript:compile",
|
||||||
@ -54,7 +54,7 @@
|
|||||||
"@maxmind/geoip2-node": "^3.4.0",
|
"@maxmind/geoip2-node": "^3.4.0",
|
||||||
"@posthog/clickhouse": "^1.7.0",
|
"@posthog/clickhouse": "^1.7.0",
|
||||||
"@posthog/cyclotron": "file:../rust/cyclotron-node",
|
"@posthog/cyclotron": "file:../rust/cyclotron-node",
|
||||||
"@posthog/hogvm": "^1.0.58",
|
"@posthog/hogvm": "^1.0.59",
|
||||||
"@posthog/plugin-scaffold": "1.4.4",
|
"@posthog/plugin-scaffold": "1.4.4",
|
||||||
"@sentry/node": "^7.49.0",
|
"@sentry/node": "^7.49.0",
|
||||||
"@sentry/profiling-node": "^0.3.0",
|
"@sentry/profiling-node": "^0.3.0",
|
||||||
|
@ -47,8 +47,8 @@ dependencies:
|
|||||||
specifier: file:../rust/cyclotron-node
|
specifier: file:../rust/cyclotron-node
|
||||||
version: file:../rust/cyclotron-node
|
version: file:../rust/cyclotron-node
|
||||||
'@posthog/hogvm':
|
'@posthog/hogvm':
|
||||||
specifier: ^1.0.58
|
specifier: ^1.0.59
|
||||||
version: 1.0.58(luxon@3.4.4)
|
version: 1.0.59(luxon@3.4.4)
|
||||||
'@posthog/plugin-scaffold':
|
'@posthog/plugin-scaffold':
|
||||||
specifier: 1.4.4
|
specifier: 1.4.4
|
||||||
version: 1.4.4
|
version: 1.4.4
|
||||||
@ -3119,8 +3119,8 @@ packages:
|
|||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@posthog/hogvm@1.0.58(luxon@3.4.4):
|
/@posthog/hogvm@1.0.59(luxon@3.4.4):
|
||||||
resolution: {integrity: sha512-n7NlJWth9WymJWd3w2YOKfq+soxAcycdfjNIVxxniL1bmEL+aI+Nff+MCPKrsv7YLj9qAnyLWBVAw9SZMksB1Q==}
|
resolution: {integrity: sha512-4KJfCXUhK7x5Wm3pheKWDmrbQ0y1lWlLWdVEjocdjSy3wOS8hQQqaFAVEKZs7hfk9pZqvNFh2UPgD4ccpwUQjA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
luxon: ^3.4.4
|
luxon: ^3.4.4
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -50,8 +50,8 @@ dependencies:
|
|||||||
specifier: 4.6.0
|
specifier: 4.6.0
|
||||||
version: 4.6.0(monaco-editor@0.49.0)(react-dom@18.2.0)(react@18.2.0)
|
version: 4.6.0(monaco-editor@0.49.0)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@posthog/hogvm':
|
'@posthog/hogvm':
|
||||||
specifier: ^1.0.58
|
specifier: ^1.0.59
|
||||||
version: 1.0.58(luxon@3.5.0)
|
version: 1.0.59(luxon@3.5.0)
|
||||||
'@posthog/icons':
|
'@posthog/icons':
|
||||||
specifier: 0.9.1
|
specifier: 0.9.1
|
||||||
version: 0.9.1(react-dom@18.2.0)(react@18.2.0)
|
version: 0.9.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
@ -392,7 +392,7 @@ dependencies:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents:
|
fsevents:
|
||||||
specifier: ^2.3.2
|
specifier: ^2.3.2
|
||||||
version: 2.3.3
|
version: 2.3.2
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@babel/core':
|
'@babel/core':
|
||||||
@ -5418,8 +5418,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
|
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@posthog/hogvm@1.0.58(luxon@3.5.0):
|
/@posthog/hogvm@1.0.59(luxon@3.5.0):
|
||||||
resolution: {integrity: sha512-n7NlJWth9WymJWd3w2YOKfq+soxAcycdfjNIVxxniL1bmEL+aI+Nff+MCPKrsv7YLj9qAnyLWBVAw9SZMksB1Q==}
|
resolution: {integrity: sha512-4KJfCXUhK7x5Wm3pheKWDmrbQ0y1lWlLWdVEjocdjSy3wOS8hQQqaFAVEKZs7hfk9pZqvNFh2UPgD4ccpwUQjA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
luxon: ^3.4.4
|
luxon: ^3.4.4
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -13142,7 +13142,6 @@ packages:
|
|||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: true
|
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/fsevents@2.3.3:
|
/fsevents@2.3.3:
|
||||||
|
@ -6,7 +6,7 @@ from rest_framework.response import Response
|
|||||||
from hogql_parser import parse_program
|
from hogql_parser import parse_program
|
||||||
from posthog.api.mixins import PydanticModelMixin
|
from posthog.api.mixins import PydanticModelMixin
|
||||||
from posthog.api.routing import TeamAndOrgViewSetMixin
|
from posthog.api.routing import TeamAndOrgViewSetMixin
|
||||||
from posthog.hogql.bytecode import create_bytecode, Local
|
from posthog.hogql.compiler.bytecode import create_bytecode, Local
|
||||||
from posthog.hogql.errors import ExposedHogQLError
|
from posthog.hogql.errors import ExposedHogQLError
|
||||||
from posthog.schema import HogCompileResponse
|
from posthog.schema import HogCompileResponse
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from rest_framework.exceptions import ValidationError
|
|||||||
from hogvm.python.debugger import color_bytecode
|
from hogvm.python.debugger import color_bytecode
|
||||||
from posthog.clickhouse.query_tagging import tag_queries
|
from posthog.clickhouse.query_tagging import tag_queries
|
||||||
from posthog.cloud_utils import is_cloud
|
from posthog.cloud_utils import is_cloud
|
||||||
from posthog.hogql.bytecode import execute_hog
|
from posthog.hogql.compiler.bytecode import execute_hog
|
||||||
from posthog.hogql.constants import LimitContext
|
from posthog.hogql.constants import LimitContext
|
||||||
from posthog.hogql.context import HogQLContext
|
from posthog.hogql.context import HogQLContext
|
||||||
from posthog.hogql.database.database import create_hogql_database, serialize_database
|
from posthog.hogql.database.database import create_hogql_database, serialize_database
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
from posthog.models.action.action import Action
|
from posthog.models.action.action import Action
|
||||||
from posthog.hogql.bytecode import create_bytecode
|
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||||
from posthog.hogql.parser import parse_expr
|
from posthog.hogql.parser import parse_expr
|
||||||
from posthog.hogql.property import action_to_expr, property_to_expr, ast
|
from posthog.hogql.property import action_to_expr, property_to_expr, ast
|
||||||
from posthog.models.team.team import Team
|
from posthog.models.team.team import Team
|
||||||
|
@ -3,7 +3,7 @@ from inline_snapshot import snapshot
|
|||||||
|
|
||||||
from hogvm.python.operation import HOGQL_BYTECODE_VERSION
|
from hogvm.python.operation import HOGQL_BYTECODE_VERSION
|
||||||
from posthog.cdp.filters import hog_function_filters_to_expr
|
from posthog.cdp.filters import hog_function_filters_to_expr
|
||||||
from posthog.hogql.bytecode import create_bytecode
|
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||||
from posthog.models.action.action import Action
|
from posthog.models.action.action import Action
|
||||||
from posthog.test.base import APIBaseTest, ClickhouseTestMixin, QueryMatchingTest
|
from posthog.test.base import APIBaseTest, ClickhouseTestMixin, QueryMatchingTest
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import logging
|
|||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from posthog.hogql.bytecode import create_bytecode
|
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||||
from posthog.hogql.parser import parse_program, parse_string_template
|
from posthog.hogql.parser import parse_program, parse_string_template
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -2,7 +2,8 @@ import sys
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from hogvm.python.execute import execute_bytecode
|
from hogvm.python.execute import execute_bytecode
|
||||||
from .bytecode import create_bytecode, parse_program
|
from posthog.hogql.compiler.bytecode import create_bytecode, parse_program
|
||||||
|
from posthog.hogql.compiler.javascript import to_js_program
|
||||||
|
|
||||||
modifiers = [arg for arg in sys.argv if arg.startswith("-")]
|
modifiers = [arg for arg in sys.argv if arg.startswith("-")]
|
||||||
args = [arg for arg in sys.argv if arg != "" and not arg.startswith("-")]
|
args = [arg for arg in sys.argv if arg != "" and not arg.startswith("-")]
|
||||||
@ -14,46 +15,53 @@ if not filename.endswith(".hog") and not filename.endswith(".hoge"):
|
|||||||
with open(filename) as file:
|
with open(filename) as file:
|
||||||
code = file.read()
|
code = file.read()
|
||||||
|
|
||||||
if filename.endswith(".hog"):
|
if "--compile" in modifiers and len(args) == 3 and args[2].endswith(".js"):
|
||||||
bytecode = create_bytecode(parse_program(code)).bytecode
|
target = args[2]
|
||||||
|
js_program = to_js_program(code)
|
||||||
|
with open(target, "w") as file:
|
||||||
|
file.write(js_program + "\n")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
bytecode = json.loads(code)
|
if filename.endswith(".hog"):
|
||||||
|
bytecode = create_bytecode(parse_program(code)).bytecode
|
||||||
if "--run" in modifiers:
|
|
||||||
if len(args) != 2:
|
|
||||||
raise ValueError("Must specify exactly one filename")
|
|
||||||
|
|
||||||
response = execute_bytecode(bytecode, globals=None, timeout=5, team=None, debug="--debug" in modifiers)
|
|
||||||
for line in response.stdout:
|
|
||||||
print(line) # noqa: T201
|
|
||||||
|
|
||||||
elif "--out" in modifiers:
|
|
||||||
if len(args) != 2:
|
|
||||||
raise ValueError("Must specify exactly one filename")
|
|
||||||
print(json.dumps(bytecode)) # noqa: T201
|
|
||||||
|
|
||||||
elif "--compile" in modifiers:
|
|
||||||
if len(args) == 3:
|
|
||||||
target = args[2]
|
|
||||||
else:
|
else:
|
||||||
target = filename[:-4] + ".hoge"
|
bytecode = json.loads(code)
|
||||||
|
|
||||||
|
if "--run" in modifiers:
|
||||||
if len(args) != 2:
|
if len(args) != 2:
|
||||||
raise ValueError("Must specify exactly one filename")
|
raise ValueError("Must specify exactly one filename")
|
||||||
|
|
||||||
# write bytecode to file
|
response = execute_bytecode(bytecode, globals=None, timeout=5, team=None, debug="--debug" in modifiers)
|
||||||
with open(target, "w") as file:
|
for line in response.stdout:
|
||||||
max_length = 120
|
print(line) # noqa: T201
|
||||||
line = "["
|
|
||||||
for index, op in enumerate(bytecode):
|
|
||||||
encoded = json.dumps(op)
|
|
||||||
if len(line) + len(encoded) > max_length - 2:
|
|
||||||
file.write(line + "\n")
|
|
||||||
line = ""
|
|
||||||
line += (" " if len(line) > 1 else "") + encoded + ("]" if index == len(bytecode) - 1 else ",")
|
|
||||||
if line == "[":
|
|
||||||
file.write(line + "]\n")
|
|
||||||
elif line != "":
|
|
||||||
file.write(line + "\n")
|
|
||||||
|
|
||||||
else:
|
elif "--out" in modifiers:
|
||||||
raise ValueError("Must specify either --run or --compile")
|
if len(args) != 2:
|
||||||
|
raise ValueError("Must specify exactly one filename")
|
||||||
|
print(json.dumps(bytecode)) # noqa: T201
|
||||||
|
|
||||||
|
elif "--compile" in modifiers:
|
||||||
|
if len(args) == 3:
|
||||||
|
target = args[2]
|
||||||
|
else:
|
||||||
|
target = filename[:-4] + ".hoge"
|
||||||
|
if len(args) != 2:
|
||||||
|
raise ValueError("Must specify exactly one filename")
|
||||||
|
|
||||||
|
# write bytecode to file
|
||||||
|
with open(target, "w") as file:
|
||||||
|
max_length = 120
|
||||||
|
line = "["
|
||||||
|
for index, op in enumerate(bytecode):
|
||||||
|
encoded = json.dumps(op)
|
||||||
|
if len(line) + len(encoded) > max_length - 2:
|
||||||
|
file.write(line + "\n")
|
||||||
|
line = ""
|
||||||
|
line += (" " if len(line) > 1 else "") + encoded + ("]" if index == len(bytecode) - 1 else ",")
|
||||||
|
if line == "[":
|
||||||
|
file.write(line + "]\n")
|
||||||
|
elif line != "":
|
||||||
|
file.write(line + "\n")
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError("Must specify either --run or --compile")
|
||||||
|
572
posthog/hogql/compiler/javascript.py
Normal file
572
posthog/hogql/compiler/javascript.py
Normal file
@ -0,0 +1,572 @@
|
|||||||
|
import dataclasses
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
from enum import StrEnum
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from posthog.hogql import ast
|
||||||
|
from posthog.hogql.base import AST
|
||||||
|
from posthog.hogql.compiler.javascript_stl import STL_FUNCTIONS, import_stl_functions
|
||||||
|
from posthog.hogql.errors import QueryError, NotImplementedError
|
||||||
|
from posthog.hogql.parser import parse_expr, parse_program
|
||||||
|
from posthog.hogql.visitor import Visitor
|
||||||
|
|
||||||
|
_JS_GET_GLOBAL = "__getGlobal"
|
||||||
|
_JS_KEYWORDS = {
|
||||||
|
"await",
|
||||||
|
"break",
|
||||||
|
"case",
|
||||||
|
"catch",
|
||||||
|
"class",
|
||||||
|
"const",
|
||||||
|
"continue",
|
||||||
|
"debugger",
|
||||||
|
"default",
|
||||||
|
"delete",
|
||||||
|
"do",
|
||||||
|
"else",
|
||||||
|
"enum",
|
||||||
|
"export",
|
||||||
|
"extends",
|
||||||
|
"false",
|
||||||
|
"finally",
|
||||||
|
"for",
|
||||||
|
"function",
|
||||||
|
"if",
|
||||||
|
"import",
|
||||||
|
"in",
|
||||||
|
"instanceof",
|
||||||
|
"new",
|
||||||
|
"null",
|
||||||
|
"return",
|
||||||
|
"super",
|
||||||
|
"switch",
|
||||||
|
"this",
|
||||||
|
"throw",
|
||||||
|
"true",
|
||||||
|
"try",
|
||||||
|
"typeof",
|
||||||
|
"var",
|
||||||
|
"void",
|
||||||
|
"while",
|
||||||
|
"with",
|
||||||
|
"yield",
|
||||||
|
"implements",
|
||||||
|
"interface",
|
||||||
|
"let",
|
||||||
|
"package",
|
||||||
|
"private",
|
||||||
|
"protected",
|
||||||
|
"public",
|
||||||
|
"static",
|
||||||
|
"arguments",
|
||||||
|
"eval",
|
||||||
|
"Error",
|
||||||
|
_JS_GET_GLOBAL, # don't let this get overridden
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class Local:
|
||||||
|
name: str
|
||||||
|
depth: int
|
||||||
|
|
||||||
|
|
||||||
|
def to_js_program(code: str) -> str:
|
||||||
|
compiler = JavaScriptCompiler()
|
||||||
|
code = compiler.visit(parse_program(code))
|
||||||
|
imports = compiler.get_inlined_stl()
|
||||||
|
return imports + ("\n\n" if imports else "") + code
|
||||||
|
|
||||||
|
|
||||||
|
def to_js_expr(expr: str) -> str:
|
||||||
|
return JavaScriptCompiler().visit(parse_expr(expr))
|
||||||
|
|
||||||
|
|
||||||
|
def _as_block(node: ast.Statement) -> ast.Block:
|
||||||
|
if isinstance(node, ast.Block):
|
||||||
|
return node
|
||||||
|
return ast.Block(declarations=[node])
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize_identifier(name: str | int) -> str:
|
||||||
|
name = str(name)
|
||||||
|
if re.match(r"^[a-zA-Z_][a-zA-Z0-9_]*$", name):
|
||||||
|
if name in _JS_KEYWORDS:
|
||||||
|
return f"__x_{name}"
|
||||||
|
if name.startswith("__x_"):
|
||||||
|
# add a second __x_ to avoid conflicts with our internal variables
|
||||||
|
return f"__x_{name}"
|
||||||
|
return name
|
||||||
|
else:
|
||||||
|
return f"[{json.dumps(name)}]"
|
||||||
|
|
||||||
|
|
||||||
|
class JavaScriptCompiler(Visitor):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
args: Optional[list[str]] = None,
|
||||||
|
locals: Optional[list[Local]] = None,
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
self.locals: list[Local] = locals or []
|
||||||
|
self.scope_depth = 0
|
||||||
|
self.args = args or []
|
||||||
|
self.indent_level = 0
|
||||||
|
self.inlined_stl: set[str] = set()
|
||||||
|
|
||||||
|
# Initialize locals with function arguments
|
||||||
|
for arg in self.args:
|
||||||
|
self._declare_local(arg)
|
||||||
|
|
||||||
|
def get_inlined_stl(self) -> str:
|
||||||
|
return import_stl_functions(self.inlined_stl)
|
||||||
|
|
||||||
|
def _start_scope(self):
|
||||||
|
self.scope_depth += 1
|
||||||
|
|
||||||
|
def _end_scope(self):
|
||||||
|
self.locals = [local for local in self.locals if local.depth < self.scope_depth]
|
||||||
|
self.scope_depth -= 1
|
||||||
|
|
||||||
|
def _declare_local(self, name: str):
|
||||||
|
for local in reversed(self.locals):
|
||||||
|
if local.depth == self.scope_depth and local.name == name:
|
||||||
|
raise QueryError(f"Variable `{name}` already declared in this scope")
|
||||||
|
self.locals.append(Local(name=name, depth=self.scope_depth))
|
||||||
|
|
||||||
|
def _indent(self, code: str) -> str:
|
||||||
|
indentation = " " * self.indent_level
|
||||||
|
return "\n".join(indentation + line if line else "" for line in code.split("\n"))
|
||||||
|
|
||||||
|
def visit_and(self, node: ast.And):
|
||||||
|
code = " && ".join([self.visit(expr) for expr in node.exprs])
|
||||||
|
return f"!!({code})"
|
||||||
|
|
||||||
|
def visit_or(self, node: ast.Or):
|
||||||
|
code = " || ".join([self.visit(expr) for expr in node.exprs])
|
||||||
|
return f"!!({code})"
|
||||||
|
|
||||||
|
def visit_not(self, node: ast.Not):
|
||||||
|
expr_code = self.visit(node.expr)
|
||||||
|
return f"(!{expr_code})"
|
||||||
|
|
||||||
|
def visit_compare_operation(self, node: ast.CompareOperation):
|
||||||
|
left_code = self.visit(node.left)
|
||||||
|
right_code = self.visit(node.right)
|
||||||
|
op = node.op
|
||||||
|
|
||||||
|
op_map = {
|
||||||
|
ast.CompareOperationOp.Eq: "==",
|
||||||
|
ast.CompareOperationOp.NotEq: "!=",
|
||||||
|
ast.CompareOperationOp.Gt: ">",
|
||||||
|
ast.CompareOperationOp.GtEq: ">=",
|
||||||
|
ast.CompareOperationOp.Lt: "<",
|
||||||
|
ast.CompareOperationOp.LtEq: "<=",
|
||||||
|
}
|
||||||
|
|
||||||
|
if op in op_map:
|
||||||
|
return f"({left_code} {op_map[op]} {right_code})"
|
||||||
|
elif op == ast.CompareOperationOp.In:
|
||||||
|
return f"({right_code}.includes({left_code}))"
|
||||||
|
elif op == ast.CompareOperationOp.NotIn:
|
||||||
|
return f"(!{right_code}.includes({left_code}))"
|
||||||
|
elif op == ast.CompareOperationOp.Like:
|
||||||
|
self.inlined_stl.add("like")
|
||||||
|
return f"like({left_code}, {right_code})"
|
||||||
|
elif op == ast.CompareOperationOp.ILike:
|
||||||
|
self.inlined_stl.add("ilike")
|
||||||
|
return f"ilike({left_code}, {right_code})"
|
||||||
|
elif op == ast.CompareOperationOp.NotLike:
|
||||||
|
self.inlined_stl.add("like")
|
||||||
|
return f"!like({left_code}, {right_code})"
|
||||||
|
elif op == ast.CompareOperationOp.NotILike:
|
||||||
|
self.inlined_stl.add("ilike")
|
||||||
|
return f"!ilike({left_code}, {right_code})"
|
||||||
|
elif op == ast.CompareOperationOp.Regex:
|
||||||
|
# TODO: re2?
|
||||||
|
return f"new RegExp({right_code}).test({left_code})"
|
||||||
|
elif op == ast.CompareOperationOp.IRegex:
|
||||||
|
return f'new RegExp({right_code}, "i").test({left_code})'
|
||||||
|
elif op == ast.CompareOperationOp.NotRegex:
|
||||||
|
return f"!(new RegExp({right_code}).test({left_code}))"
|
||||||
|
elif op == ast.CompareOperationOp.NotIRegex:
|
||||||
|
return f'!(new RegExp({right_code}, "i").test({left_code}))'
|
||||||
|
elif op == ast.CompareOperationOp.InCohort or op == ast.CompareOperationOp.NotInCohort:
|
||||||
|
cohort_name = ""
|
||||||
|
if isinstance(node.right, ast.Constant):
|
||||||
|
if isinstance(node.right.value, int):
|
||||||
|
cohort_name = f" (cohort id={node.right.value})"
|
||||||
|
else:
|
||||||
|
cohort_name = f" (cohort: {str(node.right.value)})"
|
||||||
|
raise QueryError(
|
||||||
|
f"Can't use cohorts in real-time filters. Please inline the relevant expressions{cohort_name}."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise QueryError(f"Unsupported comparison operator: {op}")
|
||||||
|
|
||||||
|
def visit_arithmetic_operation(self, node: ast.ArithmeticOperation):
|
||||||
|
left_code = self.visit(node.left)
|
||||||
|
right_code = self.visit(node.right)
|
||||||
|
op_map = {
|
||||||
|
ast.ArithmeticOperationOp.Add: "+",
|
||||||
|
ast.ArithmeticOperationOp.Sub: "-",
|
||||||
|
ast.ArithmeticOperationOp.Mult: "*",
|
||||||
|
ast.ArithmeticOperationOp.Div: "/",
|
||||||
|
ast.ArithmeticOperationOp.Mod: "%",
|
||||||
|
}
|
||||||
|
op_str = op_map[node.op]
|
||||||
|
return f"({left_code} {op_str} {right_code})"
|
||||||
|
|
||||||
|
def visit_field(self, node: ast.Field):
|
||||||
|
found_local = any(local.name == str(node.chain[0]) for local in self.locals)
|
||||||
|
array_code = ""
|
||||||
|
for index, element in enumerate(node.chain):
|
||||||
|
if index == 0:
|
||||||
|
if found_local:
|
||||||
|
array_code = _sanitize_identifier(element)
|
||||||
|
elif element in STL_FUNCTIONS:
|
||||||
|
self.inlined_stl.add(str(element))
|
||||||
|
array_code = f"{_sanitize_identifier(element)}"
|
||||||
|
else:
|
||||||
|
array_code = f"{_JS_GET_GLOBAL}({json.dumps(element)})"
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (isinstance(element, int) and not isinstance(element, bool)) or isinstance(element, str):
|
||||||
|
self.inlined_stl.add("__getProperty")
|
||||||
|
array_code = f"__getProperty({array_code}, {json.dumps(element)}, true)"
|
||||||
|
else:
|
||||||
|
raise QueryError(f"Unsupported element: {element} ({type(element)})")
|
||||||
|
return array_code
|
||||||
|
|
||||||
|
def visit_tuple_access(self, node: ast.TupleAccess):
|
||||||
|
tuple_code = self.visit(node.tuple)
|
||||||
|
index_code = str(node.index)
|
||||||
|
self.inlined_stl.add("__getProperty")
|
||||||
|
return f"__getProperty({tuple_code}, {index_code}, {json.dumps(node.nullish)})"
|
||||||
|
|
||||||
|
def visit_array_access(self, node: ast.ArrayAccess):
|
||||||
|
array_code = self.visit(node.array)
|
||||||
|
property_code = self.visit(node.property)
|
||||||
|
self.inlined_stl.add("__getProperty")
|
||||||
|
return f"__getProperty({array_code}, {property_code}, {json.dumps(node.nullish)})"
|
||||||
|
|
||||||
|
def visit_constant(self, node: ast.Constant):
|
||||||
|
value = node.value
|
||||||
|
if value is True:
|
||||||
|
return "true"
|
||||||
|
elif value is False:
|
||||||
|
return "false"
|
||||||
|
elif value is None:
|
||||||
|
return "null"
|
||||||
|
elif isinstance(value, int | float | str):
|
||||||
|
return json.dumps(value)
|
||||||
|
else:
|
||||||
|
raise QueryError(f"Unsupported constant type: {type(value)}")
|
||||||
|
|
||||||
|
def visit_call(self, node: ast.Call):
|
||||||
|
if node.params is not None:
|
||||||
|
return self.visit(ast.ExprCall(expr=ast.Call(name=node.name, args=node.params), args=node.args or []))
|
||||||
|
|
||||||
|
# Handle special functions
|
||||||
|
if node.name == "not" and len(node.args) == 1:
|
||||||
|
expr_code = self.visit(node.args[0])
|
||||||
|
return f"(!{expr_code})"
|
||||||
|
if node.name == "and" and len(node.args) > 1:
|
||||||
|
exprs_code = " && ".join([self.visit(arg) for arg in node.args])
|
||||||
|
return f"({exprs_code})"
|
||||||
|
if node.name == "or" and len(node.args) > 1:
|
||||||
|
exprs_code = " || ".join([self.visit(arg) for arg in node.args])
|
||||||
|
return f"({exprs_code})"
|
||||||
|
if node.name == "if" and len(node.args) >= 2:
|
||||||
|
condition_code = self.visit(node.args[0])
|
||||||
|
then_code = self.visit(node.args[1])
|
||||||
|
else_code = self.visit(node.args[2]) if len(node.args) == 3 else "null"
|
||||||
|
return f"({condition_code} ? {then_code} : {else_code})"
|
||||||
|
if node.name == "multiIf" and len(node.args) >= 2:
|
||||||
|
|
||||||
|
def build_nested_if(args):
|
||||||
|
condition_code = self.visit(args[0])
|
||||||
|
then_code = self.visit(args[1])
|
||||||
|
if len(args) == 2:
|
||||||
|
return f"({condition_code} ? {then_code} : null)"
|
||||||
|
elif len(args) == 3:
|
||||||
|
else_code = self.visit(args[2])
|
||||||
|
return f"({condition_code} ? {then_code} : {else_code})"
|
||||||
|
else:
|
||||||
|
else_code = build_nested_if(args[2:])
|
||||||
|
return f"({condition_code} ? {then_code} : {else_code})"
|
||||||
|
|
||||||
|
return build_nested_if(node.args)
|
||||||
|
if node.name == "ifNull" and len(node.args) == 2:
|
||||||
|
expr_code = self.visit(node.args[0])
|
||||||
|
if_null_code = self.visit(node.args[1])
|
||||||
|
return f"({expr_code} ?? {if_null_code})"
|
||||||
|
|
||||||
|
if node.name in STL_FUNCTIONS:
|
||||||
|
self.inlined_stl.add(node.name)
|
||||||
|
name = _sanitize_identifier(node.name)
|
||||||
|
args_code = ", ".join(self.visit(arg) for arg in node.args)
|
||||||
|
return f"{name}({args_code})"
|
||||||
|
else:
|
||||||
|
# Regular function calls
|
||||||
|
name = _sanitize_identifier(node.name)
|
||||||
|
args_code = ", ".join([self.visit(arg) for arg in node.args or []])
|
||||||
|
return f"{name}({args_code})"
|
||||||
|
|
||||||
|
def visit_expr_call(self, node: ast.ExprCall):
|
||||||
|
func_code = self.visit(node.expr)
|
||||||
|
args_code = ", ".join([self.visit(arg) for arg in node.args])
|
||||||
|
return f"{func_code}({args_code})"
|
||||||
|
|
||||||
|
def visit_program(self, node: ast.Program):
|
||||||
|
code_lines = []
|
||||||
|
self._start_scope()
|
||||||
|
for declaration in node.declarations:
|
||||||
|
code = self.visit(declaration)
|
||||||
|
code_lines.append(self._indent(code))
|
||||||
|
self._end_scope()
|
||||||
|
return "\n".join(code_lines)
|
||||||
|
|
||||||
|
def visit_block(self, node: ast.Block):
|
||||||
|
code_lines = []
|
||||||
|
self._start_scope()
|
||||||
|
self.indent_level += 1
|
||||||
|
for declaration in node.declarations:
|
||||||
|
code = self.visit(declaration)
|
||||||
|
code_lines.append(self._indent(code))
|
||||||
|
self.indent_level -= 1
|
||||||
|
self._end_scope()
|
||||||
|
return "{\n" + "\n".join(code_lines) + "\n" + (" " * self.indent_level) + "}"
|
||||||
|
|
||||||
|
def visit_expr_statement(self, node: ast.ExprStatement):
|
||||||
|
if node.expr is None:
|
||||||
|
return ""
|
||||||
|
expr_code = self.visit(node.expr)
|
||||||
|
return expr_code + ";"
|
||||||
|
|
||||||
|
def visit_return_statement(self, node: ast.ReturnStatement):
|
||||||
|
if node.expr:
|
||||||
|
return f"return {self.visit(node.expr)};"
|
||||||
|
else:
|
||||||
|
return "return null;"
|
||||||
|
|
||||||
|
def visit_throw_statement(self, node: ast.ThrowStatement):
|
||||||
|
return f"throw {self.visit(node.expr)};"
|
||||||
|
|
||||||
|
def visit_try_catch_statement(self, node: ast.TryCatchStatement):
|
||||||
|
try_code = self.visit(_as_block(node.try_stmt))
|
||||||
|
code = "try " + try_code + " catch (__error) { "
|
||||||
|
has_catch_all = False
|
||||||
|
for index, catch in enumerate(node.catches):
|
||||||
|
catch_var = catch[0] or "e"
|
||||||
|
self._start_scope()
|
||||||
|
self._declare_local(catch_var)
|
||||||
|
catch_type = str(catch[1]) if catch[1] is not None else None
|
||||||
|
catch_declarations = _as_block(catch[2])
|
||||||
|
catch_code = "".join(self._indent(self.visit(d)) for d in catch_declarations.declarations)
|
||||||
|
if index > 0:
|
||||||
|
code += " else "
|
||||||
|
if catch_type is not None and catch_type != "Error":
|
||||||
|
code += (
|
||||||
|
f"if (__error.type === {json.dumps(catch_type)}) {{ let {_sanitize_identifier(catch_var)} = __error;\n"
|
||||||
|
f"{catch_code}\n"
|
||||||
|
f"}}\n"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
has_catch_all = True
|
||||||
|
code += f"if (true) {{ let {_sanitize_identifier(catch_var)} = __error;\n" f"{catch_code}\n" f"}}\n"
|
||||||
|
self._end_scope()
|
||||||
|
if not has_catch_all:
|
||||||
|
code += " else { throw __error; }"
|
||||||
|
code += "}"
|
||||||
|
|
||||||
|
if node.finally_stmt:
|
||||||
|
finally_code = self.visit(_as_block(node.finally_stmt))
|
||||||
|
code += " finally " + finally_code
|
||||||
|
return code
|
||||||
|
|
||||||
|
def visit_if_statement(self, node: ast.IfStatement):
|
||||||
|
expr_code = self.visit(node.expr)
|
||||||
|
then_code = self.visit(_as_block(node.then))
|
||||||
|
code = f"if ({expr_code}) {then_code}"
|
||||||
|
if node.else_:
|
||||||
|
else_code = self.visit(_as_block(node.else_))
|
||||||
|
code += f" else {else_code}"
|
||||||
|
return code
|
||||||
|
|
||||||
|
def visit_while_statement(self, node: ast.WhileStatement):
|
||||||
|
expr_code = self.visit(node.expr)
|
||||||
|
body_code = self.visit(_as_block(node.body))
|
||||||
|
return f"while ({expr_code}) {body_code}"
|
||||||
|
|
||||||
|
def visit_for_statement(self, node: ast.ForStatement):
|
||||||
|
self._start_scope()
|
||||||
|
init_code = self.visit(node.initializer) if node.initializer else ""
|
||||||
|
init_code = init_code[:-1] if init_code.endswith(";") else init_code
|
||||||
|
condition_code = self.visit(node.condition) if node.condition else ""
|
||||||
|
condition_code = condition_code[:-1] if condition_code.endswith(";") else condition_code
|
||||||
|
increment_code = self.visit(node.increment) if node.increment else ""
|
||||||
|
increment_code = increment_code[:-1] if increment_code.endswith(";") else increment_code
|
||||||
|
body_code = self.visit(_as_block(node.body))
|
||||||
|
self._end_scope()
|
||||||
|
return f"for ({init_code}; {condition_code}; {increment_code}) {body_code}"
|
||||||
|
|
||||||
|
def visit_for_in_statement(self, node: ast.ForInStatement):
|
||||||
|
expr_code = self.visit(node.expr)
|
||||||
|
if node.keyVar and node.valueVar:
|
||||||
|
self._start_scope()
|
||||||
|
self._declare_local(node.keyVar)
|
||||||
|
self._declare_local(node.valueVar)
|
||||||
|
body_code = self.visit(_as_block(node.body))
|
||||||
|
self.inlined_stl.add("keys")
|
||||||
|
resp = f"for (let {_sanitize_identifier(node.keyVar)} of keys({expr_code})) {{ let {_sanitize_identifier(node.valueVar)} = {expr_code}[{_sanitize_identifier(node.keyVar)}]; {body_code} }}"
|
||||||
|
self._end_scope()
|
||||||
|
return resp
|
||||||
|
elif node.valueVar:
|
||||||
|
self._start_scope()
|
||||||
|
self._declare_local(node.valueVar)
|
||||||
|
body_code = self.visit(_as_block(node.body))
|
||||||
|
self.inlined_stl.add("values")
|
||||||
|
resp = f"for (let {_sanitize_identifier(node.valueVar)} of values({expr_code})) {body_code}"
|
||||||
|
self._end_scope()
|
||||||
|
return resp
|
||||||
|
else:
|
||||||
|
raise QueryError("ForInStatement requires at least a valueVar")
|
||||||
|
|
||||||
|
def visit_variable_declaration(self, node: ast.VariableDeclaration):
|
||||||
|
self._declare_local(node.name)
|
||||||
|
if node.expr:
|
||||||
|
expr_code = self.visit(node.expr)
|
||||||
|
return f"let {_sanitize_identifier(node.name)} = {expr_code};"
|
||||||
|
else:
|
||||||
|
return f"let {_sanitize_identifier(node.name)};"
|
||||||
|
|
||||||
|
def visit_variable_assignment(self, node: ast.VariableAssignment):
|
||||||
|
if isinstance(node.left, ast.TupleAccess):
|
||||||
|
tuple_code = self.visit(node.left.tuple)
|
||||||
|
index = node.left.index
|
||||||
|
right_code = self.visit(node.right)
|
||||||
|
self.inlined_stl.add("__setProperty")
|
||||||
|
return f"__setProperty({tuple_code}, {index}, {right_code});"
|
||||||
|
|
||||||
|
elif isinstance(node.left, ast.ArrayAccess):
|
||||||
|
array_code = self.visit(node.left.array)
|
||||||
|
property_code = self.visit(node.left.property)
|
||||||
|
right_code = self.visit(node.right)
|
||||||
|
self.inlined_stl.add("__setProperty")
|
||||||
|
return f"__setProperty({array_code}, {property_code}, {right_code});"
|
||||||
|
|
||||||
|
elif isinstance(node.left, ast.Field):
|
||||||
|
chain = node.left.chain
|
||||||
|
name = chain[0]
|
||||||
|
is_local = any(local.name == name for local in self.locals)
|
||||||
|
|
||||||
|
if is_local:
|
||||||
|
array_code = ""
|
||||||
|
for index, element in enumerate(chain):
|
||||||
|
if index == 0:
|
||||||
|
array_code = _sanitize_identifier(element)
|
||||||
|
if len(chain) == 1:
|
||||||
|
array_code = f"{array_code} = {self.visit(node.right)}"
|
||||||
|
elif (isinstance(element, int) and not isinstance(element, bool)) or isinstance(element, str):
|
||||||
|
if index == len(chain) - 1:
|
||||||
|
right_code = self.visit(node.right)
|
||||||
|
self.inlined_stl.add("__setProperty")
|
||||||
|
array_code = f"__setProperty({array_code}, {json.dumps(element)}, {right_code})"
|
||||||
|
else:
|
||||||
|
self.inlined_stl.add("__getProperty")
|
||||||
|
array_code = f"__getProperty({array_code}, {json.dumps(element)}, true)"
|
||||||
|
else:
|
||||||
|
raise QueryError(f"Unsupported element: {element} ({type(element)})")
|
||||||
|
return array_code
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Cannot assign to undeclared variables or globals
|
||||||
|
raise QueryError(f'Variable "{name}" not declared in this scope. Cannot assign to globals.')
|
||||||
|
|
||||||
|
else:
|
||||||
|
left_code = self.visit(node.left)
|
||||||
|
right_code = self.visit(node.right)
|
||||||
|
return f"{left_code} = {right_code};"
|
||||||
|
|
||||||
|
def visit_function(self, node: ast.Function):
|
||||||
|
self._declare_local(_sanitize_identifier(node.name))
|
||||||
|
params_code = ", ".join(_sanitize_identifier(p) for p in node.params)
|
||||||
|
self._start_scope()
|
||||||
|
for arg in node.params:
|
||||||
|
self._declare_local(arg)
|
||||||
|
if isinstance(node.body, ast.Placeholder):
|
||||||
|
body_code = ast.Block(declarations=[ast.ExprStatement(expr=node.body.expr), ast.ReturnStatement(expr=None)])
|
||||||
|
else:
|
||||||
|
body_code = self.visit(_as_block(node.body))
|
||||||
|
self._end_scope()
|
||||||
|
return f"function {_sanitize_identifier(node.name)}({params_code}) {body_code}"
|
||||||
|
|
||||||
|
def visit_lambda(self, node: ast.Lambda):
|
||||||
|
params_code = ", ".join(_sanitize_identifier(p) for p in node.args)
|
||||||
|
self._start_scope()
|
||||||
|
for arg in node.args:
|
||||||
|
self._declare_local(arg)
|
||||||
|
if isinstance(node.expr, ast.Placeholder):
|
||||||
|
expr_code = self.visit(
|
||||||
|
ast.Block(declarations=[ast.ExprStatement(expr=node.expr.expr), ast.ReturnStatement(expr=None)])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expr_code = self.visit(node.expr)
|
||||||
|
self._end_scope()
|
||||||
|
self.inlined_stl.add("__lambda")
|
||||||
|
# we wrap it in __lambda() to make the function anonymous (a true lambda without a name)
|
||||||
|
return f"__lambda(({params_code}) => {expr_code})"
|
||||||
|
|
||||||
|
def visit_dict(self, node: ast.Dict):
|
||||||
|
items = []
|
||||||
|
for key, value in node.items:
|
||||||
|
key_code = self.visit(key)
|
||||||
|
if not isinstance(key, ast.Constant) or not isinstance(key.value, str):
|
||||||
|
key_code = f"[{key_code}]"
|
||||||
|
value_code = self.visit(value)
|
||||||
|
items.append(f"{key_code}: {value_code}")
|
||||||
|
items_code = ", ".join(items)
|
||||||
|
return f"{{{items_code}}}"
|
||||||
|
|
||||||
|
def visit_array(self, node: ast.Array):
|
||||||
|
items_code = ", ".join([self.visit(expr) for expr in node.exprs])
|
||||||
|
return f"[{items_code}]"
|
||||||
|
|
||||||
|
def visit_tuple(self, node: ast.Tuple):
|
||||||
|
items_code = ", ".join([self.visit(expr) for expr in node.exprs])
|
||||||
|
self.inlined_stl.add("tuple")
|
||||||
|
return f"tuple({items_code})"
|
||||||
|
|
||||||
|
def visit_hogqlx_tag(self, node: ast.HogQLXTag):
|
||||||
|
attrs = [f'"__hx_tag": {json.dumps(node.kind)}']
|
||||||
|
for attr in node.attributes:
|
||||||
|
attrs.append(f'"{attr.name}": {self._visit_hogqlx_value(attr.value)}')
|
||||||
|
return f'{{{", ".join(attrs)}}}'
|
||||||
|
|
||||||
|
def _visit_hogqlx_value(self, value: Any) -> str:
|
||||||
|
if isinstance(value, AST):
|
||||||
|
return self.visit(value)
|
||||||
|
if isinstance(value, list):
|
||||||
|
elems = ", ".join([self._visit_hogqlx_value(v) for v in value])
|
||||||
|
return f"[{elems}]"
|
||||||
|
if isinstance(value, dict):
|
||||||
|
items = ", ".join(
|
||||||
|
[f"{self._visit_hogqlx_value(k)}: {self._visit_hogqlx_value(v)}" for k, v in value.items()]
|
||||||
|
)
|
||||||
|
return f"{{{items}}}"
|
||||||
|
if isinstance(value, StrEnum):
|
||||||
|
return '"' + str(value.value) + '"'
|
||||||
|
if value is True:
|
||||||
|
return "true"
|
||||||
|
if value is False:
|
||||||
|
return "false"
|
||||||
|
if isinstance(value, int | float):
|
||||||
|
return str(value)
|
||||||
|
if isinstance(value, str):
|
||||||
|
return json.dumps(value)
|
||||||
|
return "null"
|
||||||
|
|
||||||
|
def visit_select_query(self, node: ast.SelectQuery):
|
||||||
|
raise NotImplementedError("JavaScriptCompiler does not support SelectQuery")
|
923
posthog/hogql/compiler/javascript_stl.py
Normal file
923
posthog/hogql/compiler/javascript_stl.py
Normal file
@ -0,0 +1,923 @@
|
|||||||
|
# TODO: this should be autogenerated from hogvm/typescript/src/stl/*
|
||||||
|
|
||||||
|
STL_FUNCTIONS: dict[str, list[str | list[str]]] = {
|
||||||
|
"concat": [
|
||||||
|
"function concat (...args) { return args.map((arg) => (arg === null ? '' : __STLToString(arg))).join('') }",
|
||||||
|
["__STLToString"],
|
||||||
|
],
|
||||||
|
"match": [
|
||||||
|
"function match (str, pattern) { return new RegExp(pattern).test(str) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"like": [
|
||||||
|
"function like (str, pattern) { return __like(str, pattern, false) }",
|
||||||
|
["__like"],
|
||||||
|
],
|
||||||
|
"ilike": [
|
||||||
|
"function ilike (str, pattern) { return __like(str, pattern, true) }",
|
||||||
|
["__like"],
|
||||||
|
],
|
||||||
|
"notLike": [
|
||||||
|
"function notLike (str, pattern) { return !__like(str, pattern, false) }",
|
||||||
|
["__like"],
|
||||||
|
],
|
||||||
|
"notILike": [
|
||||||
|
"function notILike (str, pattern) { return !__like(str, pattern, true) }",
|
||||||
|
["__like"],
|
||||||
|
],
|
||||||
|
"toString": [
|
||||||
|
"function toString (value) { return __STLToString(value) }",
|
||||||
|
["__STLToString"],
|
||||||
|
],
|
||||||
|
"toUUID": [
|
||||||
|
"function toUUID (value) { return __STLToString(value) }",
|
||||||
|
["__STLToString"],
|
||||||
|
],
|
||||||
|
"toInt": [
|
||||||
|
"""function toInt(value) {
|
||||||
|
if (__isHogDateTime(value)) { return Math.floor(value.dt); }
|
||||||
|
else if (__isHogDate(value)) { const date = new Date(Date.UTC(value.year, value.month - 1, value.day)); const epoch = new Date(Date.UTC(1970, 0, 1)); const diffInDays = Math.floor((date - epoch) / (1000 * 60 * 60 * 24)); return diffInDays; }
|
||||||
|
return !isNaN(parseInt(value)) ? parseInt(value) : null; }""",
|
||||||
|
["__isHogDateTime", "__isHogDate"],
|
||||||
|
],
|
||||||
|
"toFloat": [
|
||||||
|
"""function toFloat(value) {
|
||||||
|
if (__isHogDateTime(value)) { return value.dt; }
|
||||||
|
else if (__isHogDate(value)) { const date = new Date(Date.UTC(value.year, value.month - 1, value.day)); const epoch = new Date(Date.UTC(1970, 0, 1)); const diffInDays = (date - epoch) / (1000 * 60 * 60 * 24); return diffInDays; }
|
||||||
|
return !isNaN(parseFloat(value)) ? parseFloat(value) : null; }""",
|
||||||
|
["__isHogDateTime", "__isHogDate"],
|
||||||
|
],
|
||||||
|
"ifNull": [
|
||||||
|
"function ifNull (value, defaultValue) { return value !== null ? value : defaultValue } ",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"length": [
|
||||||
|
"function length (value) { return value.length }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"empty": [
|
||||||
|
"""function empty (value) {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
if (Array.isArray(value)) { return value.length === 0 } else if (value === null) { return true } else if (value instanceof Map) { return value.size === 0 }
|
||||||
|
return Object.keys(value).length === 0
|
||||||
|
} else if (typeof value === 'number' || typeof value === 'boolean') { return false }
|
||||||
|
return !value }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"notEmpty": [
|
||||||
|
"function notEmpty (value) { return !empty(value) }",
|
||||||
|
["empty"],
|
||||||
|
],
|
||||||
|
"tuple": [
|
||||||
|
"function tuple (...args) { const tuple = args.slice(); tuple.__isHogTuple = true; return tuple; }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"lower": [
|
||||||
|
"function lower (value) { return value.toLowerCase() }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"upper": [
|
||||||
|
"function upper (value) { return value.toUpperCase() }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"reverse": [
|
||||||
|
"function reverse (value) { return value.split('').reverse().join('') }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"print": [
|
||||||
|
"function print (...args) { console.log(...args.map(__printHogStringOutput)) }",
|
||||||
|
["__printHogStringOutput"],
|
||||||
|
],
|
||||||
|
"jsonParse": [
|
||||||
|
"""function jsonParse (str) {
|
||||||
|
function convert(x) {
|
||||||
|
if (Array.isArray(x)) { return x.map(convert) }
|
||||||
|
else if (typeof x === 'object' && x !== null) {
|
||||||
|
if (x.__hogDateTime__) { return __toHogDateTime(x.dt, x.zone)
|
||||||
|
} else if (x.__hogDate__) { return __toHogDate(x.year, x.month, x.day)
|
||||||
|
} else if (x.__hogError__) { return __newHogError(x.type, x.message, x.payload) }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key]) }; return obj }
|
||||||
|
return x }
|
||||||
|
return convert(JSON.parse(str)) }""",
|
||||||
|
["__toHogDateTime", "__toHogDate", "__newHogError"],
|
||||||
|
],
|
||||||
|
"jsonStringify": [
|
||||||
|
"""function jsonStringify (value, spacing) {
|
||||||
|
function convert(x, marked) {
|
||||||
|
if (!marked) { marked = new Set() }
|
||||||
|
if (typeof x === 'object' && x !== null) {
|
||||||
|
if (marked.has(x)) { return null }
|
||||||
|
marked.add(x)
|
||||||
|
try {
|
||||||
|
if (x instanceof Map) {
|
||||||
|
const obj = {}
|
||||||
|
x.forEach((value, key) => { obj[convert(key, marked)] = convert(value, marked) })
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
if (Array.isArray(x)) { return x.map((v) => convert(v, marked)) }
|
||||||
|
if (__isHogDateTime(x) || __isHogDate(x) || __isHogError(x)) { return x }
|
||||||
|
if (typeof x === 'function') { return `fn<${x.name || 'lambda'}(${x.length})>` }
|
||||||
|
const obj = {}; for (const key in x) { obj[key] = convert(x[key], marked) }
|
||||||
|
return obj
|
||||||
|
} finally {
|
||||||
|
marked.delete(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
if (spacing && typeof spacing === 'number' && spacing > 0) {
|
||||||
|
return JSON.stringify(convert(value), null, spacing)
|
||||||
|
}
|
||||||
|
return JSON.stringify(convert(value), (key, val) => typeof val === 'function' ? `fn<${val.name || 'lambda'}(${val.length})>` : val)
|
||||||
|
}""",
|
||||||
|
["__isHogDateTime", "__isHogDate", "__isHogError"],
|
||||||
|
],
|
||||||
|
"JSONHas": [
|
||||||
|
"""function JSONHas (obj, ...path) {
|
||||||
|
let current = obj
|
||||||
|
for (const key of path) {
|
||||||
|
let currentParsed = current
|
||||||
|
if (typeof current === 'string') { try { currentParsed = JSON.parse(current) } catch (e) { return false } }
|
||||||
|
if (currentParsed instanceof Map) { if (!currentParsed.has(key)) { return false }; current = currentParsed.get(key) }
|
||||||
|
else if (typeof currentParsed === 'object' && currentParsed !== null) {
|
||||||
|
if (typeof key === 'number') {
|
||||||
|
if (Array.isArray(currentParsed)) {
|
||||||
|
if (key < 0) { if (key < -currentParsed.length) { return false }; current = currentParsed[currentParsed.length + key] }
|
||||||
|
else if (key === 0) { return false }
|
||||||
|
else { if (key > currentParsed.length) { return false }; current = currentParsed[key - 1] }
|
||||||
|
} else { return false }
|
||||||
|
} else {
|
||||||
|
if (!(key in currentParsed)) { return false }
|
||||||
|
current = currentParsed[key]
|
||||||
|
}
|
||||||
|
} else { return false }
|
||||||
|
}
|
||||||
|
return true }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"isValidJSON": [
|
||||||
|
"function isValidJSON (str) { try { JSON.parse(str); return true } catch (e) { return false } }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"JSONLength": [
|
||||||
|
"""function JSONLength (obj, ...path) {
|
||||||
|
try { if (typeof obj === 'string') { obj = JSON.parse(obj) } } catch (e) { return 0 }
|
||||||
|
if (typeof obj === 'object' && obj !== null) {
|
||||||
|
const value = __getNestedValue(obj, path, true)
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.length
|
||||||
|
} else if (value instanceof Map) {
|
||||||
|
return value.size
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
return Object.keys(value).length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0 }""",
|
||||||
|
["__getNestedValue"],
|
||||||
|
],
|
||||||
|
"JSONExtractBool": [
|
||||||
|
"""function JSONExtractBool (obj, ...path) {
|
||||||
|
try {
|
||||||
|
if (typeof obj === 'string') {
|
||||||
|
obj = JSON.parse(obj)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (path.length > 0) {
|
||||||
|
obj = __getNestedValue(obj, path, true)
|
||||||
|
}
|
||||||
|
if (typeof obj === 'boolean') {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}""",
|
||||||
|
["__getNestedValue"],
|
||||||
|
],
|
||||||
|
"base64Encode": [
|
||||||
|
"function base64Encode (str) { return Buffer.from(str).toString('base64') }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"base64Decode": [
|
||||||
|
"function base64Decode (str) { return Buffer.from(str, 'base64').toString() } ",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"tryBase64Decode": [
|
||||||
|
"function tryBase64Decode (str) { try { return Buffer.from(str, 'base64').toString() } catch (e) { return '' } }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"encodeURLComponent": [
|
||||||
|
"function encodeURLComponent (str) { return encodeURIComponent(str) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"decodeURLComponent": [
|
||||||
|
"function decodeURLComponent (str) { return decodeURIComponent(str) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"replaceOne": [
|
||||||
|
"function replaceOne (str, searchValue, replaceValue) { return str.replace(searchValue, replaceValue) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"replaceAll": [
|
||||||
|
"function replaceAll (str, searchValue, replaceValue) { return str.replaceAll(searchValue, replaceValue) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"position": [
|
||||||
|
"function position (str, elem) { if (typeof str === 'string') { return str.indexOf(String(elem)) + 1 } else { return 0 } }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"positionCaseInsensitive": [
|
||||||
|
"function positionCaseInsensitive (str, elem) { if (typeof str === 'string') { return str.toLowerCase().indexOf(String(elem).toLowerCase()) + 1 } else { return 0 } }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"trim": [
|
||||||
|
"""function trim (str, char) {
|
||||||
|
if (char === null || char === undefined) {
|
||||||
|
char = ' '
|
||||||
|
}
|
||||||
|
if (char.length !== 1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
let start = 0
|
||||||
|
while (str[start] === char) {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
let end = str.length
|
||||||
|
while (str[end - 1] === char) {
|
||||||
|
end--
|
||||||
|
}
|
||||||
|
if (start >= end) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
return str.slice(start, end)
|
||||||
|
}""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"trimLeft": [
|
||||||
|
"""function trimLeft (str, char) {
|
||||||
|
if (char === null || char === undefined) {
|
||||||
|
char = ' '
|
||||||
|
}
|
||||||
|
if (char.length !== 1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
let start = 0
|
||||||
|
while (str[start] === char) {
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
return str.slice(start)
|
||||||
|
}""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"trimRight": [
|
||||||
|
"""function trimRight (str, char) {
|
||||||
|
if (char === null || char === undefined) {
|
||||||
|
char = ' '
|
||||||
|
}
|
||||||
|
if (char.length !== 1) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
let end = str.length
|
||||||
|
while (str[end - 1] === char) {
|
||||||
|
end--
|
||||||
|
}
|
||||||
|
return str.slice(0, end)
|
||||||
|
}""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"splitByString": [
|
||||||
|
"function splitByString (separator, str, maxSplits) { if (maxSplits === undefined || maxSplits === null) { return str.split(separator) } return str.split(separator, maxSplits) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"generateUUIDv4": [
|
||||||
|
"function generateUUIDv4 () { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16) })}",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"sha256Hex": [
|
||||||
|
"function sha256Hex (str, options) { return 'SHA256 is not implemented' }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"md5Hex": [
|
||||||
|
"""function md5Hex(string) { return 'MD5 is not implemented' }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"sha256HmacChainHex": [
|
||||||
|
"function sha256HmacChainHex (data, options) { return 'sha256HmacChainHex not implemented' }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"keys": [
|
||||||
|
"""function keys (obj) { if (typeof obj === 'object' && obj !== null) { if (Array.isArray(obj)) { return Array.from(obj.keys()) } else if (obj instanceof Map) { return Array.from(obj.keys()) } return Object.keys(obj) } return [] }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"values": [
|
||||||
|
"""function values (obj) { if (typeof obj === 'object' && obj !== null) { if (Array.isArray(obj)) { return [...obj] } else if (obj instanceof Map) { return Array.from(obj.values()) } return Object.values(obj) } return [] }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"indexOf": [
|
||||||
|
"function indexOf (arrOrString, elem) { if (Array.isArray(arrOrString)) { return arrOrString.indexOf(elem) + 1 } else { return 0 } }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayPushBack": [
|
||||||
|
"function arrayPushBack (arr, item) { if (!Array.isArray(arr)) { return [item] } return [...arr, item] }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayPushFront": [
|
||||||
|
"function arrayPushFront (arr, item) { if (!Array.isArray(arr)) { return [item] } return [item, ...arr] }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayPopBack": [
|
||||||
|
"function arrayPopBack (arr) { if (!Array.isArray(arr)) { return [] } return arr.slice(0, arr.length - 1) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayPopFront": [
|
||||||
|
"function arrayPopFront (arr) { if (!Array.isArray(arr)) { return [] } return arr.slice(1) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arraySort": [
|
||||||
|
"function arraySort (arr) { if (!Array.isArray(arr)) { return [] } return [...arr].sort() }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayReverse": [
|
||||||
|
"function arrayReverse (arr) { if (!Array.isArray(arr)) { return [] } return [...arr].reverse() }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayReverseSort": [
|
||||||
|
"function arrayReverseSort (arr) { if (!Array.isArray(arr)) { return [] } return [...arr].sort().reverse() }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayStringConcat": [
|
||||||
|
"function arrayStringConcat (arr, separator = '') { if (!Array.isArray(arr)) { return '' } return arr.join(separator) }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayCount": [
|
||||||
|
"function arrayCount (func, arr) { let count = 0; for (let i = 0; i < arr.length; i++) { if (func(arr[i])) { count = count + 1 } } return count }",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayExists": [
|
||||||
|
"""function arrayExists (func, arr) { for (let i = 0; i < arr.length; i++) { if (func(arr[i])) { return true } } return false }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"arrayFilter": [
|
||||||
|
"""function arrayFilter (func, arr) { let result = []; for (let i = 0; i < arr.length; i++) { if (func(arr[i])) { result = arrayPushBack(result, arr[i]) } } return result}""",
|
||||||
|
["arrayPushBack"],
|
||||||
|
],
|
||||||
|
"arrayMap": [
|
||||||
|
"""function arrayMap (func, arr) { let result = []; for (let i = 0; i < arr.length; i++) { result = arrayPushBack(result, func(arr[i])) } return result }""",
|
||||||
|
["arrayPushBack"],
|
||||||
|
],
|
||||||
|
"has": [
|
||||||
|
"""function has (arr, elem) { if (!Array.isArray(arr) || arr.length === 0) { return false } return arr.includes(elem) }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"now": [
|
||||||
|
"""function now () { return __now() }""",
|
||||||
|
["__now"],
|
||||||
|
],
|
||||||
|
"toUnixTimestamp": [
|
||||||
|
"""function toUnixTimestamp (input, zone) { return __toUnixTimestamp(input, zone) }""",
|
||||||
|
["__toUnixTimestamp"],
|
||||||
|
],
|
||||||
|
"fromUnixTimestamp": [
|
||||||
|
"""function fromUnixTimestamp (input) { return __fromUnixTimestamp(input) }""",
|
||||||
|
["__fromUnixTimestamp"],
|
||||||
|
],
|
||||||
|
"toUnixTimestampMilli": [
|
||||||
|
"""function toUnixTimestampMilli (input, zone) { return __toUnixTimestampMilli(input, zone) }""",
|
||||||
|
["__toUnixTimestampMilli"],
|
||||||
|
],
|
||||||
|
"fromUnixTimestampMilli": [
|
||||||
|
"""function fromUnixTimestampMilli (input) { return __fromUnixTimestampMilli(input) }""",
|
||||||
|
["__fromUnixTimestampMilli"],
|
||||||
|
],
|
||||||
|
"toTimeZone": [
|
||||||
|
"""function toTimeZone (input, zone) { return __toTimeZone(input, zone) }""",
|
||||||
|
["__toTimeZone"],
|
||||||
|
],
|
||||||
|
"toDate": [
|
||||||
|
"""function toDate (input) { return __toDate(input) }""",
|
||||||
|
["__toDate"],
|
||||||
|
],
|
||||||
|
"toDateTime": [
|
||||||
|
"""function toDateTime (input, zone) { return __toDateTime(input, zone) }""",
|
||||||
|
["__toDateTime"],
|
||||||
|
],
|
||||||
|
"formatDateTime": [
|
||||||
|
"""function formatDateTime (input, format, zone) { return __formatDateTime(input, format, zone) }""",
|
||||||
|
["__formatDateTime"],
|
||||||
|
],
|
||||||
|
"HogError": [
|
||||||
|
"""function HogError (type, message, payload) { return __newHogError(type, message, payload) }""",
|
||||||
|
["__newHogError"],
|
||||||
|
],
|
||||||
|
"Error": [
|
||||||
|
"""function __x_Error (message, payload) { return __newHogError('Error', message, payload) }""",
|
||||||
|
["__newHogError"],
|
||||||
|
],
|
||||||
|
"RetryError": [
|
||||||
|
"""function RetryError (message, payload) { return __newHogError('RetryError', message, payload) }""",
|
||||||
|
["__newHogError"],
|
||||||
|
],
|
||||||
|
"NotImplementedError": [
|
||||||
|
"""function NotImplementedError (message, payload) { return __newHogError('NotImplementedError', message, payload) }""",
|
||||||
|
["__newHogError"],
|
||||||
|
],
|
||||||
|
"typeof": [
|
||||||
|
"""function __x_typeof (value) {
|
||||||
|
if (value === null || value === undefined) { return 'null'
|
||||||
|
} else if (__isHogDateTime(value)) { return 'datetime'
|
||||||
|
} else if (__isHogDate(value)) { return 'date'
|
||||||
|
} else if (__isHogError(value)) { return 'error'
|
||||||
|
} else if (typeof value === 'function') { return 'function'
|
||||||
|
} else if (Array.isArray(value)) { if (value.__isHogTuple) { return 'tuple' } return 'array'
|
||||||
|
} else if (typeof value === 'object') { return 'object'
|
||||||
|
} else if (typeof value === 'number') { return Number.isInteger(value) ? 'integer' : 'float'
|
||||||
|
} else if (typeof value === 'string') { return 'string'
|
||||||
|
} else if (typeof value === 'boolean') { return 'boolean' }
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
["__isHogDateTime", "__isHogDate", "__isHogError"],
|
||||||
|
],
|
||||||
|
"__DateTimeToString": [
|
||||||
|
r"""function __DateTimeToString(dt) {
|
||||||
|
if (__isHogDateTime(dt)) {
|
||||||
|
const date = new Date(dt.dt * 1000);
|
||||||
|
const timeZone = dt.zone || 'UTC';
|
||||||
|
const milliseconds = Math.floor(dt.dt * 1000 % 1000);
|
||||||
|
const options = { timeZone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false };
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', options);
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
let year, month, day, hour, minute, second;
|
||||||
|
for (const part of parts) {
|
||||||
|
switch (part.type) {
|
||||||
|
case 'year': year = part.value; break;
|
||||||
|
case 'month': month = part.value; break;
|
||||||
|
case 'day': day = part.value; break;
|
||||||
|
case 'hour': hour = part.value; break;
|
||||||
|
case 'minute': minute = part.value; break;
|
||||||
|
case 'second': second = part.value; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const getOffset = (date, timeZone) => {
|
||||||
|
const tzDate = new Date(date.toLocaleString('en-US', { timeZone }));
|
||||||
|
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||||
|
const offset = (tzDate - utcDate) / 60000; // in minutes
|
||||||
|
const sign = offset >= 0 ? '+' : '-';
|
||||||
|
const absOffset = Math.abs(offset);
|
||||||
|
const hours = Math.floor(absOffset / 60);
|
||||||
|
const minutes = absOffset % 60;
|
||||||
|
return `${sign}${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
let offset = 'Z';
|
||||||
|
if (timeZone !== 'UTC') {
|
||||||
|
offset = getOffset(date, timeZone);
|
||||||
|
}
|
||||||
|
let isoString = `${year}-${month}-${day}T${hour}:${minute}:${second}`;
|
||||||
|
isoString += `.${milliseconds.toString().padStart(3, '0')}`;
|
||||||
|
isoString += offset;
|
||||||
|
return isoString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__STLToString": [
|
||||||
|
r"""function __STLToString(arg) {
|
||||||
|
if (arg && __isHogDate(arg)) { return `${arg.year}-${arg.month.toString().padStart(2, '0')}-${arg.day.toString().padStart(2, '0')}`; }
|
||||||
|
else if (arg && __isHogDateTime(arg)) { return __DateTimeToString(arg); }
|
||||||
|
return __printHogStringOutput(arg); }""",
|
||||||
|
["__isHogDate", "__isHogDateTime", "__printHogStringOutput", "__DateTimeToString"],
|
||||||
|
],
|
||||||
|
"__isHogDate": [
|
||||||
|
"""function __isHogDate(obj) { return obj && obj.__hogDate__ === true }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__isHogDateTime": [
|
||||||
|
"""function __isHogDateTime(obj) { return obj && obj.__hogDateTime__ === true }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__toHogDate": [
|
||||||
|
"""function __toHogDate(year, month, day) { return { __hogDate__: true, year: year, month: month, day: day, } }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__toHogDateTime": [
|
||||||
|
"""function __toHogDateTime(timestamp, zone) {
|
||||||
|
if (__isHogDate(timestamp)) {
|
||||||
|
const date = new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day));
|
||||||
|
const dt = date.getTime() / 1000;
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' };
|
||||||
|
}
|
||||||
|
return { __hogDateTime__: true, dt: timestamp, zone: zone || 'UTC' }; }""",
|
||||||
|
["__isHogDate"],
|
||||||
|
],
|
||||||
|
"__now": [
|
||||||
|
"""function __now(zone) { return __toHogDateTime(Date.now() / 1000, zone) }""",
|
||||||
|
["__toHogDateTime"],
|
||||||
|
],
|
||||||
|
"__toUnixTimestamp": [
|
||||||
|
"""function __toUnixTimestamp(input, zone) {
|
||||||
|
if (__isHogDateTime(input)) { return input.dt; }
|
||||||
|
if (__isHogDate(input)) { return __toHogDateTime(input).dt; }
|
||||||
|
const date = new Date(input);
|
||||||
|
if (isNaN(date.getTime())) { throw new Error('Invalid date input'); }
|
||||||
|
return Math.floor(date.getTime() / 1000);}""",
|
||||||
|
["__isHogDateTime", "__isHogDate", "__toHogDateTime"],
|
||||||
|
],
|
||||||
|
"__fromUnixTimestamp": [
|
||||||
|
"""function __fromUnixTimestamp(input) { return __toHogDateTime(input) }""",
|
||||||
|
["__toHogDateTime"],
|
||||||
|
],
|
||||||
|
"__toUnixTimestampMilli": [
|
||||||
|
"""function __toUnixTimestampMilli(input, zone) { return __toUnixTimestamp(input, zone) * 1000 }""",
|
||||||
|
["__toUnixTimestamp"],
|
||||||
|
],
|
||||||
|
"__fromUnixTimestampMilli": [
|
||||||
|
"""function __fromUnixTimestampMilli(input) { return __toHogDateTime(input / 1000) }""",
|
||||||
|
["__toHogDateTime"],
|
||||||
|
],
|
||||||
|
"__toTimeZone": [
|
||||||
|
"""function __toTimeZone(input, zone) { if (!__isHogDateTime(input)) { throw new Error('Expected a DateTime') }; return { ...input, zone }}""",
|
||||||
|
["__isHogDateTime"],
|
||||||
|
],
|
||||||
|
"__toDate": [
|
||||||
|
"""function __toDate(input) { let date;
|
||||||
|
if (typeof input === 'number') { date = new Date(input * 1000); } else { date = new Date(input); }
|
||||||
|
if (isNaN(date.getTime())) { throw new Error('Invalid date input'); }
|
||||||
|
return { __hogDate__: true, year: date.getUTCFullYear(), month: date.getUTCMonth() + 1, day: date.getUTCDate() }; }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__toDateTime": [
|
||||||
|
"""function __toDateTime(input, zone) { let dt;
|
||||||
|
if (typeof input === 'number') { dt = input; }
|
||||||
|
else { const date = new Date(input); if (isNaN(date.getTime())) { throw new Error('Invalid date input'); } dt = date.getTime() / 1000; }
|
||||||
|
return { __hogDateTime__: true, dt: dt, zone: zone || 'UTC' }; }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__formatDateTime": [
|
||||||
|
"""function __formatDateTime(input, format, zone) {
|
||||||
|
if (!__isHogDateTime(input)) { throw new Error('Expected a DateTime'); }
|
||||||
|
if (!format) { throw new Error('formatDateTime requires at least 2 arguments'); }
|
||||||
|
const timestamp = input.dt * 1000;
|
||||||
|
let date = new Date(timestamp);
|
||||||
|
if (!zone) { zone = 'UTC'; }
|
||||||
|
const padZero = (num, len = 2) => String(num).padStart(len, '0');
|
||||||
|
const padSpace = (num, len = 2) => String(num).padStart(len, ' ');
|
||||||
|
const getDateComponent = (type, options = {}) => {
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', { ...options, timeZone: zone });
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const part = parts.find(p => p.type === type);
|
||||||
|
return part ? part.value : '';
|
||||||
|
};
|
||||||
|
const getNumericComponent = (type, options = {}) => {
|
||||||
|
const value = getDateComponent(type, options);
|
||||||
|
return parseInt(value, 10);
|
||||||
|
};
|
||||||
|
const getWeekNumber = (d) => {
|
||||||
|
const dateInZone = new Date(d.toLocaleString('en-US', { timeZone: zone }));
|
||||||
|
const target = new Date(Date.UTC(dateInZone.getFullYear(), dateInZone.getMonth(), dateInZone.getDate()));
|
||||||
|
const dayNr = (target.getUTCDay() + 6) % 7;
|
||||||
|
target.setUTCDate(target.getUTCDate() - dayNr + 3);
|
||||||
|
const firstThursday = new Date(Date.UTC(target.getUTCFullYear(), 0, 4));
|
||||||
|
const weekNumber = 1 + Math.round(((target - firstThursday) / 86400000 - 3 + ((firstThursday.getUTCDay() + 6) % 7)) / 7);
|
||||||
|
return weekNumber;
|
||||||
|
};
|
||||||
|
const getDayOfYear = (d) => {
|
||||||
|
const startOfYear = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||||
|
const dateInZone = new Date(d.toLocaleString('en-US', { timeZone: zone }));
|
||||||
|
const diff = dateInZone - startOfYear;
|
||||||
|
return Math.floor(diff / 86400000) + 1;
|
||||||
|
};
|
||||||
|
// Token mapping with corrections
|
||||||
|
const tokens = {
|
||||||
|
'%a': () => getDateComponent('weekday', { weekday: 'short' }),
|
||||||
|
'%b': () => getDateComponent('month', { month: 'short' }),
|
||||||
|
'%c': () => padZero(getNumericComponent('month', { month: '2-digit' })),
|
||||||
|
'%C': () => getDateComponent('year', { year: '2-digit' }),
|
||||||
|
'%d': () => padZero(getNumericComponent('day', { day: '2-digit' })),
|
||||||
|
'%D': () => {
|
||||||
|
const month = padZero(getNumericComponent('month', { month: '2-digit' }));
|
||||||
|
const day = padZero(getNumericComponent('day', { day: '2-digit' }));
|
||||||
|
const year = getDateComponent('year', { year: '2-digit' });
|
||||||
|
return `${month}/${day}/${year}`;
|
||||||
|
},
|
||||||
|
'%e': () => padSpace(getNumericComponent('day', { day: 'numeric' })),
|
||||||
|
'%F': () => {
|
||||||
|
const year = getNumericComponent('year', { year: 'numeric' });
|
||||||
|
const month = padZero(getNumericComponent('month', { month: '2-digit' }));
|
||||||
|
const day = padZero(getNumericComponent('day', { day: '2-digit' }));
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
},
|
||||||
|
'%g': () => getDateComponent('year', { year: '2-digit' }),
|
||||||
|
'%G': () => getNumericComponent('year', { year: 'numeric' }),
|
||||||
|
'%h': () => padZero(getNumericComponent('hour', { hour: '2-digit', hour12: true })),
|
||||||
|
'%H': () => padZero(getNumericComponent('hour', { hour: '2-digit', hour12: false })),
|
||||||
|
'%i': () => padZero(getNumericComponent('minute', { minute: '2-digit' })),
|
||||||
|
'%I': () => padZero(getNumericComponent('hour', { hour: '2-digit', hour12: true })),
|
||||||
|
'%j': () => padZero(getDayOfYear(date), 3),
|
||||||
|
'%k': () => padSpace(getNumericComponent('hour', { hour: 'numeric', hour12: false })),
|
||||||
|
'%l': () => padZero(getNumericComponent('hour', { hour: '2-digit', hour12: true })),
|
||||||
|
'%m': () => padZero(getNumericComponent('month', { month: '2-digit' })),
|
||||||
|
'%M': () => getDateComponent('month', { month: 'long' }),
|
||||||
|
'%n': () => '\\n',
|
||||||
|
'%p': () => getDateComponent('dayPeriod', { hour: 'numeric', hour12: true }),
|
||||||
|
'%r': () => {
|
||||||
|
const hour = padZero(getNumericComponent('hour', { hour: '2-digit', hour12: true }));
|
||||||
|
const minute = padZero(getNumericComponent('minute', { minute: '2-digit' }));
|
||||||
|
const second = padZero(getNumericComponent('second', { second: '2-digit' }));
|
||||||
|
const period = getDateComponent('dayPeriod', { hour: 'numeric', hour12: true });
|
||||||
|
return `${hour}:${minute} ${period}`;
|
||||||
|
},
|
||||||
|
'%R': () => {
|
||||||
|
const hour = padZero(getNumericComponent('hour', { hour: '2-digit', hour12: false }));
|
||||||
|
const minute = padZero(getNumericComponent('minute', { minute: '2-digit' }));
|
||||||
|
return `${hour}:${minute}`;
|
||||||
|
},
|
||||||
|
'%s': () => padZero(getNumericComponent('second', { second: '2-digit' })),
|
||||||
|
'%S': () => padZero(getNumericComponent('second', { second: '2-digit' })),
|
||||||
|
'%t': () => '\\t',
|
||||||
|
'%T': () => {
|
||||||
|
const hour = padZero(getNumericComponent('hour', { hour: '2-digit', hour12: false }));
|
||||||
|
const minute = padZero(getNumericComponent('minute', { minute: '2-digit' }));
|
||||||
|
const second = padZero(getNumericComponent('second', { second: '2-digit' }));
|
||||||
|
return `${hour}:${minute}:${second}`;
|
||||||
|
},
|
||||||
|
'%u': () => {
|
||||||
|
let day = getDateComponent('weekday', { weekday: 'short' });
|
||||||
|
const dayMap = { 'Mon': '1', 'Tue': '2', 'Wed': '3', 'Thu': '4', 'Fri': '5', 'Sat': '6', 'Sun': '7' };
|
||||||
|
return dayMap[day];
|
||||||
|
},
|
||||||
|
'%V': () => padZero(getWeekNumber(date)),
|
||||||
|
'%w': () => {
|
||||||
|
let day = getDateComponent('weekday', { weekday: 'short' });
|
||||||
|
const dayMap = { 'Sun': '0', 'Mon': '1', 'Tue': '2', 'Wed': '3', 'Thu': '4', 'Fri': '5', 'Sat': '6' };
|
||||||
|
return dayMap[day];
|
||||||
|
},
|
||||||
|
'%W': () => getDateComponent('weekday', { weekday: 'long' }),
|
||||||
|
'%y': () => getDateComponent('year', { year: '2-digit' }),
|
||||||
|
'%Y': () => getNumericComponent('year', { year: 'numeric' }),
|
||||||
|
'%z': () => {
|
||||||
|
if (zone === 'UTC') {
|
||||||
|
return '+0000';
|
||||||
|
} else {
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||||
|
timeZone: zone,
|
||||||
|
timeZoneName: 'shortOffset',
|
||||||
|
});
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const offsetPart = parts.find(part => part.type === 'timeZoneName');
|
||||||
|
if (offsetPart && offsetPart.value) {
|
||||||
|
const offsetValue = offsetPart.value;
|
||||||
|
const match = offsetValue.match(/GMT([+-]\\d{1,2})(?::(\\d{2}))?/);
|
||||||
|
if (match) {
|
||||||
|
const sign = match[1][0];
|
||||||
|
const hours = padZero(Math.abs(parseInt(match[1], 10)));
|
||||||
|
const minutes = padZero(match[2] ? parseInt(match[2], 10) : 0);
|
||||||
|
return `${sign}${hours}${minutes}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'%%': () => '%',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Replace tokens in the format string
|
||||||
|
let result = '';
|
||||||
|
let i = 0;
|
||||||
|
while (i < format.length) {
|
||||||
|
if (format[i] === '%') {
|
||||||
|
const token = format.substring(i, i + 2);
|
||||||
|
if (tokens[token]) {
|
||||||
|
result += tokens[token]();
|
||||||
|
i += 2;
|
||||||
|
} else {
|
||||||
|
// If token not found, include '%' and move to next character
|
||||||
|
result += format[i];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result += format[i];
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
["__isHogDateTime"],
|
||||||
|
],
|
||||||
|
"__printHogStringOutput": [
|
||||||
|
"""function __printHogStringOutput(obj) { if (typeof obj === 'string') { return obj } return __printHogValue(obj) } """,
|
||||||
|
["__printHogValue"],
|
||||||
|
],
|
||||||
|
"__printHogValue": [
|
||||||
|
"""
|
||||||
|
function __printHogValue(obj, marked = new Set()) {
|
||||||
|
if (typeof obj === 'object' && obj !== null && obj !== undefined) {
|
||||||
|
if (marked.has(obj) && !__isHogDateTime(obj) && !__isHogDate(obj) && !__isHogError(obj)) { return 'null'; }
|
||||||
|
marked.add(obj);
|
||||||
|
try {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
if (obj.__isHogTuple) { return obj.length < 2 ? `tuple(${obj.map((o) => __printHogValue(o, marked)).join(', ')})` : `(${obj.map((o) => __printHogValue(o, marked)).join(', ')})`; }
|
||||||
|
return `[${obj.map((o) => __printHogValue(o, marked)).join(', ')}]`;
|
||||||
|
}
|
||||||
|
if (__isHogDateTime(obj)) { const millis = String(obj.dt); return `DateTime(${millis}${millis.includes('.') ? '' : '.0'}, ${__escapeString(obj.zone)})`; }
|
||||||
|
if (__isHogDate(obj)) return `Date(${obj.year}, ${obj.month}, ${obj.day})`;
|
||||||
|
if (__isHogError(obj)) { return `${String(obj.type)}(${__escapeString(obj.message)}${obj.payload ? `, ${__printHogValue(obj.payload, marked)}` : ''})`; }
|
||||||
|
if (obj instanceof Map) { return `{${Array.from(obj.entries()).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`; }
|
||||||
|
return `{${Object.entries(obj).map(([key, value]) => `${__printHogValue(key, marked)}: ${__printHogValue(value, marked)}`).join(', ')}}`;
|
||||||
|
} finally {
|
||||||
|
marked.delete(obj);
|
||||||
|
}
|
||||||
|
} else if (typeof obj === 'boolean') return obj ? 'true' : 'false';
|
||||||
|
else if (obj === null || obj === undefined) return 'null';
|
||||||
|
else if (typeof obj === 'string') return __escapeString(obj);
|
||||||
|
if (typeof obj === 'function') return `fn<${__escapeIdentifier(obj.name || 'lambda')}(${obj.length})>`;
|
||||||
|
return obj.toString();
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[
|
||||||
|
"__isHogDateTime",
|
||||||
|
"__isHogDate",
|
||||||
|
"__isHogError",
|
||||||
|
"__escapeString",
|
||||||
|
"__escapeIdentifier",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"__escapeString": [
|
||||||
|
"""
|
||||||
|
function __escapeString(value) {
|
||||||
|
const singlequoteEscapeCharsMap = { '\\b': '\\\\b', '\\f': '\\\\f', '\\r': '\\\\r', '\\n': '\\\\n', '\\t': '\\\\t', '\\0': '\\\\0', '\\v': '\\\\v', '\\\\': '\\\\\\\\', "'": "\\\\'" }
|
||||||
|
return `'${value.split('').map((c) => singlequoteEscapeCharsMap[c] || c).join('')}'`;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__escapeIdentifier": [
|
||||||
|
"""
|
||||||
|
function __escapeIdentifier(identifier) {
|
||||||
|
const backquoteEscapeCharsMap = { '\\b': '\\\\b', '\\f': '\\\\f', '\\r': '\\\\r', '\\n': '\\\\n', '\\t': '\\\\t', '\\0': '\\\\0', '\\v': '\\\\v', '\\\\': '\\\\\\\\', '`': '\\\\`' }
|
||||||
|
if (typeof identifier === 'number') return identifier.toString();
|
||||||
|
if (/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(identifier)) return identifier;
|
||||||
|
return `\\`${identifier.split('').map((c) => backquoteEscapeCharsMap[c] || c).join('')}\\``;
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__newHogError": [
|
||||||
|
"""
|
||||||
|
function __newHogError(type, message, payload) {
|
||||||
|
let error = new Error(message || 'An error occurred');
|
||||||
|
error.__hogError__ = true
|
||||||
|
error.type = type
|
||||||
|
error.payload = payload
|
||||||
|
return error
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__isHogError": [
|
||||||
|
"""function __isHogError(obj) {return obj && obj.__hogError__ === true}""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__getNestedValue": [
|
||||||
|
"""
|
||||||
|
function __getNestedValue(obj, path, allowNull = false) {
|
||||||
|
let current = obj
|
||||||
|
for (const key of path) {
|
||||||
|
if (current == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (current instanceof Map) {
|
||||||
|
current = current.get(key)
|
||||||
|
} else if (typeof current === 'object' && current !== null) {
|
||||||
|
current = current[key]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (current === null && !allowNull) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return current
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__like": [
|
||||||
|
"""
|
||||||
|
function __like(str, pattern, caseInsensitive = false) {
|
||||||
|
if (caseInsensitive) {
|
||||||
|
str = str.toLowerCase()
|
||||||
|
pattern = pattern.toLowerCase()
|
||||||
|
}
|
||||||
|
pattern = String(pattern)
|
||||||
|
.replaceAll(/[-/\\\\^$*+?.()|[\\]{}]/g, '\\\\$&')
|
||||||
|
.replaceAll('%', '.*')
|
||||||
|
.replaceAll('_', '.')
|
||||||
|
return new RegExp(pattern).test(str)
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__getProperty": [
|
||||||
|
"""
|
||||||
|
function __getProperty(objectOrArray, key, nullish) {
|
||||||
|
if ((nullish && !objectOrArray) || key === 0) { return null }
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
return key > 0 ? objectOrArray[key - 1] : objectOrArray[objectOrArray.length + key]
|
||||||
|
} else {
|
||||||
|
return objectOrArray[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__setProperty": [
|
||||||
|
"""
|
||||||
|
function __setProperty(objectOrArray, key, value) {
|
||||||
|
if (Array.isArray(objectOrArray)) {
|
||||||
|
if (key > 0) {
|
||||||
|
objectOrArray[key - 1] = value
|
||||||
|
} else {
|
||||||
|
objectOrArray[objectOrArray.length + key] = value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
objectOrArray[key] = value
|
||||||
|
}
|
||||||
|
return objectOrArray
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
"__lambda": [
|
||||||
|
"""function __lambda (fn) { return fn }""",
|
||||||
|
[],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def import_stl_functions(requested_functions):
|
||||||
|
"""
|
||||||
|
Given a list of requested function names, returns a string containing the code
|
||||||
|
for these functions and all their dependencies, in an order suitable for evaluation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Set to keep track of all required functions
|
||||||
|
required_functions = set()
|
||||||
|
visited = set()
|
||||||
|
|
||||||
|
# Recursive function to find all dependencies
|
||||||
|
def dfs(func_name):
|
||||||
|
if func_name in visited:
|
||||||
|
return
|
||||||
|
visited.add(func_name)
|
||||||
|
if func_name not in STL_FUNCTIONS:
|
||||||
|
raise ValueError(f"Function '{func_name}' is not defined.")
|
||||||
|
_, dependencies = STL_FUNCTIONS[func_name]
|
||||||
|
for dep in sorted(dependencies):
|
||||||
|
dfs(dep)
|
||||||
|
required_functions.add(func_name)
|
||||||
|
|
||||||
|
# Start DFS from each requested function
|
||||||
|
for func in requested_functions:
|
||||||
|
dfs(func)
|
||||||
|
|
||||||
|
# Build the dependency graph
|
||||||
|
dependency_graph = {}
|
||||||
|
for func in sorted(required_functions):
|
||||||
|
_, dependencies = STL_FUNCTIONS[func]
|
||||||
|
dependency_graph[func] = dependencies
|
||||||
|
|
||||||
|
# Perform topological sort
|
||||||
|
def topological_sort(graph):
|
||||||
|
visited = set()
|
||||||
|
temp_mark = set()
|
||||||
|
result = []
|
||||||
|
|
||||||
|
def visit(node):
|
||||||
|
if node in visited:
|
||||||
|
return
|
||||||
|
if node in temp_mark:
|
||||||
|
raise ValueError(f"Circular dependency detected involving {node}")
|
||||||
|
temp_mark.add(node)
|
||||||
|
for neighbor in sorted(graph.get(node, [])):
|
||||||
|
visit(neighbor)
|
||||||
|
temp_mark.remove(node)
|
||||||
|
visited.add(node)
|
||||||
|
result.append(node)
|
||||||
|
|
||||||
|
for node in sorted(graph):
|
||||||
|
visit(node)
|
||||||
|
return result[::-1] # reverse the list to get correct order
|
||||||
|
|
||||||
|
sorted_functions = topological_sort(dependency_graph)
|
||||||
|
|
||||||
|
# Build the final code
|
||||||
|
code_pieces = []
|
||||||
|
for func in sorted_functions:
|
||||||
|
code, _ = STL_FUNCTIONS[func]
|
||||||
|
code_pieces.append(str(code).strip())
|
||||||
|
|
||||||
|
return "\n".join(code_pieces)
|
@ -1,6 +1,6 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from posthog.hogql.bytecode import to_bytecode, execute_hog, create_bytecode
|
from posthog.hogql.compiler.bytecode import to_bytecode, execute_hog, create_bytecode
|
||||||
from hogvm.python.operation import Operation as op, HOGQL_BYTECODE_IDENTIFIER as _H, HOGQL_BYTECODE_VERSION
|
from hogvm.python.operation import Operation as op, HOGQL_BYTECODE_IDENTIFIER as _H, HOGQL_BYTECODE_VERSION
|
||||||
from posthog.hogql.errors import NotImplementedError, QueryError
|
from posthog.hogql.errors import NotImplementedError, QueryError
|
||||||
from posthog.hogql.parser import parse_program
|
from posthog.hogql.parser import parse_program
|
227
posthog/hogql/compiler/test/test_javascript.py
Normal file
227
posthog/hogql/compiler/test/test_javascript.py
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
from posthog.hogql.compiler.javascript import JavaScriptCompiler, Local, _sanitize_identifier, to_js_program, to_js_expr
|
||||||
|
from posthog.hogql.errors import NotImplementedError, QueryError
|
||||||
|
from posthog.hogql import ast
|
||||||
|
from posthog.test.base import BaseTest
|
||||||
|
|
||||||
|
|
||||||
|
class TestSanitizeIdentifier(BaseTest):
|
||||||
|
def test_valid_identifiers(self):
|
||||||
|
self.assertEqual(_sanitize_identifier("validName"), "validName")
|
||||||
|
self.assertEqual(_sanitize_identifier("_validName123"), "_validName123")
|
||||||
|
|
||||||
|
def test_keywords(self):
|
||||||
|
self.assertEqual(_sanitize_identifier("await"), "__x_await")
|
||||||
|
self.assertEqual(_sanitize_identifier("class"), "__x_class")
|
||||||
|
|
||||||
|
def test_internal_conflicts(self):
|
||||||
|
self.assertEqual(_sanitize_identifier("__x_internal"), "__x___x_internal")
|
||||||
|
|
||||||
|
def test_invalid_identifiers(self):
|
||||||
|
self.assertEqual(_sanitize_identifier("123invalid"), '["123invalid"]')
|
||||||
|
self.assertEqual(_sanitize_identifier("invalid-name"), '["invalid-name"]')
|
||||||
|
|
||||||
|
def test_integer_identifiers(self):
|
||||||
|
self.assertEqual(_sanitize_identifier(123), '["123"]')
|
||||||
|
|
||||||
|
|
||||||
|
class TestJavaScript(BaseTest):
|
||||||
|
def test_javascript_create_basic_expressions(self):
|
||||||
|
self.assertEqual(to_js_expr("1 + 2"), "(1 + 2)")
|
||||||
|
self.assertEqual(to_js_expr("1 and 2"), "!!(1 && 2)")
|
||||||
|
self.assertEqual(to_js_expr("1 or 2"), "!!(1 || 2)")
|
||||||
|
self.assertEqual(to_js_expr("not true"), "(!true)")
|
||||||
|
self.assertEqual(to_js_expr("1 < 2"), "(1 < 2)")
|
||||||
|
self.assertEqual(to_js_expr("properties.bla"), '__getProperty(__getGlobal("properties"), "bla", true)')
|
||||||
|
|
||||||
|
def test_javascript_string_functions(self):
|
||||||
|
self.assertEqual(to_js_expr("concat('a', 'b')"), 'concat("a", "b")')
|
||||||
|
self.assertEqual(to_js_expr("lower('HELLO')"), 'lower("HELLO")')
|
||||||
|
self.assertEqual(to_js_expr("upper('hello')"), 'upper("hello")')
|
||||||
|
self.assertEqual(to_js_expr("reverse('abc')"), 'reverse("abc")')
|
||||||
|
|
||||||
|
def test_arithmetic_operations(self):
|
||||||
|
self.assertEqual(to_js_expr("3 - 1"), "(3 - 1)")
|
||||||
|
self.assertEqual(to_js_expr("2 * 3"), "(2 * 3)")
|
||||||
|
self.assertEqual(to_js_expr("5 / 2"), "(5 / 2)")
|
||||||
|
self.assertEqual(to_js_expr("10 % 3"), "(10 % 3)")
|
||||||
|
|
||||||
|
def test_comparison_operations(self):
|
||||||
|
self.assertEqual(to_js_expr("3 = 4"), "(3 == 4)")
|
||||||
|
self.assertEqual(to_js_expr("3 != 4"), "(3 != 4)")
|
||||||
|
self.assertEqual(to_js_expr("3 < 4"), "(3 < 4)")
|
||||||
|
self.assertEqual(to_js_expr("3 <= 4"), "(3 <= 4)")
|
||||||
|
self.assertEqual(to_js_expr("3 > 4"), "(3 > 4)")
|
||||||
|
self.assertEqual(to_js_expr("3 >= 4"), "(3 >= 4)")
|
||||||
|
|
||||||
|
def test_javascript_create_query_error(self):
|
||||||
|
with self.assertRaises(QueryError) as e:
|
||||||
|
to_js_expr("1 in cohort 2")
|
||||||
|
self.assertIn(
|
||||||
|
"Can't use cohorts in real-time filters. Please inline the relevant expressions", str(e.exception)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_scope_errors(self):
|
||||||
|
compiler = JavaScriptCompiler(locals=[Local(name="existing_var", depth=0)])
|
||||||
|
compiler._start_scope()
|
||||||
|
compiler._declare_local("new_var")
|
||||||
|
with self.assertRaises(QueryError):
|
||||||
|
compiler._declare_local("new_var")
|
||||||
|
compiler._end_scope()
|
||||||
|
|
||||||
|
def test_arithmetic_operation(self):
|
||||||
|
code = to_js_expr("3 + 5 * (10 / 2) - 7")
|
||||||
|
self.assertEqual(code, "((3 + (5 * (10 / 2))) - 7)")
|
||||||
|
|
||||||
|
def test_comparison(self):
|
||||||
|
code = to_js_expr("1 in 2")
|
||||||
|
self.assertEqual(code, "(2.includes(1))")
|
||||||
|
|
||||||
|
def test_if_else(self):
|
||||||
|
code = to_js_program("if (1 < 2) { return true } else { return false }")
|
||||||
|
expected_code = "if ((1 < 2)) {\n return true;\n} else {\n return false;\n}"
|
||||||
|
self.assertEqual(code.strip(), expected_code.strip())
|
||||||
|
|
||||||
|
def test_declare_local(self):
|
||||||
|
compiler = JavaScriptCompiler()
|
||||||
|
compiler._declare_local("a_var")
|
||||||
|
self.assertIn("a_var", [local.name for local in compiler.locals])
|
||||||
|
|
||||||
|
def test_visit_return_statement(self):
|
||||||
|
compiler = JavaScriptCompiler()
|
||||||
|
code = compiler.visit_return_statement(ast.ReturnStatement(expr=ast.Constant(value="test")))
|
||||||
|
self.assertEqual(code, 'return "test";')
|
||||||
|
|
||||||
|
def test_not_implemented_visit_select_query(self):
|
||||||
|
with self.assertRaises(NotImplementedError) as e:
|
||||||
|
to_js_expr("(select 1)")
|
||||||
|
self.assertEqual(str(e.exception), "JavaScriptCompiler does not support SelectQuery")
|
||||||
|
|
||||||
|
def test_throw_statement(self):
|
||||||
|
compiler = JavaScriptCompiler()
|
||||||
|
code = compiler.visit_throw_statement(ast.ThrowStatement(expr=ast.Constant(value="Error!")))
|
||||||
|
self.assertEqual(code, 'throw "Error!";')
|
||||||
|
|
||||||
|
def test_visit_dict(self):
|
||||||
|
code = to_js_expr("{'key1': 'value1', 'key2': 'value2'}")
|
||||||
|
self.assertEqual(code, '{"key1": "value1", "key2": "value2"}')
|
||||||
|
|
||||||
|
def test_visit_array(self):
|
||||||
|
code = to_js_expr("[1, 2, 3, 4]")
|
||||||
|
self.assertEqual(code, "[1, 2, 3, 4]")
|
||||||
|
|
||||||
|
def test_visit_lambda(self):
|
||||||
|
code = to_js_expr("x -> x + 1")
|
||||||
|
self.assertTrue(code.startswith("__lambda((x) => (x + 1))"))
|
||||||
|
|
||||||
|
def test_inlined_stl(self):
|
||||||
|
compiler = JavaScriptCompiler()
|
||||||
|
compiler.inlined_stl.add("concat")
|
||||||
|
stl_code = compiler.get_inlined_stl()
|
||||||
|
self.assertIn("function concat", stl_code)
|
||||||
|
|
||||||
|
def test_sanitize_keywords(self):
|
||||||
|
self.assertEqual(_sanitize_identifier("for"), "__x_for")
|
||||||
|
self.assertEqual(_sanitize_identifier("await"), "__x_await")
|
||||||
|
|
||||||
|
def test_json_parse(self):
|
||||||
|
code = to_js_expr('jsonParse(\'{"key": "value"}\')')
|
||||||
|
self.assertEqual(code, 'jsonParse("{\\"key\\": \\"value\\"}")')
|
||||||
|
|
||||||
|
def test_javascript_create_2(self):
|
||||||
|
self.assertEqual(to_js_expr("1 + 2"), "(1 + 2)")
|
||||||
|
self.assertEqual(to_js_expr("1 and 2"), "!!(1 && 2)")
|
||||||
|
self.assertEqual(to_js_expr("1 or 2"), "!!(1 || 2)")
|
||||||
|
self.assertEqual(to_js_expr("1 or (2 and 1) or 2"), "!!(1 || !!(2 && 1) || 2)")
|
||||||
|
self.assertEqual(to_js_expr("(1 or 2) and (1 or 2)"), "!!(!!(1 || 2) && !!(1 || 2))")
|
||||||
|
self.assertEqual(to_js_expr("not true"), "(!true)")
|
||||||
|
self.assertEqual(to_js_expr("true"), "true")
|
||||||
|
self.assertEqual(to_js_expr("false"), "false")
|
||||||
|
self.assertEqual(to_js_expr("null"), "null")
|
||||||
|
self.assertEqual(to_js_expr("3.14"), "3.14")
|
||||||
|
self.assertEqual(to_js_expr("properties.bla"), '__getProperty(__getGlobal("properties"), "bla", true)')
|
||||||
|
self.assertEqual(to_js_expr("concat('arg', 'another')"), 'concat("arg", "another")')
|
||||||
|
self.assertEqual(
|
||||||
|
to_js_expr("ifNull(properties.email, false)"),
|
||||||
|
'(__getProperty(__getGlobal("properties"), "email", true) ?? false)',
|
||||||
|
)
|
||||||
|
self.assertEqual(to_js_expr("1 in 2"), "(2.includes(1))")
|
||||||
|
self.assertEqual(to_js_expr("1 not in 2"), "(!2.includes(1))")
|
||||||
|
self.assertEqual(to_js_expr("match('test', 'e.*')"), 'match("test", "e.*")')
|
||||||
|
self.assertEqual(to_js_expr("not('test')"), '(!"test")')
|
||||||
|
self.assertEqual(to_js_expr("or('test', 'test2')"), '("test" || "test2")')
|
||||||
|
self.assertEqual(to_js_expr("and('test', 'test2')"), '("test" && "test2")')
|
||||||
|
|
||||||
|
def test_javascript_code_generation(self):
|
||||||
|
js_code = to_js_program("""
|
||||||
|
fun fibonacci(number) {
|
||||||
|
if (number < 2) {
|
||||||
|
return number;
|
||||||
|
} else {
|
||||||
|
return fibonacci(number - 1) + fibonacci(number - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fibonacci(6);
|
||||||
|
""")
|
||||||
|
expected_js = """function fibonacci(number) {
|
||||||
|
if ((number < 2)) {
|
||||||
|
return number;
|
||||||
|
} else {
|
||||||
|
return (fibonacci((number - 1)) + fibonacci((number - 2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fibonacci(6);"""
|
||||||
|
self.assertEqual(js_code.strip(), expected_js.strip())
|
||||||
|
|
||||||
|
def test_javascript_hogqlx(self):
|
||||||
|
code = to_js_expr("<Sparkline data={[1,2,3]} />")
|
||||||
|
self.assertEqual(code.strip(), '{"__hx_tag": "Sparkline", "data": [1, 2, 3]}')
|
||||||
|
|
||||||
|
def test_sanitized_function_names(self):
|
||||||
|
code = to_js_expr("typeof('test')")
|
||||||
|
self.assertEqual(code, '__x_typeof("test")')
|
||||||
|
|
||||||
|
def test_function_name_sanitization(self):
|
||||||
|
code = to_js_expr("Error('An error occurred')")
|
||||||
|
self.assertEqual(code, '__x_Error("An error occurred")')
|
||||||
|
|
||||||
|
def test_ilike(self):
|
||||||
|
code = to_js_expr("'hello' ilike '%ELLO%'")
|
||||||
|
self.assertEqual(code, 'ilike("hello", "%ELLO%")')
|
||||||
|
|
||||||
|
def test_not_ilike(self):
|
||||||
|
code = to_js_expr("'hello' not ilike '%ELLO%'")
|
||||||
|
self.assertEqual(code, '!ilike("hello", "%ELLO%")')
|
||||||
|
|
||||||
|
def test_regex(self):
|
||||||
|
code = to_js_expr("'hello' =~ 'h.*o'")
|
||||||
|
self.assertEqual(code, 'new RegExp("h.*o").test("hello")')
|
||||||
|
|
||||||
|
def test_not_regex(self):
|
||||||
|
code = to_js_expr("'hello' !~ 'h.*o'")
|
||||||
|
self.assertEqual(code, '!(new RegExp("h.*o").test("hello"))')
|
||||||
|
|
||||||
|
def test_i_regex(self):
|
||||||
|
code = to_js_expr("'hello' =~* 'H.*O'")
|
||||||
|
self.assertEqual(code, 'new RegExp("H.*O", "i").test("hello")')
|
||||||
|
|
||||||
|
def test_not_i_regex(self):
|
||||||
|
code = to_js_expr("'hello' !~* 'H.*O'")
|
||||||
|
self.assertEqual(code, '!(new RegExp("H.*O", "i").test("hello"))')
|
||||||
|
|
||||||
|
def test_array_access(self):
|
||||||
|
code = to_js_expr("array[2]")
|
||||||
|
self.assertEqual(code, '__getProperty(__getGlobal("array"), 2, false)')
|
||||||
|
|
||||||
|
def test_tuple_access(self):
|
||||||
|
code = to_js_expr("(1, 2, 3).2")
|
||||||
|
self.assertEqual(code, "__getProperty(tuple(1, 2, 3), 2, false)")
|
||||||
|
|
||||||
|
def test_function_assignment_error(self):
|
||||||
|
compiler = JavaScriptCompiler()
|
||||||
|
with self.assertRaises(QueryError) as context:
|
||||||
|
compiler.visit_variable_assignment(
|
||||||
|
ast.VariableAssignment(left=ast.Field(chain=["globalVar"]), right=ast.Constant(value=42))
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
'Variable "globalVar" not declared in this scope. Cannot assign to globals.', str(context.exception)
|
||||||
|
)
|
@ -2,7 +2,7 @@ from typing import Optional, cast
|
|||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
from posthog.hogql.bytecode import create_bytecode
|
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||||
from posthog.hogql.context import HogQLContext
|
from posthog.hogql.context import HogQLContext
|
||||||
from posthog.hogql.errors import ExposedHogQLError
|
from posthog.hogql.errors import ExposedHogQLError
|
||||||
from posthog.hogql.filters import replace_filters
|
from posthog.hogql.filters import replace_filters
|
||||||
|
@ -90,7 +90,7 @@ class Action(models.Model):
|
|||||||
|
|
||||||
def refresh_bytecode(self):
|
def refresh_bytecode(self):
|
||||||
from posthog.hogql.property import action_to_expr
|
from posthog.hogql.property import action_to_expr
|
||||||
from posthog.hogql.bytecode import create_bytecode
|
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||||
|
|
||||||
try:
|
try:
|
||||||
new_bytecode = create_bytecode(action_to_expr(self)).bytecode
|
new_bytecode = create_bytecode(action_to_expr(self)).bytecode
|
||||||
|
Loading…
Reference in New Issue
Block a user