@naanlang/naan 1.4.4 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/LICENSE.md +2 -2
  2. package/README.md +4 -4
  3. package/bin/index.js +25 -3
  4. package/dist/env_web.js +164 -22
  5. package/dist/naan.min.js +5 -5
  6. package/frameworks/browser/browser.nlg +2 -2
  7. package/frameworks/browser/https_request.nlg +2 -2
  8. package/frameworks/browser/sworker.js +28 -26
  9. package/frameworks/browser/terminals.nlg +342 -700
  10. package/frameworks/browser/worker.nlg +255 -0
  11. package/frameworks/browser/workers.nlg +5 -5
  12. package/frameworks/browser/ws_client.nlg +15 -10
  13. package/frameworks/client/apiclient.nlg +204 -19
  14. package/frameworks/client/client.nlg +22 -2
  15. package/frameworks/client/psm_client.nlg +12 -3
  16. package/frameworks/client/relaycon.nlg +87 -161
  17. package/frameworks/common/events.nlg +101 -0
  18. package/frameworks/common/repltools.nlg +80 -1
  19. package/frameworks/node/apiserver.nlg +39 -246
  20. package/frameworks/node/filesystem.nlg +39 -12
  21. package/frameworks/node/gitter.nlg +9 -7
  22. package/frameworks/node/http_request.nlg +34 -13
  23. package/frameworks/node/https_request.nlg +8 -3
  24. package/frameworks/node/node.nlg +2 -0
  25. package/frameworks/node/pty.nlg +346 -0
  26. package/frameworks/node/shell.nlg +4 -4
  27. package/frameworks/node/worker.nlg +409 -539
  28. package/frameworks/node/ws_client.nlg +2 -2
  29. package/frameworks/project/build.nlg +1 -1
  30. package/frameworks/project/projects.nlg +2 -1
  31. package/frameworks/running/debugnub.nlg +17 -4
  32. package/frameworks/running/executors.nlg +27 -274
  33. package/frameworks/running/instances.nlg +393 -0
  34. package/frameworks/running/remote_req.nlg +143 -0
  35. package/frameworks/running/remote_res.nlg +89 -0
  36. package/frameworks/running/taskexec.nlg +2 -2
  37. package/frameworks/service/imp.nlg +736 -0
  38. package/frameworks/service/model.nlg +71 -0
  39. package/frameworks/service/nubnode.nlg +105 -0
  40. package/frameworks/service/nubweb.nlg +106 -0
  41. package/frameworks/service/service.nlg +28 -0
  42. package/frameworks/storage/dbt_pouch.nlg +26 -13
  43. package/frameworks/storage/psm.nlg +39 -5
  44. package/frameworks/storage/psm_dbtables.nlg +10 -2
  45. package/lib/browser/env_web.js +171 -29
  46. package/lib/browser/env_webworker.js +91 -5
  47. package/lib/core/naanlib.js +5 -5
  48. package/lib/env_node.js +184 -8
  49. package/lib/env_nodeworker.js +74 -1
  50. package/package.json +1 -1
  51. package/plugins/serviceAws/aws_cloudwatchlogs.nlg +2 -1
  52. package/plugins/serviceAws/aws_dynamo.nlg +1 -0
  53. package/plugins/serviceAws/aws_s3.nlg +3 -3
  54. package/plugins/serviceAws/aws_sqs.nlg +3 -1
  55. package/plugins/serviceAws/aws_utils.nlg +3 -1
  56. package/plugins/serviceAws/dbt_aws.nlg +22 -20
  57. package/plugins/serviceAws/psm_aws.nlg +2 -1
  58. package/plugins/serviceNexara/speechrec.nlg +10 -6
  59. package/test/test_02_context.nlg +24 -1
  60. package/test/test_03_jsinterop.nlg +32 -3
  61. package/plugins/serviceGC/russian_trusted_root_ca_pem.crt +0 -33
@@ -0,0 +1,393 @@
1
+ /*
2
+ * instances.nlg
3
+ * Naanlib/frameworks/browser
4
+ *
5
+ * Manage NaaN instances locally and remotely.
6
+ *
7
+ * NaaN instances are managed by a top/bottom pair of of objects. InBot (INstance BOTtom) objects
8
+ * reside in the same JavaScript environment (node/browser) as their worker and communicate with the
9
+ * appropriate env_xxx.js adapter for NaaN in that environment.
10
+ *
11
+ * Zero or more InTop objects connect to each InBot object via the IMP network, providing remote
12
+ * evaluation and terminal access services. Each InTop supports only one terminal simultaneously, but
13
+ * they can be attached and detached at any time.
14
+ *
15
+ * column positioning: // // !
16
+ *
17
+ * Copyright (c) 2026 by Richard C. Zulch
18
+ *
19
+ */
20
+
21
+
22
+ /*
23
+ * InTop
24
+ *
25
+ * Create an InTop executor for the specified InBot. The open and close methods select the specific
26
+ * InBot to be used. The design intent is to only connect to one InBot, but lifecycle and connection
27
+ * changes could theoretically change its instanceID. A terminal may attach and detach from this
28
+ * object, but only one at a time. Create more InTops if you need terminals simultaneously. All of
29
+ * the ExecutorBase methods are available, such as `context()`.
30
+ *
31
+ * Options:
32
+ * {
33
+ * name: <string> // target name, e.g. "Play"
34
+ * type: <string> // target category, e.g. "Local"
35
+ * workerID: <string> // target workerID
36
+ * create: <dictionary> // creation parameters, if creation is desired
37
+ * }
38
+ *
39
+ */
40
+
41
+ closure InTop(track, options, local intop, nlregex, str, error) {
42
+ global(App)
43
+ intop = ExecutorBase(track, options.type, options.name)
44
+ intop.workerID = options.workerID || undefined
45
+ intop.msgQueue = []
46
+ nlregex = RegExp("\\n", "g")
47
+
48
+ // doMessage
49
+ //
50
+ // Process a message from our InBot.
51
+ //
52
+ intop.doMessage = function doMessage(msg) {
53
+ if msg.id == "textout"
54
+ intop.term.Write(msg.text.replace(nlregex, "\r\n"))
55
+ else if msg.id == "debugtext"
56
+ intop.debugWrite(msg.text, msg.level)
57
+ else if msg.id == "typed" { // echo in dark-grey, italic
58
+ if intop.ioctl_echo == 1 { // echo newline only
59
+ if str.firstIndexOf("\\n") >= 0
60
+ intop.term.Write("\r\n")
61
+ } else if intop.ioctl_echo != 0
62
+ formatTyped(msg.text)
63
+ } else if msg.id == "targetout"
64
+ intop.execReceived(msg.data)
65
+ else if msg.id == "status"
66
+ intop.notify.execStatus(intop, msg.status)
67
+ else if msg.id == "debugout"
68
+ intop.debugger.debugData(intop, msg.data)
69
+ else if msg.id == "download"
70
+ { }
71
+ else if msg.id == "loaded"
72
+ { }
73
+ else if msg.id == "ioctl" {
74
+ if msg.op == "echo" {
75
+ intop.ioctl_echo = msg.arg // control our typed echo
76
+ intop.term.EchoSet(msg.arg) // echo arg: 0, 1, 2
77
+ } else if msg.op == "sane" {
78
+ intop.ioctl_echo = 2 // enable typed echo
79
+ intop.term.Sane()
80
+ } else if msg.op == "raw"
81
+ intop.term.SetRawMode(msg.arg) // enable/disable raw mode
82
+ else if msg.op == "keystroke"
83
+ intop.term.PutKey(msg.arg) // simulate a keystroke
84
+ } else if msg.id == "error"
85
+ intop.execClosed(Error("instance error", msg.data))
86
+ else if msg.id == "terminated"
87
+ close()
88
+ }
89
+
90
+ // formatTyped
91
+ //
92
+ // Format typed text with distinctive style.
93
+ //
94
+ intop.formatTyped = function formatTyped(text, local str) {
95
+ str = text.replace(nlregex, "\r\n")
96
+ intop.term.Write("\x1b[90m\x1b[3m${str}\x1b[0m\n")
97
+ }
98
+
99
+ // impUnknown
100
+ //
101
+ // Received an unknown destination notice from the other side.
102
+ //
103
+ intop.impUnknown = function impUnknown(unknownID, sourceID, gateway) {
104
+ if !gateway
105
+ debuglog("InTop.impUnknownID:", unknownID, "sourceID:", sourceID)
106
+ }
107
+
108
+ // impReceive
109
+ //
110
+ // Received a message from our InBot.
111
+ //
112
+ intop.impReceive = function impReceive(msg, sourceID) {
113
+ if msg.id == "workerDestID" {
114
+ intop.workerDestID = msg.workerDestID
115
+ return
116
+ }
117
+ if msg.id != "targetout" {
118
+ if msg.id == "status" {
119
+ intop.lastStatus = milliseconds()
120
+ intop.execSetStatus(msg.status)
121
+ }
122
+ if intop.msgQueue {
123
+ if msg.id != "status"
124
+ intop.msgQueue.push(msg)
125
+ return
126
+ }
127
+ }
128
+ intop.doMessage(msg)
129
+ }
130
+
131
+ // replInterrupt
132
+ //
133
+ // User interrupt from the keyboard.
134
+ //
135
+ intop.replInterrupt = function replInterrupt() {
136
+ intop.sender(intop.botDestID, { // send interrupt to worker
137
+ id: "interrupt"
138
+ })
139
+ }
140
+
141
+ // replReturn
142
+ //
143
+ // Return key hit.
144
+ //
145
+ intop.replReturn = function replReturn(local text) {
146
+ text = intop.term.TakeText()
147
+ echotyped(text)
148
+ intop.sender(intop.botDestID, { // send user-entered text to worker
149
+ id: "keyline",
150
+ text: text
151
+ })
152
+ }
153
+
154
+ // replRaw
155
+ //
156
+ // Raw character received.
157
+ //
158
+ intop.replRaw = function(str, origin) {
159
+ ioctl_event("raw-key", { // send raw keystroke to worker
160
+ key: str
161
+ origin: origin
162
+ })
163
+ }
164
+
165
+ // replResize
166
+ //
167
+ // The terminal was resized.
168
+ //
169
+ intop.replResize = function(columns, rows) {
170
+ ioctl_event("term-resize", { // send resize advice to worker
171
+ cols: columns
172
+ rows: rows
173
+ })
174
+ }
175
+
176
+ // echotyped
177
+ //
178
+ // Echo text to other terminals.
179
+ //
180
+ function echotyped(text) {
181
+ intop.sender(intop.botDestID, {
182
+ id: "typed"
183
+ text: text
184
+ })
185
+ }
186
+
187
+ // intop.KeyText
188
+ //
189
+ // Send text to the worker, with optional echo to terminal.
190
+ //
191
+ intop.KeyText = function KeyText(text, echo) {
192
+ if echo {
193
+ text = text.trim()
194
+ echotyped(text)
195
+ intop.term.WriteLn(text)
196
+ }
197
+ intop.sender(intop.botDestID, { // send text to worker
198
+ id: "keyline"
199
+ text: text
200
+ })
201
+ }
202
+
203
+ //
204
+ // ioctl_event
205
+ //
206
+ // A terminal-initiated event.
207
+ //
208
+ intop.ioctl_event = function ioctl_event(msg, arg) {
209
+ intop.sender(intop.botDestID, { // send ioctl event to worker
210
+ id: "ioctl-event",
211
+ msg: msg
212
+ arg: arg
213
+ })
214
+ }
215
+
216
+ // DebugCmd
217
+ //
218
+ // Post a debug command to the worker.
219
+ //
220
+ intop.DebugCmd = function DebugCmd(data) {
221
+ intop.sender(intop.botDestID, { // send data to worker
222
+ id: "debugin"
223
+ data: data
224
+ })
225
+ }
226
+
227
+ // PostMessage
228
+ //
229
+ // Post a message to the worker. This uses workerDestID if available, or the bottom itself
230
+ // otherwise. The former scenario covers use of NodeWorker where we want to send the packet all
231
+ // the way to the final destination. The second scenario covers the case that the bottom is
232
+ // communicating with the env_xxx.js code directly.
233
+ //
234
+ intop.PostMessage = function PostMessage(data, local destID) {
235
+ destID = intop.workerDestID || intop.botDestID
236
+ intop.sender(destID, { // send data to worker
237
+ id: "targetin",
238
+ data: data
239
+ })
240
+ list(false, { posted: true })
241
+ }
242
+
243
+ // debugWrite
244
+ //
245
+ // Write a debug message to the terminal.
246
+ //
247
+ intop.debugWrite = function(text, level) {
248
+ text = text.replaceAll("\n", "\r\n")
249
+ if (level >= 5)
250
+ intop.term.WriteLn(text, intop.term.Cyan) // cyan for builtin logging
251
+ else if (level >= 4)
252
+ intop.term.WriteLn(text, intop.term.Blue) // blue for Naan function logging
253
+ else if (level >= 3)
254
+ intop.term.WriteLn(text, intop.term.Red) // light red for warnings
255
+ else if (level >= 2)
256
+ intop.term.WriteLn(text, intop.term.Green) // green for Naan.debug.debuglog logging
257
+ else
258
+ intop.term.WriteLn(text, intop.term.Red+intop.term.Bold) // heavy red for errors
259
+ }
260
+
261
+ // termAttach
262
+ //
263
+ // A terminal was attached.
264
+ //
265
+ intop.termAttach = function termAttach(repl, db, notify, local msg) {
266
+ if !intop.msgQueue
267
+ return (list(Error("already attached")))
268
+ intop.sender(intop.botDestID, {
269
+ id: "attached",
270
+ guid: intop.instanceID
271
+ })
272
+ intop.term = repl
273
+ intop.debugger = db
274
+ intop.notify = notify
275
+ if intop.termSaved {
276
+ intop.term.StateLoad(intop.termSaved)
277
+ intop.termSaved = false }
278
+ else
279
+ intop.term.Reset()
280
+ intop.debugger.attach(intop)
281
+ intop.term.SetNotify(intop)
282
+ while intop.msgQueue.length > 0 { // replay saved and new messages
283
+ for msg in intop.msgQueue.splice(0)
284
+ intop.doMessage(msg)
285
+ }
286
+ intop.msgQueue = false
287
+ intop.term.Focus()
288
+ intop
289
+ }
290
+
291
+ // termDetach
292
+ //
293
+ // The terminal is being detached.
294
+ //
295
+ intop.termDetach = function termDetach() {
296
+ if intop.msgQueue
297
+ return (list(Error("already detached")))
298
+ intop.term.SetNotify()
299
+ intop.msgQueue = []
300
+ intop.debugger.detach(intop)
301
+ intop.termSaved = intop.term.StateSave()
302
+ intop.notify = false
303
+ intop.debugger = false
304
+ intop.term = false
305
+ intop.sender(intop.botDestID, {
306
+ id: "detached",
307
+ guid: intop.instanceID
308
+ })
309
+ }
310
+
311
+ // close
312
+ //
313
+ // Close our access to the worker.
314
+ //
315
+ intop.close = function close() {
316
+ if !intop.instanceID
317
+ return
318
+ intop.sender(intop.botDestID, {
319
+ id: "unsubscribe"
320
+ })
321
+ intop.execClosed(Error("instance closed"))
322
+ track.delete(intop)
323
+ termDetach()
324
+ App.imp.unregister(intop.instanceID)
325
+ intop.instanceID = false
326
+ }
327
+
328
+ // destroy
329
+ //
330
+ // Destroy the worker.
331
+ //
332
+ intop.destroy = function destroy() {
333
+ if intop.destroyed
334
+ return
335
+ intop.destroyed = true
336
+ intop.sender(intop.botDestID, {
337
+ id: "destroy",
338
+ guid: intop.instanceID
339
+ })
340
+ close()
341
+ }
342
+
343
+ // open
344
+ //
345
+ // Open into the specified InBot destID, returning a result tuple.
346
+ //
347
+ intop.open = closure open(inbotID) {
348
+ if intop.destroyed
349
+ return (list(Error("InTop already destroyed")))
350
+ if intop.instanceID {
351
+ if inbotID != intop.botDestID
352
+ return (list(Error("InTop already open")))
353
+ } else
354
+ intop.instanceID = UUID()
355
+ intop.botDestID = inbotID
356
+ intop.sender = App.imp.register(intop, {
357
+ name: options.name
358
+ type: options.type
359
+ instanceID: intop.instanceID
360
+ services: [ "naan-executor" ]
361
+ })
362
+ `(error) = intop.sender(intop.botDestID, {
363
+ id: "subscribe"
364
+ })
365
+ if error
366
+ return(list(error))
367
+ track.add(intop)
368
+ list(false, { ok: true })
369
+ }
370
+
371
+ // finis
372
+
373
+ intop
374
+ };
375
+
376
+
377
+ /*
378
+ * instInit
379
+ *
380
+ * Initialize the component.
381
+ *
382
+ */
383
+
384
+ function instInit(local manifest) {
385
+ manifest = `(InTop, InBot, instInit)
386
+
387
+ Naan.module.build(module.id, "instances", function(modobj, compobj) {
388
+ require("./executors.nlg")
389
+ require("./running.nlg")
390
+ compobj.manifest = manifest
391
+ modobj.exports.InTop = InTop
392
+ })
393
+ } ();
@@ -0,0 +1,143 @@
1
+ /*
2
+ * remote_req.nlg
3
+ * Naanlib/frameworks/running
4
+ *
5
+ * Requester for accessing remote services.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2026 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * RemoteRequester
16
+ *
17
+ * Return an executor proxy for the specified remote responder.
18
+ *
19
+ * Options:
20
+ * {
21
+ * name: <string> // name
22
+ * type: <string> // type
23
+ * }
24
+ *
25
+ */
26
+
27
+ function RemoteRequester(destID, options, local track)
28
+ {
29
+ global(App)
30
+ options = merge({
31
+ name: "Remote Eval"
32
+ type: "RemoteRequester"
33
+ }, options)
34
+
35
+ // remoteReq
36
+ //
37
+ // Executes on our worker thread to return an execution instance that can be used to create a
38
+ // proxy for remote evaluation.
39
+ //
40
+ closure remoteReq(track, name, options, local target) {
41
+ target = ExecutorBase(false, "proxy", options.name)
42
+ target.instanceID = UUID()
43
+
44
+ target.sender = App.imp.register(target, {
45
+ name: options.name
46
+ type: options.type
47
+ instanceID: target.instanceID
48
+ services: [ "naan-remote-requester" ]
49
+ })
50
+
51
+ // impUnknown
52
+ //
53
+ // Received an unknown destination notice from the other side.
54
+ //
55
+ target.impUnknown = function impUnknown(unknownID, sourceID, gateway) {
56
+ if !gateway
57
+ debuglog("MainThread.impUnknownID:", unknownID, "sourceID:", sourceID)
58
+ }
59
+
60
+ // impReceive
61
+ //
62
+ // Received a payload from the other side.
63
+ //
64
+ target.impReceive = function impReceive(message, sourceID) {
65
+ if message.id == "targetout"
66
+ target.execReceived(message.data)
67
+ }
68
+
69
+ // PostMessage
70
+ //
71
+ // Post a message to the target.
72
+ //
73
+ target.PostMessage = function PostMessage(data) {
74
+ target.sender(destID, {
75
+ id: "targetin",
76
+ data: data
77
+ })
78
+ list(false, { posted: true })
79
+ }
80
+
81
+ // destroy
82
+ //
83
+ // Terminate the proxy and all its works.
84
+ //
85
+ target.destroy = function destroy() {
86
+ App.imp.unregister(target.instanceID)
87
+ track.delete(target)
88
+ }
89
+
90
+ // finis
91
+
92
+ track.add(target)
93
+ list(false, target)
94
+ }
95
+
96
+ options = merge({
97
+ name: "RemoteRequester"
98
+ proxy: "proxy"
99
+ }, options)
100
+ track = ExecutorTracker()
101
+ track.register(options.type, remoteReq)
102
+ track.spawn(options.type, options.name) // returns result tuple
103
+ };
104
+
105
+
106
+ /*
107
+ * MainThread
108
+ *
109
+ * Return an executor proxy for the main thread. Only usable in workers.
110
+ *
111
+ */
112
+
113
+ function MainThread()
114
+ {
115
+ global(App)
116
+ if !App.mainInstanceID
117
+ throw("MainThread can only be used in workers")
118
+
119
+ RemoteRequester(App.mainInstanceID, {
120
+ name: "MainThread"
121
+ type: "mainProxy"
122
+ })
123
+ };
124
+
125
+
126
+ /*
127
+ * maithInit
128
+ *
129
+ * Initialize the component.
130
+ *
131
+ */
132
+
133
+ function remreqInit(local manifest) {
134
+ manifest = `(RemoteRequester, MainThread, remreqInit)
135
+
136
+ Naan.module.build(module.id, "remote_req", function(modobj, compobj) {
137
+ require("./executors.nlg")
138
+ require("../service/imp.nlg")
139
+ compobj.manifest = manifest
140
+ modobj.exports.RemoteRequester = RemoteRequester
141
+ modobj.exports.MainThread = MainThread
142
+ })
143
+ } ();
@@ -0,0 +1,89 @@
1
+ /*
2
+ * remote_res.nlg
3
+ * Naanlib/frameworks/running
4
+ *
5
+ * Responder for providing remote services.
6
+ *
7
+ * column positioning: // // !
8
+ *
9
+ * Copyright (c) 2026 by Richard C. Zulch
10
+ *
11
+ */
12
+
13
+
14
+ /*
15
+ * RemoteResponder
16
+ *
17
+ * Make a generic IMP remoting target with optional destID.
18
+ *
19
+ * Options:
20
+ * {
21
+ * name: <string> // name of this responder
22
+ * type: <string> // type of this responder
23
+ * }
24
+ *
25
+ */
26
+
27
+ closure RemoteResponder(destID, options, local remo, name)
28
+ {
29
+ global(App)
30
+ options = merge({
31
+ name: "Remote Nub"
32
+ type: "Remote Responder"
33
+ }, options)
34
+ remo = ExecutorBase(App.nide.track, "remote-responder", name)
35
+ remo.dispatcher = TaskDispatcher()
36
+ remo.instanceID = destID || UUID()
37
+ remo.sender = App.imp.register(remo, {
38
+ name: options.name
39
+ type: options.type
40
+ instanceID: remo.instanceID
41
+ services: [ "naan-remote-responder" ]
42
+ })
43
+
44
+ // impUnknown
45
+ //
46
+ // Received an unknown destination notice from the other side.
47
+ //
48
+ remo.impUnknown = function impUnknown(unknownID, sourceID, gateway) {
49
+ if !gateway
50
+ debuglog("RemoteResponder.impUnknownID:", unknownID, "sourceID:", sourceID)
51
+ }
52
+
53
+ // impReceive
54
+ //
55
+ // Received a payload from the other side.
56
+ //
57
+ remo.impReceive = closure impReceive(message, sourceID) {
58
+ remo.dispatcher(message.data, function(reply) {
59
+ remo.sender(sourceID, {
60
+ id: "targetout",
61
+ data: reply
62
+ })
63
+ })
64
+ }
65
+
66
+ // finis
67
+
68
+ remo
69
+ };
70
+
71
+
72
+ /*
73
+ * remresInit
74
+ *
75
+ * Initialize the component.
76
+ *
77
+ */
78
+
79
+ function remresInit(local manifest) {
80
+ manifest = `(RemoteResponder, remresInit)
81
+
82
+ Naan.module.build(module.id, "remote_res", function(modobj, compobj) {
83
+ require("./executors.nlg")
84
+ require("./taskexec.nlg")
85
+ require("../service/imp.nlg")
86
+ compobj.manifest = manifest
87
+ modobj.exports.RemoteResponder = RemoteResponder
88
+ })
89
+ } ();
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * column positioning: // // !
7
7
  *
8
- * Copyright (c) 2021-2025 by Richard C. Zulch
8
+ * Copyright (c) 2021-2026 by Richard C. Zulch
9
9
  *
10
10
  */
11
11
 
@@ -122,7 +122,7 @@ closure TaskDispatcher(replier, coder, local contexts, gohome, util) {
122
122
  objectDecoder: context.coder.decodeObject
123
123
  })
124
124
  expr = caar(expr)
125
- if expr.0 == `\.invoke\-method
125
+ if tuple(expr) && expr.0 == `\.invoke\-method
126
126
  expr = context.coder.invokeMethod(expr.1, expr.2) // object method, args
127
127
  else
128
128
  expr = evalactive(expr) // evaluate expression