-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathAliensForJS.ns
158 lines (152 loc) · 6.73 KB
/
AliensForJS.ns
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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
Newspeak3
'Root'
class AliensForJS usingPlatform: p = (
(* Aliens are a capability-based API for a foreign function interface (FFI). On NS2JS, they allow Newspeak code to invoke JavaScript code and vice versa.
An Alien is a Newspeak proxy for a JavaScript object. Upon receiving a message, an Alien expatriates the arguments, sends the message to the target JavaScript object, and alienates the result.
An Expat is a JavaScript proxy for a Newspeak object. Upon receiving a message, an Expat alienates the arguments, sends the message to the target Newspeak object, and expatriates the result.
A bilingual object is one whose representation is the same in both languages: unwrapped basic types such as numbers, booleans. Note that even though Newspeak and JavaScript closures have the same representation, they are not bilingual and wrapping should occur because Newspeak closures expect Newspeak/Alien arguments and JavaScript closures expect JavaScript/Expat arguments. What about strings and arrays? It would seem we have to wrap arrays because their elements should be alieniated/expatriated. It may be safe to treat strings as bilingual if they always respond to messages with other bilingual objects.
Alien mappings:
alien sort: a ignored: b ignored: c -> alien.sort(a, b, c)
alien new: a ignored: b ignored: c -> new alien(a, b, c)
alien at: 'a' -> alien.a
alien at: 'a' put: b -> alien.a = b
Expat mappings:
?
*)
|
public global = Alien wrapping: (js ident: 'theGlobalObject').
|) (
class Alien wrapping: o = (
js assign: (js propertyOf: self at: (js literal: 'jsTarget')) toBe: o.
) (
public at: key = (
^alienate: (js
call: (js propertyOf: (js ident: 'Reflect') at: (js literal: 'get'))
with: {js propertyOf: self at: (js literal: 'jsTarget'). expatriate: key})
)
public at: key put: value = (
js
call: (js propertyOf: (js ident: 'Reflect') at: (js literal: 'set'))
with: {js propertyOf: self at: (js literal: 'jsTarget'). expatriate: key. expatriate: value}.
^value
)
doesNotUnderstand: msg = (
| jsTarget jsArguments jsSelector jsResult |
jsTarget:: js propertyOf: self at: (js literal: 'jsTarget').
jsArguments:: msg arguments collect: [:arg | expatriate: arg].
jsSelector:: copyUntilFirstColon: msg mangledSelector.
jsResult:: jsSelector = 'new'
ifTrue:
[js call: (js propertyOf: (js ident: 'Reflect') at: (js literal: 'construct')) with: {jsTarget. jsArguments}]
ifFalse:
[ | jsFunction |
jsFunction:: js propertyOf: jsTarget at: jsSelector.
(js operator: '===' with: jsFunction and: (js ident: 'undefined'))
ifTrue: [^super doesNotUnderstand: msg].
js call: (js propertyOf: (js ident: 'Reflect') at: (js literal: 'apply')) with: {jsFunction. jsTarget. jsArguments}].
^alienate: jsResult
)
public isAlien ^<Boolean> = (
^true
)
public isExpat ^<Boolean> = (
^false
)
public isUndefined = (
^js operator: '===' with: (js propertyOf: self at: (js literal: 'jsTarget')) and: (js ident: 'undefined')
)
public printString = (
self isUndefined ifTrue: [^'undefined']. (* undefined.toString() throws in JS *)
^(js call: (js propertyOf: (js propertyOf: self at: (js literal: 'jsTarget')) at: (js literal: 'toString')) with: {})
)
public value = (
| jsTarget = js propertyOf: self at: (js literal: 'jsTarget'). |
^alienate: (js
call: (js propertyOf: jsTarget at: (js literal: 'call'))
with: {jsTarget})
)
public value: a1 = (
| jsTarget = js propertyOf: self at: (js literal: 'jsTarget'). |
^alienate: (js
call: (js propertyOf: jsTarget at: (js literal: 'call'))
with: {jsTarget. expatriate: a1})
)
public value: a1 value: a2 = (
| jsTarget = js propertyOf: self at: (js literal: 'jsTarget'). |
^alienate: (js
call: (js propertyOf: jsTarget at: (js literal: 'call'))
with: {jsTarget. expatriate: a1. expatriate: a2})
)
public value: a1 value: a2 value: a3 = (
| jsTarget = js propertyOf: self at: (js literal: 'jsTarget'). |
^alienate: (js
call: (js propertyOf: jsTarget at: (js literal: 'call'))
with: {jsTarget. expatriate: a1. expatriate: a2. expatriate: a3})
)
public valueWithArguments: args = (
| jsTarget = js propertyOf: self at: (js literal: 'jsTarget'). |
^alienate: (js
call: (js propertyOf: jsTarget at: (js literal: 'apply'))
with: {jsTarget. args collect: [:nsArg | expatriate: nsArg]})
)
) : (
)
class Expat wrapping: o = (
(* :todo: Implement with ES6 Proxy instead. *)
js assign: (js propertyOf: self at: (js literal: 'nsTarget')) toBe: o.
) (
public isAlien ^<Boolean> = (
^false
)
public isExpat ^<Boolean> = (
^true
)
) : (
)
alienate: jsObj = (
#TODO.
(js operator: '===' with: (js ident: 'null') and: jsObj) ifTrue: [^nil].
(js prefixOperator: 'typeof ' on: jsObj) == 'string' ifTrue: [^jsObj].
(js prefixOperator: 'typeof ' on: jsObj) == 'number' ifTrue: [^jsObj].
(js prefixOperator: 'typeof ' on: jsObj) == 'boolean' ifTrue: [^jsObj].
(* This does not discrimate NS vs JS closures *)
(js prefixOperator: 'typeof ' on: jsObj) == 'function' ifTrue: [^Alien wrapping: jsObj].
(js operator: 'instanceof' with: jsObj and: (js propertyOf: (js propertyOf: Object at: (js literal: 'runtimeClass')) at: (js literal: 'basicNew')))
ifTrue:
[jsObj isAlien ifTrue: [Error signal: 'Shouldnt be asked to double alienate...'].
jsObj isExpat ifTrue: [^js propertyOf: jsObj at: (js literal: 'nsTarget')].
Error signal: 'Asked to alienate a raw Newspeak object...'].
(js operator: 'instanceof' with: jsObj and: (js ident: 'Uint8Array')) ifTrue: [^jsObj].
^Alien wrapping: jsObj
)
copyUntilFirstColon: sel = (
#BOGUS. (* DNU does not yet pass unmangled selectors. *)
2 to: sel size do: [:i |
((sel at: i) = "$") ifTrue: [^sel copyFrom: 2 to: i - 1]].
^sel copyFrom: 2 to: sel size
)
expatriate: nsObj = (
#TODO.
(js operator: '===' with: nil and: nsObj) ifTrue: [^js ident: 'null'].
(js prefixOperator: 'typeof ' on: nsObj) == 'string' ifTrue: [^nsObj].
(js prefixOperator: 'typeof ' on: nsObj) == 'number' ifTrue: [^nsObj].
(js prefixOperator: 'typeof ' on: nsObj) == 'boolean' ifTrue: [^nsObj].
(* This does not discrimate NS vs JS closures *)
(js prefixOperator: 'typeof ' on: nsObj) == 'function' ifTrue: [^expatriateBlock: nsObj].
(js operator: 'instanceof' with: nsObj and: (js propertyOf: (js propertyOf: Object at: (js literal: 'runtimeClass')) at: (js literal: 'basicNew')))
ifTrue:
[nsObj isAlien ifTrue: [^js propertyOf: nsObj at: (js literal: 'jsTarget')].
nsObj isExpat ifTrue: [Error signal: 'Shouldnt be asked to double expatriate...'].
^Expat wrapping: nsObj.].
(js operator: 'instanceof' with: nsObj and: (js ident: 'Uint8Array')) ifTrue: [^nsObj].
Error signal: 'Asked to expatriate a raw JS object...'
)
expatriateBlock: b = (
^js functionOf: {} body: (
js return: (
expatriate: (
b valueWithArguments: (
(js verbatim: 'Array.prototype.slice.call(arguments, 0)') collect: [:ea | alienate: ea]))))
)
) : (
)