about summary refs log tree commit diff stats
path: root/src/js/jsopaque.nim
blob: 2ce6bbc12a73749b0fe412c358780f161516b9af (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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))
s="w"> is_cjk_ambiguous = b {.push boundChecks:off.} func contains(props: PropertyTable, r: Rune): bool = let u = int(r) let i = u div (sizeof(int) * 8) let m = u mod (sizeof(int) * 8) return (props[i] and (1 shl m)) != 0 {.pop.} # Warning: this shouldn't be called without normalization. # We could make this function more efficient in edge cases, but it's already # too complex for my taste. func width*(r: Rune): int = {.cast(noSideEffect).}: let u = uint32(r) if u <= 0xFFFF: if r in CombiningTable: return 0 if not is_cjk_ambiguous: if r in DoubleWidthTable: return 2 else: if r in DoubleWidthTable or DoubleWidthAmbiguousRanges.isInRange(u): return 2 else: if r.isCombining(): return 0 if not is_cjk_ambiguous: if r.isDoubleWidthHigh(): return 2 else: if r.isDoubleWidthAmbiguousHigh(): return 2 return 1 # Width, but also works with tabs. # Needs the column width of the text so far. func twidth*(r: Rune, w: int): int = if r != Rune('\t'): return r.width() return ((w div 8) + 1) * 8 - w func width*(s: string): int = for r in s.runes(): result += r.twidth(result) func width*(s: string, start, len: int): int = var i = start var m = len if m > s.len: m = s.len while i < m: var r: Rune fastRuneAt(s, i, r) result += r.twidth(result) func notwidth*(s: string): int = for r in s.runes: result += r.width() func twidth*(s: string, w: int): int = var i = w for r in s.runes(): i += r.twidth(w) return i - w func padToWidth*(str: string, size: int, schar = '$'): string = if str.width() < size: return str & ' '.repeat(size - str.width()) else: let size = size - 1 result = newStringOfCap(str.len) var w = 0 var i = 0 while i < str.len: var r: Rune fastRuneAt(str, i, r) if w + r.width <= size: result &= r w += r.width result &= schar func breaksWord*(r: Rune): bool = return not (r.isDigitAscii() or r.width() == 0 or r.isAlpha()) type BoundaryFunction* = proc(x: Rune): JSResult[bool] proc breaksWord*(r: Rune, check: Opt[BoundaryFunction]): bool = if check.isSome: let f = check.get() let v = f(r) if v.isSome: #TODO report error? return v.get() return r.breaksWord()