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",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"env": {
|
||||
"SKIP_ASYNC_MIGRATIONS_SETUP": "0",
|
||||
"SKIP_ASYNC_MIGRATIONS_SETUP": "1",
|
||||
"DEBUG": "1",
|
||||
"BILLING_SERVICE_URL": "https://billing.dev.posthog.dev",
|
||||
"SKIP_SERVICE_VERSION_REQUIREMENTS": "1"
|
||||
|
3
bin/hoge
3
bin/hoge
@ -3,10 +3,13 @@ set -e
|
||||
|
||||
if [[ "$@" == *".hog"* ]]; then
|
||||
exec python3 -m posthog.hogql.cli --compile "$@"
|
||||
elif [[ "$@" == *".js"* ]]; then
|
||||
exec python3 -m posthog.hogql.cli --compile "$@"
|
||||
else
|
||||
echo "$0 - the Hog compilër! 🦔+🕶️= Hoge"
|
||||
echo ""
|
||||
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.hoge> run compiled .hoge bytecode"
|
||||
exit 1
|
||||
|
@ -1,7 +1,7 @@
|
||||
import dataclasses
|
||||
|
||||
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.property import action_to_expr
|
||||
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,
|
||||
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,
|
||||
-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
|
||||
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,
|
||||
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,
|
||||
"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,
|
||||
"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,
|
||||
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,
|
||||
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
|
||||
Error of type : 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 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,
|
||||
"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", 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]
|
||||
|
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', 'other': 'thing'}
|
||||
{'key': {'otherKey': 'value'}}
|
||||
{3: 'value'}
|
||||
{'kk': '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",
|
||||
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,
|
||||
"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,
|
||||
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",
|
||||
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,
|
||||
"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,
|
||||
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,
|
||||
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",
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
"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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
"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",
|
||||
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, 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]
|
||||
"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,
|
||||
"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,
|
||||
"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,
|
||||
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,
|
||||
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",
|
||||
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,
|
||||
"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,
|
||||
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,
|
||||
"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,
|
||||
"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,
|
||||
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,
|
||||
"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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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
|
||||
false
|
||||
false
|
||||
true
|
||||
true
|
||||
true
|
||||
"arganother"
|
||||
"1"
|
||||
"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]
|
||||
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) {
|
||||
print(f'FishError: {e.message}')
|
||||
} catch (e: Error) {
|
||||
print(f'Error of type {e.name}: {e.message}')
|
||||
print(f'Error of type {e.type}: {e.message}')
|
||||
}
|
||||
|
||||
try {
|
||||
@ -29,7 +29,7 @@ try {
|
||||
print(f'Problem with your food: {e.message}')
|
||||
}
|
||||
} catch (e: Error) {
|
||||
print(f'Error of type {e.name}: {e.message}')
|
||||
print(f'Error of type {e.type}: {e.message}')
|
||||
} catch (e: FishError) {
|
||||
print(f'FishError: {e.message}')
|
||||
}
|
||||
|
@ -3,7 +3,10 @@ print({'key': 'value'})
|
||||
print({'key': 'value', 'other': 'thing'})
|
||||
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', }.key)
|
||||
|
@ -37,6 +37,9 @@ test('a' not ilike 'b') // true
|
||||
test('a' in 'car') // true
|
||||
test('a' in 'foo') // 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(1, NULL)) // '1'
|
||||
test(concat(true, false)) // 'truefalse'
|
||||
|
@ -10,7 +10,7 @@ from hogvm.python.operation import (
|
||||
HOGQL_BYTECODE_VERSION as VERSION,
|
||||
)
|
||||
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
|
||||
|
||||
|
||||
|
@ -26,7 +26,7 @@ class UncaughtHogVMException(HogVMException):
|
||||
|
||||
|
||||
def like(string, pattern, flags=0):
|
||||
pattern = re.escape(pattern).replace("%", ".*")
|
||||
pattern = re.escape(pattern).replace("%", ".*").replace("_", ".")
|
||||
re_pattern = re.compile(pattern, flags)
|
||||
return re_pattern.search(string) is not None
|
||||
|
||||
|
@ -5,7 +5,7 @@ import glob
|
||||
import json
|
||||
|
||||
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"
|
||||
target_ts = "hogvm/typescript/src/stl/bytecode.ts"
|
||||
|
121
hogvm/test.sh
121
hogvm/test.sh
@ -1,32 +1,119 @@
|
||||
#!/bin/bash
|
||||
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
|
||||
pnpm run build
|
||||
cd ..
|
||||
|
||||
# Navigate to the project root (parent directory of 'hogvm')
|
||||
cd ..
|
||||
|
||||
rm -f hogvm/__tests__/__snapshots__/*.stdout.nodejs
|
||||
rm -f hogvm/__tests__/__snapshots__/*.stdout.python
|
||||
# Function to compute the basename for a given file
|
||||
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"
|
||||
|
||||
# from hogvm/__tests__/*.hog get hogvm/__tests__/__snapshots__/*
|
||||
basename="${file%.hog}"
|
||||
basename="${basename##*/}"
|
||||
basename="hogvm/__tests__/__snapshots__/$basename"
|
||||
basename=$(get_basename "$file")
|
||||
filename=$(basename "$file")
|
||||
|
||||
./bin/hoge $file $basename.hoge
|
||||
./bin/hog --nodejs $basename.hoge > $basename.stdout.nodejs
|
||||
./bin/hog --python $basename.hoge > $basename.stdout.python
|
||||
set +e
|
||||
diff $basename.stdout.nodejs $basename.stdout.python
|
||||
if [ $? -eq 0 ]; then
|
||||
mv $basename.stdout.nodejs $basename.stdout
|
||||
rm $basename.stdout.python
|
||||
./bin/hoge "$file" "$basename.hoge"
|
||||
./bin/hog --nodejs "$basename.hoge" > "$basename.stdout.nodejs"
|
||||
./bin/hog --python "$basename.hoge" > "$basename.stdout.python"
|
||||
|
||||
# Check if the current file should skip the compiledjs tests
|
||||
if is_in_array "$filename" "${SKIP_COMPILEDJS_FILES[@]}"; then
|
||||
# Skip compiledjs steps for this file
|
||||
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
|
||||
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
|
||||
set -e
|
||||
done
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@posthog/hogvm",
|
||||
"version": "1.0.58",
|
||||
"version": "1.0.59",
|
||||
"description": "PostHog Hog Virtual Machine",
|
||||
"types": "dist/index.d.ts",
|
||||
"source": "src/index.ts",
|
||||
|
@ -19,6 +19,8 @@ import {
|
||||
} from './date'
|
||||
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 {
|
||||
if (isHogDate(args[0])) {
|
||||
const month = args[0].month
|
||||
@ -71,9 +73,7 @@ export const STL: Record<string, STLFunction> = {
|
||||
},
|
||||
toString: { fn: STLToString, minArgs: 1, maxArgs: 1 },
|
||||
toUUID: {
|
||||
fn: (args) => {
|
||||
return String(args[0])
|
||||
},
|
||||
fn: STLToString,
|
||||
minArgs: 1,
|
||||
maxArgs: 1,
|
||||
},
|
||||
@ -148,8 +148,8 @@ export const STL: Record<string, STLFunction> = {
|
||||
},
|
||||
tuple: {
|
||||
fn: (args) => {
|
||||
const tuple = args.slice()
|
||||
;(tuple as any).__isHogTuple = true
|
||||
const tuple = args.slice();
|
||||
(tuple as any).__isHogTuple = true
|
||||
return tuple
|
||||
},
|
||||
minArgs: 0,
|
||||
|
@ -36,6 +36,7 @@ export function like(
|
||||
pattern = String(pattern)
|
||||
.replaceAll(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
|
||||
.replaceAll('%', '.*')
|
||||
.replaceAll('_', '.')
|
||||
if (match) {
|
||||
return match((caseInsensitive ? '(?i)' : '') + pattern, string)
|
||||
}
|
||||
|
@ -76,7 +76,7 @@
|
||||
"@medv/finder": "^3.1.0",
|
||||
"@microlink/react-json-view": "^1.21.3",
|
||||
"@monaco-editor/react": "4.6.0",
|
||||
"@posthog/hogvm": "^1.0.58",
|
||||
"@posthog/hogvm": "^1.0.59",
|
||||
"@posthog/icons": "0.9.1",
|
||||
"@posthog/plugin-scaffold": "^1.4.4",
|
||||
"@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:devNoWatch": "NODE_ENV=dev BASE_DIR=.. node -r @swc-node/register src/index.ts",
|
||||
"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:check": "tsc --noEmit -p .",
|
||||
"compile": "pnpm typescript:compile",
|
||||
@ -54,7 +54,7 @@
|
||||
"@maxmind/geoip2-node": "^3.4.0",
|
||||
"@posthog/clickhouse": "^1.7.0",
|
||||
"@posthog/cyclotron": "file:../rust/cyclotron-node",
|
||||
"@posthog/hogvm": "^1.0.58",
|
||||
"@posthog/hogvm": "^1.0.59",
|
||||
"@posthog/plugin-scaffold": "1.4.4",
|
||||
"@sentry/node": "^7.49.0",
|
||||
"@sentry/profiling-node": "^0.3.0",
|
||||
|
@ -47,8 +47,8 @@ dependencies:
|
||||
specifier: file:../rust/cyclotron-node
|
||||
version: file:../rust/cyclotron-node
|
||||
'@posthog/hogvm':
|
||||
specifier: ^1.0.58
|
||||
version: 1.0.58(luxon@3.4.4)
|
||||
specifier: ^1.0.59
|
||||
version: 1.0.59(luxon@3.4.4)
|
||||
'@posthog/plugin-scaffold':
|
||||
specifier: 1.4.4
|
||||
version: 1.4.4
|
||||
@ -3119,8 +3119,8 @@ packages:
|
||||
engines: {node: '>=12'}
|
||||
dev: false
|
||||
|
||||
/@posthog/hogvm@1.0.58(luxon@3.4.4):
|
||||
resolution: {integrity: sha512-n7NlJWth9WymJWd3w2YOKfq+soxAcycdfjNIVxxniL1bmEL+aI+Nff+MCPKrsv7YLj9qAnyLWBVAw9SZMksB1Q==}
|
||||
/@posthog/hogvm@1.0.59(luxon@3.4.4):
|
||||
resolution: {integrity: sha512-4KJfCXUhK7x5Wm3pheKWDmrbQ0y1lWlLWdVEjocdjSy3wOS8hQQqaFAVEKZs7hfk9pZqvNFh2UPgD4ccpwUQjA==}
|
||||
peerDependencies:
|
||||
luxon: ^3.4.4
|
||||
dependencies:
|
||||
|
@ -50,8 +50,8 @@ dependencies:
|
||||
specifier: 4.6.0
|
||||
version: 4.6.0(monaco-editor@0.49.0)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@posthog/hogvm':
|
||||
specifier: ^1.0.58
|
||||
version: 1.0.58(luxon@3.5.0)
|
||||
specifier: ^1.0.59
|
||||
version: 1.0.59(luxon@3.5.0)
|
||||
'@posthog/icons':
|
||||
specifier: 0.9.1
|
||||
version: 0.9.1(react-dom@18.2.0)(react@18.2.0)
|
||||
@ -392,7 +392,7 @@ dependencies:
|
||||
optionalDependencies:
|
||||
fsevents:
|
||||
specifier: ^2.3.2
|
||||
version: 2.3.3
|
||||
version: 2.3.2
|
||||
|
||||
devDependencies:
|
||||
'@babel/core':
|
||||
@ -5418,8 +5418,8 @@ packages:
|
||||
resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==}
|
||||
dev: false
|
||||
|
||||
/@posthog/hogvm@1.0.58(luxon@3.5.0):
|
||||
resolution: {integrity: sha512-n7NlJWth9WymJWd3w2YOKfq+soxAcycdfjNIVxxniL1bmEL+aI+Nff+MCPKrsv7YLj9qAnyLWBVAw9SZMksB1Q==}
|
||||
/@posthog/hogvm@1.0.59(luxon@3.5.0):
|
||||
resolution: {integrity: sha512-4KJfCXUhK7x5Wm3pheKWDmrbQ0y1lWlLWdVEjocdjSy3wOS8hQQqaFAVEKZs7hfk9pZqvNFh2UPgD4ccpwUQjA==}
|
||||
peerDependencies:
|
||||
luxon: ^3.4.4
|
||||
dependencies:
|
||||
@ -13142,7 +13142,6 @@ packages:
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
requiresBuild: true
|
||||
dev: true
|
||||
optional: true
|
||||
|
||||
/fsevents@2.3.3:
|
||||
|
@ -6,7 +6,7 @@ from rest_framework.response import Response
|
||||
from hogql_parser import parse_program
|
||||
from posthog.api.mixins import PydanticModelMixin
|
||||
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.schema import HogCompileResponse
|
||||
|
||||
|
@ -7,7 +7,7 @@ from rest_framework.exceptions import ValidationError
|
||||
from hogvm.python.debugger import color_bytecode
|
||||
from posthog.clickhouse.query_tagging import tag_queries
|
||||
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.context import HogQLContext
|
||||
from posthog.hogql.database.database import create_hogql_database, serialize_database
|
||||
|
@ -1,6 +1,6 @@
|
||||
from typing import Optional
|
||||
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.property import action_to_expr, property_to_expr, ast
|
||||
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 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.test.base import APIBaseTest, ClickhouseTestMixin, QueryMatchingTest
|
||||
|
||||
|
@ -2,7 +2,7 @@ import logging
|
||||
from typing import Any, Optional
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -2,7 +2,8 @@ import sys
|
||||
import json
|
||||
|
||||
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("-")]
|
||||
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:
|
||||
code = file.read()
|
||||
|
||||
if filename.endswith(".hog"):
|
||||
bytecode = create_bytecode(parse_program(code)).bytecode
|
||||
if "--compile" in modifiers and len(args) == 3 and args[2].endswith(".js"):
|
||||
target = args[2]
|
||||
js_program = to_js_program(code)
|
||||
with open(target, "w") as file:
|
||||
file.write(js_program + "\n")
|
||||
|
||||
else:
|
||||
bytecode = json.loads(code)
|
||||
|
||||
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]
|
||||
if filename.endswith(".hog"):
|
||||
bytecode = create_bytecode(parse_program(code)).bytecode
|
||||
else:
|
||||
target = filename[:-4] + ".hoge"
|
||||
bytecode = json.loads(code)
|
||||
|
||||
if "--run" in modifiers:
|
||||
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")
|
||||
response = execute_bytecode(bytecode, globals=None, timeout=5, team=None, debug="--debug" in modifiers)
|
||||
for line in response.stdout:
|
||||
print(line) # noqa: T201
|
||||
|
||||
else:
|
||||
raise ValueError("Must specify either --run or --compile")
|
||||
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:
|
||||
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
|
||||
|
||||
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 posthog.hogql.errors import NotImplementedError, QueryError
|
||||
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 posthog.hogql.bytecode import create_bytecode
|
||||
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||
from posthog.hogql.context import HogQLContext
|
||||
from posthog.hogql.errors import ExposedHogQLError
|
||||
from posthog.hogql.filters import replace_filters
|
||||
|
@ -90,7 +90,7 @@ class Action(models.Model):
|
||||
|
||||
def refresh_bytecode(self):
|
||||
from posthog.hogql.property import action_to_expr
|
||||
from posthog.hogql.bytecode import create_bytecode
|
||||
from posthog.hogql.compiler.bytecode import create_bytecode
|
||||
|
||||
try:
|
||||
new_bytecode = create_bytecode(action_to_expr(self)).bytecode
|
||||
|
Loading…
Reference in New Issue
Block a user