diff options
author | bptato <nincsnevem662@gmail.com> | 2024-06-03 20:42:16 +0200 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2024-06-03 21:15:44 +0200 |
commit | 3aa8f1e0694d1606c3f3795f8b83e8a82caacd3e (patch) | |
tree | 9708d4599360116a96e4aa7f983eea387e8437c6 /src | |
parent | 3e12a95ab34e120fb958ba0eeebaada5def7cd11 (diff) | |
download | chawan-3aa8f1e0694d1606c3f3795f8b83e8a82caacd3e.tar.gz |
Move JS wrapper into Monoucha
Operation "modularize Chawan somewhat" part 3
Diffstat (limited to 'src')
50 files changed, 234 insertions, 3796 deletions
diff --git a/src/bindings/constcharp.nim b/src/bindings/constcharp.nim deleted file mode 100644 index f1753147..00000000 --- a/src/bindings/constcharp.nim +++ /dev/null @@ -1,6 +0,0 @@ -type - cstringConstImpl {.importc: "const char*".} = cstring - cstringConst* = distinct cstringConstImpl - -proc `[]`*(s: cstringConst; i: int): char = cstring(s)[i] -proc `$`*(s: cstringConst): string {.borrow.} diff --git a/src/bindings/libregexp.nim b/src/bindings/libregexp.nim deleted file mode 100644 index fe603908..00000000 --- a/src/bindings/libregexp.nim +++ /dev/null @@ -1,30 +0,0 @@ -type - LREFlag* {.size: sizeof(cint).} = enum - LRE_FLAG_GLOBAL = "g" - LRE_FLAG_IGNORECASE = "i" - LRE_FLAG_MULTILINE = "m" - LRE_FLAG_DOTALL = "s" - LRE_FLAG_UNICODE = "u" - LRE_FLAG_STICKY = "y" - - LREFlags* = set[LREFlag] - -func toCInt*(flags: LREFlags): cint = - cint(cast[uint8](flags)) - -func toLREFlags*(flags: cint): LREFlags = - cast[LREFlags](flags) - -{.passc: "-Ilib/".} - -{.push header: "quickjs/libregexp.h", importc.} -proc lre_compile*(plen: ptr cint; error_msg: cstring; error_msg_size: cint; - buf: cstring; buf_len: csize_t; re_flags: cint; opaque: pointer): ptr uint8 - -proc lre_exec*(capture: ptr ptr uint8; bc_buf, cbuf: ptr uint8; - cindex, clen, cbuf_type: cint; opaque: pointer): cint - -proc lre_get_capture_count*(bc_buf: ptr uint8): cint - -proc lre_get_flags*(bc_buf: ptr uint8): cint -{.pop.} diff --git a/src/bindings/libunicode.nim b/src/bindings/libunicode.nim deleted file mode 100644 index 18d146fe..00000000 --- a/src/bindings/libunicode.nim +++ /dev/null @@ -1,44 +0,0 @@ -type - DynBufReallocFunc = proc(opaque, p: pointer; size: csize_t): pointer {.cdecl.} - - CharRange* = object - len*: cint # in points, always even - size*: cint - points*: ptr uint32 # points sorted by increasing value - mem_opaque*: pointer - realloc_func*: DynBufReallocFunc - - UnicodeNormalizationEnum* {.size: sizeof(cint).} = enum - UNICODE_NFC, UNICODE_NFD, UNICODE_NKFC, UNICODE_NKFD - -{.passc: "-Ilib/".} - -{.push header: "quickjs/libunicode.h", importc.} - -proc cr_init*(cr: ptr CharRange; mem_opaque: pointer; - realloc_func: DynBufReallocFunc) - -proc cr_free*(cr: ptr CharRange) - -proc unicode_normalize*(pdst: ptr ptr uint32; src: ptr uint32; src_len: cint; - n_type: UnicodeNormalizationEnum; opaque: pointer; - realloc_func: DynBufReallocFunc): cint - -proc unicode_script*(cr: ptr CharRange; script_name: cstring; is_ext: cint): - cint -proc unicode_prop*(cr: ptr CharRange; prop_name: cstring): cint -proc unicode_general_category*(cr: ptr CharRange; gc_name: cstring): cint - -const LRE_CC_RES_LEN_MAX* = 3 - -# conv_type: -# 0 = to upper -# 1 = to lower -# 2 = case folding -# res must be an array of LRE_CC_RES_LEN_MAX -proc lre_case_conv*(res: ptr UncheckedArray[uint32]; c: uint32; - conv_type: cint): cint - -proc lre_is_space*(c: uint32): cint - -{.pop.} diff --git a/src/bindings/quickjs.nim b/src/bindings/quickjs.nim deleted file mode 100644 index 20dd048b..00000000 --- a/src/bindings/quickjs.nim +++ /dev/null @@ -1,573 +0,0 @@ -import bindings/constcharp - -const qjsheader = "quickjs/quickjs.h" - -{.passc: "-Ilib/".} -{.passl: "-Llib/ -lquickjs -lm -lpthread".} - -const ## all tags with a reference count are negative - JS_TAG_FIRST* = -10 ## first negative tag - JS_TAG_BIG_INT* = -10 - JS_TAG_BIG_FLOAT* = -9 - JS_TAG_SYMBOL* = -8 - JS_TAG_STRING* = -7 - JS_TAG_SHAPE* = -6 ## used internally during GC - JS_TAG_ASYNC_FUNCTION* = -5 ## used internally during GC - JS_TAG_VAR_REF* = -4 ## used internally during GC - JS_TAG_MODULE* = -3 ## used internally - JS_TAG_FUNCTION_BYTECODE* = -2 ## used internally - JS_TAG_OBJECT* = -1 - JS_TAG_INT* = 0 - JS_TAG_BOOL* = 1 - JS_TAG_NULL* = 2 - JS_TAG_UNDEFINED* = 3 - JS_TAG_UNINITIALIZED* = 4 - JS_TAG_CATCH_OFFSET* = 5 - JS_TAG_EXCEPTION* = 6 - JS_TAG_FLOAT64* = 7 ## any larger tag is FLOAT64 if JS_NAN_BOXING - -when sizeof(int) < sizeof(int64): - {.passc: "-DJS_NAN_BOXING".} - type - JSValue* {.importc, header: qjsheader.} = distinct uint64 - - template JS_VALUE_GET_TAG*(v: untyped): int32 = - cast[int32](cast[uint64](v) shr 32) - - template JS_VALUE_GET_PTR*(v: untyped): pointer = - cast[pointer](v) - - template JS_MKVAL*(t, val: untyped): JSValue = - JSValue((cast[uint64](int64(t)) shl 32) or uint32(val)) - - template JS_MKPTR*(t, p: untyped): JSValue = - JSValue((cast[uint64](int64(t)) shl 32) or cast[uint](p)) - - proc `==`*(a, b: JSValue): bool {.borrow.} -else: - type - JSValueUnion* {.importc, header: qjsheader, union.} = object - int32*: int32 - float64*: float64 - `ptr`*: pointer - JSValue* {.importc, header: qjsheader.} = object - u*: JSValueUnion - tag*: int64 - - template JS_VALUE_GET_TAG*(v: untyped): int32 = - cast[int32](v.tag) - - template JS_VALUE_GET_PTR*(v: untyped): pointer = - cast[pointer](v.u) - - template JS_MKVAL*(t, val: untyped): JSValue = - JSValue(u: JSValueUnion(`int32`: val), tag: t) - - template JS_MKPTR*(t, p: untyped): JSValue = - JSValue(u: JSValueUnion(`ptr`: p), tag: t) - -type - JSRuntimeT {.importc: "JSRuntime", header: qjsheader, - incompleteStruct.} = object - JSContextT {.importc: "JSContext", header: qjsheader, - incompleteStruct.} = object - JSModuleDefT {.importc: "JSModuleDef", header: qjsheader, - incompleteStruct.} = object - - JSRuntime* = ptr JSRuntimeT - JSContext* = ptr JSContextT - JSModuleDef* = ptr JSModuleDefT - JSCFunction* = proc(ctx: JSContext; this_val: JSValue; argc: cint; - argv: ptr UncheckedArray[JSValue]): JSValue {.cdecl.} - JSCFunctionData* = proc(ctx: JSContext; this_val: JSValue; argc: cint; - argv: ptr UncheckedArray[JSValue]; magic: cint; - func_data: ptr UncheckedArray[JSValue]): JSValue {.cdecl.} - JSGetterFunction* = proc(ctx: JSContext; this_val: JSValue): JSValue {.cdecl.} - JSSetterFunction* = proc(ctx: JSContext; this_val, val: JSValue): - JSValue {.cdecl.} - JSGetterMagicFunction* = proc(ctx: JSContext; this_val: JSValue; magic: cint): - JSValue {.cdecl.} - JSSetterMagicFunction* = proc(ctx: JSContext; this_val, val: JSValue; - magic: cint): JSValue {.cdecl.} - JSInterruptHandler* = proc(rt: JSRuntime; opaque: pointer): cint {.cdecl.} - JSClassID* = uint32 - JSAtom* = distinct uint32 - JSClassFinalizer* = proc(rt: JSRuntime; val: JSValue) {.cdecl.} - JSClassCheckDestroy* = proc(rt: JSRuntime; val: JSValue): JS_BOOL {.cdecl.} - JSClassGCMark* = proc(rt: JSRuntime; val: JSValue; mark_func: JS_MarkFunc) - {.cdecl.} - JS_MarkFunc* = proc(rt: JSRuntime; gp: ptr JSGCObjectHeader) {.cdecl.} - JSModuleNormalizeFunc* = proc(ctx: JSContext; module_base_name, - module_name: cstringConst; opaque: pointer): cstring {.cdecl.} - JSModuleLoaderFunc* = proc(ctx: JSContext; module_name: cstringConst, - opaque: pointer): JSModuleDef {.cdecl.} - JSJobFunc* = proc(ctx: JSContext; argc: cint; - argv: ptr UncheckedArray[JSValue]): JSValue {.cdecl.} - JSGCObjectHeader* {.importc, header: qjsheader.} = object - JSFreeArrayBufferDataFunc* = proc(rt: JSRuntime; opaque, p: pointer) {.cdecl.} - - JSPropertyDescriptor* {.importc, header: qjsheader.} = object - flags*: cint - value*: JSValue - getter*: JSValue - setter*: JSValue - - JSClassExoticMethods* {.importc, header: qjsheader.} = object - get_own_property*: proc(ctx: JSContext; desc: ptr JSPropertyDescriptor; - obj: JSValue; prop: JSAtom): cint {.cdecl.} - get_own_property_names*: proc(ctx: JSContext; - ptab: ptr ptr UncheckedArray[JSPropertyEnum]; plen: ptr uint32; - obj: JSValue): cint {.cdecl.} - delete_property*: proc(ctx: JSContext; obj: JSValue; prop: JSAtom): cint - {.cdecl.} - define_own_property*: proc(ctx: JSContext; this_obj: JSValue; prop: JSAtom; - val, getter, setter: JSValue; flags: cint): cint {.cdecl.} - has_property*: proc(ctx: JSContext; obj: JSValue; atom: JSAtom): cint - {.cdecl.} - get_property*: proc(ctx: JSContext; obj: JSValue; atom: JSAtom; - receiver: JSValue; flags: cint): JSValue {.cdecl.} - set_property*: proc(ctx: JSContext; obj: JSValue; atom: JSAtom; - value, receiver: JSValue; flags: cint): cint {.cdecl.} - - JSClassExoticMethodsConst* {.importc: "const JSClassExoticMethods *", - header: qjsheader.} = ptr JSClassExoticMethods - - JSRuntimeCleanUpFunc* {.importc.} = proc(rt: JSRuntime) {.cdecl.} - - JSClassDef* {.importc, header: qjsheader.} = object - class_name*: cstring - finalizer*: JSClassFinalizer - gc_mark*: JSClassGCMark - call*: pointer - exotic*: JSClassExoticMethodsConst - can_destroy*: JSClassCheckDestroy - - JSClassDefConst* {.importc: "const JSClassDef *", - header: qjsheader.} = ptr JSClassDef - - JSMemoryUsage* = object - malloc_size*, malloc_limit*, memory_used_size*: int64 - malloc_count*: int64 - memory_used_count*: int64 - atom_count*, atom_size*: int64 - str_count*, str_size*: int64 - obj_count*, obj_size*: int64 - prop_count*, prop_size*: int64 - shape_count*, shape_size*: int64 - js_func_count*, js_func_size*, js_func_code_size*: int64 - js_func_pc2line_count*, js_func_pc2line_size*: int64 - c_func_count*, array_count*: int64 - fast_array_count*, fast_array_elements*: int64 - binary_object_count*, binary_object_size*: int64 - - JSCFunctionEnum* {.size: sizeof(uint8).} = enum - JS_CFUNC_generic, JS_CFUNC_generic_magic, JS_CFUNC_constructor, - JS_CFUNC_constructor_magic, JS_CFUNC_constructor_or_func, - JS_CFUNC_constructor_or_func_magic, JS_CFUNC_f_f, JS_CFUNC_f_f_f, - JS_CFUNC_getter, JS_CFUNC_setter, JS_CFUNC_getter_magic, - JS_CFUNC_setter_magic, JS_CFUNC_iterator_next - - JSCFunctionType* {.importc, union.} = object - generic*: JSCFunction - getter*: JSGetterFunction - setter*: JSSetterFunction - getter_magic*: JSGetterMagicFunction - setter_magic*: JSSetterMagicFunction - - JSCFunctionListEntryFunc = object - length*: uint8 - cproto*: JSCFunctionEnum - cfunc*: JSCFunctionType - - JSCFunctionListEntryGetSet = object - get*: JSCFunctionType - set*: JSCFunctionType - - JSCFunctionListEntryAlias = object - name: cstring - base: cint - - JSCFunctionListEntryPropList = object - tab: ptr UncheckedArray[JSCFunctionListEntry] - len: cint - - JSCFunctionListEntryU* {.union.} = object - `func`* {.importc: "func".}: JSCFunctionListEntryFunc - getset: JSCFunctionListEntryGetSet - alias: JSCFunctionListEntryAlias - prop_list: JSCFunctionListEntryPropList - str: cstring - i32: int32 - i64: int64 - f64: cdouble - - JSCFunctionListEntry* {.importc.} = object - name*: cstring - prop_flags*: uint8 - def_type*: uint8 - magic*: int16 - u* {.importc.}: JSCFunctionListEntryU - - JSRefCountHeader* {.importc.} = object - ref_count* {.importc.}: cint - - JS_BOOL* = distinct cint - - JSPropertyEnum* {.importc.} = object - is_enumerable*: JS_BOOL - atom*: JSAtom - - JSClassEnum* {.size: sizeof(uint32).} = enum - JS_CLASS_OBJECT = 1 - JS_CLASS_ARRAY - JS_CLASS_ERROR - - JSMallocState* {.importc.} = object - malloc_count*: csize_t - malloc_size*: csize_t - malloc_limit*: csize_t - opaque*: pointer - - JSMallocFunctions* {.importc.} = object - js_malloc*: proc(s: ptr JSMallocState; size: csize_t): pointer {.cdecl.} - js_free*: proc(s: ptr JSMallocState; p: pointer) {.cdecl.} - js_realloc*: proc(s: ptr JSMallocState; p: pointer; size: csize_t): pointer - {.cdecl.} - js_malloc_usable_size*: proc(p: pointer) {.cdecl.} - -func `==`*(a, b: JSAtom): bool {.borrow.} - -converter toBool*(js: JS_BOOL): bool {.inline.} = - cast[cint](js) != 0 - -converter toJSBool*(b: bool): JS_BOOL {.inline.} = - cast[JS_BOOL](cint(b)) - -converter toJSClassID*(e: JSClassEnum): JSClassID {.inline.} = - JSClassID(e) - -const - JS_NULL* = JS_MKVAL(JS_TAG_NULL, 0) - JS_UNDEFINED* = JS_MKVAL(JS_TAG_UNDEFINED, 0) - JS_FALSE* = JS_MKVAL(JS_TAG_BOOL, 0) - JS_TRUE* = JS_MKVAL(JS_TAG_BOOL, 1) - JS_EXCEPTION* = JS_MKVAL(JS_TAG_EXCEPTION, 0) - JS_UNINITIALIZED* = JS_MKVAL(JS_TAG_UNINITIALIZED, 0) - -const - JS_EVAL_TYPE_GLOBAL* = (0 shl 0) ## global code (default) - JS_EVAL_TYPE_MODULE* = (1 shl 0) ## module code - JS_EVAL_TYPE_DIRECT* = (2 shl 0) ## direct call (internal use) - JS_EVAL_TYPE_INDIRECT* = (3 shl 0) ## indirect call (internal use) - JS_EVAL_TYPE_MASK* = (3 shl 0) - JS_EVAL_FLAG_SHEBANG* = (1 shl 2) ## skip first line beginning with '#!' - JS_EVAL_FLAG_STRICT* = (1 shl 3) ## force 'strict' mode - JS_EVAL_FLAG_STRIP* = (1 shl 4) ## force 'strip' mode - JS_EVAL_FLAG_COMPILE_ONLY* = (1 shl 5) ## internal use - -const - JS_DEF_CFUNC* = 0 - JS_DEF_CGETSET* = 1 - JS_DEF_CGETSET_MAGIC* = 2 - JS_DEF_PROP_STRING* = 3 - JS_DEF_PROP_INT32* = 4 - JS_DEF_PROP_INT64* = 5 - JS_DEF_PROP_DOUBLE* = 6 - JS_DEF_PROP_UNDEFINED* = 7 - JS_DEF_OBJECT* = 8 - JS_DEF_ALIAS* = 9 - -const - JS_PROP_CONFIGURABLE* = (1 shl 0) - JS_PROP_WRITABLE* = (1 shl 1) - JS_PROP_ENUMERABLE* = (1 shl 2) - JS_PROP_C_W_E* = (JS_PROP_CONFIGURABLE or JS_PROP_WRITABLE or - JS_PROP_ENUMERABLE) - JS_PROP_LENGTH* = (1 shl 3) # used internally in Arrays - JS_PROP_TMASK* = (3 shl 4) # mask for NORMAL, GETSET, VARREF, AUTOINIT - JS_PROP_NORMAL* = (0 shl 4) - JS_PROP_GETSET* = (1 shl 4) - JS_PROP_VARREF* = (2 shl 4) # used internally - JS_PROP_AUTOINIT* = (3 shl 4) # used internally - JS_PROP_THROW* = (1 shl 14) - -const - JS_GPN_STRING_MASK* = (1 shl 0) - JS_GPN_SYMBOL_MASK* = (1 shl 1) - JS_GPN_PRIVATE_MASK* = (1 shl 2) - JS_GPN_ENUM_ONLY* = (1 shl 3) - JS_GPN_SET_ENUM* = (1 shl 4) - -const - JS_PARSE_JSON_EXT* = (1 shl 0) - -template JS_CFUNC_DEF*(n: string; len: uint8; func1: JSCFunction): - JSCFunctionListEntry = - JSCFunctionListEntry(name: cstring(n), - prop_flags: JS_PROP_WRITABLE or JS_PROP_CONFIGURABLE, - def_type: JS_DEF_CFUNC, - u: JSCFunctionListEntryU( - `func`: JSCFunctionListEntryFunc( - length: len, - cproto: JS_CFUNC_generic, - cfunc: JSCFunctionType(generic: func1)))) - -template JS_CFUNC_DEF_NOCONF*(n: string; len: uint8; func1: JSCFunction): - JSCFunctionListEntry = - JSCFunctionListEntry(name: cstring(n), - prop_flags: JS_PROP_ENUMERABLE, - def_type: JS_DEF_CFUNC, - u: JSCFunctionListEntryU( - `func`: JSCFunctionListEntryFunc( - length: len, - cproto: JS_CFUNC_generic, - cfunc: JSCFunctionType(generic: func1)))) - -template JS_CGETSET_DEF*(n: string; fgetter: JSGetterFunction; - fsetter: JSSetterFunction): JSCFunctionListEntry = - JSCFunctionListEntry(name: cstring(n), - prop_flags: JS_PROP_CONFIGURABLE, - def_type: JS_DEF_CGETSET, - u: JSCFunctionListEntryU( - getset: JSCFunctionListEntryGetSet( - get: JSCFunctionType(getter: fgetter), - set: JSCFunctionType(setter: fsetter)))) - -template JS_CGETSET_DEF_NOCONF*(n: string; fgetter: JSGetterFunction; - fsetter: JSSetterFunction): JSCFunctionListEntry = - JSCFunctionListEntry(name: cstring(n), - prop_flags: JS_PROP_ENUMERABLE, - def_type: JS_DEF_CGETSET, - u: JSCFunctionListEntryU( - getset: JSCFunctionListEntryGetSet( - get: JSCFunctionType(getter: fgetter), - set: JSCFunctionType(setter: fsetter)))) - -template JS_CGETSET_MAGIC_DEF*(n: string; fgetter, fsetter: typed; - m: int16): JSCFunctionListEntry = - JSCFunctionListEntry(name: cstring(n), - prop_flags: JS_PROP_CONFIGURABLE, - def_type: JS_DEF_CGETSET_MAGIC, - magic: m, - u: JSCFunctionListEntryU( - getset: JSCFunctionListEntryGetSet( - get: JSCFunctionType(getter_magic: fgetter), - set: JSCFunctionType(setter_magic: fsetter)))) - -{.push header: qjsheader, importc, cdecl.} - -proc JS_NewRuntime*(): JSRuntime -proc JS_NewRuntime2*(mf: ptr JSMallocFunctions; opaque: pointer): JSRuntime -proc JS_FreeRuntime*(rt: JSRuntime) -proc JS_GetRuntime*(ctx: JSContext): JSRuntime - -proc JS_ComputeMemoryUsage*(rt: JSRuntime; s: ptr JSMemoryUsage) -proc JS_RunGC*(rt: JSRuntime) - -proc JS_NewContext*(rt: JSRuntime): JSContext -proc JS_NewContextRaw*(rt: JSRuntime): JSContext -proc JS_FreeContext*(ctx: JSContext) - -proc JS_GetGlobalObject*(ctx: JSContext): JSValue -proc JS_IsInstanceOf*(ctx: JSContext; val: JSValue; obj: JSValue): cint - -proc JS_NewArray*(ctx: JSContext): JSValue -proc JS_NewObject*(ctx: JSContext): JSValue -proc JS_NewObjectClass*(ctx: JSContext; class_id: JSClassID): JSValue -proc JS_NewObjectProto*(ctx: JSContext; proto: JSValue): JSValue -proc JS_NewObjectProtoClass*(ctx: JSContext; proto: JSValue; - class_id: JSClassID): JSValue -proc JS_NewPromiseCapability*(ctx: JSContext; - resolving_funcs: ptr UncheckedArray[JSValue]): JSValue -proc JS_SetOpaque*(obj: JSValue; opaque: pointer) -proc JS_GetOpaque*(obj: JSValue; class_id: JSClassID): pointer -proc JS_GetOpaque2*(ctx: JSContext; obj: JSValue; class_id: JSClassID): pointer -proc JS_GetClassID*(obj: JSValue): JSClassID - -proc JS_ParseJSON*(ctx: JSContext; buf: cstring; buf_len: csize_t; - filename: cstring): JSValue -proc JS_ParseJSON2*(ctx: JSContext; buf: cstring; buf_len: csize_t; - filename: cstring; flags: cint): JSValue - -proc JS_NewArrayBuffer*(ctx: JSContext; buf: ptr UncheckedArray[uint8]; - len: csize_t; free_func: JSFreeArrayBufferDataFunc; opaque: pointer; - is_shared: JS_BOOL): JSValue -proc JS_GetArrayBuffer*(ctx: JSContext; psize: ptr csize_t; obj: JSValue): - ptr uint8 -proc JS_GetTypedArrayBuffer*(ctx: JSContext; obj: JSValue; - pbyte_offset, pbyte_length, pbytes_per_element: ptr csize_t): JSValue - -proc JS_NewClassID*(pclass_id: ptr JSClassID): JSClassID -proc JS_NewClass*(rt: JSRuntime; class_id: JSClassID; - class_def: ptr JSClassDef): cint -proc JS_IsRegisteredClass*(rt: JSRuntime; class_id: JSClassID): cint -proc JS_SetClassProto*(ctx: JSContext; class_id: JSClassID; obj: JSValue) -proc JS_GetClassProto*(ctx: JSContext; class_id: JSClassID): JSValue -proc JS_SetConstructor*(ctx: JSContext; func_obj: JSValue; proto: JSValue) -proc JS_SetPrototype*(ctx: JSContext; obj: JSValue; proto_val: JSValue): cint -proc JS_GetPrototype*(ctx: JSContext; val: JSValue): JSValue - -proc JS_NewBool*(ctx: JSContext; val: JS_BOOL): JSValue -proc JS_NewInt32*(ctx: JSContext; val: int32): JSValue -proc JS_NewCatchOffset*(ctx: JSContext; val: int32): JSValue -proc JS_NewInt64*(ctx: JSContext; val: int64): JSValue -proc JS_NewUint32*(ctx: JSContext; val: uint32): JSValue -proc JS_NewBigInt64*(ctx: JSContext; val: int64): JSValue -proc JS_NewBigUInt64*(ctx: JSContext; val: uint64): JSValue -proc JS_NewFloat64*(ctx: JSContext; val: cdouble): JSValue - -const JS_ATOM_NULL* = JSAtom(0) - -proc JS_NewAtomLen*(ctx: JSContext; str: cstring; len: csize_t): JSAtom -proc JS_NewAtomUInt32*(ctx: JSContext; u: uint32): JSAtom -proc JS_ValueToAtom*(ctx: JSContext; val: JSValue): JSAtom -proc JS_AtomToValue*(ctx: JSContext; atom: JSAtom): JSValue -proc JS_AtomToCString*(ctx: JSContext; atom: JSAtom): cstring -proc JS_FreeAtom*(ctx: JSContext; atom: JSAtom) -proc JS_FreeAtomRT*(rt: JSRuntime; atom: JSAtom) - -proc JS_NewCFunction2*(ctx: JSContext; cfunc: JSCFunction; name: cstring; - length: cint; proto: JSCFunctionEnum; magic: cint): JSValue -proc JS_NewCFunctionData*(ctx: JSContext; cfunc: JSCFunctionData; - length, magic, data_len: cint; data: ptr UncheckedArray[JSValue]): JSValue -proc JS_NewCFunction*(ctx: JSContext; cfunc: JSCFunction; name: cstring; - length: cint): JSValue - -proc JS_NewString*(ctx: JSContext; str: cstring): JSValue -proc JS_NewStringLen*(ctx: JSContext; str: cstring; len1: csize_t): JSValue -proc JS_NewAtomString*(ctx: JSContext; str: cstring): JSValue -proc JS_ToString*(ctx: JSContext; val: JSValue): JSValue - -proc JS_SetProperty*(ctx: JSContext; this_obj: JSValue; prop: JSAtom; - val: JSValue): cint -proc JS_SetPropertyUint32*(ctx: JSContext; this_obj: JSValue; idx: uint32; - val: JSValue): cint -proc JS_SetPropertyInt64*(ctx: JSContext; this_obj: JSValue; idx: int64; - val: JSValue): cint -proc JS_SetPropertyStr*(ctx: JSContext; this_obj: JSValue; prop: cstring; - val: JSValue): cint -proc JS_SetPropertyFunctionList*(ctx: JSContext; obj: JSValue; - tab: ptr JSCFunctionListEntry; len: cint) -proc JS_GetProperty*(ctx: JSContext; this_obj: JSValue; prop: JSAtom): JSValue -proc JS_GetPropertyStr*(ctx: JSContext; this_obj: JSValue; prop: cstring): - JSValue -proc JS_GetPropertyUint32*(ctx: JSContext; this_obj: JSValue; idx: uint32): - JSValue -proc JS_GetOwnPropertyNames*(ctx: JSContext; - ptab: ptr ptr UncheckedArray[JSPropertyEnum]; plen: ptr uint32; - obj: JSValue; flags: cint): cint - -proc JS_GetOwnProperty*(ctx: JSContext; desc: ptr JSPropertyDescriptor; - obj: JSValue; prop: JSAtom): cint -proc JS_Call*(ctx: JSContext; func_obj, this_obj: JSValue; argc: cint; - argv: ptr UncheckedArray[JSValue]): JSValue -proc JS_NewObjectFromCtor*(ctx: JSContext; ctor: JSValue; - class_id: JSClassID): JSValue -proc JS_Invoke*(ctx: JSContext; this_obj: JSValue; atom: JSAtom; argc: cint; - argv: ptr UncheckedArray[JSValue]): JSValue -proc JS_CallConstructor*(ctx: JSContext; func_obj: JSValue; argc: cint; - argv: ptr UncheckedArray[JSValue]): JSValue - -proc JS_DefineProperty*(ctx: JSContext; this_obj: JSValue; prop: JSAtom; - val, getter, setter: JSValue; flags: cint): cint -proc JS_DefinePropertyValue*(ctx: JSContext; this_obj: JSValue; prop: JSAtom; - val: JSValue; flags: cint): cint -proc JS_DefinePropertyValueUint32*(ctx: JSContext; this_obj: JSValue; - idx: uint32; val: JSValue; flags: cint): cint -proc JS_DefinePropertyValueStr*(ctx: JSContext; this_obj: JSValue; - prop: cstring; val: JSValue; flags: cint): cint -proc JS_DefinePropertyValueGetSet*(ctx: JSContext; this_obj: JSValue; - prop: JSAtom; getter, setter: JSValue; flags: cint): cint - -proc JS_FreeValue*(ctx: JSContext; v: JSValue) -proc JS_FreeValueRT*(rt: JSRuntime; v: JSValue) -proc JS_DupValue*(ctx: JSContext; v: JSValue): JSValue - -proc JS_ToBool*(ctx: JSContext; val: JSValue): cint # return -1 for JS_EXCEPTION -proc JS_ToInt32*(ctx: JSContext; pres: ptr int32; val: JSValue): cint -proc JS_ToUint32*(ctx: JSContext; pres: ptr uint32; val: JSValue): cint -proc JS_ToInt64*(ctx: JSContext; pres: ptr int64; val: JSValue): cint -proc JS_ToIndex*(ctx: JSContext; plen: ptr uint64; val: JSValue): cint -proc JS_ToFloat64*(ctx: JSContext; pres: ptr float64; val: JSValue): cint -# return an exception if 'val' is a Number -proc JS_ToBigInt64*(ctx: JSContext; pres: ptr int64; val: JSValue): cint -# same as JS_ToInt64 but allow BigInt -proc JS_ToInt64Ext*(ctx: JSContext; pres: ptr int64; val: JSValue): cint - -proc JS_ToCStringLen*(ctx: JSContext; plen: ptr csize_t; val1: JSValue): cstring -proc JS_ToCString*(ctx: JSContext; val1: JSValue): cstring -proc JS_FreeCString*(ctx: JSContext, `ptr`: cstring) - -proc JS_NewNarrowStringLen*(ctx: JSContext; s: cstring; len: csize_t): JSValue -proc JS_IsStringWideChar*(str: JSValue): JS_BOOL -proc JS_GetNarrowStringBuffer*(str: JSValue): ptr UncheckedArray[uint8] -proc JS_GetStringLength*(str: JSValue): uint32 - -proc JS_Eval*(ctx: JSContext; input: cstring; input_len: csize_t; - filename: cstring; eval_flags: cint): JSValue -proc JS_EvalFunction*(ctx: JSContext; val: JSValue): JSValue -proc JS_SetInterruptHandler*(rt: JSRuntime; cb: JSInterruptHandler; - opaque: pointer) -proc JS_SetCanBlock*(rt: JSRuntime; can_block: JS_BOOL) -proc JS_SetIsHTMLDDA*(ctx: JSContext; obj: JSValue) - -proc JS_IsNumber*(v: JSValue): JS_BOOL -proc JS_IsBigInt*(v: JSValue): JS_BOOL -proc JS_IsBigFloat*(v: JSValue): JS_BOOL -proc JS_IsBigDecimal*(v: JSValue): JS_BOOL -proc JS_IsBool*(v: JSValue): JS_BOOL -proc JS_IsNull*(v: JSValue): JS_BOOL -proc JS_IsUndefined*(v: JSValue): JS_BOOL -proc JS_IsException*(v: JSValue): JS_BOOL -proc JS_IsUninitialized*(v: JSValue): JS_BOOL -proc JS_IsString*(v: JSValue): JS_BOOL -proc JS_IsSymbol*(v: JSValue): JS_BOOL -proc JS_IsObject*(v: JSValue): JS_BOOL - -proc JS_IsFunction*(ctx: JSContext; val: JSValue): JS_BOOL -proc JS_IsArray*(ctx: JSContext; v: JSValue): cint - -proc JS_Throw*(ctx: JSContext; obj: JSValue): JSValue -proc JS_GetException*(ctx: JSContext): JSValue -proc JS_IsError*(ctx: JSContext; v: JSValue): JS_BOOL -proc JS_SetUncatchableError*(ctx: JSContext; val: JSValue; flag: JS_BOOL) -proc JS_ResetUncatchableError*(ctx: JSContext) -proc JS_NewError*(ctx: JSContext): JSValue -proc JS_ThrowSyntaxError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, - discardable.} -proc JS_ThrowTypeError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, - discardable.} -proc JS_ThrowReferenceError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, - discardable.} -proc JS_ThrowRangeError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, - discardable.} -proc JS_ThrowInternalError*(ctx: JSContext; fmt: cstring): JSValue {.varargs, - discardable.} - -proc JS_SetModuleLoaderFunc*(rt: JSRuntime; - module_normalize: JSModuleNormalizeFunc; module_loader: JSModuleLoaderFunc; - opaque: pointer) -proc JS_GetImportMeta*(ctx: JSContext; m: JSModuleDef): JSValue -proc JS_GetModuleName*(ctx: JSContext; m: JSModuleDef): JSAtom - -proc JS_EnqueueJob*(ctx: JSContext; job_func: JSJobFunc; argc: cint; - argv: ptr UncheckedArray[JSValue]): cint -proc JS_IsJobPending*(rt: JSRuntime): JS_BOOL -proc JS_ExecutePendingJob*(rt: JSRuntime; pctx: ptr JSContext): cint - -proc JS_GetRuntimeOpaque*(rt: JSRuntime): pointer -proc JS_SetRuntimeOpaque*(rt: JSRuntime; p: pointer) -proc JS_SetRuntimeCleanUpFunc*(rt: JSRuntime; - cleanup_func: JSRuntimeCleanUpFunc) - -proc JS_SetContextOpaque*(ctx: JSContext; opaque: pointer) -proc JS_GetContextOpaque*(ctx: JSContext): pointer - -proc js_malloc*(ctx: JSContext; size: csize_t): pointer -proc js_mallocz*(ctx: JSContext; size: csize_t): pointer -proc js_realloc*(ctx: JSContext; p: pointer; size: csize_t): pointer -proc js_free_rt*(rt: JSRuntime; p: pointer) -proc js_free*(ctx: JSContext; p: pointer) - -proc js_strdup*(ctx: JSContext; str: cstring): cstring -{.pop.} diff --git a/src/config/chapath.nim b/src/config/chapath.nim index d9ba3c50..8397cb3d 100644 --- a/src/config/chapath.nim +++ b/src/config/chapath.nim @@ -2,10 +2,10 @@ import std/options import std/os import std/posix -import js/fromjs -import js/javascript -import js/jserror -import js/tojs +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import monoucha/tojs import types/opt import utils/twtstr diff --git a/src/config/config.nim b/src/config/config.nim index 063c32fc..35ca1a17 100644 --- a/src/config/config.nim +++ b/src/config/config.nim @@ -4,26 +4,27 @@ import std/streams import std/strutils import std/tables -import bindings/quickjs import config/chapath import config/mailcap import config/mimetypes import config/toml -import js/jserror -import js/fromjs -import js/javascript import js/jscolor -import js/jstypes -import js/jspropenumlist -import js/jsregex -import js/tojs import loader/headers +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import monoucha/jspropenumlist +import monoucha/jsregex +import monoucha/jstypes +import monoucha/quickjs +import monoucha/tojs import types/cell import types/color import types/cookie import types/opt import types/urimethodmap import types/url +import utils/regexutils import utils/twtstr import chagashi/charset diff --git a/src/html/chadombuilder.nim b/src/html/chadombuilder.nim index 3c5b2c12..708b127c 100644 --- a/src/html/chadombuilder.nim +++ b/src/html/chadombuilder.nim @@ -1,19 +1,19 @@ import std/deques import std/options +import std/tables +import chagashi/charset +import chame/htmlparser +import chame/tags import html/catom import html/dom import html/enums -import js/jserror -import js/fromjs -import js/javascript +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import types/opt import types/url -import chagashi/charset - -import chame/htmlparser -import chame/tags - export htmlparser.ParseResult # DOMBuilder implementation for Chawan. @@ -260,6 +260,9 @@ proc parseHTMLFragment*(element: Element; s: string): seq[Node] = builder.finish() return root.childList +# Forward declaration hack +domParseHTMLFragment = parseHTMLFragment + proc newHTML5ParserWrapper*(window: Window; url: URL; factory: CAtomFactory; confidence: CharsetConfidence; charset: Charset): HTML5ParserWrapper = let opts = HTML5ParserOpts[Node, CAtom](scripting: window.settings.scripting) diff --git a/src/html/dom.nim b/src/html/dom.nim index af839cea..6eb1079f 100644 --- a/src/html/dom.nim +++ b/src/html/dom.nim @@ -22,19 +22,20 @@ import io/dynstream import io/promise import js/console import js/domexception -import js/fromjs -import js/javascript -import js/jserror -import js/jsopaque -import js/jspropenumlist -import js/jsutils import js/timeout -import js/tojs import loader/loader import loader/request +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import monoucha/jsopaque +import monoucha/jspropenumlist +import monoucha/jsutils +import monoucha/tojs import types/blob import types/color import types/matrix +import types/opt import types/referrer import types/url import types/vector @@ -4189,11 +4190,14 @@ proc toBlob(ctx: JSContext; this: HTMLCanvasElement; callback: JSValue; JS_FreeValue(ctx, res) return JS_UNDEFINED -import html/chadombuilder +# Forward declaration hack +var domParseHTMLFragment*: proc(element: Element; s: string): seq[Node] + {.nimcall.} + # https://w3c.github.io/DOM-Parsing/#dfn-fragment-parsing-algorithm proc fragmentParsingAlgorithm*(element: Element; s: string): DocumentFragment = #TODO xml - let newChildren = parseHTMLFragment(element, s) + let newChildren = domParseHTMLFragment(element, s) let fragment = element.document.newDocumentFragment() for child in newChildren: fragment.append(child) diff --git a/src/html/env.nim b/src/html/env.nim index 76aa2007..fadcd52c 100644 --- a/src/html/env.nim +++ b/src/html/env.nim @@ -1,6 +1,5 @@ import std/selectors -import bindings/quickjs import html/catom import html/chadombuilder import html/dom @@ -14,17 +13,19 @@ import js/base64 import js/console import js/domexception import js/encoding -import js/jserror import js/intl -import js/javascript -import js/jstypes import js/timeout -import js/tojs import loader/headers import loader/loader import loader/request import loader/response +import monoucha/javascript +import monoucha/jserror +import monoucha/jstypes +import monoucha/quickjs +import monoucha/tojs import types/blob +import types/opt import types/url import types/winattrs diff --git a/src/html/event.nim b/src/html/event.nim index 7ae024db..fd818048 100644 --- a/src/html/event.nim +++ b/src/html/event.nim @@ -2,13 +2,13 @@ import std/math import std/options import std/times -import bindings/quickjs -import js/jserror -import js/fromjs -import js/javascript -import js/jstypes -import js/jsutils -import js/tojs +import monoucha/quickjs +import monoucha/jserror +import monoucha/fromjs +import monoucha/javascript +import monoucha/jstypes +import monoucha/jsutils +import monoucha/tojs import types/opt type diff --git a/src/html/formdata.nim b/src/html/formdata.nim index 7f099acd..df839d3f 100644 --- a/src/html/formdata.nim +++ b/src/html/formdata.nim @@ -1,3 +1,4 @@ +import chame/tags import html/catom import html/dom import html/enums @@ -5,14 +6,13 @@ import io/dynstream import io/posixstream import js/base64 import js/domexception -import js/javascript -import js/tojs +import monoucha/javascript +import monoucha/tojs import types/blob import types/formdata +import types/opt import utils/twtstr -import chame/tags - proc constructEntryList*(form: HTMLFormElement; submitter: Element = nil; encoding = "UTF-8"): seq[FormDataEntry] diff --git a/src/html/script.nim b/src/html/script.nim index c0962b13..59ada2f0 100644 --- a/src/html/script.nim +++ b/src/html/script.nim @@ -1,4 +1,4 @@ -import js/javascript +import monoucha/javascript import types/referrer import types/url diff --git a/src/html/xmlhttprequest.nim b/src/html/xmlhttprequest.nim index 33f1e61c..92065ab4 100644 --- a/src/html/xmlhttprequest.nim +++ b/src/html/xmlhttprequest.nim @@ -1,15 +1,16 @@ import std/options import std/strutils -import bindings/quickjs import html/dom import html/event import js/domexception -import js/fromjs -import js/javascript import loader/headers import loader/request import loader/response +import monoucha/fromjs +import monoucha/javascript +import monoucha/quickjs +import types/opt import types/url type diff --git a/src/io/promise.nim b/src/io/promise.nim index 6e57791b..183091cb 100644 --- a/src/io/promise.nim +++ b/src/io/promise.nim @@ -1,11 +1,11 @@ import std/tables -import bindings/quickjs -import js/jserror -import js/javascript -import js/jsutils -import js/jsopaque -import js/tojs +import monoucha/quickjs +import monoucha/jserror +import monoucha/javascript +import monoucha/jsutils +import monoucha/jsopaque +import monoucha/tojs import types/opt type diff --git a/src/io/urlfilter.nim b/src/io/urlfilter.nim index 4d1d2404..e86e0e6b 100644 --- a/src/io/urlfilter.nim +++ b/src/io/urlfilter.nim @@ -1,6 +1,6 @@ import std/options -import js/jsregex +import monoucha/jsregex import types/url #TODO add denyhost/s for blocklists diff --git a/src/js/base64.nim b/src/js/base64.nim index a1693aa3..85844e09 100644 --- a/src/js/base64.nim +++ b/src/js/base64.nim @@ -1,7 +1,7 @@ -import bindings/quickjs +import monoucha/quickjs import js/domexception -import js/javascript -import js/jstypes +import monoucha/javascript +import monoucha/jstypes import types/opt import utils/twtstr diff --git a/src/js/console.nim b/src/js/console.nim index 712f228c..0de66162 100644 --- a/src/js/console.nim +++ b/src/js/console.nim @@ -1,6 +1,6 @@ import io/dynstream -import js/jserror -import js/javascript +import monoucha/jserror +import monoucha/javascript type Console* = ref object err*: DynStream diff --git a/src/js/domexception.nim b/src/js/domexception.nim index 902cd618..68e313e9 100644 --- a/src/js/domexception.nim +++ b/src/js/domexception.nim @@ -1,8 +1,9 @@ import std/tables -import bindings/quickjs -import js/jserror -import js/javascript +import monoucha/javascript +import monoucha/jserror +import monoucha/quickjs +import types/opt const NamesTable = { "IndexSizeError": 1u16, diff --git a/src/js/encoding.nim b/src/js/encoding.nim index edf0cf97..ccadce16 100644 --- a/src/js/encoding.nim +++ b/src/js/encoding.nim @@ -1,13 +1,13 @@ -import bindings/quickjs -import js/jserror -import js/javascript -import js/jstypes - import chagashi/charset import chagashi/decoder import chagashi/decodercore import chagashi/validator import chagashi/validatorcore +import monoucha/javascript +import monoucha/jserror +import monoucha/jstypes +import monoucha/quickjs +import types/opt type JSTextEncoder = ref object diff --git a/src/js/fromjs.nim b/src/js/fromjs.nim deleted file mode 100644 index 055ae19e..00000000 --- a/src/js/fromjs.nim +++ /dev/null @@ -1,445 +0,0 @@ -import std/macros -import std/options -import std/tables -import std/unicode - -import bindings/quickjs -import js/jserror -import js/jstypes -import js/jsopaque -import types/opt -import utils/twtstr - -proc fromJS*[T](ctx: JSContext; val: JSValue): JSResult[T] - -func isInstanceOfNonGlobal(ctx: JSContext; val: JSValue; class: string): bool = - let ctxOpaque = ctx.getOpaque() - var classid = JS_GetClassID(val) - let tclassid = ctxOpaque.creg[class] - var found = false - while true: - if classid == tclassid: - found = true - break - ctxOpaque.parents.withValue(classid, val): - classid = val[] - do: - classid = 0 # not defined by Chawan; assume parent is Object. - if classid == 0: - break - return found - -func isInstanceOfGlobal(ctx: JSContext; val: JSValue; class: string): bool = - let ctxOpaque = ctx.getOpaque() - #TODO gparent only works for a single level. (But this is not really a - # problem right now, because our global objects have at most one inheritance - # level.) - if ctx.isGlobal(class) or ctxOpaque.creg[class] == ctxOpaque.gparent: - # undefined -> global - if JS_IsUndefined(val): - return true - if JS_IsObject(val): - let global = JS_GetGlobalObject(ctx) - let p0 = JS_VALUE_GET_PTR(global) - let p1 = JS_VALUE_GET_PTR(val) - JS_FreeValue(ctx, global) - if p0 == p1: - return true - return false - -func isInstanceOf*(ctx: JSContext; val: JSValue; class: string): bool = - return ctx.isInstanceOfGlobal(val, class) or - ctx.isInstanceOfNonGlobal(val, class) - -func toString(ctx: JSContext; val: JSValue): Opt[string] = - var plen: csize_t - let outp = JS_ToCStringLen(ctx, addr plen, val) # cstring - if outp != nil: - var ret = newString(plen) - if plen != 0: - prepareMutation(ret) - copyMem(addr ret[0], outp, plen) - result = ok(ret) - JS_FreeCString(ctx, outp) - -func fromJSString(ctx: JSContext; val: JSValue): JSResult[string] = - var plen: csize_t - let outp = JS_ToCStringLen(ctx, addr plen, val) # cstring - if outp == nil: - return err() - var ret = newString(plen) - if plen != 0: - prepareMutation(ret) - copyMem(addr ret[0], outp, plen) - JS_FreeCString(ctx, outp) - return ok(ret) - -func fromJSInt[T: SomeInteger](ctx: JSContext; val: JSValue): - JSResult[T] = - when T is int: - # Always int32, so we don't risk 32-bit only breakage. - # If int64 is needed, specify it explicitly. - var ret: int32 - if JS_ToInt32(ctx, addr ret, val) < 0: - return err() - return ok(int(ret)) - elif T is int32: - var ret: int32 - if JS_ToInt32(ctx, addr ret, val) < 0: - return err() - return ok(ret) - elif T is int64: - var ret: int64 - if JS_ToInt64(ctx, addr ret, val) < 0: - return err() - return ok(ret) - elif T is uint32: - var ret: uint32 - if JS_ToUint32(ctx, addr ret, val) < 0: - return err() - return ok(ret) - else: - static: - error($T & " cannot be converted to JS automatically") - -proc fromJSFloat64(ctx: JSContext; val: JSValue): JSResult[float64] = - var f64: float64 - if JS_ToFloat64(ctx, addr f64, val) < 0: - return err() - return ok(f64) - -macro fromJSTupleBody(a: tuple) = - let len = a.getType().len - 1 - let done = ident("done") - result = newStmtList(quote do: - var `done`: bool) - for i in 0..<len: - result.add(quote do: - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) - if JS_IsException(doneVal): - return err() - defer: JS_FreeValue(ctx, doneVal) - `done` = ?fromJS[bool](ctx, doneVal) - if `done`: - return errTypeError("Too few arguments in sequence (got " & $`i` & - ", expected " & $`len` & ")") - let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) - if JS_IsException(valueVal): - return err() - defer: JS_FreeValue(ctx, valueVal) - `a`[`i`] = ?fromJS[typeof(`a`[`i`])](ctx, valueVal) - ) - if i == len - 1: - result.add(quote do: - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) - `done` = ?fromJS[bool](ctx, doneVal) - var i = `i` - # we're emulating a sequence, so we must query all remaining parameters - # too: - while not `done`: - inc i - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, - ctx.getOpaque().strRefs[jstDone]) - if JS_IsException(doneVal): - return err() - defer: JS_FreeValue(ctx, doneVal) - `done` = ?fromJS[bool](ctx, doneVal) - if `done`: - let msg = "Too many arguments in sequence (got " & $i & - ", expected " & $`len` & ")" - return err(newTypeError(msg)) - JS_FreeValue(ctx, JS_GetProperty(ctx, next, - ctx.getOpaque().strRefs[jstValue])) - ) - -proc fromJSTuple[T: tuple](ctx: JSContext; val: JSValue): JSResult[T] = - let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) - if JS_IsException(itprop): - return err() - defer: JS_FreeValue(ctx, itprop) - let it = JS_Call(ctx, itprop, val, 0, nil) - if JS_IsException(it): - return err() - defer: JS_FreeValue(ctx, it) - let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) - if JS_IsException(nextMethod): - return err() - defer: JS_FreeValue(ctx, nextMethod) - var x: T - fromJSTupleBody(x) - return ok(x) - -proc fromJSSeq[T](ctx: JSContext; val: JSValue): JSResult[seq[T]] = - let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) - if JS_IsException(itprop): - return err() - defer: JS_FreeValue(ctx, itprop) - let it = JS_Call(ctx, itprop, val, 0, nil) - if JS_IsException(it): - return err() - defer: JS_FreeValue(ctx, it) - let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) - if JS_IsException(nextMethod): - return err() - defer: JS_FreeValue(ctx, nextMethod) - var s = newSeq[T]() - while true: - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) - if JS_IsException(doneVal): - return err() - defer: JS_FreeValue(ctx, doneVal) - let done = ?fromJS[bool](ctx, doneVal) - if done: - break - let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) - if JS_IsException(valueVal): - return err() - defer: JS_FreeValue(ctx, valueVal) - let genericRes = ?fromJS[typeof(s[0])](ctx, valueVal) - s.add(genericRes) - return ok(s) - -proc fromJSSet[T](ctx: JSContext; val: JSValue): JSResult[set[T]] = - let itprop = JS_GetProperty(ctx, val, ctx.getOpaque().symRefs[jsyIterator]) - if JS_IsException(itprop): - return err() - defer: JS_FreeValue(ctx, itprop) - let it = JS_Call(ctx, itprop, val, 0, nil) - if JS_IsException(it): - return err() - defer: JS_FreeValue(ctx, it) - let nextMethod = JS_GetProperty(ctx, it, ctx.getOpaque().strRefs[jstNext]) - if JS_IsException(nextMethod): - return err() - defer: JS_FreeValue(ctx, nextMethod) - var s: set[T] - while true: - let next = JS_Call(ctx, nextMethod, it, 0, nil) - if JS_IsException(next): - return err() - defer: JS_FreeValue(ctx, next) - let doneVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstDone]) - if JS_IsException(doneVal): - return err() - defer: JS_FreeValue(ctx, doneVal) - let done = ?fromJS[bool](ctx, doneVal) - if done: - break - let valueVal = JS_GetProperty(ctx, next, ctx.getOpaque().strRefs[jstValue]) - if JS_IsException(valueVal): - return err() - defer: JS_FreeValue(ctx, valueVal) - let genericRes = ?fromJS[T](ctx, valueVal) - s.incl(genericRes) - return ok(s) - -proc fromJSTable[A, B](ctx: JSContext; val: JSValue): JSResult[Table[A, B]] = - if not JS_IsObject(val): - return err(newTypeError("object expected")) - var ptab: ptr UncheckedArray[JSPropertyEnum] - var plen: uint32 - let flags = cint(JS_GPN_STRING_MASK) - if JS_GetOwnPropertyNames(ctx, addr ptab, addr plen, val, flags) == -1: - # exception - return err() - defer: - for i in 0 ..< plen: - JS_FreeAtom(ctx, ptab[i].atom) - js_free(ctx, ptab) - var res = Table[A, B]() - for i in 0 ..< plen: - let atom = ptab[i].atom - let k = JS_AtomToValue(ctx, atom) - defer: JS_FreeValue(ctx, k) - let kn = ?fromJS[A](ctx, k) - let v = JS_GetProperty(ctx, val, atom) - defer: JS_FreeValue(ctx, v) - let vn = ?fromJS[B](ctx, v) - res[kn] = vn - return ok(res) - -template optionType[T](o: type Option[T]): auto = - T - -# Option vs Opt: -# Option is for nullable types, e.g. if you want to return either a string -# or null. (This is rather pointless for anything else.) -# Opt is for passing down exceptions received up in the chain. -# So e.g. none(T) translates to JS_NULL, but err() translates to JS_EXCEPTION. -proc fromJSOption[T](ctx: JSContext; val: JSValue): JSResult[Option[T]] = - if JS_IsNull(val): - return ok(none(T)) - let res = ?fromJS[T](ctx, val) - return ok(option(res)) - -proc fromJSBool(ctx: JSContext; val: JSValue): JSResult[bool] = - let ret = JS_ToBool(ctx, val) - if ret == -1: # exception - return err() - if ret == 0: - return ok(false) - return ok(true) - -proc fromJSEnum[T: enum](ctx: JSContext; val: JSValue): JSResult[T] = - if JS_IsException(val): - return err() - let s = ?toString(ctx, val) - let r = strictParseEnum[T](s) - if r.isSome: - return ok(r.get) - return errTypeError("`" & s & "' is not a valid value for enumeration " & $T) - -proc fromJSPObj0(ctx: JSContext; val: JSValue; t: string): - JSResult[pointer] = - if JS_IsException(val): - return err(nil) - if JS_IsNull(val): - return ok(nil) - if ctx.isInstanceOfGlobal(val, t): - return ok(?getGlobalOpaque0(ctx, val)) - if not JS_IsObject(val): - return err(newTypeError("Value is not an object")) - if not isInstanceOfNonGlobal(ctx, val, t): - return errTypeError(t & " expected") - let classid = JS_GetClassID(val) - let op = JS_GetOpaque(val, classid) - return ok(op) - -proc fromJSObject[T: ref object](ctx: JSContext; val: JSValue): JSResult[T] = - return ok(cast[T](?fromJSPObj0(ctx, val, $T))) - -proc fromJSVoid(ctx: JSContext; val: JSValue): JSResult[void] = - if JS_IsException(val): - return err() - return ok() - -proc fromJSDict[T: JSDict](ctx: JSContext; val: JSValue): JSResult[T] = - if not JS_IsUndefined(val) and not JS_IsNull(val) and not JS_IsObject(val): - return err(newTypeError("Dictionary is not an object")) - #TODO throw on missing required values - var d = T() - if JS_IsObject(val): - for k, v in d.fieldPairs: - let esm = JS_GetPropertyStr(ctx, val, k) - if not JS_IsUndefined(esm): - v = ?fromJS[typeof(v)](ctx, esm) - when v isnot JSValue: - JS_FreeValue(ctx, esm) - return ok(d) - -proc fromJSArrayBuffer(ctx: JSContext; val: JSValue): JSResult[JSArrayBuffer] = - var len: csize_t - let p = JS_GetArrayBuffer(ctx, addr len, val) - if p == nil: - return err() - let abuf = JSArrayBuffer( - len: len, - p: cast[ptr UncheckedArray[uint8]](p) - ) - return ok(abuf) - -proc fromJSArrayBufferView(ctx: JSContext; val: JSValue): - JSResult[JSArrayBufferView] = - var offset: csize_t - var nmemb: csize_t - var nsize: csize_t - let jsbuf = JS_GetTypedArrayBuffer(ctx, val, addr offset, addr nmemb, - addr nsize) - let abuf = ?fromJSArrayBuffer(ctx, jsbuf) - let view = JSArrayBufferView( - abuf: abuf, - offset: offset, - nmemb: nmemb, - nsize: nsize - ) - return ok(view) - -type FromJSAllowedT = (object and not (Result|Option|Table|JSValue|JSDict| - JSArrayBuffer|JSArrayBufferView|JSUint8Array)) - -macro fromJS2(ctx: JSContext; val: JSValue; x: static string): untyped = - let id = ident("fromJS" & x) - return quote do: - `id`(`ctx`, `val`) - -proc fromJS*[T](ctx: JSContext; val: JSValue): JSResult[T] = - when T is string: - return fromJSString(ctx, val) - elif T is Option: - return fromJSOption[optionType(T)](ctx, val) - elif T is seq: - return fromJSSeq[typeof(result.get.items)](ctx, val) - elif T is set: - return fromJSSet[typeof(result.get.items)](ctx, val) - elif T is tuple: - return fromJSTuple[T](ctx, val) - elif T is bool: - return fromJSBool(ctx, val) - elif typeof(result).valType is Table: - return fromJSTable[typeof(result.get.keys), - typeof(result.get.values)](ctx, val) - elif T is SomeInteger: - return fromJSInt[T](ctx, val) - elif T is float64: - return fromJSFloat64(ctx, val) - elif T is enum: - return fromJSEnum[T](ctx, val) - elif T is JSValue: - return ok(val) - elif T is ref object: - return fromJSObject[T](ctx, val) - elif T is void: - return fromJSVoid(ctx, val) - elif T is JSDict: - return fromJSDict[T](ctx, val) - elif T is JSArrayBuffer: - return fromJSArrayBuffer(ctx, val) - elif T is JSArrayBufferView: - return fromJSArrayBufferView(ctx, val) - else: - return fromJS2(ctx, val, $T) - -const JS_ATOM_TAG_INT = 1u32 shl 31 - -func JS_IsNumber*(v: JSAtom): JS_BOOL = - return (uint32(v) and JS_ATOM_TAG_INT) != 0 - -func fromJS*[T: string|uint32|JSAtom](ctx: JSContext; atom: JSAtom): Opt[T] = - when T is JSAtom: - return ok(atom) - elif T is SomeNumber: - if JS_IsNumber(atom): - return ok(uint32(atom) and (not JS_ATOM_TAG_INT)) - return err() - else: - let cs = JS_AtomToCString(ctx, atom) - if cs == nil: - return err() - let s = $cs - JS_FreeCString(ctx, cs) - return ok(s) - -proc fromJSPObj[T](ctx: JSContext; val: JSValue): JSResult[ptr T] = - return cast[JSResult[ptr T]](fromJSPObj0(ctx, val, $T)) - -template fromJSP*[T](ctx: JSContext; val: JSValue): untyped = - when T is FromJSAllowedT: - fromJSPObj[T](ctx, val) - else: - fromJS[T](ctx, val) diff --git a/src/js/intl.nim b/src/js/intl.nim index 8be5a768..4ae264e4 100644 --- a/src/js/intl.nim +++ b/src/js/intl.nim @@ -1,9 +1,9 @@ # Very minimal Intl module... TODO make it more complete -import bindings/quickjs -import js/javascript -import js/jstypes -import js/tojs +import monoucha/quickjs +import monoucha/javascript +import monoucha/jstypes +import monoucha/tojs type NumberFormat = ref object diff --git a/src/js/javascript.nim b/src/js/javascript.nim deleted file mode 100644 index cdf6b127..00000000 --- a/src/js/javascript.nim +++ /dev/null @@ -1,1719 +0,0 @@ -# JavaScript binding generator. Horrifying, I know. But it works! -# Warning: Function overloading is currently not implemented. Though there is a -# block dielabel: -# ... -# around each bound function call, so it shouldn't be too difficult to get it -# working. (This would involve generating JS functions in registerType.) -# Now for the pragmas: -# {.jsctor.} for constructors. These need no `this' value, and are bound as -# regular constructors in JS. They must return a ref object, which will have -# a JS counterpart too. (Other functions can return ref objects too, which -# will either use the existing JS counterpart, if exists, or create a new -# one. In other words: cross-language reference semantics work seamlessly.) -# {.jsfunc.} is used for binding normal functions. Needs a `this' value, as all -# following pragmas. As mentioned before, overloading doesn't work but OR -# generics do. By default, the Nim function name is bound; if this is not -# desired, you can rename the function like this: {.jsfunc: "preferredName".} -# This also works for all other pragmas that define named functions in JS. -# {.jsstfunc.} binds static functions. Unlike .jsfunc, it does not have a -# `this' value. A class name must be specified, e.g. {.jsstfunc: "URL".} to -# define on the URL class. To rename a static function, use the syntax -# "ClassName:funcName", e.g. "Response:error". -# {.jsget.}, {.jsfget.} must be specified on object fields; these generate -# regular getter & setter functions. -# {.jsufget, jsuffget, jsuffunc.} For fields with the [LegacyUnforgeable] -# WebIDL property. -# This makes it so a non-configurable/writable, but enumerable property -# is defined on the object when the *constructor* is called (i.e. NOT on -# the prototype.) -# {.jsfget.} and {.jsfset.} for getters/setters. Note the `f'; bare jsget/jsset -# can only be used on object fields. (I initially wanted to use the same -# keyword, unfortunately that didn't work out.) -# {.jsgetprop.} for property getters. Called when GetOwnProperty would return -# nothing. The key must be either a JSAtom, uint32 or string. (Note that the -# string option copies.) -# {.jssetprop.} for property setters. Called on SetProperty - in fact this -# is the set() method of Proxy, except it always returns true. Same rules as -# jsgetprop for keys. -# {.jsdelprop.} for property deletion. It is like the deleteProperty method -# of Proxy. Must return true if deleted, false if not deleted. -# {.jshasprop.} for overriding has_property. Must return a boolean. -# {.jspropnames.} overrides get_own_property_names. Must return a -# JSPropertyEnumList object. - -import std/macros -import std/options -import std/sets -import std/strutils -import std/tables -import std/unicode - -import js/jserror -import js/fromjs -import js/jsopaque -import js/tojs -import types/opt - -import bindings/quickjs - -export opt -export options -export tables - -export - JS_NULL, JS_UNDEFINED, JS_FALSE, JS_TRUE, JS_EXCEPTION, JS_UNINITIALIZED - -export - JS_EVAL_TYPE_GLOBAL, - JS_EVAL_TYPE_MODULE, - JS_EVAL_TYPE_DIRECT, - JS_EVAL_TYPE_INDIRECT, - JS_EVAL_TYPE_MASK, - JS_EVAL_FLAG_SHEBANG, - JS_EVAL_FLAG_STRICT, - JS_EVAL_FLAG_STRIP, - JS_EVAL_FLAG_COMPILE_ONLY - -export JSRuntime, JSContext, JSValue, JSClassID, JSAtom - -export - JS_GetGlobalObject, JS_FreeValue, JS_IsException, JS_GetPropertyStr, - JS_IsFunction, JS_NewCFunctionData, JS_Call, JS_DupValue, JS_IsUndefined - -when sizeof(int) < sizeof(int64): - export quickjs.`==` - -type - JSFunctionList = openArray[JSCFunctionListEntry] - - BoundFunction = object - t: BoundFunctionType - name: string - id: NimNode - magic: uint16 - unforgeable: bool - isstatic: bool - ctorBody: NimNode - - BoundFunctionType = enum - bfFunction = "js_func" - bfConstructor = "js_ctor" - bfGetter = "js_get" - bfSetter = "js_set" - bfPropertyGet = "js_prop_get" - bfPropertySet = "js_prop_set" - bfPropertyDel = "js_prop_del" - bfPropertyHas = "js_prop_has" - bfPropertyNames = "js_prop_names" - bfFinalizer = "js_fin" - -var runtimes {.threadvar.}: seq[JSRuntime] - -proc bindMalloc(s: ptr JSMallocState; size: csize_t): pointer {.cdecl.} = - return alloc(size) - -proc bindFree(s: ptr JSMallocState; p: pointer) {.cdecl.} = - if p == nil: - return - dealloc(p) - -proc bindRealloc(s: ptr JSMallocState; p: pointer; size: csize_t): pointer - {.cdecl.} = - return realloc(p, size) - -proc jsRuntimeCleanUp(rt: JSRuntime) {.cdecl.} = - let rtOpaque = rt.getOpaque() - GC_unref(rtOpaque) - assert rtOpaque.destroying == nil - var ps: seq[pointer] = @[] - for p in rtOpaque.plist.values: - ps.add(p) - rtOpaque.plist.clear() - var unrefs: seq[proc() {.closure.}] = @[] - for (_, unref) in rtOpaque.refmap.values: - unrefs.add(unref) - rtOpaque.refmap.clear() - for unref in unrefs: - unref() - for p in ps: - #TODO maybe finalize? - let val = JS_MKPTR(JS_TAG_OBJECT, p) - JS_SetOpaque(val, nil) - JS_FreeValueRT(rt, val) - JS_RunGC(rt) - -proc newJSRuntime*(): JSRuntime = - var mf {.global.} = JSMallocFunctions( - js_malloc: bindMalloc, - js_free: bindFree, - js_realloc: bindRealloc, - js_malloc_usable_size: nil - ) - let rt = JS_NewRuntime2(addr mf, nil) - let opaque = JSRuntimeOpaque() - GC_ref(opaque) - JS_SetRuntimeOpaque(rt, cast[pointer](opaque)) - JS_SetRuntimeCleanUpFunc(rt, jsRuntimeCleanUp) - # Must be added after opaque is set, or there is a chance of - # nim_finalize_for_js dereferencing it (at the new call). - runtimes.add(rt) - return rt - -proc newJSContext*(rt: JSRuntime): JSContext = - let ctx = JS_NewContext(rt) - let opaque = newJSContextOpaque(ctx) - GC_ref(opaque) - JS_SetContextOpaque(ctx, cast[pointer](opaque)) - return ctx - -func getClass*(ctx: JSContext; class: string): JSClassID = - # This function *should* never fail. - ctx.getOpaque().creg[class] - -func hasClass*(ctx: JSContext; class: type): bool = - return $class in ctx.getOpaque().creg - -proc free*(ctx: JSContext) = - var opaque = ctx.getOpaque() - if opaque != nil: - for a in opaque.symRefs: - JS_FreeAtom(ctx, a) - for a in opaque.strRefs: - JS_FreeAtom(ctx, a) - for v in opaque.valRefs: - JS_FreeValue(ctx, v) - for classid, v in opaque.ctors: - JS_FreeValue(ctx, v) - for v in opaque.errCtorRefs: - JS_FreeValue(ctx, v) - if opaque.globalUnref != nil: - opaque.globalUnref() - GC_unref(opaque) - JS_FreeContext(ctx) - -proc free*(rt: JSRuntime) = - JS_FreeRuntime(rt) - runtimes.del(runtimes.find(rt)) - -proc setGlobal*[T](ctx: JSContext; obj: T) = - # Add JSValue reference. - let global = JS_GetGlobalObject(ctx) - let opaque = cast[pointer](obj) - ctx.setOpaque(global, opaque) - GC_ref(obj) - let rtOpaque = JS_GetRuntime(ctx).getOpaque() - ctx.getOpaque().globalUnref = proc() = - GC_unref(obj) - rtOpaque.plist.del(opaque) - JS_FreeValue(ctx, global) - -proc setInterruptHandler*(rt: JSRuntime; cb: JSInterruptHandler; - opaque: pointer = nil) = - JS_SetInterruptHandler(rt, cb, opaque) - -proc getExceptionMsg*(ctx: JSContext): string = - result = "" - let ex = JS_GetException(ctx) - let str = fromJS[string](ctx, ex) - if str.isSome: - result &= str.get & '\n' - let stack = JS_GetPropertyStr(ctx, ex, cstring("stack")); - if not JS_IsUndefined(stack): - result &= fromJS[string](ctx, stack).get("") - JS_FreeValue(ctx, stack) - JS_FreeValue(ctx, ex) - -proc getExceptionMsg*(ctx: JSContext; err: JSError): string = - if err != nil: - JS_FreeValue(ctx, ctx.toJS(err)) # note: this implicitly throws - return ctx.getExceptionMsg() - -# Returns early with err(JSContext) if an exception was thrown in a -# context. -proc runJSJobs*(rt: JSRuntime): Err[JSContext] = - while JS_IsJobPending(rt): - var ctx: JSContext - let r = JS_ExecutePendingJob(rt, addr ctx) - if r == -1: - return err(ctx) - ok() - -# Add all LegacyUnforgeable functions defined on the prototype chain to -# the opaque. -# Since every prototype has a list of all its ancestor's LegacyUnforgeable -# functions, it is sufficient to simply merge the new list of new classes -# with their parent's list to achieve this. -proc addClassUnforgeable(ctx: JSContext; proto: JSValue; - classid, parent: JSClassID; ourUnforgeable: JSFunctionList) = - let ctxOpaque = ctx.getOpaque() - var merged = @ourUnforgeable - ctxOpaque.unforgeable.withValue(parent, uf): - merged.add(uf[]) - if merged.len > 0: - ctxOpaque.unforgeable[classid] = merged - let ufp = addr ctxOpaque.unforgeable[classid][0] - JS_SetPropertyFunctionList(ctx, proto, ufp, cint(merged.len)) - -func newJSClass*(ctx: JSContext; cdef: JSClassDefConst; tname: string; - nimt: pointer; ctor: JSCFunction; funcs: JSFunctionList; parent: JSClassID; - asglobal: bool; nointerface: bool; finalizer: JSFinalizerFunction; - namespace: JSValue; errid: Opt[JSErrorEnum]; - unforgeable, staticfuns: JSFunctionList; - ishtmldda: bool): JSClassID {.discardable.} = - let rt = JS_GetRuntime(ctx) - discard JS_NewClassID(addr result) - var ctxOpaque = ctx.getOpaque() - var rtOpaque = rt.getOpaque() - if JS_NewClass(rt, result, cdef) != 0: - raise newException(Defect, "Failed to allocate JS class: " & - $cdef.class_name) - ctxOpaque.typemap[nimt] = result - ctxOpaque.creg[tname] = result - ctxOpaque.parents[result] = parent - if ishtmldda: - ctxOpaque.htmldda = result - if finalizer != nil: - rtOpaque.fins[result] = finalizer - let proto = if parent != 0: - let parentProto = JS_GetClassProto(ctx, parent) - let x = JS_NewObjectProtoClass(ctx, parentProto, parent) - JS_FreeValue(ctx, parentProto) - x - else: - JS_NewObject(ctx) - if funcs.len > 0: - # We avoid funcs being GC'ed by putting the list in rtOpaque. - # (QuickJS uses the pointer later.) - #TODO maybe put them in ctxOpaque instead? - rtOpaque.flist.add(@funcs) - JS_SetPropertyFunctionList(ctx, proto, addr rtOpaque.flist[^1][0], - cint(funcs.len)) - #TODO check if this is an indexed property getter - if cdef.exotic != nil and cdef.exotic.get_own_property != nil: - let val = JS_DupValue(ctx, ctxOpaque.valRefs[jsvArrayPrototypeValues]) - let itSym = ctxOpaque.symRefs[jsyIterator] - doAssert JS_SetProperty(ctx, proto, itSym, val) == 1 - let news = JS_NewAtomString(ctx, cdef.class_name) - doAssert not JS_IsException(news) - ctx.definePropertyC(proto, ctxOpaque.symRefs[jsyToStringTag], - JS_DupValue(ctx, news)) - JS_SetClassProto(ctx, result, proto) - ctx.addClassUnforgeable(proto, result, parent, unforgeable) - if asglobal: - let global = JS_GetGlobalObject(ctx) - assert ctxOpaque.gclaz == "" - ctxOpaque.gclaz = tname - ctxOpaque.gparent = parent - ctx.definePropertyC(global, ctxOpaque.symRefs[jsyToStringTag], - JS_DupValue(ctx, news)) - if JS_SetPrototype(ctx, global, proto) != 1: - raise newException(Defect, "Failed to set global prototype: " & - $cdef.class_name) - # Global already exists, so set unforgeable functions here - ctxOpaque.unforgeable.withValue(result, uf): - JS_SetPropertyFunctionList(ctx, global, addr uf[][0], cint(uf[].len)) - JS_FreeValue(ctx, global) - JS_FreeValue(ctx, news) - let jctor = JS_NewCFunction2(ctx, ctor, cstring($cdef.class_name), 0, - JS_CFUNC_constructor, 0) - if staticfuns.len > 0: - rtOpaque.flist.add(@staticfuns) - JS_SetPropertyFunctionList(ctx, jctor, addr rtOpaque.flist[^1][0], - cint(staticfuns.len)) - JS_SetConstructor(ctx, jctor, proto) - if errid.isSome: - ctx.getOpaque().errCtorRefs[errid.get] = JS_DupValue(ctx, jctor) - ctxOpaque.ctors[result] = JS_DupValue(ctx, jctor) - if not nointerface: - if JS_IsNull(namespace): - let global = JS_GetGlobalObject(ctx) - ctx.definePropertyCW(global, $cdef.class_name, jctor) - JS_FreeValue(ctx, global) - else: - ctx.definePropertyCW(namespace, $cdef.class_name, jctor) - else: - JS_FreeValue(ctx, jctor) - -type FuncParam = tuple - name: string - t: NimNode - val: Option[NimNode] - generic: Option[NimNode] - isptr: bool - -func getMinArgs(params: seq[FuncParam]): int = - for i in 0..<params.len: - let it = params[i] - if it[2].isSome: - return i - let t = it.t - if t.kind == nnkBracketExpr: - if t.typeKind == varargs.getType().typeKind: - assert i == params.high, "Not even nim can properly handle this..." - return i - return params.len - -func fromJSP[T: string|uint32](ctx: JSContext; atom: JSAtom): Opt[T] = - return fromJS[T](ctx, atom) - -proc defineConsts*[T](ctx: JSContext; classid: JSClassID; - consts: static openArray[(string, T)]) = - let proto = ctx.getOpaque().ctors[classid] - for (k, v) in consts: - ctx.definePropertyE(proto, k, v) - -proc defineConsts*(ctx: JSContext; classid: JSClassID; - consts: typedesc[enum]; astype: typedesc) = - let proto = ctx.getOpaque().ctors[classid] - for e in consts: - ctx.definePropertyE(proto, $e, astype(e)) - -type - JSFuncGenerator = ref object - t: BoundFunctionType - thisname: Option[string] - funcName: string - generics: Table[string, seq[NimNode]] - funcParams: seq[FuncParam] - passCtx: bool - thisType: string - thisTypeNode: NimNode - returnType: Option[NimNode] - newName: NimNode - newBranchList: seq[NimNode] - errval: NimNode # JS_EXCEPTION or -1 - # die: didn't match parameters, but could still match other ones - dielabel: NimNode - jsFunCallLists: seq[NimNode] - jsFunCallList: NimNode - jsFunCall: NimNode - jsCallAndRet: NimNode - minArgs: int - actualMinArgs: int # minArgs without JSContext - i: int # nim parameters accounted for - j: int # js parameters accounted for (not including fix ones, e.g. `this') - unforgeable: bool - isstatic: bool - -var BoundFunctions {.compileTime.}: Table[string, seq[BoundFunction]] - -proc getGenerics(fun: NimNode): Table[string, seq[NimNode]] = - var node = fun.findChild(it.kind == nnkBracket) - if node.kind == nnkNilLit: - return # no bracket - node = node.findChild(it.kind == nnkGenericParams) - if node.kind == nnkNilLit: - return # no generics - node = node.findChild(it.kind == nnkIdentDefs) - var stack: seq[NimNode] - for i in countdown(node.len - 1, 0): stack.add(node[i]) - var gen_name: NimNode - var gen_types: seq[NimNode] - template add_gen = - if gen_name != nil: - assert gen_types.len != 0 - result[gen_name.strVal] = gen_types - gen_types.setLen(0) - - while stack.len > 0: - let node = stack.pop() - case node.kind - of nnkIdent: - add_gen - gen_name = node - of nnkSym: - assert gen_name != nil - gen_types.add(node) - of nnkInfix: - assert node[0].eqIdent(ident("|")) or node[0].eqIdent(ident("or")), - "Only OR generics are supported." - for i in countdown(node.len - 1, 1): # except infix ident - stack.add(node[i]) - of nnkBracketExpr: - gen_types.add(node) - else: - discard - add_gen - -proc getParams(fun: NimNode): seq[FuncParam] = - let formalParams = fun.findChild(it.kind == nnkFormalParams) - var funcParams: seq[FuncParam] - var returnType = none(NimNode) - if formalParams[0].kind != nnkEmpty: - returnType = some(formalParams[0]) - for i in 1..<fun.params.len: - let it = formalParams[i] - let tt = it[^2] - var t: NimNode - if it[^2].kind != nnkEmpty: - t = `tt` - elif it[^1].kind != nnkEmpty: - let x = it[^1] - t = quote do: - typeof(`x`) - else: - error("?? " & treeRepr(it)) - let isptr = t.kind == nnkVarTy - if t.kind in {nnkRefTy, nnkVarTy}: - t = t[0] - let val = if it[^1].kind != nnkEmpty: - let x = it[^1] - some(newPar(x)) - else: - none(NimNode) - var g = none(NimNode) - for i in 0 ..< it.len - 2: - let name = $it[i] - funcParams.add((name, t, val, g, isptr)) - funcParams - -proc getReturn(fun: NimNode): Option[NimNode] = - let formalParams = fun.findChild(it.kind == nnkFormalParams) - if formalParams[0].kind != nnkEmpty: - some(formalParams[0]) - else: - none(NimNode) - -template getJSParams(): untyped = - [ - (quote do: JSValue), - newIdentDefs(ident("ctx"), quote do: JSContext), - newIdentDefs(ident("this"), quote do: JSValue), - newIdentDefs(ident("argc"), quote do: cint), - newIdentDefs(ident("argv"), quote do: ptr UncheckedArray[JSValue]) - ] - -template getJSGetterParams(): untyped = - [ - (quote do: JSValue), - newIdentDefs(ident("ctx"), quote do: JSContext), - newIdentDefs(ident("this"), quote do: JSValue), - ] - -template getJSGetPropParams(): untyped = - [ - (quote do: cint), - newIdentDefs(ident("ctx"), quote do: JSContext), - newIdentDefs(ident("desc"), quote do: ptr JSPropertyDescriptor), - newIdentDefs(ident("obj"), quote do: JSValue), - newIdentDefs(ident("prop"), quote do: JSAtom), - ] - -template getJSSetPropParams(): untyped = - [ - (quote do: cint), - newIdentDefs(ident("ctx"), quote do: JSContext), - newIdentDefs(ident("obj"), quote do: JSValue), - newIdentDefs(ident("atom"), quote do: JSAtom), - newIdentDefs(ident("value"), quote do: JSValue), - newIdentDefs(ident("receiver"), quote do: JSValue), - newIdentDefs(ident("flags"), quote do: cint), - ] - -template getJSDelPropParams(): untyped = - [ - (quote do: cint), - newIdentDefs(ident("ctx"), quote do: JSContext), - newIdentDefs(ident("obj"), quote do: JSValue), - newIdentDefs(ident("prop"), quote do: JSAtom), - ] - -template getJSHasPropParams(): untyped = - [ - (quote do: cint), - newIdentDefs(ident("ctx"), quote do: JSContext), - newIdentDefs(ident("obj"), quote do: JSValue), - newIdentDefs(ident("atom"), quote do: JSAtom), - ] - - -template getJSSetterParams(): untyped = - [ - (quote do: JSValue), - newIdentDefs(ident("ctx"), quote do: JSContext), - newIdentDefs(ident("this"), quote do: JSValue), - newIdentDefs(ident("val"), quote do: JSValue), - ] - -template getJSPropNamesParams(): untyped = - [ - (quote do: cint), - newIdentDefs(ident("ctx"), quote do: JSContext), - newIdentDefs(ident("ptab"), quote do: ptr JSPropertyEnumArray), - newIdentDefs(ident("plen"), quote do: ptr uint32), - newIdentDefs(ident("obj"), quote do: JSValue) - ] - -template fromJS_or_return*(t, ctx, val: untyped): untyped = - let x = fromJS[t](ctx, val) - if x.isNone: - return toJS(ctx, x.error) - x.get - -template fromJSP_or_return*(t, ctx, val: untyped): untyped = - let x = fromJSP[t](ctx, val) - if x.isNone: - return toJS(ctx, x.error) - x.get - -template fromJS_or_die*(t, ctx, val, dl: untyped): untyped = - let x = fromJSP[t](ctx, val) - if x.isNone: - break dl - x.get - -proc addParam2(gen: var JSFuncGenerator; s, t, val: NimNode; - fallback: NimNode = nil) = - let dl = gen.dielabel - let stmt = quote do: - fromJS_or_die(`t`, ctx, `val`, `dl`) - for i in 0..gen.jsFunCallLists.high: - if fallback == nil: - gen.jsFunCallLists[i].add(newLetStmt(s, stmt)) - else: - let j = gen.j - gen.jsFunCallLists[i].add(newLetStmt(s, quote do: - if `j` < argc and not JS_IsUndefined(argv[`j`]): - `stmt` - else: - `fallback`)) - -proc addValueParam(gen: var JSFuncGenerator; s, t: NimNode; - fallback: NimNode = nil) = - let j = gen.j - gen.addParam2(s, t, quote do: argv[`j`], fallback) - -proc addUnionParamBranch(gen: var JSFuncGenerator; query, newBranch: NimNode; - fallback: NimNode = nil) = - let i = gen.i - let query = if fallback == nil: query else: - quote do: (`i` < argc and `query`) - let newBranch = newStmtList(newBranch) - for i in 0..gen.jsFunCallLists.high: - var ifstmt = newIfStmt((query, newBranch)) - let oldBranch = newStmtList() - ifstmt.add(newTree(nnkElse, oldBranch)) - gen.jsFunCallLists[i].add(ifstmt) - gen.jsFunCallLists[i] = oldBranch - gen.newBranchList.add(newBranch) - -func isSequence*(ctx: JSContext; o: JSValue): bool = - if not JS_IsObject(o): - return false - let prop = JS_GetProperty(ctx, o, ctx.getOpaque().symRefs[jsyIterator]) - # prop can't be exception (throws_ref_error is 0 and tag is object) - result = not JS_IsUndefined(prop) - JS_FreeValue(ctx, prop) - -proc addUnionParam0(gen: var JSFuncGenerator; tt, s, val: NimNode; - fallback: NimNode = nil) = - # Union types. - #TODO quite a few types are still missing. - let flattened = gen.generics[tt.strVal] # flattened member types - var tableg = none(NimNode) - var seqg = none(NimNode) - var numg = none(NimNode) - var objg = none(NimNode) - var hasString = false - var hasJSValue = false - var hasBoolean = false - let ev = gen.errval - let dl = gen.dielabel - for g in flattened: - if g.len > 0 and g[0] == Table.getType(): - tableg = some(g) - elif g.typekind == ntySequence: - seqg = some(g) - elif g == string.getTypeInst(): - hasString = true - elif g == JSValue.getTypeInst(): - hasJSValue = true - elif g == bool.getTypeInst(): - hasBoolean = true - elif g == int.getTypeInst(): #TODO should be SomeNumber - assert numg.isNone - numg = some(g) - elif g == uint32.getTypeInst(): #TODO should be SomeNumber - assert numg.isNone - numg = some(g) - elif g.getTypeInst().getTypeImpl().kind == nnkRefTy: - # Assume it's ref object. - objg = some(g) - else: - error("Type not supported yet") - - # 5. If V is a platform object, then: - if objg.isSome: - let t = objg.get - let x = ident("x") - let query = quote do: - let `x` = fromJS[`t`](ctx, `val`) - `x`.isSome - gen.addUnionParamBranch(query, quote do: - let `s` = `x`.get, - fallback) - # 10. If Type(V) is Object, then: - # Sequence: - if seqg.isSome: - let query = quote do: - isSequence(ctx, `val`) - let a = seqg.get[1] - gen.addUnionParamBranch(query, quote do: - let `s` = fromJS_or_die(seq[`a`], ctx, `val`, `dl`), - fallback) - # Record: - if tableg.isSome: - let a = tableg.get[1] - let b = tableg.get[2] - let query = quote do: - JS_IsObject(`val`) - gen.addUnionParamBranch(query, quote do: - let `s` = fromJS_or_die(Table[`a`, `b`], ctx, `val`, `dl`), - fallback) - # Object (JSObject variant): - #TODO non-JS objects (i.e. ref object) - if hasJSValue: - let query = quote do: - JS_IsObject(`val`) - gen.addUnionParamBranch(query, quote do: - let `s` = fromJS_or_die(JSValue, ctx, `val`, `dl`), - fallback) - # 11. If Type(V) is Boolean, then: - if hasBoolean: - let query = quote do: - JS_IsBool(`val`) - gen.addUnionParamBranch(query, quote do: - let `s` = fromJS_or_die(bool, ctx, `val`, `dl`), - fallback) - # 12. If Type(V) is Number, then: - if numg.isSome: - let ng = numg.get - let query = quote do: - JS_IsNumber(`val`) - gen.addUnionParamBranch(query, quote do: - let `s` = fromJS_or_die(`ng`, ctx, `val`, `dl`), - fallback) - # 14. If types includes a string type, then return the result of converting V - # to that type. - if hasString: - gen.addParam2(s, string.getType(), quote do: `val`, fallback) - # 16. If types includes a numeric type, then return the result of converting - # V to that numeric type. - elif numg.isSome: - gen.addParam2(s, numg.get.getType(), quote do: `val`, fallback) - # 17. If types includes boolean, then return the result of converting V to - # boolean. - elif hasBoolean: - gen.addParam2(s, bool.getType(), quote do: `val`, fallback) - # 19. Throw a TypeError. - else: - gen.addParam2(s, string.getType(), quote do: - if true: - discard JS_ThrowTypeError(ctx, "No match for union type") - return `ev` - JS_NULL, fallback) - - for branch in gen.newBranchList: - gen.jsFunCallLists.add(branch) - gen.newBranchList.setLen(0) - -proc addUnionParam(gen: var JSFuncGenerator; tt, s: NimNode; - fallback: NimNode = nil) = - let j = gen.j - gen.addUnionParam0(tt, s, quote do: argv[`j`], fallback) - -proc addFixParam(gen: var JSFuncGenerator; name: string) = - var s = ident("arg_" & $gen.i) - let t = gen.funcParams[gen.i].t - let id = ident(name) - if t.typeKind == ntyGenericParam: - gen.addUnionParam0(t, s, id) - else: - gen.addParam2(s, t, id) - if gen.funcParams[gen.i].isptr: - s = quote do: `s`[] - gen.jsFunCall.add(s) - inc gen.i - -proc addRequiredParams(gen: var JSFuncGenerator) = - while gen.i < gen.minArgs: - var s = ident("arg_" & $gen.i) - let tt = gen.funcParams[gen.i].t - if tt.typeKind == ntyGenericParam: - gen.addUnionParam(tt, s) - else: - gen.addValueParam(s, tt) - if gen.funcParams[gen.i].isptr: - s = quote do: `s`[] - gen.jsFunCall.add(s) - inc gen.j - inc gen.i - -proc addOptionalParams(gen: var JSFuncGenerator) = - while gen.i < gen.funcParams.len: - let j = gen.j - var s = ident("arg_" & $gen.i) - let tt = gen.funcParams[gen.i].t - if tt.typeKind == varargs.getType().typeKind: # pray it's not a generic... - let vt = tt[1].getType() - for i in 0..gen.jsFunCallLists.high: - gen.jsFunCallLists[i].add(newLetStmt(s, quote do: - var valist: seq[`vt`] = @[] - for i in `j`..<argc: - let it = fromJS_or_return(`vt`, ctx, argv[i]) - valist.add(it) - valist - )) - else: - if gen.funcParams[gen.i][2].isNone: - error("No fallback value. Maybe a non-optional parameter follows an " & - "optional parameter?") - let fallback = gen.funcParams[gen.i][2].get - if tt.typeKind == ntyGenericParam: - gen.addUnionParam(tt, s, fallback) - else: - gen.addValueParam(s, tt, fallback) - if gen.funcParams[gen.i].isptr: - s = quote do: `s`[] - gen.jsFunCall.add(s) - inc gen.j - inc gen.i - -proc finishFunCallList(gen: var JSFuncGenerator) = - for branch in gen.jsFunCallLists: - branch.add(gen.jsFunCall) - -var existingFuncs {.compileTime.}: HashSet[string] -var jsDtors {.compileTime.}: HashSet[string] - -proc registerFunction(typ: string; nf: BoundFunction) = - BoundFunctions.withValue(typ, val): - val[].add(nf) - do: - BoundFunctions[typ] = @[nf] - existingFuncs.incl(nf.id.strVal) - -proc registerFunction(typ: string; t: BoundFunctionType; name: string; - id: NimNode; magic: uint16 = 0; uf = false; isstatic = false; - ctorBody: NimNode = nil) = - registerFunction(typ, BoundFunction( - t: t, - name: name, - id: id, - magic: magic, - unforgeable: uf, - isstatic: isstatic, - ctorBody: ctorBody - )) - -proc registerConstructor(gen: JSFuncGenerator; jsProc: NimNode) = - registerFunction(gen.thisType, gen.t, gen.funcName, gen.newName, - uf = gen.unforgeable, isstatic = gen.isstatic, ctorBody = jsProc) - -proc registerFunction(gen: JSFuncGenerator) = - registerFunction(gen.thisType, gen.t, gen.funcName, gen.newName, - uf = gen.unforgeable, isstatic = gen.isstatic) - -export JS_ThrowTypeError, JS_ThrowRangeError, JS_ThrowSyntaxError, - JS_ThrowInternalError, JS_ThrowReferenceError - -proc newJSProcBody(gen: var JSFuncGenerator; isva: bool): NimNode = - let tt = gen.thisType - let fn = gen.funcName - let ma = gen.actualMinArgs - result = newStmtList() - if isva: - result.add(quote do: - if argc < `ma`: - return JS_ThrowTypeError(ctx, - "At least %d arguments required, but only %d passed", cint(`ma`), - cint(argc)) - ) - if gen.thisname.isSome and not gen.isstatic: - let tn = ident(gen.thisname.get) - let ev = gen.errval - result.add(quote do: - if not ctx.isInstanceOf(`tn`, `tt`): - discard JS_ThrowTypeError(ctx, - "'%s' called on an object that is not an instance of %s", `fn`, `tt`) - return `ev` - ) - result.add(gen.jsCallAndRet) - -proc newJSProc(gen: var JSFuncGenerator; params: openArray[NimNode]; - isva = true): NimNode = - let jsBody = gen.newJSProcBody(isva) - let jsPragmas = newNimNode(nnkPragma).add(ident("cdecl")) - return newProc(gen.newName, params, jsBody, pragmas = jsPragmas) - -func getFuncName(fun: NimNode; jsname, staticName: string): string = - if jsname != "": - return jsname - if staticName != "": - let i = staticName.find('.') - if i != -1: - return staticName.substr(i + 1) - let x = $fun[0] - if x == "$": - # stringifier - return "toString" - return x - -func getErrVal(t: BoundFunctionType): NimNode = - if t in {bfPropertyGet, bfPropertySet, bfPropertyDel, bfPropertyHas, - bfPropertyNames}: - return quote do: cint(-1) - return quote do: JS_EXCEPTION - -proc addJSContext(gen: var JSFuncGenerator) = - if gen.funcParams.len > gen.i: - if gen.funcParams[gen.i].t.eqIdent(ident("JSContext")): - gen.passCtx = true - gen.jsFunCall.add(ident("ctx")) - inc gen.i - elif gen.funcParams[gen.i].t.eqIdent(ident("JSRuntime")): - inc gen.i # special case for finalizers that have a JSRuntime param - -proc addThisName(gen: var JSFuncGenerator; thisname: Option[string]) = - if thisname.isSome: - gen.thisTypeNode = gen.funcParams[gen.i].t - gen.thisType = $gen.funcParams[gen.i].t - gen.newName = ident($gen.t & "_" & gen.thisType & "_" & gen.funcName) - else: - let rt = gen.returnType.get - if rt.kind in {nnkRefTy, nnkPtrTy}: - gen.thisTypeNode = rt[0] - gen.thisType = rt[0].strVal - else: - if rt.kind == nnkBracketExpr: - gen.thisTypeNode = rt[1] - gen.thisType = rt[1].strVal - else: - gen.thisTypeNode = rt - gen.thisType = rt.strVal - gen.newName = ident($gen.t & "_" & gen.funcName) - -func getActualMinArgs(gen: var JSFuncGenerator): int = - var ma = gen.minArgs - if gen.thisname.isSome and not gen.isstatic: - dec ma - if gen.passCtx: - dec ma - assert ma >= 0 - return ma - -func until(s: string; c: char; starti = 0): string = - result = "" - for i, cc in s: - if cc == c: - break - result &= cc - -proc initGenerator(fun: NimNode; t: BoundFunctionType; thisname = some("this"); - jsname = ""; unforgeable = false; staticName = ""): JSFuncGenerator = - let jsFunCallList = newStmtList() - let funcParams = getParams(fun) - var gen = JSFuncGenerator( - t: t, - funcName: getFuncName(fun, jsname, staticName), - generics: getGenerics(fun), - funcParams: funcParams, - returnType: getReturn(fun), - minArgs: funcParams.getMinArgs(), - thisname: thisname, - errval: getErrVal(t), - dielabel: ident("ondie"), - jsFunCallList: jsFunCallList, - jsFunCallLists: @[jsFunCallList], - jsFunCall: newCall(fun[0]), - unforgeable: unforgeable, - isstatic: staticName != "" - ) - gen.addJSContext() - gen.actualMinArgs = gen.getActualMinArgs() # must come after passctx is set - if staticName == "": - gen.addThisName(thisname) - else: - gen.thisType = staticName.until('.') - gen.newName = ident($gen.t & "_" & gen.funcName) - return gen - -proc makeJSCallAndRet(gen: var JSFuncGenerator; okstmt, errstmt: NimNode) = - let jfcl = gen.jsFunCallList - let dl = gen.dielabel - gen.jsCallAndRet = if gen.returnType.isSome: - quote do: - block `dl`: - return ctx.toJS(`jfcl`) - `errstmt` - else: - quote do: - block `dl`: - `jfcl` - `okstmt` - `errstmt` - -proc makeCtorJSCallAndRet(gen: var JSFuncGenerator; errstmt: NimNode) = - let jfcl = gen.jsFunCallList - let dl = gen.dielabel - gen.jsCallAndRet = quote do: - block `dl`: - return ctx.toJSNew(`jfcl`, this) - `errstmt` - -macro jsctor*(fun: typed) = - var gen = initGenerator(fun, bfConstructor, thisname = none(string)) - if gen.newName.strVal in existingFuncs: - #TODO TODO TODO implement function overloading - error("Function overloading hasn't been implemented yet...") - gen.addRequiredParams() - gen.addOptionalParams() - gen.finishFunCallList() - let errstmt = quote do: - return JS_ThrowTypeError(ctx, "Invalid parameters passed to constructor") - gen.makeCtorJSCallAndRet(errstmt) - let jsProc = gen.newJSProc(getJSParams()) - gen.registerConstructor(jsProc) - return newStmtList(fun) - -macro jshasprop*(fun: typed) = - var gen = initGenerator(fun, bfPropertyHas, thisname = some("obj")) - if gen.newName.strVal in existingFuncs: - #TODO TODO TODO ditto - error("Function overloading hasn't been implemented yet...") - gen.addFixParam("obj") - gen.addFixParam("atom") - gen.finishFunCallList() - let jfcl = gen.jsFunCallList - let dl = gen.dielabel - gen.jsCallAndRet = quote do: - block `dl`: - let retv = `jfcl` - return cint(retv) - doAssert false # TODO? - let jsProc = gen.newJSProc(getJSHasPropParams(), false) - gen.registerFunction() - return newStmtList(fun, jsProc) - -macro jsgetprop*(fun: typed) = - var gen = initGenerator(fun, bfPropertyGet, thisname = some("obj")) - if gen.newName.strVal in existingFuncs: - #TODO TODO TODO ditto - error("Function overloading hasn't been implemented yet...") - gen.addFixParam("obj") - gen.addFixParam("prop") - gen.finishFunCallList() - let jfcl = gen.jsFunCallList - let dl = gen.dielabel - gen.jsCallAndRet = quote do: - block `dl`: - let retv = ctx.toJS(`jfcl`) - if retv != JS_NULL: - if desc != nil: - # From quickjs.h: - # > If 1 is returned, the property descriptor 'desc' is filled - # > if != NULL. - # So desc may be nil. - desc[].setter = JS_UNDEFINED - desc[].getter = JS_UNDEFINED - desc[].value = retv - desc[].flags = 0 - return cint(1) - return cint(0) - let jsProc = gen.newJSProc(getJSGetPropParams(), false) - gen.registerFunction() - return newStmtList(fun, jsProc) - -macro jssetprop*(fun: typed) = - var gen = initGenerator(fun, bfPropertySet, thisname = some("obj")) - if gen.newName.strVal in existingFuncs: - #TODO TODO TODO ditto - error("Function overloading hasn't been implemented yet...") - gen.addFixParam("receiver") - gen.addFixParam("atom") - gen.addFixParam("value") - gen.finishFunCallList() - let jfcl = gen.jsFunCallList - let dl = gen.dielabel - gen.jsCallAndRet = if gen.returnType.isSome: - quote do: - block `dl`: - let v = toJS(ctx, `jfcl`) - if not JS_IsException(v): - return cint(1) - return cint(-1) - else: - quote do: - block `dl`: - `jfcl` - return cint(1) - return cint(-1) - let jsProc = gen.newJSProc(getJSSetPropParams(), false) - gen.registerFunction() - return newStmtList(fun, jsProc) - -macro jsdelprop*(fun: typed) = - var gen = initGenerator(fun, bfPropertyDel, thisname = some("obj")) - if gen.newName.strVal in existingFuncs: - #TODO TODO TODO ditto - error("Function overloading hasn't been implemented yet...") - gen.addFixParam("obj") - gen.addFixParam("prop") - gen.finishFunCallList() - let jfcl = gen.jsFunCallList - let dl = gen.dielabel - gen.jsCallAndRet = quote do: - block `dl`: - let retv = `jfcl` - return cint(retv) - return cint(-1) - let jsProc = gen.newJSProc(getJSDelPropParams(), false) - gen.registerFunction() - return newStmtList(fun, jsProc) - -macro jspropnames*(fun: typed) = - var gen = initGenerator(fun, bfPropertyNames, thisname = some("obj")) - if gen.newName.strVal in existingFuncs: - #TODO TODO TODO ditto - error("Function overloading hasn't been implemented yet...") - gen.addFixParam("obj") - gen.finishFunCallList() - let jfcl = gen.jsFunCallList - let dl = gen.dielabel - gen.jsCallAndRet = quote do: - block `dl`: - let retv = `jfcl` - ptab[] = retv.buffer - plen[] = retv.len - return cint(0) - return cint(-1) - let jsProc = gen.newJSProc(getJSPropNamesParams(), false) - gen.registerFunction() - return newStmtList(fun, jsProc) - -macro jsfgetn(jsname: static string; uf: static bool; fun: typed) = - var gen = initGenerator(fun, bfGetter, jsname = jsname, unforgeable = uf) - if gen.actualMinArgs != 0 or gen.funcParams.len != gen.minArgs: - error("jsfget functions must only accept one parameter.") - if gen.returnType.isNone: - error("jsfget functions must have a return type.") - if gen.newName.strVal in existingFuncs: - #TODO TODO TODO ditto - error("Function overloading hasn't been implemented yet...") - gen.addFixParam("this") - gen.finishFunCallList() - gen.makeJSCallAndRet(nil, quote do: discard) - let jsProc = gen.newJSProc(getJSGetterParams(), false) - gen.registerFunction() - return newStmtList(fun, jsProc) - -# "Why?" So the compiler doesn't cry. -template jsfget*(fun: typed) = - jsfgetn("", false, fun) - -template jsuffget*(fun: typed) = - jsfgetn("", true, fun) - -template jsfget*(jsname: static string; fun: typed) = - jsfgetn(jsname, false, fun) - -template jsuffget*(jsname: static string; fun: typed) = - jsfgetn(jsname, true, fun) - -# Ideally we could simulate JS setters using nim setters, but nim setters -# won't accept types that don't match their reflected field's type. -macro jsfsetn(jsname: static string; fun: typed) = - var gen = initGenerator(fun, bfSetter, jsname = jsname) - if gen.actualMinArgs != 1 or gen.funcParams.len != gen.minArgs: - error("jsfset functions must accept two parameters") - if gen.returnType.isSome: - let rt = gen.returnType.get - #TODO ?? - let rtType = rt[0] - let errType = getTypeInst(Err) - if not errType.sameType(rtType) and not rtType.sameType(errType): - error("jsfset functions must not have a return type") - gen.addFixParam("this") - gen.addFixParam("val") - gen.finishFunCallList() - # return param anyway - let okstmt = quote do: discard - let errstmt = quote do: return JS_DupValue(ctx, val) - gen.makeJSCallAndRet(okstmt, errstmt) - let jsProc = gen.newJSProc(getJSSetterParams(), false) - gen.registerFunction() - return newStmtList(fun, jsProc) - -template jsfset*(fun: typed) = - jsfsetn("", fun) - -template jsfset*(jsname: static string; fun: typed) = - jsfsetn(jsname, fun) - -macro jsfuncn*(jsname: static string; uf: static bool; - staticName: static string; fun: typed) = - var gen = initGenerator(fun, bfFunction, jsname = jsname, unforgeable = uf, - staticName = staticName) - if gen.minArgs == 0 and not gen.isstatic: - error("Zero-parameter functions are not supported. " & - "(Maybe pass Window or Client?)") - if not gen.isstatic: - gen.addFixParam("this") - gen.addRequiredParams() - gen.addOptionalParams() - gen.finishFunCallList() - let okstmt = quote do: - return JS_UNDEFINED - let errstmt = quote do: - return JS_ThrowTypeError(ctx, "Invalid parameters passed to function") - gen.makeJSCallAndRet(okstmt, errstmt) - let jsProc = gen.newJSProc(getJSParams()) - gen.registerFunction() - return newStmtList(fun, jsProc) - -template jsfunc*(fun: typed) = - jsfuncn("", false, "", fun) - -template jsuffunc*(fun: typed) = - jsfuncn("", true, "", fun) - -template jsfunc*(jsname: static string; fun: typed) = - jsfuncn(jsname, false, "", fun) - -template jsuffunc*(jsname: static string; fun: typed) = - jsfuncn(jsname, true, "", fun) - -template jsstfunc*(name: static string; fun: typed) = - jsfuncn("", false, name, fun) - -macro jsfin*(fun: typed) = - var gen = initGenerator(fun, bfFinalizer, thisname = some("fin")) - let finName = gen.newName - let finFun = ident(gen.funcName) - let t = gen.thisTypeNode - if gen.minArgs == 1: - let jsProc = quote do: - proc `finName`(rt: JSRuntime; val: JSValue) = - let opaque = JS_GetOpaque(val, JS_GetClassID(val)) - if opaque != nil: - `finFun`(cast[`t`](opaque)) - gen.registerFunction() - result = newStmtList(fun, jsProc) - elif gen.minArgs == 2: - let jsProc = quote do: - proc `finName`(rt: JSRuntime; val: JSValue) = - let opaque = JS_GetOpaque(val, JS_GetClassID(val)) - if opaque != nil: - `finFun`(rt, cast[`t`](opaque)) - gen.registerFunction() - result = newStmtList(fun, jsProc) - else: - error("Expected one or two parameters") - -# Having the same names for these and the macros leads to weird bugs, so the -# macros get an additional f. -template jsget*() {.pragma.} -template jsget*(name: string) {.pragma.} -template jsset*() {.pragma.} -template jsset*(name: string) {.pragma.} -template jsgetset*() {.pragma.} -template jsgetset*(name: string) {.pragma.} -template jsufget*() {.pragma.} -template jsufget*(name: string) {.pragma.} - -proc js_illegal_ctor*(ctx: JSContext; this: JSValue; argc: cint; - argv: ptr UncheckedArray[JSValue]): JSValue {.cdecl.} = - return JS_ThrowTypeError(ctx, "Illegal constructor") - -type - JSObjectPragma = object - name: string - varsym: NimNode - unforgeable: bool - - JSObjectPragmas = object - jsget: seq[JSObjectPragma] - jsset: seq[JSObjectPragma] - jsinclude: seq[JSObjectPragma] - -func getPragmaName(varPragma: NimNode): string = - if varPragma.kind == nnkExprColonExpr: - return $varPragma[0] - return $varPragma - -func getStringFromPragma(varPragma: NimNode): Option[string] = - if varPragma.kind == nnkExprColonExpr: - if not varPragma.len == 1 and varPragma[1].kind == nnkStrLit: - error("Expected string as pragma argument") - return some($varPragma[1]) - -proc findPragmas(t: NimNode): JSObjectPragmas = - let typ = t.getTypeInst()[1] # The type, as declared. - var impl = typ.getTypeImpl() # ref t - if impl.kind in {nnkRefTy, nnkPtrTy}: - impl = impl[0].getImpl() - else: - impl = typ.getImpl() - # stolen from std's macros.customPragmaNode - var identDefsStack = newSeq[NimNode](impl[2].len) - for i in 0 ..< identDefsStack.len: - identDefsStack[i] = impl[2][i] - var pragmas: JSObjectPragmas - while identDefsStack.len > 0: - var identDefs = identDefsStack.pop() - case identDefs.kind - of nnkRecList: - for child in identDefs.children: - identDefsStack.add(child) - of nnkRecCase: - # Add condition definition - identDefsStack.add(identDefs[0]) - # Add branches - for i in 1 ..< identDefs.len: - identDefsStack.add(identDefs[i].last) - else: - for i in 0 .. identDefs.len - 3: - let varNode = identDefs[i] - if varNode.kind == nnkPragmaExpr: - var varName = varNode[0] - if varName.kind == nnkPostfix: - # This is a public field. We are skipping the postfix * - varName = varName[1] - var varPragmas = varNode[1] - for varPragma in varPragmas: - let pragmaName = getPragmaName(varPragma) - var op = JSObjectPragma( - name: getStringFromPragma(varPragma).get($varName), - varsym: varName - ) - case pragmaName - of "jsget": pragmas.jsget.add(op) - of "jsset": pragmas.jsset.add(op) - of "jsufget": # LegacyUnforgeable - op.unforgeable = true - pragmas.jsget.add(op) - of "jsgetset": - pragmas.jsget.add(op) - pragmas.jsset.add(op) - of "jsinclude": pragmas.jsinclude.add(op) - return pragmas - -proc nim_finalize_for_js*(obj: pointer) = - for rt in runtimes: - let rtOpaque = rt.getOpaque() - rtOpaque.plist.withValue(obj, v): - let p = v[] - let val = JS_MKPTR(JS_TAG_OBJECT, p) - let classid = JS_GetClassID(val) - rtOpaque.fins.withValue(classid, fin): - fin[](rt, val) - JS_SetOpaque(val, nil) - rtOpaque.plist.del(obj) - if rtOpaque.destroying == obj: - # Allow QJS to collect the JSValue through checkDestroy. - rtOpaque.destroying = nil - else: - JS_FreeValueRT(rt, val) - -type - TabGetSet* = object - name*: string - get*: JSGetterMagicFunction - set*: JSSetterMagicFunction - magic*: int16 - - TabFunc* = object - name*: string - fun*: JSCFunction - -template jsDestructor*[U](T: typedesc[ref U]) = - static: - jsDtors.incl($T) - {.warning[Deprecated]:off.}: - proc `=destroy`(obj: var U) = - nim_finalize_for_js(addr obj) - -template jsDestructor*(T: typedesc[object]) = - static: - jsDtors.incl($T) - {.warning[Deprecated]:off.}: - proc `=destroy`(obj: var T) = - nim_finalize_for_js(addr obj) - -type RegistryInfo = object - t: NimNode # NimNode of type - name: string # JS name, if this is the empty string then it equals tname - tabList: NimNode # array of function table - ctorImpl: NimNode # definition & body of constructor - ctorFun: NimNode # constructor ident - getset: Table[string, (NimNode, NimNode, bool)] # name -> get, set, uf - propGetFun: NimNode # custom get function ident - propSetFun: NimNode # custom set function ident - propDelFun: NimNode # custom del function ident - propHasFun: NimNode # custom has function ident - propNamesFun: NimNode # custom property names function ident - finFun: NimNode # finalizer ident - finName: NimNode # finalizer wrapper ident - dfin: NimNode # CheckDestroy finalizer ident - classDef: NimNode # ClassDef ident - tabUnforgeable: NimNode # array of unforgeable function table - tabStatic: NimNode # array of static function table - -func tname(info: RegistryInfo): string = - return info.t.strVal - -# Differs from tname if the Nim object's name differs from the JS object's -# name. -func jsname(info: RegistryInfo): string = - if info.name != "": - return info.name - return info.tname - -proc newRegistryInfo(t: NimNode; name: string): RegistryInfo = - return RegistryInfo( - t: t, - name: name, - classDef: ident("classDef"), - tabList: newNimNode(nnkBracket), - tabUnforgeable: newNimNode(nnkBracket), - tabStatic: newNimNode(nnkBracket), - finName: newNilLit(), - finFun: newNilLit(), - propGetFun: newNilLit(), - propSetFun: newNilLit(), - propDelFun: newNilLit(), - propHasFun: newNilLit(), - propNamesFun: newNilLit() - ) - -proc bindConstructor(stmts: NimNode; info: var RegistryInfo): NimNode = - if info.ctorFun != nil: - stmts.add(info.ctorImpl) - return info.ctorFun - return ident("js_illegal_ctor") - -proc registerGetters(stmts: NimNode; info: RegistryInfo; - jsget: seq[JSObjectPragma]) = - let t = info.t - let tname = info.tname - let jsname = info.jsname - for op in jsget: - let node = op.varsym - let fn = op.name - let id = ident($bfGetter & "_" & tname & "_" & fn) - stmts.add(quote do: - proc `id`(ctx: JSContext; this: JSValue): JSValue {.cdecl.} = - if not ctx.isInstanceOf(this, `tname`): - return JS_ThrowTypeError(ctx, - "'%s' called on an object that is not an instance of %s", `fn`, - `jsname`) - let arg_0 = fromJSP_or_return(`t`, ctx, this) - when typeof(arg_0.`node`) is object: - return toJSP(ctx, arg_0, arg_0.`node`) - else: - return toJS(ctx, arg_0.`node`) - ) - registerFunction(tname, BoundFunction( - t: bfGetter, - name: fn, - id: id, - unforgeable: op.unforgeable - )) - -proc registerSetters(stmts: NimNode; info: RegistryInfo; - jsset: seq[JSObjectPragma]) = - let t = info.t - let tname = info.tname - let jsname = info.jsname - for op in jsset: - let node = op.varsym - let fn = op.name - let id = ident($bfSetter & "_" & tname & "_" & fn) - stmts.add(quote do: - proc `id`(ctx: JSContext; this: JSValue; val: JSValue): JSValue - {.cdecl.} = - if not ctx.isInstanceOf(this, `tname`): - return JS_ThrowTypeError(ctx, - "'%s' called on an object that is not an instance of %s", `fn`, - `jsname`) - let arg_0 = fromJSP_or_return(`t`, ctx, this) - let arg_1 = val - # Note: if you get a compiler error that leads back to here, that - # might be because you added jsset to a non-ref object type. - arg_0.`node` = fromJS_or_return(typeof(arg_0.`node`), ctx, arg_1) - return JS_DupValue(ctx, arg_1) - ) - registerFunction(tname, bfSetter, fn, id) - -proc bindFunctions(stmts: NimNode; info: var RegistryInfo) = - BoundFunctions.withValue(info.tname, funs): - for fun in funs[].mitems: - var f0 = fun.name - let f1 = fun.id - if fun.name.endsWith("_exceptions"): - fun.name = fun.name.substr(0, fun.name.high - "_exceptions".len) - case fun.t - of bfFunction: - f0 = fun.name - if fun.unforgeable: - info.tabUnforgeable.add(quote do: - JS_CFUNC_DEF_NOCONF(`f0`, 0, cast[JSCFunction](`f1`))) - elif fun.isstatic: - info.tabStatic.add(quote do: - JS_CFUNC_DEF(`f0`, 0, cast[JSCFunction](`f1`))) - else: - info.tabList.add(quote do: - JS_CFUNC_DEF(`f0`, 0, cast[JSCFunction](`f1`))) - of bfConstructor: - info.ctorImpl = fun.ctorBody - if info.ctorFun != nil: - error("Class " & info.tname & " has 2+ constructors.") - info.ctorFun = f1 - of bfGetter: - info.getset.withValue(f0, exv): - exv[0] = f1 - exv[2] = fun.unforgeable - do: - info.getset[f0] = (f1, newNilLit(), fun.unforgeable) - of bfSetter: - info.getset.withValue(f0, exv): - exv[1] = f1 - do: - info.getset[f0] = (newNilLit(), f1, false) - of bfPropertyGet: - if info.propGetFun.kind != nnkNilLit: - error("Class " & info.tname & " has 2+ property getters.") - info.propGetFun = f1 - of bfPropertySet: - if info.propSetFun.kind != nnkNilLit: - error("Class " & info.tname & " has 2+ property setters.") - info.propSetFun = f1 - of bfPropertyDel: - if info.propDelFun.kind != nnkNilLit: - error("Class " & info.tname & " has 2+ property setters.") - info.propDelFun = f1 - of bfPropertyHas: - if info.propHasFun.kind != nnkNilLit: - error("Class " & info.tname & " has 2+ hasprop getters.") - info.propHasFun = f1 - of bfPropertyNames: - if info.propNamesFun.kind != nnkNilLit: - error("Class " & info.tname & " has 2+ propnames getters.") - info.propNamesFun = f1 - of bfFinalizer: - f0 = fun.name - info.finFun = ident(f0) - info.finName = f1 - -proc bindGetSet(stmts: NimNode; info: RegistryInfo) = - for k, (get, set, unforgeable) in info.getset: - if not unforgeable: - info.tabList.add(quote do: JS_CGETSET_DEF(`k`, `get`, `set`)) - else: - info.tabUnforgeable.add(quote do: - JS_CGETSET_DEF_NOCONF(`k`, `get`, `set`)) - -proc bindExtraGetSet(stmts: NimNode; info: var RegistryInfo; - extraGetSet: openArray[TabGetSet]) = - for x in extraGetSet: - let k = x.name - let g = x.get - let s = x.set - let m = x.magic - info.tabList.add(quote do: JS_CGETSET_MAGIC_DEF(`k`, `g`, `s`, `m`)) - -proc bindCheckDestroy(stmts: NimNode; info: RegistryInfo) = - let t = info.t - let dfin = info.dfin - stmts.add(quote do: - proc `dfin`(rt: JSRuntime; val: JSValue): JS_BOOL {.cdecl.} = - let opaque = JS_GetOpaque(val, JS_GetClassID(val)) - if opaque != nil: - when `t` is ref object: - # Before this function is called, the ownership model is - # JSObject -> Nim object. - # Here we change it to Nim object -> JSObject. - # As a result, Nim object's reference count can now reach zero (it is - # no longer "referenced" by the JS object). - # nim_finalize_for_js will be invoked by the Nim GC when the Nim - # refcount reaches zero. Then, the JS object's opaque will be set - # to nil, and its refcount decreased again, so next time this - # function will return true. - # - # Actually, we need another hack to ensure correct - # operation. GC_unref may call the destructor of this object, and - # in this case we cannot ask QJS to keep the JSValue alive. So we set - # the "destroying" pointer to the current opaque, and return true if - # the opaque was collected. - rt.getOpaque().destroying = opaque - GC_unref(cast[`t`](opaque)) - if rt.getOpaque().destroying == nil: - # Looks like GC_unref called nim_finalize_for_js for this pointer. - # This means we can allow QJS to collect this JSValue. - return true - else: - rt.getOpaque().destroying = nil - # Returning false from this function signals to the QJS GC that it - # should not be collected yet. Accordingly, the JSObject's refcount - # will be set to one again. - return false - else: - # This is not a reference, just a pointer with a reference to the - # root ancestor object. - # Remove the reference, allowing destruction of the root object once - # again. - let rtOpaque = rt.getOpaque() - var crefunref: tuple[cref, cunref: (proc())] - discard rtOpaque.refmap.pop(opaque, crefunref) - crefunref.cunref() - # Of course, nim_finalize_for_js might only be called later for - # this object, because the parent can still have references to it. - # (And for the same reason, a reference to the same object might - # still be necessary.) - # Accordingly, we return false here as well. - return false - return true - ) - -proc bindEndStmts(endstmts: NimNode; info: RegistryInfo) = - let jsname = info.jsname - let dfin = info.dfin - let classDef = info.classDef - if info.propGetFun.kind != nnkNilLit or - info.propSetFun.kind != nnkNilLit or - info.propDelFun.kind != nnkNilLit or - info.propHasFun.kind != nnkNilLit or - info.propNamesFun.kind != nnkNilLit: - let propGetFun = info.propGetFun - let propSetFun = info.propSetFun - let propDelFun = info.propDelFun - let propHasFun = info.propHasFun - let propNamesFun = info.propNamesFun - endstmts.add(quote do: - var exotic {.global.} = JSClassExoticMethods( - get_own_property: `propGetFun`, - get_own_property_names: `propNamesFun`, - has_property: `propHasFun`, - set_property: `propSetFun`, - delete_property: `propDelFun` - ) - var cd {.global.} = JSClassDef( - class_name: `jsname`, - can_destroy: `dfin`, - exotic: addr exotic - ) - let `classDef` = JSClassDefConst(addr cd) - ) - else: - endstmts.add(quote do: - var cd {.global.} = JSClassDef( - class_name: `jsname`, - can_destroy: `dfin` - ) - let `classDef` = JSClassDefConst(addr cd)) - -macro registerType*(ctx: JSContext; t: typed; parent: JSClassID = 0; - asglobal: static bool = false; globalparent: static bool = false; - nointerface = false; name: static string = ""; - hasExtraGetSet: static bool = false; - extraGetSet: static openArray[TabGetSet] = []; namespace = JS_NULL; - errid = opt(JSErrorEnum); ishtmldda = false): JSClassID = - var stmts = newStmtList() - var info = newRegistryInfo(t, name) - if not asglobal: - info.dfin = ident("js_" & t.strVal & "ClassCheckDestroy") - if info.tname notin jsDtors: - warning("No destructor has been defined for type " & info.tname) - else: - info.dfin = newNilLit() - if info.tname in jsDtors: - error("Global object " & info.tname & " must not have a destructor!") - let pragmas = findPragmas(t) - stmts.registerGetters(info, pragmas.jsget) - stmts.registerSetters(info, pragmas.jsset) - stmts.bindFunctions(info) - stmts.bindGetSet(info) - if hasExtraGetSet: - #HACK: for some reason, extraGetSet gets weird contents when nothing is - # passed to it. So we need an extra flag to signal if anything has - # been passed to it at all. - stmts.bindExtraGetSet(info, extraGetSet) - let sctr = stmts.bindConstructor(info) - if not asglobal: - stmts.bindCheckDestroy(info) - let endstmts = newStmtList() - endstmts.bindEndStmts(info) - let tabList = info.tabList - let finName = info.finName - let classDef = info.classDef - let tname = info.tname - let unforgeable = info.tabUnforgeable - let staticfuns = info.tabStatic - let global = asglobal and not globalparent - endstmts.add(quote do: - `ctx`.newJSClass(`classDef`, `tname`, getTypePtr(`t`), `sctr`, `tabList`, - `parent`, bool(`global`), `nointerface`, `finName`, `namespace`, - `errid`, `unforgeable`, `staticfuns`, `ishtmldda`) - ) - stmts.add(newBlockStmt(endstmts)) - return stmts - -proc getMemoryUsage*(rt: JSRuntime): string = - var m: JSMemoryUsage - JS_ComputeMemoryUsage(rt, addr m) - template row(title: string; count, size, sz2, cnt2: int64, name: string): - string = - let fv0 = $(float(sz2) / float(cnt2)) - var fv = fv0.until('.') - if fv.len < fv0.len: - fv &= '.' & fv0[fv.len + 1] - else: - fv &= ".0" - title & ": " & $count & " " & $size & " (" & fv & ")" & name & "\n" - template row(title: string; count, size, sz2: int64, name: string): - string = - row(title, count, size, sz2, count, name) - template row(title: string; count, size: int64, name: string): string = - row(title, count, size, size, name) - var s = "" - if m.malloc_count != 0: - s &= row("memory allocated", m.malloc_count, m.malloc_size, "/block") - s &= row("memory used", m.memory_used_count, m.memory_used_size, - m.malloc_size - m.memory_used_size, " average slack") - if m.atom_count != 0: - s &= row("atoms", m.atom_count, m.atom_size, "/atom") - if m.str_count != 0: - s &= row("strings", m.str_count, m.str_size, "/string") - if m.obj_count != 0: - s &= row("objects", m.obj_count, m.obj_size, "/object") & - row("properties", m.prop_count, m.prop_size, m.prop_size, m.obj_count, - "/object") & - row("shapes", m.shape_count, m.shape_size, "/shape") - if m.js_func_count != 0: - s &= row("js functions", m.js_func_count, m.js_func_size, "/function") - if m.c_func_count != 0: - s &= "native functions: " & $m.c_func_count & "\n" - if m.array_count != 0: - s &= "arrays: " & $m.array_count & "\n" & - "fast arrays: " & $m.fast_array_count & "\n" & - row("fast array elements", m.fast_array_elements, - m.fast_array_elements * sizeof(JSValue), m.fast_array_elements, - m.fast_array_count, "") - if m.binary_object_count != 0: - s &= "binary objects: " & $m.binary_object_count & " " & - $m.binary_object_size - return s - -proc eval*(ctx: JSContext; s, file: string; evalFlags = JS_EVAL_TYPE_GLOBAL): - JSValue = - return JS_Eval(ctx, cstring(s), csize_t(s.len), cstring(file), - cint(evalFlags)) - -proc compileScript*(ctx: JSContext; s, file: string): JSValue = - return ctx.eval(s, file, JS_EVAL_FLAG_COMPILE_ONLY) - -proc compileModule*(ctx: JSContext; s, file: string): JSValue = - return ctx.eval(s, file, JS_EVAL_TYPE_MODULE or JS_EVAL_FLAG_COMPILE_ONLY) - -proc evalFunction*(ctx: JSContext; val: JSValue): JSValue = - return JS_EvalFunction(ctx, val) diff --git a/src/js/jscolor.nim b/src/js/jscolor.nim index a73fb599..aa6fd8ed 100644 --- a/src/js/jscolor.nim +++ b/src/js/jscolor.nim @@ -1,11 +1,12 @@ import std/strutils -import bindings/quickjs -import js/jserror -import js/fromjs -import js/javascript -import js/tojs +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import monoucha/quickjs +import monoucha/tojs import types/color +import types/opt import utils/charcategory import utils/twtstr diff --git a/src/js/jserror.nim b/src/js/jserror.nim deleted file mode 100644 index b4101830..00000000 --- a/src/js/jserror.nim +++ /dev/null @@ -1,59 +0,0 @@ -import types/opt - -type - JSError* = ref object of RootObj - e*: JSErrorEnum - message*: string - - JSErrorEnum* = enum - # QuickJS internal errors - jeEvalError = "EvalError" - jeRangeError = "RangeError" - jeReferenceError = "ReferenceError" - jeSyntaxError = "SyntaxError" - jeTypeError = "TypeError" - jeURIError = "URIError" - jeInternalError = "InternalError" - jeAggregateError = "AggregateError" - # Chawan errors - jeDOMException = "DOMException" - - JSResult*[T] = Result[T, JSError] - -const QuickJSErrors* = [ - jeEvalError, - jeRangeError, - jeReferenceError, - jeSyntaxError, - jeTypeError, - jeURIError, - jeInternalError, - jeAggregateError -] - -proc newEvalError*(message: string): JSError = - return JSError(e: jeEvalError, message: message) - -proc newRangeError*(message: string): JSError = - return JSError(e: jeRangeError, message: message) - -proc newReferenceError*(message: string): JSError = - return JSError(e: jeReferenceError, message: message) - -proc newSyntaxError*(message: string): JSError = - return JSError(e: jeSyntaxError, message: message) - -proc newTypeError*(message: string): JSError = - return JSError(e: jeTypeError, message: message) - -proc newURIError*(message: string): JSError = - return JSError(e: jeURIError, message: message) - -proc newInternalError*(message: string): JSError = - return JSError(e: jeInternalError, message: message) - -proc newAggregateError*(message: string): JSError = - return JSError(e: jeAggregateError, message: message) - -template errTypeError*(message: string): untyped = - err(newTypeError(message)) diff --git a/src/js/jsmodule.nim b/src/js/jsmodule.nim index 90ad7130..cdb836e6 100644 --- a/src/js/jsmodule.nim +++ b/src/js/jsmodule.nim @@ -1,7 +1,7 @@ -import bindings/constcharp -import bindings/quickjs -import js/javascript -import js/tojs +import monoucha/constcharp +import monoucha/javascript +import monoucha/quickjs +import monoucha/tojs proc setImportMeta(ctx: JSContext; funcVal: JSValue; isMain: bool) = let m = cast[JSModuleDef](JS_VALUE_GET_PTR(funcVal)) diff --git a/src/js/jsopaque.nim b/src/js/jsopaque.nim deleted file mode 100644 index 2ce6bbc1..00000000 --- a/src/js/jsopaque.nim +++ /dev/null @@ -1,128 +0,0 @@ -import std/tables - -import bindings/quickjs -import js/jserror -import types/opt - -type - JSSymbolRef* = enum - jsyIterator = "iterator" - jsyAsyncIterator = "asyncIterator" - jsyToStringTag = "toStringTag" - - JSStrRef* = enum - jstDone = "done" - jstValue = "value" - jstNext = "next" - jstPrototype = "prototype" - jstThen = "then" - - JSValueRef* = enum - jsvArrayPrototypeValues = "Array.prototype.values" - jsvUint8Array = "Uint8Array" - jsvObjectPrototypeValueOf = "Object.prototype.valueOf" - jsvSet = "Set" - jsvFunction = "Function" - - JSContextOpaque* = ref object - creg*: Table[string, JSClassID] - typemap*: Table[pointer, JSClassID] - ctors*: Table[JSClassID, JSValue] - parents*: Table[JSClassID, JSClassID] - # Parent unforgeables are merged on class creation. - # (i.e. to set all unforgeables on the prototype chain, it is enough to set) - # `unforgeable[classid]'.) - unforgeable*: Table[JSClassID, seq[JSCFunctionListEntry]] - gclaz*: string - gparent*: JSClassID - symRefs*: array[JSSymbolRef, JSAtom] - strRefs*: array[JSStrRef, JSAtom] - valRefs*: array[JSValueRef, JSValue] - errCtorRefs*: array[JSErrorEnum, JSValue] - htmldda*: JSClassID # only one of these exists: document.all. - globalUnref*: proc() {.closure.} - - JSFinalizerFunction* = proc(rt: JSRuntime; val: JSValue) {.nimcall, - raises: [].} - - JSRuntimeOpaque* = ref object - plist*: Table[pointer, pointer] # Nim, JS - flist*: seq[seq[JSCFunctionListEntry]] - fins*: Table[JSClassID, JSFinalizerFunction] - refmap*: Table[pointer, tuple[cref, cunref: (proc() {.closure.})]] - destroying*: pointer - -func newJSContextOpaque*(ctx: JSContext): JSContextOpaque = - let opaque = JSContextOpaque() - block: # get well-known symbols and other functions - let global = JS_GetGlobalObject(ctx) - let sym = JS_GetPropertyStr(ctx, global, "Symbol") - for s in JSSymbolRef: - let name = $s - let val = JS_GetPropertyStr(ctx, sym, cstring(name)) - assert JS_IsSymbol(val) - opaque.symRefs[s] = JS_ValueToAtom(ctx, val) - JS_FreeValue(ctx, val) - JS_FreeValue(ctx, sym) - for s in JSStrRef: - let ss = $s - opaque.strRefs[s] = JS_NewAtomLen(ctx, cstring(ss), csize_t(ss.len)) - for s in JSValueRef: - let ss = $s - let ret = JS_Eval(ctx, cstring(ss), csize_t(ss.len), "<init>", 0) - assert JS_IsFunction(ctx, ret) - opaque.valRefs[s] = ret - for e in JSErrorEnum: - let s = $e - let err = JS_GetPropertyStr(ctx, global, cstring(s)) - opaque.errCtorRefs[e] = err - JS_FreeValue(ctx, global) - return opaque - -func getOpaque*(ctx: JSContext): JSContextOpaque = - return cast[JSContextOpaque](JS_GetContextOpaque(ctx)) - -func getOpaque*(rt: JSRuntime): JSRuntimeOpaque = - return cast[JSRuntimeOpaque](JS_GetRuntimeOpaque(rt)) - -func isGlobal*(ctx: JSContext; class: string): bool = - assert class != "" - return ctx.getOpaque().gclaz == class - -proc setOpaque*(ctx: JSContext; val: JSValue; opaque: pointer) = - let rt = JS_GetRuntime(ctx) - let rtOpaque = rt.getOpaque() - let p = JS_VALUE_GET_PTR(val) - rtOpaque.plist[opaque] = p - JS_SetOpaque(val, opaque) - -# getOpaque, but doesn't work for global objects. -func getOpaque0*(val: JSValue): pointer = - if JS_VALUE_GET_TAG(val) == JS_TAG_OBJECT: - return JS_GetOpaque(val, JS_GetClassID(val)) - -func getGlobalOpaque0*(ctx: JSContext; val = JS_UNDEFINED): Opt[pointer] = - let global = JS_GetGlobalObject(ctx) - if JS_IsUndefined(val) or val == global: - let opaque = JS_GetOpaque(global, JS_CLASS_OBJECT) - JS_FreeValue(ctx, global) - return ok(opaque) - JS_FreeValue(ctx, global) - return err() - -func getGlobalOpaque*(ctx: JSContext; T: typedesc; val = JS_UNDEFINED): Opt[T] = - return ok(cast[T](?getGlobalOpaque0(ctx, val))) - -func getOpaque*(ctx: JSContext; val: JSValue; class: string): pointer = - # Unfortunately, we can't change the global object's class. - #TODO: or maybe we can, but I'm afraid of breaking something. - # This needs further investigation. - if ctx.isGlobal(class): - let global = JS_GetGlobalObject(ctx) - let opaque = JS_GetOpaque(global, JS_CLASS_OBJECT) - JS_FreeValue(ctx, global) - return opaque - return getOpaque0(val) - -func getOpaque*[T: ref object](ctx: JSContext; val: JSValue): T = - cast[T](getOpaque(ctx, val, $T)) diff --git a/src/js/jspropenumlist.nim b/src/js/jspropenumlist.nim deleted file mode 100644 index 65c16c2f..00000000 --- a/src/js/jspropenumlist.nim +++ /dev/null @@ -1,42 +0,0 @@ -import bindings/quickjs - -type - JSPropertyEnumArray* = ptr UncheckedArray[JSPropertyEnum] - - JSPropertyEnumList* = object - buffer*: JSPropertyEnumArray - size: uint32 - len*: uint32 - ctx: JSContext - - JSPropertyEnumWrapper* = object - is_enumerable: bool - name: string - -func newJSPropertyEnumList*(ctx: JSContext; size: uint32): JSPropertyEnumList = - let p = js_malloc(ctx, csize_t(sizeof(JSPropertyEnum)) * csize_t(size)) - let buffer = cast[JSPropertyEnumArray](p) - return JSPropertyEnumList( - ctx: ctx, - buffer: buffer, - size: size - ) - -proc grow(this: var JSPropertyEnumList) = - this.size *= 2 - let p = js_realloc(this.ctx, this.buffer, csize_t(this.size)) - this.buffer = cast[JSPropertyEnumArray](p) - -proc add*(this: var JSPropertyEnumList; val: uint32) = - let i = this.len - inc this.len - if this.size < this.len: - this.grow() - this.buffer[i].atom = JS_NewAtomUInt32(this.ctx, val) - -proc add*(this: var JSPropertyEnumList; val: string) = - let i = this.len - inc this.len - if this.size < this.len: - this.grow() - this.buffer[i].atom = JS_NewAtomLen(this.ctx, cstring(val), csize_t(val.len)) diff --git a/src/js/jsregex.nim b/src/js/jsregex.nim deleted file mode 100644 index 9fb09872..00000000 --- a/src/js/jsregex.nim +++ /dev/null @@ -1,150 +0,0 @@ -# Interface for QuickJS libregexp. -import std/unicode - -import bindings/libregexp -import types/opt -import utils/twtstr - -export LREFlags - -type - Regex* = object - bytecode: seq[uint8] - when defined(debug): - buf: string - - RegexCapture* = tuple # start, end - s, e: int - - RegexResult* = object - success*: bool - captures*: seq[seq[RegexCapture]] - -when defined(debug): - func `$`*(regex: Regex): string = - regex.buf - -# this is hardcoded into quickjs, so we must override it here. -proc lre_realloc(opaque, p: pointer; size: csize_t): pointer {.exportc.} = - return realloc(p, size) - -proc compileRegex*(buf: string; flags: LREFlags = {}): Result[Regex, string] = - var errorMsg = newString(64) - var plen: cint - let bytecode = lre_compile(addr plen, cstring(errorMsg), cint(errorMsg.len), - cstring(buf), csize_t(buf.len), flags.toCInt, nil) - if bytecode == nil: - return err(errorMsg.until('\0')) # Failed to compile. - assert plen > 0 - var bcseq = newSeqUninitialized[uint8](plen) - copyMem(addr bcseq[0], bytecode, plen) - dealloc(bytecode) - var regex = Regex(bytecode: bcseq) - when defined(debug): - regex.buf = buf - return ok(regex) - -func countBackslashes(buf: string; i: int): int = - var j = 0 - for i in countdown(i, 0): - if buf[i] != '\\': - break - inc j - return j - -# ^abcd -> ^abcd -# efgh$ -> efgh$ -# ^ijkl$ -> ^ijkl$ -# mnop -> ^mnop$ -proc compileMatchRegex*(buf: string): Result[Regex, string] = - if buf.len == 0: - return compileRegex(buf) - if buf[0] == '^': - return compileRegex(buf) - if buf[^1] == '$': - # Check whether the final dollar sign is escaped. - if buf.len == 1 or buf[^2] != '\\': - return compileRegex(buf) - let j = buf.countBackslashes(buf.high - 2) - if j mod 2 == 1: # odd, because we do not count the last backslash - return compileRegex(buf) - # escaped. proceed as if no dollar sign was at the end - if buf[^1] == '\\': - # Check if the regex contains an invalid trailing backslash. - let j = buf.countBackslashes(buf.high - 1) - if j mod 2 != 1: # odd, because we do not count the last backslash - return err("unexpected end") - var buf2 = "^" - buf2 &= buf - buf2 &= "$" - return compileRegex(buf2) - -proc compileSearchRegex*(str: string; defaultFlags: LREFlags): - Result[Regex, string] = - # Emulate vim's \c/\C: override defaultFlags if one is found, then remove it - # from str. - # Also, replace \< and \> with \b as (a bit sloppy) vi emulation. - var flags = defaultFlags - var s = newStringOfCap(str.len) - var quot = false - for c in str: - if quot: - quot = false - case c - of 'c': flags.incl(LRE_FLAG_IGNORECASE) - of 'C': flags.excl(LRE_FLAG_IGNORECASE) - of '<', '>': s &= "\\b" - else: s &= '\\' & c - elif c == '\\': - quot = true - else: - s &= c - if quot: - s &= '\\' - flags.incl(LRE_FLAG_GLOBAL) # for easy backwards matching - return compileRegex(s, flags) - -proc exec*(regex: Regex; str: string; start = 0; length = -1; nocaps = false): - RegexResult = - let length = if length == -1: - str.len - else: - length - assert start in 0 .. length - let bytecode = unsafeAddr regex.bytecode[0] - let captureCount = lre_get_capture_count(bytecode) - var capture: ptr UncheckedArray[int] = nil - if captureCount > 0: - let size = sizeof(ptr uint8) * captureCount * 2 - capture = cast[ptr UncheckedArray[int]](alloc0(size)) - var cstr = cstring(str) - let flags = lre_get_flags(bytecode).toLREFlags - var start = start - while true: - let ret = lre_exec(cast[ptr ptr uint8](capture), bytecode, - cast[ptr uint8](cstr), cint(start), cint(length), cint(3), nil) - if ret != 1: #TODO error handling? (-1) - break - result.success = true - if captureCount == 0 or nocaps: - break - var caps: seq[RegexCapture] = @[] - let cstrAddress = cast[int](cstr) - let ps = start - start = capture[1] - cstrAddress - for i in 0 ..< captureCount: - let s = capture[i * 2] - cstrAddress - let e = capture[i * 2 + 1] - cstrAddress - caps.add((s, e)) - result.captures.add(caps) - if LRE_FLAG_GLOBAL notin flags: - break - if start >= str.len: - break - if ps == start: - start += runeLenAt(str, start) - if captureCount > 0: - dealloc(capture) - -proc match*(regex: Regex; str: string; start = 0; length = str.len): bool = - return regex.exec(str, start, length, nocaps = true).success diff --git a/src/js/jstypes.nim b/src/js/jstypes.nim deleted file mode 100644 index 9e1d72ea..00000000 --- a/src/js/jstypes.nim +++ /dev/null @@ -1,43 +0,0 @@ -import bindings/quickjs - -# This is the WebIDL dictionary type. -# We only use it for type inference in generics. -#TODO required members -type JSDict* = object of RootObj - -# Containers compatible with the internal representation of strings in QuickJS. -# To convert these, a copy is still needed; however, they remove the UTF-8 -# transcoding step. -type - NarrowString* = distinct string - WideString* = distinct seq[uint16] - -# Various containers for array buffer types. -# Converting these only requires copying the metadata; buffers are never copied. -type - JSArrayBuffer* = object - p*: ptr UncheckedArray[uint8] - len*: csize_t - dealloc*: JSFreeArrayBufferDataFunc - - JSArrayBufferView* = object - abuf*: JSArrayBuffer - offset*: csize_t # offset into the buffer - nmemb*: csize_t # number of members - nsize*: csize_t # member size - - JSUint8Array* = object - abuf*: JSArrayBuffer - offset*: csize_t # offset into the buffer - nmemb*: csize_t # number of members - -func high*(abuf: JSArrayBuffer): int = - return int(abuf.len) - 1 - -# A specialization of JSValue to make writing generic code for functions -# easier. -type JSValueFunction* = ref object - fun*: JSValue - -converter toJSValue*(f: JSValueFunction): JSValue = - f.fun diff --git a/src/js/jsutils.nim b/src/js/jsutils.nim deleted file mode 100644 index b8a8398e..00000000 --- a/src/js/jsutils.nim +++ /dev/null @@ -1,9 +0,0 @@ -import bindings/quickjs - -template toJSValueArray*(a: openArray[JSValue]): ptr UncheckedArray[JSValue] = - cast[ptr UncheckedArray[JSValue]](unsafeAddr a[0]) - -# Warning: this must be a template, because we're taking the address of -# the passed value, and Nim is pass-by-value. -template toJSValueArray*(a: JSValue): ptr UncheckedArray[JSValue] = - cast[ptr UncheckedArray[JSValue]](unsafeAddr a) diff --git a/src/js/timeout.nim b/src/js/timeout.nim index 3c95dada..9c1b0dba 100644 --- a/src/js/timeout.nim +++ b/src/js/timeout.nim @@ -2,7 +2,7 @@ import std/selectors import std/tables import io/dynstream -import js/javascript +import monoucha/javascript type TimeoutState* = object timeoutid: int32 diff --git a/src/js/tojs.nim b/src/js/tojs.nim deleted file mode 100644 index 59109884..00000000 --- a/src/js/tojs.nim +++ /dev/null @@ -1,399 +0,0 @@ -# Automatic conversion of Nim types to JavaScript types. -# -# Every conversion involves copying unless explicitly noted below. -# -# * Primitives are converted to their respective JavaScript counterparts. -# * seq is converted to a JS array. Note: this always copies the seq's contents. -# * enum is converted to its stringifier's output. -# * JSValue is returned as-is, *without* a DupValue operation. -# * JSError is converted to a new error object corresponding to the error -# it represents. -# * JSArrayBuffer, JSUint8Array are converted to a JS object without copying -# their contents. -# * NarrowString is converted to a JS narrow string (with copying). For more -# information on JS string handling, see js/jstypes.nim. -# * Finally, ref object is converted to a JS object whose opaque is the ref -# object. (See below.) -# -# Note that ref objects can be seamlessly converted to JS objects, despite -# the fact that they are managed by two separate garbage collectors. This -# works thanks to a patch in QJS and machine oil. Basically: -# -# * Nim objects registered with registerType can be paired with one (1) -# JS object each. -# * This happens on-demand, whenever the Nim object has to be converted into JS. -# * Once the conversion happened, the JS object will be kept alive until the -# Nim object is destroyed, so that JS properties on the JS object are not -# lost during a re-conversion. -# * Similarly, the Nim object is kept alive so long as the JS object is alive. -# * The patched in can_destroy hook is used to synchronize reference counts -# of the two objects; this way, no memory leak occurs. -# -# There are also toJSP variants of object converters. These work identically -# to ref object converters, except the reference count of the closest -# `ref object' ancestor is incremented/decremented when synchronizing refcounts -# with the JS object pair. - -import std/options -import std/tables -import std/unicode - -import bindings/quickjs -import js/jserror -import js/jstypes -import js/jsutils -import js/jsopaque -import types/opt - -# Convert Nim types to the corresponding JavaScript type. -# This does not work with var objects. -proc toJS*(ctx: JSContext; s: string): JSValue -proc toJS*(ctx: JSContext; r: Rune): JSValue -proc toJS*(ctx: JSContext; n: int64): JSValue -proc toJS*(ctx: JSContext; n: int32): JSValue -proc toJS*(ctx: JSContext; n: int): JSValue -proc toJS*(ctx: JSContext; n: uint16): JSValue -proc toJS*(ctx: JSContext; n: uint32): JSValue -proc toJS*(ctx: JSContext; n: uint64): JSValue -proc toJS*(ctx: JSContext; n: float64): JSValue -proc toJS*(ctx: JSContext; b: bool): JSValue -proc toJS*[U, V](ctx: JSContext; t: Table[U, V]): JSValue -proc toJS*(ctx: JSContext; opt: Option): JSValue -proc toJS*[T, E](ctx: JSContext; opt: Result[T, E]): JSValue -proc toJS*(ctx: JSContext; s: seq): JSValue -proc toJS*[T](ctx: JSContext; s: set[T]): JSValue -proc toJS*(ctx: JSContext; t: tuple): JSValue -proc toJS*(ctx: JSContext; e: enum): JSValue -proc toJS*(ctx: JSContext; j: JSValue): JSValue -proc toJS*(ctx: JSContext; obj: ref object): JSValue -proc toJS*(ctx: JSContext; err: JSError): JSValue -proc toJS*(ctx: JSContext; abuf: JSArrayBuffer): JSValue -proc toJS*(ctx: JSContext; u8a: JSUint8Array): JSValue -proc toJS*(ctx: JSContext; ns: NarrowString): JSValue -proc toJS*(ctx: JSContext; dict: JSDict): JSValue - -# Convert Nim types to the corresponding JavaScript type, with knowledge of -# the parent object. -# This supports conversion of var object types. -# -# The idea here is to allow conversion of var objects to quasi-reference types -# by saving a pointer to their ancestor and incrementing/decrementing the -# ancestor's reference count instead. -proc toJSP*(ctx: JSContext; parent: ref object; child: var object): JSValue -proc toJSP*(ctx: JSContext; parent: ptr object; child: var object): JSValue - -# Same as toJS, but used in constructors. ctor contains the target prototype, -# used for subclassing from JS. -proc toJSNew*(ctx: JSContext; obj: ref object; ctor: JSValue): JSValue -proc toJSNew*[T, E](ctx: JSContext; opt: Result[T, E]; ctor: JSValue): JSValue - -# Avoid accidentally calling toJSP on objects that we have explicit toJS -# converters for. -template makeToJSP(typ: untyped) = - template toJSP*(ctx: JSContext; parent: ref object; child: var typ): JSValue = - toJS(ctx, child) - template toJSP*(ctx: JSContext; parent: ptr object; child: var typ): JSValue = - toJS(ctx, child) -makeToJSP(Table) -makeToJSP(Option) -makeToJSP(Result) -makeToJSP(JSValue) -makeToJSP(JSDict) - -# Note: this consumes `prop'. -proc defineProperty(ctx: JSContext; this: JSValue; name: JSAtom; - prop: JSValue; flags = cint(0)) = - if JS_DefinePropertyValue(ctx, this, name, prop, flags) <= 0: - raise newException(Defect, "Failed to define property string") - -proc defineProperty(ctx: JSContext; this, name, prop: JSValue; - flags = cint(0)) = - let atom = JS_ValueToAtom(ctx, prop); - JS_FreeValue(ctx, prop); - if unlikely(atom == JS_ATOM_NULL): - raise newException(Defect, "Failed to define property string") - ctx.defineProperty(this, atom, prop, flags) - JS_FreeAtom(ctx, atom); - -proc definePropertyC*(ctx: JSContext; this: JSValue; name: JSAtom; - prop: JSValue) = - ctx.defineProperty(this, name, prop, JS_PROP_CONFIGURABLE) - -proc defineProperty(ctx: JSContext; this: JSValue; name: string; - prop: JSValue; flags = cint(0)) = - if JS_DefinePropertyValueStr(ctx, this, cstring(name), prop, flags) <= 0: - raise newException(Defect, "Failed to define property string: " & name) - -proc definePropertyC*(ctx: JSContext; this: JSValue; name: string; - prop: JSValue) = - ctx.defineProperty(this, name, prop, JS_PROP_CONFIGURABLE) - -proc defineProperty*[T](ctx: JSContext; this: JSValue; name: string; prop: T; - flags = cint(0)) = - defineProperty(ctx, this, name, toJS(ctx, prop), flags) - -proc definePropertyE*[T](ctx: JSContext; this: JSValue; name: string; - prop: T) = - defineProperty(ctx, this, name, prop, JS_PROP_ENUMERABLE) - -proc definePropertyCW*[T](ctx: JSContext; this: JSValue; name: string; - prop: T) = - defineProperty(ctx, this, name, prop, JS_PROP_CONFIGURABLE or - JS_PROP_WRITABLE) - -proc definePropertyCWE*[T](ctx: JSContext; this: JSValue; name: string; - prop: T) = - defineProperty(ctx, this, name, prop, JS_PROP_C_W_E) - -proc newFunction*(ctx: JSContext; args: openArray[string]; body: string): - JSValue = - var paramList: seq[JSValue] = @[] - for arg in args: - paramList.add(toJS(ctx, arg)) - paramList.add(toJS(ctx, body)) - let fun = JS_CallConstructor(ctx, ctx.getOpaque().valRefs[jsvFunction], - cint(paramList.len), paramList.toJSValueArray()) - for param in paramList: - JS_FreeValue(ctx, param) - return fun - -proc toJS*(ctx: JSContext; s: cstring): JSValue = - return JS_NewString(ctx, s) - -proc toJS*(ctx: JSContext; s: string): JSValue = - return toJS(ctx, cstring(s)) - -proc toJS*(ctx: JSContext; r: Rune): JSValue = - return toJS(ctx, $r) - -proc toJS*(ctx: JSContext; n: int32): JSValue = - return JS_NewInt32(ctx, n) - -proc toJS*(ctx: JSContext; n: int64): JSValue = - return JS_NewInt64(ctx, n) - -# Always int32, so we don't risk 32-bit only breakage. -proc toJS*(ctx: JSContext; n: int): JSValue = - return toJS(ctx, int32(n)) - -proc toJS*(ctx: JSContext; n: uint16): JSValue = - return JS_NewUint32(ctx, uint32(n)) - -proc toJS*(ctx: JSContext; n: uint32): JSValue = - return JS_NewUint32(ctx, n) - -proc toJS*(ctx: JSContext; n: uint64): JSValue = - #TODO this is incorrect - return JS_NewFloat64(ctx, float64(n)) - -proc toJS*(ctx: JSContext; n: float64): JSValue = - return JS_NewFloat64(ctx, n) - -proc toJS*(ctx: JSContext; b: bool): JSValue = - return JS_NewBool(ctx, b) - -proc toJS*[U, V](ctx: JSContext; t: Table[U, V]): JSValue = - let obj = JS_NewObject(ctx) - if not JS_IsException(obj): - for k, v in t: - definePropertyCWE(ctx, obj, k, v) - return obj - -proc toJS*(ctx: JSContext; opt: Option): JSValue = - if opt.isSome: - return toJS(ctx, opt.get) - return JS_NULL - -proc toJS*[T, E](ctx: JSContext; opt: Result[T, E]): JSValue = - if opt.isSome: - when not (T is void): - return toJS(ctx, opt.get) - else: - return JS_UNDEFINED - else: - when not (E is void): - if opt.error != nil: - return JS_Throw(ctx, toJS(ctx, opt.error)) - return JS_EXCEPTION - -proc toJS*(ctx: JSContext; s: seq): JSValue = - let a = JS_NewArray(ctx) - if not JS_IsException(a): - for i, x in s: - let val = toJS(ctx, x) - if JS_IsException(val): - return val - ctx.defineProperty(a, JS_NewInt64(ctx, int64(i)), val, - JS_PROP_C_W_E or JS_PROP_THROW) - return a - -proc toJS*[T](ctx: JSContext; s: set[T]): JSValue = - #TODO this is a bit lazy :p - var x = newSeq[T]() - for e in s: - x.add(e) - var a = toJS(ctx, x) - if JS_IsException(a): - return a - let ret = JS_CallConstructor(ctx, ctx.getOpaque().valRefs[jsvSet], 1, - a.toJSValueArray()) - JS_FreeValue(ctx, a) - return ret - -proc toJS(ctx: JSContext; t: tuple): JSValue = - let a = JS_NewArray(ctx) - if not JS_IsException(a): - var i = 0 - for f in t.fields: - let val = toJS(ctx, f) - if JS_IsException(val): - return val - ctx.defineProperty(a, JS_NewInt64(ctx, int64(i)), val, - JS_PROP_C_W_E or JS_PROP_THROW) - inc i - return a - -proc toJSP0(ctx: JSContext; p, tp: pointer; ctor: JSValue; - needsref: var bool): JSValue = - JS_GetRuntime(ctx).getOpaque().plist.withValue(p, obj): - # a JSValue already points to this object. - return JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, obj[])) - let ctxOpaque = ctx.getOpaque() - let class = ctxOpaque.typemap[tp] - let jsObj = JS_NewObjectFromCtor(ctx, ctor, class) - if JS_IsException(jsObj): - return jsObj - setOpaque(ctx, jsObj, p) - # We are constructing a new JS object, so we must add unforgeable properties - # here. - ctxOpaque.unforgeable.withValue(class, uf): - JS_SetPropertyFunctionList(ctx, jsObj, addr uf[][0], cint(uf[].len)) - needsref = true - if unlikely(ctxOpaque.htmldda == class): - JS_SetIsHTMLDDA(ctx, jsObj) - return jsObj - -# Get a unique pointer for each type. -proc getTypePtr*[T](x: T): pointer = - when T is RootRef: - # I'm so sorry. - # (This dereferences the object's first member, m_type. Probably.) - return cast[ptr pointer](x)[] - elif T is RootObj: - return cast[pointer](x) - else: - return getTypeInfo(x) - -func getTypePtr*(t: typedesc[ref object]): pointer = - var x = t() - return getTypePtr(x) - -func getTypePtr*(t: type): pointer = - var x: t - return getTypePtr(x) - -proc toJSRefObj(ctx: JSContext; obj: ref object): JSValue = - if obj == nil: - return JS_NULL - let p = cast[pointer](obj) - let tp = getTypePtr(obj) - var needsref = false - let val = toJSP0(ctx, p, tp, JS_UNDEFINED, needsref) - if needsref: - GC_ref(obj) - return val - -proc toJS*(ctx: JSContext; obj: ref object): JSValue = - return toJSRefObj(ctx, obj) - -proc toJSNew*(ctx: JSContext; obj: ref object; ctor: JSValue): JSValue = - if obj == nil: - return JS_NULL - let p = cast[pointer](obj) - let tp = getTypePtr(obj) - var needsref = false - let val = toJSP0(ctx, p, tp, ctor, needsref) - if needsref: - GC_ref(obj) - return val - -proc toJSNew[T, E](ctx: JSContext; opt: Result[T, E]; ctor: JSValue): JSValue = - if opt.isSome: - when not (T is void): - return toJSNew(ctx, opt.get, ctor) - else: - return JS_UNDEFINED - else: - when not (E is void): - let res = toJS(ctx, opt.error) - if not JS_IsNull(res): - return JS_Throw(ctx, res) - else: - return JS_NULL - -proc toJS(ctx: JSContext; e: enum): JSValue = - return toJS(ctx, $e) - -proc toJS(ctx: JSContext; j: JSValue): JSValue = - return j - -proc toJS*(ctx: JSContext; err: JSError): JSValue = - if err == nil: - return JS_EXCEPTION - if err.e notin QuickJSErrors: - return toJSRefObj(ctx, err) - var msg = toJS(ctx, err.message) - if JS_IsException(msg): - return msg - let ctor = ctx.getOpaque().errCtorRefs[err.e] - let ret = JS_CallConstructor(ctx, ctor, 1, msg.toJSValueArray()) - JS_FreeValue(ctx, msg) - return ret - -proc toJS*(ctx: JSContext; abuf: JSArrayBuffer): JSValue = - return JS_NewArrayBuffer(ctx, abuf.p, abuf.len, abuf.dealloc, nil, false) - -proc toJS*(ctx: JSContext; u8a: JSUint8Array): JSValue = - let jsabuf = toJS(ctx, u8a.abuf) - let ctor = ctx.getOpaque().valRefs[jsvUint8Array] - let ret = JS_CallConstructor(ctx, ctor, 1, jsabuf.toJSValueArray()) - JS_FreeValue(ctx, jsabuf) - return ret - -proc toJS*(ctx: JSContext; ns: NarrowString): JSValue = - return JS_NewNarrowStringLen(ctx, cstring(ns), csize_t(string(ns).len)) - -proc toJS*(ctx: JSContext; dict: JSDict): JSValue = - let obj = JS_NewObject(ctx) - if JS_IsException(obj): - return obj - for k, v in dict.fieldPairs: - ctx.defineProperty(obj, k, v) - return obj - -proc toJSP(ctx: JSContext; parent: ref object; child: var object): JSValue = - let p = addr child - # Save parent as the original ancestor for this tree. - JS_GetRuntime(ctx).getOpaque().refmap[p] = ( - (proc() = - GC_ref(parent)), - (proc() = - GC_unref(parent)) - ) - let tp = getTypePtr(child) - var needsref = false - let val = toJSP0(ctx, p, tp, JS_UNDEFINED, needsref) - if needsref: - GC_ref(parent) - return val - -proc toJSP(ctx: JSContext; parent: ptr object; child: var object): JSValue = - let p = addr child - # Increment the reference count of parent's root ancestor, and save the - # increment/decrement callbacks for the child as well. - let rtOpaque = JS_GetRuntime(ctx).getOpaque() - let ru = rtOpaque.refmap[parent] - ru.cref() - rtOpaque.refmap[p] = ru - let tp = getTypePtr(child) - return toJSP0(ctx, p, tp) diff --git a/src/loader/headers.nim b/src/loader/headers.nim index 21900189..ff7f9ed3 100644 --- a/src/loader/headers.nim +++ b/src/loader/headers.nim @@ -1,9 +1,10 @@ import std/tables -import bindings/quickjs -import js/jserror -import js/fromjs -import js/javascript +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import monoucha/quickjs +import types/opt import utils/twtstr type diff --git a/src/loader/loader.nim b/src/loader/loader.nim index 8bc23c16..c84f247a 100644 --- a/src/loader/loader.nim +++ b/src/loader/loader.nim @@ -33,15 +33,16 @@ import io/serversocket import io/socketstream import io/tempfile import io/urlfilter -import js/jserror -import js/javascript import loader/cgi import loader/connecterror import loader/headers import loader/loaderhandle import loader/request import loader/response +import monoucha/javascript +import monoucha/jserror import types/cookie +import types/opt import types/referrer import types/urimethodmap import types/url diff --git a/src/loader/request.nim b/src/loader/request.nim index 1398d25f..fc6c43f5 100644 --- a/src/loader/request.nim +++ b/src/loader/request.nim @@ -2,15 +2,16 @@ import std/options import std/strutils import std/tables -import bindings/quickjs import html/script -import js/fromjs -import js/javascript -import js/jserror -import js/jstypes import loader/headers +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import monoucha/jstypes +import monoucha/quickjs import types/blob import types/formdata +import types/opt import types/referrer import types/url diff --git a/src/loader/response.nim b/src/loader/response.nim index 4cf17c99..ca300957 100644 --- a/src/loader/response.nim +++ b/src/loader/response.nim @@ -1,22 +1,22 @@ import std/strutils import std/tables -import bindings/quickjs +import chagashi/charset +import chagashi/decoder +import chagashi/validator import io/promise import io/socketstream -import js/jserror -import js/javascript import loader/headers import loader/request +import monoucha/javascript +import monoucha/jserror +import monoucha/quickjs import types/blob +import types/opt import types/url import utils/mimeguess import utils/twtstr -import chagashi/charset -import chagashi/decoder -import chagashi/validator - type ResponseType* = enum TYPE_DEFAULT = "default" diff --git a/src/local/client.nim b/src/local/client.nim index a14ce63c..4b81f668 100644 --- a/src/local/client.nim +++ b/src/local/client.nim @@ -9,8 +9,6 @@ import std/strutils import std/tables import std/unicode -import bindings/constcharp -import bindings/quickjs import config/config import html/catom import html/chadombuilder @@ -30,16 +28,9 @@ import js/base64 import js/console import js/domexception import js/encoding -import js/fromjs import js/intl -import js/javascript -import js/jserror import js/jsmodule -import js/jsopaque -import js/jstypes -import js/jsutils import js/timeout -import js/tojs import loader/headers import loader/loader import loader/request @@ -47,6 +38,15 @@ import local/container import local/lineedit import local/pager import local/term +import monoucha/constcharp +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import monoucha/jsopaque +import monoucha/jstypes +import monoucha/jsutils +import monoucha/quickjs +import monoucha/tojs import server/buffer import server/forkserver import types/blob diff --git a/src/local/container.nim b/src/local/container.nim index 00553190..8439b32a 100644 --- a/src/local/container.nim +++ b/src/local/container.nim @@ -3,6 +3,7 @@ import std/net import std/options import std/os import std/posix +import std/tables import std/unicode import config/config @@ -12,18 +13,19 @@ import io/dynstream import io/promise import io/serversocket import io/socketstream -import js/javascript -import js/jsregex -import js/jstypes import layout/renderdocument import loader/headers import loader/loader import loader/request import local/select +import monoucha/javascript +import monoucha/jsregex +import monoucha/jstypes import server/buffer import types/cell import types/color import types/cookie +import types/opt import types/referrer import types/url import types/winattrs diff --git a/src/local/lineedit.nim b/src/local/lineedit.nim index 04507de7..22fd3988 100644 --- a/src/local/lineedit.nim +++ b/src/local/lineedit.nim @@ -1,8 +1,8 @@ import std/strutils import std/unicode -import bindings/quickjs -import js/javascript +import monoucha/quickjs +import monoucha/javascript import types/cell import types/opt import types/winattrs diff --git a/src/local/pager.nim b/src/local/pager.nim index 367802d9..8eb42b65 100644 --- a/src/local/pager.nim +++ b/src/local/pager.nim @@ -8,8 +8,6 @@ import std/selectors import std/tables import std/unicode -import bindings/libregexp -import bindings/quickjs import config/chapath import config/config import config/mailcap @@ -21,13 +19,6 @@ import io/socketstream import io/stdio import io/tempfile import io/urlfilter -import js/fromjs -import js/javascript -import js/jserror -import js/jsregex -import js/jstypes -import js/jsutils -import js/tojs import loader/connecterror import loader/headers import loader/loader @@ -36,6 +27,15 @@ import local/container import local/lineedit import local/select import local/term +import monoucha/fromjs +import monoucha/javascript +import monoucha/jserror +import monoucha/jsregex +import monoucha/jstypes +import monoucha/jsutils +import monoucha/libregexp +import monoucha/quickjs +import monoucha/tojs import server/buffer import server/forkserver import types/cell @@ -46,6 +46,7 @@ import types/url import types/winattrs import utils/luwrap import utils/mimeguess +import utils/regexutils import utils/strwidth import utils/twtstr diff --git a/src/local/select.nim b/src/local/select.nim index 997eff1c..9e2f2c22 100644 --- a/src/local/select.nim +++ b/src/local/select.nim @@ -1,6 +1,6 @@ import std/unicode -import js/jsregex +import monoucha/jsregex import server/buffer import types/cell import utils/luwrap diff --git a/src/main.nim b/src/main.nim index 808eff00..e070597f 100644 --- a/src/main.nim +++ b/src/main.nim @@ -1,21 +1,20 @@ import version -# Note: we can't just import std/os or the compiler cries. (No idea why.) -from std/os import getEnv, putEnv, commandLineParams, getCurrentDir import std/options +import std/os import std/posix -import server/forkserver +import chagashi/charset import config/chapath import config/config -import js/javascript import local/client import local/term +import monoucha/javascript +import server/forkserver +import types/opt import utils/strwidth import utils/twtstr -import chagashi/charset - const ChaVersionStr = block: var s = "Chawan browser v0.1 " when defined(debug): diff --git a/src/server/buffer.nim b/src/server/buffer.nim index 9a141135..e471c1ae 100644 --- a/src/server/buffer.nim +++ b/src/server/buffer.nim @@ -10,8 +10,6 @@ import std/selectors import std/tables import std/unicode -import bindings/libregexp -import bindings/quickjs import config/config import css/cascade import css/cssparser @@ -37,14 +35,16 @@ import io/promise import io/serversocket import io/socketstream import js/console -import js/fromjs -import js/javascript -import js/jsregex import js/timeout -import js/tojs import layout/renderdocument import loader/headers import loader/loader +import monoucha/fromjs +import monoucha/javascript +import monoucha/jsregex +import monoucha/libregexp +import monoucha/quickjs +import monoucha/tojs import types/blob import types/cell import types/color diff --git a/src/types/blob.nim b/src/types/blob.nim index dd812c02..ce5311ea 100644 --- a/src/types/blob.nim +++ b/src/types/blob.nim @@ -2,9 +2,9 @@ import std/options import std/posix import std/strutils -import js/fromjs -import js/javascript -import js/jstypes +import monoucha/fromjs +import monoucha/javascript +import monoucha/jstypes import utils/mimeguess type diff --git a/src/types/cookie.nim b/src/types/cookie.nim index d1302710..2cc5928c 100644 --- a/src/types/cookie.nim +++ b/src/types/cookie.nim @@ -2,9 +2,9 @@ import std/strutils import std/times import io/urlfilter -import js/jserror -import js/javascript -import js/jsregex +import monoucha/jserror +import monoucha/javascript +import monoucha/jsregex import types/url import types/opt import utils/twtstr diff --git a/src/types/formdata.nim b/src/types/formdata.nim index ec3d8bbf..26fd7975 100644 --- a/src/types/formdata.nim +++ b/src/types/formdata.nim @@ -2,7 +2,7 @@ import std/strutils import io/dynstream import io/posixstream -import js/javascript +import monoucha/javascript import types/blob import utils/twtstr diff --git a/src/types/url.nim b/src/types/url.nim index 075e3b08..627af4aa 100644 --- a/src/types/url.nim +++ b/src/types/url.nim @@ -5,11 +5,12 @@ import std/strutils import std/tables import std/unicode -import bindings/libunicode -import js/jserror -import js/javascript import lib/punycode +import monoucha/javascript +import monoucha/jserror +import monoucha/libunicode import types/blob +import types/opt import utils/luwrap import utils/map import utils/twtstr diff --git a/src/utils/luwrap.nim b/src/utils/luwrap.nim index 853d3015..7ccb2c51 100644 --- a/src/utils/luwrap.nim +++ b/src/utils/luwrap.nim @@ -2,7 +2,7 @@ import std/algorithm import std/strutils import std/unicode -import bindings/libunicode +import monoucha/libunicode import utils/charcategory proc passRealloc(opaque, p: pointer; size: csize_t): pointer {.cdecl.} = @@ -50,7 +50,8 @@ proc capitalizeLU*(s: string): string = result = newStringOfCap(s.len) var wordStart = true for r in s.runes: - if lre_is_space(uint32(r)) == 1: + if uint32(r) < 256 and char(r) in AsciiWhitespace or + lre_is_space_non_ascii(uint32(r)) == 1: wordStart = true result &= $r elif wordStart: diff --git a/src/utils/regexutils.nim b/src/utils/regexutils.nim new file mode 100644 index 00000000..ab7308f6 --- /dev/null +++ b/src/utils/regexutils.nim @@ -0,0 +1,64 @@ +import types/opt + +import monoucha/jsregex +import monoucha/libregexp + +func countBackslashes(buf: string; i: int): int = + var j = 0 + for i in countdown(i, 0): + if buf[i] != '\\': + break + inc j + return j + +# ^abcd -> ^abcd +# efgh$ -> efgh$ +# ^ijkl$ -> ^ijkl$ +# mnop -> ^mnop$ +proc compileMatchRegex*(buf: string): Result[Regex, string] = + if buf.len == 0: + return compileRegex(buf) + if buf[0] == '^': + return compileRegex(buf) + if buf[^1] == '$': + # Check whether the final dollar sign is escaped. + if buf.len == 1 or buf[^2] != '\\': + return compileRegex(buf) + let j = buf.countBackslashes(buf.high - 2) + if j mod 2 == 1: # odd, because we do not count the last backslash + return compileRegex(buf) + # escaped. proceed as if no dollar sign was at the end + if buf[^1] == '\\': + # Check if the regex contains an invalid trailing backslash. + let j = buf.countBackslashes(buf.high - 1) + if j mod 2 != 1: # odd, because we do not count the last backslash + return err("unexpected end") + var buf2 = "^" + buf2 &= buf + buf2 &= "$" + return compileRegex(buf2) + +proc compileSearchRegex*(str: string; defaultFlags: LREFlags): + Result[Regex, string] = + # Emulate vim's \c/\C: override defaultFlags if one is found, then remove it + # from str. + # Also, replace \< and \> with \b as (a bit sloppy) vi emulation. + var flags = defaultFlags + var s = newStringOfCap(str.len) + var quot = false + for c in str: + if quot: + quot = false + case c + of 'c': flags.incl(LRE_FLAG_IGNORECASE) + of 'C': flags.excl(LRE_FLAG_IGNORECASE) + of '<', '>': s &= "\\b" + else: s &= '\\' & c + elif c == '\\': + quot = true + else: + s &= c + if quot: + s &= '\\' + flags.incl(LRE_FLAG_GLOBAL) # for easy backwards matching + return compileRegex(s, flags) diff --git a/src/version.nim b/src/version.nim index 66e81a9f..c1c90c54 100644 --- a/src/version.nim +++ b/src/version.nim @@ -24,7 +24,9 @@ macro checkVersion(xs: static string; major, minor, patch: int) = tryImport chagashi/version, "chagashi" tryImport chame/version, "chame" +tryImport monoucha/version, "monoucha" static: checkVersion("chagashi", 0, 4, 2) checkVersion("chame", 0, 14, 5) + checkVersion("monoucha", 0, 1, 1) |