Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js: add MakeFullWrapper to expose exported methods and struct fields. #8

Merged
merged 1 commit into from Jul 7, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 10 additions & 10 deletions compiler/gopherjspkg/fs_vfsdata.go

Large diffs are not rendered by default.

194 changes: 97 additions & 97 deletions compiler/natives/fs_vfsdata.go

Large diffs are not rendered by default.

74 changes: 46 additions & 28 deletions compiler/prelude/jsmapping.js
Expand Up @@ -20,7 +20,7 @@ var $needsExternalization = function(t) {
}
};

var $externalize = function(v, t) {
var $externalize = function(v, t, makeWrapper) {
if (t === $jsObjectPtr) {
return v;
}
Expand All @@ -44,37 +44,37 @@ var $externalize = function(v, t) {
case $kindArray:
if ($needsExternalization(t.elem)) {
return $mapArray(v, function(e) {
return $externalize(e, t.elem);
return $externalize(e, t.elem, makeWrapper);
});
}
return v;
case $kindFunc:
return $externalizeFunction(v, t, false);
return $externalizeFunction(v, t, false, makeWrapper);
case $kindInterface:
if (v === $ifaceNil) {
return null;
}
if (v.constructor === $jsObjectPtr) {
return v.$val.object;
}
return $externalize(v.$val, v.constructor);
return $externalize(v.$val, v.constructor, makeWrapper);
case $kindMap:
var m = {};
var keys = $keys(v);
for (var i = 0; i < keys.length; i++) {
var entry = v[keys[i]];
m[$externalize(entry.k, t.key)] = $externalize(entry.v, t.elem);
m[$externalize(entry.k, t.key, makeWrapper)] = $externalize(entry.v, t.elem, makeWrapper);
}
return m;
case $kindPtr:
if (v === t.nil) {
return null;
}
return $externalize(v.$get(), t.elem);
return $externalize(v.$get(), t.elem, makeWrapper);
case $kindSlice:
if ($needsExternalization(t.elem)) {
return $mapArray($sliceToArray(v), function(e) {
return $externalize(e, t.elem);
return $externalize(e, t.elem, makeWrapper);
});
}
return $sliceToArray(v);
Expand Down Expand Up @@ -128,20 +128,24 @@ var $externalize = function(v, t) {
return o;
}

if (makeWrapper !== undefined) {
return makeWrapper(v);
}

o = {};
for (var i = 0; i < t.fields.length; i++) {
var f = t.fields[i];
if (!f.exported) {
continue;
}
o[f.name] = $externalize(v[f.prop], f.typ);
o[f.name] = $externalize(v[f.prop], f.typ, makeWrapper);
}
return o;
}
$throwRuntimeError("cannot externalize " + t.string);
};

var $externalizeFunction = function(v, t, passThis) {
var $externalizeFunction = function(v, t, passThis, makeWrapper) {
if (v === $throwNilPointerError) {
return null;
}
Expand All @@ -154,22 +158,22 @@ var $externalizeFunction = function(v, t, passThis) {
var vt = t.params[i].elem,
varargs = [];
for (var j = i; j < arguments.length; j++) {
varargs.push($internalize(arguments[j], vt));
varargs.push($internalize(arguments[j], vt, makeWrapper));
}
args.push(new t.params[i](varargs));
break;
}
args.push($internalize(arguments[i], t.params[i]));
args.push($internalize(arguments[i], t.params[i], makeWrapper));
}
var result = v.apply(passThis ? this : undefined, args);
switch (t.results.length) {
case 0:
return;
case 1:
return $externalize(result, t.results[0]);
return $externalize($copyIfRequired(result, t.results[0]), t.results[0], makeWrapper);
default:
for (var i = 0; i < t.results.length; i++) {
result[i] = $externalize(result[i], t.results[i]);
result[i] = $externalize($copyIfRequired(result[i], t.results[i]), t.results[i], makeWrapper);
}
return result;
}
Expand All @@ -178,7 +182,7 @@ var $externalizeFunction = function(v, t, passThis) {
return v.$externalizeWrapper;
};

var $internalize = function(v, t, recv) {
var $internalize = function(v, t, recv, makeWrapper) {
if (t === $jsObjectPtr) {
return v;
}
Expand Down Expand Up @@ -226,7 +230,7 @@ var $internalize = function(v, t, recv) {
$throwRuntimeError("got array with wrong size from JavaScript native");
}
return $mapArray(v, function(e) {
return $internalize(e, t.elem);
return $internalize(e, t.elem, makeWrapper);
});
case $kindFunc:
return function() {
Expand All @@ -236,21 +240,21 @@ var $internalize = function(v, t, recv) {
var vt = t.params[i].elem,
varargs = arguments[i];
for (var j = 0; j < varargs.$length; j++) {
args.push($externalize(varargs.$array[varargs.$offset + j], vt));
args.push($externalize(varargs.$array[varargs.$offset + j], vt, makeWrapper));
}
break;
}
args.push($externalize(arguments[i], t.params[i]));
args.push($externalize(arguments[i], t.params[i], makeWrapper));
}
var result = v.apply(recv, args);
switch (t.results.length) {
case 0:
return;
case 1:
return $internalize(result, t.results[0]);
return $internalize(result, t.results[0], makeWrapper);
default:
for (var i = 0; i < t.results.length; i++) {
result[i] = $internalize(result[i], t.results[i]);
result[i] = $internalize(result[i], t.results[i], makeWrapper);
}
return result;
}
Expand Down Expand Up @@ -283,45 +287,45 @@ var $internalize = function(v, t, recv) {
case Float64Array:
return new ($sliceType($Float64))(v);
case Array:
return $internalize(v, $sliceType($emptyInterface));
return $internalize(v, $sliceType($emptyInterface), makeWrapper);
case Boolean:
return new $Bool(!!v);
case Date:
if (timePkg === undefined) {
/* time package is not present, internalize as &js.Object{Date} so it can be externalized into original Date. */
return new $jsObjectPtr(v);
}
return new timePkg.Time($internalize(v, timePkg.Time));
return new timePkg.Time($internalize(v, timePkg.Time, makeWrapper));
case Function:
var funcType = $funcType([$sliceType($emptyInterface)], [$jsObjectPtr], true);
return new funcType($internalize(v, funcType));
return new funcType($internalize(v, funcType, makeWrapper));
case Number:
return new $Float64(parseFloat(v));
case String:
return new $String($internalize(v, $String));
return new $String($internalize(v, $String, makeWrapper));
default:
if ($global.Node && v instanceof $global.Node) {
return new $jsObjectPtr(v);
}
var mapType = $mapType($String, $emptyInterface);
return new mapType($internalize(v, mapType));
return new mapType($internalize(v, mapType, makeWrapper));
}
case $kindMap:
var m = {};
var keys = $keys(v);
for (var i = 0; i < keys.length; i++) {
var k = $internalize(keys[i], t.key);
m[t.key.keyFor(k)] = { k: k, v: $internalize(v[keys[i]], t.elem) };
var k = $internalize(keys[i], t.key, makeWrapper);
m[t.key.keyFor(k)] = { k: k, v: $internalize(v[keys[i]], t.elem, makeWrapper) };
}
return m;
case $kindPtr:
if (t.elem.kind === $kindStruct) {
return $internalize(v, t.elem);
return $internalize(v, t.elem, makeWrapper);
}
case $kindSlice:
return new t(
$mapArray(v, function(e) {
return $internalize(e, t.elem);
return $internalize(e, t.elem, makeWrapper);
})
);
case $kindString:
Expand Down Expand Up @@ -386,3 +390,17 @@ var $isASCII = function(s) {
}
return true;
};

var $copyIfRequired = function(v, typ) {
// interface values
if (v.constructor.copy) {
return new v.constructor($clone(v.$val, v.constructor));
}
// array and struct values
if (typ.copy) {
var clone = typ.zero();
typ.copy(clone, v);
return clone;
}
return v;
};
Empty file removed compiler/prelude/jspmapping.js
Empty file.
2 changes: 1 addition & 1 deletion compiler/prelude/prelude.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion compiler/prelude/prelude_min.go

Large diffs are not rendered by default.

99 changes: 98 additions & 1 deletion js/js.go
@@ -1,6 +1,6 @@
// Package js provides functions for interacting with native JavaScript APIs. Calls to these functions are treated specially by GopherJS and translated directly to their corresponding JavaScript syntax.
//
// Use MakeWrapper to expose methods to JavaScript. When passing values directly, the following type conversions are performed:
// Use MakeWrapper to expose methods to JavaScript. Use MakeFullWrapper to expose methods AND fields to JavaScript. When passing values directly, the following type conversions are performed:
//
// | Go type | JavaScript type | Conversions back to interface{} |
// | --------------------- | --------------------- | ------------------------------- |
Expand Down Expand Up @@ -147,6 +147,103 @@ func MakeWrapper(i interface{}) *Object {
return o
}

// MakeFullWrapper creates a JavaScript object which has wrappers for the exported
// methods of i, and, where i is a (pointer to a) struct value, wrapped getters
// and setters
// (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)
// for the non-embedded exported fields of i. Values accessed via these methods
// and getters are themsevles wrapped when accessed, but an important point to
// note is that a new wrapped value is created on each access.
func MakeFullWrapper(i interface{}) *Object {
v := InternalObject(i)
c := v.Get("constructor")

o := Global.Get("Object").New()

defineProperty := func(k string, fns ...func(*Object)) {
op := Global.Get("Object").New()
for _, f := range fns {
f(op)
}
Global.Get("Object").Call("defineProperty", o, k, op)
}

defineProperty("__internal_object__", func(op *Object) {
op.Set("value", v)
})

{
// caculate a sensible type string

// we don't want to import any packages in this package
// so we do some string operations by hand

typ := c.Get("string").String()
pkg := c.Get("pkg").String()

ptr := ""
if typ[0] == '*' {
ptr = "*"
}

for i := 0; i < len(typ); i++ {
if typ[i] == '.' {
typ = typ[i+1:]
break
}
}

pkgTyp := pkg + "." + ptr + typ
defineProperty("$type", func(op *Object) {
op.Set("value", pkgTyp)
})
}

var fields *Object
methods := Global.Get("Array").New()
if ms := c.Get("methods"); ms != Undefined {
methods = methods.Call("concat", ms)
}
// if we are a pointer value then add fields from element
// else the constructor itself will have them
if e := c.Get("elem"); e != Undefined {
fields = e.Get("fields")
methods = methods.Call("concat", e.Get("methods"))
} else {
fields = c.Get("fields")
}
for i := 0; i < methods.Length(); i++ {
m := methods.Index(i)
if m.Get("pkg").String() != "" { // not exported
continue
}
defineProperty(m.Get("prop").String(), func(op *Object) {
op.Set("value", func(args ...*Object) *Object {
return Global.Call("$externalizeFunction", v.Get(m.Get("prop").String()), m.Get("typ"), true, InternalObject(MakeFullWrapper)).Call("apply", v, args)
})
})
}
if fields != Undefined {
for i := 0; i < fields.Length(); i++ {
f := fields.Index(i)
if !f.Get("exported").Bool() {
continue
}
defineProperty(f.Get("prop").String(), func(op *Object) {
op.Set("get", func() *Object {
vc := Global.Call("$copyIfRequired", v.Get("$val").Get(f.Get("prop").String()), f.Get("typ"))
return Global.Call("$externalize", vc, f.Get("typ"), InternalObject(MakeFullWrapper))
})
op.Set("set", func(jv *Object) {
gv := Global.Call("$internalize", jv, f.Get("typ"), InternalObject(MakeFullWrapper))
v.Get("$val").Set(f.Get("prop").String(), gv)
})
})
}
}
return o
}

// NewArrayBuffer creates a JavaScript ArrayBuffer from a byte slice.
func NewArrayBuffer(b []byte) *Object {
slice := InternalObject(b)
Expand Down