From af34f2aa5ca943d7ed6fbf3a237dca078515678f Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 24 Sep 2024 10:01:42 +0200 Subject: [PATCH 1/5] types: introduce new value helpers Introduce two new helper functions for dealing with ucode values: - `ucv_is_strictly_equal(v1, v2, treat_nan_equal)` This function implements strict value comparison rules as defined for the `===`, `!==` and `in` operators. This is useful for code dealing with uc values outside of the VM execution context. - `ucv_contains(haystack, needle)` This function implements the semantics of the `in` operator, it checks whether a given array or object contains a given value or key string respectively. Signed-off-by: Jo-Philipp Wich --- include/ucode/types.h | 2 ++ types.c | 54 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/include/ucode/types.h b/include/ucode/types.h index f149b98b..f5a01240 100644 --- a/include/ucode/types.h +++ b/include/ucode/types.h @@ -570,9 +570,11 @@ ucv_is_scalar(uc_value_t *uv) } bool ucv_is_equal(uc_value_t *, uc_value_t *); +bool ucv_is_strictly_equal(uc_value_t *, uc_value_t *, bool); bool ucv_is_truish(uc_value_t *); bool ucv_compare(int, uc_value_t *, uc_value_t *, int *); +bool ucv_contains(uc_value_t *, uc_value_t *); uc_value_t *ucv_key_get(uc_vm_t *, uc_value_t *, uc_value_t *); uc_value_t *ucv_key_set(uc_vm_t *, uc_value_t *, uc_value_t *, uc_value_t *); diff --git a/types.c b/types.c index 5dbf6a86..c692f7fb 100644 --- a/types.c +++ b/types.c @@ -2032,6 +2032,37 @@ ucv_is_equal(uc_value_t *uv1, uc_value_t *uv2) } } +bool +ucv_is_strictly_equal(uc_value_t *v1, uc_value_t *v2, bool nan_equal) +{ + uc_type_t t1 = ucv_type(v1); + uc_type_t t2 = ucv_type(v2); + double d1, d2; + + if (t1 != t2) + return false; + + switch (t1) { + case UC_DOUBLE: + d1 = ((uc_double_t *)v1)->dbl; + d2 = ((uc_double_t *)v2)->dbl; + + if (isnan(d1) && isnan(d2)) + return nan_equal; + + return (d1 == d2); + + case UC_NULL: + case UC_BOOLEAN: + case UC_INTEGER: + case UC_STRING: + return ucv_is_equal(v1, v2); + + default: + return (v1 == v2); + } +} + bool ucv_is_truish(uc_value_t *val) { @@ -2247,6 +2278,29 @@ ucv_compare(int how, uc_value_t *v1, uc_value_t *v2, int *deltap) } } +bool +ucv_contains(uc_value_t *haystack, uc_value_t *needle) +{ + uc_type_t t = ucv_type(haystack); + bool found = false; + + if (t == UC_ARRAY) { + for (size_t i = 0, len = ucv_array_length(haystack); i < len; i++) { + uc_value_t *elem = ucv_array_get(haystack, i); + + if (ucv_is_strictly_equal(elem, needle, true)) { + found = true; + break; + } + } + } + else if (t == UC_OBJECT && ucv_type(needle) == UC_STRING) { + ucv_object_get(haystack, ucv_string_get(needle), &found); + } + + return found; +} + static char * ucv_key_to_string(uc_vm_t *vm, uc_value_t *val) From 19a62fc0636690f16bde456b8d6f5aad03e2fb9c Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 24 Sep 2024 10:15:04 +0200 Subject: [PATCH 2/5] vm: use `ucv_strictly_equal()` and `ucv_contains()` helper Drop the VM specific implementation in favor to the generic types.c helper functions. Signed-off-by: Jo-Philipp Wich --- vm.c | 61 ++---------------------------------------------------------- 1 file changed, 2 insertions(+), 59 deletions(-) diff --git a/vm.c b/vm.c index fc322c5d..e2e6bb37 100644 --- a/vm.c +++ b/vm.c @@ -1024,37 +1024,6 @@ uc_vm_raise_exception(uc_vm_t *vm, uc_exception_type_t type, const char *fmt, .. vm->exception.stacktrace = uc_vm_get_error_context(vm); } -static bool -uc_vm_test_strict_equality(uc_value_t *v1, uc_value_t *v2, bool nan_equal) -{ - uc_type_t t1 = ucv_type(v1); - uc_type_t t2 = ucv_type(v2); - double d1, d2; - - if (t1 != t2) - return false; - - switch (t1) { - case UC_DOUBLE: - d1 = ((uc_double_t *)v1)->dbl; - d2 = ((uc_double_t *)v2)->dbl; - - if (isnan(d1) && isnan(d2)) - return nan_equal; - - return (d1 == d2); - - case UC_NULL: - case UC_BOOLEAN: - case UC_INTEGER: - case UC_STRING: - return ucv_is_equal(v1, v2); - - default: - return (v1 == v2); - } -} - static void uc_vm_insn_load(uc_vm_t *vm, uc_vm_insn_t insn) @@ -2125,33 +2094,7 @@ uc_vm_insn_in(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r2 = uc_vm_stack_pop(vm); uc_value_t *r1 = uc_vm_stack_pop(vm); - uc_value_t *item; - size_t arrlen, arridx; - bool found = false; - - switch (ucv_type(r2)) { - case UC_ARRAY: - for (arridx = 0, arrlen = ucv_array_length(r2); - arridx < arrlen; arridx++) { - item = ucv_array_get(r2, arridx); - - if (uc_vm_test_strict_equality(r1, item, true)) { - found = true; - break; - } - } - - break; - - case UC_OBJECT: - if (ucv_type(r1) == UC_STRING) - ucv_object_get(r2, ucv_string_get(r1), &found); - - break; - - default: - found = false; - } + bool found = ucv_contains(r2, r1); ucv_put(r1); ucv_put(r2); @@ -2164,7 +2107,7 @@ uc_vm_insn_equality(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r2 = uc_vm_stack_pop(vm); uc_value_t *r1 = uc_vm_stack_pop(vm); - bool equal = uc_vm_test_strict_equality(r1, r2, false); + bool equal = ucv_is_strictly_equal(r1, r2, false); ucv_put(r1); ucv_put(r2); From 72681447cdbfbcb634e481db7f46c7f853598afd Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 24 Sep 2024 10:46:18 +0200 Subject: [PATCH 3/5] arith: introduce new helper for ucode value arithmetic Factor out the core arithmetic operations from `vm.c`, move them into a separate `arith.c` compilation unit and expose two new helper functions `ucv_arith_unary()` and `ucv_arith_binary()` allowing the computation of numeric operations on ucode values outside of the VM context. This is intended for use-cases where evaluation of simple ucode expressions is required, e.g. within a debugger module or a code intelligence context within a language server. Signed-off-by: Jo-Philipp Wich --- CMakeLists.txt | 2 +- arith.c | 501 ++++++++++++++++++++++++++++++++++++++++++ include/ucode/arith.h | 27 +++ 3 files changed, 529 insertions(+), 1 deletion(-) create mode 100644 arith.c create mode 100644 include/ucode/arith.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bdf07383..77e9b7af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -96,7 +96,7 @@ include(FindPkgConfig) pkg_check_modules(JSONC REQUIRED json-c) include_directories(${JSONC_INCLUDE_DIRS}) -set(UCODE_SOURCES lexer.c lib.c vm.c chunk.c vallist.c compiler.c source.c types.c program.c platform.c) +set(UCODE_SOURCES lexer.c lib.c vm.c chunk.c vallist.c compiler.c source.c types.c program.c platform.c arith.c) add_library(libucode SHARED ${UCODE_SOURCES}) set(SOVERSION 0 CACHE STRING "Override ucode library version") set_target_properties(libucode PROPERTIES OUTPUT_NAME ucode SOVERSION ${SOVERSION}) diff --git a/arith.c b/arith.c new file mode 100644 index 00000000..3e677d18 --- /dev/null +++ b/arith.c @@ -0,0 +1,501 @@ +/* + * Copyright (C) 2024 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include + +#include "ucode/arith.h" + + +static int64_t +int64(uc_value_t *nv, uint64_t *u64) +{ + int64_t n; + + n = ucv_int64_get(nv); + *u64 = 0; + + if (errno == ERANGE) { + n = INT64_MAX; + *u64 = ucv_uint64_get(nv); + } + + return n; +} + +static uint64_t +abs64(int64_t n) +{ + if (n == INT64_MIN) + return 0x8000000000000000ULL; + + if (n < 0) + return -n; + + return n; +} + +static uint64_t +upow64(uint64_t base, uint64_t exponent) +{ + uint64_t result = 1; + + while (exponent) { + if (exponent & 1) + result *= base; + + exponent >>= 1; + base *= base; + } + + return result; +} + + +static uc_value_t * +ucv_arith_logic_result(uc_tokentype_t operation, uc_value_t *v1, uc_value_t *v2) +{ + switch (operation) { + case TK_AND: return ucv_get(ucv_is_truish(v1) ? v2 : v1); + case TK_OR: return ucv_get(ucv_is_truish(v1) ? v1 : v2); + case TK_NULLISH: return ucv_get(v1 ? v1 : v2); + case TK_IN: return ucv_boolean_new(ucv_contains(v2, v1)); + default: return NULL; + } +} + +static uc_value_t * +ucv_arith_bitwise_result(uc_tokentype_t operation, uc_value_t *v1, uc_value_t *v2) +{ + uc_value_t *nv1, *nv2, *rv = NULL; + uint64_t u1, u2; + int64_t n1, n2; + + nv1 = ucv_to_number(v1); + nv2 = ucv_to_number(v2); + + n1 = int64(nv1, &u1); + n2 = int64(nv2, &u2); + + if (n1 < 0 || n2 < 0) { + switch (operation) { + case TK_LSHIFT: + rv = ucv_int64_new(n1 << n2); + break; + + case TK_RSHIFT: + rv = ucv_int64_new(n1 >> n2); + break; + + case TK_BAND: + rv = ucv_int64_new(n1 & n2); + break; + + case TK_BXOR: + rv = ucv_int64_new(n1 ^ n2); + break; + + case TK_BOR: + rv = ucv_int64_new(n1 | n2); + break; + + default: + break; + } + } + else { + if (!u1) u1 = (uint64_t)n1; + if (!u2) u2 = (uint64_t)n2; + + switch (operation) { + case TK_LSHIFT: + rv = ucv_uint64_new(u1 << (u2 % (sizeof(uint64_t) * CHAR_BIT))); + break; + + case TK_RSHIFT: + rv = ucv_uint64_new(u1 >> (u2 % (sizeof(uint64_t) * CHAR_BIT))); + break; + + case TK_BAND: + rv = ucv_uint64_new(u1 & u2); + break; + + case TK_BXOR: + rv = ucv_uint64_new(u1 ^ u2); + break; + + case TK_BOR: + rv = ucv_uint64_new(u1 | u2); + break; + + default: + break; + } + } + + ucv_put(nv1); + ucv_put(nv2); + + return rv; +} + +static uc_value_t * +ucv_arith_string_concat(uc_vm_t *vm, uc_value_t *v1, uc_value_t *v2) +{ + char buf[sizeof(void *)], *s1, *s2; + uc_stringbuf_t *sbuf; + size_t l1, l2; + + /* optimize cases for string+string concat... */ + if (ucv_type(v1) == UC_STRING && ucv_type(v2) == UC_STRING) { + s1 = ucv_string_get(v1); + s2 = ucv_string_get(v2); + l1 = ucv_string_length(v1); + l2 = ucv_string_length(v2); + + /* ... result fits into a tagged pointer */ + if (l1 + l2 + 1 < sizeof(buf)) { + memcpy(&buf[0], s1, l1); + memcpy(&buf[l1], s2, l2); + + return ucv_string_new_length(buf, l1 + l2); + } + } + + sbuf = ucv_stringbuf_new(); + + ucv_to_stringbuf(vm, sbuf, v1, false); + ucv_to_stringbuf(vm, sbuf, v2, false); + + return ucv_stringbuf_finish(sbuf); +} + +uc_value_t * +ucv_arith_binary(uc_vm_t *vm, uc_tokentype_t operation, uc_value_t *v1, uc_value_t *v2) +{ + uc_value_t *nv1, *nv2, *rv = NULL; + uint64_t u1, u2; + int64_t n1, n2; + double d1, d2; + + if (operation == TK_AND || operation == TK_OR || + operation == TK_NULLISH || operation == TK_IN) + return ucv_arith_logic_result(operation, v1, v2); + + if (operation == TK_LSHIFT || operation == TK_RSHIFT || + operation == TK_BAND || operation == TK_BXOR || operation == TK_BOR) + return ucv_arith_bitwise_result(operation, v1, v2); + + if (operation == TK_ADD && + (ucv_type(v1) == UC_STRING || ucv_type(v2) == UC_STRING)) + return ucv_arith_string_concat(vm, v1, v2); + + nv1 = ucv_to_number(v1); + nv2 = ucv_to_number(v2); + + /* any operation involving NaN results in NaN */ + if (!nv1 || !nv2) { + ucv_put(nv1); + ucv_put(nv2); + + return ucv_double_new(NAN); + } + if (ucv_type(nv1) == UC_DOUBLE || ucv_type(nv2) == UC_DOUBLE) { + d1 = ucv_double_get(nv1); + d2 = ucv_double_get(nv2); + + switch (operation) { + case TK_ADD: + case TK_INC: + rv = ucv_double_new(d1 + d2); + break; + + case TK_SUB: + case TK_DEC: + rv = ucv_double_new(d1 - d2); + break; + + case TK_MUL: + rv = ucv_double_new(d1 * d2); + break; + + case TK_DIV: + if (d2 == 0.0) + rv = ucv_double_new(INFINITY); + else if (isnan(d2)) + rv = ucv_double_new(NAN); + else if (!isfinite(d2)) + rv = ucv_double_new(isfinite(d1) ? 0.0 : NAN); + else + rv = ucv_double_new(d1 / d2); + + break; + + case TK_MOD: + rv = ucv_double_new(fmod(d1, d2)); + break; + + case TK_EXP: + rv = ucv_double_new(pow(d1, d2)); + break; + + default: + if (vm) + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "undefined arithmetic operation %d", + operation); + + break; + } + } + else { + n1 = int64(nv1, &u1); + n2 = int64(nv2, &u2); + + switch (operation) { + case TK_ADD: + case TK_INC: + if (n1 < 0 || n2 < 0) { + if (u1) + rv = ucv_uint64_new(u1 - abs64(n2)); + else if (u2) + rv = ucv_uint64_new(u2 - abs64(n1)); + else + rv = ucv_int64_new(n1 + n2); + } + else { + if (!u1) u1 = (uint64_t)n1; + if (!u2) u2 = (uint64_t)n2; + + rv = ucv_uint64_new(u1 + u2); + } + + break; + + case TK_SUB: + case TK_DEC: + if (n1 < 0 && n2 < 0) { + if (n1 > n2) + rv = ucv_uint64_new(abs64(n2) - abs64(n1)); + else + rv = ucv_int64_new(n1 - n2); + } + else if (n1 >= 0 && n2 >= 0) { + if (!u1) u1 = (uint64_t)n1; + if (!u2) u2 = (uint64_t)n2; + + if (u2 > u1) + rv = ucv_int64_new(-(u2 - u1)); + else + rv = ucv_uint64_new(u1 - u2); + } + else if (n1 >= 0) { + if (!u1) u1 = (uint64_t)n1; + + rv = ucv_uint64_new(u1 + abs64(n2)); + } + else { + rv = ucv_int64_new(n1 - n2); + } + + break; + + case TK_MUL: + if (n1 < 0 && n2 < 0) { + rv = ucv_uint64_new(abs64(n1) * abs64(n2)); + } + else if (n1 >= 0 && n2 >= 0) { + if (!u1) u1 = (uint64_t)n1; + if (!u2) u2 = (uint64_t)n2; + + rv = ucv_uint64_new(u1 * u2); + } + else { + rv = ucv_int64_new(n1 * n2); + } + + break; + + case TK_DIV: + if (n2 == 0) { + rv = ucv_double_new(INFINITY); + } + else if (n1 < 0 || n2 < 0) { + rv = ucv_int64_new(n1 / n2); + } + else { + if (!u1) u1 = (uint64_t)n1; + if (!u2) u2 = (uint64_t)n2; + + rv = ucv_uint64_new(u1 / u2); + } + + break; + + case TK_MOD: + if (n1 < 0 || n2 < 0) { + rv = ucv_int64_new(n1 % n2); + } + else { + if (!u1) u1 = (uint64_t)n1; + if (!u2) u2 = (uint64_t)n2; + + rv = ucv_uint64_new(u1 % u2); + } + + break; + + case TK_EXP: + if (n1 < 0 || n2 < 0) { + if (n1 < 0 && n2 < 0) + rv = ucv_double_new(-(1.0 / (double)upow64(abs64(n1), abs64(n2)))); + else if (n2 < 0) + rv = ucv_double_new(1.0 / (double)upow64(abs64(n1), abs64(n2))); + else + rv = ucv_int64_new(-upow64(abs64(n1), abs64(n2))); + } + else { + if (!u1) u1 = (uint64_t)n1; + if (!u2) u2 = (uint64_t)n2; + + rv = ucv_uint64_new(upow64(u1, u2)); + } + + break; + + default: + if (vm) + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "undefined arithmetic operation %d", + operation); + + break; + } + } + + ucv_put(nv1); + ucv_put(nv2); + + return rv; +} + +uc_value_t * +ucv_arith_unary(uc_vm_t *vm, uc_tokentype_t operation, uc_value_t *v) +{ + uc_value_t *nv = NULL, *rv = NULL; + bool is_sub = false; + uint64_t u; + int64_t n; + double d; + + switch (operation) { + case TK_SUB: + case TK_DEC: + is_sub = true; + /* fall through */ + + case TK_ADD: + case TK_INC: + if (ucv_type(v) == UC_STRING) { + nv = uc_number_parse(ucv_string_get(v), NULL); + + if (!nv) + nv = ucv_get(v); + } + else { + nv = ucv_get(v); + } + + switch (ucv_type(nv)) { + case UC_INTEGER: + n = ucv_int64_get(nv); + + /* numeric value is in range 9223372036854775808..18446744073709551615 */ + if (errno == ERANGE) { + if (is_sub) + /* make negation of large numeric value result in smallest negative value */ + rv = ucv_int64_new(INT64_MIN); + else + /* for positive number coercion return value as-is */ + rv = ucv_get(nv); + } + + /* numeric value is in range -9223372036854775808..9223372036854775807 */ + else { + if (is_sub) { + if (n == INT64_MIN) + /* make negation of minimum value result in maximum signed positive value */ + rv = ucv_int64_new(INT64_MAX); + else + /* for all other values flip the sign */ + rv = ucv_int64_new(-n); + } + else { + /* for positive number coercion return value as-is */ + rv = ucv_get(nv); + } + } + + break; + + case UC_DOUBLE: + d = ucv_double_get(nv); + rv = ucv_double_new(is_sub ? -d : d); + break; + + case UC_BOOLEAN: + n = (int64_t)ucv_boolean_get(v); + rv = ucv_int64_new(is_sub ? -n : n); + break; + + case UC_NULL: + rv = ucv_int64_new(0); + break; + + default: + rv = ucv_double_new(NAN); + } + + break; + + case TK_COMPL: + nv = ucv_to_number(v); + n = int64(nv, &u); + + if (n < 0) { + rv = ucv_int64_new(~n); + } + else { + if (!u) u = (uint64_t)n; + + rv = ucv_uint64_new(~u); + } + + break; + + default: + if (vm) + uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, + "undefined unary operation %d", operation); + + break; + } + + ucv_put(nv); + + return rv; +} diff --git a/include/ucode/arith.h b/include/ucode/arith.h new file mode 100644 index 00000000..6d02baf5 --- /dev/null +++ b/include/ucode/arith.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 Jo-Philipp Wich + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef UCODE_ARITH_H +#define UCODE_ARITH_H + +#include "types.h" +#include "lexer.h" +#include "vm.h" + +uc_value_t *ucv_arith_unary(uc_vm_t *, uc_tokentype_t, uc_value_t *); +uc_value_t *ucv_arith_binary(uc_vm_t *, uc_tokentype_t, uc_value_t *, uc_value_t *); + +#endif /* UCODE_ARITH_H */ From 4f80b998022abe83cf529799eba37ed5047cb9ac Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Tue, 24 Sep 2024 12:07:37 +0200 Subject: [PATCH 4/5] vm: utilize `ucv_arith_*()` helper to implement calculations Use the factored out `arith.c` routines to implement arithmetic operations for ucode values and remove the local implementations from the VM. As a result, the instruction handling is slightly simplified and the amount of code reduced. Signed-off-by: Jo-Philipp Wich --- vm.c | 496 ++++------------------------------------------------------- 1 file changed, 29 insertions(+), 467 deletions(-) diff --git a/vm.c b/vm.c index e2e6bb37..b0093ba6 100644 --- a/vm.c +++ b/vm.c @@ -29,6 +29,7 @@ #include "ucode/program.h" #include "ucode/lib.h" /* uc_error_context_format() */ #include "ucode/platform.h" +#include "ucode/arith.h" #undef __insn #define __insn(_name) #_name, @@ -92,6 +93,15 @@ static const char *exception_type_strings[] = { }; +static inline uc_tokentype_t +uc_vm_insn_to_token(uc_vm_insn_t insn) +{ + if (insn >= I_BOR && insn <= I_MINUS) + return TK_BOR + (insn - I_BOR); + + return TK_ERROR; +} + static void uc_vm_reset_stack(uc_vm_t *vm) { @@ -1375,356 +1385,6 @@ uc_vm_insn_store_local(uc_vm_t *vm, uc_vm_insn_t insn) uc_vm_stack_set(vm, frame->stackframe + vm->arg.u32, val); } -static int64_t -int64(uc_value_t *nv, uint64_t *u64) -{ - int64_t n; - - n = ucv_int64_get(nv); - *u64 = 0; - - if (errno == ERANGE) { - n = INT64_MAX; - *u64 = ucv_uint64_get(nv); - } - - return n; -} - -static uint64_t -abs64(int64_t n) -{ - if (n == INT64_MIN) - return 0x8000000000000000ULL; - - if (n < 0) - return -n; - - return n; -} - - -static uc_value_t * -uc_vm_value_bitop(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_value_t *operand) -{ - uc_value_t *nv1, *nv2, *rv = NULL; - uint64_t u1, u2; - int64_t n1, n2; - - nv1 = ucv_to_number(value); - nv2 = ucv_to_number(operand); - - n1 = int64(nv1, &u1); - n2 = int64(nv2, &u2); - - if (n1 < 0 || n2 < 0) { - switch (operation) { - case I_LSHIFT: - rv = ucv_int64_new(n1 << n2); - break; - - case I_RSHIFT: - rv = ucv_int64_new(n1 >> n2); - break; - - case I_BAND: - rv = ucv_int64_new(n1 & n2); - break; - - case I_BXOR: - rv = ucv_int64_new(n1 ^ n2); - break; - - case I_BOR: - rv = ucv_int64_new(n1 | n2); - break; - - default: - break; - } - } - else { - if (!u1) u1 = (uint64_t)n1; - if (!u2) u2 = (uint64_t)n2; - - switch (operation) { - case I_LSHIFT: - rv = ucv_uint64_new(u1 << (u2 % (sizeof(uint64_t) * CHAR_BIT))); - break; - - case I_RSHIFT: - rv = ucv_uint64_new(u1 >> (u2 % (sizeof(uint64_t) * CHAR_BIT))); - break; - - case I_BAND: - rv = ucv_uint64_new(u1 & u2); - break; - - case I_BXOR: - rv = ucv_uint64_new(u1 ^ u2); - break; - - case I_BOR: - rv = ucv_uint64_new(u1 | u2); - break; - - default: - break; - } - } - - ucv_put(nv1); - ucv_put(nv2); - - return rv; -} - -static uc_value_t * -uc_vm_string_concat(uc_vm_t *vm, uc_value_t *v1, uc_value_t *v2) -{ - char buf[sizeof(void *)], *s1, *s2; - uc_stringbuf_t *sbuf; - size_t l1, l2; - - /* optimize cases for string+string concat... */ - if (ucv_type(v1) == UC_STRING && ucv_type(v2) == UC_STRING) { - s1 = ucv_string_get(v1); - s2 = ucv_string_get(v2); - l1 = ucv_string_length(v1); - l2 = ucv_string_length(v2); - - /* ... result fits into a tagged pointer */ - if (l1 + l2 + 1 < sizeof(buf)) { - memcpy(&buf[0], s1, l1); - memcpy(&buf[l1], s2, l2); - - return ucv_string_new_length(buf, l1 + l2); - } - } - - sbuf = ucv_stringbuf_new(); - - ucv_to_stringbuf(vm, sbuf, v1, false); - ucv_to_stringbuf(vm, sbuf, v2, false); - - return ucv_stringbuf_finish(sbuf); -} - -static uint64_t -upow64(uint64_t base, uint64_t exponent) -{ - uint64_t result = 1; - - while (exponent) { - if (exponent & 1) - result *= base; - - exponent >>= 1; - base *= base; - } - - return result; -} - -static uc_value_t * -uc_vm_value_arith(uc_vm_t *vm, uc_vm_insn_t operation, uc_value_t *value, uc_value_t *operand) -{ - uc_value_t *nv1, *nv2, *rv = NULL; - uint64_t u1, u2; - int64_t n1, n2; - double d1, d2; - - if (operation == I_LSHIFT || operation == I_RSHIFT || - operation == I_BAND || operation == I_BXOR || operation == I_BOR) - return uc_vm_value_bitop(vm, operation, value, operand); - - if (operation == I_ADD && (ucv_type(value) == UC_STRING || ucv_type(operand) == UC_STRING)) - return uc_vm_string_concat(vm, value, operand); - - nv1 = ucv_to_number(value); - nv2 = ucv_to_number(operand); - - /* any operation involving NaN results in NaN */ - if (!nv1 || !nv2) { - ucv_put(nv1); - ucv_put(nv2); - - return ucv_double_new(NAN); - } - if (ucv_type(nv1) == UC_DOUBLE || ucv_type(nv2) == UC_DOUBLE) { - d1 = ucv_double_get(nv1); - d2 = ucv_double_get(nv2); - - switch (operation) { - case I_ADD: - case I_PLUS: - rv = ucv_double_new(d1 + d2); - break; - - case I_SUB: - case I_MINUS: - rv = ucv_double_new(d1 - d2); - break; - - case I_MUL: - rv = ucv_double_new(d1 * d2); - break; - - case I_DIV: - if (d2 == 0.0) - rv = ucv_double_new(INFINITY); - else if (isnan(d2)) - rv = ucv_double_new(NAN); - else if (!isfinite(d2)) - rv = ucv_double_new(isfinite(d1) ? 0.0 : NAN); - else - rv = ucv_double_new(d1 / d2); - - break; - - case I_MOD: - rv = ucv_double_new(fmod(d1, d2)); - break; - - case I_EXP: - rv = ucv_double_new(pow(d1, d2)); - break; - - default: - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "undefined arithmetic operation %d", - operation); - break; - } - } - else { - n1 = int64(nv1, &u1); - n2 = int64(nv2, &u2); - - switch (operation) { - case I_ADD: - case I_PLUS: - if (n1 < 0 || n2 < 0) { - if (u1) - rv = ucv_uint64_new(u1 - abs64(n2)); - else if (u2) - rv = ucv_uint64_new(u2 - abs64(n1)); - else - rv = ucv_int64_new(n1 + n2); - } - else { - if (!u1) u1 = (uint64_t)n1; - if (!u2) u2 = (uint64_t)n2; - - rv = ucv_uint64_new(u1 + u2); - } - - break; - - case I_SUB: - case I_MINUS: - if (n1 < 0 && n2 < 0) { - if (n1 > n2) - rv = ucv_uint64_new(abs64(n2) - abs64(n1)); - else - rv = ucv_int64_new(n1 - n2); - } - else if (n1 >= 0 && n2 >= 0) { - if (!u1) u1 = (uint64_t)n1; - if (!u2) u2 = (uint64_t)n2; - - if (u2 > u1) - rv = ucv_int64_new(-(u2 - u1)); - else - rv = ucv_uint64_new(u1 - u2); - } - else if (n1 >= 0) { - if (!u1) u1 = (uint64_t)n1; - - rv = ucv_uint64_new(u1 + abs64(n2)); - } - else { - rv = ucv_int64_new(n1 - n2); - } - - break; - - case I_MUL: - if (n1 < 0 && n2 < 0) { - rv = ucv_uint64_new(abs64(n1) * abs64(n2)); - } - else if (n1 >= 0 && n2 >= 0) { - if (!u1) u1 = (uint64_t)n1; - if (!u2) u2 = (uint64_t)n2; - - rv = ucv_uint64_new(u1 * u2); - } - else { - rv = ucv_int64_new(n1 * n2); - } - - break; - - case I_DIV: - if (n2 == 0) { - rv = ucv_double_new(INFINITY); - } - else if (n1 < 0 || n2 < 0) { - rv = ucv_int64_new(n1 / n2); - } - else { - if (!u1) u1 = (uint64_t)n1; - if (!u2) u2 = (uint64_t)n2; - - rv = ucv_uint64_new(u1 / u2); - } - - break; - - case I_MOD: - if (n1 < 0 || n2 < 0) { - rv = ucv_int64_new(n1 % n2); - } - else { - if (!u1) u1 = (uint64_t)n1; - if (!u2) u2 = (uint64_t)n2; - - rv = ucv_uint64_new(u1 % u2); - } - - break; - - case I_EXP: - if (n1 < 0 || n2 < 0) { - if (n1 < 0 && n2 < 0) - rv = ucv_double_new(-(1.0 / (double)upow64(abs64(n1), abs64(n2)))); - else if (n2 < 0) - rv = ucv_double_new(1.0 / (double)upow64(abs64(n1), abs64(n2))); - else - rv = ucv_int64_new(-upow64(abs64(n1), abs64(n2))); - } - else { - if (!u1) u1 = (uint64_t)n1; - if (!u2) u2 = (uint64_t)n2; - - rv = ucv_uint64_new(upow64(u1, u2)); - } - - break; - - default: - uc_vm_raise_exception(vm, EXCEPTION_RUNTIME, - "undefined arithmetic operation %d", - operation); - break; - } - } - - ucv_put(nv1); - ucv_put(nv2); - - return rv; -} static void uc_vm_insn_update_var(uc_vm_t *vm, uc_vm_insn_t insn) @@ -1759,7 +1419,8 @@ uc_vm_insn_update_var(uc_vm_t *vm, uc_vm_insn_t insn) scope = next; } - val = uc_vm_value_arith(vm, vm->arg.u32 >> 24, val, inc); + val = ucv_arith_binary(vm, + uc_vm_insn_to_token(vm->arg.u32 >> 24), val, inc); ucv_object_add(scope, ucv_string_get(name), ucv_get(val)); uc_vm_stack_push(vm, val); @@ -1781,7 +1442,9 @@ uc_vm_insn_update_val(uc_vm_t *vm, uc_vm_insn_t insn) case UC_ARRAY: if (assert_mutable_value(vm, v)) { val = ucv_key_get(vm, v, k); - uc_vm_stack_push(vm, ucv_key_set(vm, v, k, uc_vm_value_arith(vm, vm->arg.u8, val, inc))); + uc_vm_stack_push(vm, ucv_key_set(vm, v, k, + ucv_arith_binary(vm, uc_vm_insn_to_token(vm->arg.u8), + val, inc))); } break; @@ -1814,7 +1477,8 @@ uc_vm_insn_update_upval(uc_vm_t *vm, uc_vm_insn_t insn) else val = vm->stack.entries[ref->slot]; - val = uc_vm_value_arith(vm, vm->arg.u32 >> 24, val, inc); + val = ucv_arith_binary(vm, uc_vm_insn_to_token(vm->arg.u32 >> 24), + val, inc); uc_vm_stack_push(vm, val); @@ -1837,8 +1501,8 @@ uc_vm_insn_update_local(uc_vm_t *vm, uc_vm_insn_t insn) uc_value_t *inc = uc_vm_stack_pop(vm); uc_value_t *val; - val = uc_vm_value_arith(vm, vm->arg.u32 >> 24, - vm->stack.entries[frame->stackframe + slot], inc); + val = ucv_arith_binary(vm, uc_vm_insn_to_token(vm->arg.u32 >> 24), + vm->stack.entries[frame->stackframe + slot], inc); uc_vm_stack_push(vm, val); @@ -1952,13 +1616,13 @@ uc_vm_insn_mobj(uc_vm_t *vm, uc_vm_insn_t insn) } static void -uc_vm_insn_arith(uc_vm_t *vm, uc_vm_insn_t insn) +uc_vm_insn_arith_binary(uc_vm_t *vm, uc_vm_insn_t insn) { uc_value_t *r2 = uc_vm_stack_pop(vm); uc_value_t *r1 = uc_vm_stack_pop(vm); uc_value_t *rv; - rv = uc_vm_value_arith(vm, insn, r1, r2); + rv = ucv_arith_binary(vm, uc_vm_insn_to_token(insn), r1, r2); ucv_put(r1); ucv_put(r2); @@ -1967,114 +1631,18 @@ uc_vm_insn_arith(uc_vm_t *vm, uc_vm_insn_t insn) } static void -uc_vm_insn_plus_minus(uc_vm_t *vm, uc_vm_insn_t insn) -{ - uc_value_t *v = uc_vm_stack_pop(vm), *nv; - bool is_sub = (insn == I_MINUS); - int64_t n; - double d; - - if (ucv_type(v) == UC_STRING) { - nv = uc_number_parse(ucv_string_get(v), NULL); - - if (nv) { - ucv_put(v); - v = nv; - } - } - - switch (ucv_type(v)) { - case UC_INTEGER: - n = ucv_int64_get(v); - - /* numeric value is in range 9223372036854775808..18446744073709551615 */ - if (errno == ERANGE) { - if (is_sub) - /* make negation of large numeric value result in smallest negative value */ - uc_vm_stack_push(vm, ucv_int64_new(INT64_MIN)); - else - /* for positive number coercion return value as-is */ - uc_vm_stack_push(vm, ucv_get(v)); - } - - /* numeric value is in range -9223372036854775808..9223372036854775807 */ - else { - if (is_sub) { - if (n == INT64_MIN) - /* make negation of minimum value result in maximum signed positive value */ - uc_vm_stack_push(vm, ucv_int64_new(INT64_MAX)); - else - /* for all other values flip the sign */ - uc_vm_stack_push(vm, ucv_int64_new(-n)); - } - else { - /* for positive number coercion return value as-is */ - uc_vm_stack_push(vm, ucv_get(v)); - } - } - - break; - - case UC_DOUBLE: - d = ucv_double_get(v); - uc_vm_stack_push(vm, ucv_double_new(is_sub ? -d : d)); - break; - - case UC_BOOLEAN: - n = (int64_t)ucv_boolean_get(v); - uc_vm_stack_push(vm, ucv_int64_new(is_sub ? -n : n)); - break; - - case UC_NULL: - uc_vm_stack_push(vm, ucv_int64_new(0)); - break; - - default: - uc_vm_stack_push(vm, ucv_double_new(NAN)); - } - - ucv_put(v); -} - -static void -uc_vm_insn_bitop(uc_vm_t *vm, uc_vm_insn_t insn) +uc_vm_insn_arith_unary(uc_vm_t *vm, uc_vm_insn_t insn) { - uc_value_t *r2 = uc_vm_stack_pop(vm); - uc_value_t *r1 = uc_vm_stack_pop(vm); + uc_value_t *v = uc_vm_stack_pop(vm); uc_value_t *rv; - rv = uc_vm_value_bitop(vm, insn, r1, r2); + rv = ucv_arith_unary(vm, uc_vm_insn_to_token(insn), v); - ucv_put(r1); - ucv_put(r2); + ucv_put(v); uc_vm_stack_push(vm, rv); } -static void -uc_vm_insn_complement(uc_vm_t *vm, uc_vm_insn_t insn) -{ - uc_value_t *v = uc_vm_stack_pop(vm); - uc_value_t *nv; - uint64_t u; - int64_t n; - - nv = ucv_to_number(v); - n = int64(nv, &u); - - if (n < 0) { - uc_vm_stack_push(vm, ucv_int64_new(~n)); - } - else { - if (!u) u = (uint64_t)n; - - uc_vm_stack_push(vm, ucv_uint64_new(~u)); - } - - ucv_put(nv); - ucv_put(v); -} - static void uc_vm_insn_rel(uc_vm_t *vm, uc_vm_insn_t insn) { @@ -2840,24 +2408,18 @@ uc_vm_execute_chunk(uc_vm_t *vm) case I_DIV: case I_MOD: case I_EXP: - uc_vm_insn_arith(vm, insn); - break; - - case I_PLUS: - case I_MINUS: - uc_vm_insn_plus_minus(vm, insn); - break; - case I_LSHIFT: case I_RSHIFT: case I_BAND: case I_BXOR: case I_BOR: - uc_vm_insn_bitop(vm, insn); + uc_vm_insn_arith_binary(vm, insn); break; + case I_PLUS: + case I_MINUS: case I_COMPL: - uc_vm_insn_complement(vm, insn); + uc_vm_insn_arith_unary(vm, insn); break; case I_EQS: From 117b29f2b8d1aae30aab3c950f265917f4f7aaaa Mon Sep 17 00:00:00 2001 From: Jo-Philipp Wich Date: Wed, 7 Aug 2024 12:58:17 +0200 Subject: [PATCH 5/5] vscode: implement a language server extension for Visual Studio Code This commit introduces a vscode extension for improved ucode support within the IDE. It features auto complete suggestions as well as semantic syntax highlighting, compile time error reporting and support for JSDoc type annotations in variable or function declarations. As part of the vscode extensions, a new ucode library "uscope" is introduced which provides facilities for static ucode source analysis, e.g. to determine the validity scope of variable declarations or to properly tokenize ucode sources. Signed-off-by: Jo-Philipp Wich --- CMakeLists.txt | 8 + lib/uscope/jsdoc.c | 2608 +++++++++ lib/uscope/jsdoc.h | 146 + lib/uscope/parse.c | 4132 ++++++++++++++ lib/uscope/uscope.h | 58 + vscode/.vscodeignore | 3 + vscode/CHANGELOG.md | 0 vscode/LICENSE | 1 + vscode/README.md | 101 + vscode/client/src/extension.ts | 82 + vscode/client/tsconfig.json | 12 + vscode/language-configuration.json | 25 + vscode/package.json | 136 + vscode/server/launcher.uc | 154 + vscode/server/server.uc | 991 ++++ vscode/server/types.json | 8057 ++++++++++++++++++++++++++++ vscode/tsconfig.json | 20 + 17 files changed, 16534 insertions(+) create mode 100644 lib/uscope/jsdoc.c create mode 100644 lib/uscope/jsdoc.h create mode 100644 lib/uscope/parse.c create mode 100644 lib/uscope/uscope.h create mode 100644 vscode/.vscodeignore create mode 100644 vscode/CHANGELOG.md create mode 120000 vscode/LICENSE create mode 100644 vscode/README.md create mode 100644 vscode/client/src/extension.ts create mode 100644 vscode/client/tsconfig.json create mode 100644 vscode/language-configuration.json create mode 100644 vscode/package.json create mode 100644 vscode/server/launcher.uc create mode 100644 vscode/server/server.uc create mode 100644 vscode/server/types.json create mode 100644 vscode/tsconfig.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 77e9b7af..47e78fb6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,6 +74,7 @@ option(SOCKET_SUPPORT "Socket plugin support" ON) option(ZLIB_SUPPORT "Zlib plugin support" ${DEFAULT_ZLIB_SUPPORT}) option(DIGEST_SUPPORT "Digest plugin support" ${DEFAULT_DIGEST_SUPPORT}) option(DIGEST_SUPPORT_EXTENDED "Enable additional hash algorithms" ${DEFAULT_DIGEST_SUPPORT}) +option(USCOPE_SUPPORT "ucode source inspection plugin support" ON) set(LIB_SEARCH_PATH "${CMAKE_INSTALL_PREFIX}/lib/ucode/*.so:${CMAKE_INSTALL_PREFIX}/share/ucode/*.uc:./*.so:./*.uc" CACHE STRING "Default library search path") string(REPLACE ":" "\", \"" LIB_SEARCH_DEFINE "${LIB_SEARCH_PATH}") @@ -310,6 +311,13 @@ if(DIGEST_SUPPORT) target_link_libraries(digest_lib ${libmd}) endif() +if(USCOPE_SUPPORT) + set(LIBRARIES ${LIBRARIES} uscope_lib) + add_library(uscope_lib MODULE lib/uscope/parse.c lib/uscope/jsdoc.c) + set_target_properties(uscope_lib PROPERTIES OUTPUT_NAME uscope PREFIX "") + target_link_options(uscope_lib PRIVATE ${UCODE_MODULE_LINK_OPTIONS}) +endif() + if(UNIT_TESTING) enable_testing() add_definitions(-DUNIT_TESTING) diff --git a/lib/uscope/jsdoc.c b/lib/uscope/jsdoc.c new file mode 100644 index 00000000..68c99666 --- /dev/null +++ b/lib/uscope/jsdoc.c @@ -0,0 +1,2608 @@ +#include +#include +#include +#include + +#include + +#include +#include + +#include "jsdoc.h" +#include "uscope.h" + + +typedef enum { + T_ERROR, + T_EOF, + T_IDENT, + T_STRING, + T_ARROW, + T_ELLIP, + T_COLON = ':', + T_ANGLE_L = '<', + T_ANGLE_R = '>', + T_COMMA = ',', + T_BRACE_L = '{', + T_BRACE_R = '}', + T_BRACK_L = '[', + T_BRACK_R = ']', + T_PAREN_L = '(', + T_PAREN_R = ')', + T_ASTERISK = '*', + T_DOT = '.', + T_QMARK = '?', + T_EMARK = '!', + T_ALT = '|', +} jsdoc_token_type_t; + +typedef struct { + jsdoc_token_type_t type; + struct { + size_t count; + char *entries; + } value; +} jsdoc_token_t; + +typedef struct { + char *input; + size_t length; + jsdoc_token_t prev; + jsdoc_token_t curr; + struct { + size_t count; + jsdoc_token_t *entries; + } lookahead; +} jsdoc_parser_t; + +typedef enum { + TAG_NONE, + TAG_UNKNOWN, + + TAG_CLASS, + TAG_CONSTANT, + TAG_CONSTRUCTS, + TAG_ENUM, + TAG_EVENT, + TAG_EXTERNAL, + TAG_FILE, + TAG_FUNCTION, + TAG_INTERFACE, + TAG_KIND, + TAG_MEMBER, + TAG_MEMBEROF, + TAG_MIXIN, + TAG_MODULE, + TAG_NAME, + TAG_NAMESPACE, + TAG_TYPE, + TAG_TYPEDEF, + TAG_RETURNS, + TAG_PARAM, + TAG_PROPERTY, + TAG_THROWS, + TAG_DEFAULT, + TAG_DESCRIPTION, + TAG_FIRES, + TAG_YIELDS, + TAG_ELEMENT, +} jsdoc_tag_type_t; + +static const struct { + const char *name; + jsdoc_tag_type_t type; +} tag_types[] = { + { "class", TAG_CLASS }, + { "constant", TAG_CONSTANT }, + { "constructs", TAG_CONSTRUCTS }, + { "enum", TAG_ENUM }, + { "event", TAG_EVENT }, + { "external", TAG_EXTERNAL }, + { "file", TAG_FILE }, + { "function", TAG_FUNCTION }, + { "interface", TAG_INTERFACE }, + { "kind", TAG_KIND }, + { "member", TAG_MEMBER }, + { "memberof", TAG_MEMBEROF }, + { "mixin", TAG_MIXIN }, + { "module", TAG_MODULE }, + { "name", TAG_NAME }, + { "namespace", TAG_NAMESPACE }, + { "type", TAG_TYPE }, + { "typedef", TAG_TYPEDEF }, + { "returns", TAG_RETURNS }, + { "param", TAG_PARAM }, + { "property", TAG_PROPERTY }, + { "throws", TAG_THROWS }, + { "element", TAG_ELEMENT }, + + { "constructor", TAG_CLASS }, + { "const", TAG_CONSTANT }, + { "prop", TAG_PROPERTY }, + { "var", TAG_MEMBER }, + { "defaultvalue", TAG_DEFAULT }, + { "desc", TAG_DESCRIPTION }, + { "fileoverview", TAG_FILE }, + { "overview", TAG_FILE }, + { "emits", TAG_FIRES }, + { "func", TAG_FUNCTION }, + { "method", TAG_FUNCTION }, + { "arg", TAG_PARAM }, + { "argument", TAG_PARAM }, + { "return", TAG_RETURNS }, + { "exception", TAG_THROWS }, + { "yield", TAG_YIELDS }, + { "elem", TAG_ELEMENT }, +}; + +typedef struct { + jsdoc_tag_type_t type; + uc_stringbuf_t value; +} jsdoc_tag_t; + +static jsdoc_token_t tok = { 0 }; + +static jsdoc_token_t * +parse_token(char **ptr, size_t *len) +{ + tok.type = 0; + tok.value.count = 0; + tok.value.entries = NULL; + + while (*len > 0 && isspace(**ptr)) + (*ptr)++, (*len)--; + + if (*len == 0) { + tok.type = T_EOF; + } + else if (**ptr == '"' || **ptr == '\'') { + char quot = **ptr; + bool esc = false; + + tok.type = T_STRING; + + for ((*ptr)++, (*len)--; *len > 0; (*ptr)++, (*len)--) { + if (esc) { + switch (**ptr) { + case 'n': uc_vector_push(&tok.value, '\n'); break; + case 'r': uc_vector_push(&tok.value, '\r'); break; + case 't': uc_vector_push(&tok.value, '\t'); break; + default: uc_vector_push(&tok.value, **ptr); break; + } + + esc = false; + } + else if (**ptr == '\\') { + esc = true; + } + else if (**ptr == quot) { + break; + } + else { + uc_vector_push(&tok.value, **ptr); + } + } + + if (*len == 0) { + uc_vector_clear(&tok.value); + tok.type = T_ERROR; + } + + (*ptr)++, (*len)--; + } + else if (isalpha(**ptr) || **ptr == '_') { + tok.type = T_IDENT; + + while (*len > 0 && (isalnum(**ptr) || **ptr == '_')) { + uc_vector_push(&tok.value, **ptr); + (*ptr)++, (*len)--; + + if (*len > 0 && **ptr == ':' && tok.value.count == 6 && + !strncmp(tok.value.entries, "module", 6)) { + uc_vector_push(&tok.value, ':'); + (*ptr)++, (*len)--; + } + } + } + else if (*len >= 3 && !strncmp(*ptr, "...", 3)) { + tok.type = T_ELLIP; + *ptr += 3; *len -= 3; + } + else if (*len >= 2 && !strncmp(*ptr, "=>", 2)) { + tok.type = T_ARROW; + *ptr += 2, *len -= 2; + } + else if (strchr("<>{}[]().,:*?!|", **ptr)) { + tok.type = **ptr; + (*ptr)++, (*len)--; + } + else { + tok.type = T_ERROR; + } + + return &tok; +} + +static jsdoc_token_type_t +skip_token(char **ptr, size_t *len) +{ + jsdoc_token_t *tok = parse_token(ptr, len); + + uc_vector_clear(&tok->value); + + return tok->type; +} + +static const struct { + const char *name; + jsdoc_type_t type; +} typemap[] = { + { "string", TYPE_STRING }, + { "integer", TYPE_INTEGER }, + { "int", TYPE_INTEGER }, + { "number", TYPE_NUMBER }, + { "double", TYPE_DOUBLE }, + { "float", TYPE_DOUBLE }, + { "boolean", TYPE_BOOLEAN }, + { "bool", TYPE_BOOLEAN }, + { "Array", TYPE_ARRAY }, + { "array", TYPE_ARRAY }, + { "Object", TYPE_OBJECT }, + { "object", TYPE_OBJECT }, + { "function", TYPE_FUNCTION }, + { "any", TYPE_ANY }, +}; + +static const struct { + const char *name; + jsdoc_kind_t kind; +} kindmap[] = { + { "class", KIND_CLASS }, + { "constant", KIND_CONSTANT }, + { "event", KIND_EVENT }, + { "external", KIND_EXTERNAL }, + { "file", KIND_FILE }, + { "function", KIND_FUNCTION }, + { "member", KIND_MEMBER }, + { "mixin", KIND_MIXIN }, + { "module", KIND_MODULE }, + { "namespace", KIND_NAMESPACE }, + { "typedef", KIND_TYPEDEF }, +}; + +static void +ucv_replace(uc_value_t **dest, uc_value_t *src) +{ + ucv_put(*dest); + *dest = src; +} + +static void +ucv_update(uc_value_t **dest, uc_value_t *src) +{ + if (src) { + ucv_put(*dest); + *dest = ucv_get(src); + } +} + +static void +ucv_clear(uc_value_t **uv) +{ + ucv_put(*uv); + *uv = NULL; +} + +static void +parse_advance(jsdoc_parser_t *p) +{ + p->prev = p->curr; + + if (p->lookahead.count > 0) { + for (size_t i = 0; i < p->lookahead.count; i++) { + if (i == 0) + p->curr = p->lookahead.entries[i]; + else + p->lookahead.entries[i - 1] = p->lookahead.entries[i]; + } + + p->lookahead.count--; + } + else { + p->curr = *parse_token(&p->input, &p->length); + } +} + +static bool +parse_consume(jsdoc_parser_t *p, jsdoc_token_type_t type) +{ + if (p->curr.type != type) + return false; + + parse_advance(p); + + return true; +} + +static bool +parse_capture(jsdoc_parser_t *p, jsdoc_token_type_t type, uc_value_t **dest) +{ + if (p->curr.type != type) + return false; + + ucv_replace(dest, + ucv_string_new_length(p->curr.value.entries, p->curr.value.count)); + + uc_vector_clear(&p->curr.value); + parse_advance(p); + + return true; +} + +static jsdoc_token_type_t +parse_peek(jsdoc_parser_t *p, size_t depth) +{ + while (p->lookahead.count <= depth) { + jsdoc_token_t *tok = parse_token(&p->input, &p->length); + uc_vector_push(&p->lookahead, *tok); + } + + return p->lookahead.entries[depth].type; +} + +static jsdoc_typedef_t * +parse_single_type(jsdoc_parser_t *p); + +static jsdoc_typedef_t * +parse_union_type(jsdoc_parser_t *p) +{ + jsdoc_typedef_t *spec = NULL, *alt, *tmp; + size_t alt_count = 0; + + while ((alt = parse_single_type(p)) != NULL) { + if (alt_count == 0) { + spec = alt; + } + else if (alt_count == 1) { + tmp = xalloc(sizeof(*tmp)); + tmp->type = TYPE_UNION; + + uc_vector_push(&tmp->details.alternatives, spec); + uc_vector_push(&tmp->details.alternatives, alt); + + spec = tmp; + } + else { + uc_vector_push(&spec->details.alternatives, alt); + } + + if (!parse_consume(p, '|')) + break; + + alt_count++; + } + + return spec; +} + +static jsdoc_type_t +jsdoc_type_lookup(uc_value_t *name) +{ + char *s = ucv_string_get(name); + + for (size_t i = 0; i < ARRAY_SIZE(typemap); i++) + if (!strcmp(typemap[i].name, s)) + return typemap[i].type; + + return TYPE_UNSPEC; +} + +static bool +jsdoc_type_imply(jsdoc_typedef_t **specp, jsdoc_type_t type) +{ + if (!*specp) { + *specp = xalloc(sizeof(**specp)); + (*specp)->type = type; + + return true; + } + + return ((*specp)->type == type); +} + +static uc_value_t * +parse_dotted_name(jsdoc_parser_t *p, uc_value_t *label) +{ + if (p->curr.type != '.') + return label; + + uc_stringbuf_t *sbuf = ucv_stringbuf_new(); + + printbuf_memappend_fast(sbuf, + ucv_string_get(label), + (int)ucv_string_length(label)); + + ucv_clear(&label); + + while (p->curr.type == '.' && parse_peek(p, 0) == T_IDENT) { + parse_consume(p, '.'); + parse_capture(p, T_IDENT, &label); + + printbuf_strappend(sbuf, "."); + + printbuf_memappend_fast(sbuf, + ucv_string_get(label), + (int)ucv_string_length(label)); + + ucv_clear(&label); + } + + return ucv_stringbuf_finish(sbuf); +} + +static jsdoc_typedef_t * +parse_single_type(jsdoc_parser_t *p) +{ + if (p->curr.type == T_EOF) + return NULL; + + jsdoc_typedef_t *spec = NULL; + + if (p->curr.type == '(') { + struct { size_t count; jsdoc_token_type_t *entries; } parens = { 0 }; + size_t lookahead = 0; + + while (true) { + jsdoc_token_type_t tt = parse_peek(p, lookahead++); + + switch (tt) { + case '(': uc_vector_push(&parens, ')'); break; + case '<': uc_vector_push(&parens, '>'); break; + case '{': uc_vector_push(&parens, '}'); break; + case '[': uc_vector_push(&parens, ']'); break; + default: break; + } + + if (parens.count == 0 && tt == ')') + break; + + if (parens.count > 0 && tt == parens.entries[parens.count - 1]) + parens.count--; + } + + uc_vector_clear(&parens); + + if (parse_peek(p, lookahead++) != T_ARROW) { + parse_consume(p, '('); + + spec = parse_union_type(p); + + if (!parse_consume(p, ')')) + goto inval; + + return spec; + } + + jsdoc_type_imply(&spec, TYPE_FUNCTION); + } + else if (parse_consume(p, '{')) { + jsdoc_type_imply(&spec, TYPE_OBJECT); + + jsdoc_property_t prop = { 0 }; + + while (parse_capture(p, T_IDENT, &prop.name) || parse_capture(p, T_STRING, &prop.name)) { + prop.optional = parse_consume(p, '?'); + + if (parse_consume(p, ':')) + prop.type = parse_union_type(p); + else + jsdoc_type_imply(&prop.type, TYPE_ANY); + + uc_vector_push(&spec->details.object.properties, prop); + memset(&prop, 0, sizeof(prop)); + + if (!parse_consume(p, ',')) + break; + } + + if (!parse_consume(p, '}')) + goto inval; + + return spec; + } + else if (parse_consume(p, '[')) { + jsdoc_type_imply(&spec, TYPE_ARRAY); + + jsdoc_element_t elem = { 0 }; + + while (p->curr.type != ']') { + if ((p->curr.type == T_IDENT || p->curr.type == T_STRING) && parse_peek(p, ':')) { + parse_capture(p, p->curr.type, &elem.name); + parse_consume(p, ':'); + } + + elem.type = parse_union_type(p); + + if (!elem.type) { + ucv_put(elem.name); + goto inval; + } + + uc_vector_push(&spec->details.array.elements, elem); + memset(&elem, 0, sizeof(elem)); + + if (!parse_consume(p, ',')) + break; + } + + if (!parse_consume(p, ']')) + goto inval; + + return spec; + } + else { + uc_value_t *ident = NULL; + + spec = xalloc(sizeof(*spec)); + + if (parse_consume(p, '?')) + spec->nullable = true; + else if (parse_consume(p, '!')) + spec->required = true; + + if (parse_consume(p, '*') || parse_consume(p, '?')) { + spec->type = TYPE_ANY; + } + else if (parse_capture(p, T_IDENT, &ident)) { + ident = parse_dotted_name(p, ident); + spec->type = jsdoc_type_lookup(ident); + + if (spec->type == TYPE_UNSPEC) { + spec->type = TYPE_TYPENAME; + ucv_replace(&spec->details.typename, ident); + } + else { + ucv_put(ident); + } + } + else { + goto inval; + } + } + + if (spec->type == TYPE_OBJECT) { + if ((parse_consume(p, '.') && parse_consume(p, '<')) || parse_consume(p, '<')) { + spec->details.object.val_type = parse_single_type(p); + + if (parse_consume(p, ',')) { + spec->details.object.key_type = spec->details.object.val_type; + spec->details.object.val_type = parse_single_type(p); + } + + if (!parse_consume(p, '>')) + goto inval; + } + } + else if (spec->type == TYPE_ARRAY) { + if ((parse_consume(p, '.') && parse_consume(p, '<')) || parse_consume(p, '>')) { + spec->details.array.item_type = parse_single_type(p); + + if (!parse_consume(p, '>')) + goto inval; + } + } + else if (spec->type == TYPE_FUNCTION) { + /* parse arglist */ + if (parse_consume(p, '(')) { + while (p->curr.type != ')') { + jsdoc_param_t param = { 0 }; + + param.restarg = parse_consume(p, T_ELLIP); + + if (parse_peek(p, 0) == ':') { + if (!parse_capture(p, T_IDENT, ¶m.name)) + goto inval; + + parse_consume(p, ':'); + } + + param.type = parse_union_type(p); + + if (param.type) { + uc_vector_push(&spec->details.function.params, param); + } + else { + free(param.name); + goto inval; + } + + if (!parse_consume(p, ',')) + break; + } + + if (!parse_consume(p, ')')) + goto inval; + } + + if (parse_consume(p, ':') || parse_consume(p, T_ARROW)) { + spec->details.function.return_type = parse_union_type(p); + + if (!spec->details.function.return_type) + goto inval; + } + else { + jsdoc_type_imply(&spec->details.function.return_type, TYPE_ANY); + } + } + + if (parse_consume(p, '[')) { + if (!parse_consume(p, ']')) + goto inval; + + jsdoc_typedef_t *arr = xalloc(sizeof(*arr)); + + arr->type = TYPE_ARRAY; + arr->details.array.item_type = spec; + + return arr; + } + + return spec; + +inval: + jsdoc_typedef_free(spec); + + return NULL; +} + +static bool +type_equal(const jsdoc_typedef_t *a, const jsdoc_typedef_t *b) +{ + if (!a || !b) + return false; + + if (a->type != b->type) + return false; + + if (a->type == TYPE_TYPENAME) { + if (!a->details.typename || !b->details.typename) + return false; + + return ucv_is_equal(a->details.typename, b->details.typename); + } + + return true; +} + +static int +cmp_typedef(const void *a, const void *b) +{ + const jsdoc_typedef_t * const *t1 = a; + const jsdoc_typedef_t * const *t2 = b; + + return (int)(*t1)->type - (int)(*t2)->type; +} + +static jsdoc_typedef_t * +union_typedef_upsert(jsdoc_typedef_t *dst, const jsdoc_typedef_t *src, + unsigned int flags) +{ + uc_vector_foreach(&dst->details.alternatives, dst_alt) { + if (type_equal(src, *dst_alt)) { + jsdoc_typedef_merge(dst_alt, src, flags); + + return *dst_alt; + } + } + + uc_vector_push(&dst->details.alternatives, NULL); + + jsdoc_typedef_t **dst_alt = uc_vector_last(&dst->details.alternatives); + + jsdoc_typedef_merge(dst_alt, src, flags); + + return *dst_alt; +} + +static void +union_typedef_sort(jsdoc_typedef_t *dst) +{ + qsort(dst->details.alternatives.entries, + dst->details.alternatives.count, + sizeof(dst->details.alternatives.entries[0]), + cmp_typedef); +} + +bool +jsdoc_typedef_merge(jsdoc_typedef_t **dest, const jsdoc_typedef_t *src, + unsigned int flags) +{ + if (!src) + return false; + + jsdoc_type_imply(dest, TYPE_ANY); + + if ((*dest)->type == TYPE_UNION && src->type != TYPE_UNION) { + union_typedef_upsert(*dest, src, flags); + union_typedef_sort(*dest); + + return true; + } + + if ((*dest)->type != TYPE_UNSPEC && (*dest)->type != TYPE_ANY && !type_equal(src, *dest)) { + if (!(flags & MERGE_UNION) && (*dest)->type != TYPE_UNION) + return false; + + jsdoc_typedef_t *u = NULL; + + jsdoc_type_imply(&u, TYPE_UNION); + + uc_vector_push(&u->details.alternatives, *dest); + + union_typedef_upsert(u, src, flags & ~MERGE_UNION); + union_typedef_sort(u); + + *dest = u; + + return true; + } + + (*dest)->type = src->type; + (*dest)->nullable |= src->nullable; + (*dest)->required |= src->required; + + if (!(flags & MERGE_TYPEONLY)) + ucv_update(&(*dest)->value, src->value); + + switch (src->type) { + case TYPE_FUNCTION: + jsdoc_typedef_merge( + &(*dest)->details.function.return_type, + src->details.function.return_type, flags); + + ucv_update( + &(*dest)->details.function.return_description, + src->details.function.return_description); + + for (size_t i = 0; i < src->details.function.params.count; i++) { + jsdoc_param_t *src_param = uscope_vector_get(&src->details.function.params, i); + jsdoc_param_t *dst_param = uscope_vector_get(&(*dest)->details.function.params, i); + + if (!dst_param) + dst_param = uc_vector_push(&(*dest)->details.function.params, { 0 }); + + ucv_update(&dst_param->name, src_param->name); + ucv_update(&dst_param->defval, src_param->defval); + ucv_update(&dst_param->description, src_param->description); + + dst_param->optional |= src_param->optional; + dst_param->restarg |= src_param->restarg; + + jsdoc_typedef_merge(&dst_param->type, src_param->type, flags); + } + + uc_vector_foreach(&src->details.function.throws, src_throw) { + jsdoc_throws_t *cpy_throw = NULL; + + uc_vector_foreach(&(*dest)->details.function.throws, dst_throw) { + if (type_equal(dst_throw->type, src_throw->type)) { + cpy_throw = src_throw; + break; + } + } + + if (!cpy_throw) + cpy_throw = uc_vector_push(&(*dest)->details.function.throws, { 0 }); + + ucv_update(&cpy_throw->description, src_throw->description); + + jsdoc_typedef_merge(&cpy_throw->type, src_throw->type, flags); + } + + break; + + case TYPE_OBJECT: + uc_vector_foreach(&src->details.object.properties, src_prop) { + if (flags & MERGE_NOELEMS) + continue; + + jsdoc_property_t *cpy_prop = NULL; + + uc_vector_foreach(&(*dest)->details.object.properties, dst_prop) { + if (!src_prop->name || !dst_prop->name) + continue; + + if (!ucv_is_equal(src_prop->name, dst_prop->name)) + continue; + + cpy_prop = dst_prop; + break; + } + + if (!cpy_prop) + cpy_prop = uc_vector_push(&(*dest)->details.object.properties, { 0 }); + + ucv_update(&cpy_prop->name, src_prop->name); + ucv_update(&cpy_prop->defval, src_prop->defval); + ucv_update(&cpy_prop->description, src_prop->description); + + cpy_prop->optional |= src_prop->optional; + + jsdoc_typedef_merge(&cpy_prop->type, src_prop->type, flags); + } + + jsdoc_typedef_merge( + &(*dest)->details.object.key_type, + src->details.object.key_type, flags); + + jsdoc_typedef_merge( + &(*dest)->details.object.val_type, + src->details.object.val_type, flags); + + break; + + case TYPE_ARRAY: + jsdoc_typedef_merge( + &(*dest)->details.array.item_type, + src->details.array.item_type, flags); + + for (size_t i = 0; i < src->details.array.elements.count; i++) { + if (flags & MERGE_NOELEMS) + continue; + + jsdoc_element_t *src_elem, *dst_elem; + + src_elem = uscope_vector_get(&src->details.array.elements, i); + dst_elem = (i >= (*dest)->details.array.elements.count) + ? uc_vector_push(&(*dest)->details.array.elements, { 0 }) + : uscope_vector_get(&(*dest)->details.array.elements, i); + + ucv_update(&dst_elem->name, src_elem->name); + ucv_update(&dst_elem->description, src_elem->description); + + jsdoc_typedef_merge(&dst_elem->type, src_elem->type, flags); + } + + break; + + case TYPE_UNION: + uc_vector_foreach(&src->details.alternatives, src_alt) + union_typedef_upsert(*dest, *src_alt, flags); + + union_typedef_sort(*dest); + break; + + case TYPE_TYPENAME: + ucv_update(&(*dest)->details.typename, src->details.typename); + break; + + default: + break; + } + + return true; +} + +void +jsdoc_typedef_free(jsdoc_typedef_t *spec) +{ + if (!spec) + return; + + switch (spec->type) { + case TYPE_OBJECT: + jsdoc_typedef_free(spec->details.object.key_type); + jsdoc_typedef_free(spec->details.object.val_type); + + while (spec->details.object.properties.count > 0) { + jsdoc_property_t *prop = uc_vector_last(&spec->details.object.properties); + + jsdoc_typedef_free(prop->type); + ucv_put(prop->name); + ucv_put(prop->defval); + ucv_put(prop->description); + + spec->details.object.properties.count--; + } + + uc_vector_clear(&spec->details.object.properties); + break; + + case TYPE_ARRAY: + jsdoc_typedef_free(spec->details.array.item_type); + + while (spec->details.array.elements.count > 0) { + jsdoc_element_t *elem = uc_vector_last(&spec->details.array.elements); + + jsdoc_typedef_free(elem->type); + ucv_put(elem->name); + ucv_put(elem->description); + + spec->details.array.elements.count--; + } + + uc_vector_clear(&spec->details.array.elements); + break; + + case TYPE_FUNCTION: + while (spec->details.function.params.count > 0) { + jsdoc_param_t *param = uc_vector_last(&spec->details.function.params); + + jsdoc_typedef_free(param->type); + ucv_put(param->name); + ucv_put(param->defval); + ucv_put(param->description); + + spec->details.function.params.count--; + } + + jsdoc_typedef_free(spec->details.function.return_type); + ucv_put(spec->details.function.return_description); + + uc_vector_clear(&spec->details.function.params); + break; + + case TYPE_UNION: + while (spec->details.alternatives.count > 0) + jsdoc_typedef_free(spec->details.alternatives.entries[--spec->details.alternatives.count]); + + uc_vector_clear(&spec->details.alternatives); + break; + + case TYPE_TYPENAME: + ucv_put(spec->details.typename); + break; + + default: + break; + } + + ucv_put(spec->value); + free(spec); +} + +void +jsdoc_reset(jsdoc_t *js) +{ + js->kind = KIND_UNSPEC; + js->constant = false; + + ucv_clear(&js->name); + ucv_clear(&js->defval); + ucv_clear(&js->subject); + ucv_clear(&js->description); + + jsdoc_typedef_free(js->type); + js->type = NULL; +} + +void +jsdoc_free(jsdoc_t *js) +{ + if (js) { + jsdoc_reset(js); + free(js); + } +} + +static void +parse_init(jsdoc_parser_t *p, char *input, size_t length) +{ + memset(p, 0, sizeof(*p)); + + p->input = input; + p->length = length; + + parse_advance(p); +} + +static void +parse_free(jsdoc_parser_t *p) +{ + while (p->lookahead.count > 0) { + uc_vector_clear(&p->lookahead.entries[p->lookahead.count - 1].value); + p->lookahead.count--; + } + + uc_vector_clear(&p->curr.value); + uc_vector_clear(&p->prev.value); + uc_vector_clear(&p->lookahead); +} + +static bool +skip_char(char **s, size_t *len, char c) +{ + while (*len > 0 && isspace(**s)) + (*s)++, (*len)--; + + if (*len == 0 || **s != c) + return false; + + (*s)++, (*len)--; + + return true; +} + +static size_t +json_parse(json_tokener *jstok, const char *s, size_t len, json_object **res) +{ + enum json_tokener_error err = json_tokener_get_error(jstok); + + if (err != json_tokener_success && err != json_tokener_continue) + return 0; + + json_object *jso = json_tokener_parse_ex(jstok, s, len); + + if (jso) { + json_object_put(*res); + *res = jso; + } + + return json_tokener_get_parse_end(jstok); +} + +static bool +parse_value(uc_vm_t *vm, char **input, size_t *len, uc_value_t **res) +{ + if (skip_char(input, len, '{')) { + uc_value_t *uv = ucv_object_new(vm); + + while (*len > 0 && !skip_char(input, len, '}')) { + uc_value_t *key = NULL, *val = NULL; + + if (!parse_value(vm, input, len, &key) || + !skip_char(input, len, ':') || ucv_type(key) != UC_STRING || + !parse_value(vm, input, len, &val)) { + + ucv_put(key); + ucv_put(val); + ucv_put(uv); + + return false; + } + + ucv_object_add(uv, ucv_string_get(key), val); + ucv_put(key); + + if (!skip_char(input, len, ',')) + break; + } + + if ((*input)[-1] != '}') { + ucv_put(uv); + + return false; + } + + *res = uv; + + return true; + } + else if (skip_char(input, len, '[')) { + uc_value_t *uv = ucv_array_new(vm); + + while (*len > 0 && !skip_char(input, len, ']')) { + uc_value_t *elem = NULL; + + if (!parse_value(vm, input, len, &elem)) { + ucv_put(elem); + ucv_put(uv); + + return false; + } + + if (!skip_char(input, len, ',')) + break; + } + + if ((*input)[-1] != ']') { + ucv_put(uv); + + return false; + } + + *res = uv; + + return true; + } + + skip_char(input, len, 0); + + json_tokener *jstok = json_tokener_new(); + char c = *len ? **input : '\0'; + json_object *jso = NULL; + size_t consumed = 0; + + if (c == '\'') { + bool esc = false; + char *s = *input + 1; + size_t l = *len - 1; + + json_parse(jstok, "\"", 1, &jso); + + for (; l > 0; s++, l--) { + if (esc) + esc = false; + else if (*s == '\\') + esc = true; + else if (*s == '\'') + break; + } + + if (!esc && l > 0 && *s == '\'') { + consumed = json_parse(jstok, *input, s - *input, &jso) + 1; + json_parse(jstok, "\"", 1, &jso); + } + } + else if (isalpha(c) || c == '_') { + char *s = *input; + size_t l = *len; + + for (; l > 0 && isalnum(*s); s++, l--) + ; + + json_parse(jstok, "\"", 1, &jso); + consumed = json_parse(jstok, *input, s - *input, &jso); + json_parse(jstok, "\"", 1, &jso); + } + else { + consumed = json_parse(jstok, *input, *len, &jso); + } + + *input += consumed; + *len -= consumed; + + if (json_tokener_get_error(jstok) != json_tokener_success) { + json_tokener_free(jstok); + json_object_put(jso); + + *res = NULL; + + return false; + } + + *res = ucv_from_json(vm, jso); + + json_tokener_free(jstok); + json_object_put(jso); + + return true; +} + +static jsdoc_tag_type_t +find_tag(const char **line, const char *end, char kind) +{ + for (; *line < end && isspace(**line); (*line)++); + + if (kind == '*') { + if (*line < end && **line == '*') { + (*line)++; + + if (*line < end && **line == ' ') + (*line)++; + } + + const char *lp = *line; + + for (; lp < end && isspace(*lp); lp++); + + if (lp < end && *lp == '@') { + for (const char *s = ++lp, *p = s; p <= end; p++) { + if (p == end || isspace(*p)) { + for (*line = p; *line < end && isspace(**line); (*line)++); + + for (size_t i = 0, taglen = p - s; i < ARRAY_SIZE(tag_types); i++) + if (strlen(tag_types[i].name) == taglen && + strncmp(tag_types[i].name, s, taglen) == 0) + return tag_types[i].type; + + return TAG_UNKNOWN; + } + } + } + } + else if (kind == '/') { + if (*line + 1 < end && !strncmp(*line, "//", 2)) { + *line += 2; + + if (*line < end && **line == ' ') + (*line)++; + } + } + + return TAG_NONE; +} + +static bool +parse_tag_type_name(jsdoc_t *js, jsdoc_tag_t *tag) +{ + uc_value_t *ident = NULL; + jsdoc_typedef_t *tspec; + jsdoc_parser_t p; + + parse_init(&p, tag->value.buf, tag->value.bpos); + + if (parse_consume(&p, '{')) { + tspec = parse_union_type(&p); + + if (!tspec || !parse_consume(&p, '}')) { + jsdoc_typedef_free(tspec); + parse_free(&p); + + return false; + } + + jsdoc_typedef_merge(&js->type, tspec, 0); + jsdoc_typedef_free(tspec); + } + + if (parse_capture(&p, T_IDENT, &ident)) + ucv_replace(&js->name, parse_dotted_name(&p, ident)); + + parse_free(&p); + + return true; +} + +static bool +parse_tag_type_description(jsdoc_tag_t *tag, jsdoc_typedef_t **typep, uc_value_t **descp) +{ + jsdoc_typedef_t *tspec; + jsdoc_parser_t p; + + parse_init(&p, tag->value.buf, tag->value.bpos); + + if (parse_consume(&p, '{')) { + tspec = parse_union_type(&p); + + if (!tspec || !parse_consume(&p, '}')) { + jsdoc_typedef_free(tspec); + parse_free(&p); + + return false; + } + + jsdoc_typedef_merge(typep, tspec, 0); + jsdoc_typedef_free(tspec); + } + + if (p.length > 0 && *p.input == '-') + for (p.input++, p.length--; + p.length > 0 && isspace(*p.input); + p.input++, p.length--); + + if (p.length > 0) + *descp = ucv_string_new_length(p.input, p.length); + + parse_free(&p); + + return true; +} + +static void +parse_tag_returns(jsdoc_t *js, jsdoc_tag_t *tag) +{ + jsdoc_typedef_t *spec = NULL; + uc_value_t *description = NULL; + + if (!jsdoc_type_imply(&js->type, TYPE_FUNCTION)) + return; + + if (!parse_tag_type_description(tag, &spec, &description)) + return; + + if (spec) { + jsdoc_typedef_merge(&js->type->details.function.return_type, spec, 0); + jsdoc_typedef_free(spec); + } + else { + jsdoc_type_imply(&js->type->details.function.return_type, TYPE_ANY); + } + + if (description) + ucv_replace(&js->type->details.function.return_description, + description); +} + +static void +parse_tag_throws(jsdoc_t *js, jsdoc_tag_t *tag, size_t index) +{ + jsdoc_throws_t throw = { 0 }, *tp; + + if (!jsdoc_type_imply(&js->type, TYPE_FUNCTION)) + return; + + if (!parse_tag_type_description(tag, &throw.type, &throw.description)) + return; + + if (js->type->details.function.throws.count <= index) { + jsdoc_type_imply(&throw.type, TYPE_ANY); + uc_vector_push(&js->type->details.function.throws, throw); + } + else { + tp = &js->type->details.function.throws.entries[index]; + + if (throw.type) { + jsdoc_typedef_merge(&tp->type, throw.type, 0); + jsdoc_typedef_free(throw.type); + } + else { + jsdoc_type_imply(&tp->type, TYPE_ANY); + } + + if (throw.description) + ucv_replace(&tp->description, throw.description); + } +} + +static bool +parse_tag_type(jsdoc_typedef_t **typep, jsdoc_tag_t *tag) +{ + jsdoc_typedef_t *tspec; + jsdoc_parser_t p; + + parse_init(&p, tag->value.buf, tag->value.bpos); + + if (parse_consume(&p, '{')) { + tspec = parse_union_type(&p); + + if (!tspec || !parse_consume(&p, '}')) { + jsdoc_typedef_free(tspec); + parse_free(&p); + + return false; + } + + jsdoc_typedef_merge(typep, tspec, 0); + jsdoc_typedef_free(tspec); + } + + parse_free(&p); + + return true; +} + +static bool +skip_until(jsdoc_parser_t *p, jsdoc_token_type_t type) +{ + struct { size_t count; jsdoc_token_type_t *entries; } parens = { 0 }; + size_t old_length = p->length; + char *old_pos = p->input; + + while (true) { + jsdoc_token_type_t tt = skip_token(&p->input, &p->length); + + switch (tt) { + case T_EOF: + uc_vector_clear(&parens); + p->length = old_length; + p->input = old_pos; + + return false; + + case T_ERROR: + p->length--; + p->input++; + + break; + + case '(': uc_vector_push(&parens, ')'); break; + case '[': uc_vector_push(&parens, ']'); break; + case '{': uc_vector_push(&parens, '}'); break; + case '<': uc_vector_push(&parens, '>'); break; + + case ')': + case ']': + case '}': + case '>': + if (parens.count > 0 && parens.entries[parens.count - 1] == tt) + parens.count--; + + break; + + default: + break; + } + + if (tt == type && parens.count == 0) + break; + } + + uc_vector_clear(&parens); + + return true; +} + +static void +parse_tag_param(jsdoc_t *js, jsdoc_tag_t *tag, size_t index) +{ + jsdoc_param_t param = { 0 }; + jsdoc_parser_t p = { 0 }; + + if (!jsdoc_type_imply(&js->type, TYPE_FUNCTION)) + goto out; + + parse_init(&p, tag->value.buf, tag->value.bpos); + + if (!parse_consume(&p, '{')) + goto out; + + if (parse_consume(&p, T_ELLIP)) { + param.restarg = true; + param.type = parse_single_type(&p); + } + else { + param.type = parse_union_type(&p); + } + + if (!param.type || !parse_consume(&p, '}')) + goto out; + + if (parse_consume(&p, '[')) { + param.optional = true; + + if (!parse_capture(&p, T_IDENT, ¶m.name)) + goto out; + + if (parse_consume(&p, '=')) { + char *def = p.input; + + if (!skip_until(&p, ']')) + goto out; + + for (p.input--, p.length++; isspace(*p.input); p.input--, p.length++); + + if (p.input > def) { + size_t len = p.input - def; + parse_value(NULL, &def, &len, ¶m.defval); + } + } + + if (!parse_consume(&p, ']')) + goto out; + } + else if (!parse_capture(&p, T_IDENT, ¶m.name)) { + goto out; + } + + if (p.length > 0 && *p.input == '-') + for (p.input++, p.length--; + p.length > 0 && isspace(*p.input); + p.input++, p.length--); + + if (p.length > 0) + param.description = ucv_string_new_length(p.input, p.length); + + if (js->type->details.function.params.count <= index) { + uc_vector_push(&js->type->details.function.params, param); + memset(¶m, 0, sizeof(param)); + } + else { + jsdoc_param_t *exist = &js->type->details.function.params.entries[index]; + + ucv_update(&exist->name, param.name); + ucv_update(&exist->defval, param.defval); + ucv_update(&exist->description, param.description); + + jsdoc_typedef_free(exist->type); + + exist->type = param.type; + exist->restarg |= param.restarg; + exist->optional |= param.optional; + + param.type = NULL; + } + +out: + ucv_put(param.name); + ucv_put(param.defval); + ucv_put(param.description); + + jsdoc_typedef_free(param.type); + parse_free(&p); +} + +static jsdoc_property_t * +upsert_property(jsdoc_typedef_t *obj, uc_value_t *name) +{ + if (!obj || obj->type != TYPE_OBJECT) { + ucv_put(name); + + return NULL; + } + + uc_vector_foreach(&obj->details.object.properties, prop) { + if (ucv_is_equal(prop->name, name)) { + ucv_put(name); + + return prop; + } + } + + return uc_vector_push(&obj->details.object.properties, { .name = name }); +} + +static jsdoc_property_t * +lookup_property_path(jsdoc_parser_t *p, jsdoc_typedef_t *obj) +{ + jsdoc_property_t *prop = NULL; + uc_value_t *ident = NULL; + + if (!parse_capture(p, T_IDENT, &ident)) + return NULL; + + if ((prop = upsert_property(obj, ident)) == NULL) + return NULL; + + obj = prop->type; + + while ((p->curr.type == '.' && parse_peek(p, 0) == T_IDENT) || + (p->curr.type == '[' && parse_peek(p, 0) == ']')) + { + if (parse_consume(p, '.') && parse_capture(p, T_IDENT, &ident)) { + if ((prop = upsert_property(obj, ident)) == NULL) + return NULL; + + obj = prop->type; + } + else if (parse_consume(p, '[') && parse_consume(p, ']')) { + if (obj->type != TYPE_ARRAY) + return NULL; + + obj = obj->details.array.item_type; + prop = NULL; + } + } + + return prop; +} + +static void +parse_tag_property(jsdoc_t *js, jsdoc_tag_t *tag) +{ + uc_value_t *defval = NULL, *description = NULL; + jsdoc_property_t *prop = NULL; + jsdoc_typedef_t *ptype = NULL; + jsdoc_parser_t p = { 0 }; + + if (!jsdoc_type_imply(&js->type, TYPE_OBJECT)) + goto out; + + parse_init(&p, tag->value.buf, tag->value.bpos); + + if (!parse_consume(&p, '{')) + goto out; + + ptype = parse_union_type(&p); + + if (!ptype || !parse_consume(&p, '}')) + goto out; + + if (parse_consume(&p, '[')) { + prop = lookup_property_path(&p, js->type); + + if (!prop) + goto out; + + prop->optional = true; + + if (parse_consume(&p, '=')) { + char *def = p.input; + + if (!skip_until(&p, ']')) + goto out; + + for (p.input--, p.length++; isspace(*p.input); p.input--, p.length++); + + if (p.input > def) { + size_t len = p.input - def; + parse_value(NULL, &def, &len, &defval); + } + } + + if (!parse_consume(&p, ']')) + goto out; + } + else { + prop = lookup_property_path(&p, js->type); + + if (!prop) + goto out; + } + + if (p.length > 0 && *p.input == '-') + for (p.input++, p.length--; + p.length > 0 && isspace(*p.input); + p.input++, p.length--); + + if (p.length > 0) + description = ucv_string_new_length(p.input, p.length); + + jsdoc_typedef_merge(&prop->type, ptype, 0); + + prop->description = ucv_get(description); + prop->defval = ucv_get(defval); + +out: + ucv_put(description); + ucv_put(defval); + + jsdoc_typedef_free(ptype); + + parse_free(&p); +} + +static void +parse_tag_element(jsdoc_t *js, jsdoc_tag_t *tag, size_t index) +{ + jsdoc_typedef_t *ptype = NULL; + jsdoc_element_t *elem = NULL; + jsdoc_parser_t p = { 0 }; + + if (!jsdoc_type_imply(&js->type, TYPE_ARRAY)) + goto out; + + parse_init(&p, tag->value.buf, tag->value.bpos); + + if (!parse_consume(&p, '{')) + goto out; + + ptype = parse_union_type(&p); + + if (!ptype || !parse_consume(&p, '}')) + goto out; + + if (index < js->type->details.array.elements.count) + elem = uscope_vector_get(&js->type->details.array.elements, index); + else + elem = uc_vector_push(&js->type->details.array.elements, { 0 }); + + /* NB: this may fail, names are not mandatory for array items */ + parse_capture(&p, T_IDENT, &elem->name); + + if (p.length > 0 && *p.input == '-') + for (p.input++, p.length--; + p.length > 0 && isspace(*p.input); + p.input++, p.length--); + + if (p.length > 0) + elem->description = ucv_string_new_length(p.input, p.length); + + jsdoc_typedef_merge(&elem->type, ptype, 0); + +out: + jsdoc_typedef_free(ptype); + + parse_free(&p); +} + +static void +parse_tag_args(jsdoc_t *js, jsdoc_tag_t *tag, size_t index) +{ + while (tag->value.bpos > 0 && isspace(tag->value.buf[tag->value.bpos - 1])) + tag->value.bpos--; + + switch (tag->type) { + case TAG_KIND: + for (size_t i = 0; i < ARRAY_SIZE(kindmap); i++) { + if (!strcmp(kindmap[i].name, tag->value.buf)) { + js->kind = kindmap[i].kind; + break; + } + } + + break; + + case TAG_CONSTANT: + js->kind = KIND_CONSTANT; + js->constant = true; + parse_tag_type_name(js, tag); + break; + + case TAG_FUNCTION: + js->kind = KIND_FUNCTION; + + if (tag->value.bpos) { + ucv_replace(&js->name, + ucv_string_new_length(tag->value.buf, tag->value.bpos)); + //move_str(&js->name, &tag->value.buf); + } + + jsdoc_type_imply(&js->type, TYPE_FUNCTION); + break; + + case TAG_MEMBER: + js->kind = KIND_MEMBER; + parse_tag_type_name(js, tag); + break; + + case TAG_NAME: + ucv_replace(&js->name, + ucv_string_new_length(tag->value.buf, tag->value.bpos)); + //move_str(&js->name, &tag->value.buf); + break; + + case TAG_TYPE: + parse_tag_type(&js->type, tag); + break; + + case TAG_TYPEDEF: + js->kind = KIND_TYPEDEF; + parse_tag_type_name(js, tag); + break; + + case TAG_CONSTRUCTS: + js->kind = KIND_FUNCTION; + jsdoc_type_imply(&js->type, TYPE_FUNCTION); + + if (tag->value.bpos > 0) { + ucv_replace(&js->name, + ucv_string_new_length(tag->value.buf, tag->value.bpos)); + //move_str(&js->name, &tag->value.buf); + } + + break; + + case TAG_ENUM: + js->kind = KIND_ENUM; + + if (jsdoc_type_imply(&js->type, TYPE_OBJECT)) + parse_tag_type(&js->type->details.object.val_type, tag); + + break; + + case TAG_PARAM: + js->kind = KIND_FUNCTION; + parse_tag_param(js, tag, index); + break; + + case TAG_RETURNS: + js->kind = KIND_FUNCTION; + parse_tag_returns(js, tag); + break; + + case TAG_THROWS: + js->kind = KIND_FUNCTION; + parse_tag_throws(js, tag, index); + break; + + case TAG_PROPERTY: + parse_tag_property(js, tag); + break; + + case TAG_ELEMENT: + parse_tag_element(js, tag, index); + break; + + case TAG_DESCRIPTION: + if (tag->value.bpos > 0) { + ucv_replace(&js->description, + ucv_string_new_length(tag->value.buf, tag->value.bpos)); + //move_str(&js->description, &tag->value.buf); + } + + break; + + case TAG_DEFAULT: + if (tag->value.bpos > 0) { + char *s = tag->value.buf; + size_t l = tag->value.bpos; + + parse_value(NULL, &s, &l, &js->defval); + } + + break; + + case TAG_EVENT: + case TAG_EXTERNAL: + case TAG_FILE: + case TAG_CLASS: + case TAG_MIXIN: + case TAG_MODULE: + case TAG_NAMESPACE: + case TAG_INTERFACE: + case TAG_MEMBEROF: + case TAG_FIRES: + case TAG_YIELDS: + case TAG_UNKNOWN: + case TAG_NONE: + /* not implemented */ + break; + } +} + +static uc_value_t * +extract_subject(uc_value_t *text) +{ + uc_value_t *rv = NULL; + + if (!text) + return NULL; + + char *s = ucv_string_get(text); + while (isspace(*s)) s++; + + char *e = strstr(s, "\n\n"); + if (!e) e = s + ucv_string_length(text); + + if (e - s > 128) { + for (char *p = e; p > s + 1; p--) { + if (strchr(" \t\n", *p) && p[-1] == '.' && p - s <= 128) { + rv = ucv_string_new_length(s, p - s); + break; + } + } + + for (char *p = e; !rv && p > s; p--) { + if (strchr(" \t\n", *p) && p - s <= 128) { + uc_stringbuf_t *sbuf = ucv_stringbuf_new(); + ucv_stringbuf_addstr(sbuf, s, p - s); + ucv_stringbuf_append(sbuf, "..."); + rv = ucv_stringbuf_finish(sbuf); + break; + } + } + } + else { + rv = ucv_string_new_length(s, e - s); + } + + for (char *p = ucv_string_get(rv); p && *p; p++) + if (isspace(*p)) + *p = ' '; + + return rv; +} + +static void +rewrap_text(uc_stringbuf_t *buf) +{ + size_t len = buf->bpos, i = 0, o = 0, nl = 0; + + if (!buf->buf) + return; + + for (char prev = '\n'; i < len; ) { + if (buf->buf[i] == '\n') { + if (++nl >= 2) { + nl = 0; + buf->buf[o++] = '\n'; + prev = buf->buf[o++] = '\n'; + + while (buf->buf[i] == '\n') + i++; + } + else { + i++; + } + } + else { + if (nl == 1 && prev != '\n') + buf->buf[o++] = ' '; + + nl = 0; + prev = buf->buf[o++] = buf->buf[i++]; + } + } + + buf->buf[o] = 0; + buf->bpos = o; +} + +jsdoc_t * +jsdoc_new(jsdoc_type_t type) +{ + jsdoc_t *js = xalloc(sizeof(jsdoc_t)); + + js->type = xalloc(sizeof(jsdoc_typedef_t)); + js->type->type = type; + + return js; +} + +jsdoc_t * +jsdoc_merge(const jsdoc_t *base, const jsdoc_t *override, unsigned int flags) +{ + jsdoc_t *res = jsdoc_new(TYPE_UNSPEC); + + if (base) { + res->kind = base->kind; + res->constant = base->constant; + + res->name = ucv_get(base->name); + res->defval = ucv_get(base->defval); + res->subject = ucv_get(base->subject); + res->description = ucv_get(base->description); + + jsdoc_typedef_merge(&res->type, base->type, flags); + } + + if (override) { + res->kind = override->kind; + res->constant |= override->constant; + + ucv_update(&res->name, override->name); + ucv_update(&res->defval, override->defval); + ucv_update(&res->subject, override->subject); + ucv_update(&res->description, override->description); + + jsdoc_typedef_merge(&res->type, override->type, flags); + } + + return res; +} + +jsdoc_t * +jsdoc_parse(const char *comment, size_t len, jsdoc_t *js) +{ + size_t property_index = 0; + size_t element_index = 0; + size_t param_index = 0; + size_t throw_index = 0; + uint32_t tags_seen = 0; + char kind = ' '; + + struct { + size_t count; + jsdoc_tag_t *entries; + } tags = { 0 }; + + jsdoc_tag_t *tag = uc_vector_push(&tags, { .type = TAG_DESCRIPTION }); + + if (len >= 2 && !strncmp(comment, "/*", 2)) { + comment += 2, len -= 2; + kind = '*'; + + if (len > 0 && *comment == '*') + comment++, len--; + + while (len > 0 && isspace(*comment)) + comment++, len--; + + if (len >= 2 && !strncmp(comment + len - 2, "*/", 2)) + len -= 2; + } + else if (len >= 2 && !strncmp(comment, "//", 2)) { + comment += 2, len -= 2; + kind = '/'; + } + + for (const char *p = comment, *ln = comment; p <= comment + len; p++) { + if (p == comment + len || *p == '\n') { + jsdoc_tag_type_t type = find_tag(&ln, p, kind); + + if (type) { + uc_vector_grow(&tags); + tag = &tags.entries[tags.count++]; + tag->type = type; + + if (p > ln) { + fprintf(stderr, "APPEND-FIRST [%.*s]\n", + (int)(p - ln), ln); + + printbuf_memappend_fast((&tag->value), ln, p - ln); + } + } + else if (tag->value.bpos > 0 || p > ln) { + fprintf(stderr, "APPEND-CONT [%.*s]\n", + (int)(p - ln), ln); + + printbuf_strappend((&tag->value), "\n"); + printbuf_memappend_fast((&tag->value), ln, p - ln); + } + + ln = p + 1; + } + } + + if (!js) + js = xalloc(sizeof(*js)); + + for (size_t i = 0; i < tags.count; i++) { + jsdoc_tag_t *tag = &tags.entries[i]; + + rewrap_text(&tag->value); + + /* @param, @throws, @property and @element may be repeated ... */ + if (tag->type == TAG_PARAM) + parse_tag_args(js, tag, param_index++); + else if (tag->type == TAG_THROWS) + parse_tag_args(js, tag, throw_index++); + else if (tag->type == TAG_PROPERTY) + parse_tag_args(js, tag, property_index++); + else if (tag->type == TAG_ELEMENT) + parse_tag_args(js, tag, element_index++); + + /* ... for all other types only consider the first */ + else if (!(tags_seen & (1u << tag->type))) + parse_tag_args(js, tag, 0); + + tags_seen |= (1u << tag->type); + + free(tag->value.buf); + } + + uc_vector_clear(&tags); + + js->subject = extract_subject(js->description); + + return js; +} + +jsdoc_typedef_t * +jsdoc_typedef_new(jsdoc_type_t type) +{ + jsdoc_typedef_t *def = xalloc(sizeof(jsdoc_typedef_t)); + + def->type = type; + + return def; +} + +static uc_value_t * +jsdoc_typedef_to_uv(uc_vm_t *vm, jsdoc_typedef_t *type) +{ + uc_value_t *t = ucv_object_new(vm); + uc_value_t *uv; + + switch (type ? type->type : TYPE_UNSPEC) { + case TYPE_FUNCTION: + uv = ucv_object_new(vm); + + uc_value_t *f_args = ucv_array_new_length(vm, type->details.function.params.count); + + uc_vector_foreach(&type->details.function.params, param) { + uc_value_t *f_arg = ucv_object_new(vm); + + if (param->name) + ucv_object_add(f_arg, "name", ucv_get(param->name)); + + if (param->description) + ucv_object_add(f_arg, "description", ucv_get(param->description)); + + if (param->defval) + ucv_object_add(f_arg, "default", ucv_get(param->defval)); + + ucv_object_add(f_arg, "type", jsdoc_typedef_to_uv(vm, param->type)); + + if (param->restarg) + ucv_object_add(f_arg, "restarg", ucv_boolean_new(true)); + + if (param->optional) + ucv_object_add(f_arg, "optional", ucv_boolean_new(true)); + + ucv_array_push(f_args, f_arg); + } + + ucv_object_add(uv, "arguments", f_args); + + uc_value_t *f_ret = ucv_object_new(vm); + + ucv_object_add(f_ret, "type", + jsdoc_typedef_to_uv(vm, type->details.function.return_type)); + + if (type->details.function.return_description) + ucv_object_add(f_ret, "description", + ucv_get(type->details.function.return_description)); + + ucv_object_add(uv, "return", f_ret); + + if (type->details.function.throws.count > 0) { + uc_value_t *f_throws = ucv_array_new_length(vm, type->details.function.throws.count); + + uc_vector_foreach(&type->details.function.throws, throw) { + uc_value_t *f_throw = ucv_object_new(vm); + + if (throw->description) + ucv_object_add(f_throw, "description", + ucv_get(throw->description)); + + ucv_object_add(f_throw, "type", + jsdoc_typedef_to_uv(vm, throw->type)); + + ucv_array_push(f_throws, f_throw); + } + + ucv_object_add(uv, "throws", f_throws); + } + + ucv_object_add(t, "type", ucv_string_new("function")); + ucv_object_add(t, "function", uv); + break; + + case TYPE_OBJECT: + uv = ucv_object_new(vm); + + ucv_object_add(uv, "key_type", + jsdoc_typedef_to_uv(vm, type->details.object.key_type)); + + ucv_object_add(uv, "value_type", + jsdoc_typedef_to_uv(vm, type->details.object.val_type)); + + if (type->details.object.properties.count > 0) { + uc_value_t *o_props = ucv_object_new(vm); + + uc_vector_foreach(&type->details.object.properties, prop) { + uc_value_t *o_prop = ucv_object_new(vm); + + ucv_object_add(o_prop, "name", ucv_get(prop->name)); + + if (prop->description) + ucv_object_add(o_prop, "description", + ucv_get(prop->description)); + + if (prop->defval) + ucv_object_add(o_prop, "default", + ucv_get(prop->defval)); + + if (prop->optional) + ucv_object_add(o_prop, "optional", + ucv_boolean_new(true)); + + ucv_object_add(o_prop, "type", + jsdoc_typedef_to_uv(vm, prop->type)); + + char *pname = ucv_string_get(prop->name); + char *p = NULL; + + if (!pname) { + xasprintf(&p, ".property.%p", prop); + pname = p; + } + + ucv_object_add(o_props, pname, o_prop); + free(p); + } + + ucv_object_add(uv, "properties", o_props); + } + + ucv_object_add(t, "type", ucv_string_new("object")); + ucv_object_add(t, "object", uv); + break; + + case TYPE_ARRAY: + uv = ucv_object_new(vm); + + ucv_object_add(uv, "item_type", + jsdoc_typedef_to_uv(vm, type->details.array.item_type)); + + if (type->details.array.elements.count > 0) { + uc_value_t *a_elems = ucv_array_new_length(vm, + type->details.array.elements.count); + + uc_vector_foreach(&type->details.array.elements, elem) { + uc_value_t *a_elem = ucv_object_new(vm); + + if (elem->name) + ucv_object_add(a_elem, "name", + ucv_get(elem->name)); + + if (elem->description) + ucv_object_add(a_elem, "description", + ucv_get(elem->description)); + + ucv_object_add(a_elem, "type", + jsdoc_typedef_to_uv(vm, elem->type)); + + ucv_array_push(a_elems, a_elem); + } + + ucv_object_add(uv, "elements", a_elems); + } + + ucv_object_add(t, "type", ucv_string_new("array")); + ucv_object_add(t, "array", uv); + break; + + case TYPE_UNION: + uv = ucv_array_new_length(vm, type->details.alternatives.count); + + uc_vector_foreach(&type->details.alternatives, subtype) + ucv_array_push(uv, jsdoc_typedef_to_uv(vm, *subtype)); + + ucv_object_add(t, "type", ucv_string_new("union")); + ucv_object_add(t, "union", uv); + break; + + case TYPE_TYPENAME: + ucv_object_add(t, "type", ucv_string_new("typename")); + ucv_object_add(t, "typename", ucv_get(type->details.typename)); + break; + + case TYPE_UNSPEC: + ucv_object_add(t, "type", ucv_string_new("unspec")); + break; + + case TYPE_INTEGER: + ucv_object_add(t, "type", ucv_string_new("integer")); + break; + + case TYPE_DOUBLE: + ucv_object_add(t, "type", ucv_string_new("double")); + break; + + case TYPE_NUMBER: + ucv_object_add(t, "type", ucv_string_new("number")); + break; + + case TYPE_BOOLEAN: + ucv_object_add(t, "type", ucv_string_new("boolean")); + break; + + case TYPE_STRING: + ucv_object_add(t, "type", ucv_string_new("string")); + break; + + case TYPE_ANY: + ucv_object_add(t, "type", ucv_string_new("any")); + break; + } + + if (type && type->nullable) + ucv_object_add(t, "nullable", ucv_boolean_new(true)); + + if (type && type->required) + ucv_object_add(t, "required", ucv_boolean_new(true)); + + if (type && type->value) + ucv_object_add(t, "value", ucv_get(type->value)); + + return t; +} + +uc_value_t * +jsdoc_to_uv(uc_vm_t *vm, const jsdoc_t *js) +{ + if (!js) + return NULL; + + uc_value_t *uv = ucv_object_new(vm); + + if (js->name) + ucv_object_add(uv, "name", ucv_get(js->name)); + + if (js->subject) + ucv_object_add(uv, "subject", ucv_get(js->subject)); + + if (js->description) + ucv_object_add(uv, "description", ucv_get(js->description)); + + if (js->defval) + ucv_object_add(uv, "default", ucv_get(js->defval)); + + for (size_t i = 0; i < ARRAY_SIZE(kindmap); i++) { + if (kindmap[i].kind == js->kind) { + ucv_object_add(uv, "kind", ucv_string_new(kindmap[i].name)); + break; + } + } + + ucv_object_add(uv, "type", jsdoc_typedef_to_uv(vm, js->type)); + + if (js->constant) + ucv_object_add(uv, "constant", ucv_boolean_new(true)); + + return uv; +} + +jsdoc_typedef_t * +jsdoc_typedef_from_uv(uc_vm_t *vm, uc_value_t *uv) +{ + uscope_variable_t *uvar = ucv_resource_data(uv, "uscope.variable"); + jsdoc_typedef_t *t = NULL; + + jsdoc_type_t vartype = (uvar && uvar->jsdoc && uvar->jsdoc->type) + ? uvar->jsdoc->type->type : TYPE_UNSPEC; + + uc_function_t *function; + uc_closure_t *closure; + + if (vartype == TYPE_ARRAY) { + jsdoc_type_imply(&t, TYPE_ARRAY); + jsdoc_type_imply(&t->details.array.item_type, TYPE_ANY); + + for (size_t i = 0; i < ucv_array_length(uvar->value); i++) { + jsdoc_element_t *elem = uc_vector_push(&t->details.array.elements, { 0 }); + uc_value_t *v = ucv_array_get(uvar->value, i); + uscope_variable_t *pvar = ucv_resource_data(v, "uscope.variable"), *var2; + + if (ucv_is_marked(v)) { + jsdoc_type_imply(&elem->type, TYPE_ANY); + continue; + } + + if (!pvar || !pvar->property || !pvar->name) + continue; + + if (ucv_type(pvar->name) == UC_STRING) + elem->name = ucv_get(pvar->name); + else if (pvar->jsdoc) + ucv_update(&elem->name, pvar->jsdoc->name); + else if ((var2 = ucv_resource_data(pvar->value, "uscope.variable")) != NULL) { + if (var2->jsdoc) + ucv_update(&elem->name, var2->jsdoc->name); + } + + ucv_set_mark(v); + + elem->type = jsdoc_typedef_from_uv(vm, v); + + ucv_clear_mark(v); + + if (pvar->jsdoc) { + ucv_update(&elem->description, pvar->jsdoc->subject); + jsdoc_typedef_merge(&elem->type, pvar->jsdoc->type, 0); + } + } + + return t; + } + + if (vartype == TYPE_OBJECT) { + jsdoc_type_imply(&t, TYPE_OBJECT); + jsdoc_type_imply(&t->details.object.key_type, TYPE_ANY); + jsdoc_type_imply(&t->details.object.val_type, TYPE_ANY); + + for (size_t i = 0; i < ucv_array_length(uvar->value); i++) { + uc_value_t *v = ucv_array_get(uvar->value, i); + + if (ucv_is_marked(v)) + continue; + + uscope_variable_t *pvar = ucv_resource_data(v, "uscope.variable"); + + if (!pvar || !pvar->name || !pvar->property) + continue; + + ucv_set_mark(v); + + jsdoc_property_t *prop = uc_vector_push(&t->details.object.properties, { + .name = ucv_get(pvar->name), + .type = jsdoc_typedef_from_uv(vm, v) + }); + + ucv_clear_mark(v); + + if (pvar->jsdoc) { + ucv_update(&prop->description, pvar->jsdoc->subject); + jsdoc_typedef_merge(&prop->type, pvar->jsdoc->type, 0); + } + } + + return t; + } + + uv = uscope_resolve_variable(vm, uv, true); + + switch (ucv_type(uv)) { + case UC_ARRAY: + jsdoc_type_imply(&t, TYPE_ARRAY); + jsdoc_type_imply(&t->details.array.item_type, TYPE_ANY); + + for (size_t i = 0; i < ucv_array_length(uv); i++) { + jsdoc_element_t *elem = uc_vector_push(&t->details.array.elements, { 0 }); + uc_value_t *v = ucv_array_get(uv, i); + uscope_variable_t *var = ucv_resource_data(v, "uscope.variable"), *var2; + + if (ucv_is_marked(v)) { + jsdoc_type_imply(&elem->type, TYPE_ANY); + continue; + } + + if (var && ucv_type(var->name) == UC_STRING) + elem->name = ucv_get(var->name); + else if (var && var->jsdoc) + ucv_update(&elem->name, var->jsdoc->name); + else if (var && (var2 = ucv_resource_data(var->value, "uscope.variable")) != NULL) { + if (var2->jsdoc) + ucv_update(&elem->name, var2->jsdoc->name); + } + + ucv_set_mark(v); + + elem->type = jsdoc_typedef_from_uv(vm, v); + + ucv_clear_mark(v); + + if (var && var->jsdoc) { + ucv_update(&elem->description, var->jsdoc->subject); + jsdoc_typedef_merge(&elem->type, var->jsdoc->type, 0); + } + } + break; + + case UC_OBJECT: + jsdoc_type_imply(&t, TYPE_OBJECT); + jsdoc_type_imply(&t->details.object.key_type, TYPE_ANY); + jsdoc_type_imply(&t->details.object.val_type, TYPE_ANY); + + ucv_object_foreach(uv, k, v) { + if (ucv_is_marked(v)) + continue; + + ucv_set_mark(v); + + uscope_variable_t *var = ucv_resource_data(v, "uscope.variable"); + jsdoc_property_t *prop = uc_vector_push(&t->details.object.properties, { + .name = ucv_string_new(k), + .type = jsdoc_typedef_from_uv(vm, v) + }); + + ucv_clear_mark(v); + + if (var && var->jsdoc) { + ucv_update(&prop->description, var->jsdoc->subject); + jsdoc_typedef_merge(&prop->type, var->jsdoc->type, 0); + } + } + break; + + case UC_CLOSURE: + closure = (uc_closure_t *)uv; + function = closure->function; + + jsdoc_type_imply(&t, TYPE_FUNCTION); + + for (size_t i = 0; i < function->nargs; i++) { + jsdoc_param_t *param = uc_vector_push(&t->details.function.params, { + .restarg = (function->vararg && i + 1 == function->nargs) + }); + + jsdoc_type_imply(¶m->type, TYPE_ANY); + } + + jsdoc_type_imply(&t->details.function.return_type, TYPE_ANY); + break; + + case UC_CFUNCTION: + jsdoc_type_imply(&t, TYPE_FUNCTION); + jsdoc_type_imply(&t->details.function.return_type, TYPE_ANY); + break; + + case UC_BOOLEAN: jsdoc_type_imply(&t, TYPE_BOOLEAN); break; + case UC_DOUBLE: jsdoc_type_imply(&t, TYPE_DOUBLE); break; + case UC_INTEGER: jsdoc_type_imply(&t, TYPE_INTEGER); break; + case UC_STRING: jsdoc_type_imply(&t, TYPE_STRING); break; + default: jsdoc_type_imply(&t, TYPE_ANY); break; + } + + if (ucv_is_scalar(uv)) + t->value = uv; + else + ucv_put(uv); + + return t; +} + +jsdoc_t * +jsdoc_from_uv(uc_vm_t *vm, uc_value_t *uv, jsdoc_t *js) +{ + jsdoc_typedef_t *st = jsdoc_typedef_from_uv(vm, uv); + uc_cfunction_t *cfunction; + uc_closure_t *closure; + + if (!js) + js = xalloc(sizeof(*js)); + + if (!js->type) { + js->type = st; + } + else { + jsdoc_typedef_merge(&js->type, st, 0); + jsdoc_typedef_free(st); + } + + js->constant |= ucv_is_constant(uv); + + switch (ucv_type(uv)) { + case UC_CLOSURE: + closure = (uc_closure_t *)uv; + + if (closure->function->name[0]) { + ucv_replace(&js->name, ucv_string_new(closure->function->name)); + //update_str(&js->name, closure->function->name); + } + + if (js->kind == KIND_UNSPEC) + js->kind = KIND_FUNCTION; + + break; + + case UC_CFUNCTION: + cfunction = (uc_cfunction_t *)uv; + + if (cfunction->name[0]) { + ucv_replace(&js->name, ucv_string_new(cfunction->name)); + //update_str(&js->name, cfunction->name); + } + + if (js->kind == KIND_UNSPEC) + js->kind = KIND_FUNCTION; + + break; + + default: + if (js->kind == KIND_UNSPEC) + js->kind = KIND_MEMBER; + + break; + } + + return js; +} + +jsdoc_t * +jsdoc_from_param(jsdoc_kind_t kind, const jsdoc_param_t *param) +{ + jsdoc_t *js = xalloc(sizeof(jsdoc_t)); + + js->kind = kind; + js->name = ucv_get(param->name); + js->subject = extract_subject(param->description); + js->description = ucv_get(param->description); + js->defval = ucv_get(param->defval); + + if (param->restarg) { + jsdoc_type_imply(&js->type, TYPE_ARRAY); + jsdoc_typedef_merge(&js->type->details.array.item_type, + param->type, MERGE_TYPEONLY); + } + else { + jsdoc_typedef_merge(&js->type, param->type, MERGE_TYPEONLY); + } + + return js; +} + +jsdoc_t * +jsdoc_from_property(jsdoc_kind_t kind, const jsdoc_property_t *prop) +{ + jsdoc_t *js = xalloc(sizeof(jsdoc_t)); + + js->kind = kind; + js->name = ucv_get(prop->name); + js->subject = extract_subject(prop->description); + js->description = ucv_get(prop->description); + js->defval = ucv_get(prop->defval); + + jsdoc_typedef_merge(&js->type, prop->type, MERGE_TYPEONLY); + + return js; +} + +jsdoc_t * +jsdoc_from_element(jsdoc_kind_t kind, const jsdoc_element_t *elem) +{ + jsdoc_t *js = xalloc(sizeof(jsdoc_t)); + + js->kind = kind; + js->name = ucv_get(elem->name); + js->subject = extract_subject(elem->description); + js->description = ucv_get(elem->description); + + jsdoc_typedef_merge(&js->type, elem->type, MERGE_TYPEONLY); + + return js; +} + +jsdoc_t * +jsdoc_from_return(jsdoc_kind_t kind, const jsdoc_typedef_t *fnspec) +{ + jsdoc_t *js = xalloc(sizeof(jsdoc_t)); + + js->kind = kind; + + if (fnspec->type == TYPE_FUNCTION) { + js->subject = extract_subject(fnspec->details.function.return_description); + js->description = ucv_get(fnspec->details.function.return_description); + + jsdoc_typedef_merge(&js->type, + fnspec->details.function.return_type, MERGE_TYPEONLY); + } + else { + jsdoc_type_imply(&js->type, TYPE_ANY); + } + + return js; +} diff --git a/lib/uscope/jsdoc.h b/lib/uscope/jsdoc.h new file mode 100644 index 00000000..1f505c63 --- /dev/null +++ b/lib/uscope/jsdoc.h @@ -0,0 +1,146 @@ +#pragma once + +#include + +#include "ucode/types.h" + + +typedef enum { + TYPE_UNSPEC, + TYPE_ARRAY, + TYPE_OBJECT, + TYPE_FUNCTION, + TYPE_INTEGER, + TYPE_DOUBLE, + TYPE_NUMBER, + TYPE_BOOLEAN, + TYPE_STRING, + TYPE_ANY, + TYPE_UNION, + TYPE_TYPENAME, +} jsdoc_type_t; + +typedef enum { + KIND_UNSPEC, + KIND_CLASS, + KIND_CONSTANT, + KIND_EVENT, + KIND_EXTERNAL, + KIND_FILE, + KIND_FUNCTION, + KIND_MEMBER, + KIND_MIXIN, + KIND_MODULE, + KIND_NAMESPACE, + KIND_ENUM, + KIND_TYPEDEF, +} jsdoc_kind_t; + +typedef enum { + MERGE_TYPEONLY = (1u << 0), + MERGE_UNION = (1u << 1), + MERGE_NOELEMS = (1u << 2), +} jsdoc_merge_flag_t; + +struct jsdoc_typedef; + +typedef struct { + uc_value_t *description; + struct jsdoc_typedef *type; +} jsdoc_throws_t; + +typedef struct { + uc_value_t *name; + uc_value_t *description; + uc_value_t *defval; + struct jsdoc_typedef *type; + bool optional; +} jsdoc_property_t; + +typedef struct { + uc_value_t *name; + uc_value_t *description; + struct jsdoc_typedef *type; +} jsdoc_element_t; + +typedef struct { + uc_value_t *name; + uc_value_t *description; + uc_value_t *defval; + struct jsdoc_typedef *type; + bool optional; + bool restarg; +} jsdoc_param_t; + +typedef struct jsdoc_typedef { + jsdoc_type_t type; + uc_value_t *value; + bool nullable; + bool required; + union { + struct { + struct jsdoc_typedef *key_type; + struct jsdoc_typedef *val_type; + struct { + size_t count; + jsdoc_property_t *entries; + } properties; + } object; + struct { + struct jsdoc_typedef *item_type; + struct { + size_t count; + jsdoc_element_t *entries; + } elements; + } array; + struct { + size_t count; + struct jsdoc_typedef **entries; + } alternatives; + struct { + struct { + size_t count; + jsdoc_param_t *entries; + } params; + struct { + size_t count; + jsdoc_throws_t *entries; + } throws; + struct jsdoc_typedef *return_type; + uc_value_t *return_description; + } function; + uc_value_t *typename; + } details; +} jsdoc_typedef_t; + +typedef struct { + jsdoc_kind_t kind; + jsdoc_typedef_t *type; + uc_value_t *name; + uc_value_t *subject; + uc_value_t *description; + uc_value_t *defval; + bool constant; +} jsdoc_t; + + +jsdoc_t *jsdoc_new(jsdoc_type_t); +jsdoc_t *jsdoc_merge(const jsdoc_t *, const jsdoc_t *, unsigned int); +jsdoc_t *jsdoc_parse(const char *, size_t, jsdoc_t *); + +jsdoc_t *jsdoc_from_property(jsdoc_kind_t, const jsdoc_property_t *); +jsdoc_t *jsdoc_from_element(jsdoc_kind_t, const jsdoc_element_t *); +jsdoc_t *jsdoc_from_param(jsdoc_kind_t, const jsdoc_param_t *); +jsdoc_t *jsdoc_from_return(jsdoc_kind_t, const jsdoc_typedef_t *); + +jsdoc_t *jsdoc_from_uv(uc_vm_t *, uc_value_t *, jsdoc_t *); +uc_value_t *jsdoc_to_uv(uc_vm_t *, const jsdoc_t *); + +void jsdoc_reset(jsdoc_t *); +void jsdoc_free(jsdoc_t *); + +jsdoc_typedef_t *jsdoc_typedef_new(jsdoc_type_t); +bool jsdoc_typedef_merge(jsdoc_typedef_t **, const jsdoc_typedef_t *, unsigned int); +void jsdoc_typedef_free(jsdoc_typedef_t *); + +jsdoc_typedef_t *jsdoc_typedef_from_uv(uc_vm_t *, uc_value_t *); diff --git a/lib/uscope/parse.c b/lib/uscope/parse.c new file mode 100644 index 00000000..e3d98954 --- /dev/null +++ b/lib/uscope/parse.c @@ -0,0 +1,4132 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "ucode/compiler.h" +#include "ucode/lexer.h" +#include "ucode/util.h" +#include "ucode/lib.h" /* uc_error_context_format() */ +#include "ucode/module.h" /* uc_error_context_format() */ +#include "ucode/arith.h" + +#include "jsdoc.h" +#include "uscope.h" + + +#define VAR_NAME_AUTO_RETURN ((uc_value_t *)1) + +typedef enum { + TT_NAMESPACE = 0, + TT_CLASS = 1, + TT_ENUM = 2, + TT_INTERFACE = 3, + TT_STRUCT = 4, + TT_TYPEPAR = 5, + TT_TYPE = 6, + TT_PARAMETER = 7, + TT_VARIABLE = 8, + TT_PROPERTY = 9, + TT_ENUMMEMBER = 10, + TT_EVENT = 11, + TT_FUNCTION = 12, + TT_METHOD = 13, + TT_MACRO = 14, + TT_KEYWORD = 15, + TT_MODIFIER = 16, + TT_COMMENT = 17, + TT_STRING = 18, + TT_NUMBER = 19, + TT_REGEXP = 20, + TT_OPERATOR = 21, + TT_TPLTAG = 22, + TT_TPLTEXT = 23, + TT_PUNCT = 24, + TT_KW_CONTROL = 25, + TT_BOOLEAN = 26, + TT_NULL = 27, + TT_THIS = 28, + TT_KW_OPERATOR = 29, +} semantic_token_type_t; + +static const char *semantic_token_type_names[] = { + "namespace", "class", "enum", "interface", "struct", + "typeParameter", "type", "parameter", "variable", "property", + "enumMember", "event", "function", "method", "macro", + "keyword", "modifier", "comment", "string", "number", + "regexp", "operator", "ucode-template-tag", "ucode-template-text", "ucode-punctuation", + "keyword.control", "ucode-boolean", "ucode-null", "ucode-this", "ucode-operator" +}; + +typedef enum { + TM_DECLARATION = 0, + TM_DEFINITION = 1, + TM_READONLY = 2, + TM_STATIC = 3, + TM_DEPRECATED = 4, + TM_ABSTRACT = 5, + TM_ASYNC = 6, + TM_MOD = 7, + TM_DOC = 8, + TM_DEFLIB = 9 +} semantic_token_modifier_t; + +static const char *semantic_token_modifier_names[] = { + "declaration", "definition", "readonly", "static", "deprecated", + "abstract", "async", "modification", "documentation", "defaultLibrary" +}; + +static const char *access_kind_names[] = { + "declaration", + "read", + "write", + "update", + "export", +}; + +typedef struct { + uc_token_t token; + semantic_token_type_t sem_type; + uint32_t sem_modifiers; +} semantic_token_t; + +typedef struct { + uscope_position_t location; + size_t token_id; + jsdoc_t *jsdoc; +} undef_t; + +typedef struct { + uscope_position_t start; + uscope_position_t end; + struct { + size_t count; + uc_value_t **entries; + } variables; +} scope_t; + +typedef struct { + uc_value_t *name; + uc_value_t *alias; + uc_value_t *value; + uc_value_t *source; + bool is_default; + bool is_wildcard; +} xport_item_t; + +typedef struct { + uc_vm_t *vm; + uc_parser_t parser; + uc_value_t *global; + size_t curr_token_offset; + size_t prev_token_offset; + struct { + size_t count; + struct { + uc_token_t token; + size_t offset; + } *entries; + } lookahead; + struct { + size_t count; + semantic_token_t *entries; + } tokens; + struct { + size_t count; + struct { + uscope_position_t start; + uscope_position_t end; + uc_value_t *message; + } *entries; + } errors; + struct { + size_t count; + scope_t *entries; + } scopes; + struct { + size_t count; + size_t *entries; + } scopechain; + struct { + size_t count; + uc_value_t **entries; + } thischain; + struct { + size_t count; + xport_item_t *entries; + } imports; + struct { + size_t count; + xport_item_t *entries; + } exports; + struct { + size_t count; + jsdoc_t **entries; + } function_jsdocs; + struct { + size_t count; + jsdoc_t **entries; + } declaration_jsdocs; +} uscope_parser_t; + +typedef struct { + uc_value_t *(*prefix)(uscope_parser_t *); + uc_value_t *(*infix)(uscope_parser_t *, uc_value_t *); + uc_precedence_t precedence; +} parse_rule_t; + +typedef struct { + size_t count; + struct { + bool spread; + uc_value_t *value; + } *entries; +} callargs_t; + +static const semantic_token_type_t type_semtype_map[] = { + 0, + TT_TPLTAG, TT_TPLTAG, TT_TPLTAG, TT_TPLTAG, + TT_KW_CONTROL, TT_KW_CONTROL, TT_PUNCT, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_PUNCT, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_KW_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_PUNCT, TT_PUNCT, + TT_PUNCT, TT_PUNCT, TT_PUNCT, TT_TPLTEXT, + TT_PUNCT, TT_PUNCT, TT_PUNCT, TT_KW_CONTROL, + TT_KW_CONTROL, TT_KEYWORD, TT_KEYWORD, TT_KEYWORD, + TT_KEYWORD, TT_KEYWORD, TT_VARIABLE, TT_KEYWORD, + TT_KEYWORD, TT_KEYWORD, TT_KEYWORD, TT_KEYWORD, + TT_KEYWORD, TT_PUNCT, TT_KEYWORD, TT_KEYWORD, + TT_KEYWORD, TT_KEYWORD, TT_OPERATOR, TT_BOOLEAN, + TT_BOOLEAN, TT_NUMBER, TT_NUMBER, TT_STRING, + TT_REGEXP, TT_NULL, TT_THIS, TT_KEYWORD, + TT_KEYWORD, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, TT_OPERATOR, + TT_OPERATOR, TT_PUNCT, TT_STRING, TT_KEYWORD, + TT_KEYWORD, TT_PUNCT, TT_COMMENT, TT_PUNCT +}; + +static void +scope_enter(uscope_parser_t *usp) +{ + scope_t *scope; + + uc_vector_grow(&usp->scopes); + + scope = &usp->scopes.entries[usp->scopes.count]; + scope->start.offset = usp->parser.prev.pos; + scope->start.column = scope->start.offset; + scope->start.line = uc_source_get_line(usp->parser.lex.source, &scope->start.column); + + uc_vector_push(&usp->scopechain, usp->scopes.count++); +} + +static void +scope_leave(uscope_parser_t *usp) +{ + size_t curr_scope_id = usp->scopechain.entries[--usp->scopechain.count]; + scope_t *scope = &usp->scopes.entries[curr_scope_id]; + + scope->end.offset = usp->parser.prev.pos; + scope->end.column = scope->end.offset; + scope->end.line = uc_source_get_line(usp->parser.lex.source, &scope->end.column); + + uc_vector_foreach(&scope->variables, var) { + uscope_variable_t *spec = ucv_resource_data(*var, "uscope.variable"); + + if (!spec->superseded) + spec->range.end = scope->end; + } +} + +static uc_value_t * +comment_capture(uscope_parser_t *usp, size_t offset) +{ + struct { size_t count; uc_value_t **entries; } comments = { 0 }; + uscope_position_t pos = { 0 }, end = { 0 }, prev = { 0 }; + uc_value_t *rv = NULL; + + for (size_t i = offset; i > 0; i--) { + uc_token_t *tok = &usp->tokens.entries[i-1].token; + + if (tok->type != TK_COMMENT) + break; + + pos.column = tok->pos; + pos.line = uc_source_get_line(usp->parser.lex.source, &pos.column); + + end.column = tok->end; + end.line = uc_source_get_line(usp->parser.lex.source, &end.column); + + char *s = ucv_string_get(tok->uv); + + if (!s) + break; + + if (comments.count > 0 && (end.line != prev.line || + pos.column != prev.column || + strncmp(s, "//", 2) != 0)) + break; + + prev = pos; + uc_vector_push(&comments, tok->uv); + + usp->tokens.entries[i-1].sem_modifiers |= (1u << TM_DOC); + } + + if (comments.count > 1) { + uc_stringbuf_t *sbuf = ucv_stringbuf_new(); + + for (size_t i = comments.count; i > 0; i--) { + uc_value_t *s = comments.entries[i - 1]; + ucv_stringbuf_addstr(sbuf, ucv_string_get(s), ucv_string_length(s)); + } + + rv = ucv_stringbuf_finish(sbuf); + } + else if (comments.count > 0) { + rv = ucv_get(comments.entries[0]); + } + + uc_vector_clear(&comments); + + return rv; +} + + +static bool +is_undef(uc_value_t *uv) +{ + return (ucv_resource_dataptr(uv, "uscope.undefined") != NULL); +} + +static uc_value_t * +undef_new(uscope_parser_t *usp, ssize_t token_num, jsdoc_t *jsdoc) +{ + undef_t *undef = xalloc(sizeof(undef_t)); + semantic_token_t *stok = &usp->tokens.entries[token_num]; + + undef->token_id = token_num; + undef->location.offset = stok->token.pos; + undef->location.column = undef->location.offset; + undef->location.line = + uc_source_get_line(usp->parser.lex.source, &undef->location.column); + + undef->jsdoc = jsdoc; + + return uc_resource_new( + ucv_resource_type_lookup(usp->vm, "uscope.undefined"), + undef); +} + +static uscope_variable_t * +variable_check(uc_value_t *uv) +{ + return ucv_resource_data(uv, "uscope.variable"); +} + +static jsdoc_t * +variable_get_jsdoc(uc_value_t *uv) +{ + uscope_variable_t *var = ucv_resource_data(uv, "uscope.variable"); + + if (var) + return var->jsdoc; + + undef_t *undef = ucv_resource_data(uv, "uscope.undefined"); + + if (undef) + return undef->jsdoc; + + return NULL; +} + +static jsdoc_t * +variable_upsert_jsdoc(uc_value_t *uv, jsdoc_kind_t kind) +{ + uscope_variable_t *var = ucv_resource_data(uv, "uscope.variable"); + + if (!var) + return NULL; + + if (!var->jsdoc) { + var->jsdoc = xalloc(sizeof(jsdoc_t)); + var->jsdoc->kind = kind; + } + + return var->jsdoc; +} + +static uscope_reference_t * +variable_get_reference(uc_value_t *uv, ssize_t index) +{ + uscope_variable_t *var = ucv_resource_data(uv, "uscope.variable"); + + if (!var || var->references.count == 0) + return NULL; + + if (index < 0) + index += var->references.count; + + if (index < 0) + return NULL; + + return uscope_vector_get(&var->references, (size_t)index); +} + +static jsdoc_t * +jsdoc_derive(uc_value_t *comment, uc_value_t *value, bool constant) +{ + const char *comment_str = comment ? ucv_string_get(comment) : ""; + size_t comment_len = comment ? ucv_string_length(comment) : 0; + jsdoc_t *dst_jsdoc = jsdoc_parse(comment_str, comment_len, NULL); + jsdoc_t *src_jsdoc = variable_get_jsdoc(value); + + /* NB: if an expression is preceeded by a @typedef jsdoc, then do not + consider that comment to be documentation for our expression */ + if (dst_jsdoc->kind == KIND_TYPEDEF) + jsdoc_reset(dst_jsdoc); + + if (src_jsdoc) { + jsdoc_typedef_merge(&dst_jsdoc->type, src_jsdoc->type, 0); + } + else if (value && !is_undef(value)) { + jsdoc_typedef_t *def = jsdoc_typedef_from_uv(NULL, value); + + if (def) { + jsdoc_typedef_merge(&dst_jsdoc->type, def, 0); + jsdoc_typedef_free(def); + } + } + + dst_jsdoc->constant = constant; + + return dst_jsdoc; +} + +static jsdoc_t * +jsdoc_capture(uscope_parser_t *usp, size_t token_id, uc_value_t *uv) +{ + uc_value_t *doc = comment_capture(usp, token_id); + jsdoc_t *jsdoc = jsdoc_derive(doc, uv, false); + + ucv_put(doc); + + return jsdoc; +} + +__attribute__((format(printf, 1, 0))) +static uc_value_t * +ucv_asprintf(const char *fmt, ...) +{ + uc_value_t *res = NULL; + char *s = NULL; + int len = 0; + va_list ap; + + va_start(ap, fmt); + len = xvasprintf(&s, fmt, ap); + va_end(ap); + + if (len > 0) + res = ucv_string_new_length(s, len); + + free(s); + + return res; +} + +static jsdoc_type_t +jsdoc_get_type(const jsdoc_t *jsdoc) +{ + if (!jsdoc || !jsdoc->type) + return TYPE_UNSPEC; + + return jsdoc->type->type; +} + +static void +jsdoc_merge_function_details(uscope_variable_t *var) +{ + if (!var || jsdoc_get_type(var->jsdoc) != TYPE_FUNCTION) + return; + + uscope_variable_t *val = variable_check(var->value); + + if (!val || jsdoc_get_type(val->jsdoc) != TYPE_FUNCTION) + return; + + jsdoc_t *new_var_jsdoc = jsdoc_merge(val->jsdoc, var->jsdoc, MERGE_UNION); + jsdoc_t *new_val_jsdoc = jsdoc_merge(var->jsdoc, val->jsdoc, MERGE_UNION); + + jsdoc_free(var->jsdoc); + jsdoc_free(val->jsdoc); + + val->jsdoc = new_val_jsdoc; + var->jsdoc = new_var_jsdoc; +} + +static uc_value_t * +variable_new(uscope_parser_t *usp, size_t token_id, uc_value_t *name, + uc_value_t **initval, jsdoc_t *jsdoc, + bool constant, bool global) +{ + size_t scope_idx = global ? 0 : usp->scopechain.count - 1; + size_t scope_id = usp->scopechain.entries[scope_idx]; + scope_t *scope = &usp->scopes.entries[scope_id]; + + uscope_variable_t *var = xalloc(sizeof(uscope_variable_t)); + + uc_value_t *uv = ucv_resource_new( + ucv_resource_type_lookup(usp->vm, "uscope.variable"), + var); + + semantic_token_t *stok = &usp->tokens.entries[token_id]; + + if (name == VAR_NAME_AUTO_RETURN) + var->name = ucv_asprintf(".return.%p", var); + else if (name == NULL) + var->name = ucv_get(stok->token.uv); + else + var->name = name; + + var->value = initval ? ucv_get(*initval) : NULL; + var->global = global; + var->constant = constant; + var->initialized = (initval != NULL); + var->jsdoc = jsdoc; + + jsdoc_merge_function_details(var); + + if (constant) + variable_upsert_jsdoc(uv, KIND_MEMBER)->constant = true; + + uc_vector_grow(&var->references); + + uscope_reference_t *ref = &var->references.entries[var->references.count++]; + + ref->access_kind = ACCESS_DECLARATION; + ref->token_id = token_id; + ref->location.offset = stok->token.pos; + ref->location.column = ref->location.offset; + ref->location.line = uc_source_get_line(usp->parser.lex.source, &ref->location.column); + + var->range.start = ref->location; + + stok->sem_modifiers |= (1u << TM_DECLARATION); + + if (var->initialized) + stok->sem_modifiers |= (1u << TM_DEFINITION); + + if (!global) { + uc_vector_foreach(&scope->variables, uv_old) { + uscope_variable_t *var_old = + ucv_resource_data(*uv_old, "uscope.variable"); + + if (var_old->property || var_old->superseded) + continue; + + if (!ucv_is_equal(var_old->name, var->name)) + continue; + + fprintf(stderr, "Superseding variable '%s' due to redeclaration\n", + ucv_string_get(var_old->name)); + + var_old->range.end = var->range.start; + var_old->superseded = true; + } + } + + uc_vector_push(&scope->variables, ucv_get(uv)); + + return uv; +} + +static uc_value_t * +property_new(uscope_parser_t *usp, size_t token_id, uc_value_t *name, + uc_value_t **initval, jsdoc_t *jsdoc, uc_value_t *base) +{ + size_t scope_id = usp->scopechain.entries[usp->scopechain.count - 1]; + scope_t *scope = &usp->scopes.entries[scope_id]; + + uc_vector_grow(&scope->variables); + + uscope_variable_t *var = xalloc(sizeof(uscope_variable_t)); + + uc_value_t *uv = ucv_resource_new( + ucv_resource_type_lookup(usp->vm, "uscope.variable"), + var); + + semantic_token_t *stok = &usp->tokens.entries[token_id]; + + var->base = ucv_get(base); + var->name = name ? name : ucv_get(stok->token.uv); + var->value = initval ? ucv_get(*initval) : NULL; + var->property = true; + var->initialized = (initval != NULL); + var->jsdoc = jsdoc; + + var->range.start.offset = stok->token.pos; + var->range.start.column = var->range.start.offset; + var->range.start.line = uc_source_get_line(usp->parser.lex.source, &var->range.start.column); + + uscope_reference_t *ref = uc_vector_push(&var->references, { + .access_kind = ACCESS_DECLARATION + }); + + if (usp->parser.prev.type == TK_LABEL && + (stok->token.type == TK_DOT || stok->token.type == TK_QDOT)) + { + ref->token_id = usp->prev_token_offset; + ref->location.offset = usp->parser.prev.pos; + ref->location.column = ref->location.offset; + ref->location.line = uc_source_get_line(usp->parser.lex.source, &ref->location.line); + } + else { + ref->token_id = token_id; + ref->location = var->range.start; + } + + scope->variables.entries[scope->variables.count++] = ucv_get(uv); + + return uv; +} + +static void +reference_add(uscope_parser_t *usp, uc_value_t *uv, size_t token_id, uscope_access_kind_t access) +{ + uscope_variable_t *var = ucv_resource_data(uv, "uscope.variable"); + + uc_vector_grow(&var->references); + + semantic_token_t *stok = &usp->tokens.entries[token_id]; + uscope_reference_t *ref = &var->references.entries[var->references.count++]; + + ref->access_kind = access; + ref->token_id = token_id; + ref->location.offset = stok->token.pos; + ref->location.column = ref->location.offset; + ref->location.line = + uc_source_get_line(usp->parser.lex.source, &ref->location.column); +} + +static uc_value_t * +variable_lookup(uscope_parser_t *usp, uc_value_t *name) +{ + uc_value_t *uv = NULL; + + for (size_t i = usp->scopechain.count; i > 0 && uv == NULL; i--) { + size_t scope_id = usp->scopechain.entries[i - 1]; + scope_t *scope = &usp->scopes.entries[scope_id]; + + for (size_t j = 0; j < scope->variables.count; j++) { + uscope_variable_t *var = ucv_resource_data(scope->variables.entries[j], "uscope.variable"); + + if (var->property || var->superseded) + continue; + + if (!ucv_is_equal(name, var->name)) + continue; + + uv = scope->variables.entries[j]; + } + } + + return uv; +} + +static void +variable_supersede(uscope_parser_t *usp, uc_value_t *name, size_t token_id) +{ + size_t scope_id = *uc_vector_last(&usp->scopechain); + scope_t *scope = &usp->scopes.entries[scope_id]; + semantic_token_t *stok = uscope_vector_get(&usp->tokens, token_id); + + assert(stok); + + uc_vector_foreach(&scope->variables, uv) { + uscope_variable_t *var = ucv_resource_data(*uv, "uscope.variable"); + + if (var->property || var->superseded) + continue; + + if (!ucv_is_equal(name, var->name)) + continue; + + var->superseded = true; + var->range.end.offset = stok->token.pos; + var->range.end.column = var->range.end.offset; + var->range.end.line = uc_source_get_line(usp->parser.lex.source, &var->range.end.column); + } +} + +static uc_value_t * +variable_access(uscope_parser_t *usp, jsdoc_t *jsdoc, uscope_access_kind_t access) +{ + size_t token_id = usp->prev_token_offset; + uc_value_t *name = usp->tokens.entries[token_id].token.uv; + uc_value_t *uv = variable_lookup(usp, name); + + if (uv) { + /* if this variable access is annotated with a jsdoc and if the jsdoc + type differs from the one already set on the variable, then treat + the referenced variable as new one from now on, otherwise simply + update the jsdoc type which also retroactively applies to earlier + variable accesses */ + + + // FIXME: jsdoc change -> update variable semantics + jsdoc_free(jsdoc); + + reference_add(usp, uv, token_id, access); + + return ucv_get(uv); + } + + fprintf(stderr, "IMPLICIT-VAR '%s'\n", ucv_string_get(name)); + + // FIXME: strict mode here + uc_value_t *new_var = variable_new(usp, token_id, + ucv_get(name), NULL, jsdoc, false, true); + + variable_get_reference(new_var, 0)->access_kind = access; + + return new_var; +} + +static bool +member_lookup(const jsdoc_t *jsdoc, uc_value_t *key, + uc_value_t **descp, jsdoc_typedef_t **typep) +{ + if (descp) *descp = NULL; + if (typep) *typep = NULL; + + if (!jsdoc || !jsdoc->type) + return false; + + if (jsdoc->type->type == TYPE_OBJECT) { + uc_vector_foreach(&jsdoc->type->details.object.properties, prop) { + if (prop->name && ucv_is_equal(prop->name, key)) { + if (descp) *descp = prop->description; + if (typep) *typep = prop->type; + + return true; + } + } + + if (jsdoc->type->details.object.val_type) { + if (descp) *descp = NULL; + if (typep) *typep = jsdoc->type->details.object.val_type; + + return true; + } + } + else if (jsdoc->type->type == TYPE_ARRAY) { + int64_t index = ucv_to_integer(key); + + if (index < 0) + index += jsdoc->type->details.array.elements.count; + + jsdoc_element_t *elem = (index >= 0) ? uscope_vector_get( + &jsdoc->type->details.array.elements, + (size_t)index) : NULL; + + if (elem) { + if (descp) *descp = elem->description; + if (typep) *typep = elem->type; + + return true; + } + else if (jsdoc->type->details.array.item_type) { + if (descp) *descp = NULL; + if (typep) *typep = jsdoc->type->details.array.item_type; + + return true; + } + } + + return false; +} + +uc_value_t * +uscope_resolve_variable(uc_vm_t *vm, uc_value_t *uv, bool recursive) +{ + struct { size_t count; uc_value_t **entries; } seen = { 0 }; + uscope_variable_t *var; + + while ((var = ucv_resource_data(uv, "uscope.variable")) != NULL) { + jsdoc_typedef_t *def; + + uv = NULL; + + if ((def = var->jsdoc ? var->jsdoc->type : NULL) != NULL) { + if (def->type == TYPE_OBJECT || def->type == TYPE_ARRAY) + uv = var->value; + else + uv = def->value; + } + + if (!recursive) + break; + + for (size_t i = 0; i < seen.count; i++) { + if (seen.entries[i] == uv) { + uv = NULL; + break; + } + } + + uc_vector_push(&seen, uv); + } + + uc_vector_clear(&seen); + + return ucv_get(uv); +} + +static uc_value_t * +ucv_replace(uc_value_t **dest, uc_value_t *val) +{ + ucv_put(*dest); + + *dest = val; + + return *dest; +} + +static bool +member_upsert(const jsdoc_t *jsdoc, uc_value_t *key, + uc_value_t ***descp, jsdoc_typedef_t ***typep) +{ + if (descp) *descp = NULL; + if (typep) *typep = NULL; + + if (!jsdoc || !jsdoc->type) + return false; + + if (jsdoc->type->type == TYPE_OBJECT) { + uc_vector_foreach(&jsdoc->type->details.object.properties, prop) { + if (prop->name && ucv_is_equal(prop->name, key)) { + if (descp) *descp = &prop->description; + if (typep) *typep = &prop->type; + + return true; + } + } + + jsdoc_property_t *pp = uc_vector_push(&jsdoc->type->details.object.properties, { + .name = ucv_get(key) + }); + + if (descp) *descp = &pp->description; + if (typep) *typep = &pp->type; + + return true; + + } + else if (jsdoc->type->type == TYPE_ARRAY) { + int64_t index = ucv_to_integer(key); + + if (index < 0) + index += jsdoc->type->details.array.elements.count; + + if (index < 0) + return false; + + jsdoc_element_t *elem = uscope_vector_get( + &jsdoc->type->details.array.elements, + (size_t)index); + + if (!elem) + while ((size_t)index >= jsdoc->type->details.array.elements.count) + elem = uc_vector_push(&jsdoc->type->details.array.elements, { 0 }); + + if (descp) *descp = &elem->description; + if (typep) *typep = &elem->type; + + return true; + } + + return false; +} + +static uc_value_t * +update_variable(uscope_parser_t *usp, uc_value_t *uv, uscope_access_kind_t access, + uc_value_t *value, jsdoc_t *user_jsdoc) +{ + uscope_variable_t *var = variable_check(uv); + + if (!var) { + jsdoc_free(user_jsdoc); + + return NULL; + } + + uscope_reference_t *ref = uc_vector_last(&var->references); + + jsdoc_t *val_jsdoc = variable_get_jsdoc(value); + jsdoc_t *new_jsdoc; + + if (val_jsdoc) { + new_jsdoc = jsdoc_merge(val_jsdoc, user_jsdoc, 0); + } + else if (user_jsdoc) { + val_jsdoc = jsdoc_from_uv(usp->vm, value, NULL); + new_jsdoc = jsdoc_merge(val_jsdoc, user_jsdoc, 0); + jsdoc_free(val_jsdoc); + } + else { + new_jsdoc = jsdoc_from_uv(usp->vm, value, NULL); + } + + jsdoc_type_t new_type = jsdoc_get_type(new_jsdoc); + jsdoc_type_t old_type = jsdoc_get_type(var->jsdoc); + + bool is_type_change = var->initialized && + ((new_type != old_type) || + (new_type == TYPE_OBJECT && value != var->value) || + (new_type == TYPE_ARRAY && value != var->value)); + + if (var->property) { + uscope_variable_t *basevar = variable_check(var->base); + jsdoc_typedef_t **type = NULL; + uc_value_t **desc = NULL; + + if (basevar && member_upsert(basevar->jsdoc, var->name, &desc, &type)) { + jsdoc_typedef_free(*type); *type = NULL; + jsdoc_typedef_merge(type, new_jsdoc->type, 0); + + // FIXME: do we want to copy the assigned values subject as + // property description? + if (!*desc && new_jsdoc->subject) + *desc = ucv_get(new_jsdoc->subject); + + if (*type) + ucv_replace(&(*type)->value, + ucv_is_scalar(value) ? ucv_get(value) : NULL); + + usp->tokens.entries[ref->token_id].sem_modifiers |= (1u << TM_MOD); + } + } + else if (is_type_change) { + size_t token_id = uc_vector_last(&var->references)->token_id; + uc_value_t *new_var = variable_new(usp, token_id, + ucv_get(var->name), &value, + jsdoc_merge(var->jsdoc, new_jsdoc, 0), + var->constant, var->global); + + variable_get_reference(new_var, 0)->access_kind = access; + + var->range.end = uc_vector_last(&var->references)->location; + var->superseded = true; + var->references.count--; + + ucv_put(new_var); + + usp->tokens.entries[ref->token_id].sem_modifiers |= (1u << TM_MOD); + } + else { + jsdoc_t *old_jsdoc = var->jsdoc; + + if (!var->initialized) + usp->tokens.entries[ref->token_id].sem_modifiers |= (1u << TM_DEFINITION); + else + usp->tokens.entries[ref->token_id].sem_modifiers |= (1u << TM_MOD); + + var->initialized = true; + var->jsdoc = jsdoc_merge(old_jsdoc, new_jsdoc, 0); + + jsdoc_free(old_jsdoc); + } + + ref->access_kind = access; + + jsdoc_free(user_jsdoc); + jsdoc_free(new_jsdoc); + + return value; +} + +static uc_value_t *parse_declaration(uscope_parser_t *); +static uc_value_t *parse_paren(uscope_parser_t *); +static uc_value_t *parse_unary(uscope_parser_t *); +static uc_value_t *parse_delete(uscope_parser_t *); +static uc_value_t *parse_constant(uscope_parser_t *); +static uc_value_t *parse_template(uscope_parser_t *); +static uc_value_t *parse_labelexpr(uscope_parser_t *); +static uc_value_t *parse_funcexpr(uscope_parser_t *); +static uc_value_t *parse_array(uscope_parser_t *); +static uc_value_t *parse_object(uscope_parser_t *); + +static uc_value_t *parse_call(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_binary(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_post_inc(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_comma(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_and(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_or(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_nullish(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_dot(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_subscript(uscope_parser_t *, uc_value_t *); +static uc_value_t *parse_ternary(uscope_parser_t *, uc_value_t *); + +static parse_rule_t +parse_rules[TK_ERROR + 1] = { + [TK_LPAREN] = { parse_paren, parse_call, P_CALL }, + [TK_QLPAREN] = { NULL, parse_call, P_CALL }, + [TK_SUB] = { parse_unary, parse_binary, P_ADD }, + [TK_ADD] = { parse_unary, parse_binary, P_ADD }, + [TK_COMPL] = { parse_unary, NULL, P_UNARY }, + [TK_NOT] = { parse_unary, NULL, P_UNARY }, + [TK_DELETE] = { parse_delete, NULL, P_UNARY }, + [TK_INC] = { parse_unary, parse_post_inc, P_INC }, + [TK_DEC] = { parse_unary, parse_post_inc, P_INC }, + [TK_DIV] = { NULL, parse_binary, P_MUL }, + [TK_MUL] = { NULL, parse_binary, P_MUL }, + [TK_MOD] = { NULL, parse_binary, P_MUL }, + [TK_EXP] = { NULL, parse_binary, P_EXP }, + [TK_NUMBER] = { parse_constant, NULL, P_NONE }, + [TK_DOUBLE] = { parse_constant, NULL, P_NONE }, + [TK_STRING] = { parse_constant, NULL, P_NONE }, + [TK_TRUE] = { parse_constant, NULL, P_NONE }, + [TK_FALSE] = { parse_constant, NULL, P_NONE }, + [TK_NULL] = { parse_constant, NULL, P_NONE }, + [TK_THIS] = { parse_constant, NULL, P_NONE }, + [TK_REGEXP] = { parse_constant, NULL, P_NONE }, + [TK_TEMPLATE] = { parse_template, NULL, P_NONE }, + [TK_COMMA] = { NULL, parse_comma, P_COMMA }, + [TK_LABEL] = { parse_labelexpr, NULL, P_NONE }, + [TK_FUNC] = { parse_funcexpr, NULL, P_NONE }, + [TK_AND] = { NULL, parse_and, P_AND }, + [TK_OR] = { NULL, parse_or, P_OR }, + [TK_NULLISH] = { NULL, parse_nullish, P_OR }, + [TK_BOR] = { NULL, parse_binary, P_BOR }, + [TK_BXOR] = { NULL, parse_binary, P_BXOR }, + [TK_BAND] = { NULL, parse_binary, P_BAND }, + [TK_EQ] = { NULL, parse_binary, P_EQUAL }, + [TK_EQS] = { NULL, parse_binary, P_EQUAL }, + [TK_NE] = { NULL, parse_binary, P_EQUAL }, + [TK_NES] = { NULL, parse_binary, P_EQUAL }, + [TK_LT] = { NULL, parse_binary, P_COMPARE }, + [TK_LE] = { NULL, parse_binary, P_COMPARE }, + [TK_GT] = { NULL, parse_binary, P_COMPARE }, + [TK_GE] = { NULL, parse_binary, P_COMPARE }, + [TK_IN] = { NULL, parse_binary, P_COMPARE }, + [TK_LSHIFT] = { NULL, parse_binary, P_SHIFT }, + [TK_RSHIFT] = { NULL, parse_binary, P_SHIFT }, + [TK_DOT] = { NULL, parse_dot, P_CALL }, + [TK_QDOT] = { NULL, parse_dot, P_CALL }, + [TK_LBRACK] = { parse_array, parse_subscript, P_CALL }, + [TK_QLBRACK] = { NULL, parse_subscript, P_CALL }, + [TK_QMARK] = { NULL, parse_ternary, P_TERNARY }, + [TK_LBRACE] = { parse_object, NULL, P_NONE }, +}; + +static parse_rule_t *parse_rule_select(uc_tokentype_t type) { + return &parse_rules[type]; +} + +__attribute__((format(printf, 3, 0))) static void +parse_syntax_error(uscope_parser_t *usp, size_t off, const char *fmt, ...) +{ + uc_source_t *source = usp->parser.lex.source; + size_t len = 0; + va_list ap; + char *s; + + if (usp->parser.synchronizing) + return; + + usp->parser.synchronizing = true; + + va_start(ap, fmt); + len = xvasprintf(&s, fmt, ap); + va_end(ap); + + uc_vector_grow(&usp->errors); + + uscope_position_t *sp = &usp->errors.entries[usp->errors.count].start; + uscope_position_t *ep = &usp->errors.entries[usp->errors.count].end; + + sp->offset = off; + sp->column = off; + sp->line = uc_source_get_line(source, &sp->column); + + ep->offset = sp->offset; + ep->column = sp->column; + ep->line = sp->line; + + usp->errors.entries[usp->errors.count].message = ucv_string_new_length(s, len); + usp->errors.count++; + + free(s); +} + +static uc_token_t * +parse_next_token(uscope_parser_t *usp) +{ + uc_token_t *tok = NULL; + + while (true) { + tok = uc_lexer_next_token(&usp->parser.lex); + + uc_vector_grow(&usp->tokens); + usp->tokens.entries[usp->tokens.count].token.type = tok->type; + usp->tokens.entries[usp->tokens.count].token.pos = tok->pos; + usp->tokens.entries[usp->tokens.count].token.end = tok->end; + usp->tokens.entries[usp->tokens.count].token.uv = ucv_get(tok->uv); + usp->tokens.entries[usp->tokens.count].sem_type = type_semtype_map[tok->type]; + usp->tokens.count++; + + if (tok->type == TK_LSTM || tok->type == TK_COMMENT) { + ucv_put(tok->uv); + continue; + } + + if (tok->type == TK_RSTM) + tok->type = TK_SCOL; + + break; + } + + return tok; +} + +static void +token_set_sem_type(uscope_parser_t *usp, semantic_token_type_t sem_type) +{ + usp->tokens.entries[usp->prev_token_offset].sem_type = sem_type; +} + +static void +token_set_sem_modifier(uscope_parser_t *usp, semantic_token_modifier_t sem_modifier) +{ + usp->tokens.entries[usp->prev_token_offset].sem_modifiers |= (1u << sem_modifier); +} + +static void +parse_advance(uscope_parser_t *usp) +{ + ucv_put(usp->parser.prev.uv); + + usp->parser.prev = usp->parser.curr; + usp->prev_token_offset = usp->curr_token_offset; + + if (usp->lookahead.count > 0) { + for (size_t i = 0; i < usp->lookahead.count; i++) { + if (i == 0) { + usp->parser.curr = usp->lookahead.entries[i].token; + usp->curr_token_offset = usp->lookahead.entries[i].offset; + } + else { + usp->lookahead.entries[i-1] = usp->lookahead.entries[i]; + } + } + + usp->lookahead.count--; + } + else { + usp->parser.curr = *parse_next_token(usp); + usp->curr_token_offset = usp->tokens.count - 1; + } +} + +static uc_token_t * +parse_peek(uscope_parser_t *usp) +{ + uc_vector_grow(&usp->lookahead); + + usp->lookahead.entries[usp->lookahead.count].token = *parse_next_token(usp); + usp->lookahead.entries[usp->lookahead.count].offset = usp->tokens.count - 1; + + return &usp->lookahead.entries[usp->lookahead.count++].token; +} + +static void +parse_consume(uscope_parser_t *usp, uc_tokentype_t type) +{ + if (usp->parser.curr.type == type) { + parse_advance(usp); + + return; + } + + parse_syntax_error(usp, usp->parser.curr.pos, + "Unexpected token\nExpecting %s", uc_tokenname(type)); +} + +static bool +parse_check(uscope_parser_t *usp, uc_tokentype_t type) +{ + return (usp->parser.curr.type == type); +} + +static bool +token_match(uscope_parser_t *usp, uc_tokentype_t type) +{ + if (!parse_check(usp, type)) + return false; + + parse_advance(usp); + + return true; +} + +static bool +keyword_check(uscope_parser_t *usp, const char *keyword) +{ + size_t keywordlen = strlen(keyword); + + return (usp->parser.curr.type == TK_LABEL && + ucv_string_length(usp->parser.curr.uv) == keywordlen && + strcmp(ucv_string_get(usp->parser.curr.uv), keyword) == 0); +} + +static bool +keyword_match(uscope_parser_t *usp, const char *keyword) +{ + if (!keyword_check(usp, keyword)) + return false; + + parse_advance(usp); + + return true; +} + +static void +keyword_consume(uscope_parser_t *usp, const char *keyword) +{ + if (keyword_check(usp, keyword)) { + parse_advance(usp); + + return; + } + + parse_syntax_error(usp, usp->parser.curr.pos, + "Unexpected token\nExpecting '%s'", keyword); +} + +static bool +parse_is_at_assignment(uscope_parser_t *usp) +{ + switch (usp->parser.curr.type) { + case TK_ASBAND: + case TK_ASBXOR: + case TK_ASBOR: + case TK_ASLEFT: + case TK_ASRIGHT: + case TK_ASMUL: + case TK_ASDIV: + case TK_ASMOD: + case TK_ASADD: + case TK_ASSUB: + case TK_ASAND: + case TK_ASOR: + case TK_ASEXP: + case TK_ASNULLISH: + case TK_ASSIGN: + return true; + + default: + return false; + } +} + +static uc_value_t * +parse_precedence(uscope_parser_t *usp, uc_precedence_t precedence) +{ + uc_value_t *result = NULL; + parse_rule_t *rule = parse_rule_select(usp->parser.curr.type); + + if (!rule->prefix) { + parse_syntax_error(usp, usp->parser.curr.pos, "Expecting expression"); + parse_advance(usp); + + return NULL; + } + + /* allow reserved words as property names in object literals */ + if (rule->prefix == parse_object) + usp->parser.lex.no_keyword = true; + + /* unless a sub-expression follows, treat subsequent slash as division + * operator and not as beginning of regexp literal */ + if (rule->prefix != parse_paren && + rule->prefix != parse_unary && + rule->prefix != parse_array) + usp->parser.lex.no_regexp = true; + + parse_advance(usp); + + result = rule->prefix(usp); + + while (precedence <= parse_rule_select(usp->parser.curr.type)->precedence) { + rule = parse_rule_select(usp->parser.curr.type); + + if (!rule->infix) { + parse_syntax_error(usp, usp->parser.curr.pos, "Expecting ';' or binary operator"); + parse_advance(usp); + ucv_put(result); + + return NULL; + } + + /* allow reserved words in property accessors */ + if (rule->infix == parse_dot) + usp->parser.lex.no_keyword = true; + + parse_advance(usp); + + result = rule->infix(usp, result); + } + + return result; +} + +static uc_value_t * +calculate_binary_result(uc_vm_t *vm, uc_tokentype_t operator, uc_value_t *operand, uc_value_t *value) +{ + uc_value_t *v1 = uscope_resolve_variable(vm, operand, true); + uc_value_t *v2 = uscope_resolve_variable(vm, value, true); + + if (is_undef(v1)) { + ucv_put(v2); + + return v1; + } + + if (is_undef(v2)) { + ucv_put(v1); + + return v2; + } + + uc_value_t *result = ucv_arith_binary(vm, operator, v1, v2); + + ucv_put(v1); + ucv_put(v2); + + return result; +} + +static uc_value_t * +calculate_unary_result(uc_vm_t *vm, uc_tokentype_t operator, uc_value_t *operand) +{ + uc_value_t *v1 = uscope_resolve_variable(vm, operand, true); + uc_value_t *nv, *zv, *result; + int64_t n; + + if (is_undef(v1)) + return v1; + + switch (operator) { + case TK_SUB: + nv = ucv_to_number(v1); + zv = ucv_uint64_new(0); + result = ucv_arith_binary(vm, TK_SUB, zv, nv); + ucv_put(nv); + ucv_put(zv); + break; + + case TK_ADD: + result = ucv_to_number(v1); + break; + + case TK_COMPL: + nv = ucv_to_number(v1); + n = ucv_int64_get(nv); + result = (n < 0) ? ucv_int64_new(~n) : ucv_uint64_new(~ucv_uint64_get(nv)); + ucv_put(nv); + break; + + case TK_NOT: + result = ucv_boolean_new(ucv_is_truish(v1) == false); + break; + + case TK_INC: + nv = ucv_to_number(v1); + zv = ucv_uint64_new(1); + result = ucv_arith_binary(vm, TK_ADD, nv, zv); + ucv_put(nv); + ucv_put(zv); + break; + + case TK_DEC: + nv = ucv_to_number(v1); + zv = ucv_uint64_new(1); + result = ucv_arith_binary(vm, TK_SUB, nv, zv); + ucv_put(nv); + ucv_put(zv); + break; + + default: + result = NULL; + break; + } + + ucv_put(v1); + + return result; +} + +static int +is_truish(uc_vm_t *vm, uc_value_t *uv) +{ + uc_value_t *v = uscope_resolve_variable(vm, uv, true); + + if (is_undef(v)) { + ucv_put(v); + + return -1; + } + + bool rv = ucv_is_truish(v); + + ucv_put(v); + + return rv; +} + + +static uc_value_t * +parse_text(uscope_parser_t *usp) +{ + return ucv_get(usp->parser.prev.uv); +} + +static uc_value_t * +parse_post_inc(uscope_parser_t *usp, uc_value_t *operand) +{ + uscope_variable_t *var = ucv_resource_data(operand, "uscope.variable"); + uc_value_t *res = NULL; + + if (var) { + res = uscope_resolve_variable(usp->vm, operand, true); + + uc_value_t *inc = is_undef(res) + ? ucv_get(res) : ucv_arith_binary(usp->vm, + usp->parser.prev.type == TK_INC ? TK_ADD : TK_SUB, + res, ucv_uint64_new(1)); + + update_variable(usp, operand, ACCESS_UPDATE, inc, NULL); + ucv_put(inc); + } + + ucv_put(operand); + + return res; +} + +static uc_value_t * +parse_delete(uscope_parser_t *usp) +{ + uc_value_t *operand = parse_precedence(usp, P_UNARY); + uscope_variable_t *var = ucv_resource_data(operand, "uscope.variable"); + + ucv_replace(&operand, + ucv_boolean_new(var ? var->initialized : false)); + + return operand; +} + +static uc_value_t * +parse_comma(uscope_parser_t *usp, uc_value_t *left) +{ + ucv_put(left); + + return parse_precedence(usp, P_ASSIGN); +} + +static uc_value_t * +parse_unary(uscope_parser_t *usp) +{ + uc_tokentype_t operator = usp->parser.prev.type; + uc_value_t *operand = parse_precedence(usp, P_UNARY); + uc_value_t *result = NULL; + + result = calculate_unary_result(usp->vm, operator, operand); + + ucv_put(operand); + + return result; +} + +static uc_value_t * +parse_binary(uscope_parser_t *usp, uc_value_t *left) +{ + uc_tokentype_t operator = usp->parser.prev.type; + parse_rule_t *rule = parse_rule_select(operator); + uc_value_t *right = parse_precedence(usp, rule->precedence + 1); + uc_value_t *result = NULL; + + result = calculate_binary_result(usp->vm, operator, left, right); + + ucv_put(left); + ucv_put(right); + + return result; +} + +static uc_value_t * +parse_assignment(uscope_parser_t *usp, uc_value_t *left, jsdoc_t *jsdoc) +{ + uc_tokentype_t operator = usp->parser.curr.type; + uc_value_t *result = NULL; + + parse_advance(usp); + + uc_value_t *right = parse_precedence(usp, P_ASSIGN); + uc_tokentype_t op = TK_ASSIGN; + + switch (operator) { + case TK_ASBAND: op = TK_BAND; break; + case TK_ASBXOR: op = TK_BXOR; break; + case TK_ASBOR: op = TK_BOR; break; + case TK_ASLEFT: op = TK_LSHIFT; break; + case TK_ASRIGHT: op = TK_RSHIFT; break; + case TK_ASMUL: op = TK_MUL; break; + case TK_ASDIV: op = TK_DIV; break; + case TK_ASMOD: op = TK_MOD; break; + case TK_ASADD: op = TK_ADD; break; + case TK_ASSUB: op = TK_SUB; break; + case TK_ASAND: op = TK_AND; break; + case TK_ASOR: op = TK_OR; break; + case TK_ASEXP: op = TK_EXP; break; + case TK_ASNULLISH: op = TK_NULLISH; break; + default: break; + } + + if (right) { + if (op != TK_ASSIGN) + result = calculate_binary_result(usp->vm, op, left, right); + else + result = ucv_get(right); + } + + update_variable(usp, left, + (op == TK_ASSIGN) ? ACCESS_WRITE : ACCESS_UPDATE, + right, jsdoc); + + ucv_put(left); + ucv_put(right); + + return result; +} + +static uc_value_t * +parse_dot(uscope_parser_t *usp, uc_value_t *left) +{ + bool optional_chaining = (usp->parser.prev.type == TK_QDOT); + uscope_variable_t *var = variable_check(left); + uc_value_t *key = ucv_get(usp->parser.curr.uv); + size_t token_id = usp->prev_token_offset; + uc_value_t *prop = NULL; + jsdoc_t *jsdoc = NULL; + + usp->parser.lex.no_regexp = true; + parse_consume(usp, TK_LABEL); + token_set_sem_type(usp, TT_PROPERTY); + + if (var && var->jsdoc && jsdoc_get_type(var->jsdoc) == TYPE_OBJECT) { + for (size_t i = 0; i < ucv_array_length(var->value); i++) { + uscope_variable_t *pvar = + variable_check(ucv_array_get(var->value, i)); + + if (!pvar || !pvar->name || !pvar->property) + continue; + + if (!ucv_is_equal(pvar->name, key)) + continue; + + prop = ucv_get(ucv_array_get(var->value, i)); + break; + } + + if (!prop) { + prop = property_new(usp, token_id, ucv_get(key), NULL, NULL, left); + ucv_array_push(var->value, prop); + } + else { + reference_add(usp, prop, usp->prev_token_offset, ACCESS_READ); + } + } + else { + // Base value is not an object, creating dangling property reference... + prop = property_new(usp, token_id, ucv_get(key), NULL, NULL, left); + } + + if (var) { + jsdoc_typedef_t *type = NULL; + uc_value_t *desc = NULL; + + if (member_lookup(var->jsdoc, key, &desc, &type)) { + jsdoc = variable_upsert_jsdoc(prop, KIND_MEMBER); + ucv_replace(&jsdoc->description, ucv_get(desc)); + jsdoc_typedef_merge(&jsdoc->type, type, 0); + } + } + + ucv_put(left); + ucv_put(key); + + uscope_reference_t *ref = variable_get_reference(prop, -1); + + if (ref) + ref->optional = optional_chaining; + + if (parse_is_at_assignment(usp)) + return parse_assignment(usp, prop, NULL); + + return prop; +} + +static uc_value_t * +parse_expression(uscope_parser_t *usp) +{ + return parse_precedence(usp, P_COMMA); +} + +static uc_value_t * +uscope_resolve_expression(uscope_parser_t *usp, uc_precedence_t precedence) +{ + uc_value_t *expr = parse_precedence(usp, precedence); + uc_value_t *result = uscope_resolve_variable(usp->vm, expr, true); + + ucv_put(expr); + + return result; +} + +static uc_value_t * +parse_subscript(uscope_parser_t *usp, uc_value_t *left) +{ + bool optional_chaining = (usp->parser.prev.type == TK_QLBRACK); + size_t token_id = usp->prev_token_offset; + uc_value_t *key = uscope_resolve_expression(usp, P_ASSIGN); + + usp->parser.lex.no_regexp = true; + parse_consume(usp, TK_RBRACK); + + uscope_variable_t *var = variable_check(left); + uc_value_t *prop = NULL; + + jsdoc_type_t vartype = (var && var->jsdoc) + ? jsdoc_get_type(var->jsdoc) : TYPE_UNSPEC; + + if (vartype == TYPE_ARRAY || vartype == TYPE_OBJECT) { + for (size_t i = 0; i < ucv_array_length(var->value); i++) { + uscope_variable_t *pvar = + variable_check(ucv_array_get(var->value, i)); + + if (!pvar || !pvar->name || !pvar->property) + continue; + + if (!ucv_compare(I_EQ, pvar->name, key, NULL)) + continue; + + prop = ucv_get(ucv_array_get(var->value, i)); + break; + } + + if (!prop) { + prop = property_new(usp, token_id, ucv_get(key), NULL, NULL, left); + ucv_array_push(var->value, prop); + } + else { + reference_add(usp, prop, token_id, ACCESS_READ); + } + } + else { + // Base value is not an object, creating dangling property reference... + prop = property_new(usp, token_id, ucv_get(key), NULL, NULL, left); + } + + if (var && key) { + jsdoc_typedef_t *type = NULL; + uc_value_t *desc = NULL; + + if (member_lookup(var->jsdoc, key, &desc, &type)) { + jsdoc_t *jsdoc = variable_upsert_jsdoc(prop, KIND_MEMBER); + jsdoc->description = ucv_get(desc); + jsdoc_typedef_merge(&jsdoc->type, type, 0); + } + } + + ucv_put(left); + ucv_put(key); + + uscope_reference_t *ref = variable_get_reference(prop, -1); + + if (ref) + ref->optional = optional_chaining; + + if (parse_is_at_assignment(usp)) + return parse_assignment(usp, prop, NULL); + + return prop; +} + +static uc_value_t * +parse_and(uscope_parser_t *usp, uc_value_t *left) +{ + uc_value_t *right = parse_precedence(usp, P_AND); + uc_value_t *result = NULL; + + result = calculate_binary_result(usp->vm, TK_AND, left, right); + + ucv_put(left); + ucv_put(right); + + return result; +} + +static uc_value_t * +parse_or(uscope_parser_t *usp, uc_value_t *left) +{ + uc_value_t *right = parse_precedence(usp, P_OR); + uc_value_t *result = NULL; + + result = calculate_binary_result(usp->vm, TK_OR, left, right); + + ucv_put(left); + ucv_put(right); + + return result; +} + +static uc_value_t * +parse_nullish(uscope_parser_t *usp, uc_value_t *left) +{ + uc_value_t *right = parse_precedence(usp, P_OR); + uc_value_t *result = NULL; + + result = calculate_binary_result(usp->vm, TK_NULLISH, left, right); + + ucv_put(left); + ucv_put(right); + + return result; +} + +static uc_value_t * +parse_ternary(uscope_parser_t *usp, uc_value_t *condition) +{ + size_t token_num = usp->prev_token_offset; + + uc_value_t *true_expr = parse_precedence(usp, P_ASSIGN); + + parse_consume(usp, TK_COLON); + + uc_value_t *false_expr = parse_precedence(usp, P_TERNARY); + uc_value_t *result = NULL; + + switch (is_truish(usp->vm, condition)) { + case -1: + result = undef_new(usp, token_num, + jsdoc_merge( + variable_get_jsdoc(true_expr), + variable_get_jsdoc(false_expr), + MERGE_UNION | MERGE_TYPEONLY)); + break; + + case 0: result = ucv_get(false_expr); break; + case 1: result = ucv_get(true_expr); break; + } + + ucv_put(condition); + ucv_put(true_expr); + ucv_put(false_expr); + + return result; +} + +static void +upsert_element(jsdoc_t *jsdoc, size_t index, const jsdoc_t *elem_jsdoc) +{ + if (jsdoc_get_type(jsdoc) != TYPE_ARRAY) + return; + + jsdoc_element_t *elem = NULL; + + while (index >= jsdoc->type->details.array.elements.count) + elem = uc_vector_push(&jsdoc->type->details.array.elements, { 0 }); + + if (!elem) + elem = uscope_vector_get(&jsdoc->type->details.array.elements, index); + + if (!elem->description && elem_jsdoc->description) + elem->description = ucv_get(elem_jsdoc->description); + + jsdoc_typedef_merge(&elem->type, elem_jsdoc->type, 0); +} + +static uc_value_t * +parse_array(uscope_parser_t *usp) +{ + size_t index = 0; + uc_value_t *av = ucv_array_new(usp->vm); + uc_value_t *uv; + + /* create anonymous const */ + uscope_variable_t *anon = xalloc(sizeof(uscope_variable_t)); + anon->constant = true; + anon->initialized = true; + anon->value = ucv_get(av); + anon->jsdoc = jsdoc_new(TYPE_ARRAY); + anon->name = ucv_asprintf(".array.%p", av); + + uc_vector_grow(&anon->references); + + uscope_reference_t *ref = &anon->references.entries[anon->references.count++]; + + ref->access_kind = ACCESS_DECLARATION; + ref->token_id = usp->prev_token_offset; + ref->value = ucv_get(av); + + size_t scope_id = *uc_vector_last(&usp->scopechain); + scope_t *scope = uscope_vector_get(&usp->scopes, scope_id); + + uv = ucv_resource_new( + ucv_resource_type_lookup(usp->vm, "uscope.variable"), + anon); + + uc_vector_push(&scope->variables, ucv_get(uv)); + uc_vector_push(&usp->thischain, uv); + + do { + if (parse_check(usp, TK_RBRACK)) { + break; + } + else if (token_match(usp, TK_ELLIP)) { + size_t token_id = usp->prev_token_offset; + uc_value_t *src_arr = uscope_resolve_expression(usp, P_ASSIGN); + + if (ucv_type(src_arr) == UC_ARRAY) { + for (size_t i = 0; i < ucv_array_length(src_arr); i++) { + uc_value_t *v = ucv_array_get(src_arr, i); + jsdoc_t *elem_jsdoc = jsdoc_derive(NULL, v, false); + + upsert_element(anon->jsdoc, index++, elem_jsdoc); + + uc_value_t *initval = uscope_resolve_variable(usp->vm, v, true); + + ucv_array_push(av, + property_new(usp, token_id, + ucv_uint64_new(ucv_array_length(av)), + &initval, elem_jsdoc, uv)); + + ucv_put(initval); + } + } + else { + ucv_array_push(av, undef_new(usp, token_id, NULL)); + } + + ucv_put(src_arr); + } + else { + size_t token_id = usp->curr_token_offset; + uc_value_t *expr = parse_precedence(usp, P_ASSIGN); + jsdoc_t *elem_jsdoc = jsdoc_capture(usp, token_id, expr); + + upsert_element(anon->jsdoc, index++, elem_jsdoc); + + uc_value_t *initval = uscope_resolve_variable(usp->vm, expr, true); + + ucv_array_push(av, + property_new(usp, token_id, + ucv_uint64_new(ucv_array_length(av)), &initval, + elem_jsdoc, uv)); + + ucv_put(initval); + ucv_put(expr); + } + } while (token_match(usp, TK_COMMA)); + + usp->parser.lex.no_regexp = true; + parse_consume(usp, TK_RBRACK); + + usp->thischain.count--; + + ucv_put(av); + + return uv; +} + +static void +upsert_property(jsdoc_t *jsdoc, uc_value_t *name, const jsdoc_t *prop_jsdoc) +{ + if (jsdoc_get_type(jsdoc) != TYPE_OBJECT) + return; + + jsdoc_property_t *prop = NULL; + + uc_vector_foreach(&jsdoc->type->details.object.properties, pp) { + if (pp->name && ucv_is_equal(pp->name, name)) { + prop = pp; + break; + } + } + + if (!prop) { + prop = uc_vector_push(&jsdoc->type->details.object.properties, { + .name = ucv_get(name) + }); + } + + if (!prop->description && prop_jsdoc->description) + prop->description = ucv_get(prop_jsdoc->description); + + jsdoc_typedef_merge(&prop->type, prop_jsdoc->type, 0); +} + +static uc_value_t * +parse_object(uscope_parser_t *usp) +{ + uc_value_t *props = ucv_array_new(usp->vm); + uc_value_t *uv = NULL; + + /* create anonymous const */ + uscope_variable_t *anon = xalloc(sizeof(uscope_variable_t)); + anon->constant = true; + anon->initialized = true; + anon->value = ucv_get(props); + anon->jsdoc = jsdoc_new(TYPE_OBJECT); + anon->name = ucv_asprintf(".object.%p", props); + + uc_vector_grow(&anon->references); + + uscope_reference_t *ref = &anon->references.entries[anon->references.count++]; + + ref->access_kind = ACCESS_DECLARATION; + ref->token_id = usp->prev_token_offset; + ref->value = ucv_get(props); + + size_t scope_id = usp->scopechain.entries[usp->scopechain.count - 1]; + scope_t *scope = &usp->scopes.entries[scope_id]; + + uv = ucv_resource_new( + ucv_resource_type_lookup(usp->vm, "uscope.variable"), + anon); + + uc_vector_push(&scope->variables, ucv_get(uv)); + uc_vector_push(&usp->thischain, uv); + + while (!parse_check(usp, TK_RBRACE)) { + if (token_match(usp, TK_ELLIP)) { + size_t token_id = usp->prev_token_offset; + uc_value_t *src_obj = uscope_resolve_expression(usp, P_ASSIGN); + + if (ucv_type(src_obj) == UC_OBJECT) { + ucv_object_foreach(src_obj, k, v) { + jsdoc_t *prop_jsdoc = jsdoc_derive(NULL, v, false); + uc_value_t *key = ucv_string_new(k); + + upsert_property(anon->jsdoc, key, prop_jsdoc); + ucv_put(key); + + uc_value_t *initval = uscope_resolve_variable(usp->vm, v, true); + + ucv_array_push(props, + property_new(usp, token_id, + ucv_string_new(k), &initval, prop_jsdoc, uv)); + + ucv_put(initval); + } + } + else { + // FIXME: need any kind of placeholder logic here? + } + + ucv_put(src_obj); + } + else if (token_match(usp, TK_LBRACK)) { + size_t token_id = usp->prev_token_offset; + uc_value_t *key = uscope_resolve_expression(usp, P_ASSIGN); + + parse_consume(usp, TK_RBRACK); + parse_consume(usp, TK_COLON); + + uc_value_t *value = parse_precedence(usp, P_ASSIGN); + + // FIXME: need any kind of placeholder logic here? + if (!is_undef(key)) { + jsdoc_t *prop_jsdoc = jsdoc_capture(usp, token_id, value); + + upsert_property(anon->jsdoc, key, prop_jsdoc); + + uc_value_t *prop = property_new(usp, token_id, ucv_get(key), + NULL, prop_jsdoc, uv); + + uscope_variable_t *pvar = ucv_resource_data(prop, "uscope.variable"); + pvar->value = uscope_resolve_variable(usp->vm, value, true); + pvar->initialized = true; + + ucv_array_push(props, prop); + } + + ucv_put(key); + ucv_put(value); + } + else { + if (token_match(usp, TK_LABEL) || token_match(usp, TK_STRING)) { + size_t token_id = usp->prev_token_offset; + if (parse_check(usp, TK_COLON)) { + uc_value_t *key = ucv_get(usp->parser.prev.uv); + + token_set_sem_type(usp, TT_PROPERTY); + parse_consume(usp, TK_COLON); + + uc_value_t *value = parse_precedence(usp, P_ASSIGN); + jsdoc_t *prop_jsdoc = jsdoc_capture(usp, token_id, value); + + upsert_property(anon->jsdoc, key, prop_jsdoc); + + uc_value_t *prop = property_new(usp, token_id, key, NULL, prop_jsdoc, uv); + uscope_variable_t *pvar = ucv_resource_data(prop, "uscope.variable"); + + ucv_array_push(props, prop); + + pvar->value = uscope_resolve_variable(usp->vm, value, true); + pvar->initialized = true; + + ucv_put(value); + } + else { + uc_value_t *key = ucv_get(usp->parser.prev.uv); + uc_value_t *value = variable_access(usp, NULL, ACCESS_READ); + jsdoc_t *prop_jsdoc = jsdoc_capture(usp, token_id, value); + + upsert_property(anon->jsdoc, key, prop_jsdoc); + + uc_value_t *prop = property_new(usp, token_id, ucv_get(key), + NULL, prop_jsdoc, uv); + + uscope_variable_t *pvar = ucv_resource_data(prop, "uscope.variable"); + + ucv_array_push(props, prop) ; + + pvar->value = uscope_resolve_variable(usp->vm, value, true); + pvar->initialized = true; + + ucv_put(value); + ucv_put(key); + } + } + else { + parse_syntax_error(usp, usp->parser.curr.pos, "Expecting property name"); + goto out; + } + } + + usp->parser.lex.no_keyword = true; + + if (!token_match(usp, TK_COMMA)) + break; + } + + usp->parser.lex.no_regexp = true; + parse_consume(usp, TK_RBRACE); + +out: + usp->thischain.count--; + + ucv_put(props); + + return uv; +} + +static uc_value_t * +parse_template(uscope_parser_t *usp) +{ + uc_value_t *text = parse_constant(usp); + uc_value_t *tmp, *result = text; + + while (true) { + if (token_match(usp, TK_TEMPLATE)) { + text = parse_constant(usp); + + tmp = calculate_binary_result(usp->vm, TK_ADD, result, text); + + ucv_put(text); + ucv_replace(&result, tmp); + } + else if (token_match(usp, TK_PLACEH)) { + uc_value_t *expr = parse_precedence(usp, P_ASSIGN); + + parse_consume(usp, TK_RBRACE); + + tmp = calculate_binary_result(usp->vm, TK_ADD, result, expr); + + ucv_put(expr); + ucv_replace(&result, tmp); + } + else { + break; + } + } + + return result; +} + +static bool +is_global_function(uscope_variable_t *var, const char *name) +{ + if (!var || !var->global || !name || ucv_type(var->name) != UC_STRING) + return false; + + return (strcmp(ucv_string_get(var->name), name) == 0); +} + +static bool +apply_function_side_effects(uscope_parser_t *usp, size_t token_num, + uscope_variable_t *var, callargs_t *args, + uc_value_t **retval) +{ + if (is_global_function(var, "push") || is_global_function(var, "unshift")) { + /* need non-spread subject as first arg and at least one member */ + if (args->count < 2 || args->entries[0].spread == true) + return false; + + jsdoc_typedef_t **arr_defp = NULL; + + uc_vector_foreach(args, arg) { + if (!arr_defp) { + jsdoc_t *jsdoc = variable_get_jsdoc(arg->value); + + if (jsdoc_get_type(jsdoc) != TYPE_ARRAY) + break; + + arr_defp = &jsdoc->type->details.array.item_type; + continue; + } + + jsdoc_typedef_t *arg_def = jsdoc_typedef_from_uv(usp->vm, arg->value); + + jsdoc_t js1 = { .type = *arr_defp }, js2 = { .type = arg_def }; + uc_value_t *jsu1, *jsu2; + char *jss1, *jss2; + + jsu1 = jsdoc_to_uv(usp->vm, &js1); + jss1 = ucv_to_string(usp->vm, jsu1); + jsu2 = jsdoc_to_uv(usp->vm, &js2); + jss2 = ucv_to_string(usp->vm, jsu1); + + fprintf(stderr, "ARRAY TYPE MERGE: %s + %s\n", jss1, jss2); + + ucv_put(jsu1); + ucv_put(jsu2); + free(jss1); + free(jss2); + + if (arg->spread) { + if (arg_def->type == TYPE_ARRAY) { + jsdoc_typedef_merge(arr_defp, + arg_def->details.array.item_type, + MERGE_UNION | MERGE_TYPEONLY); + } + } + else { + jsdoc_typedef_merge(arr_defp, arg_def, + MERGE_UNION | MERGE_TYPEONLY); + } + + jsdoc_typedef_free(arg_def); + } + + if (args->entries[args->count - 1].spread) + *retval = undef_new(usp, token_num, + jsdoc_derive(NULL, args->entries[args->count - 1].value, false)); + else + *retval = ucv_get(args->entries[args->count - 1].value); + + return true; + } + else if (is_global_function(var, "sort")) { + /* need non-spread subject as first arg */ + if (args->count < 1 || args->entries[0].spread == true) + return false; + + jsdoc_t *src_jsdoc = variable_get_jsdoc(args->entries[0].value); + + /* clear elements from typedef as the sorting will alter indexes */ + if (jsdoc_get_type(src_jsdoc) == TYPE_ARRAY) { + jsdoc_t *dst_jsdoc = xalloc(sizeof(jsdoc_t)); + + jsdoc_typedef_merge(&dst_jsdoc->type, src_jsdoc->type, + MERGE_TYPEONLY | MERGE_NOELEMS); + + *retval = undef_new(usp, token_num, dst_jsdoc); + } + else if (jsdoc_get_type(src_jsdoc) == TYPE_OBJECT) { + *retval = ucv_get(args->entries[0].value); + } + else { + *retval = NULL; + } + + return true; + } + else if (is_global_function(var, "reverse")) { + /* need non-spread subject as first arg */ + if (args->count < 1 || args->entries[0].spread == true) + return false; + + jsdoc_t *src_jsdoc = variable_get_jsdoc(args->entries[0].value); + + if (jsdoc_get_type(src_jsdoc) == TYPE_ARRAY || jsdoc_get_type(src_jsdoc) == TYPE_STRING) { + jsdoc_t *dst_jsdoc = xalloc(sizeof(jsdoc_t)); + + jsdoc_typedef_merge(&dst_jsdoc->type, src_jsdoc->type, + MERGE_TYPEONLY | MERGE_NOELEMS); + + *retval = undef_new(usp, token_num, dst_jsdoc); + } + else { + *retval = NULL; + } + + return true; + } + else if (is_global_function(var, "keys")) { + /* need non-spread subject as first arg */ + if (args->count < 1 || args->entries[0].spread == true) + return false; + + jsdoc_t *src_jsdoc = variable_get_jsdoc(args->entries[0].value); + + if (jsdoc_get_type(src_jsdoc) == TYPE_OBJECT) { + jsdoc_t *dst_jsdoc = jsdoc_new(TYPE_ARRAY); + + dst_jsdoc->type->details.array.item_type = jsdoc_typedef_new(TYPE_STRING); + + *retval = undef_new(usp, token_num, dst_jsdoc); + } + else { + *retval = NULL; + } + + return true; + } + else if (is_global_function(var, "proto")) { + /* we only handle the two argument form here and return a fused + placeholder value containing both the subject and the prototype + object properties */ + if (args->count < 2 || + args->entries[0].spread == true || + args->entries[1].spread == true) + return false; + + jsdoc_t *this_jsdoc = jsdoc_derive(NULL, args->entries[0].value, false); + jsdoc_t *proto_jsdoc = variable_get_jsdoc(args->entries[1].value); + + if (jsdoc_get_type(proto_jsdoc) == TYPE_OBJECT) + jsdoc_typedef_merge(&this_jsdoc->type, proto_jsdoc->type, MERGE_UNION); + + *retval = undef_new(usp, token_num, this_jsdoc); + + return true; + } + + return false; +} + +static void +token_id_to_position(uscope_parser_t *usp, size_t token_id, bool end, + uscope_position_t *position) +{ + semantic_token_t *stok = uscope_vector_get(&usp->tokens, token_id); + + if (!stok) + return; + + position->offset = end ? stok->token.end : stok->token.pos; + position->column = position->offset; + position->line = + uc_source_get_line(usp->parser.lex.source, &position->column); +} + +static uc_value_t * +parse_call(uscope_parser_t *usp, uc_value_t *callee) +{ + size_t token_num = usp->prev_token_offset; + uscope_variable_t *var = ucv_resource_data(callee, "uscope.variable"); + + for (size_t i = 0; var && i < var->references.count; i++) + usp->tokens.entries[var->references.entries[i].token_id].sem_type = TT_FUNCTION; + + callargs_t args = { 0 }; + + if (!parse_check(usp, TK_RPAREN)) { + do { + if (token_match(usp, TK_ELLIP)) { + uc_value_t *arg = parse_precedence(usp, P_ASSIGN); + uc_vector_push(&args, ((typeof(*args.entries)){ true, arg })); + } + else { + uc_value_t *arg = parse_precedence(usp, P_ASSIGN); + uc_vector_push(&args, ((typeof(*args.entries)){ false, arg })); + } + } while (token_match(usp, TK_COMMA)); + } + + uc_value_t *retval = NULL; + bool have_retval = false; + + have_retval = apply_function_side_effects(usp, token_num, var, &args, &retval); + + while (args.count > 0) + ucv_put(args.entries[--args.count].value); + + uc_vector_clear(&args); + + usp->parser.lex.no_regexp = true; + parse_consume(usp, TK_RPAREN); + + jsdoc_t *result_jsdoc = variable_get_jsdoc(retval); + jsdoc_t *var_jsdoc = NULL; + + if (var && jsdoc_get_type(var->jsdoc) == TYPE_FUNCTION) { + jsdoc_typedef_t *fndef = var->jsdoc->type; + jsdoc_t *return_jsdoc = jsdoc_from_return(KIND_MEMBER, fndef); + + var_jsdoc = jsdoc_merge(result_jsdoc, return_jsdoc, MERGE_UNION); + + jsdoc_free(return_jsdoc); + } + + if (!have_retval) + retval = undef_new(usp, token_num, result_jsdoc); + + uc_value_t *retvar = variable_new(usp, token_num, + VAR_NAME_AUTO_RETURN, &retval, var_jsdoc, false, false); + + uscope_variable_t *retvar_var = variable_check(retvar); + + retvar_var->base = ucv_get(callee); + retvar_var->superseded = true; + token_id_to_position(usp, usp->prev_token_offset, + true, &retvar_var->range.end); + + ucv_put(retval); + ucv_put(callee); + + return retvar; +} + +static uc_value_t * +parse_control(uscope_parser_t *usp) +{ + parse_consume(usp, TK_SCOL); + + return NULL; +} + +static uc_value_t * +parse_tplexp(uscope_parser_t *usp) +{ + uc_value_t *expr = parse_precedence(usp, P_ASSIGN); + + token_match(usp, TK_SCOL); + parse_consume(usp, TK_REXP); + + return expr; +} + +static uc_value_t * +parse_expstmt(uscope_parser_t *usp) +{ + if (token_match(usp, TK_SCOL)) + return NULL; + + uc_value_t *expr = parse_expression(usp); + + switch (usp->parser.curr.type) { + case TK_RBRACE: + case TK_ELIF: + case TK_ENDIF: + case TK_ENDFOR: + case TK_ENDWHILE: + case TK_ENDFUNC: + case TK_EOF: + break; + + case TK_ELSE: + // NB: we're lenient here, the ucode compiler has stricter rules + token_match(usp, TK_SCOL); + break; + + default: + parse_consume(usp, TK_SCOL); + break; + } + + return expr; +} + +static void +jsdoc_merge_return_type(uscope_parser_t *usp, size_t jsdoc_token_id, uc_value_t *uv) +{ + if (!usp->function_jsdocs.count) + return; + + jsdoc_t *func_jsdoc = *uc_vector_last(&usp->function_jsdocs); + jsdoc_typedef_t **rt = &func_jsdoc->type->details.function.return_type; + uc_value_t **rd = &func_jsdoc->type->details.function.return_description; + + if (uv) { + jsdoc_t *ret_jsdoc = jsdoc_capture(usp, jsdoc_token_id, uv); + + if (!*rd && ret_jsdoc->subject) + *rd = ucv_get(ret_jsdoc->subject); + + jsdoc_typedef_merge(rt, ret_jsdoc->type, MERGE_UNION); + jsdoc_free(ret_jsdoc); + } + else if (*rt) { + (*rt)->nullable = true; + } +} + +static uc_value_t * +parse_return(uscope_parser_t *usp) +{ + size_t jsdoc_token_id = usp->prev_token_offset; + uc_value_t *retval = parse_expstmt(usp); + + jsdoc_merge_return_type(usp, jsdoc_token_id, retval); + ucv_put(retval); + + return NULL; +} + +static uc_value_t * +parse_declexpr(uscope_parser_t *usp, bool constant) +{ + jsdoc_t *decl_wide_doc = jsdoc_capture(usp, usp->prev_token_offset, NULL); + uc_value_t *result = ucv_array_new(usp->vm); + + do { + if (!token_match(usp, TK_LABEL)) { + parse_syntax_error(usp, usp->parser.curr.pos, "Expecting variable name"); + goto out; + } + + jsdoc_t *var_specific_doc = jsdoc_capture(usp, usp->prev_token_offset, NULL); + size_t token_id = usp->prev_token_offset; + uc_value_t *initval = NULL; + + variable_supersede(usp, usp->parser.prev.uv, token_id); + token_set_sem_type(usp, TT_VARIABLE); + + jsdoc_t *decl_jsdoc = jsdoc_merge(decl_wide_doc, var_specific_doc, 0); + + if (constant) + token_set_sem_modifier(usp, TM_READONLY); + + if (token_match(usp, TK_ASSIGN)) { + uc_vector_push(&usp->declaration_jsdocs, decl_jsdoc); + + initval = parse_precedence(usp, P_ASSIGN); + + usp->declaration_jsdocs.count--; + + jsdoc_typedef_t *valdef = jsdoc_typedef_from_uv(usp->vm, initval); + + jsdoc_typedef_merge(&var_specific_doc->type, valdef, 0); + jsdoc_typedef_free(valdef); + } + else if (constant) { + parse_syntax_error(usp, usp->parser.prev.pos, + "Expecting initializer expression"); + + jsdoc_free(var_specific_doc); + goto out; + } + + ucv_array_push(result, + variable_new(usp, token_id, NULL, &initval, + decl_jsdoc, constant, false)); + + jsdoc_free(var_specific_doc); + ucv_put(initval); + } + while (token_match(usp, TK_COMMA)); + +out: + jsdoc_free(decl_wide_doc); + + return result; +} + +static uc_value_t * +parse_local(uscope_parser_t *usp) +{ + ucv_put(parse_declexpr(usp, false)); + parse_consume(usp, TK_SCOL); + + return NULL; +} + +static uc_value_t * +parse_const(uscope_parser_t *usp) +{ + ucv_put(parse_declexpr(usp, true)); + parse_consume(usp, TK_SCOL); + + return NULL; +} + +static uc_value_t * +parse_block(uscope_parser_t *usp, uc_tokentype_t endtype) +{ + scope_enter(usp); + + while (!parse_check(usp, endtype) && + !parse_check(usp, TK_EOF)) { + + ucv_put(parse_declaration(usp)); + } + + parse_consume(usp, endtype); + + scope_leave(usp); + + return NULL; +} + +static uc_value_t * +create_function(uscope_parser_t *usp, uc_value_t *name, + bool arrow, bool vararg, uc_value_t *arglist) +{ + uc_cfn_ptr_t loadstring_fn = uc_stdlib_function("loadstring"); + + if (!loadstring_fn) + return NULL; + + uc_stringbuf_t *sbuf = ucv_stringbuf_new(); + + ucv_stringbuf_append(sbuf, "const retval = null, argvals = null; return "); + + if (arrow) + ucv_stringbuf_append(sbuf, "("); + else if (name) + ucv_stringbuf_printf(sbuf, "function %s (", ucv_string_get(name)); + else + ucv_stringbuf_append(sbuf, "function ("); + + for (size_t i = 0; i < ucv_array_length(arglist); i++) { + if (i > 0) + ucv_stringbuf_append(sbuf, ", "); + + if (i + 1 == ucv_array_length(arglist) && vararg) + ucv_stringbuf_append(sbuf, "...args"); + else + ucv_stringbuf_printf(sbuf, "arg%zu", i); + } + + if (arrow) + ucv_stringbuf_append(sbuf, ") => argvals, retval;"); + else + ucv_stringbuf_append(sbuf, ") { return argvals, retval; };"); + + uc_vm_stack_push(usp->vm, ucv_stringbuf_finish(sbuf)); + + uc_value_t *fn = loadstring_fn(usp->vm, 1); + + ucv_put(uc_vm_stack_pop(usp->vm)); + + if (ucv_type(fn) != UC_CLOSURE) { + ucv_put(fn); + + return NULL; + } + + uc_vm_stack_push(usp->vm, fn); + + if (uc_vm_call(usp->vm, false, 0) != EXCEPTION_NONE) + return NULL; + + fn = uc_vm_stack_pop(usp->vm); + + if (ucv_type(fn) != UC_CLOSURE) { + ucv_put(fn); + + return NULL; + } + + uc_upvalref_t **upvals = ((uc_closure_t *)fn)->upvals; + + // FIXME: attach function.return_type to undef jsdoc + upvals[0]->closed = true; + upvals[0]->value = undef_new(usp, usp->prev_token_offset, NULL); + + upvals[1]->closed = true; + upvals[1]->value = arglist; + + return fn; +} + +static jsdoc_t * +jsdoc_new_function(uscope_parser_t *usp, size_t jsdoc_token_id) +{ + jsdoc_t *func_jsdoc = jsdoc_capture(usp, jsdoc_token_id, NULL); + jsdoc_t *decl_jsdoc = usp->declaration_jsdocs.count + ? *uc_vector_last(&usp->declaration_jsdocs) : NULL; + + if (jsdoc_get_type(decl_jsdoc) == TYPE_FUNCTION) { + jsdoc_t *tmp = jsdoc_merge(decl_jsdoc, + (jsdoc_get_type(func_jsdoc) == TYPE_FUNCTION) ? func_jsdoc : NULL, + 0); + + jsdoc_free(func_jsdoc); + + uc_vector_push(&usp->function_jsdocs, tmp); + + return tmp; + } + else if (jsdoc_get_type(func_jsdoc) == TYPE_FUNCTION) { + uc_vector_push(&usp->function_jsdocs, func_jsdoc); + + return func_jsdoc; + } + else { + jsdoc_t *tmp = jsdoc_new(TYPE_FUNCTION); + + jsdoc_free(func_jsdoc); + + uc_vector_push(&usp->function_jsdocs, tmp); + + return tmp; + } +} + +static uc_value_t * +parse_funcexpr_common(uscope_parser_t *usp, bool require_name) +{ + size_t keyword_token_id = usp->prev_token_offset; + size_t declaration_token_id = keyword_token_id; + uc_value_t *arglist = ucv_array_new(usp->vm); + uc_value_t *name = NULL; + bool vararg = false; + size_t nargs = 0; + + if (token_match(usp, TK_LABEL)) { + name = ucv_get(usp->parser.prev.uv); + declaration_token_id = usp->prev_token_offset; + } + else if (require_name) { + parse_syntax_error(usp, usp->parser.curr.pos, + "Expecting function name"); + + return NULL; + } + + jsdoc_t *func_jsdoc = jsdoc_new_function(usp, keyword_token_id); + + scope_enter(usp); + + parse_consume(usp, TK_LPAREN); + + while (!parse_check(usp, TK_RPAREN)) { + size_t token_id = usp->prev_token_offset; + + vararg = token_match(usp, TK_ELLIP); + + if (token_match(usp, TK_LABEL)) { + token_set_sem_type(usp, TT_PARAMETER); + + jsdoc_param_t *param = + uscope_vector_get(&func_jsdoc->type->details.function.params, nargs); + + if (!param) + param = uc_vector_push(&func_jsdoc->type->details.function.params, { 0 }); + + jsdoc_t *param_jsdoc = jsdoc_capture(usp, token_id, NULL); + + param->name = ucv_get(usp->parser.prev.uv); + + jsdoc_typedef_merge(¶m->type, param_jsdoc->type, 0); + + if (param_jsdoc->description && !param->description) + param->description = ucv_get(param_jsdoc->description); + + jsdoc_free(param_jsdoc); + + uc_value_t *argvar = variable_new(usp, usp->prev_token_offset, + NULL, NULL, jsdoc_from_param(KIND_MEMBER, param), false, false); + + ucv_array_push(arglist, argvar); + + nargs++; + + if (vararg || !token_match(usp, TK_COMMA)) + break; + } + else { + parse_syntax_error(usp, usp->parser.curr.pos, + "Expecting Label"); + + goto out; + } + } + + parse_consume(usp, TK_RPAREN); + + if (token_match(usp, TK_COLON)) + parse_block(usp, TK_ENDFUNC); + else if (token_match(usp, TK_LBRACE)) + parse_block(usp, TK_RBRACE); + else + parse_syntax_error(usp, usp->parser.curr.pos, + "Expecting '{' or ':' after function parameters"); + +out: + scope_leave(usp); + + uc_value_t *initval = create_function(usp, name, false, vararg, arglist); + + uc_value_t *var = variable_new(usp, declaration_token_id, name, &initval, + func_jsdoc, false, false); + + ucv_put(initval); + + usp->function_jsdocs.count--; + + return var; +} + +static uc_value_t * +parse_funcexpr(uscope_parser_t *usp) +{ + return parse_funcexpr_common(usp, false); +} + +static uc_value_t * +parse_funcdecl(uscope_parser_t *usp) +{ + return parse_funcexpr_common(usp, true); +} + +static uc_tokentype_t +parse_altifblock(uscope_parser_t *usp) +{ + scope_enter(usp); + + while (true) { + switch (usp->parser.curr.type) { + case TK_ELIF: + case TK_ELSE: + case TK_ENDIF: + case TK_EOF: + scope_leave(usp); + + return usp->parser.curr.type; + + default: + parse_declaration(usp); + break; + } + } +} + +static uc_value_t * +parse_arrowfn(uscope_parser_t *usp) +{ + size_t declaration_token_id = usp->prev_token_offset; + uc_value_t *arglist = ucv_array_new(usp->vm); + bool vararg = false; + size_t nargs = 0; + + jsdoc_t *func_jsdoc = jsdoc_new_function(usp, declaration_token_id); + + scope_enter(usp); + + if (usp->parser.prev.type == TK_LPAREN) { + while (usp->parser.curr.type != TK_RPAREN) { + size_t jsdoc_token_id = usp->prev_token_offset; + + vararg = token_match(usp, TK_ELLIP); + + if (token_match(usp, TK_LABEL)) { + token_set_sem_type(usp, TT_PARAMETER); + + uc_value_t *argvar = variable_new(usp, usp->prev_token_offset, + NULL, NULL, + jsdoc_capture(usp, jsdoc_token_id, NULL), + false, false); + + ucv_array_push(arglist, argvar); + + nargs++; + + if (vararg || !token_match(usp, TK_COMMA)) + break; + } + else { + parse_syntax_error(usp, usp->parser.curr.pos, + "Expecting Label"); + + goto out; + } + } + + parse_consume(usp, TK_RPAREN); + } + else { + uc_value_t *argvar = variable_new(usp, usp->prev_token_offset, + NULL, NULL, NULL, false, false); + + ucv_array_push(arglist, argvar); + + nargs++; + } + + parse_consume(usp, TK_ARROW); + + if (token_match(usp, TK_LBRACE)) { + while (!parse_check(usp, TK_RBRACE) && + !parse_check(usp, TK_EOF)) { + + parse_declaration(usp); + } + + parse_consume(usp, TK_RBRACE); + } + else { + size_t jsdoc_token_id = usp->curr_token_offset; + uc_value_t *expr = parse_precedence(usp, P_ASSIGN); + + jsdoc_merge_return_type(usp, jsdoc_token_id, expr); + ucv_put(expr); + } + +out: + scope_leave(usp); + + uc_value_t *initval = create_function(usp, NULL, true, vararg, arglist); + + uc_value_t *var = variable_new(usp, declaration_token_id, NULL, &initval, + func_jsdoc, false, false); + + ucv_put(initval); + + usp->function_jsdocs.count--; + + return var; +} + +static uc_value_t * +parse_paren(uscope_parser_t *usp) +{ + uc_tokentype_t t = usp->parser.curr.type; + + while (t != TK_RPAREN) { + if (t == TK_LABEL) { + if (parse_peek(usp)->type == TK_COMMA) { + t = parse_peek(usp)->type; + continue; + } + + t = uc_vector_last(&usp->lookahead)->token.type; + } + + if (t == TK_ELLIP) { + if (parse_peek(usp)->type == TK_LABEL && + parse_peek(usp)->type == TK_COMMA) { + t = parse_peek(usp)->type; + continue; + } + + t = uc_vector_last(&usp->lookahead)->token.type; + } + + break; + } + + if (t == TK_RPAREN && parse_peek(usp)->type == TK_ARROW) + return parse_arrowfn(usp); + + uc_value_t *result = NULL; + + if (!parse_check(usp, TK_RPAREN)) + result = parse_expression(usp); + + usp->parser.lex.no_regexp = true; + parse_consume(usp, TK_RPAREN); + + return result; +} + +static uc_value_t * +parse_labelexpr(uscope_parser_t *usp) +{ + if (usp->parser.curr.type == TK_ARROW) + return parse_arrowfn(usp); + + jsdoc_t *jsdoc = jsdoc_capture(usp, usp->prev_token_offset, NULL); + + if (parse_is_at_assignment(usp)) + return parse_assignment(usp, + variable_access(usp, NULL, ACCESS_READ), jsdoc); + + return variable_access(usp, jsdoc, ACCESS_READ); +} + +static uc_value_t *parse_statement(uscope_parser_t *usp); + +static uc_value_t * +parse_if(uscope_parser_t *usp) +{ + parse_consume(usp, TK_LPAREN); + ucv_put(parse_expression(usp)); + parse_consume(usp, TK_RPAREN); + + if (token_match(usp, TK_COLON)) { + //usp->parser.exprstack->flags |= F_ALTBLOCKMODE; + + parse_altifblock(usp); + + while (true) { + uc_tokentype_t block_type = parse_altifblock(usp); + + if (block_type == TK_ELIF) { + parse_advance(usp); + parse_consume(usp, TK_LPAREN); + ucv_put(parse_expression(usp)); + parse_consume(usp, TK_RPAREN); + parse_consume(usp, TK_COLON); + } + else if (block_type == TK_ELSE) { + parse_advance(usp); + } + else if (block_type == TK_ENDIF) { + parse_advance(usp); + break; + } + else { + parse_syntax_error(usp, usp->parser.curr.pos, + "Expecting 'elif', 'else' or 'endif'"); + break; + } + } + } + else { + ucv_put(parse_statement(usp)); + + if (token_match(usp, TK_ELSE)) + ucv_put(parse_statement(usp)); + } + + return NULL; +} + +static uc_value_t * +parse_while(uscope_parser_t *usp) +{ + parse_consume(usp, TK_LPAREN); + ucv_put(parse_expression(usp)); + parse_consume(usp, TK_RPAREN); + + if (token_match(usp, TK_COLON)) { + scope_enter(usp); + + ucv_put(parse_block(usp, TK_ENDWHILE)); + + scope_leave(usp); + } + else { + ucv_put(parse_statement(usp)); + } + + return NULL; +} + +static uc_value_t * +parse_try(uscope_parser_t *usp) +{ + // Try block + scope_enter(usp); + parse_consume(usp, TK_LBRACE); + ucv_put(parse_block(usp, TK_RBRACE)); + scope_leave(usp); + + // Catch block + parse_consume(usp, TK_CATCH); + scope_enter(usp); + + // Exception variable (optional) + if (token_match(usp, TK_LPAREN)) { + parse_consume(usp, TK_LABEL); + + uc_value_t *initval = ucv_object_new(usp->vm); + + ucv_put(variable_new(usp, + usp->prev_token_offset, NULL, &initval, NULL, false, false)); + + ucv_put(initval); + + parse_consume(usp, TK_RPAREN); + } + + parse_consume(usp, TK_LBRACE); + ucv_put(parse_block(usp, TK_RBRACE)); + + scope_leave(usp); + + return NULL; +} + +static uc_value_t * +parse_switch(uscope_parser_t *usp) +{ + scope_enter(usp); + + parse_consume(usp, TK_LPAREN); + ucv_put(parse_expression(usp)); + parse_consume(usp, TK_RPAREN); + + parse_consume(usp, TK_LBRACE); + + while (!parse_check(usp, TK_RBRACE) && + !parse_check(usp, TK_EOF)) { + if (token_match(usp, TK_CASE)) { + ucv_put(parse_expression(usp)); + } + else if (!token_match(usp, TK_DEFAULT)) { + parse_syntax_error(usp, usp->parser.curr.pos, "Expecting 'case' or 'default'"); + return NULL; + } + + parse_consume(usp, TK_COLON); + + while (!parse_check(usp, TK_CASE) && + !parse_check(usp, TK_DEFAULT) && + !parse_check(usp, TK_RBRACE) && + !parse_check(usp, TK_EOF)) { + + parse_declaration(usp); + } + } + + parse_consume(usp, TK_RBRACE); + + scope_leave(usp); + + return NULL; +} + +static uc_value_t * +parse_for_in(uscope_parser_t *usp, uc_tokentype_t kind, size_t off) +{ + jsdoc_t *doc1 = jsdoc_capture(usp, usp->prev_token_offset, NULL); + uc_value_t *kv = NULL, *vv = NULL; + + scope_enter(usp); + + if (kind == TK_LOCAL) { + parse_consume(usp, TK_LOCAL); + + jsdoc_t *doc2 = jsdoc_capture(usp, usp->prev_token_offset, NULL); + + parse_consume(usp, TK_LABEL); + + kv = variable_new(usp, usp->prev_token_offset, NULL, NULL, + jsdoc_merge(doc1, doc2, 0), false, false); + + jsdoc_free(doc2); + + if (token_match(usp, TK_COMMA)) { + doc2 = jsdoc_capture(usp, usp->prev_token_offset, NULL); + + parse_consume(usp, TK_LABEL); + + vv = variable_new(usp, usp->prev_token_offset, NULL, NULL, + jsdoc_merge(doc1, doc2, 0), false, false); + + jsdoc_free(doc2); + } + } + else if (kind == TK_COMMA) { + parse_consume(usp, TK_LABEL); + + kv = variable_access(usp, jsdoc_merge(doc1, NULL, 0), ACCESS_UPDATE); + + parse_consume(usp, TK_COMMA); + + jsdoc_t *doc2 = jsdoc_capture(usp, usp->prev_token_offset, NULL); + + parse_consume(usp, TK_LABEL); + + vv = variable_access(usp, jsdoc_merge(doc1, doc2, 0), ACCESS_UPDATE); + + jsdoc_free(doc2); + } + else { + parse_consume(usp, TK_LABEL); + + kv = variable_access(usp, jsdoc_merge(doc1, NULL, 0), ACCESS_UPDATE); + } + + jsdoc_free(doc1); + + parse_consume(usp, TK_IN); + + uc_value_t *iterable = parse_expression(usp); + uscope_variable_t *iterspec = ucv_resource_data(iterable, "uscope.variable"); + + if (iterspec) { + uscope_variable_t *kvspec = ucv_resource_data(kv, "uscope.variable"); + + if (iterspec->type == UC_OBJECT) + kvspec->type = UC_STRING; + else if (iterspec->type == UC_ARRAY && vv != NULL) + kvspec->type = UC_INTEGER; + } + + ucv_put(iterable); + ucv_put(kv); + ucv_put(vv); + + parse_consume(usp, TK_RPAREN); + + if (token_match(usp, TK_COLON)) { + scope_enter(usp); + ucv_put(parse_block(usp, TK_ENDFOR)); + scope_leave(usp); + } + else { + ucv_put(parse_statement(usp)); + } + + + scope_leave(usp); + + return NULL; +} + +static uc_value_t * +parse_for_count(uscope_parser_t *usp, size_t off) +{ + scope_enter(usp); + + // Initializer + if (token_match(usp, TK_LOCAL)) + ucv_put(parse_declexpr(usp, false)); + else if (!parse_check(usp, TK_SCOL)) + ucv_put(parse_expression(usp)); + + parse_consume(usp, TK_SCOL); + + // Condition + if (!parse_check(usp, TK_SCOL)) + ucv_put(parse_expression(usp)); + + parse_consume(usp, TK_SCOL); + + // Increment + if (!parse_check(usp, TK_RPAREN)) + ucv_put(parse_expression(usp)); + + parse_consume(usp, TK_RPAREN); + + // Body + if (token_match(usp, TK_COLON)) { + scope_enter(usp); + + ucv_put(parse_block(usp, TK_ENDFOR)); + parse_consume(usp, TK_ENDFOR); + + scope_leave(usp); + } + else { + ucv_put(parse_statement(usp)); + } + + scope_leave(usp); + + return NULL; +} + +static uc_value_t * +parse_for(uscope_parser_t *usp) +{ + uc_tokentype_t tokens[5] = { 0 }; + size_t off = usp->parser.prev.pos; + + tokens[0] = parse_peek(usp)->type; + tokens[1] = parse_peek(usp)->type; + tokens[2] = parse_peek(usp)->type; + tokens[3] = parse_peek(usp)->type; + tokens[4] = parse_peek(usp)->type; + + parse_consume(usp, TK_LPAREN); + +#define compare(count, ...) \ + !memcmp(tokens, \ + (uc_tokentype_t[count]){ __VA_ARGS__ }, \ + count * sizeof(uc_tokentype_t)) + + if (compare(5, TK_LOCAL, TK_LABEL, TK_COMMA, TK_LABEL, TK_IN) || + compare(3, TK_LOCAL, TK_LABEL, TK_IN)) + return parse_for_in(usp, TK_LOCAL, off); + + if (compare(4, TK_LABEL, TK_COMMA, TK_LABEL, TK_IN) || + compare(2, TK_LABEL, TK_IN)) + return parse_for_in(usp, tokens[1], off); + +#undef compare + + return parse_for_count(usp, off); +} + +static uc_value_t * +parse_statement(uscope_parser_t *usp) +{ + uc_value_t *result; + + if (token_match(usp, TK_IF)) + result = parse_if(usp); + else if (token_match(usp, TK_WHILE)) + result = parse_while(usp); + else if (token_match(usp, TK_FOR)) + result = parse_for(usp); + else if (token_match(usp, TK_SWITCH)) + result = parse_switch(usp); + else if (token_match(usp, TK_TRY)) + result = parse_try(usp); + else if (token_match(usp, TK_FUNC)) + result = parse_funcdecl(usp); + else if (token_match(usp, TK_BREAK)) + result = parse_control(usp); + else if (token_match(usp, TK_CONTINUE)) + result = parse_control(usp); + else if (token_match(usp, TK_RETURN)) + result = parse_return(usp); + else if (token_match(usp, TK_TEXT)) + result = parse_text(usp); + else if (token_match(usp, TK_LEXP)) + result = parse_tplexp(usp); + else if (token_match(usp, TK_LBRACE)) + result = parse_block(usp, TK_RBRACE); + else + result = parse_expstmt(usp); + + ucv_put(result); + + return NULL; +} + +static uc_value_t * +parse_importlist(uscope_parser_t *usp) +{ + do { + jsdoc_t *jsdoc = jsdoc_capture(usp, usp->prev_token_offset, NULL); + xport_item_t *import = uc_vector_push(&usp->imports, { 0 }); + + if (token_match(usp, TK_DEFAULT)) { + keyword_consume(usp, "as"); + token_set_sem_type(usp, TT_KW_OPERATOR); + parse_consume(usp, TK_LABEL); + + import->is_default = true; + import->alias = ucv_get(usp->parser.prev.uv); + import->value = variable_new(usp, usp->prev_token_offset, + ucv_get(import->alias), NULL, jsdoc, true, true); // FIXME: load actual import + + //usp->imports.count++; + } + else if (token_match(usp, TK_STRING)) { + import->name = ucv_get(usp->parser.prev.uv); + + keyword_consume(usp, "as"); + token_set_sem_type(usp, TT_KW_OPERATOR); + parse_consume(usp, TK_LABEL); + + import->alias = ucv_get(usp->parser.prev.uv); + import->value = variable_new(usp, usp->prev_token_offset, + ucv_get(import->alias), NULL, jsdoc, true, true); // FIXME: load actual import + + //usp->imports.count++; + } + else if (token_match(usp, TK_LABEL)) { + import->name = ucv_get(usp->parser.prev.uv); + + if (keyword_match(usp, "as")) { + token_set_sem_type(usp, TT_KW_OPERATOR); + parse_consume(usp, TK_LABEL); + + import->alias = ucv_get(usp->parser.prev.uv); + } + else { + import->alias = ucv_get(import->name); + } + + import->value = variable_new(usp, usp->prev_token_offset, + ucv_get(import->alias), NULL, jsdoc, true, true); // FIXME: load actual import + + //usp->imports.count++; + } + else { + jsdoc_free(jsdoc); + + parse_syntax_error(usp, usp->parser.curr.pos, + "Unexpected token\nExpecting Label, String or 'default'"); + + break; + } + + if (token_match(usp, TK_RBRACE)) + break; + + } while (token_match(usp, TK_COMMA)); + + return NULL; +} + +static uc_value_t * +parse_import(uscope_parser_t *usp) +{ + size_t jsdoc_token_id = usp->prev_token_offset; + size_t off = usp->imports.count; + + if (token_match(usp, TK_LBRACE)) { + ucv_put(parse_importlist(usp)); + keyword_consume(usp, "from"); + token_set_sem_type(usp, TT_KW_OPERATOR); + } + else if (token_match(usp, TK_MUL)) { + //import->value.import_stmt.is_wildcard = true; + keyword_consume(usp, "as"); + token_set_sem_type(usp, TT_KW_OPERATOR); + parse_consume(usp, TK_LABEL); + + xport_item_t *import = uc_vector_push(&usp->imports, { 0 }); + + import->is_wildcard = true; + import->alias = ucv_get(usp->parser.prev.uv); + + // FIXME: load actual import + import->value = variable_new(usp, usp->prev_token_offset, + ucv_get(import->alias), NULL, + jsdoc_capture(usp, jsdoc_token_id, NULL), true, true); + + //usp->imports.count++; + + keyword_consume(usp, "from"); + token_set_sem_type(usp, TT_KW_OPERATOR); + } + else if (token_match(usp, TK_LABEL)) { + xport_item_t *default_import = uc_vector_push(&usp->imports, { 0 }); + + default_import->is_default = true; + default_import->alias = ucv_get(usp->parser.prev.uv); + + // FIXME: load actual import + default_import->value = variable_new(usp, usp->prev_token_offset, + NULL, NULL, jsdoc_capture(usp, jsdoc_token_id, NULL), true, true); + + //usp->imports.count++; + + if (token_match(usp, TK_COMMA)) { + jsdoc_token_id = usp->prev_token_offset; + + if (token_match(usp, TK_LBRACE)) { + ucv_put(parse_importlist(usp)); + } + else if (token_match(usp, TK_MUL)) { + xport_item_t *wildcard_import = uc_vector_push(&usp->imports, { 0 }); + + keyword_consume(usp, "as"); + token_set_sem_type(usp, TT_KW_OPERATOR); + parse_consume(usp, TK_LABEL); + + wildcard_import->is_wildcard = true; + wildcard_import->alias = ucv_get(usp->parser.prev.uv); + + // FIXME: load actual import + wildcard_import->value = variable_new(usp, usp->prev_token_offset, + ucv_get(wildcard_import->alias), NULL, + jsdoc_capture(usp, jsdoc_token_id, NULL), true, true); + + //usp->imports.count++; + } + else { + parse_syntax_error(usp, usp->parser.curr.pos, + "Unexpected token\nExpecting '{' or '*'"); + } + } + + keyword_consume(usp, "from"); + token_set_sem_type(usp, TT_KW_OPERATOR); + } + + parse_consume(usp, TK_STRING); + + while (off < usp->imports.count) + usp->imports.entries[off++].source = ucv_get(usp->parser.prev.uv); + + parse_consume(usp, TK_SCOL); + + return NULL; +} + +static xport_item_t * +add_export(uscope_parser_t *usp, bool is_default, uc_value_t *value) +{ + uc_vector_grow(&usp->exports); + + xport_item_t *export = &usp->exports.entries[usp->exports.count++]; + uscope_variable_t *var = ucv_resource_data(value, "uscope.variable"); + + export->is_default = is_default; + export->value = value; + + if (var) + export->name = ucv_get(var->name); + + return export; +} + +static uc_value_t * +parse_exportlist(uscope_parser_t *usp) +{ + do { + jsdoc_t *jsdoc = jsdoc_capture(usp, usp->prev_token_offset, NULL); + + parse_consume(usp, TK_LABEL); + + xport_item_t *export = add_export(usp, false, + variable_access(usp, jsdoc, ACCESS_EXPORT)); + + if (keyword_match(usp, "as")) { + token_set_sem_type(usp, TT_KW_OPERATOR); + + if (token_match(usp, TK_LABEL) || token_match(usp, TK_STRING)) { + export->alias = ucv_get(usp->parser.prev.uv); + } + else if (token_match(usp, TK_DEFAULT)) { + token_set_sem_type(usp, TT_KW_OPERATOR); + export->is_default = true; + } + else { + parse_syntax_error(usp, usp->parser.curr.pos, + "Unexpected token\nExpecting Label, String or 'default'"); + break; + } + } + + if (token_match(usp, TK_RBRACE)) + break; + + } while (token_match(usp, TK_COMMA)); + + parse_consume(usp, TK_SCOL); + + return NULL; +} + +static uc_value_t * +parse_export(uscope_parser_t *usp) +{ + if (token_match(usp, TK_LBRACE)) { + ucv_put(parse_exportlist(usp)); + + return NULL; + } + + if (token_match(usp, TK_LOCAL)) { + uc_value_t *decls = parse_declexpr(usp, false); + + for (size_t i = 0; i < ucv_array_length(decls); i++) + add_export(usp, false, ucv_get(ucv_array_get(decls, i))); + + ucv_put(decls); + } + else if (token_match(usp, TK_CONST)) { + uc_value_t *decls = parse_declexpr(usp, true); + + for (size_t i = 0; i < ucv_array_length(decls); i++) + add_export(usp, false, ucv_get(ucv_array_get(decls, i))); + + ucv_put(decls); + } + else if (token_match(usp, TK_FUNC)) { + add_export(usp, false, parse_funcdecl(usp)); + } + else if (token_match(usp, TK_DEFAULT)) { + add_export(usp, true, parse_expression(usp)); + } + else { + parse_syntax_error(usp, usp->parser.curr.pos, + "Unexpected token\nExpecting 'let', 'const', 'function', 'default' or '{'"); + + return NULL; + } + + parse_consume(usp, TK_SCOL); + + return NULL; +} + +static void +parse_source(uscope_parser_t *usp, uc_source_t *source) +{ + uc_lexer_init(&usp->parser.lex, usp->parser.config, source); + parse_advance(usp); + + scope_enter(usp); + + while (!token_match(usp, TK_EOF)) + ucv_put(parse_declaration(usp)); + + scope_leave(usp); + + uc_lexer_free(&usp->parser.lex); + + uc_vector_clear(&usp->lookahead); + + ucv_put(usp->parser.prev.uv); + ucv_put(usp->parser.curr.uv); +} + + +// Updated parse_constant function +static uc_value_t * +parse_constant(uscope_parser_t *usp) +{ + switch (usp->parser.prev.type) { + case TK_THIS: + return usp->thischain.count + ? ucv_get(usp->thischain.entries[usp->thischain.count - 1]) + : undef_new(usp, usp->prev_token_offset, NULL); + + case TK_NULL: + return NULL; + + case TK_TRUE: + return ucv_boolean_new(true); + + case TK_FALSE: + return ucv_boolean_new(false); + + case TK_STRING: + case TK_TEMPLATE: + case TK_DOUBLE: + case TK_NUMBER: + case TK_REGEXP: + return ucv_get(usp->parser.prev.uv); + + default: + break; + } + + return NULL; +} + +// Implement parsing functions that return AST nodes instead of emitting bytecode +static uc_value_t * +parse_declaration(uscope_parser_t *usp) +{ + uc_value_t *result; + + if (token_match(usp, TK_LOCAL)) + result = parse_local(usp); + else if (token_match(usp, TK_CONST)) + result = parse_const(usp); + else if (token_match(usp, TK_EXPORT)) + result = parse_export(usp); + else if (token_match(usp, TK_IMPORT)) + result = parse_import(usp); + else + result = parse_statement(usp); + + ucv_put(result); + + return NULL; +} + +// Implement more parsing functions for different constructs + +struct keyword { + unsigned type; + const char *pat; + unsigned plen; +}; + + +static const struct keyword reserved_words[] = { + { TK_ENDFUNC, "endfunction", 11 }, + { TK_CONTINUE, "continue", 8 }, + { TK_ENDWHILE, "endwhile", 8 }, + { TK_FUNC, "function", 8 }, + { TK_DEFAULT, "default", 7 }, + { TK_DELETE, "delete", 6 }, + { TK_RETURN, "return", 6 }, + { TK_ENDFOR, "endfor", 6 }, + { TK_SWITCH, "switch", 6 }, + { TK_IMPORT, "import", 6 }, + { TK_EXPORT, "export", 6 }, + { TK_ENDIF, "endif", 5 }, + { TK_WHILE, "while", 5 }, + { TK_BREAK, "break", 5 }, + { TK_CATCH, "catch", 5 }, + { TK_CONST, "const", 5 }, + { TK_FALSE, "false", 5 }, + { TK_TRUE, "true", 4 }, + { TK_ELIF, "elif", 4 }, + { TK_ELSE, "else", 4 }, + { TK_THIS, "this", 4 }, + { TK_NULL, "null", 4 }, + { TK_CASE, "case", 4 }, + { TK_TRY, "try", 3 }, + { TK_FOR, "for", 3 }, + { TK_LOCAL, "let", 3 }, + { TK_IF, "if", 2 }, + { TK_IN, "in", 2 }, +}; + +const char * +uc_tokenname(unsigned type) +{ + static char buf[sizeof("'endfunction'")]; + const char *tokennames[] = { + [TK_LEXP] = "{{", + [TK_REXP] = "}}", + [TK_LSTM] = "{%", + [TK_RSTM] = "%}", + [TK_COMMA] = ",", + [TK_ASSIGN] = "=", + [TK_ASADD] = "+=", + [TK_ASSUB] = "-=", + [TK_ASMUL] = "*=", + [TK_ASDIV] = "/=", + [TK_ASMOD] = "%=", + [TK_ASLEFT] = "<<=", + [TK_ASRIGHT] = ">>=", + [TK_ASBAND] = "&=", + [TK_ASBXOR] = "^=", + [TK_ASBOR] = "|=", + [TK_QMARK] = "?", + [TK_COLON] = ":", + [TK_OR] = "||", + [TK_AND] = "&&", + [TK_BOR] = "|", + [TK_BXOR] = "^", + [TK_BAND] = "&", + [TK_EQS] = "===", + [TK_NES] = "!==", + [TK_EQ] = "==", + [TK_NE] = "!=", + [TK_LT] = "<", + [TK_LE] = "<=", + [TK_GT] = ">", + [TK_GE] = ">=", + [TK_LSHIFT] = "<<", + [TK_RSHIFT] = ">>", + [TK_ADD] = "+", + [TK_SUB] = "-", + [TK_MUL] = "*", + [TK_DIV] = "/", + [TK_MOD] = "%", + [TK_EXP] = "**", + [TK_NOT] = "!", + [TK_COMPL] = "~", + [TK_INC] = "++", + [TK_DEC] = "--", + [TK_DOT] = ".", + [TK_LBRACK] = "[", + [TK_RBRACK] = "]", + [TK_LPAREN] = "(", + [TK_RPAREN] = ")", + [TK_LBRACE] = "{", + [TK_RBRACE] = "}", + [TK_SCOL] = ";", + [TK_ELLIP] = "...", + [TK_ARROW] = "=>", + [TK_QLBRACK] = "?.[", + [TK_QLPAREN] = "?.(", + [TK_QDOT] = "?.", + [TK_ASEXP] = "**=", + [TK_ASAND] = "&&=", + [TK_ASOR] = "||=", + [TK_ASNULLISH] = "\?\?=", + [TK_NULLISH] = "\?\?", + [TK_PLACEH] = "${", + + [TK_TEXT] = "Text", + [TK_LABEL] = "Label", + [TK_NUMBER] = "Number", + [TK_DOUBLE] = "Double", + [TK_STRING] = "String", + [TK_REGEXP] = "Regexp", + [TK_TEMPLATE] = "Template", + [TK_ERROR] = "Error", + [TK_EOF] = "End of file", + }; + + size_t i; + + for (i = 0; i < ARRAY_SIZE(reserved_words); i++) { + if (reserved_words[i].type != type) + continue; + + snprintf(buf, sizeof(buf), "'%s'", reserved_words[i].pat); + + return buf; + } + + return tokennames[type] ? tokennames[type] : "?"; +} + +static uc_value_t * +position_to_uv(uc_vm_t *vm, uscope_position_t *pos) +{ + uc_value_t *rv = ucv_object_new(vm); + + ucv_object_add(rv, "offset", ucv_uint64_new(pos->offset)); + ucv_object_add(rv, "character", ucv_uint64_new(pos->column ? pos->column - 1 : 0)); + ucv_object_add(rv, "line", ucv_uint64_new(pos->line ? pos->line - 1 : 0)); + + return rv; +} + +static void +convert_parse_config(uc_parse_config_t *config, uc_value_t *spec) +{ + uc_value_t *v, *p; + size_t i, j; + bool found; + + struct { + const char *key; + bool *flag; + uc_search_path_t *path; + } fields[] = { + { "lstrip_blocks", &config->lstrip_blocks, NULL }, + { "trim_blocks", &config->trim_blocks, NULL }, + { "strict_declarations", &config->strict_declarations, NULL }, + { "raw_mode", &config->raw_mode, NULL }, + { "module_search_path", NULL, &config->module_search_path }, + { "force_dynlink_list", NULL, &config->force_dynlink_list } + }; + + for (i = 0; i < ARRAY_SIZE(fields); i++) { + v = ucv_object_get(spec, fields[i].key, &found); + + if (!found) + continue; + + if (fields[i].flag) { + *fields[i].flag = ucv_is_truish(v); + } + else if (fields[i].path) { + fields[i].path->count = 0; + fields[i].path->entries = NULL; + + for (j = 0; j < ucv_array_length(v); j++) { + p = ucv_array_get(v, j); + + if (ucv_type(p) != UC_STRING) + continue; + + uc_vector_push(fields[i].path, ucv_string_get(p)); + } + } + } +} + +static void +parse_typedef(uc_vm_t *vm, uc_value_t *typedefs, uc_token_t *tok) +{ + if (tok->type != TK_COMMENT || ucv_type(tok->uv) != UC_STRING) + return; + + size_t len = ucv_string_length(tok->uv); + char *str = ucv_string_get(tok->uv); + + if (len < 3 || strncmp(str, "/**", 3) != 0 || !strstr(str, "typedef")) + return; + + jsdoc_t jsdoc = { 0 }; + + jsdoc_parse(str, len, &jsdoc); + + if (jsdoc.kind == KIND_TYPEDEF && jsdoc.name) + ucv_object_add(typedefs, + ucv_string_get(jsdoc.name), + jsdoc_to_uv(vm, &jsdoc)); + + jsdoc_reset(&jsdoc); +} + +static uc_value_t * +lookup_var_spec(uc_value_t *scopes, uc_value_t *uvar) +{ + for (size_t i = ucv_array_length(scopes); i > 0; i--) { + uc_value_t *vars = ucv_object_get( + ucv_array_get(scopes, i - 1), "variables", NULL); + + for (size_t j = ucv_array_length(vars); j > 0; j--) { + uc_value_t *spec = ucv_array_get(vars, j - 1); + uc_value_t *other = ucv_object_get(spec, "variable", NULL); + + if (uvar == other) + return spec; + } + } + + return NULL; +} + +static uc_value_t * +uc_analyze(uc_vm_t *vm, size_t nargs) +{ + uc_value_t *code = uc_fn_arg(0); + uc_value_t *spec = uc_fn_arg(1); + uc_value_t *global = uc_fn_arg(1); + uc_value_t *rv = NULL; + uc_source_t *source = NULL; + size_t len; + char *s; + + if (ucv_type(code) == UC_STRING) { + len = ucv_string_length(code); + s = xalloc(len); + memcpy(s, ucv_string_get(code), len); + } + else { + s = ucv_to_string(vm, code); + len = strlen(s); + } + + if (len >= 4 && *(uint32_t *)s == htobe32(UC_PRECOMPILED_BYTECODE_MAGIC)) { + uc_vm_raise_exception(vm, EXCEPTION_TYPE, + "Cannot parse precompiled source files"); + + free(s); + + return NULL; + } + + source = uc_source_new_buffer("[code argument]", s, len); + + uc_parse_config_t conf = *vm->config; + + convert_parse_config(&conf, spec); + + uscope_parser_t usp = { + .vm = vm, + .parser = { .config = &conf }, + .global = global ? global : uc_vm_scope_get(vm) + }; + + parse_source(&usp, source); + + if (!vm->config || conf.module_search_path.entries != vm->config->module_search_path.entries) + uc_vector_clear(&conf.module_search_path); + + if (!vm->config || conf.force_dynlink_list.entries != vm->config->force_dynlink_list.entries) + uc_vector_clear(&conf.force_dynlink_list); + + rv = ucv_object_new(vm); + + /* apply token modifiers */ + uc_vector_foreach(&usp.scopes, scope) { + uc_vector_foreach(&scope->variables, uv) { + uscope_variable_t *var = ucv_resource_data(*uv, "uscope.variable"); + + uc_vector_foreach(&var->references, ref) { + semantic_token_t *stok = &usp.tokens.entries[ref->token_id]; + + if (jsdoc_get_type(var->jsdoc) == TYPE_FUNCTION) + stok->sem_type = TT_FUNCTION; + else if (var->property) + stok->sem_type = TT_PROPERTY; + + if (var->constant) + stok->sem_modifiers |= (1u << TM_READONLY); + } + } + } + + /* build token array and jsdoc typedef arrays */ + uc_value_t *tokens = ucv_array_new(vm); + uc_value_t *typedefs = ucv_object_new(vm); + + for (size_t i = 0; i <= usp.curr_token_offset; i++) { + uscope_position_t start, end; + + start.offset = usp.tokens.entries[i].token.pos; + start.column = start.offset; + start.line = uc_source_get_line(source, &start.column); + + end.offset = usp.tokens.entries[i].token.end; + end.column = end.offset; + end.line = uc_source_get_line(source, &end.column); + + uc_value_t *entry = ucv_object_new(vm); + ucv_object_add(entry, "start", position_to_uv(vm, &start)); + ucv_object_add(entry, "end", position_to_uv(vm, &end)); + ucv_object_add(entry, "type", ucv_uint64_new(usp.tokens.entries[i].token.type)); + ucv_object_add(entry, "value", usp.tokens.entries[i].token.uv); + ucv_object_add(entry, "semanticType", ucv_uint64_new(usp.tokens.entries[i].sem_type)); + ucv_object_add(entry, "semanticModifiers", ucv_uint64_new(usp.tokens.entries[i].sem_modifiers)); + + ucv_array_push(tokens, entry); + + parse_typedef(vm, typedefs, &usp.tokens.entries[i].token); + } + + ucv_object_add(rv, "tokens", tokens); + ucv_object_add(rv, "typedefs", typedefs); + + uc_source_put(source); + + /* build parse error array */ + uc_value_t *errors = ucv_array_new_length(vm, usp.errors.count); + + for (size_t i = 0; i < usp.errors.count; i++) { + uscope_position_t *sp = &usp.errors.entries[i].start; + uscope_position_t *ep = &usp.errors.entries[i].end; + + uc_value_t *entry = ucv_object_new(vm); + ucv_object_add(entry, "start", position_to_uv(vm, sp)); + ucv_object_add(entry, "end", position_to_uv(vm, ep)); + ucv_object_add(entry, "message", usp.errors.entries[i].message); + + ucv_array_push(errors, entry); + } + + ucv_object_add(rv, "errors", errors); + + /* build scope array */ + uc_value_t *scopes = ucv_array_new_length(vm, usp.scopes.count); + + for (size_t i = 0; i < usp.scopes.count; i++) { + scope_t *scope = &usp.scopes.entries[i]; + + uscope_position_t *sp = &scope->start; + uscope_position_t *ep = &scope->end; + + uc_value_t *so = ucv_object_new(vm); + uc_value_t *vars = ucv_array_new_length(vm, scope->variables.count); + + ucv_object_add(so, "start", position_to_uv(vm, sp)); + ucv_object_add(so, "end", position_to_uv(vm, ep)); + ucv_object_add(so, "variables", vars); + + ucv_array_push(scopes, so); + + for (size_t j = 0; j < scope->variables.count; j++) { + uc_value_t *var = scope->variables.entries[j]; + uscope_variable_t *varspec = ucv_resource_data(var, "uscope.variable"); + + uc_value_t *vo = ucv_object_new(vm); + uc_value_t *refs = ucv_array_new_length(vm, varspec->references.count); + + for (size_t k = 0; k < varspec->references.count; k++) { + uscope_reference_t *ref = &varspec->references.entries[k]; + uscope_position_t *loc = &ref->location; + + uc_value_t *ro = ucv_object_new(vm); + + ucv_object_add(ro, "location", position_to_uv(vm, loc)); + ucv_object_add(ro, "token", ucv_get(ucv_array_get(tokens, ref->token_id))); + ucv_object_add(ro, "access", ucv_string_new(access_kind_names[ref->access_kind])); + ucv_object_add(ro, "value", ucv_get(ref->value)); + + ucv_array_push(refs, ro); + } + + uc_value_t *range = ucv_object_new(vm); + ucv_object_add(range, "start", position_to_uv(vm, &varspec->range.start)); + ucv_object_add(range, "end", position_to_uv(vm, &varspec->range.end)); + + uc_value_t tuv = { .type = varspec->type }; + ucv_object_add(vo, "name", ucv_get(varspec->name)); + ucv_object_add(vo, "variable", ucv_get(var)); + + if (varspec->base) + ucv_object_add(vo, "base", + ucv_get(lookup_var_spec(scopes, varspec->base))); + + ucv_object_add(vo, "value", ucv_get(varspec->value)); + ucv_object_add(vo, "jsdoc", jsdoc_to_uv(vm, varspec->jsdoc)); + ucv_object_add(vo, "type", ucv_string_new(ucv_typename(&tuv))); + ucv_object_add(vo, "initialized", ucv_boolean_new(varspec->initialized)); + ucv_object_add(vo, "constant", ucv_boolean_new(varspec->constant)); + ucv_object_add(vo, "export", ucv_boolean_new(varspec->export)); + ucv_object_add(vo, "property", ucv_boolean_new(varspec->property)); + ucv_object_add(vo, "references", refs); + ucv_object_add(vo, "range", range); + + ucv_array_push(vars, vo); + ucv_put(var); + } + + uc_vector_clear(&scope->variables); + } + + ucv_object_add(rv, "scopes", scopes); + + uc_value_t *imports = ucv_array_new_length(vm, usp.imports.count); + + uc_vector_foreach(&usp.imports, import) { + uc_value_t *io = ucv_object_new(vm); + + ucv_object_add(io, "default", ucv_boolean_new(import->is_default)); + ucv_object_add(io, "wildcard", ucv_boolean_new(import->is_wildcard)); + ucv_object_add(io, "name", import->name); + ucv_object_add(io, "alias", import->alias); + ucv_object_add(io, "source", import->source); + ucv_object_add(io, "value", import->value); + + ucv_array_push(imports, io); + } + + ucv_object_add(rv, "imports", imports); + + uc_value_t *exports = ucv_array_new_length(vm, usp.exports.count); + + uc_vector_foreach(&usp.exports, export) { + uc_value_t *eo = ucv_object_new(vm); + + ucv_object_add(eo, "default", ucv_boolean_new(export->is_default)); + ucv_object_add(eo, "name", export->name); + ucv_object_add(eo, "alias", export->alias); + ucv_object_add(eo, "value", export->value); + + ucv_array_push(exports, eo); + } + + ucv_object_add(rv, "exports", exports); + + uc_vector_clear(&usp.errors); + uc_vector_clear(&usp.tokens); + uc_vector_clear(&usp.scopes); + uc_vector_clear(&usp.imports); + uc_vector_clear(&usp.exports); + uc_vector_clear(&usp.scopechain); + uc_vector_clear(&usp.thischain); + uc_vector_clear(&usp.function_jsdocs); + uc_vector_clear(&usp.declaration_jsdocs); + + return rv; +} + +static const uc_function_list_t uscope_fns[] = { + { "analyze", uc_analyze }, +}; + + +static uc_value_t * +uc_var_tostring(uc_vm_t *vm, size_t nargs) +{ + uscope_variable_t *var = uc_fn_thisval("uscope.variable"); + uscope_position_t *pos = &var->references.entries[0].location; + uc_stringbuf_t *sbuf = ucv_stringbuf_new(); + + if (var->property) { + ucv_stringbuf_printf(sbuf, "<%sproperty reference ", + var->initialized ? "" : "uninitialized "); + + ucv_stringbuf_append(sbuf, "<"); + ucv_to_stringbuf(vm, sbuf, var->name, false); + ucv_stringbuf_printf(sbuf, "> @ %zu:%zu>", + pos ? pos->line : 0, pos ? pos->column : 0); + } + else { + ucv_stringbuf_printf(sbuf, "<%s%s %s '%s' @ %zu:%zu>", + var->initialized ? "" : "uninitialized ", + var->constant ? "const" : "local", + var->export ? "export" : "variable", + ucv_string_get(var->name), + pos ? pos->line : 0, pos ? pos->column : 0); + } + + return ucv_stringbuf_finish(sbuf); +} + +static void +close_var(void *ud) +{ + uscope_variable_t *var = ud; + + while (var->references.count > 0) + ucv_put(var->references.entries[--var->references.count].value); + + uc_vector_clear(&var->references); + + ucv_put(var->base); + ucv_put(var->name); + ucv_put(var->value); + + jsdoc_free(var->jsdoc); + + free(var); +} + +static const uc_function_list_t var_fns[] = { + { "tostring", uc_var_tostring } +}; + + +static uc_value_t * +uc_und_tostring(uc_vm_t *vm, size_t nargs) +{ + undef_t *undef = uc_fn_thisval("uscope.undefined"); + uc_stringbuf_t *sbuf = ucv_stringbuf_new(); + + ucv_stringbuf_printf(sbuf, "", + undef->location.line, undef->location.column); + + return ucv_stringbuf_finish(sbuf); +} + +static const uc_function_list_t und_fns[] = { + { "tostring", uc_und_tostring } +}; + +static void +close_undef(void *ud) +{ + undef_t *undef = ud; + + jsdoc_free(undef->jsdoc); + + free(undef); +} + + +void uc_module_init(uc_vm_t *vm, uc_value_t *scope) +{ + uc_function_list_register(scope, uscope_fns); + + uc_type_declare(vm, "uscope.variable", var_fns, close_var); + uc_type_declare(vm, "uscope.undefined", und_fns, close_undef); + + uc_value_t *tt = + ucv_array_new_length(vm, ARRAY_SIZE(semantic_token_type_names)); + + for (size_t i = 0; i < ARRAY_SIZE(semantic_token_type_names); i++) + ucv_array_push(tt, ucv_string_new(semantic_token_type_names[i])); + + ucv_object_add(scope, "TOKEN_TYPES", tt); + + uc_value_t *tm = + ucv_array_new_length(vm, ARRAY_SIZE(semantic_token_modifier_names)); + + for (size_t i = 0; i < ARRAY_SIZE(semantic_token_modifier_names); i++) + ucv_array_push(tm, ucv_string_new(semantic_token_modifier_names[i])); + + ucv_object_add(scope, "TOKEN_MODIFIERS", tm); +} diff --git a/lib/uscope/uscope.h b/lib/uscope/uscope.h new file mode 100644 index 00000000..31f216b6 --- /dev/null +++ b/lib/uscope/uscope.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include "ucode/types.h" +#include "jsdoc.h" + + +#define uscope_vector_get(vec, i) \ + (((i) < (vec)->count) ? &(vec)->entries[i] : NULL) + + +typedef enum { + ACCESS_DECLARATION, + ACCESS_READ, + ACCESS_WRITE, + ACCESS_UPDATE, + ACCESS_EXPORT, +} uscope_access_kind_t; + +typedef struct { + size_t offset; + size_t line; + size_t column; +} uscope_position_t; + +typedef struct { + uscope_position_t location; + size_t token_id; + uscope_access_kind_t access_kind; + uc_value_t *value; + bool optional; +} uscope_reference_t; + +typedef struct { + uc_value_t *base; + uc_value_t *name; + uc_value_t *value; + uc_type_t type; + bool constant; + bool export; + bool initialized; + bool global; + bool property; + bool superseded; + struct { + size_t count; + uscope_reference_t *entries; + } references; + struct { + uscope_position_t start; + uscope_position_t end; + } range; + jsdoc_t *jsdoc; +} uscope_variable_t; + + +uc_value_t *uscope_resolve_variable(uc_vm_t *, uc_value_t *, bool); diff --git a/vscode/.vscodeignore b/vscode/.vscodeignore new file mode 100644 index 00000000..a3c55554 --- /dev/null +++ b/vscode/.vscodeignore @@ -0,0 +1,3 @@ +**/*.ts +**/tsconfig.json + diff --git a/vscode/CHANGELOG.md b/vscode/CHANGELOG.md new file mode 100644 index 00000000..e69de29b diff --git a/vscode/LICENSE b/vscode/LICENSE new file mode 120000 index 00000000..ea5b6064 --- /dev/null +++ b/vscode/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/vscode/README.md b/vscode/README.md new file mode 100644 index 00000000..a147ad55 --- /dev/null +++ b/vscode/README.md @@ -0,0 +1,101 @@ +# ucode Language Server Extension for Visual Studio Code + +## Overview + +This extension provides language server support for ucode in Visual Studio Code. It offers syntax highlighting, code completion, and error diagnostics for ucode development. + +## Features + +- Syntax highlighting for ucode files +- Code completion suggestions +- Basic compile time error diagnostics +- Go to definition and find all references +- Configurable ucode interpreter path +- Custom module search paths + +## Requirements + +- Visual Studio Code version 1.60.0 or higher +- ucode interpreter +- Valgrind (optional, for debugging) + +## Installation + +1. Open Visual Studio Code +2. Go to the Extensions view (Ctrl+Shift+X or Cmd+Shift+X on macOS) +3. Search for "ucode Language Server" +4. Click Install + +Alternatively, the VSIX file can be downloaded from the [VS Code Marketplace](https://marketplace.visualstudio.com) and installed manually. + +## Configuration + +This extension contributes the following settings: + +- `ucodeLanguageServer.valgrindDebugging`: Enable/disable running the language server process in Valgrind. + - Type: `boolean` + - Default: `false` + - Scope: Resource + +- `ucodeLanguageServer.interpreterPath`: Override the path to the ucode interpreter executable. + - Type: `string` + - Default: `"ucode"` + - Scope: Resource + +- `ucodeLanguageServer.moduleSearchPath`: Specify a list of module search path patterns for the ucode runtime. + - Type: `array` + - Default: `[]` + - Scope: Resource + +These settings can be modified in the `settings.json` file or through the Settings UI in VS Code. + +Example configuration in `settings.json`: + +```json +{ + "ucodeLanguageServer.valgrindDebugging": false, + "ucodeLanguageServer.interpreterPath": "/usr/local/bin/ucode", + "ucodeLanguageServer.moduleSearchPath": [ + "/path/to/your/ucode/modules", + "${workspaceFolder}/ucode_modules" + ] +} +``` + +Note: The `${workspaceFolder}` variable can be used in the `moduleSearchPath` to refer to the root of the current workspace. + +## Known Issues + +**Note:** This is an early development version of the ucode Language Server Extension. It may be unstable and contain bugs. + +Current limitations: + +- The extension is in active development and may change significantly. +- Some features may be incomplete or behave unexpectedly. +- Performance optimizations are ongoing. +- Error reporting and diagnostics may be incomplete or inaccurate. +- Limited testing across platforms and ucode project configurations. + +Bugs and unexpected behavior can be reported by [opening an issue](https://github.com/your-repo/issues) on the GitHub repository. + +Contributions are welcome. + +It is recommended to use the latest version of the extension, as improvements and bug fixes are released regularly. + +## Release Notes + +### 0.0.20240924 + +Initial release of ucode Language Server Extension + +- Basic syntax highlighting +- Code completion for function calls and property accesses +- Error diagnostics for cucode compile time errors + +## License + +This extension is licensed under the [MIT License](LICENSE.md). + +## Contact + +Questions, issues, or suggestions can be submitted by [opening an issue](https://github.com/jow-/ucode/issues) on the GitHub repository. diff --git a/vscode/client/src/extension.ts b/vscode/client/src/extension.ts new file mode 100644 index 00000000..d388e8ed --- /dev/null +++ b/vscode/client/src/extension.ts @@ -0,0 +1,82 @@ +import * as path from 'path'; +import { window, workspace, ExtensionContext } from 'vscode'; +import { + Executable, + LanguageClient, + LanguageClientOptions, + ServerOptions, + TransportKind, + RevealOutputChannelOn +} from 'vscode-languageclient/node'; + +let client: LanguageClient; + +function sendSettings(client: LanguageClient) { + const settings = workspace.getConfiguration('ucodeLanguageServer'); + + client.sendNotification('custom/updateSettings', { + moduleSearchPath: settings.get('moduleSearchPath') + }); +} + +export function activate(context: ExtensionContext) { + const ucodeInterp: string = workspace.getConfiguration('ucodeLanguageServer').get('interpreterPath') || 'ucode'; + const serverScript: string = context.asAbsolutePath(path.join('server', 'launcher.uc')); + const serverSpec: Executable = { + command: ucodeInterp, + args: [serverScript, '--'], + transport: TransportKind.stdio + }; + const serverOptions: ServerOptions = { run: serverSpec, debug: serverSpec }; + + const clientOptions: LanguageClientOptions = { + documentSelector: [{ scheme: 'file', language: 'ucode' }], + synchronize: { + fileEvents: workspace.createFileSystemWatcher('**/.clientrc') + }, + outputChannel: window.createOutputChannel('ucode Language Server'), + revealOutputChannelOn: RevealOutputChannelOn.Debug + }; + + client = new LanguageClient( + 'ucodeLanguageServer', + 'ucode Language Server', + serverOptions, + clientOptions + ); + + sendSettings(client); + + context.subscriptions.push( + workspace.onDidChangeConfiguration(event => { + if (event.affectsConfiguration('ucodeLanguageServer.valgrindDebugging') || + event.affectsConfiguration('ucodeLanguageServer.interpreterPath')) { + + const ucodeInterp: string = workspace.getConfiguration('ucodeLanguageServer').get('interpreterPath') || 'ucode'; + const useValgrind: boolean = workspace.getConfiguration('ucodeLanguageServer').get('valgrindDebugging'); + + if (useValgrind) { + serverSpec.command = 'valgrind'; + serverSpec.args = ['--num-callers=100', '--leak-check=full', ucodeInterp, serverScript, '--']; + } + else { + serverSpec.command = ucodeInterp; + serverSpec.args = [serverScript, '--']; + } + + client.restart(); + } + + sendSettings(client); + }) + ); + + client.start(); +} + +export function deactivate(): Thenable | undefined { + if (!client) { + return undefined; + } + return client.stop(); +} diff --git a/vscode/client/tsconfig.json b/vscode/client/tsconfig.json new file mode 100644 index 00000000..64f799f7 --- /dev/null +++ b/vscode/client/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "out", + "rootDir": "src", + "sourceMap": true + }, + "include": ["src"], + "exclude": ["node_modules", ".vscode-test"] + } diff --git a/vscode/language-configuration.json b/vscode/language-configuration.json new file mode 100644 index 00000000..2ab82c99 --- /dev/null +++ b/vscode/language-configuration.json @@ -0,0 +1,25 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": [ "/*", "*/" ] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + { "open": "{", "close": "}" }, + { "open": "[", "close": "]" }, + { "open": "(", "close": ")" }, + { "open": "'", "close": "'", "notIn": ["string", "comment"] }, + { "open": "\"", "close": "\"", "notIn": ["string"] } + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["'", "'"], + ["\"", "\""] + ] +} diff --git a/vscode/package.json b/vscode/package.json new file mode 100644 index 00000000..8ef82797 --- /dev/null +++ b/vscode/package.json @@ -0,0 +1,136 @@ +{ + "name": "ucode-language", + "displayName": "ucode Language Support", + "description": "Language support for ucode", + "version": "0.0.20240924", + "engines": { + "vscode": "^1.63.0" + }, + "categories": [ + "Programming Languages" + ], + "activationEvents": [ + "onLanguage:ucode" + ], + "pricing": "Free", + "publisher": "jow", + "homepage": "https://ucode.mein.io/", + "repository": { + "type": "git", + "url": "https://github.com/jow-/ucode/" + }, + "license": "ISC", + "main": "./client/out/extension", + "contributes": { + "configuration": { + "type": "object", + "title": "ucode", + "properties": { + "ucodeLanguageServer.valgrindDebugging": { + "scope": "resource", + "type": "boolean", + "description": "Run the language server process in valgrind.", + "default": false + }, + "ucodeLanguageServer.interpreterPath": { + "scope": "resource", + "type": "string", + "description": "Override the path to the ucode interpreter executable.", + "default": "ucode" + }, + "ucodeLanguageServer.moduleSearchPath": { + "scope": "resource", + "type": "array", + "items": { "type": "string" }, + "description": "List of module search path patterns for the ucode runtime.", + "default": [ "${workspaceFolder}/*.uc", "${workspaceFolder}/*.so" ] + } + } + }, + "configurationDefaults": { + "[ucode]": { + "editor.semanticHighlighting.enabled": true, + "editor.wordBasedSuggestions": "off" + } + }, + "languages": [ + { + "id": "ucode", + "aliases": [ + "ucode", + "uc" + ], + "extensions": [ + ".uc", + ".ut" + ], + "configuration": "./language-configuration.json" + } + ], + "semanticTokenTypes": [ + { + "id": "ucode-template-tag", + "description": "ucode template tag" + }, + { + "id": "ucode-template-text", + "description": "ucode template text" + }, + { + "id": "ucode-punctuation", + "description": "ucode punctuation" + }, + { + "id": "keyword.control", + "description": "ucode if/else/elsif keywords" + }, + { + "id": "ucode-boolean", + "description": "ucode true/false literals" + }, + { + "id": "ucode-null", + "description": "ucode null literal" + }, + { + "id": "ucode-this", + "description": "ucode this literal" + }, + { + "id": "ucode-operator", + "description": "ucode operator keyword" + } + ], + "semanticTokenScopes": [ + { + "scopes": { + "ucode-boolean": ["variable.other.constant"], + "ucode-null": ["variable.other.constant"], + "ucode-this": ["variable.other.constant"], + "ucode-operator": ["keyword.other.operator"], + "ucode-template-tag": ["keyword.other.operator"], + "ucode-template-text": ["constant.character"] + } + } + ] + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -b", + "watch": "tsc -b -w" + }, + "devDependencies": { + "@types/node": "^16.11.7", + "@types/vscode": "^1.63.0", + "typescript": "^4.5.5" + }, + "dependencies": { + "vscode-languageclient": "^9.0.0" + }, + "files": [ + "client/out", + "server/launcher.uc", + "server/server.uc", + "server/types.json" + ] +} diff --git a/vscode/server/launcher.uc b/vscode/server/launcher.uc new file mode 100644 index 00000000..d24ac346 --- /dev/null +++ b/vscode/server/launcher.uc @@ -0,0 +1,154 @@ +import { stdin, stdout, open, stat } from 'fs'; +import { poll, POLLIN, POLLHUP, POLLERR } from 'socket'; + +const server_path = `${sourcepath(0, true)}/server.uc`; + +let server, prev_mtime; +let opened = {}; + +unshift(REQUIRE_SEARCH_PATH, `${sourcepath(0, true)}/*.uc`); + +function check_update() { + let curr_mtime = stat(server_path)?.mtime ?? 0; + + if (curr_mtime != prev_mtime) { + warn(`Reloading ${server_path}...\n`); + + try { + for (let mod in global.modules) + delete global.modules[mod]; + + require('server'); + + if (global.modules.server) + server = global.modules.server; + + for (let uri, msg in opened) + server.handle(msg); + } + catch (e) { + warn(`Unable to reload server: ${e}\n`); + } + + prev_mtime = curr_mtime; + } +} + +function recvmsg() { + let clen, ctype = 'application/vscode-jsonrpc; charset=utf-8'; + + while (true) { + let header = rtrim(stdin.read('line'), '\r\n'); + + if (header == null) { + warn("EOF while reading message header\n"); + exit(1); + } + + if (!length(header)) + break; + + let kv = split(header, ':', 2); + + switch (kv[0]) { + case 'Content-Length': + clen = +kv[1]; + break; + + case 'Content-Type': + ctype = trim(kv[1]); + break; + } + } + + if (clen == null || clen == 0) { + warn(`Invalid Content-Length in request\n`); + return null; + } + + if (ctype != 'application/vscode-jsonrpc; charset=utf-8') { + warn(`Unexpected Content-Type '${ctype ?? '?'}' in request\n`); + return null; + } + + let payload = stdin.read(clen); + let message; + + try { + message = json(payload); + } + catch (e) { + warn(`Invalid request payload '${payload}: ${e}\n`); + return null; + } + + let msgstr = `${message}`; + + if (length(msgstr) > 127) + msgstr = `${substr(msgstr, 0, 127)}...`; + + warn(`[RX] ${msgstr}\n`); + + return message; +} + +function reply(id, payload) { + let message = sprintf('%J', ('jsonrpc' in payload) ? payload : { + jsonrpc: '2.0', + id, + result: payload + }); + + let header = sprintf('Content-Length: %d\r\n\r\n', length(message)); + + let msgstr = `${message}`; + + if (length(msgstr) > 127) + msgstr = `${substr(msgstr, 0, 127)}...`; + + warn(`[TX] ${msgstr}\n`); + + stdout.write(header); + stdout.write(message); + stdout.flush(); +} + +while (true) { + const events = poll(250, stdin); + + check_update(); + + if (events[0][1] & (POLLHUP|POLLERR)) { + warn(`Peer closed connection, shutting down.\n`); + break; + } + else if (events[0][1] & POLLIN) { + let rpc = recvmsg(); + + if (rpc.method in [ "textDocument/didOpen", "textDocument/didChange" ]) + opened[rpc.params.textDocument.uri] = rpc; + + try { + let out = server.handle(rpc); + reply(rpc.id, out); + } + catch (e) { + warn(`Error handling '${rpc.method}': ${e}\n${e.stacktrace[0].context}\n`); + reply(rpc.id, null); + } + } + else { + try { + let out = server.idle(); + + if (type(out) == 'array') + for (let msg in out) + reply(null, msg); + else if (out) + reply(null, out); + } + catch (e) { + warn(`Error invoking idle method: ${e}\n${e.stacktrace[0].context}\n`); + } + } +} diff --git a/vscode/server/server.uc b/vscode/server/server.uc new file mode 100644 index 00000000..9357928e --- /dev/null +++ b/vscode/server/server.uc @@ -0,0 +1,991 @@ +import { analyze, TOKEN_TYPES, TOKEN_MODIFIERS } from 'uscope'; +import { open, writefile, popen } from 'fs'; + +let types; +try { + types = json(open(`${sourcepath(0, true)}/types.json`, 'r')); +} +catch (e) { + types = {}; +} + +const KIND_TEXT = 1; +const KIND_METHOD = 2; +const KIND_FUNCTION = 3; +const KIND_CONSTRUCTOR = 4; +const KIND_FIELD = 5; +const KIND_VARIABLE = 6; +const KIND_CLASS = 7; +const KIND_INTERFACE = 8; +const KIND_MODULE = 9; +const KIND_PROPERTY = 10; +const KIND_UNIT = 11; +const KIND_VALUE = 12; +const KIND_ENUM = 13; +const KIND_KEYWORD = 14; +const KIND_SNIPPET = 15; +const KIND_COLOR = 16; +const KIND_FILE = 17; +const KIND_REFERENCE = 18; +const KIND_FOLDER = 19; +const KIND_ENUM_MEMBER = 20; +const KIND_CONSTANT = 21; +const KIND_STRUCT = 22; +const KIND_EVENT = 23; +const KIND_OPERATOR = 24; +const KIND_TYPE_PARAMETER = 25; + +const COMPLETION_INVOKED = 1; +const COMPLETION_TRIGGER_CHAR = 2; +const COMPLETION_TRIGGER_INCOMPLETE = 3; + +const INSERT_FORMAT_PLAINTEXT = 1; +const INSERT_FORMAT_SNIPPET = 2; + +const SEVERITY_ERROR = 1; +const SEVERITY_WARNING = 2; +const SEVERITY_INFORMATION = 3; +const SEVERITY_HINT = 4; + +const FIND_PREFER_NEXT = (1 << 0); +const FIND_SKIP_SPACE = (1 << 1); + +const TK_LEXP = 1, TK_REXP = 2, TK_LSTM = 3, TK_RSTM = 4, TK_IF = 5; +const TK_ELSE = 6, TK_COMMA = 7, TK_ASSIGN = 8, TK_ASADD = 9, TK_ASSUB = 10; +const TK_ASMUL = 11, TK_ASDIV = 12, TK_ASMOD = 13, TK_ASLEFT = 14, TK_ASRIGHT = 15; +const TK_ASBAND = 16, TK_ASBXOR = 17, TK_ASBOR = 18, TK_QMARK = 19, TK_COLON = 20; +const TK_OR = 21, TK_AND = 22, TK_BOR = 23, TK_BXOR = 24, TK_BAND = 25; +const TK_EQS = 26, TK_NES = 27, TK_EQ = 28, TK_NE = 29, TK_LT = 30; +const TK_LE = 31, TK_GT = 32, TK_GE = 33, TK_IN = 34, TK_LSHIFT = 35; +const TK_RSHIFT = 36, TK_ADD = 37, TK_SUB = 38, TK_MUL = 39, TK_DIV = 40; +const TK_MOD = 41, TK_EXP = 42, TK_NOT = 43, TK_COMPL = 44, TK_INC = 45; +const TK_DEC = 46, TK_DOT = 47, TK_LBRACK = 48, TK_RBRACK = 49, TK_LPAREN = 50; +const TK_RPAREN = 51, TK_TEXT = 52, TK_LBRACE = 53, TK_RBRACE = 54, TK_SCOL = 55; +const TK_ENDIF = 56, TK_ELIF = 57, TK_WHILE = 58, TK_ENDWHILE = 59, TK_FOR = 60; +const TK_ENDFOR = 61, TK_FUNC = 62, TK_LABEL = 63, TK_ENDFUNC = 64, TK_TRY = 65; +const TK_CATCH = 66, TK_SWITCH = 67, TK_CASE = 68, TK_DEFAULT = 69, TK_ELLIP = 70; +const TK_RETURN = 71, TK_BREAK = 72, TK_CONTINUE = 73, TK_LOCAL = 74, TK_ARROW = 75; +const TK_TRUE = 76, TK_FALSE = 77, TK_NUMBER = 78, TK_DOUBLE = 79, TK_STRING = 80; +const TK_REGEXP = 81, TK_NULL = 82, TK_THIS = 83, TK_DELETE = 84, TK_CONST = 85; +const TK_QLBRACK = 86, TK_QLPAREN = 87, TK_QDOT = 88, TK_ASEXP = 89, TK_ASAND = 90; +const TK_ASOR = 91, TK_ASNULLISH = 92, TK_NULLISH = 93, TK_PLACEH = 94, TK_TEMPLATE = 95; +const TK_IMPORT = 96, TK_EXPORT = 97, TK_EOF = 98, TK_COMMENT = 99, TK_ERROR = 100; + + +const documents = {}; + +function clockms() { + const tv = clock(true); + + return tv[0] * 1000 + tv[1] / 1000000; +} + +function compare_positions(pos1, pos2) { + if (pos1.line != pos2.line) + return pos1.line - pos2.line; + + return pos1.character - pos2.character; +} + +function find_token_id_at_position(tokens, search_position, flags) { + const use_next = (flags & FIND_PREFER_NEXT); + let left = 0, right = length(tokens) - 1; + let last_before = -1, first_after = -1; + + while (left <= right) { + let mid = (left + right) / 2; + let start_comparison = compare_positions(tokens[mid].start, search_position); + let end_comparison = compare_positions(tokens[mid].end, search_position); + + if (start_comparison == 0 && !use_next && mid > 0 && + compare_positions(tokens[mid - 1].end, search_position) == 0) + return mid - 1; + + if (end_comparison == 0 && use_next && mid + 1 < length(tokens) && + compare_positions(tokens[mid + 1].start, search_position) == 0) + return mid + 1; + + if (start_comparison <= 0 && end_comparison >= 0) + return mid; + + if (end_comparison < 0) { + left = mid + 1; + last_before = mid; + } + else { + right = mid - 1; + first_after = mid; + } + } + + if (flags & FIND_SKIP_SPACE) { + if (use_next) + return (first_after > -1) ? first_after : last_before; + else + return (last_before > -1) ? last_before : first_after; + } + + return -1; +} + +function find_token_at_position(tokens, search_position, flags) { + const id = find_token_id_at_position(tokens, search_position, flags); + return (id > -1) ? tokens[id] : null; +} + +function find_scopes_at_position(scopes, search_position) { + let matching_scopechain = []; + + for (let scope in scopes) { + if (compare_positions(search_position, scope.start) >= 0 && + compare_positions(search_position, scope.end) <= 0) { + + unshift(matching_scopechain, scope); + } + + if (length(matching_scopechain) && + compare_positions(scope.start, matching_scopechain[0].end) >= 0) + break; + } + + return matching_scopechain; +} + +function find_variable_at_position(document, search_position) { + let token = find_token_at_position(document.tokens, search_position); + + warn(`VAR-TOKEN: ${search_position} => ${token}\n`); + + if (token?.type == TK_LABEL) { + let scopes = find_scopes_at_position(document.scopes, search_position); + let name = token.value; + + for (let scope in scopes) + for (let variable in scope.variables) + if (variable.name == name) { + warn(`VAR-CANDIDATE: ${variable}\n`); + for (let ref in variable.references) { + warn(`REF-CANDIDATE: ${ref.token} (${ref.token === token})\n`); + if (ref.token == token) + return variable; + } + } + } + + return null; +} + +function find_call_at_position(document, search_position) { + let scopes = find_scopes_at_position(document.scopes, search_position); + let call; + + for (let scope in scopes) { + for (let variable in scope.variables) { + if (index(variable.name, '.return.') == 0) { + const s1 = variable.range.start; + const e1 = variable.range.end; + const len1 = e1.offset - s1.offset; + + if (compare_positions(search_position, s1) >= 0 && + compare_positions(search_position, e1) < 0) { + + const s2 = call?.range?.start; + const e2 = call?.range?.end; + const len2 = e2?.offset - s2?.offset; + + if (!call || len1 < len2) + call = variable; + } + } + } + } + + return call; +} + +function format_type(typeSpec) +{ + for (let t in [ 'function', 'object', 'array', 'module' ]) + if (t in typeSpec) + return t; + + for (let t in [ 'boolean', 'string', 'number', 'integer', 'double', 'null' ]) { + if (typeSpec.type === t) { + if (typeSpec.value !== null) + return sprintf('(%s) %J', t, typeSpec.value); + else + return t; + } + } + + return '(unknown type)'; +} + +function format_typename(typespec) { + switch (typespec?.type ?? 'unspec') { + case 'unspec': return '?'; + case 'any': return '*'; + case 'typename': return typespec.typename; + case 'union': return join('|', map(typespec.union, format_typename)); + default: return typespec.type; + } +} + +function format_detail(prefixText, labelText, typeSpec) +{ + let res = (typeSpec.constant ? 'const ' : ''); + + res += prefixText + labelText; + + if ('function' in typeSpec) { + res += '('; + + res += join(', ', map(typeSpec.function.arguments, + (arg, i) => (arg.name ? `${arg.name}:` : '') + format_typename(arg.type))); + + res += ')'; + + if (typeSpec.function.return) + res += ' → ' + format_typename(typeSpec.function.return?.type); + } + else { + res += ' : ' + format_type(typeSpec); + } + + return res; +} + +function handleInitialize(params) { + return { + capabilities: { + textDocumentSync: 1, + definitionProvider: true, + referencesProvider: true, + completionProvider: { + resolveProvider: false, + triggerCharacters: ['.', '['], + completionItem: { + labelDetailsSupport: true + } + }, + semanticTokensProvider: { + //documentSelector: [ + // { language: 'ucode', scheme: 'file' } + //], + full: true, + legend: { + tokenTypes: TOKEN_TYPES, + tokenModifiers: TOKEN_MODIFIERS + } + }, + signatureHelpProvider: { + triggerCharacters: ['('], + retriggerCharacters: [','] + }, + renameProvider: { + prepareProvider: true + } + } + }; +} + +function quotePropertyName(propname, force) { + if (!force && match(propname, /^[A-Za-z_][A-Za-z0-9_]*$/)) + return propname; + + if (index(propname, "'") != -1) + return `"${replace(propname, /["\\]/g, (m) => `\\${m}`)}"`; + + return `'${replace(propname, /['\\]/g, (m) => `\\${m}`)}'`; +} + +function format_label_detail(typespec) { + if (typespec?.function) { + let res = '('; + + for (let i, argspec in typespec.function.arguments ?? []) { + if (i > 0) + res += ', '; + + if (argspec.restarg) + res += '...'; + + res += argspec.name ?? format_typename(argspec.type); + } + + res += ')'; + + if (typespec.function.return) { + res += ' → '; + res += replace( + format_typename(typespec.function.return?.type), + /^.+\./, '' + ); + } + + return res; + } + + let res = ` : ${format_typename(typespec)}`; + + if (typespec.value != null) + res += ` = ${typespec.value}`; + + return res; +} + +function isTemplateSyntax(uri, text) { + return (match(uri, /\.ut$/) || match(text, /^#!.*(\|[[:space:]]-[[:alnum:]]*T[[:alnum:]]*\>)/)); +} + +function logStructure(doc) { + warn('-- DOCUMENT STRUCTURE --\n'); + + for (let scope in doc.scopes) { + warn(`Scope ${scope.start.line}:${scope.start.character}..${scope.end.line}:${scope.end.character}\n`); + + for (let var in scope.variables) { + warn(`+ ${var.property ? 'Prop' : 'Var'} '${var.name}' ${var.range.start.line}:${var.range.start.character}..${var.range.end.line}:${var.range.end.character}\n`); + + for (let ref in var.references) { + warn(` + Ref ${ref.access} ${ref.location.line}:${ref.location.character}\n`); + } + } + } + + warn('-- END STRUCTURE --\n'); +} + +function parseDocument(uri) { + const document = documents[uri]; + const now = clockms(); + + //warn(`Query <${uri}>: dirty=${doc.dirty} delta=${now - doc.changed}ms length=${length(doc.text)}\n`); + + if (document.tokens == null || (document.dirty /*&& now - doc.changed > 250*/)) { + warn(`Reparsing ${uri} after ${now - document.changed}ms...\n`); + + const res = analyze(document.text, { raw_mode: !isTemplateSyntax(uri, document.text) }); + + if (res) { + for (let k, v in res) + document[k] = v; + + document.dirty = false; + } + + //logStructure(doc); + + return true; + } + + return false; +} + +function handlePropertyCompletion(document, position, propertyToken, closingToken, varspec, prefixText) { + const start = propertyToken?.start; + const end = closingToken?.end ?? position; + const optional = (propertyToken.type == TK_QLBRACK || propertyToken.type == TK_QDOT); + const bracketed = (propertyToken.type == TK_LBRACK || propertyToken.type == TK_QLBRACK); + + const completions = []; + + let jsType = varspec.jsdoc?.type; + + while (jsType?.type == 'typename' && document.typedefs[jsType.typename]) + jsType = document.typedefs[jsType.typename].type; + + const isArray = (jsType?.type == 'array'); + const proplist = isArray ? jsType?.array?.elements : jsType?.object?.properties; + + warn(`PROPLIST: ${isArray}:${proplist}\n`); + + for (let propidx, propspec in proplist) { + let propname = isArray ? `${propidx}` : propidx; + + if (index(propname, prefixText) != 0) + continue; + + const op = optional ? '?.' : '.'; + const op2 = optional ? '?.[' : '['; + const propNameQuoted = quotePropertyName(propname, bracketed); + + let newText, labelText, filterText, sortText; + + if (isArray) { + labelText = bracketed ? `[${propname}]` : propname; + sortText = sprintf('%03d', propidx); + newText = filterText = `${op2}${propidx}]`; + } + else { + const st = optional ? (bracketed ? '?.[' : '?.') : (bracketed ? '[' : '.'); + const et = (bracketed ? ']' : ''); + + labelText = propNameQuoted; + filterText = `${st}${propNameQuoted}${et}`; + sortText = propname; + newText = (bracketed || propNameQuoted != propname) + ? `${op2}${propNameQuoted}]` : `${op}${propname}`; + } + + const fnspec = propspec.type?.function; + + if (fnspec) { + newText += length(fnspec.arguments) + ? `(\${1:${join(', ', map(fnspec.arguments, (arg, i) => arg.name ?? `arg${i}`))}})` + : '(\${1})'; + } + + warn(`COMPLETION <<${start.line}:${start.character}..${end.line}:${end.character}|${substr(document.text, start.offset, end.character - start.character)}>> <<${filterText}>>\n`); + + push(completions, { + label: labelText, + labelDetails: { + detail: format_label_detail(propspec.type), + description: propspec.description ?? '' + }, + filterText, + textEdit: { + newText, + range: { start, end } + }, + kind: (propspec.type?.type == 'function') ? KIND_METHOD : KIND_PROPERTY, + detail: format_detail(propname, '', propspec.type), + documentation: propspec.description ?? '', + sortText, + insertTextFormat: INSERT_FORMAT_SNIPPET + }); + } + + return completions; +} + +function handleVariableCompletion(document, position, variableToken) { + const scopes = find_scopes_at_position(document.scopes, position); + const start = variableToken?.start ?? position; + const end = variableToken?.end ?? position; + const varnameSeen = {}; + const completions = []; + + warn(`VAR-TOKEN: ${variableToken}\n`); + + for (let depth, scope in scopes) { + for (let varspec in scope.variables) { + if (varspec.property || ord(varspec.name) == 46) + continue; + + if (variableToken && index(varspec.name, variableToken.value) !== 0) + continue; + + if (compare_positions(start, varspec.range.start) <= 0) + continue; + + if (compare_positions(start, varspec.range.end) > 0) + continue; + + if (varnameSeen[varspec.name]++) + continue; + + const fnspec = varspec.jsdoc?.type?.function; + let newText = varspec.name; + + if (fnspec) { + newText += length(fnspec.arguments) + ? `(\${1:${join(', ', map(fnspec.arguments, (arg, i) => arg.name ?? `arg${i}`))}})` + : '(\${1})'; + } + + push(completions, { + label: varspec.name, + labelDetails: { + detail: format_label_detail(varspec.jsdoc?.type), + description: varspec.jsdoc?.subject ?? '' + }, + filterText: variableToken?.value ?? '', + textEdit: { + newText, + range: { start, end } + }, + kind: fnspec ? KIND_FUNCTION : (varspec.jsdoc?.constant ? KIND_CONSTANT : KIND_VARIABLE), + detail: format_detail(varspec.name, '', varspec.jsdoc?.type), + documentation: varspec.jsdoc?.description ?? '', + sortText: sprintf('%03d', depth), + insertTextFormat: INSERT_FORMAT_SNIPPET + }); + } + } + + return completions; +} + +function determineCompletionContext(document, position) +{ + let token_id = find_token_id_at_position(document.tokens, position); + + /* this should not happen */ + if (token_id == -1) + return { type: 'variable' }; + + /* if the determined token token is an EOF one, step back until we find the + * last non-eof, non-comment token */ + while (token_id > 0 && document.tokens[token_id].type in [ TK_EOF, TK_COMMENT ]) + token_id--; + + const tok = document.tokens[token_id]; + const isDot = tok.type in [ TK_DOT, TK_QDOT ]; + const isSub = tok.type in [ TK_LBRACK, TK_QLBRACK ]; + + /* if the last token is a label, determine whether we're at a partially + typed property access or a variable read expression */ + if (tok.type == TK_LABEL) { + for (let scope in find_scopes_at_position(document.scopes, position)) { + // scan variables backwards as it is likely that the user types near + // the end of the document + for (let i = length(scope.variables); i > 0; i--) { + const var = scope.variables[i - 1]; + + if (var.property != true || length(var.references) != 1) + continue; + + const ref = var.references[0]; + + if (ref.access != 'declaration' || ref.token !== tok) + continue; + + return { + type: 'property', + propertyToken: find_token_at_position(document.tokens, var.range.start), + lhsVariable: var.base, + rhsLabel: tok.value + }; + } + } + + // no matching property found, assume variable access + return { + type: 'variable', + variableToken: tok + }; + } + + /* otherwise, if it is a dot or left square bracket, find a property + reference whose range start matches the token */ + else if (isDot || isSub) { + let etok; + + for (let i = 1; isSub && !etok && i <= 2; i++) + if (document.tokens[token_id + i].type == TK_RBRACK) + etok = document.tokens[token_id + i]; + + for (let scope in find_scopes_at_position(document.scopes, position)) { + // scan variables backwards as it is likely that the user types near + // the end of the document + for (let i = length(scope.variables); i > 0; i--) { + const var = scope.variables[i - 1]; + + if (var.property != true) + continue; + + if (var.range.start.offset != tok.start.offset) + continue; + + warn(`VAR: ${var}\n`); + + return { + type: 'property', + propertyToken: tok, + closingToken: etok, + lhsVariable: var.base, + rhsLabel: '' + }; + } + } + } + + /* in all other cases assume new variable completion */ + else { + return { type: 'variable' }; + } +} + +function handleCompletion(params) { + const uri = params.textDocument.uri; + const position = params.position; + const document = documents[uri]; + + parseDocument(uri); + + let ctx = determineCompletionContext(document, position); + + warn(`completionContext() = ${ctx}\n`); + + if (ctx?.type == 'property') + return handlePropertyCompletion(document, position, + ctx.propertyToken, ctx.closingToken, ctx.lhsVariable, ctx.rhsLabel); + + return handleVariableCompletion(document, position, ctx.variableToken); +} + +function handleDefinition(params) { + const uri = params.textDocument.uri; + const position = params.position; + const document = documents[uri]; + + const varspec = find_variable_at_position(document, position); + + if (varspec) { + const ref = varspec.references[0]; + + if (ref != null && ref.access in [ 'declaration', 'write', 'update' ]) { + return [ + { + uri, + range: { + start: ref.token.start, + end: ref.token.end + } + } + ]; + } + } + + return []; +} + +function handleReferences(params) { + const uri = params.textDocument.uri; + const document = documents[uri]; + const position = params.position; + const varspec = find_variable_at_position(document, position); + + return map(varspec?.references ?? [], ref => ({ + uri, + range: { + start: ref.token.start, + end: ref.token.end + } + })); +} + +function handleSignatureHelp(params) { + const uri = params.textDocument.uri; + const document = documents[uri]; + const position = params.position; + + parseDocument(uri); + + const varspec = find_call_at_position(document, position); + + if (!varspec || !varspec.base || !varspec.base.jsdoc) + return null; + + warn(`SIGNATURE: ${varspec}\n`); + + let jsType = varspec.base.jsdoc.type; + + while (jsType?.type == 'typename' && document.typedefs[jsType.typename]) + jsType = document.typedefs[jsType.typename].type; + + if (jsType?.type != 'function') + return null; + + let label = varspec.base.name ?? varspec.name ?? 'function'; + let offsets = []; + + label += '('; + + for (let i, arg in jsType.function.arguments) { + if (i > 0) label += ', '; + + push(offsets, length(label), length(label) + length(arg.name)); + + label += arg.name; + } + + label += ')'; + + let activeParameter = 0; + + let start_token_id = find_token_id_at_position(document.tokens, varspec.range.start, FIND_PREFER_NEXT); + let end_token_id = find_token_id_at_position(document.tokens, position, FIND_SKIP_SPACE); + + warn(`START-TOKEN@${varspec.range.start}: ${document.tokens[start_token_id]}\n`); + warn(`TOKEN-RANGE: ${start_token_id + 1}..${end_token_id}\n`); + + for (let i = start_token_id + 1, stack = []; i < end_token_id; i++) { + let tt = document.tokens[i].type; + + warn(`TOK: ${tt}\n`); + + if (tt == TK_LPAREN || tt == TK_QLPAREN) + push(stack, TK_RPAREN); + else if (tt == TK_LBRACK || tt == TK_QLBRACK) + push(stack, TK_RBRACK); + else if (tt == TK_LBRACE) + push(stack, TK_RBRACE); + else if (length(stack) > 0 && tt == stack[-1]) + pop(stack); + else if (length(stack) == 0 && tt == TK_COMMA) + activeParameter++; + else if (length(stack) == 0 && tt == TK_RPAREN) + break; + } + + warn(`ACTIVE-PARAM: ${activeParameter}\n`); + + return { + signatures: [ + { + label, + documentation: varspec.base.jsdoc.description ?? '', + parameters: map(jsType.function?.arguments, arg => ({ + label: [ + offsets[activeParameter * 2 + 0], + offsets[activeParameter * 2 + 1] + ], + documentation: arg.description ?? '' + })) + } + ], + activeSignature: 0, + activeParameter + }; +} + +function handleSemanticTokens(params) { + const uri = params.textDocument.uri; + const document = documents[uri]; + + let tokens = []; + let prevLine = 0; + let prevColumn = 0; + + for (let token in document.tokens) { + let tokenLine = token.start.line; + let tokenStart = token.start.character; + + if (token.start.line < token.end.line) { + let s = substr(document.text, token.start.offset, token.end.offset - token.start.offset); + //warn(`ML-TOKEN[${s}]\n`); + for (let i, ln in split(s, '\n')) { + let deltaStart = (i > 0 || tokenLine > prevLine) ? tokenStart : tokenStart - prevColumn; + let deltaLine = (i > 0) ? 1 : tokenLine - prevLine; + + push(tokens, + deltaLine, + deltaStart, + length(ln), + token.semanticType, + token.semanticModifiers + ); + + tokenStart = 0; + } + } + else { + let deltaStart = (tokenLine > prevLine) ? tokenStart : tokenStart - prevColumn; + let deltaLine = tokenLine - prevLine; + + push(tokens, + deltaLine, + deltaStart, + token.end.offset - token.start.offset, + token.semanticType, + token.semanticModifiers + ); + } + + prevLine = token.end.line; + prevColumn = tokenStart; + } + + warn(`Sending ${length(tokens)}/${length(document.tokens)}/${length(document.text)} tokens...\n`); + + return { data: tokens }; +} + +function updateDocument(uri, text) { + const now = clockms(); + const doc = documents[uri] ??= { uri, changed: 0 }; + + const delta = now - doc.changed; + + const old = doc.text ?? ''; + const new = text; + + if (false && doc.text != null) { + writefile('/tmp/ucode-server-old.txt', doc.text); + writefile('/tmp/ucode-server-new.txt', text); + + const diff = popen('diff -pab -u /tmp/ucode-server-old.txt /tmp/ucode-server-new.txt', 'r'); + warn(`-- DOCUMENT UPDATE (${length(text)} bytes) --\n`); + warn(diff.read('all')); + warn('-- END UPDATE --\n'); + diff.close(); + } + + doc.changed = now; + doc.dirty = true; + doc.text = text; + + return delta; +} + +function sendDiagnostics(doc) { + let diagnostics = []; + + for (let error in doc.errors ?? []) { + push(diagnostics, { + range: { + start: error.start, + end: error.end + }, + severity: SEVERITY_ERROR, + message: error.message, + source: 'ucode-ast' + }); + } + + return { + jsonrpc: '2.0', + method: 'textDocument/publishDiagnostics', + params: { uri: doc.uri, diagnostics } + }; +} + +function handleTextDocumentDidOpen(params) { + let uri = params.textDocument.uri; + let text = params.textDocument.text; + + updateDocument(uri, text); + parseDocument(uri); + + return sendDiagnostics(documents[uri]); +} + +function handleTextDocumentDidChange(params) { + let uri = params.textDocument.uri; + + for (let change in params.contentChanges) + if ('text' in change) + updateDocument(uri, change.text); +} + +function handleTextDocumentDidClose(params) { + let uri = params.textDocument.uri; + + delete documents[uri]; +} + +function handlePrepareRename(params) { + const uri = params.textDocument.uri; + const document = documents[uri]; + const position = params.position; + + parseDocument(uri); + + warn(`FIND TOKEN...\n`); + + const token = find_token_at_position(document.tokens, position); + + warn(`TOKEN: ${token}\n`); + + if (!token || token.type != TK_LABEL) + return null; + + for (let scope in find_scopes_at_position(document.scopes, token.start)) { + warn(`SCOPE...\n`); + for (let varspec in scope.variables) { + if (type(varspec.name) != 'string' || ord(varspec.name) == 46) + continue; + + for (let ref in varspec.references) { + if (ref.token === token) { + warn(`RET???\n`); + return { + range: { + start: ref.token.start, + end: ref.token.end + }, + placeholder: ref.token.value + }; + } + } + } + } +} + +function handleRename(params) { + const uri = params.textDocument.uri; + const document = documents[uri]; + const position = params.position; + + parseDocument(uri); + + const token = find_token_at_position(document.tokens, position); + + if (!token || token.type != TK_LABEL) + return null; + + for (let scope in find_scopes_at_position(document.scopes, token.start)) { + for (let varspec in scope.variables) { + if (type(varspec.name) != 'string' || ord(varspec.name) == 46) + continue; + + for (let ref in varspec.references) { + if (ref.token === token) { + return { + changes: { + [uri]: map(varspec.references, r => ({ + range: { + start: r.token.start, + end: r.token.end + }, + newText: params.newName + })) + } + }; + } + } + } + } +} + +return { + handle: function(rpc) { + if (rpc.method == "initialize") + return handleInitialize(rpc.params); + else if (rpc.method == "textDocument/completion") + return handleCompletion(rpc.params); + else if (rpc.method == 'textDocument/definition') + return handleDefinition(rpc.params); + else if (rpc.method == 'textDocument/references') + return handleReferences(rpc.params); + else if (rpc.method == 'textDocument/signatureHelp') + return handleSignatureHelp(rpc.params); + else if (rpc.method == 'textDocument/semanticTokens/full') + return handleSemanticTokens(rpc.params); + else if (rpc.method == "textDocument/didOpen") + return handleTextDocumentDidOpen(rpc.params); + else if (rpc.method == "textDocument/didChange") + return handleTextDocumentDidChange(rpc.params); + else if (rpc.method == 'textDocument/didClose') + return handleTextDocumentDidClose(rpc.params); + else if (rpc.method == 'textDocument/prepareRename') + return handlePrepareRename(rpc.params); + else if (rpc.method == 'textDocument/rename') + return handleRename(rpc.params); + }, + + idle: function() { + const now = clockms(); + const replies = []; + + for (let uri, doc in documents) { + if (parseDocument(uri)) + push(replies, sendDiagnostics(doc)); + } + + return replies; + } +}; diff --git a/vscode/server/types.json b/vscode/server/types.json new file mode 100644 index 00000000..0f7e3259 --- /dev/null +++ b/vscode/server/types.json @@ -0,0 +1,8057 @@ +{ + "core": { + "print": { + "kind": "function", + "name": "module:core#print", + "return": [ + { + "type": "number" + } + ], + "params": { + "values": { + "type": [ + { + "type": "...*" + } + ], + "name": "values", + "description": "Arbitrary values to print" + } + }, + "subject": "Print any of the given values to stdout.", + "description": "Print any of the given values to stdout.\n\nThe `print()` function writes a string representation of each given argument\nto stdout and returns the amount of bytes written.\n\nString values are printed as-is, integer and double values are printed in\ndecimal notation, boolean values are printed as `true` or `false` while\narrays and objects are converted to their JSON representation before being\nwritten to the standard output. The `null` value is represented by an empty\nstring so `print(null)` would print nothing. Resource values are printed in\nthe form ``, e.g. ``.\n\nIf resource, array or object values contain a `tostring()` function in their\nprototypes, then this function is invoked to obtain an alternative string\nrepresentation of the value.\n\nExamples:\n\n```javascript\nprint(1 != 2); // Will print 'true'\nprint(0xff); // Will print '255'\nprint(2e3); // Will print '2000'\nprint(null); // Will print nothing\nprint({ hello: true, world: 123 }); // Will print '{ \"hello\": true, \"world\": 123 }'\nprint([1,2,3]); // Will print '[ 1, 2, 3 ]'\n\nprint(proto({ foo: \"bar\" }, // Will print 'MyObj'\n{ tostring: () => \"MyObj\" })); // instead of '{ \"foo\": \"bar\" }'\n\n```\n\nReturns the amount of bytes printed." + }, + "length": { + "kind": "function", + "name": "module:core#length", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "description": "Determine the length of the given object, array or string.\n\nReturns the length of the given value.\n\n- For strings, the length is the amount of bytes within the string\n- For arrays, the length is the amount of array elements\n- For objects, the length is defined as the amount of keys\n\nReturns `null` if the given argument is not an object, array or string.", + "params": { + "x": { + "type": [ + { + "type": "object" + }, + { + "type": "array" + }, + { + "type": "string" + } + ], + "name": "x", + "description": "The input object, array, or string." + } + }, + "subject": "Determine the length of the given object, array or string." + }, + "index": { + "kind": "function", + "name": "module:core#index", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "arr_or_str": { + "type": [ + { + "type": "array" + }, + { + "type": "string" + } + ], + "name": "arr_or_str", + "description": "The array or string to search for the value." + }, + "needle": { + "type": [ + { + "type": "*" + } + ], + "name": "needle", + "description": "The value to find within the array or string." + } + }, + "subject": "Finds the given value passed as the second argument within the array or\nstring specified in the first argument.", + "description": "Finds the given value passed as the second argument within the array or\nstring specified in the first argument.\n\nReturns the first matching array index or first matching string offset or\n`-1` if the value was not found.\n\nReturns `null` if the first argument was neither an array nor a string." + }, + "rindex": { + "kind": "function", + "name": "module:core#rindex", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "arr_or_str": { + "type": [ + { + "type": "array" + }, + { + "type": "string" + } + ], + "name": "arr_or_str", + "description": "The array or string to search for the value." + }, + "needle": { + "type": [ + { + "type": "*" + } + ], + "name": "needle", + "description": "The value to find within the array or string." + } + }, + "subject": "Finds the given value passed as the second argument within the array or\nstring specified in the first argument.", + "description": "Finds the given value passed as the second argument within the array or\nstring specified in the first argument.\n\nReturns the last matching array index or last matching string offset or\n`-1` if the value was not found.\n\nReturns `null` if the first argument was neither an array nor a string." + }, + "push": { + "kind": "function", + "name": "module:core#push", + "return": [ + { + "type": "*" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The array to push values to." + }, + "values": { + "type": [ + { + "type": "...*" + } + ], + "name": "[values]", + "description": "The values to push.", + "optional": true + } + }, + "subject": "Pushes the given argument(s) to the given array.", + "description": "Pushes the given argument(s) to the given array.\n\nReturns the last pushed value." + }, + "pop": { + "kind": "function", + "name": "module:core#pop", + "return": [ + { + "type": "*" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The input array." + } + }, + "subject": "Pops the last item from the given array and returns it.", + "description": "Pops the last item from the given array and returns it.\n\nReturns `null` if the array was empty or if a non-array argument was passed." + }, + "shift": { + "kind": "function", + "name": "module:core#shift", + "return": [ + { + "type": "*" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The array from which to pop the first item." + } + }, + "subject": "Pops the first item from the given array and returns it.", + "description": "Pops the first item from the given array and returns it.\n\nReturns `null` if the array was empty or if a non-array argument was passed." + }, + "unshift": { + "kind": "function", + "name": "module:core#unshift", + "return": [ + { + "type": "*" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The array to which the values will be added." + }, + "Values": { + "type": [ + { + "type": "...*" + } + ], + "name": "Values", + "description": "to add." + } + }, + "subject": "Add the given values to the beginning of the array passed via first argument.", + "description": "Add the given values to the beginning of the array passed via first argument.\n\nReturns the last value added to the array." + }, + "chr": { + "kind": "function", + "name": "module:core#chr", + "return": [ + { + "type": "string" + } + ], + "params": { + "n1": { + "type": [ + { + "type": "...number" + } + ], + "name": "n1", + "description": "The numeric values." + } + }, + "subject": "Converts each given numeric value to a byte and return the resulting string.\nInvalid numeric values or values < 0 result in `\\0` bytes, values larger than\n255 are truncated to 255.", + "description": "Converts each given numeric value to a byte and return the resulting string.\nInvalid numeric values or values < 0 result in `\\0` bytes, values larger than\n255 are truncated to 255.\n\nReturns a new strings consisting of the given byte values." + }, + "die": { + "kind": "function", + "name": "module:core#die", + "throws": [ + { + "type": [ + { + "type": "Error" + } + ], + "description": "The error with the given message." + } + ], + "params": { + "msg": { + "type": [ + { + "type": "string" + } + ], + "name": "msg", + "description": "The error message." + } + }, + "subject": "Raise an exception with the given message and abort execution.", + "description": "Raise an exception with the given message and abort execution." + }, + "exists": { + "kind": "function", + "name": "module:core#exists", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "obj": { + "type": [ + { + "type": "object" + } + ], + "name": "obj", + "description": "The input object." + }, + "key": { + "type": [ + { + "type": "string" + } + ], + "name": "key", + "description": "The key to check for existence." + } + }, + "subject": "Check whether the given key exists within the given object value.", + "description": "Check whether the given key exists within the given object value.\n\nReturns `true` if the given key is present within the object passed as the\nfirst argument, otherwise `false`." + }, + "exit": { + "kind": "function", + "name": "module:core#exit", + "params": { + "n": { + "type": [ + { + "type": "number" + } + ], + "name": "n", + "description": "The exit code." + } + }, + "subject": "Terminate the interpreter with the given exit code.", + "description": "Terminate the interpreter with the given exit code.\n\nThis function does not return." + }, + "getenv": { + "kind": "function", + "name": "module:core#getenv", + "return": [ + { + "type": "string" + }, + { + "type": "object", + "keytype": [ + { + "type": "string" + } + ], + "itemtype": [ + { + "type": "string" + } + ] + } + ], + "params": { + "name": { + "type": [ + { + "type": "string" + } + ], + "name": "[name]", + "description": "The name of the environment variable.", + "optional": true + } + }, + "subject": "Query an environment variable or then entire environment.", + "description": "Query an environment variable or then entire environment.\n\nReturns the value of the given environment variable, or - if omitted - a\ndictionary containing all environment variables." + }, + "filter": { + "kind": "function", + "name": "module:core#filter", + "return": [ + { + "type": "array" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The input array." + }, + "fn": { + "type": [ + { + "type": "function" + } + ], + "name": "fn", + "description": "The filter function." + } + }, + "subject": "Filter the array passed as the first argument by invoking the function\nspecified in the second argument for each array item.", + "description": "Filter the array passed as the first argument by invoking the function\nspecified in the second argument for each array item.\n\nIf the invoked function returns a truthy result, the item is retained,\notherwise, it is dropped. The filter function is invoked with three\narguments:\n\n1. The array value\n2. The current index\n3. The array being filtered\n\n(Note that the `map` function behaves similarly to `filter` with respect\nto its `fn` parameters.)\n\nReturns a new array containing only retained items, in the same order as\nthe input array." + }, + "hex": { + "kind": "function", + "name": "module:core#hex", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "*" + } + ], + "name": "x", + "description": "The hexadecimal string to be converted." + } + }, + "subject": "Converts the given hexadecimal string into a number.", + "description": "Converts the given hexadecimal string into a number.\n\nReturns the resulting integer value or `NaN` if the input value cannot be\ninterpreted as hexadecimal number." + }, + "int": { + "kind": "function", + "name": "module:core#int", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "*" + } + ], + "name": "x", + "description": "The value to be converted to an integer." + }, + "base": { + "type": [ + { + "type": "int" + } + ], + "name": "[base]", + "description": "The base into which the value is to be converted, the default is 10.\nNote that the base parameter is ignored if the `x` value is already numeric.", + "optional": true + } + }, + "subject": "Converts the given value to an integer, using an optional base.", + "description": "Converts the given value to an integer, using an optional base.\n\nReturns `NaN` if the value is not convertible." + }, + "join": { + "kind": "function", + "name": "module:core#join", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "sep": { + "type": [ + { + "type": "string" + } + ], + "name": "sep", + "description": "The separator to be used in joining the array elements." + }, + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The array to be joined into a string." + } + }, + "subject": "Joins the array passed as the second argument into a string, using the\nseparator passed in the first argument as glue.", + "description": "Joins the array passed as the second argument into a string, using the\nseparator passed in the first argument as glue.\n\nReturns `null` if the second argument is not an array." + }, + "keys": { + "kind": "function", + "name": "module:core#keys", + "return": [ + { + "type": "array", + "nullable": true + } + ], + "params": { + "obj": { + "type": [ + { + "type": "object" + } + ], + "name": "obj", + "description": "The object from which to retrieve the key names." + } + }, + "subject": "Enumerates all object key names.", + "description": "Enumerates all object key names.\n\nReturns an array of all key names present in the passed object.\nReturns `null` if the given argument is not an object." + }, + "lc": { + "kind": "function", + "name": "module:core#lc", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "description": "Convert the given string to lowercase and return the resulting string.\n\nReturns `null` if the given argument could not be converted to a string.", + "params": { + "s": { + "type": [ + { + "type": "string" + } + ], + "name": "s", + "description": "The input string." + } + }, + "subject": "Convert the given string to lowercase and return the resulting string." + }, + "map": { + "kind": "function", + "name": "module:core#map", + "return": [ + { + "type": "array" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The input array." + }, + "fn": { + "type": [ + { + "type": "function" + } + ], + "name": "fn", + "description": "The mapping function." + } + }, + "subject": "Transform the array passed as the first argument by invoking the function\nspecified in the second argument for each array item.", + "description": "Transform the array passed as the first argument by invoking the function\nspecified in the second argument for each array item.\n\nThe mapping function is invoked with three arguments (see examples, below,\nfor some possibly counterintuitive usage):\n\n1. The array value\n2. The current index\n3. The array being filtered\n\n(Note that the `filter` function behaves similarly to `map` with respect\nto its `fn` parameters.)\n\nReturns a new array of the same length as the input array containing the\ntransformed values." + }, + "ord": { + "kind": "function", + "name": "module:core#ord", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "s": { + "type": [ + { + "type": "string" + } + ], + "name": "s", + "description": "The input string." + }, + "offset": { + "type": [ + { + "type": "number" + } + ], + "name": "[offset]", + "description": "The offset of the character.", + "optional": true + } + }, + "subject": "Without further arguments, this function returns the byte value of the first\ncharacter in the given string.", + "description": "Without further arguments, this function returns the byte value of the first\ncharacter in the given string.\n\nIf an offset argument is supplied, the byte value of the character at this\nposition is returned. If an invalid index is supplied, the function will\nreturn `null`. Negative index entries are counted towards the end of the\nstring, e.g. `-2` will return the value of the second last character.\n\nReturns the byte value of the character.\nReturns `null` if the offset is invalid or if the input is not a string." + }, + "type": { + "kind": "function", + "name": "module:core#type", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "x": { + "type": [ + { + "type": "*" + } + ], + "name": "x", + "description": "The value to determine the type of." + } + }, + "subject": "Query the type of the given value.", + "description": "Query the type of the given value.\n\nReturns the type of the given value as a string which might be one of\n`\"function\"`, `\"object\"`, `\"array\"`, `\"double\"`, `\"int\"`, or `\"bool\"`.\n\nReturns `null` when no value or `null` is passed." + }, + "reverse": { + "kind": "function", + "name": "module:core#reverse", + "return": [ + { + "type": "(Array|string)", + "nullable": true + } + ], + "params": { + "arr_or_str": { + "type": [ + { + "type": "array" + }, + { + "type": "string" + } + ], + "name": "arr_or_str", + "description": "The input array or string." + } + }, + "subject": "Reverse the order of the given input array or string.", + "description": "Reverse the order of the given input array or string.\n\nIf an array is passed, returns the array in reverse order.\nIf a string is passed, returns the string with the sequence of the characters\nreversed.\n\nReturns the reversed array or string.\nReturns `null` if neither an array nor a string were passed." + }, + "sort": { + "kind": "function", + "name": "module:core#sort", + "return": [ + { + "type": "array" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The input array to be sorted." + }, + "fn": { + "type": [ + { + "type": "function" + } + ], + "name": "[fn]", + "description": "The sort function.", + "optional": true + } + }, + "subject": "Sort the given array according to the given sort function.\nIf no sort function is provided, a default ascending sort order is applied.", + "description": "Sort the given array according to the given sort function.\nIf no sort function is provided, a default ascending sort order is applied.\n\nThe input array is sorted in-place, no copy is made.\n\nThe custom sort function is repeatedly called until the entire array is\nsorted. It will receive two values as arguments and should return a value\nlower than, larger than or equal to zero depending on whether the first\nargument is smaller, larger or equal to the second argument respectively.\n\nReturns the sorted input array." + }, + "splice": { + "kind": "function", + "name": "module:core#splice", + "return": [ + { + "type": "*" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The input array to be modified." + }, + "off": { + "type": [ + { + "type": "number" + } + ], + "name": "off", + "description": "The index to start removing elements." + }, + "len": { + "type": [ + { + "type": "number" + } + ], + "name": "[len]", + "description": "The number of elements to remove.", + "optional": true + }, + "elements": { + "type": [ + { + "type": "...*" + } + ], + "name": "[elements]", + "description": "The elements to insert.", + "optional": true + } + }, + "subject": "Removes the elements designated by `off` and `len` from the given array,\nand replaces them with the additional arguments passed, if any.", + "description": "Removes the elements designated by `off` and `len` from the given array,\nand replaces them with the additional arguments passed, if any.\n\nThe array grows or shrinks as necessary.\n\nReturns the modified input array." + }, + "slice": { + "kind": "function", + "name": "module:core#slice", + "return": [ + { + "type": "array" + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array" + } + ], + "name": "arr", + "description": "The source array to be copied." + }, + "off": { + "type": [ + { + "type": "number" + } + ], + "name": "[off]", + "description": "The index of the first element to copy.", + "optional": true + }, + "end": { + "type": [ + { + "type": "number" + } + ], + "name": "[end]", + "description": "The index of the first element to exclude from the returned array.", + "optional": true + } + }, + "subject": "Performs a shallow copy of a portion of the source array, as specified by\nthe start and end offsets. The original array is not modified.", + "description": "Performs a shallow copy of a portion of the source array, as specified by\nthe start and end offsets. The original array is not modified.\n\nReturns a new array containing the copied elements, if any.\nReturns `null` if the given source argument is not an array value." + }, + "split": { + "kind": "function", + "name": "module:core#split", + "return": [ + { + "type": "array" + } + ], + "params": { + "str": { + "type": [ + { + "type": "string" + } + ], + "name": "str", + "description": "The input string to be split." + }, + "sep": { + "type": [ + { + "type": "string" + }, + { + "type": "RegExp" + } + ], + "name": "sep", + "description": "The separator." + }, + "limit": { + "type": [ + { + "type": "number" + } + ], + "name": "[limit]", + "description": "The limit on the number of splits.", + "optional": true + } + }, + "subject": "Split the given string using the separator passed as the second argument\nand return an array containing the resulting pieces.", + "description": "Split the given string using the separator passed as the second argument\nand return an array containing the resulting pieces.\n\nIf a limit argument is supplied, the resulting array contains no more than\nthe given amount of entries, that means the string is split at most\n`limit - 1` times total.\n\nThe separator may either be a plain string or a regular expression.\n\nReturns a new array containing the resulting pieces." + }, + "substr": { + "kind": "function", + "name": "module:core#substr", + "return": [ + { + "type": "string" + } + ], + "params": { + "str": { + "type": [ + { + "type": "string" + } + ], + "name": "str", + "description": "The input string." + }, + "off": { + "type": [ + { + "type": "number" + } + ], + "name": "off", + "description": "The starting offset." + }, + "len": { + "type": [ + { + "type": "number" + } + ], + "name": "[len]", + "description": "The length of the substring.", + "optional": true + } + }, + "subject": "Extracts a substring out of `str` and returns it. First character is at\noffset zero.", + "description": "Extracts a substring out of `str` and returns it. First character is at\noffset zero.\n\n- If `off` is negative, starts that far back from the end of the string.\n- If `len` is omitted, returns everything through the end of the string.\n- If `len` is negative, leaves that many characters off the string end.\n\nReturns the extracted substring." + }, + "time": { + "kind": "function", + "name": "module:core#time", + "return": [ + { + "type": "number" + } + ], + "subject": "Returns the current UNIX epoch.", + "description": "Returns the current UNIX epoch." + }, + "uc": { + "kind": "function", + "name": "module:core#uc", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "str": { + "type": [ + { + "type": "*" + } + ], + "name": "str", + "description": "The string to be converted to uppercase." + } + }, + "subject": "Converts the given string to uppercase and returns the resulting string.", + "description": "Converts the given string to uppercase and returns the resulting string.\n\nReturns null if the given argument could not be converted to a string." + }, + "uchr": { + "kind": "function", + "name": "module:core#uchr", + "return": [ + { + "type": "string" + } + ], + "params": { + "Numeric": { + "type": [ + { + "type": "...number" + } + ], + "name": "Numeric", + "description": "values to convert." + } + }, + "subject": "Converts each given numeric value to an UTF-8 multibyte sequence and returns\nthe resulting string.", + "description": "Converts each given numeric value to an UTF-8 multibyte sequence and returns\nthe resulting string.\n\nInvalid numeric values or values outside the range `0`..`0x10FFFF` are\nrepresented by the unicode replacement character `0xFFFD`.\n\nReturns a new UTF-8 encoded string consisting of unicode characters\ncorresponding to the given numeric codepoints." + }, + "values": { + "kind": "function", + "name": "module:core#values", + "return": [ + { + "type": "array", + "nullable": true + } + ], + "params": { + "obj": { + "type": [ + { + "type": "*" + } + ], + "name": "obj", + "description": "The object from which to extract values." + } + }, + "subject": "Returns an array containing all values of the given object.", + "description": "Returns an array containing all values of the given object.\n\nReturns null if no object was passed." + }, + "trim": { + "kind": "function", + "name": "module:core#trim", + "return": [ + { + "type": "string" + } + ], + "params": { + "str": { + "type": [ + { + "type": "string" + } + ], + "name": "str", + "description": "The string to be trimmed." + }, + "c": { + "type": [ + { + "type": "string" + } + ], + "name": "[c]", + "description": "The characters to be trimmed from the start and end of the string.", + "optional": true + } + }, + "subject": "Trim any of the specified characters in `c` from the start and end of `str`.\nIf the second argument is omitted, trims the characters, ` ` (space), `\\t`,\n`\\r`, and `\\n`.", + "description": "Trim any of the specified characters in `c` from the start and end of `str`.\nIf the second argument is omitted, trims the characters, ` ` (space), `\\t`,\n`\\r`, and `\\n`.\n\nReturns the trimmed string." + }, + "ltrim": { + "kind": "function", + "name": "module:core#ltrim", + "return": [ + { + "type": "string" + } + ], + "params": { + "s": { + "type": [ + { + "type": "string" + } + ], + "name": "s", + "description": "The input string." + }, + "c": { + "type": [ + { + "type": "string" + } + ], + "name": "[c]", + "description": "The characters to trim.", + "optional": true + } + }, + "subject": "Trim any of the specified characters from the start of the string.\nIf the second argument is omitted, trims the characters ` ` (space), '\\t',\n'\\r', and '\\n'.", + "description": "Trim any of the specified characters from the start of the string.\nIf the second argument is omitted, trims the characters ` ` (space), '\\t',\n'\\r', and '\\n'.\n\nReturns the left trimmed string." + }, + "rtrim": { + "kind": "function", + "name": "module:core#rtrim", + "return": [ + { + "type": "string" + } + ], + "params": { + "str": { + "type": [ + { + "type": "string" + } + ], + "name": "str", + "description": "The input string." + }, + "c": { + "type": [ + { + "type": "string" + } + ], + "name": "[c]", + "description": "The characters to trim.", + "optional": true + } + }, + "subject": "Trim any of the specified characters from the end of the string.\nIf the second argument is omitted, trims the characters ` ` (space), '\\t',\n'\\r', and '\\n'.", + "description": "Trim any of the specified characters from the end of the string.\nIf the second argument is omitted, trims the characters ` ` (space), '\\t',\n'\\r', and '\\n'.\n\nReturns the right trimmed string." + }, + "sprintf": { + "kind": "function", + "name": "module:core#sprintf", + "return": [ + { + "type": "string" + } + ], + "params": { + "fmt": { + "type": [ + { + "type": "string" + } + ], + "name": "fmt", + "description": "The format string." + }, + "Arguments": { + "type": [ + { + "type": "...*" + } + ], + "name": "Arguments", + "description": "to be formatted." + } + }, + "subject": "Formats the given arguments according to the given format string.", + "description": "Formats the given arguments according to the given format string.\n\nSee `printf()` for details.\n\nReturns the formatted string." + }, + "printf": { + "kind": "function", + "name": "module:core#printf", + "return": [ + { + "type": "number" + } + ], + "params": { + "fmt": { + "type": [ + { + "type": "string" + } + ], + "name": "fmt", + "description": "The format string." + }, + "Arguments": { + "type": [ + { + "type": "...*" + } + ], + "name": "Arguments", + "description": "to be formatted." + } + }, + "subject": "Formats the given arguments according to the given format string and outputs\nthe result to stdout.", + "description": "Formats the given arguments according to the given format string and outputs\nthe result to stdout.\n\nUcode supports a restricted subset of the formats allowed by the underlying\nlibc's `printf()` implementation, namely it allows the `d`, `i`, `o`, `u`,\n`x`, `X`, `e`, `E`, `f`, `F`, `g`, `G`, `c` and `s` conversions.\n\nAdditionally, an ucode specific `J` format is implemented, which causes the\ncorresponding value to be formatted as JSON string. By prefixing the `J`\nformat letter with a precision specifier, the resulting JSON output will be\npretty printed. A precision of `0` will use tabs for indentation, any other\npositive precision will use that many spaces for indentation while a negative\nor omitted precision specifier will turn off pretty printing.\n\nOther format specifiers such as `n` or `z` are not accepted and returned\nverbatim. Format specifiers including `*` directives are rejected as well.\n\nReturns the number of bytes written to the standard output." + }, + "require": { + "kind": "function", + "name": "module:core#require", + "return": [ + { + "type": "*" + } + ], + "params": { + "name": { + "type": [ + { + "type": "string" + } + ], + "name": "name", + "description": "The name of the module to require in dotted notation." + } + }, + "subject": "Load and evaluate ucode scripts or shared library extensions.", + "description": "Load and evaluate ucode scripts or shared library extensions.\n\nThe `require()` function expands each member of the global\n`REQUIRE_SEARCH_PATH` array to a filesystem path by replacing the `*`\nplaceholder with a slash-separated version of the given dotted module name\nand subsequently tries to load a file at the resulting location.\n\nIf a file is found at one of the search path locations, it is compiled and\nevaluated or loaded via the C runtime's `dlopen()` function, depending on\nwhether the found file is a ucode script or a compiled dynamic library.\n\nThe resulting program function of the compiled/loaded module is then\nsubsequently executed with the current global environment, without a `this`\ncontext and without arguments.\n\nFinally, the return value of the invoked program function is returned back\nby `require()` to the caller.\n\nBy default, modules are cached in the global `modules` dictionary and\nsubsequent attempts to require the same module will return the cached module\ndictionary entry without re-evaluating the module.\n\nTo force reloading a module, the corresponding entry from the global\n`modules` dictionary can be deleted.\n\nTo preload a module or to provide a \"virtual\" module without a corresponding\nfilesystem resource, an entry can be manually added to the global `modules`\ndictionary.\n\nSummarized, the `require()` function can be roughly described by the\nfollowing code:\n\n```\nfunction require(name) {\nif (exists(modules, name))\nreturn modules[name];\n\nfor (const item in REQUIRE_SEARCH_PATH) {\nconst modpath = replace(item, '*', replace(name, '.', '/'));\nconst entryfunc = loadfile(modpath, { raw_mode: true });\n\nif (entryfunc) {\nconst modval = entryfunc();\nmodules[name] = modval;\n\nreturn modval;\n}\n}\n\ndie(`Module ${name} not found`);\n}\n```\n\nDue to the fact that `require()` is a runtime operation, module source code\nis only lazily evaluated/loaded upon invoking the first require invocation,\nwhich might lead to situations where errors in module sources are only\nreported much later throughout the program execution. Unless runtime loading\nof modules is absolutely required, e.g. to conditionally load extensions, the\ncompile time\n{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#named_import|`import` syntax}\nshould be preferred.\n\nReturns the module value (typically an object) on success.\n\nThrows an exception if the module function threw an exception.\n\nThrows an exception if no matching module could be found, if the module\ncontains syntax errors or upon other I/O related problems." + }, + "iptoarr": { + "kind": "function", + "name": "module:core#iptoarr", + "return": [ + { + "type": "array", + "itemtype": [ + { + "type": "number" + } + ], + "nullable": true + } + ], + "params": { + "address": { + "type": [ + { + "type": "string" + } + ], + "name": "address", + "description": "The IP address string to convert." + } + }, + "subject": "Convert the given IP address string to an array of byte values.", + "description": "Convert the given IP address string to an array of byte values.\n\nIPv4 addresses result in arrays of 4 integers while IPv6 ones in arrays\ncontaining 16 integers. The resulting array can be turned back into IP\naddress strings using the inverse `arrtoip()` function.\n\nReturns an array containing the address byte values.\nReturns `null` if the given argument is not a string or an invalid IP." + }, + "arrtoip": { + "kind": "function", + "name": "module:core#arrtoip", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "arr": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "number" + } + ] + } + ], + "name": "arr", + "description": "The byte array to convert into an IP address string." + } + }, + "subject": "Convert the given input array of byte values to an IP address string.", + "description": "Convert the given input array of byte values to an IP address string.\n\nInput arrays of length 4 are converted to IPv4 addresses, arrays of length 16\nto IPv6 ones. All other lengths are rejected. If any array element is not an\ninteger or exceeds the range 0..255 (inclusive), the array is rejected.\n\nReturns a string containing the formatted IP address.\nReturns `null` if the input array was invalid." + }, + "match": { + "kind": "function", + "name": "module:core#match", + "return": [ + { + "type": "array", + "nullable": true + } + ], + "params": { + "str": { + "type": [ + { + "type": "string" + } + ], + "name": "str", + "description": "The string to be matched against the pattern." + }, + "pattern": { + "type": [ + { + "type": "RegExp" + } + ], + "name": "pattern", + "description": "The regular expression pattern." + } + }, + "subject": "Match the given string against the regular expression pattern specified as\nthe second argument.", + "description": "Match the given string against the regular expression pattern specified as\nthe second argument.\n\nIf the passed regular expression uses the `g` flag, the return value will be\nan array of arrays describing all found occurrences within the string.\n\nWithout the `g` modifier, an array describing the first match is returned.\n\nReturns `null` if the pattern was not found within the given string." + }, + "replace": { + "kind": "function", + "name": "module:core#replace", + "return": [ + { + "type": "string" + } + ], + "params": { + "str": { + "type": [ + { + "type": "string" + } + ], + "name": "str", + "description": "The string in which to replace occurrences." + }, + "pattern": { + "type": [ + { + "type": "RegExp" + }, + { + "type": "string" + } + ], + "name": "pattern", + "description": "The pattern to be replaced." + }, + "replace": { + "type": [ + { + "type": "function" + }, + { + "type": "string" + } + ], + "name": "replace", + "description": "The replacement value." + }, + "limit": { + "type": [ + { + "type": "number" + } + ], + "name": "[limit]", + "description": "The optional limit of substitutions.", + "optional": true + } + }, + "subject": "Replace occurrences of the specified pattern in the string passed as the\nfirst argument.", + "description": "Replace occurrences of the specified pattern in the string passed as the\nfirst argument.\n\n- The pattern value may be either a regular expression or a plain string.\n- The replace value may be a function which is invoked for each found pattern\nor any other value which is converted into a plain string and used as\nreplacement.\n- When an optional limit is specified, substitutions are performed only that\nmany times.\n- If the pattern is a regular expression and not using the `g` flag, then\nonly the first occurrence in the string is replaced.\n- If the `g` flag is used or if the pattern is not a regular expression, all\noccurrences are replaced.\n- If the replace value is a callback function, it is invoked with the found\nsubstring as the first and any capture group values as subsequent\nparameters.\n- If the replace value is a string, specific substrings are substituted\nbefore it is inserted into the result.\n\nReturns a new string with the pattern replaced." + }, + "json": { + "kind": "function", + "name": "module:core#json", + "return": [ + { + "type": "*" + } + ], + "params": { + "str_or_resource": { + "type": [ + { + "type": "string" + } + ], + "name": "str_or_resource", + "description": "The string or resource object to be parsed as JSON." + } + }, + "subject": "Parse the given string or resource as JSON and return the resulting value.", + "description": "Parse the given string or resource as JSON and return the resulting value.\n\nIf the input argument is a plain string, it is directly parsed as JSON.\n\nIf an array, object or resource value is given, this function will attempt to\ninvoke a `read()` method on it to read chunks of input text to incrementally\nparse as JSON data. Reading will stop if the object's `read()` method returns\neither `null` or an empty string.\n\nThrows an exception on parse errors, trailing garbage, or premature EOF.\n\nReturns the parsed JSON data." + }, + "include": { + "kind": "function", + "name": "module:core#include", + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file to be included." + }, + "scope": { + "type": [ + { + "type": "object" + } + ], + "name": "[scope]", + "description": "The optional scope object to override the execution scope.", + "optional": true + } + }, + "subject": "Evaluate and include the file at the given path and optionally override the\nexecution scope with the given scope object.", + "description": "Evaluate and include the file at the given path and optionally override the\nexecution scope with the given scope object.\n\nBy default, the file is executed within the same scope as the calling\n`include()`, but by passing an object as the second argument, it is possible\nto extend the scope available to the included file.\n\nThis is useful to supply additional properties as global variables to the\nincluded code. To sandbox included code, that is giving it only access to\nexplicitly provided properties, the `proto()` function can be used to create\na scope object with an empty prototype." + }, + "render": { + "kind": "function", + "name": "module:core#render", + "return": [ + { + "type": "string" + } + ], + "params": { + "path_or_func": { + "type": [ + { + "type": "string" + }, + { + "type": "function" + } + ], + "name": "path_or_func", + "description": "The path to the file or the function to be rendered." + }, + "scope_or_fnarg1": { + "type": [ + { + "type": "object" + }, + { + "type": "*" + } + ], + "name": "[scope_or_fnarg1]", + "description": "The optional scope or the first argument for the function.", + "optional": true + }, + "fnarg2": { + "type": [ + { + "type": "*" + } + ], + "name": "[fnarg2]", + "description": "The second argument for the function.", + "optional": true + }, + "fnargN": { + "type": [ + { + "type": "...*" + } + ], + "name": "[fnargN]", + "description": "Additional arguments for the function.", + "optional": true + } + }, + "subject": "When invoked with a string value as the first argument, the function acts\nlike `include()` but captures the output of the included file as a string and\nreturns the captured contents.", + "description": "When invoked with a string value as the first argument, the function acts\nlike `include()` but captures the output of the included file as a string and\nreturns the captured contents.\n\nThe second argument is treated as the scope.\n\nWhen invoked with a function value as the first argument, `render()` calls\nthe given function and passes all subsequent arguments to it.\n\nAny output produced by the called function is captured and returned as a\nstring. The return value of the called function is discarded." + }, + "warn": { + "kind": "function", + "name": "module:core#warn", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "...*" + } + ], + "name": "x", + "description": "The values to be printed." + } + }, + "subject": "Print any of the given values to stderr. Arrays and objects are converted to\ntheir JSON representation.", + "description": "Print any of the given values to stderr. Arrays and objects are converted to\ntheir JSON representation.\n\nReturns the amount of bytes printed." + }, + "system": { + "kind": "function", + "name": "module:core#system", + "return": [ + { + "type": "number" + } + ], + "params": { + "command": { + "type": [ + { + "type": "string" + }, + { + "type": "array" + } + ], + "name": "command", + "description": "The command to be executed." + }, + "timeout": { + "type": [ + { + "type": "number" + } + ], + "name": "[timeout]", + "description": "The optional timeout in milliseconds.", + "optional": true + } + }, + "subject": "Executes the given command, waits for completion, and returns the resulting\nexit code.", + "description": "Executes the given command, waits for completion, and returns the resulting\nexit code.\n\nThe command argument may be either a string, in which case it is passed to\n`/bin/sh -c`, or an array, which is directly converted into an `execv()`\nargument vector.\n\n- If the program terminated normally, a positive integer holding the\nprogram's `exit()` code is returned.\n- If the program was terminated by an uncaught signal, a negative signal\nnumber is returned.\n- If the optional timeout argument is specified, the program is terminated\nby `SIGKILL` after that many milliseconds if it doesn't complete within\nthe timeout.\n\nOmitting the timeout argument or passing `0` disables the command timeout.\n\nReturns the program exit code." + }, + "trace": { + "kind": "function", + "name": "module:core#trace", + "params": { + "level": { + "type": [ + { + "type": "number" + } + ], + "name": "level", + "description": "The level of tracing to enable." + } + }, + "subject": "Enables or disables VM opcode tracing.", + "description": "Enables or disables VM opcode tracing.\n\nWhen invoked with a positive non-zero level, opcode tracing is enabled and\ndebug information is printed to stderr as the program is executed.\n\nInvoking `trace()` with zero as an argument turns off opcode tracing." + }, + "proto": { + "kind": "function", + "name": "module:core#proto", + "return": [ + { + "type": "object", + "nullable": true + } + ], + "params": { + "val": { + "type": [ + { + "type": "array" + }, + { + "type": "object" + } + ], + "name": "val", + "description": "The array or object value." + }, + "proto": { + "type": [ + { + "type": "object" + } + ], + "name": "[proto]", + "description": "The optional prototype object.", + "optional": true + } + }, + "subject": "Get or set the prototype of the array or object value `val`.", + "description": "Get or set the prototype of the array or object value `val`.\n\nWhen invoked without a second argument, the function returns the current\nprototype of the value in `val` or `null` if there is no prototype or if the\ngiven value is neither an object nor an array.\n\nWhen invoked with a second prototype argument, the given `proto` value is set\nas the prototype on the array or object in `val`.\n\nThrows an exception if the given prototype value is not an object." + }, + "sleep": { + "kind": "function", + "name": "module:core#sleep", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "milliseconds": { + "type": [ + { + "type": "number" + } + ], + "name": "milliseconds", + "description": "The amount of milliseconds to sleep." + } + }, + "subject": "Pause execution for the given amount of milliseconds.", + "description": "Pause execution for the given amount of milliseconds." + }, + "assert": { + "kind": "function", + "name": "module:core#assert", + "throws": [ + { + "type": [ + { + "type": "Error" + } + ], + "description": "When the condition is falsy." + } + ], + "params": { + "cond": { + "type": [ + { + "type": "*" + } + ], + "name": "cond", + "description": "The value to check for truthiness." + }, + "message": { + "type": [ + { + "type": "string" + } + ], + "name": "[message]", + "description": "The message to include in the exception.", + "optional": true + } + }, + "subject": "Raise an exception with the given message parameter when the value in `cond`\nis not truish.", + "description": "Raise an exception with the given message parameter when the value in `cond`\nis not truish.\n\nWhen `message` is omitted, the default value is `Assertion failed`." + }, + "regexp": { + "kind": "function", + "name": "module:core#regexp", + "return": [ + { + "type": "RegExp" + } + ], + "params": { + "source": { + "type": [ + { + "type": "string" + } + ], + "name": "source", + "description": "The pattern string." + }, + "flags": { + "type": [ + { + "type": "string" + } + ], + "name": "[flags]", + "description": "The optional regular expression flags.", + "optional": true + } + }, + "subject": "Construct a regular expression instance from the given `source` pattern\nstring and any flags optionally specified by the `flags` argument.", + "description": "Construct a regular expression instance from the given `source` pattern\nstring and any flags optionally specified by the `flags` argument.\n\n- Throws a type error exception if `flags` is not a string or if the string\nin `flags` contains unrecognized regular expression flag characters.\n- Throws a syntax error when the pattern in `source` cannot be compiled into\na valid regular expression.\n\nReturns the compiled regular expression value." + }, + "wildcard": { + "kind": "function", + "name": "module:core#wildcard", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "subject": { + "type": [ + { + "type": "*" + } + ], + "name": "subject", + "description": "The subject to match against the wildcard pattern." + }, + "pattern": { + "type": [ + { + "type": "string" + } + ], + "name": "pattern", + "description": "The wildcard pattern." + }, + "nocase": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[nocase]", + "description": "Whether to perform case-insensitive matching.", + "optional": true + } + }, + "subject": "Match the given subject against the supplied wildcard (file glob) pattern.", + "description": "Match the given subject against the supplied wildcard (file glob) pattern.\n\n- If a truthy value is supplied as the third argument, case-insensitive\nmatching is performed.\n- If a non-string value is supplied as the subject, it is converted into a\nstring before being matched.\n\nReturns `true` when the value matched the given pattern, otherwise `false`." + }, + "sourcepath": { + "kind": "function", + "name": "module:core#sourcepath", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "depth": { + "type": [ + { + "type": "number" + } + ], + "name": "[depth=0]", + "description": "The depth to walk up the call stack.", + "default": "0", + "optional": true + }, + "dironly": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[dironly]", + "description": "Whether to return only the directory portion of the source file path.", + "optional": true + } + }, + "subject": "Determine the path of the source file currently being executed by ucode.", + "description": "Determine the path of the source file currently being executed by ucode." + }, + "min": { + "kind": "function", + "name": "module:core#min", + "return": [ + { + "type": "*" + } + ], + "params": { + "val": { + "type": [ + { + "type": "...*" + } + ], + "name": "[val]", + "description": "The values to compare.", + "optional": true + } + }, + "subject": "Return the smallest value among all parameters passed to the function.", + "description": "Return the smallest value among all parameters passed to the function." + }, + "max": { + "kind": "function", + "name": "module:core#max", + "return": [ + { + "type": "*" + } + ], + "params": { + "val": { + "type": [ + { + "type": "...*" + } + ], + "name": "[val]", + "description": "The values to compare.", + "optional": true + } + }, + "subject": "Return the largest value among all parameters passed to the function.", + "description": "Return the largest value among all parameters passed to the function." + }, + "b64dec": { + "kind": "function", + "name": "module:core#b64dec", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "str": { + "type": [ + { + "type": "string" + } + ], + "name": "str", + "description": "The base64 encoded string to decode." + } + }, + "subject": "Decodes the given base64 encoded string and returns the decoded result.", + "description": "Decodes the given base64 encoded string and returns the decoded result.\n\n- If non-whitespace, non-base64 characters are encountered, if invalid\npadding or trailing garbage is found, the function returns `null`.\n- If a non-string argument is given, the function returns `null`." + }, + "b64enc": { + "kind": "function", + "name": "module:core#b64enc", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "str": { + "type": [ + { + "type": "string" + } + ], + "name": "str", + "description": "The string to encode." + } + }, + "subject": "Encodes the given string into base64 and returns the resulting string.", + "description": "Encodes the given string into base64 and returns the resulting string.\n\n- If a non-string argument is given, the function returns `null`." + }, + "uniq": { + "kind": "function", + "name": "module:core#uniq", + "return": [ + { + "type": "array", + "nullable": true + } + ], + "params": { + "array": { + "type": [ + { + "type": "array" + } + ], + "name": "array", + "description": "The input array." + } + }, + "subject": "Returns a new array containing all unique values of the given input array.", + "description": "Returns a new array containing all unique values of the given input array.\n\n- The order is preserved, and subsequent duplicate values are skipped.\n- If a non-array argument is given, the function returns `null`." + }, + "TimeSpec": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "sec": { + "type": [ + { + "type": "number" + } + ], + "name": "sec", + "description": "Seconds (0..60)" + }, + "min": { + "type": [ + { + "type": "number" + } + ], + "name": "min", + "description": "Minutes (0..59)" + }, + "hour": { + "type": [ + { + "type": "number" + } + ], + "name": "hour", + "description": "Hours (0..23)" + }, + "mday": { + "type": [ + { + "type": "number" + } + ], + "name": "mday", + "description": "Day of month (1..31)" + }, + "mon": { + "type": [ + { + "type": "number" + } + ], + "name": "mon", + "description": "Month (1..12)" + }, + "year": { + "type": [ + { + "type": "number" + } + ], + "name": "year", + "description": "Year (>= 1900)" + }, + "wday": { + "type": [ + { + "type": "number" + } + ], + "name": "wday", + "description": "Day of week (1..7, Sunday = 7)" + }, + "yday": { + "type": [ + { + "type": "number" + } + ], + "name": "yday", + "description": "Day of year (1-366, Jan 1st = 1)" + }, + "isdst": { + "type": [ + { + "type": "number" + } + ], + "name": "isdst", + "description": "Daylight saving time in effect (yes = 1)" + } + } + } + ], + "name": "module:core.TimeSpec", + "subject": "A time spec is a plain object describing a point in time, it is returned by\nthe {@link module:core#gmtime|gmtime()} and\n{@link module:core#localtime|localtime()} functions and expected as parameter\nby the complementary {@link module:core#timegm|timegm()} and\n{@link module:core#timelocal|timelocal()} functions.", + "description": "A time spec is a plain object describing a point in time, it is returned by\nthe {@link module:core#gmtime|gmtime()} and\n{@link module:core#localtime|localtime()} functions and expected as parameter\nby the complementary {@link module:core#timegm|timegm()} and\n{@link module:core#timelocal|timelocal()} functions.\n\nWhen returned by `gmtime()` or `localtime()`, all members of the object will\nbe initialized, when passed as argument to `timegm()` or `timelocal()`, most\nmember values are optional." + }, + "localtime": { + "kind": "function", + "name": "module:core#localtime", + "return": [ + { + "type": "module:core.TimeSpec" + } + ], + "params": { + "epoch": { + "type": [ + { + "type": "number" + } + ], + "name": "[epoch]", + "description": "The epoch timestamp.", + "optional": true + } + }, + "subject": "Return the given epoch timestamp (or now, if omitted) as a dictionary\ncontaining broken-down date and time information according to the local\nsystem timezone.", + "description": "Return the given epoch timestamp (or now, if omitted) as a dictionary\ncontaining broken-down date and time information according to the local\nsystem timezone.\n\nSee {@link module:core.TimeSpec|TimeSpec} for a description of the fields.\n\nNote that in contrast to the underlying `localtime(3)` C library function,\nthe values for `mon`, `wday`, and `yday` are 1-based, and the `year` is\n1900-based." + }, + "gmtime": { + "kind": "function", + "name": "module:core#gmtime", + "return": [ + { + "type": "module:core.TimeSpec" + } + ], + "params": { + "epoch": { + "type": [ + { + "type": "number" + } + ], + "name": "[epoch]", + "description": "The epoch timestamp.", + "optional": true + } + }, + "subject": "Like `localtime()` but interpreting the given epoch value as UTC time.", + "description": "Like `localtime()` but interpreting the given epoch value as UTC time.\n\nSee {@link module:core#localtime|localtime()} for details on the return value." + }, + "timelocal": { + "kind": "function", + "name": "module:core#timelocal", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "datetimespec": { + "type": [ + { + "type": "module:core.TimeSpec" + } + ], + "name": "datetimespec", + "description": "The broken-down date and time dictionary." + } + }, + "subject": "Performs the inverse operation of {@link module:core#localtime|localtime()}\nby taking a broken-down date and time dictionary and transforming it into an\nepoch value according to the local system timezone.", + "description": "Performs the inverse operation of {@link module:core#localtime|localtime()}\nby taking a broken-down date and time dictionary and transforming it into an\nepoch value according to the local system timezone.\n\nThe `wday` and `yday` fields of the given date time specification are\nignored. Field values outside of their valid range are internally normalized,\ne.g. October 40th is interpreted as November 9th.\n\nReturns the resulting epoch value or null if the input date time dictionary\nwas invalid or if the date time specification cannot be represented as epoch\nvalue." + }, + "timegm": { + "kind": "function", + "name": "module:core#timegm", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "datetimespec": { + "type": [ + { + "type": "module:core.TimeSpec" + } + ], + "name": "datetimespec", + "description": "The broken-down date and time dictionary." + } + }, + "subject": "Like `timelocal()` but interpreting the given date time specification as UTC\ntime.", + "description": "Like `timelocal()` but interpreting the given date time specification as UTC\ntime.\n\nSee {@link module:core#timelocal|timelocal()} for details." + }, + "clock": { + "kind": "function", + "name": "module:core#clock", + "return": [ + { + "type": "array", + "itemtype": [ + { + "type": "number" + } + ], + "nullable": true + } + ], + "params": { + "monotonic": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[monotonic]", + "description": "Whether to query the monotonic system clock.", + "optional": true + } + }, + "subject": "Reads the current second and microsecond value of the system clock.", + "description": "Reads the current second and microsecond value of the system clock.\n\nBy default, the realtime clock is queried which might skew forwards or\nbackwards due to NTP changes, system sleep modes etc. If a truish value is\npassed as argument, the monotonic system clock is queried instead, which will\nreturn the monotonically increasing time since some arbitrary point in the\npast (usually the system boot time).\n\nReturns a two element array containing the full seconds as the first element\nand the nanosecond fraction as the second element.\n\nReturns `null` if a monotonic clock value is requested and the system does\nnot implement this clock type." + }, + "hexenc": { + "kind": "function", + "name": "module:core#hexenc", + "return": [ + { + "type": "string" + } + ], + "params": { + "val": { + "type": [ + { + "type": "string" + } + ], + "name": "val", + "description": "The byte string to encode." + } + }, + "subject": "Encodes the given byte string into a hexadecimal digit string, converting\nthe input value to a string if needed.", + "description": "Encodes the given byte string into a hexadecimal digit string, converting\nthe input value to a string if needed." + }, + "hexdec": { + "kind": "function", + "name": "module:core#hexdec", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "hexstring": { + "type": [ + { + "type": "string" + } + ], + "name": "hexstring", + "description": "The hexadecimal digit string to decode." + }, + "skipchars": { + "type": [ + { + "type": "string" + } + ], + "name": "[skipchars]", + "description": "The characters to skip during decoding.", + "optional": true + } + }, + "subject": "Decodes the given hexadecimal digit string into a byte string, optionally\nskipping specified characters.", + "description": "Decodes the given hexadecimal digit string into a byte string, optionally\nskipping specified characters.\n\nIf the characters to skip are not specified, a default of `\" \\t\\n\"` is used.\n\nReturns null if the input string contains invalid characters or an uneven\namount of hex digits.\n\nReturns the decoded byte string on success." + }, + "gc": { + "kind": "function", + "name": "module:core#gc", + "return": [ + { + "type": "(boolean|number)", + "nullable": true + } + ], + "params": { + "operation": { + "type": [ + { + "type": "string" + } + ], + "name": "[operation]", + "description": "The operation to perform.", + "optional": true + }, + "argument": { + "type": [ + { + "type": "*" + } + ], + "name": "[argument]", + "description": "The argument for the operation.", + "optional": true + } + }, + "subject": "Interacts with the mark and sweep garbage collector of the running ucode\nvirtual machine.", + "description": "Interacts with the mark and sweep garbage collector of the running ucode\nvirtual machine.\n\nDepending on the given `operation` string argument, the meaning of `argument`\nand the function return value differs.\n\nThe following operations are defined:\n\n- `collect` - Perform a complete garbage collection cycle, returns `true`.\n- `start` - (Re-)start periodic garbage collection, `argument` is an optional\ninteger in the range `1..65535` specifying the interval.\nDefaults to `1000` if omitted. Returns `true` if the periodic GC\nwas previously stopped and is now started or if the interval\nchanged. Returns `false` otherwise.\n- `stop` - Stop periodic garbage collection. Returns `true` if the periodic\nGC was previously started and is now stopped, `false` otherwise.\n- `count` - Count the amount of active complex object references in the VM\ncontext, returns the counted amount.\n\nIf the `operation` argument is omitted, the default is `collect`." + }, + "ParseConfig": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "lstrip_blocks": { + "type": [ + { + "type": "boolean" + } + ], + "name": "lstrip_blocks", + "description": "Whether to strip whitespace preceding template directives.\nSee {@link tutorial-02-syntax.html#whitespace-handling|Whitespace handling}." + }, + "trim_blocks": { + "type": [ + { + "type": "boolean" + } + ], + "name": "trim_blocks", + "description": "Whether to trim trailing newlines following template directives.\nSee {@link tutorial-02-syntax.html#whitespace-handling|Whitespace handling}." + }, + "strict_declarations": { + "type": [ + { + "type": "boolean" + } + ], + "name": "strict_declarations", + "description": "Whether to compile the code in strict mode (`true`) or not (`false`)." + }, + "raw_mode": { + "type": [ + { + "type": "boolean" + } + ], + "name": "raw_mode", + "description": "Whether to compile the code in plain script mode (`true`) or not (`false`)." + }, + "module_search_path": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "string" + } + ] + } + ], + "name": "module_search_path", + "description": "Override the module search path for compile time imports while compiling the\nucode source." + }, + "force_dynlink_list": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "string" + } + ] + } + ], + "name": "force_dynlink_list", + "description": "List of module names assumed to be dynamic library extensions, allows\ncompiling ucode source with import statements referring to `*.so` extensions\nnot present at compile time." + } + } + } + ], + "name": "module:core.ParseConfig", + "subject": "A parse configuration is a plain object describing options to use when\ncompiling ucode at runtime. It is expected as parameter by the\n{@link module:core#loadfile|loadfile()} and\n{@link module:core#loadstring|loadstring()} functions.", + "description": "A parse configuration is a plain object describing options to use when\ncompiling ucode at runtime. It is expected as parameter by the\n{@link module:core#loadfile|loadfile()} and\n{@link module:core#loadstring|loadstring()} functions.\n\nAll members of the parse configuration object are optional and will default\nto the state of the running ucode file if omitted." + }, + "loadstring": { + "kind": "function", + "name": "module:core#loadstring", + "return": [ + { + "type": "function" + } + ], + "params": { + "code": { + "type": [ + { + "type": "string" + } + ], + "name": "code", + "description": "The code string to compile." + }, + "options": { + "type": [ + { + "type": "module:core.ParseConfig" + } + ], + "name": "[options]", + "description": "The options for compilation.", + "optional": true + } + }, + "subject": "Compiles the given code string into a ucode program and returns the resulting\nprogram entry function.", + "description": "Compiles the given code string into a ucode program and returns the resulting\nprogram entry function.\n\nThe optional `options` dictionary overrides parse and compile options.\n\n- If a non-string `code` argument is given, it is implicitly converted to a\nstring value first.\n- If `options` is omitted or a non-object value, the compile options of the\nrunning ucode program are reused.\n\nSee {@link module:core.ParseConfig|ParseConfig} for known keys within the\n`options` object. Unrecognized keys are ignored, unspecified options default\nto those of the running program.\n\nReturns the compiled program entry function.\n\nThrows an exception on compilation errors." + }, + "loadfile": { + "kind": "function", + "name": "module:core#loadfile", + "return": [ + { + "type": "function" + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path of the file to compile." + }, + "options": { + "type": [ + { + "type": "module:core.ParseConfig" + } + ], + "name": "[options]", + "description": "The options for compilation.", + "optional": true + } + }, + "subject": "Compiles the given file into a ucode program and returns the resulting\nprogram entry function.", + "description": "Compiles the given file into a ucode program and returns the resulting\nprogram entry function.\n\nSee {@link module:core#loadstring|`loadstring()`} for details.\n\nReturns the compiled program entry function.\n\nThrows an exception on compilation or file I/O errors." + }, + "call": { + "kind": "function", + "name": "module:core#call", + "return": [ + { + "type": "*" + } + ], + "params": { + "fn": { + "type": [ + { + "type": "function" + } + ], + "name": "fn", + "description": "Function value to call." + }, + "ctx": { + "type": [ + { + "type": "*" + } + ], + "name": "[ctx=null]", + "description": "`this` context for the invoked function.", + "default": "null", + "optional": true + }, + "scope": { + "type": [ + { + "type": "object" + } + ], + "name": "[scope=null]", + "description": "Global environment for the invoked function.", + "default": "null", + "optional": true + }, + "arg": { + "type": [ + { + "type": "...*" + } + ], + "name": "[arg]", + "description": "Additional arguments to pass to the invoked function.", + "optional": true + } + }, + "subject": "Calls the given function value with a modified environment.", + "description": "Calls the given function value with a modified environment.\n\nThe given `ctx` argument is used as `this` context for the invoked function\nand the given `scope` value as global environment. Any further arguments are\npassed to the invoked function as-is.\n\nWhen `ctx` is omitted or `null`, the function will get invoked with `this`\nbeing `null`.\n\nWhen `scope` is omitted or `null`, the function will get executed with the\ncurrent global environment of the running program. When `scope` is set to a\ndictionary, the dictionary is used as global function environment.\n\nWhen the `scope` dictionary has no prototype, the current global environment\nwill be set as prototype, means the scope will inherit from it.\n\nWhen a scope prototype is set, it is kept. This allows passing an isolated\n(sandboxed) function scope without access to the global environment.\n\nAny further argument is forwarded as-is to the invoked function as function\ncall argument.\n\nReturns `null` if the given function value `fn` is not callable.\n\nReturns the return value of the invoked function in all other cases.\n\nForwards exceptions thrown by the invoked function." + }, + "signal": { + "kind": "function", + "name": "module:core#signal", + "return": [ + { + "type": "function" + }, + { + "type": "string" + } + ], + "params": { + "signal": { + "type": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "name": "signal", + "description": "The signal to query/set handler for." + }, + "handler": { + "type": [ + { + "type": "function" + }, + { + "type": "string" + } + ], + "name": "[handler]", + "description": "The signal handler to install for the given signal.", + "optional": true + } + }, + "subject": "Set or query process signal handler function.", + "description": "Set or query process signal handler function.\n\nWhen invoked with two arguments, a signal specification and a signal handler\nvalue, this function configures a new process signal handler.\n\nWhen invoked with one argument, a signal specification, this function returns\nthe currently configured handler for the given signal.\n\nThe signal specification might either be an integer signal number or a string\nvalue containing a signal name (with or without \"SIG\" prefix). Signal names\nare treated case-insensitively.\n\nThe signal handler might be either a callable function value or one of the\ntwo special string values `\"ignore\"` and `\"default\"`. Passing `\"ignore\"` will\nmask the given process signal while `\"default\"` will restore the operating\nsystems default behaviour for the given signal.\n\nIn case a callable handler function is provided, it is invoked at the\nearliest opportunity after receiving the corresponding signal from the\noperating system. The invoked function will receive a single argument, the\nnumber of the signal it is invoked for.\n\nNote that within the ucode VM, process signals are not immediately delivered,\ninstead the VM keeps track of received signals and delivers them to the ucode\nscript environment at the next opportunity, usually before executing the next\nbyte code instruction. This means that if a signal is received while\nperforming a computationally expensive operation in C mode, such as a complex\nregexp match, the corresponding ucode signal handler will only be invoked\nafter that operation concluded and control flow returns to the VM.\n\nReturns the signal handler function or one of the special values `\"ignore\"`\nor `\"default\"` corresponding to the given signal specification.\n\nReturns `null` if an invalid signal spec or signal handler was provided.\n\nReturns `null` if changing the signal action failed, e.g. due to insufficient\npermission, or when attempting to ignore a non-ignorable signal." + } + }, + "debug": { + "memdump": { + "kind": "function", + "name": "module:debug#memdump", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "file": { + "type": [ + { + "type": "string" + }, + { + "type": "module:fs.file" + }, + { + "type": "module:fs.proc" + } + ], + "name": "file", + "description": "The file path or open file handle to write report to." + } + }, + "subject": "Write a memory dump report to the given file.", + "description": "Write a memory dump report to the given file.\n\nThis function generates a human readable memory dump of ucode values\ncurrently managed by the running VM which is useful to track down logical\nmemory leaks in scripts.\n\nThe file parameter can be either a string value containing a file path, in\nwhich case this function tries to create and write the report file at the\ngiven location, or an already open file handle this function should write to.\n\nReturns `true` if the report has been written.\n\nReturns `null` if the file could not be opened or if the handle was invalid." + }, + "traceback": { + "kind": "function", + "name": "module:debug#traceback", + "return": [ + { + "type": "array", + "itemtype": [ + { + "type": "module:debug.StackTraceEntry" + } + ] + } + ], + "params": { + "level": { + "type": [ + { + "type": "number" + } + ], + "name": "[level=1]", + "description": "The number of callframes up the call trace should start, `0` is this function\nitself, `1` the function calling it and so on.", + "default": "1", + "optional": true + } + }, + "subject": "Capture call stack trace.", + "description": "Capture call stack trace.\n\nThis function captures the current call stack and returns it. The optional\nlevel parameter controls how many calls up the trace should start. It\ndefaults to `1`, that is the function calling this `traceback()` function.\n\nReturns an array of stack trace entries describing the function invocations\nup to the point where `traceback()` is called." + }, + "StackTraceEntry": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "callee": { + "type": [ + { + "type": "function" + } + ], + "name": "callee", + "description": "The function that was called." + }, + "this": { + "type": [ + { + "type": "*" + } + ], + "name": "this", + "description": "The `this` context the function was called with." + }, + "mcall": { + "type": [ + { + "type": "boolean" + } + ], + "name": "mcall", + "description": "Indicates whether the function was invoked as a method." + }, + "strict": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[strict]", + "description": "Indicates whether the VM was running in strict mode when the function was\ncalled (only applicable to non-C, pure ucode calls)." + }, + "filename": { + "type": [ + { + "type": "string" + } + ], + "name": "[filename]", + "description": "The name of the source file that called the function (only applicable to\nnon-C, pure ucode calls)." + }, + "line": { + "type": [ + { + "type": "number" + } + ], + "name": "[line]", + "description": "The source line of the function call (only applicable to non-C, pure ucode\ncalls)." + }, + "byte": { + "type": [ + { + "type": "number" + } + ], + "name": "[byte]", + "description": "The source line offset of the function call (only applicable to non-C, pure\nucode calls)." + }, + "context": { + "type": [ + { + "type": "string" + } + ], + "name": "[context]", + "description": "The surrounding source code context formatted as human-readable string,\nuseful for generating debug messages (only applicable to non-C, pure ucode\ncalls)." + } + } + } + ], + "name": "module:debug.StackTraceEntry", + "optional": true, + "description": "" + }, + "sourcepos": { + "kind": "function", + "name": "module:debug#sourcepos", + "return": [ + { + "type": "module:debug.SourcePosition", + "nullable": true + } + ], + "subject": "Obtain information about the current source position.", + "description": "Obtain information about the current source position.\n\nThe `sourcepos()` function determines the source code position of the\ncurrent instruction invoking this function.\n\nReturns a dictionary containing the filename, line number and line byte\noffset of the call site.\n\nReturns `null` if this function was invoked from C code." + }, + "SourcePosition": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "filename": { + "type": [ + { + "type": "string" + } + ], + "name": "filename", + "description": "The name of the source file that called this function." + }, + "line": { + "type": [ + { + "type": "number" + } + ], + "name": "line", + "description": "The source line of the function call." + }, + "byte": { + "type": [ + { + "type": "number" + } + ], + "name": "byte", + "description": "The source line offset of the function call." + } + } + } + ], + "name": "module:debug.SourcePosition", + "description": "" + }, + "UpvalRef": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "name": { + "type": [ + { + "type": "string" + } + ], + "name": "name", + "description": "The name of the captured variable." + }, + "closed": { + "type": [ + { + "type": "boolean" + } + ], + "name": "closed", + "description": "Indicates whether the captured variable (upvalue) is closed or not. A closed\nupvalue means that the function value outlived the declaration scope of the\ncaptured variable." + }, + "value": { + "type": [ + { + "type": "*" + } + ], + "name": "value", + "description": "The current value of the captured variable." + }, + "slot": { + "type": [ + { + "type": "number" + } + ], + "name": "[slot]", + "description": "The stack slot of the captured variable. Only applicable to open (non-closed)\ncaptured variables." + } + } + } + ], + "name": "module:debug.UpvalRef", + "optional": true, + "description": "" + }, + "getinfo": { + "kind": "function", + "name": "module:debug#getinfo", + "return": [ + { + "type": "module:debug.ValueInformation", + "nullable": true + } + ], + "params": { + "value": { + "type": [ + { + "type": "*" + } + ], + "name": "value", + "description": "The value to query information for." + } + }, + "subject": "Obtain information about the given value.", + "description": "Obtain information about the given value.\n\nThe `getinfo()` function allows querying internal information about the\ngiven ucode value, such as the current reference count, the mark bit state\netc.\n\nReturns a dictionary with value type specific details.\n\nReturns `null` if a `null` value was provided." + }, + "ValueInformation": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "type": { + "type": [ + { + "type": "string" + } + ], + "name": "type", + "description": "The name of the value type, one of `integer`, `boolean`, `string`, `double`,\n`array`, `object`, `regexp`, `cfunction`, `closure`, `upvalue` or `resource`." + }, + "value": { + "type": [ + { + "type": "*" + } + ], + "name": "value", + "description": "The value itself." + }, + "tagged": { + "type": [ + { + "type": "boolean" + } + ], + "name": "tagged", + "description": "Indicates whether the given value is internally stored as tagged pointer\nwithout an additional heap allocation." + }, + "mark": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[mark]", + "description": "Indicates whether the value has it's mark bit set, which is used for loop\ndetection during recursive object traversal on garbage collection cycles or\ncomplex value stringification. Only applicable to non-tagged values." + }, + "refcount": { + "type": [ + { + "type": "number" + } + ], + "name": "[refcount]", + "description": "The current reference count of the value. Note that the `getinfo()` function\nplaces a reference to the value into the `value` field of the resulting\ninformation dictionary, so the ref count will always be at least 2 - one\nreference for the function call argument and one for the value property in\nthe result dictionary. Only applicable to non-tagged values." + }, + "unsigned": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[unsigned]", + "description": "Whether the number value is internally stored as unsigned integer. Only\napplicable to non-tagged integer values." + }, + "address": { + "type": [ + { + "type": "number" + } + ], + "name": "[address]", + "description": "The address of the underlying C heap memory. Only applicable to non-tagged\n`string`, `array`, `object`, `cfunction` or `resource` values." + }, + "length": { + "type": [ + { + "type": "number" + } + ], + "name": "[length]", + "description": "The length of the underlying string memory. Only applicable to non-tagged\n`string` values." + }, + "count": { + "type": [ + { + "type": "number" + } + ], + "name": "[count]", + "description": "The amount of elements in the underlying memory structure. Only applicable to\n`array` and `object` values." + }, + "constant": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[constant]", + "description": "Indicates whether the value is constant (immutable). Only applicable to\n`array` and `object` values." + }, + "prototype": { + "type": [ + { + "type": "*" + } + ], + "name": "[prototype]", + "description": "The associated prototype value, if any. Only applicable to `array`, `object`\nand `prototype` values." + }, + "source": { + "type": [ + { + "type": "string" + } + ], + "name": "[source]", + "description": "The original regex source pattern. Only applicable to `regexp` values." + }, + "icase": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[icase]", + "description": "Whether the compiled regex has the `i` (ignore case) flag set. Only\napplicable to `regexp` values." + }, + "global": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[global]", + "description": "Whether the compiled regex has the `g` (global) flag set. Only applicable to\n`regexp` values." + }, + "newline": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[newline]", + "description": "Whether the compiled regex has the `s` (single line) flag set. Only\napplicable to `regexp` values." + }, + "nsub": { + "type": [ + { + "type": "number" + } + ], + "name": "[nsub]", + "description": "The amount of capture groups within the regular expression. Only applicable\nto `regexp` values." + }, + "name": { + "type": [ + { + "type": "string" + } + ], + "name": "[name]", + "description": "The name of the non-anonymous function. Only applicable to `cfunction` and\n`closure` values. Set to `null` for anonymous function values." + }, + "arrow": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[arrow]", + "description": "Indicates whether the ucode function value is an arrow function. Only\napplicable to `closure` values." + }, + "module": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[module]", + "description": "Indicates whether the ucode function value is a module entry point. Only\napplicable to `closure` values." + }, + "strict": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[strict]", + "description": "Indicates whether the function body will be executed in strict mode. Only\napplicable to `closure` values." + }, + "vararg": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[vararg]", + "description": "Indicates whether the ucode function takes a variable number of arguments.\nOnly applicable to `closure` values." + }, + "nargs": { + "type": [ + { + "type": "number" + } + ], + "name": "[nargs]", + "description": "The number of arguments expected by the ucode function, excluding a potential\nfinal variable argument ellipsis. Only applicable to `closure` values." + }, + "argnames": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "string" + } + ] + } + ], + "name": "[argnames]", + "description": "The names of the function arguments in their declaration order. Only\napplicable to `closure` values." + }, + "nupvals": { + "type": [ + { + "type": "number" + } + ], + "name": "[nupvals]", + "description": "The number of upvalues associated with the ucode function. Only applicable to\n`closure` values." + }, + "upvals": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "module:debug.UpvalRef" + } + ] + } + ], + "name": "[upvals]", + "description": "An array of upvalue information objects. Only applicable to `closure` values." + }, + "filename": { + "type": [ + { + "type": "string" + } + ], + "name": "[filename]", + "description": "The path of the source file the function was declared in. Only applicable to\n`closure` values." + }, + "line": { + "type": [ + { + "type": "number" + } + ], + "name": "[line]", + "description": "The source line number the function was declared at. Only applicable to\n`closure` values." + }, + "byte": { + "type": [ + { + "type": "number" + } + ], + "name": "[byte]", + "description": "The source line offset the function was declared at. Only applicable to\n`closure` values." + } + } + } + ], + "name": "module:debug.ValueInformation", + "optional": true, + "description": "" + }, + "LocalInfo": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "index": { + "type": [ + { + "type": "number" + } + ], + "name": "index", + "description": "The index of the local variable." + }, + "name": { + "type": [ + { + "type": "string" + } + ], + "name": "name", + "description": "The name of the local variable." + }, + "value": { + "type": [ + { + "type": "*" + } + ], + "name": "value", + "description": "The current value of the local variable." + }, + "linefrom": { + "type": [ + { + "type": "number" + } + ], + "name": "linefrom", + "description": "The source line number of the local variable declaration." + }, + "bytefrom": { + "type": [ + { + "type": "number" + } + ], + "name": "bytefrom", + "description": "The source line offset of the local variable declaration." + }, + "lineto": { + "type": [ + { + "type": "number" + } + ], + "name": "lineto", + "description": "The source line number where the local variable goes out of scope." + }, + "byteto": { + "type": [ + { + "type": "number" + } + ], + "name": "byteto", + "description": "The source line offset where the local vatiable goes out of scope." + } + } + } + ], + "name": "module:debug.LocalInfo", + "description": "" + }, + "getlocal": { + "kind": "function", + "name": "module:debug#getlocal", + "return": [ + { + "type": "module:debug.LocalInfo", + "nullable": true + } + ], + "params": { + "level": { + "type": [ + { + "type": "number" + } + ], + "name": "[level=1]", + "description": "The amount of call stack levels up local variables should be queried.", + "default": "1", + "optional": true + }, + "variable": { + "type": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "name": "variable", + "description": "The variable index or variable name to obtain information for." + } + }, + "subject": "Obtain local variable.", + "description": "Obtain local variable.\n\nThe `getlocal()` function retrieves information about the specified local\nvariable at the given call stack depth.\n\nThe call stack depth specifies the amount of levels up local variables should\nbe queried. A value of `0` refers to this `getlocal()` function call itself,\n`1` to the function calling `getlocal()` and so on.\n\nThe variable to query might be either specified by name or by its index with\nindex numbers following the source code declaration order.\n\nReturns a dictionary holding information about the given variable.\n\nReturns `null` if the stack depth exceeds the size of the current call stack.\n\nReturns `null` if the invocation at the given stack depth is a C call.\n\nReturns `null` if the given variable name is not found or the given variable\nindex is invalid." + }, + "setlocal": { + "kind": "function", + "name": "module:debug#setlocal", + "return": [ + { + "type": "module:debug.LocalInfo", + "nullable": true + } + ], + "params": { + "level": { + "type": [ + { + "type": "number" + } + ], + "name": "[level=1]", + "description": "The amount of call stack levels up local variables should be updated.", + "default": "1", + "optional": true + }, + "variable": { + "type": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "name": "variable", + "description": "The variable index or variable name to update." + }, + "value": { + "type": [ + { + "type": "*" + } + ], + "name": "[value=null]", + "description": "The value to set the local variable to.", + "default": "null", + "optional": true + } + }, + "subject": "Set local variable.", + "description": "Set local variable.\n\nThe `setlocal()` function manipulates the value of the specified local\nvariable at the given call stack depth.\n\nThe call stack depth specifies the amount of levels up local variables should\nbe updated. A value of `0` refers to this `setlocal()` function call itself,\n`1` to the function calling `setlocal()` and so on.\n\nThe variable to update might be either specified by name or by its index with\nindex numbers following the source code declaration order.\n\nReturns a dictionary holding information about the updated variable.\n\nReturns `null` if the stack depth exceeds the size of the current call stack.\n\nReturns `null` if the invocation at the given stack depth is a C call.\n\nReturns `null` if the given variable name is not found or the given variable\nindex is invalid." + }, + "UpvalInfo": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "index": { + "type": [ + { + "type": "number" + } + ], + "name": "index", + "description": "The index of the captured variable (upvalue)." + }, + "name": { + "type": [ + { + "type": "string" + } + ], + "name": "name", + "description": "The name of the captured variable." + }, + "closed": { + "type": [ + { + "type": "boolean" + } + ], + "name": "closed", + "description": "Indicates whether the captured variable is closed or not. A closed upvalue\nmeans that the function outlived the declaration scope of the captured\nvariable." + }, + "value": { + "type": [ + { + "type": "*" + } + ], + "name": "value", + "description": "The current value of the captured variable." + } + } + } + ], + "name": "module:debug.UpvalInfo", + "description": "" + }, + "getupval": { + "kind": "function", + "name": "module:debug#getupval", + "return": [ + { + "type": "module:debug.UpvalInfo", + "nullable": true + } + ], + "params": { + "target": { + "type": [ + { + "type": "function" + }, + { + "type": "number" + } + ], + "name": "target", + "description": "Either a function value referring to a closure to query upvalues for or a\nstack depth number selecting a closure that many levels up." + }, + "variable": { + "type": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "name": "variable", + "description": "The variable index or variable name to obtain information for." + } + }, + "subject": "Obtain captured variable (upvalue).", + "description": "Obtain captured variable (upvalue).\n\nThe `getupval()` function retrieves information about the specified captured\nvariable associated with the given function value or the invoked function at\nthe given call stack depth.\n\nThe call stack depth specifies the amount of levels up the function should be\nselected to query associated captured variables for. A value of `0` refers to\nthis `getupval()` function call itself, `1` to the function calling\n`getupval()` and so on.\n\nThe variable to query might be either specified by name or by its index with\nindex numbers following the source code declaration order.\n\nReturns a dictionary holding information about the given variable.\n\nReturns `null` if the given function value is not a closure.\n\nReturns `null` if the stack depth exceeds the size of the current call stack.\n\nReturns `null` if the invocation at the given stack depth is not a closure.\n\nReturns `null` if the given variable name is not found or the given variable\nindex is invalid." + }, + "setupval": { + "kind": "function", + "name": "module:debug#setupval", + "return": [ + { + "type": "module:debug.UpvalInfo", + "nullable": true + } + ], + "params": { + "target": { + "type": [ + { + "type": "function" + }, + { + "type": "number" + } + ], + "name": "target", + "description": "Either a function value referring to a closure to update upvalues for or a\nstack depth number selecting a closure that many levels up." + }, + "variable": { + "type": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "name": "variable", + "description": "The variable index or variable name to update." + }, + "value": { + "type": [ + { + "type": "*" + } + ], + "name": "value", + "description": "The value to set the variable to." + } + }, + "subject": "Set upvalue.", + "description": "Set upvalue.\n\nThe `setupval()` function manipulates the value of the specified captured\nvariable associated with the given function value or the invoked function at\nthe given call stack depth.\n\nThe call stack depth specifies the amount of levels up the function should be\nselected to update associated captured variables for. A value of `0` refers\nto this `setupval()` function call itself, `1` to the function calling\n`setupval()` and so on.\n\nThe variable to update might be either specified by name or by its index with\nindex numbers following the source code declaration order.\n\nReturns a dictionary holding information about the updated variable.\n\nReturns `null` if the given function value is not a closure.\n\nReturns `null` if the stack depth exceeds the size of the current call stack.\n\nReturns `null` if the invocation at the given stack depth is not a closure.\n\nReturns `null` if the given variable name is not found or the given variable\nindex is invalid." + } + }, + "fs": { + "error": { + "kind": "function", + "name": "module:fs#error", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "subject": "Query error information.", + "description": "Query error information.\n\nReturns a string containing a description of the last occurred error or\n`null` if there is no error information." + }, + "popen": { + "kind": "function", + "name": "module:fs#popen", + "return": [ + { + "type": "module:fs.proc", + "nullable": true + } + ], + "params": { + "command": { + "type": [ + { + "type": "string" + } + ], + "name": "command", + "description": "The command to be executed." + }, + "mode": { + "type": [ + { + "type": "string" + } + ], + "name": "[mode=\"r\"]", + "description": "The open mode of the process handle.", + "default": "\"r\"", + "optional": true + } + }, + "subject": "Starts a process and returns a handle representing the executed process.", + "description": "Starts a process and returns a handle representing the executed process.\n\nThe handle will be connected to the process stdin or stdout, depending on the\nvalue of the mode argument.\n\nThe mode argument may be either \"r\" to open the process for reading (connect\nto its stdin) or \"w\" to open the process for writing (connect to its stdout).\n\nThe mode character \"r\" or \"w\" may be optionally followed by \"e\" to apply the\nFD_CLOEXEC flag onto the open descriptor.\n\nReturns a process handle referring to the executed process.\n\nReturns `null` if an error occurred." + }, + "open": { + "kind": "function", + "name": "module:fs#open", + "return": [ + { + "type": "module:fs.file", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file." + }, + "mode": { + "type": [ + { + "type": "string" + } + ], + "name": "[mode=\"r\"]", + "description": "The file opening mode.", + "default": "\"r\"", + "optional": true + }, + "perm": { + "type": [ + { + "type": "number" + } + ], + "name": "[perm=0o666]", + "description": "The file creation permissions (for modes `w…` and `a…`)", + "default": "0o666", + "optional": true + } + }, + "subject": "Opens a file.", + "description": "Opens a file.\n\nThe mode argument specifies the way the file is opened, it may\nstart with one of the following values:\n\n| Mode | Description |\n|---------|---------------------------------------------------------------------------------------------------------------|\n| \"r\" | Opens a file for reading. The file must exist. |\n| \"w\" | Opens a file for writing. If the file exists, it is truncated. If the file does not exist, it is created. |\n| \"a\" | Opens a file for appending. Data is written at the end of the file. If the file does not exist, it is created. |\n| \"r+\" | Opens a file for both reading and writing. The file must exist. |\n| \"w+\" | Opens a file for both reading and writing. If the file exists, it is truncated. If the file does not exist, it is created. |\n| \"a+\" | Opens a file for both reading and appending. Data can be read and written at the end of the file. If the file does not exist, it is created. |\n\nAdditionally, the following flag characters may be appended to\nthe mode value:\n\n| Flag | Description |\n|---------|---------------------------------------------------------------------------------------------------------------|\n| \"x\" | Opens a file for exclusive creation. If the file exists, the `open` call fails. |\n| \"e\" | Opens a file with the `O_CLOEXEC` flag set, ensuring that the file descriptor is closed on `exec` calls. |\n\nIf the mode is one of `\"w…\"` or `\"a…\"`, the permission argument\ncontrols the filesystem permissions bits used when creating\nthe file.\n\nReturns a file handle object associated with the opened file." + }, + "fdopen": { + "kind": "function", + "name": "module:fs#fdopen", + "return": [ + { + "type": "object" + } + ], + "params": { + "fd": { + "type": [ + { + "type": "number" + } + ], + "name": "fd", + "description": "The file descriptor." + }, + "mode": { + "type": [ + { + "type": "string" + } + ], + "name": "[mode=\"r\"]", + "description": "The open mode.", + "default": "\"r\"", + "optional": true + } + }, + "subject": "Associates a file descriptor number with a file handle object.", + "description": "Associates a file descriptor number with a file handle object.\n\nThe mode argument controls how the file handle object is opened\nand must match the open mode of the underlying descriptor.\n\nIt may be set to one of the following values:\n\n| Mode | Description |\n|---------|--------------------------------------------------------------------------------------------------------------|\n| \"r\" | Opens a file stream for reading. The file descriptor must be valid and opened in read mode. |\n| \"w\" | Opens a file stream for writing. The file descriptor must be valid and opened in write mode. |\n| \"a\" | Opens a file stream for appending. The file descriptor must be valid and opened in write mode. |\n| \"r+\" | Opens a file stream for both reading and writing. The file descriptor must be valid and opened in read/write mode. |\n| \"w+\" | Opens a file stream for both reading and writing. The file descriptor must be valid and opened in read/write mode. |\n| \"a+\" | Opens a file stream for both reading and appending. The file descriptor must be valid and opened in read/write mode. |\n\nReturns the file handle object associated with the file descriptor." + }, + "opendir": { + "kind": "function", + "name": "module:fs#opendir", + "return": [ + { + "type": "module:fs.dir", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the directory." + } + }, + "subject": "Opens a directory and returns a directory handle associated with the open\ndirectory descriptor.", + "description": "Opens a directory and returns a directory handle associated with the open\ndirectory descriptor.\n\nReturns a director handle referring to the open directory.\n\nReturns `null` if an error occurred." + }, + "readlink": { + "kind": "function", + "name": "module:fs#readlink", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the symbolic link." + } + }, + "subject": "Reads the target path of a symbolic link.", + "description": "Reads the target path of a symbolic link.\n\nReturns a string containing the target path.\n\nReturns `null` if an error occurred." + }, + "FileStatResult": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "dev": { + "type": [ + { + "type": "object", + "properties": { + "major": { + "type": [ + { + "type": "number" + } + ], + "name": "dev.major", + "description": "The major device number." + }, + "minor": { + "type": [ + { + "type": "number" + } + ], + "name": "dev.minor", + "description": "The minor device number." + } + } + } + ], + "name": "dev", + "description": "The device information." + }, + "perm": { + "type": [ + { + "type": "object", + "properties": { + "setuid": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.setuid", + "description": "Whether the setuid bit is set." + }, + "setgid": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.setgid", + "description": "Whether the setgid bit is set." + }, + "sticky": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.sticky", + "description": "Whether the sticky bit is set." + }, + "user_read": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.user_read", + "description": "Whether the file is readable by the owner." + }, + "user_write": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.user_write", + "description": "Whether the file is writable by the owner." + }, + "user_exec": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.user_exec", + "description": "Whether the file is executable by the owner." + }, + "group_read": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.group_read", + "description": "Whether the file is readable by the group." + }, + "group_write": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.group_write", + "description": "Whether the file is writable by the group." + }, + "group_exec": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.group_exec", + "description": "Whether the file is executable by the group." + }, + "other_read": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.other_read", + "description": "Whether the file is readable by others." + }, + "other_write": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.other_write", + "description": "Whether the file is writable by others." + }, + "other_exec": { + "type": [ + { + "type": "boolean" + } + ], + "name": "perm.other_exec", + "description": "Whether the file is executable by others." + } + } + } + ], + "name": "perm", + "description": "The file permissions." + }, + "inode": { + "type": [ + { + "type": "number" + } + ], + "name": "inode", + "description": "The inode number." + }, + "mode": { + "type": [ + { + "type": "number" + } + ], + "name": "mode", + "description": "The file mode." + }, + "nlink": { + "type": [ + { + "type": "number" + } + ], + "name": "nlink", + "description": "The number of hard links." + }, + "uid": { + "type": [ + { + "type": "number" + } + ], + "name": "uid", + "description": "The user ID of the owner." + }, + "gid": { + "type": [ + { + "type": "number" + } + ], + "name": "gid", + "description": "The group ID of the owner." + }, + "size": { + "type": [ + { + "type": "number" + } + ], + "name": "size", + "description": "The file size in bytes." + }, + "blksize": { + "type": [ + { + "type": "number" + } + ], + "name": "blksize", + "description": "The block size for file system I/O." + }, + "blocks": { + "type": [ + { + "type": "number" + } + ], + "name": "blocks", + "description": "The number of 512-byte blocks allocated for the file." + }, + "atime": { + "type": [ + { + "type": "number" + } + ], + "name": "atime", + "description": "The timestamp when the file was last accessed." + }, + "mtime": { + "type": [ + { + "type": "number" + } + ], + "name": "mtime", + "description": "The timestamp when the file was last modified." + }, + "ctime": { + "type": [ + { + "type": "number" + } + ], + "name": "ctime", + "description": "The timestamp when the file status was last changed." + }, + "type": { + "type": [ + { + "type": "string" + } + ], + "name": "type", + "description": "The type of the file (\"directory\", \"file\", etc.)." + } + } + } + ], + "name": "module:fs.FileStatResult", + "description": "" + }, + "stat": { + "kind": "function", + "name": "module:fs#stat", + "return": [ + { + "type": "module:fs.FileStatResult", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file or directory." + } + }, + "subject": "Retrieves information about a file or directory.", + "description": "Retrieves information about a file or directory.\n\nReturns an object containing information about the file or directory.\n\nReturns `null` if an error occurred, e.g. due to insufficient permissions." + }, + "lstat": { + "kind": "function", + "name": "module:fs#lstat", + "return": [ + { + "type": "module:fs.FileStatResult", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file or directory." + } + }, + "subject": "Retrieves information about a file or directory, without following symbolic\nlinks.", + "description": "Retrieves information about a file or directory, without following symbolic\nlinks.\n\nReturns an object containing information about the file or directory.\n\nReturns `null` if an error occurred, e.g. due to insufficient permissions." + }, + "mkdir": { + "kind": "function", + "name": "module:fs#mkdir", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the new directory." + } + }, + "subject": "Creates a new directory.", + "description": "Creates a new directory.\n\nReturns `true` if the directory was successfully created.\n\nReturns `null` if an error occurred, e.g. due to inexistent path." + }, + "rmdir": { + "kind": "function", + "name": "module:fs#rmdir", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the directory to be removed." + } + }, + "subject": "Removes the specified directory.", + "description": "Removes the specified directory.\n\nReturns `true` if the directory was successfully removed.\n\nReturns `null` if an error occurred, e.g. due to inexistent path." + }, + "symlink": { + "kind": "function", + "name": "module:fs#symlink", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "target": { + "type": [ + { + "type": "string" + } + ], + "name": "target", + "description": "The target of the symbolic link." + }, + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path of the symbolic link." + } + }, + "subject": "Creates a new symbolic link.", + "description": "Creates a new symbolic link.\n\nReturns `true` if the symlink was successfully created.\n\nReturns `null` if an error occurred, e.g. due to inexistent path." + }, + "unlink": { + "kind": "function", + "name": "module:fs#unlink", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file or symbolic link." + } + }, + "subject": "Removes the specified file or symbolic link.", + "description": "Removes the specified file or symbolic link.\n\nReturns `true` if the unlink operation was successful.\n\nReturns `null` if an error occurred, e.g. due to inexistent path." + }, + "getcwd": { + "kind": "function", + "name": "module:fs#getcwd", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "subject": "Retrieves the current working directory.", + "description": "Retrieves the current working directory.\n\nReturns a string containing the current working directory path.\n\nReturns `null` if an error occurred." + }, + "chdir": { + "kind": "function", + "name": "module:fs#chdir", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the new working directory." + } + }, + "subject": "Changes the current working directory to the specified path.", + "description": "Changes the current working directory to the specified path.\n\nReturns `true` if the permission change was successful.\n\nReturns `null` if an error occurred, e.g. due to insufficient permissions or\ninvalid arguments." + }, + "chmod": { + "kind": "function", + "name": "module:fs#chmod", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file or directory." + }, + "mode": { + "type": [ + { + "type": "number" + } + ], + "name": "mode", + "description": "The new mode (permissions)." + } + }, + "subject": "Changes the permission mode bits of a file or directory.", + "description": "Changes the permission mode bits of a file or directory.\n\nReturns `true` if the permission change was successful.\n\nReturns `null` if an error occurred, e.g. due to insufficient permissions or\ninvalid arguments." + }, + "chown": { + "kind": "function", + "name": "module:fs#chown", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file or directory." + }, + "uid": { + "type": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "name": "[uid=-1]", + "description": "The new owner's user ID. When given as number, it is used as-is, when given\nas string, the user name is resolved to the corresponding uid first.", + "default": "-1", + "optional": true + }, + "gid": { + "type": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "name": "[gid=-1]", + "description": "The new group's ID. When given as number, it is used as-is, when given as\nstring, the group name is resolved to the corresponding gid first.", + "default": "-1", + "optional": true + } + }, + "subject": "Changes the owner and group of a file or directory.", + "description": "Changes the owner and group of a file or directory.\n\nThe user and group may be specified either as uid or gid number respectively,\nor as a string containing the user or group name, in which case it is\nresolved to the proper uid/gid first.\n\nIf either the user or group parameter is omitted or given as `-1`,\nit is not changed.\n\nReturns `true` if the ownership change was successful.\n\nReturns `null` if an error occurred or if a user/group name cannot be\nresolved to a uid/gid value." + }, + "rename": { + "kind": "function", + "name": "module:fs#rename", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "oldPath": { + "type": [ + { + "type": "string" + } + ], + "name": "oldPath", + "description": "The current path of the file or directory." + }, + "newPath": { + "type": [ + { + "type": "string" + } + ], + "name": "newPath", + "description": "The new path of the file or directory." + } + }, + "subject": "Renames or moves a file or directory.", + "description": "Renames or moves a file or directory.\n\nReturns `true` if the rename operation was successful.\n\nReturns `null` if an error occurred." + }, + "dirname": { + "kind": "function", + "name": "module:fs#dirname", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to extract the directory name from." + } + }, + "subject": "Retrieves the directory name of a path.", + "description": "Retrieves the directory name of a path.\n\nReturns the directory name component of the specified path.\n\nReturns `null` if the path argument is not a string." + }, + "basename": { + "kind": "function", + "name": "module:fs#basename", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to extract the base name from." + } + }, + "subject": "Retrieves the base name of a path.", + "description": "Retrieves the base name of a path.\n\nReturns the base name component of the specified path.\n\nReturns `null` if the path argument is not a string." + }, + "lsdir": { + "kind": "function", + "name": "module:fs#lsdir", + "return": [ + { + "type": "array", + "itemtype": [ + { + "type": "string" + } + ], + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the directory." + } + }, + "subject": "Lists the content of a directory.", + "description": "Lists the content of a directory.\n\nReturns a sorted array of the names of files and directories in the specified\ndirectory.\n\nReturns `null` if an error occurred, e.g. if the specified directory cannot\nbe opened." + }, + "mkstemp": { + "kind": "function", + "name": "module:fs#mkstemp", + "return": [ + { + "type": "module:fs.file", + "nullable": true + } + ], + "params": { + "template": { + "type": [ + { + "type": "string" + } + ], + "name": "[template=\"/tmp/XXXXXX\"]", + "description": "The path template to use when forming the temporary file name.", + "default": "\"/tmp/XXXXXX\"", + "optional": true + } + }, + "subject": "Creates a unique, ephemeral temporary file.", + "description": "Creates a unique, ephemeral temporary file.\n\nCreates a new temporary file, opens it in read and write mode, unlinks it and\nreturns a file handle object referring to the yet open but deleted file.\n\nUpon closing the handle, the associated file will automatically vanish from\nthe system.\n\nThe optional path template argument may be used to override the path and name\nchosen for the temporary file. If the path template contains no path element,\n`/tmp/` is prepended, if it does not end with `XXXXXX`, then * `.XXXXXX` is\nappended to it. The `XXXXXX` sequence is replaced with a random value\nensuring uniqueness of the temporary file name.\n\nReturns a file handle object referring to the ephemeral file on success.\n\nReturns `null` if an error occurred, e.g. on insufficient permissions or\ninaccessible directory." + }, + "access": { + "kind": "function", + "name": "module:fs#access", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file or directory." + }, + "mode": { + "type": [ + { + "type": "number" + } + ], + "name": "[mode=\"f\"]", + "description": "Optional access mode.", + "default": "\"f\"", + "optional": true + } + }, + "subject": "Checks the accessibility of a file or directory.", + "description": "Checks the accessibility of a file or directory.\n\nThe optional modes argument specifies the access modes which should be\nchecked. A file is only considered accessible if all access modes specified\nin the modes argument are possible.\n\nThe following modes are recognized:\n\n| Mode | Description |\n|------|---------------------------------------|\n| \"r\" | Tests whether the file is readable. |\n| \"w\" | Tests whether the file is writable. |\n| \"x\" | Tests whether the file is executable. |\n| \"f\" | Tests whether the file exists. |\n\nReturns `true` if the given path is accessible or `false` when it is not.\n\nReturns `null` if an error occurred, e.g. due to inaccessible intermediate\npath components, invalid path arguments etc." + }, + "readfile": { + "kind": "function", + "name": "module:fs#readfile", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file." + }, + "limit": { + "type": [ + { + "type": "number" + } + ], + "name": "[limit]", + "description": "Number of bytes to limit the result to. When omitted, the entire content is\nreturned.", + "optional": true + } + }, + "subject": "Reads the content of a file, optionally limited to the given amount of bytes.", + "description": "Reads the content of a file, optionally limited to the given amount of bytes.\n\nReturns a string containing the file contents.\n\nReturns `null` if an error occurred, e.g. due to insufficient permissions." + }, + "writefile": { + "kind": "function", + "name": "module:fs#writefile", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file." + }, + "data": { + "type": [ + { + "type": "*" + } + ], + "name": "data", + "description": "The data to be written." + }, + "limit": { + "type": [ + { + "type": "number" + } + ], + "name": "[limit]", + "description": "Truncates the amount of data to be written to the specified amount of bytes.\nWhen omitted, the entire content is written.", + "optional": true + } + }, + "subject": "Writes the given data to a file, optionally truncated to the given amount\nof bytes.", + "description": "Writes the given data to a file, optionally truncated to the given amount\nof bytes.\n\nIn case the given data is not a string, it is converted to a string before\nbeing written into the file. String values are written as-is, integer and\ndouble values are written in decimal notation, boolean values are written as\n`true` or `false` while arrays and objects are converted to their JSON\nrepresentation before being written into the file. The `null` value is\nrepresented by an empty string so `writefile(…, null)` would write an empty\nfile. Resource values are written in the form ``, e.g.\n``.\n\nIf resource, array or object values contain a `tostring()` function in their\nprototypes, then this function is invoked to obtain an alternative string\nrepresentation of the value.\n\nIf a file already exists at the given path, it is truncated. If no file\nexists, it is created with default permissions 0o666 masked by the currently\neffective umask.\n\nReturns the number of bytes written.\n\nReturns `null` if an error occurred, e.g. due to insufficient permissions." + }, + "realpath": { + "kind": "function", + "name": "module:fs#realpath", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "The path to the file or directory." + } + }, + "subject": "Resolves the absolute path of a file or directory.", + "description": "Resolves the absolute path of a file or directory.\n\nReturns a string containing the resolved path.\n\nReturns `null` if an error occurred, e.g. due to insufficient permissions." + }, + "pipe": { + "kind": "function", + "name": "module:fs#pipe", + "return": [ + { + "type": "array", + "itemtype": [ + { + "type": "module:fs.file" + } + ], + "nullable": true + } + ], + "subject": "Creates a pipe and returns file handle objects associated with the read- and\nwrite end of the pipe respectively.", + "description": "Creates a pipe and returns file handle objects associated with the read- and\nwrite end of the pipe respectively.\n\nReturns a two element array containing both a file handle object open in read\nmode referring to the read end of the pipe and a file handle object open in\nwrite mode referring to the write end of the pipe.\n\nReturns `null` if an error occurred." + } + }, + "fs.proc": { + "close": { + "kind": "function", + "name": "module:fs.proc#close", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "subject": "Closes the program handle and awaits program termination.", + "description": "Closes the program handle and awaits program termination.\n\nUpon calling `close()` on the handle, the program's input or output stream\n(depending on the open mode) is closed. Afterwards, the function awaits the\ntermination of the underlying program and returns its exit code.\n\n- When the program was terminated by a signal, the return value will be the\nnegative signal number, e.g. `-9` for SIGKILL.\n\n- When the program terminated normally, the return value will be the positive\nexit code of the program.\n\nReturns a negative signal number if the program was terminated by a signal.\n\nReturns a positive exit code if the program terminated normally.\n\nReturns `null` if an error occurred." + }, + "read": { + "kind": "function", + "name": "module:fs.proc#read", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "length": { + "type": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "name": "length", + "description": "The length of data to read. Can be a number, the string \"line\", the string\n\"all\", or a single character string." + } + }, + "subject": "Reads a chunk of data from the program handle.", + "description": "Reads a chunk of data from the program handle.\n\nThe length argument may be either a positive number of bytes to read, in\nwhich case the read call returns up to that many bytes, or a string to\nspecify a dynamic read size.\n\n- If length is a number, the method will read the specified number of bytes\nfrom the handle. Reading stops after the given amount of bytes or after\nencountering EOF, whatever comes first.\n\n- If length is the string \"line\", the method will read an entire line,\nterminated by \"\\n\" (a newline), from the handle. Reading stops at the next\nnewline or when encountering EOF. The returned data will contain the\nterminating newline character if one was read.\n\n- If length is the string \"all\", the method will read from the handle until\nencountering EOF and return the complete contents.\n\n- If length is a single character string, the method will read from the\nhandle until encountering the specified character or upon encountering\nEOF. The returned data will contain the terminating character if one was\nread.\n\nReturns a string containing the read data.\n\nReturns an empty string on EOF.\n\nReturns `null` if a read error occurred." + }, + "write": { + "kind": "function", + "name": "module:fs.proc#write", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "data": { + "type": [ + { + "type": "*" + } + ], + "name": "data", + "description": "The data to be written." + } + }, + "subject": "Writes a chunk of data to the program handle.", + "description": "Writes a chunk of data to the program handle.\n\nIn case the given data is not a string, it is converted to a string before\nbeing written to the program's stdin. String values are written as-is,\ninteger and double values are written in decimal notation, boolean values are\nwritten as `true` or `false` while arrays and objects are converted to their\nJSON representation before being written. The `null` value is represented by\nan empty string so `proc.write(null)` would be a no-op. Resource values are\nwritten in the form ``, e.g. ``.\n\nIf resource, array or object values contain a `tostring()` function in their\nprototypes, then this function is invoked to obtain an alternative string\nrepresentation of the value.\n\nReturns the number of bytes written.\n\nReturns `null` if a write error occurred." + }, + "flush": { + "kind": "function", + "name": "module:fs.proc#flush", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "subject": "Forces a write of all buffered data to the underlying handle.", + "description": "Forces a write of all buffered data to the underlying handle.\n\nReturns `true` if the data was successfully flushed.\n\nReturns `null` on error." + }, + "fileno": { + "kind": "function", + "name": "module:fs.proc#fileno", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "subject": "Obtains the number of the handle's underlying file descriptor.", + "description": "Obtains the number of the handle's underlying file descriptor.\n\nReturns the descriptor number.\n\nReturns `null` on error." + } + }, + "fs.file": { + "close": { + "kind": "function", + "name": "module:fs.file#close", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "subject": "Closes the file handle.", + "description": "Closes the file handle.\n\nUpon calling `close()` on the handle, buffered data is flushed and the\nunderlying file descriptor is closed.\n\nReturns `true` if the handle was properly closed.\n\nReturns `null` if an error occurred." + }, + "read": { + "kind": "function", + "name": "module:fs.file#read", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "length": { + "type": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "name": "length", + "description": "The length of data to read. Can be a number, the string \"line\", the string\n\"all\", or a single character string." + } + }, + "subject": "Reads a chunk of data from the file handle.", + "description": "Reads a chunk of data from the file handle.\n\nThe length argument may be either a positive number of bytes to read, in\nwhich case the read call returns up to that many bytes, or a string to\nspecify a dynamic read size.\n\n- If length is a number, the method will read the specified number of bytes\nfrom the handle. Reading stops after the given amount of bytes or after\nencountering EOF, whatever comes first.\n\n- If length is the string \"line\", the method will read an entire line,\nterminated by \"\\n\" (a newline), from the handle. Reading stops at the next\nnewline or when encountering EOF. The returned data will contain the\nterminating newline character if one was read.\n\n- If length is the string \"all\", the method will read from the handle until\nencountering EOF and return the complete contents.\n\n- If length is a single character string, the method will read from the\nhandle until encountering the specified character or upon encountering\nEOF. The returned data will contain the terminating character if one was\nread.\n\nReturns a string containing the read data.\n\nReturns an empty string on EOF.\n\nReturns `null` if a read error occurred." + }, + "write": { + "kind": "function", + "name": "module:fs.file#write", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "data": { + "type": [ + { + "type": "*" + } + ], + "name": "data", + "description": "The data to be written." + } + }, + "subject": "Writes a chunk of data to the file handle.", + "description": "Writes a chunk of data to the file handle.\n\nIn case the given data is not a string, it is converted to a string before\nbeing written into the file. String values are written as-is, integer and\ndouble values are written in decimal notation, boolean values are written as\n`true` or `false` while arrays and objects are converted to their JSON\nrepresentation before being written. The `null` value is represented by an\nempty string so `file.write(null)` would be a no-op. Resource values are\nwritten in the form ``, e.g. ``.\n\nIf resource, array or object values contain a `tostring()` function in their\nprototypes, then this function is invoked to obtain an alternative string\nrepresentation of the value.\n\nReturns the number of bytes written.\n\nReturns `null` if a write error occurred." + }, + "seek": { + "kind": "function", + "name": "module:fs.file#seek", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "offset": { + "type": [ + { + "type": "number" + } + ], + "name": "[offset=0]", + "description": "The offset in bytes.", + "default": "0", + "optional": true + }, + "position": { + "type": [ + { + "type": "number" + } + ], + "name": "[position=0]", + "description": "The position of the offset.\n\n| Position | Description |\n|----------|----------------------------------------------------------------------------------------------|\n| `0` | The given offset is relative to the start of the file. This is the default value if omitted. |\n| `1` | The given offset is relative to the current read position. |\n| `2` | The given offset is relative to the end of the file. |", + "default": "0", + "optional": true + } + }, + "subject": "Set file read position.", + "description": "Set file read position.\n\nSet the read position of the open file handle to the given offset and\nposition.\n\nReturns `true` if the read position was set.\n\nReturns `null` if an error occurred." + }, + "truncate": { + "kind": "function", + "name": "module:fs.file#truncate", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "offset": { + "type": [ + { + "type": "number" + } + ], + "name": "[offset=0]", + "description": "The offset in bytes.", + "default": "0", + "optional": true + } + }, + "subject": "Truncate file to a given size", + "description": "Truncate file to a given size\n\nReturns `true` if the file was successfully truncated.\n\nReturns `null` if an error occurred." + }, + "lock": { + "kind": "function", + "name": "module:fs.file#lock", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "op": { + "type": [ + { + "type": "string" + } + ], + "name": "[op]", + "description": "The lock operation flags", + "optional": true + } + }, + "subject": "Locks or unlocks a file.", + "description": "Locks or unlocks a file.\n\nThe mode argument specifies lock/unlock operation flags.\n\n| Flag | Description |\n|---------|------------------------------|\n| \"s\" | shared lock |\n| \"x\" | exclusive lock |\n| \"n\" | don't block when locking |\n| \"u\" | unlock |\n\nReturns `true` if the file was successfully locked/unlocked.\n\nReturns `null` if an error occurred." + }, + "tell": { + "kind": "function", + "name": "module:fs.file#tell", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "subject": "Obtain current read position.", + "description": "Obtain current read position.\n\nObtains the current, absolute read position of the open file.\n\nReturns an integer containing the current read offset in bytes.\n\nReturns `null` if an error occurred." + }, + "isatty": { + "kind": "function", + "name": "module:fs.file#isatty", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "subject": "Check for TTY.", + "description": "Check for TTY.\n\nChecks whether the open file handle refers to a TTY (terminal) device.\n\nReturns `true` if the handle refers to a terminal.\n\nReturns `false` if the handle refers to another kind of file.\n\nReturns `null` on error." + }, + "flush": { + "kind": "function", + "name": "module:fs.file#flush", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "subject": "Forces a write of all buffered data to the underlying handle.", + "description": "Forces a write of all buffered data to the underlying handle.\n\nReturns `true` if the data was successfully flushed.\n\nReturns `null` on error." + }, + "fileno": { + "kind": "function", + "name": "module:fs.file#fileno", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "subject": "Obtains the number of the handle's underlying file descriptor.", + "description": "Obtains the number of the handle's underlying file descriptor.\n\nReturns the descriptor number.\n\nReturns `null` on error." + } + }, + "fs.dir": { + "read": { + "kind": "function", + "name": "module:fs.dir#read", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "subject": "Read the next entry from the open directory.", + "description": "Read the next entry from the open directory.\n\nReturns a string containing the entry name.\n\nReturns `null` if there are no more entries to read.\n\nReturns `null` if an error occurred." + }, + "tell": { + "kind": "function", + "name": "module:fs.dir#tell", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "subject": "Obtain current read position.", + "description": "Obtain current read position.\n\nReturns the current read position in the open directory handle which can be\npassed back to the `seek()` function to return to this position. This is\nmainly useful to read an open directory handle (or specific items) multiple\ntimes.\n\nReturns an integer referring to the current position.\n\nReturns `null` if an error occurred." + }, + "seek": { + "kind": "function", + "name": "module:fs.dir#seek", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "offset": { + "type": [ + { + "type": "number" + } + ], + "name": "offset", + "description": "Position value obtained by `tell()`." + } + }, + "subject": "Set read position.", + "description": "Set read position.\n\nSets the read position within the open directory handle to the given offset\nvalue. The offset value should be obtained by a previous call to `tell()` as\nthe specific integer values are implementation defined.\n\nReturns `true` if the read position was set.\n\nReturns `null` if an error occurred." + }, + "close": { + "kind": "function", + "name": "module:fs.dir#close", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "subject": "Closes the directory handle.", + "description": "Closes the directory handle.\n\nCloses the underlying file descriptor referring to the opened directory.\n\nReturns `true` if the handle was properly closed.\n\nReturns `null` if an error occurred." + } + }, + "log": { + "LogOption": { + "kind": "typedef", + "type": [ + { + "type": "module:log.LogOption" + } + ], + "name": "module:log.LogOption", + "subject": "The following log option strings are recognized:", + "description": "The following log option strings are recognized:\n\n| Log Option | Description |\n|------------|------------------------------------------------------------|\n| `\"pid\"` | Include PID with each message. |\n| `\"cons\"` | Log to console if an error occurs while sending to syslog. |\n| `\"ndelay\"` | Open the connection to the logger immediately. |\n| `\"odelay\"` | Delay open until the first message is logged. |\n| `\"nowait\"` | Do not wait for child processes created during logging. |" + }, + "LogFacility": { + "kind": "typedef", + "type": [ + { + "type": "module:log.LogFacility" + } + ], + "name": "module:log.LogFacility", + "subject": "The following log facility strings are recognized:", + "description": "The following log facility strings are recognized:\n\n| Facility | Description |\n|--------------|--------------------------------------------------|\n| `\"auth\"` | Authentication/authorization messages. |\n| `\"authpriv\"` | Private authentication messages. |\n| `\"cron\"` | Clock daemon (cron and at commands). |\n| `\"daemon\"` | System daemons without separate facility values. |\n| `\"ftp\"` | FTP server daemon. |\n| `\"kern\"` | Kernel messages. |\n| `\"lpr\"` | Line printer subsystem. |\n| `\"mail\"` | Mail system. |\n| `\"news\"` | Network news subsystem. |\n| `\"syslog\"` | Messages generated internally by syslogd. |\n| `\"user\"` | Generic user-level messages. |\n| `\"uucp\"` | UUCP subsystem. |\n| `\"local0\"` | Local use 0 (custom facility). |\n| `\"local1\"` | Local use 1 (custom facility). |\n| `\"local2\"` | Local use 2 (custom facility). |\n| `\"local3\"` | Local use 3 (custom facility). |\n| `\"local4\"` | Local use 4 (custom facility). |\n| `\"local5\"` | Local use 5 (custom facility). |\n| `\"local6\"` | Local use 6 (custom facility). |\n| `\"local7\"` | Local use 7 (custom facility). |" + }, + "LogPriority": { + "kind": "typedef", + "type": [ + { + "type": "module:log.LogPriority" + } + ], + "name": "module:log.LogPriority", + "subject": "The following log priority strings are recognized:", + "description": "The following log priority strings are recognized:\n\n| Priority | Description |\n|-------------|-------------------------------------|\n| `\"emerg\"` | System is unusable. |\n| `\"alert\"` | Action must be taken immediately. |\n| `\"crit\"` | Critical conditions. |\n| `\"err\"` | Error conditions. |\n| `\"warning\"` | Warning conditions. |\n| `\"notice\"` | Normal, but significant, condition. |\n| `\"info\"` | Informational message. |\n| `\"debug\"` | Debug-level message. |" + }, + "openlog": { + "kind": "function", + "name": "module:log#openlog", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "ident": { + "type": [ + { + "type": "string" + } + ], + "name": "[ident]", + "description": "A string identifying the program name. If omitted, the name of the calling\nprocess is used by default.", + "optional": true + }, + "options": { + "type": [ + { + "type": "number" + }, + { + "type": "module:log.LogOption" + }, + { + "type": "array", + "itemtype": [ + { + "type": "module:log.LogOption" + } + ] + } + ], + "name": "[options]", + "description": "Logging options to use.\n\nSee {@link module:log.LogOption|LogOption} for recognized option names.", + "optional": true + }, + "facility": { + "type": [ + { + "type": "number" + }, + { + "type": "module:log.LogFacility" + } + ], + "name": "[facility=\"user\"]", + "description": "The facility to use for log messages generated by subsequent syslog calls.\n\nSee {@link module:log.LogFacility|LogFacility} for recognized facility names.", + "default": "\"user\"", + "optional": true + } + }, + "subject": "Open connection to system logger.", + "description": "Open connection to system logger.\n\nThe `openlog()` function instructs the program to establish a connection to\nthe system log service and configures the default facility and identification\nfor use in subsequent log operations. It may be omitted, in which case the\nfirst call to `syslog()` will implicitly call `openlog()` with a default\nident value representing the program name and a default `LOG_USER` facility.\n\nThe log option argument may be either a single string value containing an\noption name, an array of option name strings or a numeric value representing\na bitmask of `LOG_*` option constants.\n\nThe facility argument may be either a single string value containing a\nfacility name or one of the numeric `LOG_*` facility constants in the module\nnamespace.\n\nReturns `true` if the system `openlog()` function was invoked.\n\nReturns `false` if an invalid argument, such as an unrecognized option or\nfacility name, was provided." + }, + "syslog": { + "kind": "function", + "name": "module:log#syslog", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "priority": { + "type": [ + { + "type": "number" + }, + { + "type": "module:log.LogPriority" + } + ], + "name": "priority", + "description": "Log message priority. May be either a number value (potentially bitwise OR-ed\nwith a log facility constant) which is passed as-is to the system `syslog()`\nfunction or a priority name string.\n\nSee {@link module:log.LogPriority|LogPriority} for recognized priority names." + }, + "format": { + "type": [ + { + "type": "*" + } + ], + "name": "format", + "description": "The sprintf-like format string for the log message, or any other, non-null,\nnon-string value type which will be implicitly stringified and logged as-is." + }, + "args": { + "type": [ + { + "type": "...*" + } + ], + "name": "[args]", + "description": "In case a format string value was provided in the previous argument, then\nall subsequent arguments are used to replace the placeholders in the format\nstring.", + "optional": true + } + }, + "subject": "Log a message to the system logger.", + "description": "Log a message to the system logger.\n\nThis function logs a message to the system logger. The function behaves in a\nsprintf-like manner, allowing the use of format strings and associated\narguments to construct log messages.\n\nIf the `openlog` function has not been called explicitly before, `syslog()`\nimplicitly calls `openlog()`, using a default ident and `LOG_USER` facility\nvalue before logging the message.\n\nIf the `format` argument is not a string and not `null`, it will be\nimplicitly converted to a string and logged as-is, without further format\nstring processing.\n\nReturns `true` if a message was passed to the system `syslog()` function.\n\nReturns `false` if an invalid priority value or an empty message was given." + }, + "closelog": { + "kind": "function", + "name": "module:log#closelog", + "subject": "Close connection to system logger.", + "description": "Close connection to system logger.\n\nThe usage of this function is optional, and usually an explicit log\nconnection tear down is not required." + }, + "UlogChannel": { + "kind": "typedef", + "type": [ + { + "type": "module:log.UlogChannel" + } + ], + "name": "module:log.UlogChannel", + "subject": "The following ulog channel strings are recognized:", + "description": "The following ulog channel strings are recognized:\n\n| Channel | Description |\n|------------|---------------------------------------------------|\n| `\"kmsg\"` | Log to `/dev/kmsg`, log messages appear in dmesg. |\n| `\"syslog\"` | Use standard `syslog()` mechanism. |\n| `\"stdio\"` | Use stderr for log output. |" + }, + "ulog_open": { + "kind": "function", + "name": "module:log#ulog_open", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "channel": { + "type": [ + { + "type": "number" + }, + { + "type": "module:log.UlogChannel" + }, + { + "type": "array", + "itemtype": [ + { + "type": "module:log.UlogChannel" + } + ] + } + ], + "name": "[channel]", + "description": "Specifies the log channels to use.\n\nSee {@link module:log.UlogChannel|UlogChannel} for recognized channel names.", + "optional": true + }, + "facility": { + "type": [ + { + "type": "number" + }, + { + "type": "module:log.LogFacility" + } + ], + "name": "[facility]", + "description": "The facility to use for log messages generated by subsequent `ulog()` calls.\n\nSee {@link module:log.LogFacility|LogFacility} for recognized facility names.", + "optional": true + }, + "ident": { + "type": [ + { + "type": "string" + } + ], + "name": "[ident]", + "description": "A string identifying the program name. If omitted, the name of the calling\nprocess is used by default.", + "optional": true + } + }, + "subject": "Configure ulog logger.", + "description": "Configure ulog logger.\n\nThis functions configures the ulog mechanism and is analogeous to using the\n`openlog()` function in conjuncton with `syslog()`.\n\nThe `ulog_open()` function is OpenWrt specific and may not be present on\nother systems. Use `openlog()` and `syslog()` instead for portability to\nnon-OpenWrt environments.\n\nA program may use multiple channels to simultaneously output messages using\ndifferent means. The channel argument may either be a single string value\ncontaining a channel name, an array of channel names or a numeric value\nrepresenting a bitmask of `ULOG_*` channel constants.\n\nThe facility argument may be either a single string value containing a\nfacility name or one of the numeric `LOG_*` facility constants in the module\nnamespace.\n\nThe default facility value varies, depending on the execution context of the\nprogram. In OpenWrt's preinit boot phase, or when stdout is not connected to\nan interactive terminal, the facility defaults to `\"daemon\"` (`LOG_DAEMON`),\notherwise to `\"user\"` (`LOG_USER`).\n\nLikewise, the default channel is selected depending on the context. During\nOpenWrt's preinit boot phase, the `\"kmsg\"` channel is used, for interactive\nterminals the `\"stdio\"` one and for all other cases the `\"syslog\"` channel\nis selected.\n\nReturns `true` if ulog was configured.\n\nReturns `false` if an invalid argument, such as an unrecognized channel or\nfacility name, was provided." + }, + "ulog": { + "kind": "function", + "name": "module:log#ulog", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "priority": { + "type": [ + { + "type": "number" + }, + { + "type": "module:log.LogPriority" + } + ], + "name": "priority", + "description": "Log message priority. May be either a number value or a priority name string.\n\nSee {@link module:log.LogPriority|LogPriority} for recognized priority names." + }, + "format": { + "type": [ + { + "type": "*" + } + ], + "name": "format", + "description": "The sprintf-like format string for the log message, or any other, non-null,\nnon-string value type which will be implicitly stringified and logged as-is." + }, + "args": { + "type": [ + { + "type": "...*" + } + ], + "name": "[args]", + "description": "In case a format string value was provided in the previous argument, then\nall subsequent arguments are used to replace the placeholders in the format\nstring.", + "optional": true + } + }, + "subject": "Log a message via the ulog mechanism.", + "description": "Log a message via the ulog mechanism.\n\nThe `ulog()` function outputs the given log message to all configured ulog\nchannels unless the given priority level exceeds the globally configured ulog\npriority threshold. See {@link module:log#ulog_threshold|ulog_threshold()}\nfor details.\n\nThe `ulog()` function is OpenWrt specific and may not be present on other\nsystems. Use `syslog()` instead for portability to non-OpenWrt environments.\n\nLike `syslog()`, the function behaves in a sprintf-like manner, allowing the\nuse of format strings and associated arguments to construct log messages.\n\nIf the `ulog_open()` function has not been called explicitly before, `ulog()`\nimplicitly configures certain defaults, see\n{@link module:log#ulog_open|ulog_open()} for a detailled description.\n\nIf the `format` argument is not a string and not `null`, it will be\nimplicitly converted to a string and logged as-is, without further format\nstring processing.\n\nReturns `true` if a message was passed to the underlying `ulog()` function.\n\nReturns `false` if an invalid priority value or an empty message was given." + }, + "ulog_close": { + "kind": "function", + "name": "module:log#ulog_close", + "subject": "Close ulog logger.", + "description": "Close ulog logger.\n\nResets the ulog channels, the default facility and the log ident value to\ndefaults.\n\nIn case the `\"syslog\"` channel has been configured, the underlying\n`closelog()` function will be invoked.\n\nThe usage of this function is optional, and usually an explicit ulog teardown\nis not required.\n\nThe `ulog_close()` function is OpenWrt specific and may not be present on\nother systems. Use `closelog()` in conjunction with `syslog()` instead for\nportability to non-OpenWrt environments." + }, + "ulog_threshold": { + "kind": "function", + "name": "module:log#ulog_threshold", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "priority": { + "type": [ + { + "type": "number" + }, + { + "type": "module:log.LogPriority" + } + ], + "name": "[priority]", + "description": "The priority threshold to configure.\n\nSee {@link module:log.LogPriority|LogPriority} for recognized priority names.", + "optional": true + } + }, + "subject": "Set ulog priority threshold.", + "description": "Set ulog priority threshold.\n\nThis function configures the application wide log message threshold for log\nmessages emitted with `ulog()`. Any message with a priority higher (= less\nsevere) than the threshold priority will be discarded. This is useful to\nimplement application wide verbosity settings without having to wrap `ulog()`\ninvocations into a helper function or guarding code.\n\nWhen no explicit threshold has been set, `LOG_DEBUG` is used by default,\nallowing log messages with all known priorities.\n\nThe `ulog_threshold()` function is OpenWrt specific and may not be present on\nother systems. There is no syslog equivalent to this ulog specific threshold\nmechanism.\n\nThe priority argument may be either a string value containing a priority name\nor one of the numeric `LOG_*` priority constants in the module namespace.\n\nReturns `true` if a threshold was set.\n\nReturns `false` if an invalid priority value was given." + }, + "INFO": { + "kind": "function", + "name": "module:log#INFO", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "format": { + "type": [ + { + "type": "*" + } + ], + "name": "format", + "description": "The sprintf-like format string for the log message, or any other, non-null,\nnon-string value type which will be implicitly stringified and logged as-is." + }, + "args": { + "type": [ + { + "type": "...*" + } + ], + "name": "[args]", + "description": "In case a format string value was provided in the previous argument, then\nall subsequent arguments are used to replace the placeholders in the format\nstring.", + "optional": true + } + }, + "subject": "Invoke ulog with LOG_INFO.", + "description": "Invoke ulog with LOG_INFO.\n\nThis function is convenience wrapper for `ulog(LOG_INFO, ...)`.\n\nSee {@link module:log#ulog|ulog()} for details." + }, + "NOTE": { + "kind": "function", + "name": "module:log#NOTE", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "format": { + "type": [ + { + "type": "*" + } + ], + "name": "format", + "description": "The sprintf-like format string for the log message, or any other, non-null,\nnon-string value type which will be implicitly stringified and logged as-is." + }, + "args": { + "type": [ + { + "type": "...*" + } + ], + "name": "[args]", + "description": "In case a format string value was provided in the previous argument, then\nall subsequent arguments are used to replace the placeholders in the format\nstring.", + "optional": true + } + }, + "subject": "Invoke ulog with LOG_NOTICE.", + "description": "Invoke ulog with LOG_NOTICE.\n\nThis function is convenience wrapper for `ulog(LOG_NOTICE, ...)`.\n\nSee {@link module:log#ulog|ulog()} for details." + }, + "WARN": { + "kind": "function", + "name": "module:log#WARN", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "format": { + "type": [ + { + "type": "*" + } + ], + "name": "format", + "description": "The sprintf-like format string for the log message, or any other, non-null,\nnon-string value type which will be implicitly stringified and logged as-is." + }, + "args": { + "type": [ + { + "type": "...*" + } + ], + "name": "[args]", + "description": "In case a format string value was provided in the previous argument, then\nall subsequent arguments are used to replace the placeholders in the format\nstring.", + "optional": true + } + }, + "subject": "Invoke ulog with LOG_WARNING.", + "description": "Invoke ulog with LOG_WARNING.\n\nThis function is convenience wrapper for `ulog(LOG_WARNING, ...)`.\n\nSee {@link module:log#ulog|ulog()} for details." + }, + "ERR": { + "kind": "function", + "name": "module:log#ERR", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "format": { + "type": [ + { + "type": "*" + } + ], + "name": "format", + "description": "The sprintf-like format string for the log message, or any other, non-null,\nnon-string value type which will be implicitly stringified and logged as-is." + }, + "args": { + "type": [ + { + "type": "...*" + } + ], + "name": "[args]", + "description": "In case a format string value was provided in the previous argument, then\nall subsequent arguments are used to replace the placeholders in the format\nstring.", + "optional": true + } + }, + "subject": "Invoke ulog with LOG_ERR.", + "description": "Invoke ulog with LOG_ERR.\n\nThis function is convenience wrapper for `ulog(LOG_ERR, ...)`.\n\nSee {@link module:log#ulog|ulog()} for details." + } + }, + "math": { + "abs": { + "kind": "function", + "name": "module:math#abs", + "return": [ + { + "type": "number" + } + ], + "description": "Returns the absolute value of the given numeric value.", + "params": { + "number": { + "type": [ + { + "type": "*" + } + ], + "name": "number", + "description": "The number to return the absolute value for." + } + }, + "subject": "Returns the absolute value of the given numeric value." + }, + "atan2": { + "kind": "function", + "name": "module:math#atan2", + "return": [ + { + "type": "number" + } + ], + "params": { + "y": { + "type": [ + { + "type": "*" + } + ], + "name": "y", + "description": "The `y` value." + }, + "x": { + "type": [ + { + "type": "*" + } + ], + "name": "x", + "description": "The `x` value." + } + }, + "subject": "Calculates the principal value of the arc tangent of `y`/`x`,\nusing the signs of the two arguments to determine the quadrant\nof the result.", + "description": "Calculates the principal value of the arc tangent of `y`/`x`,\nusing the signs of the two arguments to determine the quadrant\nof the result.\n\nOn success, this function returns the principal value of the arc\ntangent of `y`/`x` in radians; the return value is in the range [-pi, pi].\n\n- If `y` is +0 (-0) and `x` is less than 0, +pi (-pi) is returned.\n- If `y` is +0 (-0) and `x` is greater than 0, +0 (-0) is returned.\n- If `y` is less than 0 and `x` is +0 or -0, -pi/2 is returned.\n- If `y` is greater than 0 and `x` is +0 or -0, pi/2 is returned.\n- If either `x` or `y` is NaN, a NaN is returned.\n- If `y` is +0 (-0) and `x` is -0, +pi (-pi) is returned.\n- If `y` is +0 (-0) and `x` is +0, +0 (-0) is returned.\n- If `y` is a finite value greater (less) than 0, and `x` is negative\ninfinity, +pi (-pi) is returned.\n- If `y` is a finite value greater (less) than 0, and `x` is positive\ninfinity, +0 (-0) is returned.\n- If `y` is positive infinity (negative infinity), and `x` is finite,\npi/2 (-pi/2) is returned.\n- If `y` is positive infinity (negative infinity) and `x` is negative\ninfinity, +3*pi/4 (-3*pi/4) is returned.\n- If `y` is positive infinity (negative infinity) and `x` is positive\ninfinity, +pi/4 (-pi/4) is returned.\n\nWhen either `x` or `y` can't be converted to a numeric value, `NaN` is\nreturned." + }, + "cos": { + "kind": "function", + "name": "module:math#cos", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "number" + } + ], + "name": "x", + "description": "Radians value to calculate cosine for." + } + }, + "subject": "Calculates the cosine of `x`, where `x` is given in radians.", + "description": "Calculates the cosine of `x`, where `x` is given in radians.\n\nReturns the resulting consine value.\n\nReturns `NaN` if the `x` value can't be converted to a number." + }, + "exp": { + "kind": "function", + "name": "module:math#exp", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "number" + } + ], + "name": "x", + "description": "Power to raise `e` to." + } + }, + "subject": "Calculates the value of `e` (the base of natural logarithms)\nraised to the power of `x`.", + "description": "Calculates the value of `e` (the base of natural logarithms)\nraised to the power of `x`.\n\nOn success, returns the exponential value of `x`.\n\n- If `x` is positive infinity, positive infinity is returned.\n- If `x` is negative infinity, `+0` is returned.\n- If the result underflows, a range error occurs, and zero is returned.\n- If the result overflows, a range error occurs, and `Infinity` is returned.\n\nReturns `NaN` if the `x` value can't be converted to a number." + }, + "log": { + "kind": "function", + "name": "module:math#log", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "number" + } + ], + "name": "x", + "description": "Value to calulate natural logarithm of." + } + }, + "subject": "Calculates the natural logarithm of `x`.", + "description": "Calculates the natural logarithm of `x`.\n\nOn success, returns the natural logarithm of `x`.\n\n- If `x` is `1`, the result is `+0`.\n- If `x` is positive nfinity, positive infinity is returned.\n- If `x` is zero, then a pole error occurs, and the function\nreturns negative infinity.\n- If `x` is negative (including negative infinity), then a domain\nerror occurs, and `NaN` is returned.\n\nReturns `NaN` if the `x` value can't be converted to a number." + }, + "sin": { + "kind": "function", + "name": "module:math#sin", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "number" + } + ], + "name": "x", + "description": "Radians value to calculate sine for." + } + }, + "subject": "Calculates the sine of `x`, where `x` is given in radians.", + "description": "Calculates the sine of `x`, where `x` is given in radians.\n\nReturns the resulting sine value.\n\n- When `x` is positive or negative infinity, a domain error occurs\nand `NaN` is returned.\n\nReturns `NaN` if the `x` value can't be converted to a number." + }, + "sqrt": { + "kind": "function", + "name": "module:math#sqrt", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "number" + } + ], + "name": "x", + "description": "Value to calculate square root for." + } + }, + "subject": "Calculates the nonnegative square root of `x`.", + "description": "Calculates the nonnegative square root of `x`.\n\nReturns the resulting square root value.\n\n- If `x` is `+0` (`-0`) then `+0` (`-0`) is returned.\n- If `x` is positive infinity, positive infinity is returned.\n- If `x` is less than `-0`, a domain error occurs, and `NaN` is returned.\n\nReturns `NaN` if the `x` value can't be converted to a number." + }, + "pow": { + "kind": "function", + "name": "module:math#pow", + "return": [ + { + "type": "number" + } + ], + "params": { + "x": { + "type": [ + { + "type": "number" + } + ], + "name": "x", + "description": "The base value." + }, + "y": { + "type": [ + { + "type": "number" + } + ], + "name": "y", + "description": "The power value." + } + }, + "subject": "Calculates the value of `x` raised to the power of `y`.", + "description": "Calculates the value of `x` raised to the power of `y`.\n\nOn success, returns the value of `x` raised to the power of `y`.\n\n- If the result overflows, a range error occurs, and the function\nreturns `Infinity`.\n- If result underflows, and is not representable, a range error\noccurs, and `0.0` with the appropriate sign is returned.\n- If `x` is `+0` or `-0`, and `y` is an odd integer less than `0`,\na pole error occurs `Infinity` is returned, with the same sign\nas `x`.\n- If `x` is `+0` or `-0`, and `y` is less than `0` and not an odd\ninteger, a pole error occurs and `Infinity` is returned.\n- If `x` is `+0` (`-0`), and `y` is an odd integer greater than `0`,\nthe result is `+0` (`-0`).\n- If `x` is `0`, and `y` greater than `0` and not an odd integer,\nthe result is `+0`.\n- If `x` is `-1`, and `y` is positive infinity or negative infinity,\nthe result is `1.0`.\n- If `x` is `+1`, the result is `1.0` (even if `y` is `NaN`).\n- If `y` is `0`, the result is `1.0` (even if `x` is `NaN`).\n- If `x` is a finite value less than `0`, and `y` is a finite\nnoninteger, a domain error occurs, and `NaN` is returned.\n- If the absolute value of `x` is less than `1`, and `y` is negative\ninfinity, the result is positive infinity.\n- If the absolute value of `x` is greater than `1`, and `y` is\nnegative infinity, the result is `+0`.\n- If the absolute value of `x` is less than `1`, and `y` is positive\ninfinity, the result is `+0`.\n- If the absolute value of `x` is greater than `1`, and `y` is positive\ninfinity, the result is positive infinity.\n- If `x` is negative infinity, and `y` is an odd integer less than `0`,\nthe result is `-0`.\n- If `x` is negative infinity, and `y` less than `0` and not an odd\ninteger, the result is `+0`.\n- If `x` is negative infinity, and `y` is an odd integer greater than\n`0`, the result is negative infinity.\n- If `x` is negative infinity, and `y` greater than `0` and not an odd\ninteger, the result is positive infinity.\n- If `x` is positive infinity, and `y` less than `0`, the result is `+0`.\n- If `x` is positive infinity, and `y` greater than `0`, the result is\npositive infinity.\n\nReturns `NaN` if either the `x` or `y` value can't be converted to a number." + }, + "rand": { + "kind": "function", + "name": "module:math#rand", + "return": [ + { + "type": "number" + } + ], + "subject": "Produces a pseudo-random positive integer.", + "description": "Produces a pseudo-random positive integer.\n\nReturns the calculated pseuo-random value. The value is within the range\n`0` to `RAND_MAX` inclusive where `RAND_MAX` is a platform specific value\nguaranteed to be at least `32767`.\n\nThe {@link module:math~srand `srand()`} function sets its argument as the\nseed for a new sequence of pseudo-random integers to be returned by `rand()`. These sequences are\nrepeatable by calling {@link module:math~srand `srand()`} with the same\nseed value.\n\nIf no seed value is explicitly set by calling\n{@link module:math~srand `srand()`} prior to the first call to `rand()`,\nthe math module will automatically seed the PRNG once, using the current\ntime of day in milliseconds as seed value." + }, + "srand": { + "kind": "function", + "name": "module:math#srand", + "params": { + "seed": { + "type": [ + { + "type": "number" + } + ], + "name": "seed", + "description": "The seed value." + } + }, + "subject": "Seeds the pseudo-random number generator.", + "description": "Seeds the pseudo-random number generator.\n\nThis functions seeds the PRNG with the given value and thus affects the\npseudo-random integer sequence produced by subsequent calls to\n{@link module:math~rand `rand()`}.\n\nSetting the same seed value will result in the same pseudo-random numbers\nproduced by {@link module:math~rand `rand()`}." + }, + "isnan": { + "kind": "function", + "name": "module:math#isnan", + "return": [ + { + "type": "boolean" + } + ], + "params": { + "x": { + "type": [ + { + "type": "number" + } + ], + "name": "x", + "description": "The value to test." + } + }, + "subject": "Tests whether `x` is a `NaN` double.", + "description": "Tests whether `x` is a `NaN` double.\n\nThis functions checks whether the given argument is of type `double` with\na `NaN` (not a number) value.\n\nReturns `true` if the value is `NaN`, otherwise false.\n\nNote that a value can also be checked for `NaN` with the expression\n`x !== x` which only evaluates to `true` if `x` is `NaN`." + } + }, + "socket": { + "AddressInfo": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "addr": { + "type": [ + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "addr", + "description": "A socket address structure." + }, + "canonname": { + "type": [ + { + "type": "string" + } + ], + "name": "[canonname=null]", + "description": "The canonical hostname associated with the address." + }, + "family": { + "type": [ + { + "type": "number" + } + ], + "name": "family", + "description": "The address family (e.g., `2` for `AF_INET`, `10` for `AF_INET6`)." + }, + "flags": { + "type": [ + { + "type": "number" + } + ], + "name": "flags", + "description": "Additional flags indicating properties of the address." + }, + "protocol": { + "type": [ + { + "type": "number" + } + ], + "name": "protocol", + "description": "The protocol number." + }, + "socktype": { + "type": [ + { + "type": "number" + } + ], + "name": "socktype", + "description": "The socket type (e.g., `1` for `SOCK_STREAM`, `2` for `SOCK_DGRAM`)." + } + } + } + ], + "name": "module:socket.AddressInfo", + "default": "null", + "optional": true, + "subject": "Represents a network address information object returned by\n{@link module:socket#addrinfo|`addrinfo()`}.", + "description": "Represents a network address information object returned by\n{@link module:socket#addrinfo|`addrinfo()`}." + }, + "error": { + "kind": "function", + "name": "module:socket#error", + "return": [ + { + "type": "string", + "nullable": true + }, + { + "type": "number", + "nullable": true + } + ], + "params": { + "numeric": { + "type": [ + { + "type": "boolean" + } + ], + "name": "[numeric]", + "description": "Whether to return a numeric error code (`true`) or a human readable error\nmessage (false).", + "optional": true + } + }, + "subject": "Query error information.", + "description": "Query error information.\n\nReturns a string containing a description of the last occurred error when\nthe *numeric* argument is absent or false.\n\nReturns a positive (`errno`) or negative (`EAI_*` constant) error code number\nwhen the *numeric* argument is `true`.\n\nReturns `null` if there is no error information." + }, + "sockaddr": { + "kind": "function", + "name": "module:socket#sockaddr", + "return": [ + { + "type": "module:socket.socket.SocketAddress", + "nullable": true + } + ], + "description": "Parses the provided address value into a socket address representation.\n\nThis function parses the given address value into a socket address\nrepresentation required for a number of socket operations. The address value\ncan be provided in various formats:\n- For IPv4 addresses, it can be a string representing the IP address,\noptionally followed by a port number separated by colon, e.g.\n`192.168.0.1:8080`.\n- For IPv6 addresses, it must be an address string enclosed in square\nbrackets if a port number is specified, otherwise the brackets are\noptional. The address string may also include a scope ID in the form\n`%ifname` or `%number`, e.g. `[fe80::1%eth0]:8080` or `fe80::1%15`.\n- Any string value containing a slash is treated as UNIX domain socket path.\n- Alternatively, it can be provided as an array returned by\n{@link module:core#iptoarr|iptoarr()}, representing the address octets.\n- It can also be an object representing a network address, with properties\nfor `address` (the IP address) and `port` or a single property `path` to\ndenote a UNIX domain socket address.", + "params": { + "address": { + "type": [ + { + "type": "string" + }, + { + "type": "array", + "itemtype": [ + { + "type": "number" + } + ] + }, + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "address", + "description": "The address value to parse." + } + }, + "subject": "Parses the provided address value into a socket address representation." + }, + "nameinfo": { + "kind": "function", + "name": "module:socket#nameinfo", + "return": [ + { + "type": "object", + "nullable": true, + "properties": { + "hostname": { + "name": "hostname", + "type": [ + { + "type": "string" + } + ] + }, + "service": { + "name": "service", + "type": [ + { + "type": "string" + } + ] + } + } + } + ], + "params": { + "address": { + "type": [ + { + "type": "string" + }, + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "address", + "description": "The network address to resolve. It can be specified as:\n- A string representing the IP address.\n- An object representing the address with properties `address` and `port`." + }, + "flags": { + "type": [ + { + "type": "number" + } + ], + "name": "[flags]", + "description": "Optional flags that provide additional control over the resolution process,\nspecified as bitwise OR-ed number of `NI_*` constants.", + "optional": true + } + }, + "subject": "Resolves the given network address into hostname and service name.", + "description": "Resolves the given network address into hostname and service name.\n\nThe `nameinfo()` function provides an API for reverse DNS lookup and service\nname resolution. It returns an object containing the following properties:\n- `hostname`: The resolved hostname.\n- `service`: The resolved service name.\n\nReturns an object representing the resolved hostname and service name.\nReturn `null` if an error occurred during resolution." + }, + "addrinfo": { + "kind": "function", + "name": "module:socket#addrinfo", + "return": [ + { + "type": "array", + "itemtype": [ + { + "type": "module:socket.AddressInfo" + } + ], + "nullable": true + } + ], + "params": { + "hostname": { + "type": [ + { + "type": "string" + } + ], + "name": "hostname", + "description": "The hostname to resolve." + }, + "service": { + "type": [ + { + "type": "string" + } + ], + "name": "[service]", + "description": "Optional service name to resolve. If not provided, the service field of the\nresulting address information structures is left uninitialized.", + "optional": true + }, + "hints": { + "type": [ + { + "type": "object" + } + ], + "name": "[hints]", + "description": "Optional hints object that provides additional control over the resolution\nprocess. It can contain the following properties:\n- `family`: The preferred address family (`AF_INET` or `AF_INET6`).\n- `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.).\n- `protocol`: The protocol of returned addresses.\n- `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior.", + "optional": true + } + }, + "subject": "Resolves the given hostname and optional service name into a list of network\naddresses, according to the provided hints.", + "description": "Resolves the given hostname and optional service name into a list of network\naddresses, according to the provided hints.\n\nThe `addrinfo()` function provides an API for performing DNS and service name\nresolution. It returns an array of objects, each representing a resolved\naddress.\n\nReturns an array of resolved addresses.\nReturns `null` if an error occurred during resolution." + }, + "PollSpec": { + "kind": "typedef", + "type": [ + { + "type": "array" + } + ], + "name": "module:socket.PollSpec", + "subject": "Represents a poll state serving as input parameter and return value type for\n{@link module:socket#poll|`poll()`}.", + "description": "Represents a poll state serving as input parameter and return value type for\n{@link module:socket#poll|`poll()`}." + }, + "poll": { + "kind": "function", + "name": "module:socket#poll", + "return": [ + { + "type": "array", + "itemtype": [ + { + "type": "module:socket.PollSpec" + } + ] + } + ], + "params": { + "timeout": { + "type": [ + { + "type": "number" + } + ], + "name": "timeout", + "description": "Amount of milliseconds to wait for socket activity before aborting the poll\ncall. If set to `0`, the poll call will return immediately if none of the\nprovided sockets has pending events, if set to a negative value, the poll\ncall will wait indefinitely, in all other cases the poll call will wait at\nmost for the given amount of milliseconds before returning." + }, + "sockets": { + "type": [ + { + "type": "...(module:socket.socket|module:socket.PollSpec)" + } + ], + "name": "sockets", + "description": "An arbitrary amount of socket arguments. Each argument may be either a plain\n{@link module:socket.socket|socket instance} (or any other kind of handle\nimplementing a `fileno()` method) or a `[socket, flags]` tuple specifying the\nsocket and requested poll flags. If a plain socket (or other kind of handle)\ninstead of a tuple is provided, the requested poll flags default to\n`POLLIN|POLLERR|POLLHUP` for this socket." + } + }, + "subject": "Polls a number of sockets for state changes.", + "description": "Polls a number of sockets for state changes.\n\nReturns an array of `[socket, flags]` tuples for each socket with pending\nevents. When a tuple is passed as socket argument, it is included as-is into\nthe result tuple array, with the flags entry changed to a bitwise OR-ed value\ndescribing the pending events for this socket. When a plain socket instance\n(or another kind of handle) is passed, a new tuple array is created for this\nsocket within the result tuple array, containing this socket as first and the\nbitwise OR-ed pending events as second element.\n\nReturns `null` if an error occurred." + }, + "connect": { + "kind": "function", + "name": "module:socket#connect", + "return": [ + { + "type": "module:socket.socket" + } + ], + "params": { + "host": { + "type": [ + { + "type": "string" + }, + { + "type": "array", + "itemtype": [ + { + "type": "number" + } + ] + }, + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "host", + "description": "The host to connect to, can be an IP address, hostname,\n{@link module:socket.socket.SocketAddress|SocketAddress}, or an array value\nreturned by {@link module:core#iptoarr|iptoarr()}." + }, + "service": { + "type": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "name": "[service]", + "description": "The service to connect to, can be a symbolic service name (such as \"http\") or\na port number. Optional if host is specified as\n{@link module:socket.socket.SocketAddress|SocketAddress}.", + "optional": true + }, + "hints": { + "type": [ + { + "type": "object" + } + ], + "name": "[hints]", + "description": "Optional preferences for the socket. It can contain the following properties:\n- `family`: The preferred address family (`AF_INET` or `AF_INET6`).\n- `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.).\n- `protocol`: The protocol of the created socket.\n- `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior.\n\nIf no hints are not provided, the default socket type preference is set to\n`SOCK_STREAM`.", + "optional": true + }, + "timeout": { + "type": [ + { + "type": "number" + } + ], + "name": "[timeout=-1]", + "description": "The timeout in milliseconds for socket connect operations. If set to a\nnegative value, no specifc time limit is imposed and the function will\nblock until either a connection was successfull or the underlying operating\nsystem timeout is reached.", + "default": "-1", + "optional": true + } + }, + "subject": "Creates a network socket and connects it to the specified host and service.", + "description": "Creates a network socket and connects it to the specified host and service.\n\nThis high level function combines the functionality of\n{@link module:socket#create|create()},\n{@link module:socket#addrinfo|addrinfo()} and\n{@link module:socket.socket#connect|connect()} to simplify connection\nestablishment with the socket module." + }, + "listen": { + "kind": "function", + "name": "module:socket#listen", + "return": [ + { + "type": "module:socket.socket" + } + ], + "params": { + "host": { + "type": [ + { + "type": "string" + }, + { + "type": "array", + "itemtype": [ + { + "type": "number" + } + ] + }, + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "host", + "description": "The host to bind to, can be an IP address, hostname,\n{@link module:socket.socket.SocketAddress|SocketAddress}, or an array value\nreturned by {@link module:core#iptoarr|iptoarr()}." + }, + "service": { + "type": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "name": "[service]", + "description": "The service to listen on, can be a symbolic service name (such as \"http\") or\na port number. Optional if host is specified as\n{@link module:socket.socket.SocketAddress|SocketAddress}.", + "optional": true + }, + "hints": { + "type": [ + { + "type": "object" + } + ], + "name": "[hints]", + "description": "Optional preferences for the socket. It can contain the following properties:\n- `family`: The preferred address family (`AF_INET` or `AF_INET6`).\n- `socktype`: The socket type (`SOCK_STREAM`, `SOCK_DGRAM`, etc.).\n- `protocol`: The protocol of the created socket.\n- `flags`: Bitwise OR-ed `AI_*` flags to control the resolution behavior.\n\nIf no hints are provided, the default socket type preference is set to\n`SOCK_STREAM`.", + "optional": true + }, + "backlog": { + "type": [ + { + "type": "number" + } + ], + "name": "[backlog=128]", + "description": "The maximum length of the queue of pending connections.", + "default": "128", + "optional": true + } + }, + "subject": "Binds a listening network socket to the specified host and service.", + "description": "Binds a listening network socket to the specified host and service.\n\nThis high-level function combines the functionality of\n{@link module:socket#create|create()},\n{@link module:socket#addrinfo|addrinfo()},\n{@link module:socket.socket#bind|bind()}, and\n{@link module:socket.socket#listen|listen()} to simplify setting up a\nlistening socket with the socket module." + }, + "create": { + "kind": "function", + "name": "module:socket#create", + "return": [ + { + "type": "module:socket.socket", + "nullable": true + } + ], + "description": "Creates a network socket instance.\n\nThis function creates a new network socket with the specified domain and\ntype, determined by one of the modules `AF_*` and `SOCK_*` constants\nrespectively, and returns the resulting socket instance for use in subsequent\nsocket operations.\n\nThe domain argument specifies the protocol family, such as AF_INET or\nAF_INET6, and defaults to AF_INET if not provided.\n\nThe type argument specifies the socket type, such as SOCK_STREAM or\nSOCK_DGRAM, and defaults to SOCK_STREAM if not provided. It may also\nbe bitwise OR-ed with SOCK_NONBLOCK to enable non-blocking mode or\nSOCK_CLOEXEC to enable close-on-exec semantics.\n\nThe protocol argument may be used to indicate a particular protocol\nto be used with the socket, and it defaults to 0 (automatically\ndetermined protocol) if not provided.\n\nReturns a socket descriptor representing the newly created socket.\n\nReturns `null` if an error occurred during socket creation.", + "params": { + "domain": { + "type": [ + { + "type": "number" + } + ], + "name": "[domain=AF_INET]", + "description": "The communication domain for the socket, e.g., AF_INET or AF_INET6.", + "default": "AF_INET", + "optional": true + }, + "type": { + "type": [ + { + "type": "number" + } + ], + "name": "[type=SOCK_STREAM]", + "description": "The socket type, e.g., SOCK_STREAM or SOCK_DGRAM. It may also be\nbitwise OR-ed with SOCK_NONBLOCK or SOCK_CLOEXEC.", + "default": "SOCK_STREAM", + "optional": true + }, + "protocol": { + "type": [ + { + "type": "number" + } + ], + "name": "[protocol=0]", + "description": "The protocol to be used with the socket.", + "default": "0", + "optional": true + } + }, + "subject": "Creates a network socket instance." + } + }, + "socket.socket": { + "setopt": { + "kind": "function", + "name": "module:socket.socket#setopt", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "level": { + "type": [ + { + "type": "number" + } + ], + "name": "level", + "description": "The protocol level at which the option resides. This can be a level such as\n`SOL_SOCKET` for the socket API level or a specific protocol level defined\nby the system." + }, + "option": { + "type": [ + { + "type": "number" + } + ], + "name": "option", + "description": "The socket option to set. This can be an integer representing the option,\nsuch as `SO_REUSEADDR`, or a constant defined by the system." + }, + "value": { + "type": [ + { + "type": "*" + } + ], + "name": "value", + "description": "The value to set the option to. The type of this argument depends on the\nspecific option being set. It can be an integer, a boolean, a string, or a\ndictionary representing the value to set. If a dictionary is provided, it is\ninternally translated to the corresponding C struct type required by the\noption." + } + }, + "subject": "Sets options on the socket.", + "description": "Sets options on the socket.\n\nSets the specified option on the socket to the given value.\n\nReturns `true` if the option was successfully set.\n\nReturns `null` if an error occurred." + }, + "getopt": { + "kind": "function", + "name": "module:socket.socket#getopt", + "return": [ + { + "type": "*", + "nullable": true + } + ], + "description": "Gets options from the socket.\n\nRetrieves the value of the specified option from the socket.\n\nReturns the value of the requested option.\n\nReturns `null` if an error occurred or if the option is not supported.", + "params": { + "level": { + "type": [ + { + "type": "number" + } + ], + "name": "level", + "description": "The protocol level at which the option resides. This can be a level such as\n`SOL_SOCKET` for the socket API level or a specific protocol level defined\nby the system." + }, + "option": { + "type": [ + { + "type": "number" + } + ], + "name": "option", + "description": "The socket option to retrieve. This can be an integer representing the\noption, such as `SO_REUSEADDR`, or a constant defined by the system." + } + }, + "subject": "Gets options from the socket." + }, + "fileno": { + "kind": "function", + "name": "module:socket.socket#fileno", + "return": [ + { + "type": "number" + } + ], + "subject": "Returns the UNIX file descriptor number associated with the socket.", + "description": "Returns the UNIX file descriptor number associated with the socket.\n\nReturns the file descriptor number.\n\nReturns `-1` if an error occurred." + }, + "SocketAddress": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "family": { + "type": [ + { + "type": "number" + } + ], + "name": "family", + "description": "Address family, one of AF_INET, AF_INET6, AF_UNIX or AF_PACKET." + }, + "address": { + "type": [ + { + "type": "string" + } + ], + "name": "address", + "description": "IPv4/IPv6 address string (AF_INET or AF_INET6 only) or hardware address in\nhexadecimal notation (AF_PACKET only)." + }, + "port": { + "type": [ + { + "type": "number" + } + ], + "name": "[port]", + "description": "Port number (AF_INET or AF_INET6 only)." + }, + "flowinfo": { + "type": [ + { + "type": "number" + } + ], + "name": "[flowinfo]", + "description": "IPv6 flow information (AF_INET6 only)." + }, + "interface": { + "type": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "name": "[interface]", + "description": "Link local address scope (for IPv6 sockets) or bound network interface\n(for packet sockets), either a network device name string or a nonzero\npositive integer representing a network interface index (AF_INET6 and\nAF_PACKET only)." + }, + "path": { + "type": [ + { + "type": "string" + } + ], + "name": "path", + "description": "Domain socket filesystem path (AF_UNIX only)." + }, + "protocol": { + "type": [ + { + "type": "number" + } + ], + "name": "[protocol=0]", + "description": "Physical layer protocol (AF_PACKET only)." + }, + "hardware_type": { + "type": [ + { + "type": "number" + } + ], + "name": "[hardware_type=0]", + "description": "ARP hardware type (AF_PACKET only)." + }, + "packet_type": { + "type": [ + { + "type": "number" + } + ], + "name": "[packet_type=PACKET_HOST]", + "description": "Packet type (AF_PACKET only)." + } + } + } + ], + "name": "module:socket.socket.SocketAddress", + "optional": true, + "default": "PACKET_HOST", + "description": "" + }, + "connect": { + "kind": "function", + "name": "module:socket.socket#connect", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "address": { + "type": [ + { + "type": "string" + }, + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "address", + "description": "The address of the remote endpoint to connect to." + }, + "port": { + "type": [ + { + "type": "number" + } + ], + "name": "port", + "description": "The port number of the remote endpoint to connect to." + } + }, + "subject": "Connects the socket to a remote address.", + "description": "Connects the socket to a remote address.\n\nAttempts to establish a connection to the specified remote address.\n\nReturns `true` if the connection is successfully established.\nReturns `null` if an error occurred during the connection attempt." + }, + "send": { + "kind": "function", + "name": "module:socket.socket#send", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "params": { + "data": { + "type": [ + { + "type": "*" + } + ], + "name": "data", + "description": "The data to be sent through the socket. String data is sent as-is, any other\ntype is implicitly converted to a string first before being sent on the\nsocket." + }, + "flags": { + "type": [ + { + "type": "number" + } + ], + "name": "[flags]", + "description": "Optional flags that modify the behavior of the send operation.", + "optional": true + }, + "address": { + "type": [ + { + "type": "module:socket.socket.SocketAddress" + }, + { + "type": "array", + "itemtype": [ + { + "type": "number" + } + ] + }, + { + "type": "string" + } + ], + "name": "[address]", + "description": "The address of the remote endpoint to send the data to. It can be either an\nIP address string, an array returned by {@link module:core#iptoarr|iptoarr()},\nor an object representing a network address. If not provided, the data is\nsent to the remote endpoint the socket is connected to.", + "optional": true + } + }, + "subject": "Sends data through the socket.", + "description": "Sends data through the socket.\n\nSends the provided data through the socket handle to the specified remote\naddress, if provided.\n\nReturns the number of bytes sent.\nReturns `null` if an error occurred during the send operation." + }, + "recv": { + "kind": "function", + "name": "module:socket.socket#recv", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "length": { + "type": [ + { + "type": "number" + } + ], + "name": "[length=4096]", + "description": "The maximum number of bytes to receive.", + "default": "4096", + "optional": true + }, + "flags": { + "type": [ + { + "type": "number" + } + ], + "name": "[flags]", + "description": "Optional flags that modify the behavior of the receive operation.", + "optional": true + }, + "address": { + "type": [ + { + "type": "object" + } + ], + "name": "[address]", + "description": "An object where the function will store the address from which the data was\nreceived. If provided, it will be filled with the details obtained from the\nsockaddr argument of the underlying `recvfrom()` syscall. See the type\ndefinition of {@link module:socket.socket.SocketAddress|SocketAddress} for\ndetails on the format.", + "optional": true + } + }, + "subject": "Receives data from the socket.", + "description": "Receives data from the socket.\n\nReceives data from the socket handle, optionally specifying the maximum\nlength of data to receive, flags to modify the receive behavior, and an\noptional address dictionary where the function will place the address from\nwhich the data was received (for unconnected sockets).\n\nReturns a string containing the received data.\nReturns an empty string if the remote side closed the socket.\nReturns `null` if an error occurred during the receive operation." + }, + "ControlMessage": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "level": { + "type": [ + { + "type": "number" + } + ], + "name": "level", + "description": "The message socket level (`cmsg_level`), e.g. `SOL_SOCKET`." + }, + "type": { + "type": [ + { + "type": "number" + } + ], + "name": "type", + "description": "The protocol specific message type (`cmsg_type`), e.g. `SCM_RIGHTS`." + }, + "data": { + "type": [ + { + "type": "*" + } + ], + "name": "data", + "description": "The payload of the control message. If the control message type is known by\nthe socket module, it is represented as a mixed value (array, object, number,\netc.) with structure specific to the control message type. If the control\nmessage cannot be decoded, *data* is set to a string value containing the raw\npayload." + } + } + } + ], + "name": "module:socket.socket.ControlMessage", + "subject": "Represents a single control (ancillary data) message returned\nin the *ancillary* array by {@link module:socket.socket#recvmsg|`recvmsg()`}.", + "description": "Represents a single control (ancillary data) message returned\nin the *ancillary* array by {@link module:socket.socket#recvmsg|`recvmsg()`}." + }, + "sendmsg": { + "kind": "function", + "name": "module:socket.socket#sendmsg", + "return": [ + { + "type": "number", + "nullable": true + } + ], + "description": "Sends a message through the socket.\n\nSends a message through the socket handle, supporting complex message\nstructures including multiple data buffers and ancillary data. This function\nallows for precise control over the message content and delivery behavior.\n\nReturns the number of sent bytes.\n\nReturns `null` if an error occurred.", + "params": { + "data": { + "type": [ + { + "type": "*" + } + ], + "name": "[data]", + "description": "The data to be sent. If a string is provided, it is sent as is. If an array\nis specified, each item is sent as a separate `struct iovec`. Non-string\nvalues are implicitly converted to a string and sent. If omitted, only\nancillary data and address are considered.", + "optional": true + }, + "ancillaryData": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "module:socket.socket.ControlMessage" + } + ] + }, + { + "type": "string" + } + ], + "name": "[ancillaryData]", + "description": "Optional ancillary data to be sent. If an array is provided, each element is\nconverted to a control message. If a string is provided, it is sent as-is\nwithout further interpretation. Refer to\n{@link module:socket.socket#recvmsg|`recvmsg()`} and\n{@link module:socket.socket.ControlMessage|ControlMessage} for details.", + "optional": true + }, + "address": { + "type": [ + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "[address]", + "description": "The destination address for the message. If provided, it sets or overrides\nthe packet destination address.", + "optional": true + }, + "flags": { + "type": [ + { + "type": "number" + } + ], + "name": "[flags]", + "description": "Optional flags to modify the behavior of the send operation. This should be a\nbitwise OR-ed combination of `MSG_*` flag values.", + "optional": true + } + }, + "subject": "Sends a message through the socket." + }, + "ReceivedMessage": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "flags": { + "type": [ + { + "type": "number" + } + ], + "name": "flags", + "description": "Integer value containing bitwise OR-ed `MSG_*` result flags returned by the\nunderlying receive call." + }, + "length": { + "type": [ + { + "type": "number" + } + ], + "name": "length", + "description": "Integer value containing the number of bytes returned by the `recvmsg()`\nsyscall, which might be larger than the received data in case `MSG_TRUNC`\nwas passed." + }, + "address": { + "type": [ + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "address", + "description": "The address from which the message was received." + }, + "data": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "string" + } + ] + }, + { + "type": "string" + } + ], + "name": "data", + "description": "An array of strings, each representing the received message data.\nEach string corresponds to one buffer size specified in the *sizes* argument.\nIf a single receive size was passed instead of an array of sizes, *data* will\nhold a string containing the received data." + }, + "ancillary": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "module:socket.socket.ControlMessage" + } + ] + } + ], + "name": "[ancillary]", + "description": "An array of received control messages. Only included if a non-zero positive\n*ancillarySize* was passed to `recvmsg()`." + } + } + } + ], + "name": "module:socket.socket.ReceivedMessage", + "optional": true, + "subject": "Represents a message object returned by\n{@link module:socket.socket#recvmsg|`recvmsg()`}.", + "description": "Represents a message object returned by\n{@link module:socket.socket#recvmsg|`recvmsg()`}." + }, + "recvmsg": { + "kind": "function", + "name": "module:socket.socket#recvmsg", + "return": [ + { + "type": "module:socket.socket.ReceivedMessage", + "nullable": true + } + ], + "description": "Receives a message from the socket.\n\nReceives a message from the socket handle, allowing for more complex data\nreception compared to `recv()`. This includes the ability to receive\nancillary data (such as file descriptors, credentials, etc.), multiple\nmessage segments, and optional flags to modify the receive behavior.\n\nReturns an object containing the received message data, ancillary data,\nand the sender's address.\n\nReturns `null` if an error occurred during the receive operation.", + "params": { + "sizes": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "number" + } + ] + }, + { + "type": "number" + } + ], + "name": "[sizes]", + "description": "Specifies the sizes of the buffers used for receiving the message. If an\narray of numbers is provided, each number determines the size of an\nindividual buffer segment, creating multiple `struct iovec` for reception.\nIf a single number is provided, a single buffer of that size is used.", + "optional": true + }, + "ancillarySize": { + "type": [ + { + "type": "number" + } + ], + "name": "[ancillarySize]", + "description": "The size allocated for the ancillary data buffer. If not provided, ancillary\ndata is not processed.", + "optional": true + }, + "flags": { + "type": [ + { + "type": "number" + } + ], + "name": "[flags]", + "description": "Optional flags to modify the behavior of the receive operation. This should\nbe a bitwise OR-ed combination of flag values.", + "optional": true + } + }, + "subject": "Receives a message from the socket." + }, + "bind": { + "kind": "function", + "name": "module:socket.socket#bind", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "address": { + "type": [ + { + "type": "string" + }, + { + "type": "module:socket.socket.SocketAddress" + } + ], + "name": "address", + "description": "The IP address to bind the socket to." + } + }, + "subject": "Binds a socket to a specific address.", + "description": "Binds a socket to a specific address.\n\nThis function binds the socket to the specified address.\n\nReturns `true` if the socket is successfully bound.\n\nReturns `null` on error, e.g. when the address is in use." + }, + "listen": { + "kind": "function", + "name": "module:socket.socket#listen", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "backlog": { + "type": [ + { + "type": "number" + } + ], + "name": "[backlog=128]", + "description": "The maximum length of the queue of pending connections.", + "default": "128", + "optional": true + } + }, + "subject": "Listen for connections on a socket.", + "description": "Listen for connections on a socket.\n\nThis function marks the socket as a passive socket, that is, as a socket that\nwill be used to accept incoming connection requests using `accept()`.\n\nThe `backlog` parameter specifies the maximum length to which the queue of\npending connections may grow. If a connection request arrives when the queue\nis full, the client connection might get refused.\n\nIf `backlog` is not provided, it defaults to 128.\n\nReturns `true` if the socket is successfully marked as passive.\nReturns `null` if an error occurred, e.g. when the requested port is in use." + }, + "accept": { + "kind": "function", + "name": "module:socket.socket#accept", + "return": [ + { + "type": "module:socket.socket", + "nullable": true + } + ], + "params": { + "address": { + "type": [ + { + "type": "object" + } + ], + "name": "[address]", + "description": "An optional dictionary to receive the address details of the peer socket.\nSee {@link module:socket.socket.SocketAddress|SocketAddress} for details.", + "optional": true + }, + "flags": { + "type": [ + { + "type": "number" + } + ], + "name": "[flags]", + "description": "Optional flags to modify the behavior of the peer socket.", + "optional": true + } + }, + "subject": "Accept a connection on a socket.", + "description": "Accept a connection on a socket.\n\nThis function accepts a connection on the socket. It extracts the first\nconnection request on the queue of pending connections, creates a new\nconnected socket, and returns a new socket handle referring to that socket.\nThe newly created socket is not in listening state and has no backlog.\n\nWhen a optional `address` dictionary is provided, it is populated with the\nremote address details of the peer socket.\n\nThe optional `flags` parameter is a bitwise-or-ed number of flags to modify\nthe behavior of accepted peer socket. Possible values are:\n- `SOCK_CLOEXEC`: Enable close-on-exec semantics for the new socket.\n- `SOCK_NONBLOCK`: Enable nonblocking mode for the new socket.\n\nReturns a socket handle representing the newly created peer socket of the\naccepted connection.\n\nReturns `null` if an error occurred." + }, + "shutdown": { + "kind": "function", + "name": "module:socket.socket#shutdown", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "how": { + "type": [ + { + "type": "number" + } + ], + "name": "how", + "description": "Specifies which half of the connection to shut down.\nIt can be one of the following constant values: `SHUT_RD`, `SHUT_WR`,\nor `SHUT_RDWR`." + } + }, + "subject": "Shutdown part of a full-duplex connection.", + "description": "Shutdown part of a full-duplex connection.\n\nThis function shuts down part of the full-duplex connection associated with\nthe socket handle. The `how` parameter specifies which half of the connection\nto shut down. It can take one of the following constant values:\n\n- `SHUT_RD`: Disables further receive operations.\n- `SHUT_WR`: Disables further send operations.\n- `SHUT_RDWR`: Disables further send and receive operations.\n\nReturns `true` if the shutdown operation is successful.\nReturns `null` if an error occurred." + }, + "PeerCredentials": { + "kind": "typedef", + "type": [ + { + "type": "object", + "properties": { + "uid": { + "type": [ + { + "type": "number" + } + ], + "name": "uid", + "description": "The effective user ID the remote socket endpoint." + }, + "gid": { + "type": [ + { + "type": "number" + } + ], + "name": "gid", + "description": "The effective group ID the remote socket endpoint." + }, + "pid": { + "type": [ + { + "type": "number" + } + ], + "name": "pid", + "description": "The ID of the process the remote socket endpoint belongs to." + } + } + } + ], + "name": "module:socket.socket.PeerCredentials", + "subject": "Represents a credentials information object returned by\n{@link module:socket.socket#peercred|`peercred()`}.", + "description": "Represents a credentials information object returned by\n{@link module:socket.socket#peercred|`peercred()`}." + }, + "peercred": { + "kind": "function", + "name": "module:socket.socket#peercred", + "return": [ + { + "type": "module:socket.socket.PeerCredentials", + "nullable": true + } + ], + "subject": "Retrieves the peer credentials.", + "description": "Retrieves the peer credentials.\n\nThis function retrieves the remote uid, gid and pid of a connected UNIX\ndomain socket.\n\nReturns the remote credentials if the operation is successful.\nReturns `null` on error." + }, + "peername": { + "kind": "function", + "name": "module:socket.socket#peername", + "return": [ + { + "type": "module:socket.socket.SocketAddress", + "nullable": true + } + ], + "subject": "Retrieves the remote address.", + "description": "Retrieves the remote address.\n\nThis function retrieves the remote address of a connected socket.\n\nReturns the remote address if the operation is successful.\nReturns `null` on error." + }, + "sockname": { + "kind": "function", + "name": "module:socket.socket#sockname", + "return": [ + { + "type": "module:socket.socket.SocketAddress", + "nullable": true + } + ], + "subject": "Retrieves the local address.", + "description": "Retrieves the local address.\n\nThis function retrieves the local address of a bound or connected socket.\n\nReturns the local address if the operation is successful.\nReturns `null` on error." + }, + "close": { + "kind": "function", + "name": "module:socket.socket#close", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "subject": "Closes the socket.", + "description": "Closes the socket.\n\nThis function closes the socket, releasing its resources and terminating its\nassociated connections.\n\nReturns `true` if the socket was successfully closed.\nReturns `null` on error." + } + }, + "struct": { + "pack": { + "kind": "function", + "name": "module:struct#pack", + "return": [ + { + "type": "string" + } + ], + "params": { + "format": { + "type": [ + { + "type": "string" + } + ], + "name": "format", + "description": "The format string." + }, + "values": { + "type": [ + { + "type": "...*" + } + ], + "name": "values", + "description": "Variable number of values to pack." + } + }, + "subject": "Pack given values according to specified format.", + "description": "Pack given values according to specified format.\n\nThe `pack()` function creates a byte string containing the argument values\npacked according to the given format string.\n\nReturns the packed string.\n\nRaises a runtime exception if a given argument value does not match the\nrequired type of the corresponding format string directive or if and invalid\nformat string is provided." + }, + "unpack": { + "kind": "function", + "name": "module:struct#unpack", + "return": [ + { + "type": "array" + } + ], + "params": { + "format": { + "type": [ + { + "type": "string" + } + ], + "name": "format", + "description": "The format string." + }, + "input": { + "type": [ + { + "type": "string" + } + ], + "name": "input", + "description": "The input string to unpack." + }, + "offset": { + "type": [ + { + "type": "number" + } + ], + "name": "[offset=0]", + "description": "The offset within the input string to start unpacking from.", + "default": "0", + "optional": true + } + }, + "subject": "Unpack given byte string according to specified format.", + "description": "Unpack given byte string according to specified format.\n\nThe `unpack()` function interpretes a byte string according to the given\nformat string and returns the resulting values. If the optional offset\nargument is given, unpacking starts from this byte position within the input.\nIf not specified, the start offset defaults to `0`, the start of the given\ninput string.\n\nReturns an array of unpacked values.\n\nRaises a runtime exception if the format string is invalid or if an invalid\ninput string or offset value is given." + }, + "new": { + "kind": "function", + "name": "module:struct#new", + "return": [ + { + "type": "module:struct.instance" + } + ], + "params": { + "format": { + "type": [ + { + "type": "string" + } + ], + "name": "format", + "description": "The format string." + } + }, + "subject": "Precompile format string.", + "description": "Precompile format string.\n\nThe `new()` function precompiles the given format string argument and returns\na `struct` object instance useful for packing and unpacking multiple items\nwithout having to recompute the internal format each time.\n\nReturns an precompiled struct format instance.\n\nRaises a runtime exception if the format string is invalid." + }, + "buffer": { + "kind": "function", + "name": "module:struct#buffer", + "return": [ + { + "type": "module:struct.buffer" + } + ], + "params": { + "initialData": { + "type": [ + { + "type": "string" + } + ], + "name": "[initialData]", + "description": "Optional initial data to populate the buffer with.", + "optional": true + } + }, + "subject": "Creates a new struct buffer instance.", + "description": "Creates a new struct buffer instance.\n\nThe `buffer()` function creates a new struct buffer object that can be used\nfor incremental packing and unpacking of binary data. If an initial data\nstring is provided, the buffer is initialized with this content.\n\nNote that even when initial data is provided, the buffer position is always\nset to zero. This design assumes that the primary intent when initializing\na buffer with data is to read (unpack) from the beginning. If you want to\nappend data to a pre-initialized buffer, you need to explicitly move the\nposition to the end, either by calling `end()` or by setting the position\nmanually with `pos()`.\n\nReturns a new struct buffer instance." + } + }, + "struct.instance": { + "pack": { + "kind": "function", + "name": "module:struct.instance#pack", + "return": [ + { + "type": "string" + } + ], + "params": { + "values": { + "type": [ + { + "type": "...*" + } + ], + "name": "values", + "description": "Variable number of values to pack." + } + }, + "subject": "Pack given values.", + "description": "Pack given values.\n\nThe `pack()` function creates a byte string containing the argument values\npacked according to the given format instance.\n\nReturns the packed string.\n\nRaises a runtime exception if a given argument value does not match the\nrequired type of the corresponding format string directive." + }, + "unpack": { + "kind": "function", + "name": "module:struct.instance#unpack", + "return": [ + { + "type": "array" + } + ], + "params": { + "input": { + "type": [ + { + "type": "string" + } + ], + "name": "input", + "description": "The input string to unpack." + }, + "offset": { + "type": [ + { + "type": "number" + } + ], + "name": "[offset=0]", + "description": "The offset within the input string to start unpacking from.", + "default": "0", + "optional": true + } + }, + "subject": "Unpack given byte string.", + "description": "Unpack given byte string.\n\nThe `unpack()` function interpretes a byte string according to the given\nformat instance and returns the resulting values. If the optional offset\nargument is given, unpacking starts from this byte position within the input.\nIf not specified, the start offset defaults to `0`, the start of the given\ninput string.\n\nReturns an array of unpacked values.\n\nRaises a runtime exception if an invalid input string or offset value is\ngiven." + } + }, + "struct.buffer": { + "pos": { + "kind": "function", + "name": "module:struct.buffer#pos", + "return": [ + { + "type": "number" + }, + { + "type": "module:struct.buffer" + } + ], + "description": "Get or set the current position in the buffer.\n\nIf called without arguments, returns the current position.\nIf called with a position argument, sets the current position to that value.", + "params": { + "position": { + "type": [ + { + "type": "number" + } + ], + "name": "[position]", + "description": "The position to set. If omitted, the current position is returned.", + "optional": true + } + }, + "subject": "Get or set the current position in the buffer." + }, + "length": { + "kind": "function", + "name": "module:struct.buffer#length", + "return": [ + { + "type": "number" + }, + { + "type": "module:struct.buffer" + } + ], + "description": "Get or set the current buffer length.\n\nIf called without arguments, returns the current length of the buffer.\nIf called with a length argument, sets the buffer length to that value,\npadding the data with trailing zero bytes or truncating it depending on\nwhether the updated length is larger or smaller than the current length\nrespectively.\n\nIn case the updated length is smaller than the current buffer offset, the\nposition is updated accordingly, so that it points to the new end of the\ntruncated buffer data.", + "params": { + "length": { + "type": [ + { + "type": "number" + } + ], + "name": "[length]", + "description": "The length to set. If omitted, the current length is returned.", + "optional": true + } + }, + "subject": "Get or set the current buffer length." + }, + "start": { + "kind": "function", + "name": "module:struct.buffer#start", + "return": [ + { + "type": "module:struct.buffer" + } + ], + "description": "Set the buffer position to the start (0).", + "subject": "Set the buffer position to the start (0)." + }, + "end": { + "kind": "function", + "name": "module:struct.buffer#end", + "return": [ + { + "type": "module:struct.buffer" + } + ], + "description": "Set the buffer position to the end.", + "subject": "Set the buffer position to the end." + }, + "put": { + "kind": "function", + "name": "module:struct.buffer#put", + "return": [ + { + "type": "module:struct.buffer" + } + ], + "description": "Pack data into the buffer at the current position.\n\nThe `put()` function packs the given values into the buffer according to\nthe specified format string, starting at the current buffer position.\nThe format string follows the same syntax as used in `struct.pack()`.\n\nFor a detailed explanation of the format string syntax, refer to the\n[\"Format Strings\" section]{@link module:struct} in the module\ndocumentation.", + "params": { + "format": { + "type": [ + { + "type": "string" + } + ], + "name": "format", + "description": "The format string specifying how to pack the data." + }, + "values": { + "type": [ + { + "type": "...*" + } + ], + "name": "values", + "description": "The values to pack into the buffer." + } + }, + "subject": "Pack data into the buffer at the current position." + }, + "get": { + "kind": "function", + "name": "module:struct.buffer#get", + "return": [ + { + "type": "array" + } + ], + "description": "Unpack multiple values from the buffer at the current position.\n\nThe `read()` function unpacks multiple values from the buffer according to\nthe specified format string, starting at the current buffer position.\nThe format string follows the same syntax as used in `struct.unpack()`.\n\nFor a detailed explanation of the format string syntax, refer to the\n[\"Format Strings\" section]{@link module:struct} in the module documentation.", + "params": { + "format": { + "type": [ + { + "type": "string" + } + ], + "name": "format", + "description": "The format string specifying how to unpack the data." + } + }, + "subject": "Unpack multiple values from the buffer at the current position." + }, + "slice": { + "kind": "function", + "name": "module:struct.buffer#slice", + "return": [ + { + "type": "string" + } + ], + "description": "Extract a slice of the buffer content.\n\nThe `slice()` function returns a substring of the buffer content\nbetween the specified start and end positions.\n\nBoth the start and end position values may be negative, in which case they're\nrelative to the end of the buffer, e.g. `slice(-3)` will extract the last\nthree bytes of data.", + "params": { + "start": { + "type": [ + { + "type": "number" + } + ], + "name": "[start=0]", + "description": "The starting position of the slice.", + "default": "0", + "optional": true + }, + "end": { + "type": [ + { + "type": "number" + } + ], + "name": "[end=buffer.length()]", + "description": "The ending position of the slice (exclusive).", + "default": "buffer.length()", + "optional": true + } + }, + "subject": "Extract a slice of the buffer content." + }, + "set": { + "kind": "function", + "name": "module:struct.buffer#set", + "return": [ + { + "type": "module:struct.buffer" + } + ], + "description": "Set a slice of the buffer content to given byte value.\n\nThe `set()` function overwrites a substring of the buffer content with the\ngiven byte value, similar to the C `memset()` function, between the specified\nstart and end positions.\n\nBoth the start and end position values may be negative, in which case they're\nrelative to the end of the buffer, e.g. `set(0, -2)` will overwrite the last\ntwo bytes of data with `\\x00`.\n\nWhen the start or end positions are beyond the current buffer length, the\nbuffer is grown accordingly.", + "params": { + "value": { + "type": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "name": "[value=0]", + "description": "The byte value to use when overwriting buffer contents. When a string is\ngiven, the first character is used as value.", + "default": "0", + "optional": true + }, + "start": { + "type": [ + { + "type": "number" + } + ], + "name": "[start=0]", + "description": "The position to start overwriting from.", + "default": "0", + "optional": true + }, + "end": { + "type": [ + { + "type": "number" + } + ], + "name": "[end=buffer.length()]", + "description": "The position to end overwriting (exclusive).", + "default": "buffer.length()", + "optional": true + } + }, + "subject": "Set a slice of the buffer content to given byte value." + }, + "pull": { + "kind": "function", + "name": "module:struct.buffer#pull", + "return": [ + { + "type": "string" + } + ], + "description": "Extract and remove all content from the buffer.\n\nThe `pull()` function returns all content of the buffer as a string\nand resets the buffer to an empty state.", + "subject": "Extract and remove all content from the buffer." + } + }, + "uci": { + "error": { + "kind": "function", + "name": "module:uci#error", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "subject": "Query error information.", + "description": "Query error information.\n\nReturns a string containing a description of the last occurred error or\n`null` if there is no error information." + }, + "cursor": { + "kind": "function", + "name": "module:uci#cursor", + "return": [ + { + "type": "module:uci.cursor", + "nullable": true + } + ], + "params": { + "config_dir": { + "type": [ + { + "type": "string" + } + ], + "name": "[config_dir=/etc/config]", + "description": "The directory to search for configuration files. It defaults to the well\nknown uci configuration directory `/etc/config` but may be set to a different\npath for special purpose applications.", + "default": "/etc/config", + "optional": true + }, + "delta_dir": { + "type": [ + { + "type": "string" + } + ], + "name": "[delta_dir=/tmp/.uci]", + "description": "The directory to save delta records in. It defaults to the well known\n`/tmp/.uci` path which is used as default by the uci command line tool.\n\nBy changing this path to a different location, it is possible to isolate\nuncommitted application changes from the uci cli or other processes on the\nsystem.", + "default": "/tmp/.uci", + "optional": true + } + }, + "subject": "Instantiate uci cursor.", + "description": "Instantiate uci cursor.\n\nA uci cursor is a context for interacting with uci configuration files. It's\npurpose is to cache and hold changes made to loaded configuration states\nuntil those changes are written out to disk or discared.\n\nUnsaved and uncommitted changes in a cursor instance are private and not\nvisible to other cursor instances instantiated by the same program or other\nprocesses on the system.\n\nReturns the instantiated cursor on success.\n\nReturns `null` on error, e.g. if an invalid path argument was provided." + } + }, + "uci.cursor": { + "load": { + "kind": "function", + "name": "module:uci.cursor#load", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to load, e.g. `\"system\"` to load\n`/etc/config/system` into the cursor." + } + }, + "subject": "Explicitly reload configuration file.", + "description": "Explicitly reload configuration file.\n\nUsually, any attempt to query or modify a value within a given configuration\nwill implicitly load the underlying file into memory. By invoking `load()`\nexplicitly, a potentially loaded stale configuration is discarded and\nreloaded from the file system, ensuring that the latest state is reflected in\nthe cursor.\n\nReturns `true` if the configuration was successfully loaded.\n\nReturns `null` on error, e.g. if the requested configuration does not exist." + }, + "unload": { + "kind": "function", + "name": "module:uci.cursor#unload", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to unload." + } + }, + "subject": "Explicitly unload configuration file.", + "description": "Explicitly unload configuration file.\n\nThe `unload()` function forcibly discards a loaded configuration state from\nthe cursor so that the next attempt to read or modify that configuration\nwill load it anew from the file system.\n\nReturns `true` if the configuration was successfully unloaded.\n\nReturns `false` if the configuration was not loaded to begin with.\n\nReturns `null` on error, e.g. if the requested configuration does not exist." + }, + "get": { + "kind": "function", + "name": "module:uci.cursor#get", + "return": [ + { + "type": "(string|string[])", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to query, e.g. `\"system\"` to query values\nin `/etc/config/system`." + }, + "section": { + "type": [ + { + "type": "string" + } + ], + "name": "section", + "description": "The name of the section to query within the configuration." + }, + "option": { + "type": [ + { + "type": "string" + } + ], + "name": "[option]", + "description": "The name of the option to query within the section. If omitted, the type of\nthe section is returned instead.", + "optional": true + } + }, + "subject": "Query a single option value or section type.", + "description": "Query a single option value or section type.\n\nWhen invoked with three arguments, the function returns the value of the\ngiven option, within the specified section of the given configuration.\n\nWhen invoked with just a config and section argument, the function returns\nthe type of the specified section.\n\nIn either case, the given configuration is implicitly loaded into the cursor\nif not already present.\n\nReturns the configuration value or section type on success.\n\nReturns `null` on error, e.g. if the requested configuration does not exist\nor if an invalid argument was passed." + }, + "get_all": { + "kind": "function", + "name": "module:uci.cursor#get_all", + "return": [ + { + "type": "(Object|module:uci.cursor.SectionObject)", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to query, e.g. `\"system\"` to query values\nin `/etc/config/system`." + }, + "section": { + "type": [ + { + "type": "string" + } + ], + "name": "[section]", + "description": "The name of the section to query within the configuration. If omitted a\nnested dictionary containing all section values is returned.", + "optional": true + } + }, + "subject": "Query a complete section or configuration.", + "description": "Query a complete section or configuration.\n\nWhen invoked with two arguments, the function returns all values of the\nspecified section within the given configuration as dictionary.\n\nWhen invoked with just a config argument, the function returns a nested\ndictionary of all sections present within the given configuration.\n\nIn either case, the given configuration is implicitly loaded into the cursor\nif not already present.\n\nReturns the section or configuration dictionary on success.\n\nReturns `null` on error, e.g. if the requested configuration does not exist\nor if an invalid argument was passed." + }, + "get_first": { + "kind": "function", + "name": "module:uci.cursor#get_first", + "return": [ + { + "type": "(string|string[])", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to query, e.g. `\"system\"` to query values\nin `/etc/config/system`." + }, + "type": { + "type": [ + { + "type": "string" + } + ], + "name": "type", + "description": "The section type to find the first section for within the configuration." + }, + "option": { + "type": [ + { + "type": "string" + } + ], + "name": "[option]", + "description": "The name of the option to query within the section. If omitted, the name of\nthe section is returned instead.", + "optional": true + } + }, + "subject": "Query option value or name of first section of given type.", + "description": "Query option value or name of first section of given type.\n\nWhen invoked with three arguments, the function returns the value of the\ngiven option within the first found section of the specified type in the\ngiven configuration.\n\nWhen invoked with just a config and section type argument, the function\nreturns the name of the first found section of the given type.\n\nIn either case, the given configuration is implicitly loaded into the cursor\nif not already present.\n\nReturns the configuration value or section name on success.\n\nReturns `null` on error, e.g. if the requested configuration does not exist\nor if an invalid argument was passed." + }, + "add": { + "kind": "function", + "name": "module:uci.cursor#add", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to add the section to, e.g. `\"system\"` to\nmodify `/etc/config/system`." + }, + "type": { + "type": [ + { + "type": "string" + } + ], + "name": "type", + "description": "The type value to use for the added section." + } + }, + "subject": "Add anonymous section to given configuration.", + "description": "Add anonymous section to given configuration.\n\nAdds a new anonymous (unnamed) section of the specified type to the given\nconfiguration. In order to add a named section, the three argument form of\n`set()` should be used instead.\n\nIn contrast to other query functions, `add()` will not implicitly load the\nconfiguration into the cursor. The configuration either needs to be loaded\nexplicitly through `load()` beforehand, or implicitly by querying it through\none of the `get()`, `get_all()`, `get_first()` or `foreach()` functions.\n\nReturns the autogenerated, ephemeral name of the added unnamed section\non success.\n\nReturns `null` on error, e.g. if the targeted configuration was not loaded or\nif an invalid section type value was passed." + }, + "set": { + "kind": "function", + "name": "module:uci.cursor#set", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to set values in, e.g. `\"system\"` to\nmodify `/etc/config/system`." + }, + "section": { + "type": [ + { + "type": "string" + } + ], + "name": "section", + "description": "The section name to create or set a value in." + }, + "option_or_type": { + "type": [ + { + "type": "string" + } + ], + "name": "option_or_type", + "description": "The option name to set within the section or, when the subsequent value\nargument is omitted, the type of the section to create within the\nconfiguration." + }, + "value": { + "type": [ + { + "type": "Array" + }, + { + "type": "string" + }, + { + "type": "boolean" + }, + { + "type": "number" + } + ], + "name": "[value]", + "description": "The option value to set.", + "optional": true + } + }, + "subject": "Set option value or add named section in given configuration.", + "description": "Set option value or add named section in given configuration.\n\nWhen invoked with four arguments, the function sets the value of the given\noption within the specified section of the given configuration to the\nprovided value. A value of `\"\"` (empty string) can be used to delete an\nexisting option.\n\nWhen invoked with three arguments, the function adds a new named section to\nthe given configuration, using the specified type.\n\nIn either case, the given configuration is implicitly loaded into the cursor\nif not already present.\n\nReturns the `true` if the named section was added or the specified option was\nset.\n\nReturns `null` on error, e.g. if the targeted configuration was not found or\nif an invalid value was passed." + }, + "delete": { + "kind": "function", + "name": "module:uci.cursor#delete", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to delete values in, e.g. `\"system\"` to\nmodify `/etc/config/system`." + }, + "section": { + "type": [ + { + "type": "string" + } + ], + "name": "section", + "description": "The section name to remove the specified option in or, when the subsequent\nargument is omitted, the section to remove entirely." + }, + "option": { + "type": [ + { + "type": "string" + } + ], + "name": "[option]", + "description": "The option name to remove within the section.", + "optional": true + } + }, + "subject": "Delete an option or section from given configuration.", + "description": "Delete an option or section from given configuration.\n\nWhen invoked with three arguments, the function deletes the given option\nwithin the specified section of the given configuration.\n\nWhen invoked with two arguments, the function deletes the entire specified\nsection within the given configuration.\n\nIn either case, the given configuration is implicitly loaded into the cursor\nif not already present.\n\nReturns the `true` if specified option or section has been deleted.\n\nReturns `null` on error, e.g. if the targeted configuration was not found or\nif an invalid value was passed." + }, + "rename": { + "kind": "function", + "name": "module:uci.cursor#rename", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to rename values in, e.g. `\"system\"` to\nmodify `/etc/config/system`." + }, + "section": { + "type": [ + { + "type": "string" + } + ], + "name": "section", + "description": "The section name to rename or to rename an option in." + }, + "option_or_name": { + "type": [ + { + "type": "string" + } + ], + "name": "option_or_name", + "description": "The option name to rename within the section or, when the subsequent name\nargument is omitted, the new name of the renamed section within the\nconfiguration." + }, + "name": { + "type": [ + { + "type": "string" + } + ], + "name": "[name]", + "description": "The new name of the option to rename.", + "optional": true + } + }, + "subject": "Rename an option or section in given configuration.", + "description": "Rename an option or section in given configuration.\n\nWhen invoked with four arguments, the function renames the given option\nwithin the specified section of the given configuration to the provided\nvalue.\n\nWhen invoked with three arguments, the function renames the entire specified\nsection to the provided value.\n\nIn either case, the given configuration is implicitly loaded into the cursor\nif not already present.\n\nReturns the `true` if specified option or section has been renamed.\n\nReturns `null` on error, e.g. if the targeted configuration was not found or\nif an invalid value was passed." + }, + "reorder": { + "kind": "function", + "name": "module:uci.cursor#reorder", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The name of the configuration file to move the section in, e.g. `\"system\"` to\nmodify `/etc/config/system`." + }, + "section": { + "type": [ + { + "type": "string" + } + ], + "name": "section", + "description": "The section name to move." + }, + "index": { + "type": [ + { + "type": "number" + } + ], + "name": "index", + "description": "The target index to move the section to, starting from `0`." + } + }, + "subject": "Reorder sections in given configuration.", + "description": "Reorder sections in given configuration.\n\nThe `reorder()` function moves a single section by repositioning it to the\ngiven index within the configurations section list.\n\nThe given configuration is implicitly loaded into the cursor if not already\npresent.\n\nReturns the `true` if specified section has been moved.\n\nReturns `null` on error, e.g. if the targeted configuration was not found or\nif an invalid value was passed." + }, + "save": { + "kind": "function", + "name": "module:uci.cursor#save", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "[config]", + "description": "The name of the configuration file to save delta records for, e.g. `\"system\"`\nto store changes for `/etc/config/system`.", + "optional": true + } + }, + "subject": "Save accumulated cursor changes to delta directory.", + "description": "Save accumulated cursor changes to delta directory.\n\nThe `save()` function writes consolidated changes made to in-memory copies of\nloaded configuration files to the uci delta directory which effectively makes\nthem available to other processes using the same delta directory path as well\nas the `uci changes` cli command when using the default delta directory.\n\nNote that uci deltas are overlayed over the actual configuration file values\nso they're reflected by `get()`, `foreach()` etc. even if the underlying\nconfiguration files are not actually changed (yet). The delta records may be\neither permanently merged into the configuration by invoking `commit()` or\nreverted through `revert()` in order to restore the current state of the\nunderlying configuration file.\n\nWhen the optional \"config\" parameter is omitted, delta records for all\ncurrently loaded configuration files are written.\n\nIn case that neither sharing changes with other processes nor any revert\nfunctionality is required, changes may be committed directly using `commit()`\ninstead, bypassing any delta record creation.\n\nReturns the `true` if operation completed successfully.\n\nReturns `null` on error, e.g. if the requested configuration was not loaded\nor when a file system error occurred." + }, + "commit": { + "kind": "function", + "name": "module:uci.cursor#commit", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "[config]", + "description": "The name of the configuration file to commit, e.g. `\"system\"` to update the\n`/etc/config/system` file.", + "optional": true + } + }, + "subject": "Update configuration files with accumulated cursor changes.", + "description": "Update configuration files with accumulated cursor changes.\n\nThe `commit()` function merges changes made to in-memory copies of loaded\nconfiguration files as well as existing delta records in the cursors\nconfigured delta directory and writes them back into the underlying\nconfiguration files, persistently committing changes to the file system.\n\nWhen the optional \"config\" parameter is omitted, all currently loaded\nconfiguration files with either present delta records or yet unsaved\ncursor changes are updated.\n\nReturns the `true` if operation completed successfully.\n\nReturns `null` on error, e.g. if the requested configuration was not loaded\nor when a file system error occurred." + }, + "revert": { + "kind": "function", + "name": "module:uci.cursor#revert", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "[config]", + "description": "The name of the configuration file to revert, e.g. `\"system\"` to discard any\nchanges for the `/etc/config/system` file.", + "optional": true + } + }, + "subject": "Revert accumulated cursor changes and associated delta records.", + "description": "Revert accumulated cursor changes and associated delta records.\n\nThe `revert()` function discards any changes made to in-memory copies of\nloaded configuration files and discards any related existing delta records in\nthe cursors configured delta directory.\n\nWhen the optional \"config\" parameter is omitted, all currently loaded\nconfiguration files with either present delta records or yet unsaved\ncursor changes are reverted.\n\nReturns the `true` if operation completed successfully.\n\nReturns `null` on error, e.g. if the requested configuration was not loaded\nor when a file system error occurred." + }, + "changes": { + "kind": "function", + "name": "module:uci.cursor#changes", + "return": [ + { + "type": "object", + "keytype": [ + { + "type": "string" + } + ], + "itemtype": [ + { + "type": "array", + "itemtype": [ + { + "type": "module:uci.cursor.ChangeRecord" + } + ] + } + ], + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "[config]", + "description": "The name of the configuration file to enumerate changes for, e.g. `\"system\"`\nto query pending changes for the `/etc/config/system` file.", + "optional": true + } + }, + "subject": "Enumerate pending changes.", + "description": "Enumerate pending changes.\n\nThe `changes()` function returns a list of change records for currently\nloaded configuration files, originating both from the cursors associated\ndelta directory and yet unsaved cursor changes.\n\nWhen the optional \"config\" parameter is specified, the requested\nconfiguration is implicitly loaded if it is not already loaded into the\ncursor.\n\nReturns a dictionary of change record arrays, keyed by configuration name.\n\nReturns `null` on error, e.g. if the requested configuration could not be\nloaded." + }, + "foreach": { + "kind": "function", + "name": "module:uci.cursor#foreach", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "params": { + "config": { + "type": [ + { + "type": "string" + } + ], + "name": "config", + "description": "The configuration to iterate sections for, e.g. `\"system\"` to read the\n`/etc/config/system` file." + }, + "type": { + "type": [ + { + "type": "string", + "nullable": true + } + ], + "name": "type", + "description": "Invoke the callback only for sections of the specified type." + }, + "callback": { + "type": [ + { + "type": "module:uci.cursor.SectionCallback" + } + ], + "name": "callback", + "description": "The callback to invoke for each section, will receive a section dictionary\nas sole argument." + } + }, + "subject": "Iterate configuration sections.", + "description": "Iterate configuration sections.\n\nThe `foreach()` function iterates all sections of the given configuration,\noptionally filtered by type, and invokes the given callback function for\neach encountered section.\n\nWhen the optional \"type\" parameter is specified, the callback is only invoked\nfor sections of the given type, otherwise it is invoked for all sections.\n\nThe requested configuration is implicitly loaded into the cursor.\n\nReturns `true` if the callback was executed successfully at least once.\n\nReturns `false` if the callback was never invoked, e.g. when the\nconfiguration is empty or contains no sections of the given type.\n\nReturns `null` on error, e.g. when an invalid callback was passed or the\nrequested configuration not found." + }, + "configs": { + "kind": "function", + "name": "module:uci.cursor#configs", + "return": [ + { + "type": "array", + "itemtype": [ + { + "type": "string" + } + ], + "nullable": true + } + ], + "subject": "Enumerate existing configurations.", + "description": "Enumerate existing configurations.\n\nThe `configs()` function yields an array of configuration files present in\nthe cursors associated configuration directory, `/etc/config/` by default.\n\nReturns an array of configuration names on success.\n\nReturns `null` on error, e.g. due to filesystem errors." + } + }, + "uloop": { + "error": { + "kind": "function", + "name": "module:uloop#error", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "description": "Retrieves the last error message.\n\nThis function retrieves the last error message generated by the uloop event loop.\nIf no error occurred, it returns `null`.", + "subject": "Retrieves the last error message." + }, + "init": { + "kind": "function", + "name": "module:uloop#init", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "description": "Initializes the uloop event loop.\n\nThis function initializes the uloop event loop, allowing subsequent\nusage of uloop functionalities. It takes no arguments.\n\nReturns `true` on success.\nReturns `null` if an error occurred during initialization.", + "subject": "Initializes the uloop event loop." + }, + "run": { + "kind": "function", + "name": "module:uloop#run", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "description": "Runs the uloop event loop.\n\nThis function starts running the uloop event loop, allowing it to handle\nscheduled events and callbacks. If a timeout value is provided and is\nnon-negative, the event loop will run for that amount of milliseconds\nbefore returning. If the timeout is omitted or negative, the event loop\nruns indefinitely until explicitly stopped.", + "params": { + "timeout": { + "type": [ + { + "type": "number" + } + ], + "name": "[timeout=-1]", + "description": "Optional. The timeout value in milliseconds for running the event loop.\nDefaults to -1, indicating an indefinite run.", + "default": "-1", + "optional": true + } + }, + "subject": "Runs the uloop event loop." + }, + "cancelling": { + "kind": "function", + "name": "module:uloop#cancelling", + "return": [ + { + "type": "boolean" + } + ], + "description": "Checks if the uloop event loop is currently shutting down.\n\nThis function checks whether the uloop event loop is currently in the process\nof shutting down.", + "subject": "Checks if the uloop event loop is currently shutting down." + }, + "running": { + "kind": "function", + "name": "module:uloop#running", + "return": [ + { + "type": "boolean" + } + ], + "description": "Checks if the uloop event loop is currently running.\n\nThis function checks whether the uloop event loop is currently started\nand running.", + "subject": "Checks if the uloop event loop is currently running." + }, + "end": { + "kind": "function", + "name": "module:uloop#end", + "return": [ + { + "type": "void" + } + ], + "description": "Halts the uloop event loop.\n\nThis function halts the uloop event loop, stopping its execution and\npreventing further processing of scheduled events and callbacks.\n\nExpired timeouts and already queued event callbacks are still run to\ncompletion.", + "subject": "Halts the uloop event loop." + }, + "done": { + "kind": "function", + "name": "module:uloop#done", + "return": [ + { + "type": "void" + } + ], + "description": "Stops the uloop event loop and cancels pending timeouts and events.\n\nThis function immediately stops the uloop event loop, cancels all pending\ntimeouts and events, unregisters all handles, and deallocates associated\nresources.", + "subject": "Stops the uloop event loop and cancels pending timeouts and events." + }, + "timer": { + "kind": "function", + "name": "module:uloop#timer", + "return": [ + { + "type": "module:uloop.timer", + "nullable": true + } + ], + "description": "Creates a timer instance for scheduling callbacks.\n\nThis function creates a timer instance for scheduling callbacks to be\nexecuted after a specified timeout duration. It takes an optional timeout\nparameter, which defaults to -1, indicating that the timer is initially not\narmed and can be enabled later by invoking the `.set(timeout)` method on the\ninstance.\n\nA callback function must be provided to be executed when the timer expires.", + "params": { + "timeout": { + "type": [ + { + "type": "number" + } + ], + "name": "[timeout=-1]", + "description": "Optional. The timeout duration in milliseconds. Defaults to -1, indicating\nthe timer is not initially armed.", + "default": "-1", + "optional": true + }, + "callback": { + "type": [ + { + "type": "function" + } + ], + "name": "callback", + "description": "The callback function to be executed when the timer expires." + } + }, + "subject": "Creates a timer instance for scheduling callbacks." + }, + "handle": { + "kind": "function", + "name": "module:uloop#handle", + "return": [ + { + "type": "module:uloop.handle", + "nullable": true + } + ], + "description": "Creates a handle instance for monitoring file descriptor events.\n\nThis function creates a handle instance for monitoring events on a file\ndescriptor, file, or socket. It takes the file or socket handle, a callback\nfunction to be invoked when the specified IO events occur, and bitwise OR-ed\nflags of IO events (`ULOOP_READ`, `ULOOP_WRITE`) that the callback should be\ninvoked for.", + "params": { + "handle": { + "type": [ + { + "type": "number" + }, + { + "type": "module:fs.file" + }, + { + "type": "module:fs.proc" + }, + { + "type": "module:socket.socket" + } + ], + "name": "handle", + "description": "The file handle (descriptor number, file or socket instance)." + }, + "callback": { + "type": [ + { + "type": "function" + } + ], + "name": "callback", + "description": "The callback function to be invoked when the specified IO events occur." + }, + "events": { + "type": [ + { + "type": "number" + } + ], + "name": "events", + "description": "Bitwise OR-ed flags of IO events (`ULOOP_READ`, `ULOOP_WRITE`) that the\ncallback should be invoked for." + } + }, + "subject": "Creates a handle instance for monitoring file descriptor events." + }, + "process": { + "kind": "function", + "name": "module:uloop#process", + "return": [ + { + "type": "module:uloop.process", + "nullable": true + } + ], + "description": "Creates a process instance for executing external programs.\n\nThis function creates a process instance for executing external programs.\nIt takes the executable path string, an optional string array as the argument\nvector, an optional dictionary describing environment variables, and a\ncallback function to be invoked when the invoked process ends.", + "params": { + "executable": { + "type": [ + { + "type": "string" + } + ], + "name": "executable", + "description": "The path to the executable program." + }, + "args": { + "type": [ + { + "type": "array", + "itemtype": [ + { + "type": "string" + } + ] + } + ], + "name": "[args]", + "description": "Optional. An array of strings representing the arguments passed to the\nexecutable.", + "optional": true + }, + "env": { + "type": [ + { + "type": "object", + "keytype": [ + { + "type": "string" + } + ], + "itemtype": [ + { + "type": "*" + } + ] + } + ], + "name": "[env]", + "description": "Optional. A dictionary describing environment variables for the process.", + "optional": true + }, + "callback": { + "type": [ + { + "type": "function" + } + ], + "name": "callback", + "description": "The callback function to be invoked when the invoked process ends." + } + }, + "subject": "Creates a process instance for executing external programs." + }, + "task": { + "kind": "function", + "name": "module:uloop#task", + "return": [ + { + "type": "module:uloop.task", + "nullable": true + } + ], + "description": "Creates a task instance for executing background tasks.\n\nThis function creates a task instance for executing background tasks.\nIt takes the task function to be invoked as a background process,\nan optional output callback function to be invoked when output is received\nfrom the task, and an optional input callback function to be invoked\nwhen input is required by the task.", + "params": { + "taskFunction": { + "type": [ + { + "type": "function" + } + ], + "name": "taskFunction", + "description": "The task function to be invoked as a background process." + }, + "outputCallback": { + "type": [ + { + "type": "function" + } + ], + "name": "[outputCallback]", + "description": "Optional. The output callback function to be invoked when output is received\nfrom the task. It is invoked with the output data as the argument.", + "optional": true + }, + "inputCallback": { + "type": [ + { + "type": "function" + } + ], + "name": "[inputCallback]", + "description": "Optional. The input callback function to be invoked when input is required\nby the task. It is invoked with a function to send input to the task\nas the argument.", + "optional": true + } + }, + "subject": "Creates a task instance for executing background tasks." + }, + "interval": { + "kind": "function", + "name": "module:uloop#interval", + "return": [ + { + "type": "module:uloop.interval", + "nullable": true + } + ], + "description": "Creates an interval instance for scheduling repeated callbacks.\n\nThis function creates an interval instance for scheduling repeated callbacks\nto be executed at regular intervals. It takes an optional timeout parameter,\nwhich defaults to -1, indicating that the interval is initially not armed\nand can be armed later with the `.set(timeout)` method. A callback function\nmust be provided to be executed when the interval expires.", + "params": { + "timeout": { + "type": [ + { + "type": "number" + } + ], + "name": "[timeout=-1]", + "description": "Optional. The interval duration in milliseconds. Defaults to -1, indicating\nthe interval is not initially armed.", + "default": "-1", + "optional": true + }, + "callback": { + "type": [ + { + "type": "function" + } + ], + "name": "callback", + "description": "The callback function to be executed when the interval expires." + } + }, + "subject": "Creates an interval instance for scheduling repeated callbacks." + }, + "signal": { + "kind": "function", + "name": "module:uloop#signal", + "return": [ + { + "type": "module:uloop.signal", + "nullable": true + } + ], + "description": "Creates a signal instance for handling Unix signals.\n\nThis function creates a signal instance for handling Unix signals.\nIt takes the signal name string (with or without \"SIG\" prefix) or signal\nnumber, and a callback function to be invoked when the specified Unix signal\nis caught.", + "params": { + "signal": { + "type": [ + { + "type": "string" + }, + { + "type": "number" + } + ], + "name": "signal", + "description": "The signal name string (with or without \"SIG\" prefix) or signal number." + }, + "callback": { + "type": [ + { + "type": "function" + } + ], + "name": "callback", + "description": "The callback function to be invoked when the specified Unix signal is caught." + } + }, + "subject": "Creates a signal instance for handling Unix signals." + } + }, + "uloop.timer": { + "set": { + "kind": "function", + "name": "module:uloop.timer#set", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "description": "Rearms the uloop timer with the specified timeout.\n\nThis method rearms the uloop timer with the specified timeout value,\nallowing it to trigger after the specified amount of time. If no timeout\nvalue is provided or if the provided value is negative, the timer remains\ndisabled until rearmed with a positive timeout value.", + "params": { + "timeout": { + "type": [ + { + "type": "number" + } + ], + "name": "[timeout=-1]", + "description": "Optional. The timeout value in milliseconds until the timer expires.\nDefaults to -1, which disables the timer until rearmed with a positive timeout.", + "default": "-1", + "optional": true + } + }, + "subject": "Rearms the uloop timer with the specified timeout." + }, + "remaining": { + "kind": "function", + "name": "module:uloop.timer#remaining", + "return": [ + { + "type": "number" + } + ], + "description": "Returns the number of milliseconds until the uloop timer expires.\n\nThis method returns the remaining time until the uloop timer expires. If\nthe timer is not armed (i.e., disabled), it returns -1.", + "subject": "Returns the number of milliseconds until the uloop timer expires." + }, + "cancel": { + "kind": "function", + "name": "module:uloop.timer#cancel", + "return": [ + { + "type": "boolean" + } + ], + "description": "Cancels the uloop timer, disarming it and removing it from the event loop.\n\nThis method destroys the uloop timer and releases its associated resources.", + "subject": "Cancels the uloop timer, disarming it and removing it from the event loop." + } + }, + "uloop.handle": { + "fileno": { + "kind": "function", + "name": "module:uloop.handle#fileno", + "return": [ + { + "type": "number" + } + ], + "description": "Returns the file descriptor number.\n\nThis method returns the file descriptor number associated with the underlying\nhandle, which might refer to a socket or file instance.", + "subject": "Returns the file descriptor number." + }, + "handle": { + "kind": "function", + "name": "module:uloop.handle#handle", + "return": [ + { + "type": "module:fs.file" + }, + { + "type": "module:fs.proc" + }, + { + "type": "module:socket.socket" + } + ], + "description": "Returns the underlying file or socket instance.\n\nThis method returns the underlying file or socket instance associated with\nthe uloop handle.", + "subject": "Returns the underlying file or socket instance." + }, + "delete": { + "kind": "function", + "name": "module:uloop.handle#delete", + "return": [ + { + "type": "void" + } + ], + "description": "Unregisters the uloop handle.\n\nThis method unregisters the uloop handle from the uloop event loop and frees\nany associated resources. After calling this method, the handle instance\nshould no longer be used.", + "subject": "Unregisters the uloop handle." + } + }, + "uloop.process": { + "pid": { + "kind": "function", + "name": "module:uloop.process#pid", + "return": [ + { + "type": "number" + } + ], + "description": "Returns the process ID.\n\nThis method returns the process ID (PID) of the operating system process\nlaunched by {@link module:uloop#process|process().", + "subject": "Returns the process ID." + }, + "delete": { + "kind": "function", + "name": "module:uloop.process#delete", + "return": [ + { + "type": "boolean" + } + ], + "description": "Unregisters the process from uloop.\n\nThis method unregisters the process from the uloop event loop and releases\nany associated resources. However, note that the operating system process\nitself is not terminated by this method.", + "subject": "Unregisters the process from uloop." + } + }, + "uloop.pipe": { + "send": { + "kind": "function", + "name": "module:uloop.pipe#send", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "description": "Sends a serialized message to the task handle.\n\nThis method serializes the provided message and sends it over the task\ncommunication pipe. In the main thread, the message is deserialized and\npassed as an argument to the output callback function registered with the\ntask handle.", + "params": { + "msg": { + "type": [ + { + "type": "*" + } + ], + "name": "msg", + "description": "The message to be serialized and sent over the pipe. It can be of arbitrary type." + } + }, + "subject": "Sends a serialized message to the task handle." + }, + "receive": { + "kind": "function", + "name": "module:uloop.pipe#receive", + "return": [ + { + "type": "*", + "nullable": true + } + ], + "description": "Reads input from the task handle.\n\nThis method reads input from the task communication pipe. The input callback\nfunction registered with the task handle is invoked to return the input data,\nwhich is then serialized, sent over the pipe, and deserialized by the receive\nmethod.", + "subject": "Reads input from the task handle." + }, + "sending": { + "kind": "function", + "name": "module:uloop.pipe#sending", + "return": [ + { + "type": "boolean" + } + ], + "description": "Checks if the task handle provides input.\n\nThis method checks if the task handle has an input callback registered.\nIt returns a boolean value indicating whether an input callback is present.", + "subject": "Checks if the task handle provides input." + }, + "receiving": { + "kind": "function", + "name": "module:uloop.pipe#receiving", + "return": [ + { + "type": "boolean" + } + ], + "description": "Checks if the task handle reads output.\n\nThis method checks if the task handle has an output callback registered.\nIt returns a boolean value indicating whether an output callback is present.", + "subject": "Checks if the task handle reads output." + } + }, + "uloop.task": { + "pid": { + "kind": "function", + "name": "module:uloop.task#pid", + "return": [ + { + "type": "number" + } + ], + "description": "Returns the process ID.\n\nThis method returns the process ID (PID) of the underlying forked process\nlaunched by {@link module:uloop#task|task().", + "subject": "Returns the process ID." + }, + "kill": { + "kind": "function", + "name": "module:uloop.task#kill", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "description": "Terminates the task process.\n\nThis method terminates the task process. It sends a termination signal to\nthe task process, causing it to exit. Returns `true` on success, indicating\nthat the task process was successfully terminated. Returns `null` on error,\nsuch as when the task process has already terminated.", + "subject": "Terminates the task process." + }, + "finished": { + "kind": "function", + "name": "module:uloop.task#finished", + "return": [ + { + "type": "boolean" + } + ], + "description": "Checks if the task ran to completion.\n\nThis method checks if the task function has already run to completion.\nIt returns a boolean value indicating whether the task function has finished\nexecuting.", + "subject": "Checks if the task ran to completion." + } + }, + "uloop.interval": { + "set": { + "kind": "function", + "name": "module:uloop.interval#set", + "return": [ + { + "type": "boolean", + "nullable": true + } + ], + "description": "Rearms the uloop interval timer with the specified interval.\n\nThis method rearms the interval timer with the specified interval value,\nallowing it to trigger repeatedly after the specified amount of time. If no\ninterval value is provided or if the provided value is negative, the interval\nremains disabled until rearmed with a positive interval value.", + "params": { + "interval": { + "type": [ + { + "type": "number" + } + ], + "name": "[interval=-1]", + "description": "Optional. The interval value in milliseconds specifying when the interval\ntriggers again. Defaults to -1, which disables the interval until rearmed\nwith a positive interval value.", + "default": "-1", + "optional": true + } + }, + "subject": "Rearms the uloop interval timer with the specified interval." + }, + "remaining": { + "kind": "function", + "name": "module:uloop.interval#remaining", + "return": [ + { + "type": "number" + } + ], + "description": "Returns the milliseconds until the next expiration.\n\nThis method returns the remaining time until the uloop interval expires\nand triggers again. If the interval is not armed (i.e., disabled),\nit returns -1.", + "subject": "Returns the milliseconds until the next expiration." + }, + "expirations": { + "kind": "function", + "name": "module:uloop.interval#expirations", + "return": [ + { + "type": "number" + } + ], + "description": "Returns number of times the interval timer fired.\n\nThis method returns the number of times the uloop interval timer has expired\n(fired) since it was instantiated.", + "subject": "Returns number of times the interval timer fired." + }, + "cancel": { + "kind": "function", + "name": "module:uloop.interval#cancel", + "return": [ + { + "type": "boolean" + } + ], + "description": "Cancels the uloop interval.\n\nThis method cancels the uloop interval, disarming it and removing it from the\nevent loop. Associated resources are released.", + "subject": "Cancels the uloop interval." + } + }, + "uloop.signal": { + "signo": { + "kind": "function", + "name": "module:uloop.signal#signo", + "return": [ + { + "type": "number" + } + ], + "description": "Returns the associated signal number.\n\nThis method returns the signal number that this uloop signal handler is\nconfigured to respond to.", + "subject": "Returns the associated signal number." + }, + "delete": { + "kind": "function", + "name": "module:uloop.signal#delete", + "return": [ + { + "type": "boolean" + } + ], + "description": "Uninstalls the signal handler.\n\nThis method uninstalls the signal handler, restoring the previous or default\nhandler for the signal, and releasing any associated resources.", + "subject": "Uninstalls the signal handler." + } + }, + "zlib": { + "deflate": { + "kind": "function", + "name": "module:zlib#deflate", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "str_or_resource": { + "type": [ + { + "type": "string" + } + ], + "name": "str_or_resource", + "description": "The string or resource object to be parsed as JSON." + }, + "gzip": { + "type": [ + { + "type": "boolean", + "nullable": true + } + ], + "name": "[gzip=false]", + "description": "Add a gzip header if true (creates a gzip-compliant output, otherwise defaults to Zlib)", + "default": "false", + "optional": true + }, + "level": { + "type": [ + { + "type": "number", + "nullable": true + } + ], + "name": "[level=Z_DEFAULT_COMPRESSION]", + "description": "The compression level (0-9).", + "default": "Z_DEFAULT_COMPRESSION", + "optional": true + } + }, + "subject": "Compresses data in Zlib or gzip format.", + "description": "Compresses data in Zlib or gzip format.\n\nIf the input argument is a plain string, it is directly compressed.\n\nIf an array, object or resource value is given, this function will attempt to\ninvoke a `read()` method on it to read chunks of input text to incrementally\ncompress. Reading will stop if the object's `read()` method returns\neither `null` or an empty string.\n\nThrows an exception on errors.\n\nReturns the compressed data." + }, + "inflate": { + "kind": "function", + "name": "module:zlib#inflate", + "return": [ + { + "type": "string", + "nullable": true + } + ], + "params": { + "str_or_resource": { + "type": [ + { + "type": "string" + } + ], + "name": "str_or_resource", + "description": "The string or resource object to be parsed as JSON." + } + }, + "subject": "Decompresses data in Zlib or gzip format.", + "description": "Decompresses data in Zlib or gzip format.\n\nIf the input argument is a plain string, it is directly decompressed.\n\nIf an array, object or resource value is given, this function will attempt to\ninvoke a `read()` method on it to read chunks of input text to incrementally\ndecompress. Reading will stop if the object's `read()` method returns\neither `null` or an empty string.\n\nThrows an exception on errors.\n\nReturns the decompressed data." + } + } +} diff --git a/vscode/tsconfig.json b/vscode/tsconfig.json new file mode 100644 index 00000000..1bf59001 --- /dev/null +++ b/vscode/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es2020", + "lib": ["es2020"], + "outDir": "out", + "rootDir": "src", + "sourceMap": true + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + ".vscode-test" + ], + "references": [ + { "path": "./client" } + ] +}