Skip to content

Instantly share code, notes, and snippets.

@mjendrusch
Created October 13, 2016 21:27
Show Gist options
  • Save mjendrusch/8d059d9bbbc7a407ed56aed8d8e48c50 to your computer and use it in GitHub Desktop.
Save mjendrusch/8d059d9bbbc7a407ed56aed8d8e48c50 to your computer and use it in GitHub Desktop.
JsObject type for quick and dirty interfacing to JavaScript, with partial type inference
# A fully dynamic type for ad hoc interfacing with JavaScript
import macros
import tables
import typeinfo
import typetraits
{. experimental .}
# Hack for type inference:
# Build a Table of (JsObject instance to (field to type)),
# which updates at each evaluation of `.=`.
static:
var tab: Table[string, Table[string, cstring]] =
initTable[string, Table[string, cstring]]()
type
JsObject* = ref object of RootObj
proc hasOwnProperty*(obj: JsObject; name: cstring): bool
{. importcpp: "#.hasOwnProperty(#)" .}
macro stringifyIdent(x: typed): string =
$x
macro stringToIdent(x: static[cstring]): untyped =
result = newIdentNode(!($x))
proc extractImpl(obj: JsObject; name: cstring): JsObject {.importcpp: "#[#]".}
proc getImpl(obj: JsObject; name: cstring): JsObject =
if not obj.hasOwnProperty(name):
raise newException(FieldError, $name & " is not a field of `obj`")
result = extractImpl(obj, name);
proc setImpl[T](obj: JsObject; name: cstring; val: T)
{. importcpp: "#[#] = #" .}
# This is where the magic happens:
# at each call-site update the type of obj in the Table
template `.=`*[T](obj: JsObject; nam: cstring; val: T) =
static:
if not tab.hasKey(stringifyIdent(obj)):
tab[stringifyIdent(obj)] = initTable[string, cstring]()
tab[stringifyIdent(obj)][stringifyIdent(nam)] = T.name
setImpl(obj, stringifyIdent(nam), val)
# Needs `auto` return type:
# returns the type of `obj` extracted from the Table
template `.`*(obj: JsObject; nam: cstring): auto =
when compiles(stringToIdent(tab[stringifyIdent(obj)][stringifyIdent(nam)])):
cast[stringToIdent(tab[stringifyIdent(obj)][stringifyIdent(nam)])](getImpl(obj, stringifyIdent(nam)))
else:
cast[JsObject](getImpl(obj, nam))
converter toJsObj[T](val: T): JsObject {. importcpp: "(#)" .}
# No type-inference yet in this version. Coming soon.
proc `.()`*(obj: JsObject; name: cstring; args: varargs[JsObject]): JsObject
{. importcpp: "#[#](...@)", discardable .}
# Utility constructor
proc newJsObj(): JsObject =
{. emit: "`result` = {};" .}
{. pragma: dirtyImport, importc, nodecl .}
# Usage example:
# Quick & dirty interface to `console` and `JSON`
var
console {. dirtyImport .}: JsObject
JSON {. dirtyImport .}: JsObject
y = newJsObj()
# partial type inference in action:
y.a = 42
var integer: int = y.a
y.a = cstring"blubb"
var stringy: cstring = y.a
# compile-time error:
# var inty: int = y.a
y.b = proc(z: int): int =
return z * z
console.log(y.a)
console.log(y, JSON.stringify(y.b(10)))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment