about summary refs log tree commit diff stats
path: root/src/js
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-07-22 19:52:31 +0200
committerbptato <nincsnevem662@gmail.com>2022-07-22 19:52:31 +0200
commit6f7bcc54ab03bc31be309352c73fd8d8153f9c91 (patch)
treedc4fd8a80ccc8a5a8f7c5a567fcbf80c3e66eccb /src/js
parentc69a8ab7576e2053afc5dfcee5c7152a07c31230 (diff)
downloadchawan-6f7bcc54ab03bc31be309352c73fd8d8153f9c91.tar.gz
Add search function
Uses libregexp from QuickJS. Incremental search is quite hacky for now,
but overall it seems to work OK.
Diffstat (limited to 'src/js')
-rw-r--r--src/js/javascript.nim14
-rw-r--r--src/js/regex.nim105
2 files changed, 117 insertions, 2 deletions
diff --git a/src/js/javascript.nim b/src/js/javascript.nim
index 2518f252..95e37110 100644
--- a/src/js/javascript.nim
+++ b/src/js/javascript.nim
@@ -35,6 +35,9 @@ proc newJSContext*(rt: JSRuntime): JSContext =
   opaque.err = ""
   JS_SetContextOpaque(result, opaque)
 
+proc newJSContextRaw*(rt: JSRuntime): JSContext =
+  result = JS_NewContextRaw(rt)
+
 func getJSObject*(ctx: JSContext, v: JSValue): JSObject =
   result.ctx = ctx
   result.val = v
@@ -94,14 +97,21 @@ proc setFunctionProperty*(obj: JSObject, name: string, fun: JSCFunction) =
   #    return fun(JSObject(ctx: ctx, qjs: obj), invoc)
   #), cstring(name), 1))
 
-proc free*(ctx: JSContext) =
+proc free*(ctx: var JSContext) =
+  eprint "free"
+  let opaque = ctx.getOpaque()
+  if opaque != nil:
+    dealloc(opaque)
   JS_FreeContext(ctx)
+  ctx = nil
 
-proc free*(rt: JSRuntime) =
+proc free*(rt: var JSRuntime) =
   JS_FreeRuntime(rt)
+  rt = nil
 
 proc free*(obj: JSObject) =
   JS_FreeValue(obj.ctx, obj.val)
+  #TODO maybe? obj.val = JS_NULL
 
 proc eval*(ctx: JSContext, s: string, file: string, eval_flags: int): JSObject =
   result.ctx = ctx
diff --git a/src/js/regex.nim b/src/js/regex.nim
new file mode 100644
index 00000000..a1bd7d35
--- /dev/null
+++ b/src/js/regex.nim
@@ -0,0 +1,105 @@
+# Interface for QuickJS libregexp.
+
+import options
+
+import bindings/libregexp
+import bindings/quickjs
+import js/javascript
+
+export
+  LRE_FLAG_GLOBAL,
+  LRE_FLAG_IGNORECASE,
+  LRE_FLAG_MULTILINE,
+  LRE_FLAG_DOTALL,
+  LRE_FLAG_UTF16,
+  LRE_FLAG_STICKY
+
+type
+  Regex* = object
+    bytecode*: ptr uint8
+
+  RegexResult* = object
+    success*: bool
+    captures*: seq[tuple[s, e: int]] # start, end
+
+var dummyRuntime = newJSRuntime()
+var dummyContext = dummyRuntime.newJSContextRaw()
+
+proc `=destroy`(regex: var Regex) =
+  if regex.bytecode != nil:
+    dummyRuntime.js_free_rt(regex.bytecode)
+    regex.bytecode = nil
+
+proc compileRegex*(buf: string, flags: int): Option[Regex] =
+  var regex: Regex
+  var len: cint
+  var error_msg_size = 64
+  var error_msg = cast[cstring](alloc0(error_msg_size))
+  let bytecode = lre_compile(addr len, error_msg, cint(error_msg_size), cstring(buf), csize_t(buf.len), cint(flags), dummyContext)
+  if error_msg != nil:
+    #TODO error handling?
+    dealloc(error_msg)
+    error_msg = nil
+  if bytecode == nil:
+    return none(Regex) # Failed to compile.
+  regex.bytecode = bytecode
+  return some(regex)
+
+proc compileSearchRegex*(str: string): Option[Regex] =
+  # Parse any applicable flags in regex/<flags>. The last forward slash is
+  # dropped when <flags> is empty, and interpreted as a character when the
+  # flags are is invalid.
+
+  var i = str.high
+  var flagsi = -1
+  while i >= 0:
+    case str[i]
+    of '/':
+      if i > 0 and str[i - 1] == '\\': break # escaped
+      flagsi = i
+      break
+    of 'i', 'm', 's': discard
+    else: break # invalid flag
+    dec i
+
+  var flags = LRE_FLAG_GLOBAL # for easy backwards matching
+
+  if flagsi == -1:
+    return compileRegex(str, flags)
+
+  for i in flagsi..str.high:
+    case str[i]
+    of '/': discard
+    of 'i': flags = flags or LRE_FLAG_IGNORECASE
+    of 'm': flags = flags or LRE_FLAG_MULTILINE
+    of 's': flags = flags or LRE_FLAG_DOTALL
+    else: assert false
+  return compileRegex(str.substr(0, flagsi - 1), flags)
+
+proc exec*(regex: Regex, str: string, start = 0): RegexResult =
+  assert 0 <= start and start <= str.len
+  let cstr = cstring(str)
+  let captureCount = lre_get_capture_count(cast[ptr uint8](regex.bytecode))
+  var capture: ptr ptr uint8 = nil
+  if captureCount > 0:
+    capture = cast[ptr ptr uint8](alloc0(sizeof(ptr uint8) * captureCount * 2))
+  let ret = lre_exec(capture, regex.bytecode,
+                     cast[ptr uint8](cstr), cint(start),
+                     cint(str.len), cint(0), dummyContext)
+  result.success = ret == 1 #TODO error handling? (-1)
+  if result.success:
+    var i = 0
+    let cstrAddress = cast[int](cstr)
+    while i < captureCount * sizeof(ptr uint8):
+      let startPointerAddress = cast[int](capture) + i
+      i += sizeof(ptr uint8)
+      let endPointerAddress = cast[int](capture) + i
+      i += sizeof(ptr uint8)
+      let startPointer = cast[ptr ptr uint8](startPointerAddress)
+      let endPointer = cast[ptr ptr uint8](endPointerAddress)
+      let startAddress = cast[int](startPointer[])
+      let endAddress = cast[int](endPointer[])
+      let s = startAddress - cstrAddress
+      let e = endAddress - cstrAddress
+      result.captures.add((s, e))
+  dealloc(capture)