/ Check-in [70ea852cb1]
DEMO | DOWNLOAD | DEPLOY | SEARCH
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Release "2.6.2". Pre switch to new docs. Fix File.realpath
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:70ea852cb1d721ec768358e8f3e25083080d479e
User & Date: pmacdona 2018-11-28 03:22:48
Context
2018-11-28
18:11
Release "2.7". check-in: f8bdb1a6c7 user: pmacdona tags: trunk
03:22
Release "2.6.2". Pre switch to new docs. Fix File.realpath check-in: 70ea852cb1 user: pmacdona tags: trunk
2018-11-18
22:26
Release "2.6.1". WebSocket: add support for .shtml server-side-include. check-in: 35dd8b0148 user: pmacdona tags: trunk
Changes

Changes to Makefile.

395
396
397
398
399
400
401
402
403
404
405
406
407
408
409

stubs:
	(cd src && ../$(PROGBIN) ../tools/mkstubs.jsi)

ref:
	./$(PROGBIN) tools/mkproto.jsi > tools/protos.jsi
	$(MAKE) -C www
#	$(MAKE) -C html

release: stubs ref src/jsi.c src/jsiOne.c

printconf:
	@echo $(EXPECT_CONFIG_VER)

test:







|







395
396
397
398
399
400
401
402
403
404
405
406
407
408
409

stubs:
	(cd src && ../$(PROGBIN) ../tools/mkstubs.jsi)

ref:
	./$(PROGBIN) tools/mkproto.jsi > tools/protos.jsi
	$(MAKE) -C www
	$(MAKE) -C doc

release: stubs ref src/jsi.c src/jsiOne.c

printconf:
	@echo $(EXPECT_CONFIG_VER)

test:

Added doc/Makefile.









>
>
>
>
1
2
3
4
all:
	../jsish ../tools/mkref.jsi -md true > md/Reference.md
	echo ../jsish ../tools/mkindex.jsi -md true > index.md.html

Added doc/all.shtml.















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<style class="fallback">body{visibility:hidden;}</style>
<script>window.markdeepOptions={tocStyle:"medium"}</script>

<center style="">
    <span class="image"><a href="Jsish"><img class="markdeep" src="logojsi.png" /></a ><div class="imagecaption"><strong class="asterisk">Javascript Shell Interpreter</strong></div></span>
    <div style="font-size: 75%">
        [DEMOS](Demos)&nbsp;/&nbsp;[DOWNLOAD](Start)&nbsp;/&nbsp;[DEVELOP](Develop)&nbsp;/&nbsp;[DEPLOY](Deploy)
    </div>
        <hr style="margin:0;">
[Types](Types)&nbsp;[Builtin](Builtin)&nbsp;[Reference](Reference)&nbsp;&nbsp;[**Index**](Index)&nbsp;&nbsp;[Log](Log)&nbsp;[Debug](Debug)&nbsp;[Testing](Testing)



</center>

<!--#include file="pages/Start.md"-->
<!--#include file="pages/Testing.md"-->
<script src="dumpdeep.js"></script>
<link rel="stylesheet" href="jsistyle.css" type="text/css" media="screen" />
<!-- Markdeep: --><style class="fallback">body{visibility:hidden;}</style><script src="markdeep.min.js"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility='visible')</script>


Added doc/dumpdeep.js.



















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
// Script to dump markdeep export via websocket.
console.log('dumpdeep.js');
function DeepDumpWs() {
    console.log('DeepDumpWsTO');
    if (document.URL.indexOf("?export=save")<0) return;
    var url = document.URL.replace(/^http/,"ws");
    var ws = new WebSocket(url, "ws");
    ws.onopen = function() {
        var data = document.querySelectorAll("body pre");
        if (!data[0]) data = document.querySelectorAll("body code");
        if (!data[0]) return;
        data = data[0].innerText;
        console.log('DATA '+data);
        clearInterval(DeepDumpWsTO);
        ws.send(JSON.stringify({cmd:"save", url:url, data:data}));
    };
    ws.onmessage = function(msg) {
        if (msg.data !== 'DONE!!!')
            document.location = msg.data+"?export=save";
        else
            document.body.innerHTML = '<style>body{background:#ddd;}</style>DOWNLOAD COMPLETE: PLEASE CLOSE THIS TAB';
    };
};
window.onload = DeepDumpWs;
var DeepDumpWsTO = setTimeout(DeepDumpWs, 10000);

Added doc/include.shtml.















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<style class="fallback">body{visibility:hidden;}</style>
<script>window.markdeepOptions={tocStyle:"medium"}</script>

<center style="">
    <span class="image"><a href="Jsish"><img class="markdeep" src="logojsi.png" /></a ><div class="imagecaption"><strong class="asterisk">Javascript Shell Interpreter</strong></div></span>
    <div style="font-size: 75%">
        &nbsp;[DOWNLOAD](Start)&nbsp;/&nbsp;[DEVELOP](Develop)&nbsp;/&nbsp;[DEPLOY](Deploy)
    </div>
        <hr style="margin:0;">
[Types](Types)&nbsp;[Builtin](Builtin)&nbsp;[Reference](Reference)&nbsp;&nbsp;[Index](Index)&nbsp;&nbsp;[**Fossil**](https://jsish.org/jsi)&nbsp;&nbsp;[Demos](Demos)&nbsp;[Log](Logging)&nbsp;[Test](Testing)&nbsp;[Debug](Debug)&nbsp;[Misc](Misc)



</center>

<!--#include file="$md"-->

<link rel="stylesheet" href="jsistyle.css" type="text/css" media="screen" />
<!-- Markdeep: --><style class="fallback">body{visibility:hidden;}</style><script src="markdeep.min.js"></script>
<script>var startOfMarkDeep=true; window.alreadyProcessedMarkdeep||(document.body.style.visibility='visible'); document.title=location.pathname.match(/\/([\w]+)[^\/]*$/)[1];</script>
<script src="dumpdeep.js"></script>


Added doc/jsistyle.css.

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
.md .mediumTOC {background:#fff; border:1px solid #ccc; padding-right:3px;}
.md .mediumTOC center { display:none; }
h1,h2,h3,h4 { clear:none; padding:0px; }
.tocNumber {display:none; }
.md h1::before, .md h2::before, .md h3::before,.md h4::before { display:none; }
body { max-width:980px; }
.output { background:#000; color:#fff; }
.markdeepFooter { visibility:hidden; }

Added doc/logojsi.png.

cannot compute difference between binary files

Added doc/md/Builtin.md.



























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
Builtins
========

Jsi implements the following built-in commands.

[CData](Reference#CData)
----
See [CData](CData).


[Channel](Reference#Channel)
----
A Channel object can be created for reading and/or writing individual files.
Generally an object is instantiated to open the file and then read/write operations are performed.

~~~~ JS linenumbers
var f = new Channel('tests/filetest.txt');
if (!f)
    puts('Can not open tests/filetest.txt');
else {
    while((n = f.gets())!=undefined) {
        puts(n);
    }
}
~~~~


[Event](Reference#Event)
----

As in Ecmascript, events are scheduled using the standard functions:

function setTimeout(callback:function, msdelay:number):number
:   Invoke function after msdelay.

    Returns the event handle.

function setInterval(callback:function, msdelay:number):number
: Invoke function every msdelay milliseconds.

    Returns the event handle.

function clearInterval(id:number):void
: Delete function invocation.


One difference is that we need to explicitly call update() so that events will be serviced:

~~~~ JS linenumbers
function foo() {
    puts("FOO:");
}
setInterval(foo, 1000);
update(-1);
~~~~

The call to update(-1) causes foo to be called once a second.


### Update
function update(options:number|object=void):number
:   update/process all pending scheduled events

    - With no arguments, update will process all events in the queue then return.
    - If a number argument is given, it is treated as the minTime option below.
    - If an object argument is given, it may contain any of the options in the following table

| Option    | Description                                                                 |
|-----------|-----------------------------------------------------------------------------|
| maxEvents | Maximum number of events to process                                         |
| maxPasses | Maximum passes through event queue                                          |
| minTime   | Minimum milliseconds before returning, or -1 to loop forever (Default is 0) |
| sleep     | Sleep time between event checks in milliseconds (Default is 1)              |

Events are processed until one of minTime, maxEvents, or maxPasses is exceeded.

The returned value is the number of events processed.


### Info
Info.event = function (id:number=void):array|object
:   Return event information, which is one of:

    - With no args, a list of all events.
    - With one arg, the information for the given event.

In Jsi we can use Info.event() to query queued events:

~~~~ JS linenumbers
function foo() {
    puts("FOO: " + i++);
    if (i>=3) exit(0);
}
var i=0, id = setInterval(foo,1000);
var evs = Info.event();
for (var i in evs) {
  puts('EV(' + i + '): ' + Info.event(evs[i]).toString());
}
update();
~~~~

~~~~
EV(0): { built-in:false, count:0, initial:1000, once:false, type:"timer", when:1000 }
FOO: 0
FOO: 1
FOO: 2
~~~~

This returns value is:

- With no arg, the list of ids for queued events.
- With one arg, the information for the given event id.

Also see [Info](#info).


[File](Reference#File)
----
 methods are used for accessing various
attributes and information on files where just basic IO is not necessarily the goal.

~~~~
File.mkdir('XX1');
File.mkdir('XX1/AA');
File.mkdir('XX1/BB');
File.rename('XX1/BB'*,*'XX1/CC.txt');
puts(File.glob(null,'XX1').sort());
puts(File.dirname('XX1/AA'));
puts(File.rootname('XX1/CC.txt'));
puts(File.tail('XX1/CC.txt'));
puts(File.type('XX1/CC.txt'));
puts(File.extension('XX1/CC.txt'));
//puts(File.realpath('XX1/CC.txt'));
puts(File.writable('XX1/CC.txt'));
puts(File.readable('XX1/CC.txt'));
puts(File.exists('XX1/CC.txt'));
puts(File.isdir('XX1/CC.txt'));
puts(File.isfile('XX1/CC.txt'));
File.remove(*'XX1',true);
~~~~

~~~~
[ "AA", "CC.txt" ]
XX1
XX1/CC
CC.txt
directory
.txt
true
true
true
true
false
file
~~~~


### glob
The File glob method returns an array of matching files according to the rules:

- With no arguments (or null) returns all files/directories in current directory.
- If first argument is a pattern (either a glob or regexp) just files are returned.
- If second argument is a string, it denotes the directory to search in.
- If second argument is a function, this function is called with each path.
- Otherwise second argument is a set of options.

~~~~
File.glob();
File.glob("*.c");
File.glob(/^jsi.*\.c$/);
File.glob(''*,'tests');
File.glob('.js'*,{dir:'tests', recurse:true});
~~~~


[Info](Reference#Info)
----
The info sub-methods are used for accessing various internal attributes and information about Jsi.

~~~~
Info.vars('*');
Info.funcs('*');
~~~~

This internal self-inspection capability is referred to as Introspection.

Documentation for Jsi in is generated via the Info command.


[Interp](Reference#Interp)
----
See [interps](Interp).


[JSON](Reference#JSON)
----
JSON (JavaScript Object Notation) is an open standard format that
uses human-readable text to transmit data objects consisting of attribute–value pairs.
It is the primary means of exchanging data with web-browsers.

The JSON object provides the following methods:

function stringify(val:any, strict:boolean=true):string

The stringify() method converts a javascript data object to a string:

~~~~
var obj = { a:1, b:2, c:"able", d:[ 1, 2, 3 ] };
var str = JSON.stringify(obj);
//RETURNS: '{"a":1, "b":2, "c":"able", "d":[1,2,3]}';
~~~~

function parse(str:string, strict:boolean=true):any

The parse() method converts a string into javascript data:

~~~~
var str = '{"a":1, "b":2, "c":"able", "d":[1,2,3]}';
var obj = JSON.parse(str);
//RETURNS: { a:1, b:2, c:"able", d:[ 1, 2, 3 ] }
~~~~

When strict is false then parse()
that does not require quoting of names.

~~~~
var str = '{a:1, b:2, c:"able", d:[1,2,3]}';
var obj = JSON.parse(str, false);
~~~~

Non-strict parsing is particularly helpful when encoding JSON in [C](#builtins/json).

!!! WARNING
    The underlying parser is not a validating parser.


[MySql](Reference#MySql)
----
See [js-mysql](MySql).


[Signal](Reference#Signal)
----
Signal is used to send signals to processes, or setup handlers
for receiving signals.  It is currently only supported on unix.

The following signal commands are available:

| Method                 | Description                                 |
|------------------------|---------------------------------------------|
| alarm(secs)            | Setup alarm in seconds                      |
| callback(func,sig)     | Setup callback handler for signal           |
| default(?sig,sig,...?) | Set named signals to default action         |
| handle(?sig,sig,...?)  | Set named signals to handle action          |
| ignore(?sig,sig,...?)  | Set named signals to ignore action          |
| kill(pid?,sig?)        | Send signal to process id (default SIGTERM) |
| names()                | Return names of all signals                 |


[Socket](Reference#Socket)
----
The Socket extension provides access to TCP and UDP sockets, both client and server.

Following is a simple example.  First we run a server:

~~~~
var s = new Socket({server:true,onRecv:puts});
while (1) update();
~~~~

and in another terminial, a client.

~~~~
var s = new Socket({noAsync:true});
s.send('hello world\n');
~~~~

For another socket example see [sockdemo.jsi](../js-demos/sockdemo.jsi?mimetype=application/javascript).

[Sqlite](Reference#Sqlite)
----
See [Sqlite](Sqlite).


[System](Reference#System)
----
The System object contains all the built-in toplevel commands in Jsi
and are also exported as globals.


### exec
function exec(val:string, options:null|string|object=void)
:   The exec() command is used to execute operating system commands:

~~~~
var a = exec('ls -d /tmp');
~~~~

A second argument may be given with the following options:

| Option   | Type   | Description                                                  |
|----------|--------|--------------------------------------------------------------|
| bg       | BOOL   | Run command in background using system() and return OS code. |
| inputStr | STRING | Use string as input and return OS code.                      |
| noError  | BOOL   | Suppress OS errors.                                          |
| noTrim   | BOOL   | Do not trim trailing whitespace from output.                 |
| retAll   | BOOL   | Return the OS return code and data as an object.             |
| retCode  | BOOL   | Return only the OS return code.                              |


The first argument is treated as follows:

- If the command ends with '&', set the 'bg' option to true.
- If the second argument is null, set the 'noError' option to true.
- If the second argument is a string, the 'inputStr' option is set.
- By default, returns the string output, unless the 'bg', 'inputStr', 'retAll' or 'retCode' options are used

In C, when the bg option is true, system() is called, otherwise popen() is used.

For more examples see [exec test](../tests/exec.js).


### format
The sprintf command adds printf like functionality to javascript, eg.

~~~~
var s, me = 'Me', cnt = 9;
s = format('Help %s = %d!', me, cnt);
~~~~

This sometimes more convenient than the traditional:

~~~~
s = 'Help '* + me + *' = ' + cnt + '!';
~~~~

which ends up dealing with a lot of quotes and plus signs.
Format also simplifies aligned output:

~~~~
s = format('Help %-20s = %d!', me, cnt);
~~~~


### source
Include files of javascript inline.


[WebSocket](Reference#WebSocket)
----
The WebSocket extension uses libwebsockets to implement
bidirectional socket communication with a web browser.

When used in conjunction with [Sqlite](Sqlite) and [JSON](#builtins/json),
it is a simple matter to implement browser based applications.


The following creates a minimal client and server using WebSockets.
First the server file ws.js:

~~~~ JS linenumbers
function ws_input(data, id) {
    puts("ws_input: "* + id + *": " + data);
};

var ws = new WebSocket({callback:ws_input});
var msg = { str:"whos there?", cnt:0 };
while (true) {
    update(1);
    if ((msg.cnt++ % 10) == 0)
       ws.send(JSON.stringify(msg));
}
~~~~

Next the client file: wsc.js:

~~~~ JS linenumbers
function wsc_input(data) {
    puts("wsc_input: " + data);
};

var ws = new WebSocket({client:true, callback:wsc_input});
var msg = { str:"knock knock", cnt:0 };

while (true) {
    msg.cnt++;
    ws.send(JSON.stringify(msg));
    update(1);
}
~~~~

Which we run with:

~~~~ SH
jsish ws.js &
jsish wsc.js
~~~~

===
There are several ways to use Web in Jsi, all of which ultimately use
the builtin WebSocket api


[Zvfs](Reference#Zvfs)
----
Zvfs stands for Zip Virtual File System, which is used by Jsi to read and write zip files.

There are two important uses of Zvfs:

- Running scripts directly from .zip files.
- Running scripts zipped into the jsish binary (zero-install).


In both cases the Zip archive can contain all files (scripts, web pages, images, etc)
required by an application, and thus
can deploy complete working standalone applications.


### Executing Zips
Jsi can execute a zip archive that contains main.jsi  or  lib/main.jsi, eg:

~~~~
# jsish DebugUI.zip my.jsi arg1 arg2
~~~~

Jsi mounts the .zip file on /zvfs1, then executes main.jsi (or lib/main.jsi) therein.

Any other resources contained in the archive are also available to Jsi.


### Zero-Install
It is also possible to zip "main.jsi (and other files) directly onto the end of the jsish binary itself.
This lets jsish be used to deploy Zero-Install, standalone application.

The simplest way to create a Zero-Install application is to copy your scripts into lib/ then use:

~~~~
make jsize
~~~~

The direct approach uses the script "tools/mkjsize.js":

~~~~
cp jsish jsize
tools/mkjsize.js create jsize zipdir
~~~~

For further working example applications see [Start#start/examples].


### Writing
To create a zip archive use:
~~~~
  Zvfs.create('arch.zip', file.glob('*.js'))
~~~~
This creates a zip file containing all the .js files in the current directory.


### Reading
Jsi can mount .zip files as local filesystem:

~~~~
var dir = Zvfs.mount('arch.zip');
File.glob('*', dir);
~~~~

!!! NOTE
    If a mount point is not given, it is generated in the pattern /zvfsN, where N=1,2,3,...


[console](Reference#console)
----


log
---
Same as puts, except
prints arguments to stderr.

Each argument is quoted (unlike the built-in string concatenation).
If called with 0 or 1 argument, a newline is output, otherwise stderr is flushed.


### puts
Same as console.log, except
prints arguments to stdout.
Also, available in [System](Reference#System) (and therefore the toplevel).

The puts method outputs a string to stdout.
With 1 or 0 arguments a newline is appended.

~~~~
puts("Hello World");
puts("Hello"," World\n");
~~~~

Scripts also used in web browsers can add the following for compatibility:

~~~~
if (puts === undefined)
    var puts = console.log.bind(console);
~~~~


### input
User input can be obtained using console.input():

~~~~
puts("Enter your name: ", "");
var str = console.input();
~~~~


### args
Program arguments are available using console.args:

~~~~
for (var i in console.args) {
   puts(console.args[i]);
}
~~~~

C-API
-----
See [C-API](C-API).

Added doc/md/C-API.md.











































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
C-API
=====
Jsi has a largish Tcl-like C-API..
Some of this API (Jsi-Lite) is available to C programmers
without requiring the interpreter.
The best overview of this API is the header file [jsi.h](../src/jsi.h).

CData
----
See [CData](CData).

DBQuery
----
See [DBQuery](DBQuery).


DString
----
DString provides dynamic string functionality via a struct
which uses the stack when strings are short:

~~~~ C linenumbers
Jsi_DString d = {"Here is your score "};
puts(Jsi_DSPrintf(&d, "%s: -> %d/%d", user, n, m));
Jsi_DSFree(&d);
~~~~

Since the above strings are under 200 characters, no malloc call is required.

~~~~ C linenumbers
Jsi_DString d = {};
Jsi_DSPrintf(&d , "%0300d", 1); // Malloc
Jsi_DSSetLength(&d, 0);
Jsi_DSPrintf(&d , "%0300d", 1); // No-malloc
Jsi_DSFree(&d);
Jsi_DSPrintf(&d , "%0300d", 1); // Malloc
Jsi_DSFree(&d);
~~~~

Space is discared with Jsi_DSFree, and can be reused with Jsi_DSSetLength:

You can also use Jsi_DSInit():

~~~~ C linenumbers
Jsi_DString d;
Jsi_DSInit(&d);
Jsi_DSAppend(&d, "Some stuff", NULL);
Jsi_DSAppendLen(&d, "!", 1);
Jsi_DSFree(&d);
~~~~

### Function Summary
Here are the function signatures:

~~~~ C linenumbers
char*   Jsi_DSAppend(Jsi_DString *dsPtr, const char *str, ...);
char*   Jsi_DSAppendLen(Jsi_DString *dsPtr, const char *bytes, int length);
void    Jsi_DSFree(Jsi_DString *dsPtr);
char*   Jsi_DSFreeDup(Jsi_DString *dsPtr);
void    Jsi_DSInit(Jsi_DString *dsPtr);
uint    Jsi_DSLength(Jsi_DString *dsPtr);
char*   Jsi_DSPrintf(Jsi_DString *dsPtr, const char *fmt, ...);
char*   Jsi_DSSet(Jsi_DString *dsPtr, const char *str);
uint    Jsi_DSSetLength(Jsi_DString *dsPtr, uint length);
char*   Jsi_DSValue(Jsi_DString *dsPtr);
#define JSI_DSTRING_VAR(varPtr,size) //...
~~~~

and the details:

| Name            | Description                                                                                    |
|-----------------|------------------------------------------------------------------------------------------------|
| Jsi_DSAppend    | Append one or more string arguments (plus NULL sentinal).                                      |
| Jsi_DSAppendLen | Append a string of given length (or -1 for strlen).                                            |
| Jsi_DSFree      | Release allocated memory and sets variable back to re-initialized/empty.                       |
| Jsi_DSFreeDup   | Return malloced string, then calls Jsi_DSFree.                                                 |
| Jsi_DSInit      | Initialize the variable, ignoring current data therein.                                        |
| Jsi_DSLength    | Return the length.                                                                             |
| Jsi_DSPrintf    | Format output and append to DString. Returns string from the current printf.                   |
| Jsi_DSSet       | Same as Jsi_DSSetLength(dsPtr,0) plus Jsi_AppendLen.                                           |
| Jsi_DSSetLength | If &lt; current length truncates string. Else sets min allocated space. Return allocated size. |
| Jsi_DSValue     | Return string value.                                                                           |
| JSI_DSTRING_VAR | Macro that declares a large DString on the stack.                                              |



### Function Detail
The following contains detailed descriptions of the DString functions.

char   Jsi_DSAppend(Jsi_DString dsPtr, const char *str, ...)
: Calls Jsi_DSAppendLen for each string value argument, passing in -1 for the length.
  Each string is assumed to be null terminated and the final argument must be a NULL.

  RETURNS: The string starting at the first appended character.

char   Jsi_DSAppendLen(Jsi_DString dsPtr, const char *bytes, int length)
: Append length bytes to the DString. If length is &lt; 0,
  the value of strlen is used.  If required, the DString is realloced to
  be large enough to contain bytes, plus an extra null byte that is added to the end.

  RETURNS: The string starting at the first appended character.

void    Jsi_DSFree(Jsi_DString dsPtr)
: Frees any allocated space and sets the DString back to empty such that it is safe to exit the scope.
  Or the DString may be reused (also see Jsi_DSSetLength).

char   *Jsi_DSFreeDup(Jsi_DString dsPtr)
: Returns the malloced string value and resets the DString in the same way as Jsi_DSFree.
  This just avoids the user having to do an extra malloc/free if the DString was already malloced.
  It is then the responsibility of the caller to free the returned value.

  RETURNS: The string that was contained in the DString.

void    Jsi_DSInit(Jsi_DString dsPtr)
:  Initialize a DString.

uint     Jsi_DSLength(Jsi_DString dsPtr)
: RETURNS: The string length dsPtr->len.

char*  Jsi_DSPrintf(Jsi_DString dsPtr, const char *fmt, ...)
: Perform printf style string formatting as directed by the fmt string.
  Under the covers, this utilizes vsnprintf.

  RETURNS: The string starting at the first appended character.

char   *Jsi_DSSet(Jsi_DString *dsPtr, const char str)
: Same as calling Jsi_DSSetLength(dsPtr,0) followed by Jsi_DSAppendLen(dsPtr,str).
  Sets the DString to str without freeing any allocated space.

!!!WARNING
    It is not safe to exit the scope without first calling Jsi_DSFree.


uint    Jsi_DSSetLength(Jsi_DString *dsPtr, uint length)
: Depending on dsPtr->len, truncates a string or sets the minimum allocated space.

- If length is &lt; 0, does nothing and just returns the current size allocation.
- if length is &lt; current length, the string is truncated.
- Otherwise, enforces the allocated space is at least length.

!!! NOTE
    This will not set dsPtr->len unless truncating.
    Also an extra byte is always added to the allocation,
    but this is not reported in the allocated length.

  RETURNS: The currently allocated size. ie. the size of the maximum string that
  will fit without a call to realloc.

char*  Jsi_DSValue(Jsi_DString dsPtr)
: Gets the current string value.

  RETURNS: The string dsPtr->str.

#define JSI_DSTRING_VAR(varPtr,size) //...
: Declares a DString struct and pointer in the current stack frame.  See the next section on Large Strings.



### Large String Buffers
When working with larger strings, we may want to preallocate a large
string in order to avoid repeated calls to realloc() as the string grows.
The normal approach might be something like:

~~~~ C linenumbers
Jsi_DString dStr;
Jsi_DSInit(&dStr);
Jsi_DSSetLength(&dStr, 50000);
~~~~

Another alternative is to use the JSI_DSTRING_VAR macro, which avoids using malloc entirely.
JSI_DSTRING_VAR efficiently declares a Jsi_DString* pointing to an enlarged static DString upon the stack: eg:

~~~~ C linenumbers
JSI_DSTRING_VAR(dsPtr, 50000);
Jsi_DSPrintf(dsPtr, "%04999d", 1); // No malloc.
~~~~


### Comparison With C++
Consider C++ stringstream, which provides convenient dynamic string support with type safety.

~~~~ CPP linenumbers
std::stringstream str;
str << "ABC " << 123;
puts(str.str().c_str());
~~~~

The tradeoffs of stringstream are:

- Implicit memory allocation.
- Implicit code execution.
- Hard to inspect in gdb.
- Awkward to obtain the C string value.
- Not available in plain C-code.

DString provides familiar printf style formatting:

~~~~ C linenumbers
Jsi_DString dstr = {};
puts(Jsi_DSPrintf(&dstr, "ABC %d", 123));
~~~~

Printf style modifiers can be significantly simpler than stringstream:

~~~~ CPP linenumbers
Jsi_DSPrintf(&dstr, "%02d%-3d%04d", v1, v2, v3);
str << std::setfill('0') << std::setw(2) << v1
    << std::setfill(' ') << std::setw(3) << std::left  << v2
    << std::setfill('0') << std::setw(4) << std::right << v3;
~~~~


### Safety
The gcc compiler makes DString usage quite safe.

- It generates warnings for Jsi_DSPrintf argument mismatches.
- It warns when Jsi_DSAppend is missing NULL terminator.

There however are some gotchas to be aware of:

- DStrings greater than 200 bytes can not be assigned to one another.
- Failing to call Jsi_DSFree can leak memory.
- A DString has a maximum (compile time limit) of a 100 Meg.


JSON
----
Jsi implements a non-validating JSON parser.  That is, will accept JSON that
is not strictly compliant.


### High-Level


#### Jsi_JSONParse

The main function for parsing JSON to generate a Jsi_Value is:

~~~~
Jsi_RC Jsi_JSONParse(Jsi_Interp *interp, const char *js, Jsi_Value ret, int flags);
~~~~

If the bit flags field does not contain JSI_JSON_STRICT, the parser will accept
non-standard, unquoted property names, as in:

~~~~
{file:"FF", line:NN}
~~~~


#### Jsi_JSONParseFmt

This function is used primarily to reduce C-code.

~~~~ C
Jsi_RC Jsi_JSONParseFmt(Jsi_Interp *interp, Jsi_Value ret, const char *fmt, ...);
~~~~


Consider a simple example where we wish to return a javascript object:

~~~~ C
{file:"File", line:1}
~~~~

In C we would write something like:

~~~~ C linenumbers
Jsi_Obj *nobj;
Jsi_ValueMakeObject(interp, ret, nobj = Jsi_ObjectNew(interp));
Jsi_ObjInsertFromValue( interp, nobj, Jsi_ValueNewStringKey(interp, "file"),
    Jsi_ValueNewStringKey(interp, file));
Jsi_ObjInsertFromValue( interp, nobj, Jsi_ValueNewStringKey(interp, "line"),
   Jsi_ValueNewNumber(interp, line));
~~~~

Alternatively, this can be simplified to:

~~~~
return Jsi_JSONParse(interp, ret, "{file:\"%s\", line:%d}", file, line);
~~~~

The more deeply nested the object, the more code is simplified, eg:

~~~~
Jsi_JSONParseFmt(interp, ret, "{ a: [ {x:%d, y:%d}, {x:%d, y:[%d,%d,%d]}] }",a,b,c,d,e,f);
~~~~

Due to the efficiency nature of low-level parsing, this adds very little extra overhead.

This approach can save a lot of code, particularly when writing commands
that return nested objects and/or arrays.
.

!!! NOTE
    Permissive mode is the default.  So there is no need to quote property names which in C rapidly becomes tedious:

~~~~
"{\"os\":\"%s\", \"platform\":\"%s\", \"hasThreads\":%s, \"pointerSize\":%d, "
~~~~

!!!WARNING
    The above works only for cases where data does not contain special JSON characters.


### Low-Level
For the low level,
parses JSON into an array of tokens in a highly stack efficient manner.
It uses a single
array of tokens for output so that for limited size JSON strings,
no memory gets allocated during the parse.
When memory does get allocated, it is only to resize the token array.


### Sub-Interps
Any data sent between sub-[interps](Interp) will first be converted to/from JSON.
This because all data objects are private to an interp.

Jsi-Lite
----

Jsi-Lite is a subset of the Jsi C source code which can be used without the script engine.


### Jsi_DString
[Jsi_DString](#dstring) is available in Jsi-Lite.


### Jsi_Hash
This pages describes how to use Jsi_Hash.
Search for Jsi_Hash in [jsi.h](../jsi.h#Jsi_Hash) for details.

Hash provides simple hash table functionality.

~~~~ C linenumbers
int isNew;
Jsi_Hash *tbl = Jsi_HashNew(interp, JSI_KEYS_STRING, NULL);
hPtr = Jsi_HashEntryNew(tbl, "foo", &isNew);
Jsi_HashEntrySet(hPtr, 99);
Jsi_HashSet(tbl, "bar", 100);
Jsi_HashSearch search;
for (hPtr = Jsi_HashEntryFirst(tbl, &search);
    hPtr != NULL; hPtr = Jsi_HashEntryNext(&search)) {
    key = Jsi_HashKeyGet(hPtr);
    int n = Jsi_HashValueGet(hPtr);
}
~~~~

There are plenty of examples using Hash in the Jsi source code.


### Jsi_Tree
The underlying data structure for objects in JSI is a tree Red-Black trees with invariant node
pointers: nodes are allocated using a single malloc, including space for the key.
This introduces a problem in that varying string keys can not be copied between nodes,
which is required when re-balancing the tree. Although tree supports swapping node positions
instead of keys, objects instead use a key of type STRINGPTR, a combination Hash table and and Tree,
which is fairly efficient because objects often share keys.



### Jsi_List
Jsi_List implements a double linked list.
Not heavily used. Included mainly for completeness.


### Jsi_Map
Jsi_Map encapsulates Hash/Tree/List.  Allows switching
underlying implementation by changing a single declaration.


### Example Code
We use Jsi-Lite as follows:

~~~~ C linenumbers
#define JSI_LITE_ONLY
#include "jsi.c"
//Your code goes here.
~~~~


Following is some demo code:

- a minimal demo of Jsi-Lite: [../c-demos/litedemo.c].
- a demo of Jsi_List: [../c-demos/listdemo.c].
- a more comprehensive database demo: [../c-demos/dbdemo.c].


Options
----
A Jsi_OptionSpec array provides details of fields in a struct. This information is used for
translating values to/from and from C struct fields, both for Javascript and
by the [Sqlite C-API](DBQuery).

A Jsi_OptionSpec array specifies for a struct field the
name, type, offset, help string, etc.  This specification is passed, along with a pointer
to the actual data,
to Jsi_OptionProcess() and Jsi_OptionConf().


### Example Usage
The following example is a subset of the Jsi Sqlite command:

~~~~ C linenumbers
enum { MUTEX_DEFAULT, MUTEX_NONE, MUTEX_FULL };
static const char *mtxStrs[] = { "default", "none", "full", 0 };

static Jsi_OptionSpec Options[] =
{
    JSI_OPT(INT,    SqliteDb, debug,    .help="Set debugging level"),
    JSI_OPT(CUSTOM, SqliteDb, execOpts, .help="Default options for exec", .custom=&Jsi_OptSwitchSuboption, .data=ExecFmtOptions),

    JSI_OPT(INT,    SqliteDb, maxStmts, .help="Max cache size for compiled statements"),
    JSI_OPT(BOOL,   SqliteDb, readonly, .help="Database is readonly", .flags=JSI_OPT_INIT_ONLY ),
    JSI_OPT(VALUE,  SqliteDb, vfs,      .help="VFS to use", .flags=JSI_OPT_INIT_ONLY),
    JSI_OPT(CUSTOM, SqliteDb, mutex,    .help="Mutex type to use", .custom=&Jsi_OptSwitchEnum, .data=mtxStrs),
    JSI_OPT_END(SqliteDb)
};

  //...
if (Jsi_OptionsProcess(interp, Options, optObj, &opts, 0) < 0) {
     return JSI_ERROR;
}
if (opts.debug)
    puts("Sqlite created");
~~~~


### Field Specification
Options are specified using the JSI_OPT() macro. The first 3 arguments are TYPE,STRUCT,NAME,
where the NAME is a field name in STRUCT.  Additional attributes can be specified using ".name=value" form.

<a name=opttypes></a>
#### Option Types

The option TYPE is one of:

| Name      | C-Type      | Description                                                                   |
|-----------|-------------|-------------------------------------------------------------------------------|
| BOOL      | bool        | Boolean (stored in a "char" variable).                                        |
| INT       | int         | An integer.                                                                   |
| UINT      | uint        | An unsigned integer.                                                          |
| LONG      | long        | An long integer.                                                              |
| ULONG     | ulong       | An long unsigned integer.                                                     |
| SHORT     | short       | An short integer.                                                             |
| USHORT    | ushort      | An short unsigned integer.                                                    |
| INT8      | int8_t      | An 8-bit integer.                                                             |
| INT16     | int16_t     | A 16-bit integer.                                                             |
| INT32     | int32_t     | A 32-bit integer.                                                             |
| INT64     | int64_t     | A 64-bit integer.                                                             |
| UINT8     | uint8_t     | An unsigned 8-bit integer.                                                    |
| UINT16    | uint16_t    | An unsigned 16-bit integer.                                                   |
| UINT32    | uint32_t    | An unsigned 32-bit integer.                                                   |
| UINT64    | uint64_t    | An unsigned 64-bit integer.                                                   |
| SIZE_T    | size_t      | An size_t integer.                                                            |
| INTPTR_T  | intptr_t    | An pointer sized integer.                                                     |
| UINTPTR_T | uintptr_t   | An pointer sized unsigned integer.                                            |
| FLOAT     | float       | A floating point.                                                             |
| DOUBLE    | double      | Double floating point.                                                        |
| LDOUBLE   | ldouble     | Long double floating point.                                                   |
| DSTRING   | Jsi_DString | A Jsi_DString value.                                                          |
| STRKEY    | const char* | A char* string key.                                                           |
| STRBUF    | char[]      | A fixed size char string buffer.                                              |
| VALUE     | Jsi_Value*  | A Jsi_Value.                                                                  |
| STRING    | Jsi_Value*  | A Jsi_Value referring to a string.                                            |
| VAR       | Jsi_Value*  | A Jsi_Value referring to a variable.                                          |
| OBJ       | Jsi_Value*  | A Jsi_Value referring to an object.                                           |
| ARRAY     | Jsi_Value*  | A Jsi_Value referring to an array.                                            |
| FUNC      | Jsi_Value*  | A Jsi_Value referring to a function.                                          |
| TIME_T    | time_t      | A date variable, milliseconds since 1970 stored in a time_t/long.             |
| TIME_D    | double      | A date variable, milliseconds since 1970 stored in a Jsi_Number/double.       |
| TIME_W    | int64_t     | A date variable, milliseconds since 1970 stored in a Jsi_Wide/64 bit integer. |
| CUSTOM    | void*       | Custom parsed value.                                                          |


#### Flags
The following flags are predefined for individual items in an OptionSpec:

| Name                 | Description                                    |
|----------------------|------------------------------------------------|
| JSI_OPT_INIT_ONLY    | Allow set only at init, disallow update/conf   |
| JSI_OPT_NO_DUPVALUE  | Values/Strings are not to be duped (dangerous) |
| JSI_OPT_READ_ONLY    | Value can never be set.                        |
| JSI_OPT_DIRTY_FIELD  | Used for DB update.                            |
| JSI_OPT_IS_SPECIFIED | User set the option.                           |
| JSI_OPT_DB_IGNORE    | Field is not to be used for DB.                |
| JSI_OPT_CUST_NOCASE  | Ignore case (eg. for ENUM and BITSET)          |
| JSI_OPT_CUST_FLAG2   | Reserved for custom flags                      |
| JSI_OPT_CUST_FLAG2   | Reserved for custom flags                      |
| JSI_OPT_CUST_FLAG2   | Reserved for custom flags                      |


#### Custom Types
Custom types provide parsing/formatting functions to interpret javascript data to/from a C struct-field.
Again, the best example is the Jsi source itself.

The following predefined custom types come builtin:

| Name                     | Description                    | .data      |
|--------------------------|--------------------------------|------------|
| Jsi_OptSwitchEnum        | Enum match                     | stringlist |
| Jsi_OptSwitchBitset      | Set of zero or more enum match | stringlist |
| Jsi_OptSwitchValueVerify | Provide callback to verify     | Callback   |
| Jsi_OptSwitchSuboption   | Recursive sub-option support   | sub-Option |

Refer to the Jsi source for example usage and declarations.


#### OptionSpec Fields
The following fields are defined in Jsi_OptionSpec:

| Field   | Type              | Description                                                            |
|---------|-------------------|------------------------------------------------------------------------|
| type    | enum              | Field type (from above)                                                |
| name    | char*             | Name of field                                                          |
| offset  | int               | Offset in array                                                        |
| size    | const char*       | Sizeof of field.                                                       |
| iniVal  | union             | Used for compile-time validation                                       |
| flags   | int               | Flags (from above)                                                     |
| custom  | Jsi_OptionCustom* | Custom parsing struct                                                  |
| data    | void*             | User data for custom options.                                          |
| help    | char*             | Short 1 line help string.                                              |
| info    | char*             | Longer command description: re-define JSI_DETAIL macro to compile-out. |
| init    | const char*       | Initial string value (used by info.cmds).                              |
| extName | const char*       | External name: used by the DB interface.                               |

The first five fields are implicitly set by the JSI_OPT() macro.
The JSI_END() macro also sets extName.

All others are available for setting by the user with .FIELD=VALUE notation.


### Parse Flags
The following flags are available for use with Jsi_OptionProcess/Jsi_OptionConf:

| Name                  | Description                                                 |
|-----------------------|-------------------------------------------------------------|
| JSI_OPTS_IS_UPDATE    | This is an update/conf (do not reset the specified flags)   |
| JSI_OPTS_IGNORE_EXTRA | Ignore extra members not found in spec                      |
| JSI_OPTS_FORCE_STRICT | Complain about unknown options, even when noStrict is used. |
| JSI_OPTS_PREFIX       | Allow matching unique prefix of object members.             |
| JSI_OPTS_VERBOSE      | Dump verbose options                                        |


### Compile-time Validation
Compile-time validation checks option values to ensure that stated type and the field
type actually match.  If they don't, the program will likely crash at runtime.
The implementation uses an implicit assignment to iniVal which should
generate a compiler warning if there is a type mismatch.

Here is an example:

~~~~ C linenumbers
typedef struct {
    int val;
    //...
} MyData;

Jsi_OptionSpec opts[] = {
    JSI_OPT(STRING, MyData, val),
    JSI_OPT_END(    MyData)
};
~~~~

which should generate:

~~~~ the output
test.c:7:6: warning: initialization from incompatible pointer type [enabled by default]
test.c:7:6: warning: (near initialization for ‘opts[0].iniVal.ini_STRING’) [enabled by default]
~~~~


### Is Specified
When a user provides a value for an option, the flag JSI_OPT_IS_SPECIFIED is set in the option flags.
Call Jsi_OptionChanged to determine if an option was specified.

!!! NOTE
    The Jsi header file [jsi.h](../jsi.h#Jsi_OptionSpec) and source are best consulted for complete examples.

!!!WARNING
    There are known concurrency issues with this feature.

Added doc/md/CData.md.















































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
[CData](Reference#CData)
======

CData is used to defined structs whose field types
overlap the basic C types.  These can then be used to
provide type-checked objects for functions.

Structs can be defined in either javascript or C.
Either way, once defined structs use the same commands for accessing data.

JS-Code
----
To define structs in javascript we use the commands **CStruct** and **CEnum**.

Then a **CData** object can be used for type-checking a function:

~~~~ JS linenumbers
CEnum('Fruit', 'apple,banana,orange,grape');
CStruct('Bee', 'int class:4; int size:4=8; Fruit fruit;');

function bee(options:object) {
    var d  = new CData('Bee', options);
    return d.get();
    return self;
}

bug({size:2);
bug({size:3, male:true, fruit:'orange'});
bug({size:19});
~~~~

~~~~ SH
/tmp/bug.jsi:10: error: for bitfield option "size": invalid value (at or near "19")
~~~~

!!!
    Note:  CData with 2 args reports option errors from the invocation line.


### CEnum
There are 3 forms for enum definitions.

The simplest is the single-line form as above, which is split on comma:

~~~~
CEnum.define('Fruit', 'apple,banana,orange,grape=9');
~~~~

There is a multi-line form which is split on newlines, with comments extracted as help:

~~~~
CEnum.define('Vegetable', '
    corn=0, // My favorite
    peas=2, // Your favorite
    potato=-1
');
~~~~

And there is an object form:

~~~~
CEnum.define({name:'Herd'}, [
    {name:'sheep'},
    {name:'goat', value:3, help:'set a value'},
    {name:'cow'}]);
~~~~

Here are some test output from cdatatest.jsi:

~~~~
CEnum.names() ==> [ "Fruit", "Herd", "Vegetable" ]
CEnum.names('Fruit') ==> [ "apple", "banana", "orange", "grape" ]
CEnum.conf('Fruit') ==> { flags:0, help:null, idx:4, name:"Fruit" }
CEnum.conf('Fruit', 'idx') ==> 4
CEnum.find('Fruit', 1) ==> banana
CEnum.fieldconf('Fruit', 'banana') ==> { flags:0, help:null, idx:1, name:"banana", value:1 }
CEnum.fieldconf('Fruit', 'banana', 'value') ==> 1
CEnum.value('Fruit', 'apple') ==> 0
CEnum.get('Fruit') ==> { fields:[ { name:"apple", value:0 }, { name:"banana", value:1 }, { name:"orange", value:2 }, { name:"grape", value:9 } ], name:"Fruit" }
~~~~


### CStruct
As with CEnum, there are 3 forms of struct definitions.

The simplest is the single-line form as above, which is split on semicolon:

~~~~
CStruct.define('Bee', 'int s:4; int r:3; int t=8; Fruit k;');
~~~~

The multi-line form is split on newlines, with comments extracted as help:

~~~~
CStruct.define('Pest', '
    int x=3;    // int field.
    Bee b;      // A sub-struct
    int y=5;
');
~~~~

And there is an object form:

~~~~
CStruct.define({name:'Bkey', help:'Struct to use for a key'}, [
        {name:'a', type:'int', help:'first key field'},
        {name:'b', type:'int', help:'second key field'}]
);
~~~~


#### Field Type
The type of a struct field is one of:

- a base type (see below)
- a struct
- an array of base type or struct
- an enum
- a bitfield of an int type or enum
- a bitset of an enum (name ends in @)

Struct data is initialized to zero by default, but individual fields can be given a simple value using =.

Here are some test output from cdatatest.jsi:

~~~~
CStruct.conf('Bee') ==> { crc:0, flags:0, help:null, idx:4, name:"Bee", size:6, ssig:0, value:10 }
CStruct.conf('Bee','name') ==> Bee
CStruct.names() ==> [ "Bkey", "Pest", "Bee", "Flower" ]
CStruct.names('Bee') ==> []
CStruct.fieldconf('Bee', 't') ==> { arrSize:0, bits:0, boffset:7, flags:1, help:null, idx:2, info:null, init:8, name:"t", offset:1, size:4, type:"int" }
CStruct.fieldconf('Bee', 's', 'bits') ==> 4
CStruct.get('Bee') ==> { fields:[ { bitoffs:0, bitsize:4, id:"int", isbit:0, label:"", name:"s", offset:0, size:4 }, { bitoffs:4, bitsize:3, id:"int", isbit:0, label:"", name:"r", offset:0, size:4 }, { bitoffs:7, bitsize:0, id:"int", isbit:0, label:"", name:"t", offset:1, size:4 }, { bitoffs:39, bitsize:0, id:"Fruit", isbit:0, label:"", name:"k", offset:5, size:1 } ], name:"Bee", size:6 }
'
~~~~


### CType
CType is simply available to query available types: see [Option Types](C-API#c-api/options).

Here are some test output from cdatatest.jsi:

~~~~
CType.names().sort() ==> [ "ARRAY", "BOOL", "CUSTOM", "DOUBLE", "DSTRING", "FLOAT", "FUNC", "INT", "INT16", "INT32", "INT64", "INT8", "INTPTR_T", "LDOUBLE", "LONG", "NUMBER", "OBJ", "REGEXP", "SHORT", "SIZE_T", "SSIZE_T", "STRBUF", "STRING", "STRKEY", "TIME_D", "TIME_T", "TIME_W", "UINT", "UINT16", "UINT32", "UINT64", "UINT8", "UINTPTR_T", "ULONG", "USEROBJ", "USHORT", "VALUE", "VAR" ]
CType.names(true).sort() ==> [ "Bee", "Bkey", "Flower", "Fruit", "Herd", "Jsi_DString", "Jsi_Number", "Jsi_Strbuf", "Jsi_Value", "Pest", "Vegetable", "bool", "const char", "double", "float", "int", "int16_t", "int32_t", "int64_t", "int8_t", "intptr_t", "ldouble", "long", "short", "size_t", "ssize_t", "time_d", "time_t", "time_w", "uint", "uint16_t", "uint32_t", "uint64_t", "uint8_t", "uintptr_t", "ulong", "ushort" ]
CType.conf('INT') ==> { cName:"int", flags:0, fmt:"d", help:"Integer", idName:"INT", size:4, user:0, xfmt:"#x" }
CType.conf('INT','flags') ==> 0
~~~~


### CData
A basic JS use of CData to create and manipulate a single struct is:

~~~~
var alpha  = new CData('Bee');
alpha.get(null, 't');
alpha.set(null, 't', 4);
~~~~

Data is accessed with set/get/incr:

~~~~
alpha.get(null) ==> { k:"apple", r:0, s:0, t:8 }
alpha.get(null, 't') ==> 8
alpha.set(null, 't', 4) ==> undefined
alpha.incr(null, 't', 1) ==> 5
n=alpha.get(null, 't') ==> 5
~~~~

As above there is also an object form:

~~~~
var bees   = new CData({structName:'Bee', arrSize:10, help:'An array of bees'});
~~~~

The string form is the simplest way to create [maps](C-API#c-api/jsi-lite) and
[hashes](C-API#c-api/jsi-lite) of structs.

~~~~
var alpha, beta, tree, tree2, tree3, hash, hash2, hash3, pest, flower, n;
beta   = new CData('Bee[10]');      // Array
tree   = new CData('Bee{}');        // Map with string key
tree2  = new CData('Bee{0}');       // Map with number key
tree3  = new CData('Bee{@Bkey}');   // Map with struct key
hash   = new CData('Bee{#}');       // Hash with string key
hash2  = new CData('Bee{#0}');      // Hash with number key
hash3  = new CData('Bee{#@Bkey}');  // Hash with struct key
~~~~


which are indexed with a non-null key:

~~~~
beta.set(0, 't', 2);
tree.set('X', 't', 2);
hash.set('X', 't', 2);
~~~~


Struct keys are also supported:

~~~~
bkey={a:1,b:2} ==> { a:1, b:2 }
tree3.set(bkey, 't', 2) ==> undefined
tree3.get(bkey, 't') ==> 2
~~~~


### Database
Database definitions may be extracted from struct definitions and used with Sqlite:

~~~~
var db = new Sqlite('/tmp/bees.db') ==> "#Sqlite_1"
schema = CStruct.schema('Bee') ==>
  s int
 ,r int
 ,t INT
 ,k TEXT
  -- MD5=2c2573332fca5f166b7272366bd888b0
db.eval('CREATE TABLE IF NOT EXISTS Bee (
'+schema+'
);') ==> undefined
db.query('INSERT INTO Bee %s',{cdata:'beta'}) ==> 10
db.query('SELECT %s FROM Bee',{cdata:'beta'}) ==> 10
~~~~


### Limitations
JS-defined structs differ from C-defined ones in that they:

- are not visible to sub-interps,
- may be incompatible with actual C-structs due to packing differences, and
- not can not be used with C-extensions.


C-Code
----

Definitions in ".jsc"
files are preprocessed with "jsish -c".  The resulting ".h" output file contains
compile-ready code.

### Enums/Structs
Structs and enums are defined in "jsc" as follows:

~~~~ C
enum BeeType = {
    Drone,
    Worker,
    Queen
};

struct Bee = {
    int max;
    int buzzCnt;
    int stingCnt;
    int pollinateCnt;
    BeeType type;
    STRING32 flower;
};
~~~~

The available [struct field types](C-API#c-api/options) are:

~~~~ C
bool int8_t int16_t int32_t int64_t uint8_t uint16_t
uint32_t uint64_t float double ldouble Jsi_Strbuf time_w time_d time_t size_t
intptr_t uintptr_t Jsi_Number int uint long ulong short ushort Jsi_Value*
Jsi_DString Jsi_Value* Jsi_Value* Jsi_Value* Jsi_Value* Jsi_Value* Jsi_Value* Jsi_Value*
Jsi_Strkey const char*
~~~~


Also supported are "char" arrays and "STRING2", "STRING4", ...:
"jsi.h" has "STRING"n typedefs for every power of 2 up to 64k.

All other types will be ignored or, like "void *", are used for custom types.

### Commands
There are two types of extension-commands in Jsi:


 Name      | Description
-----------|----------------------------------------------------------
Object     | Object commands with constructors eg. 'new RegExp(/abc/)'
Non-object | Non-object commands, eg. 'Interp.conf()'

Here is an excerpt from the example [c-demos/cdata/Bee.jsc](../c-demos/cdata/Bee.jsc?mimetype=application/javascript):

~~~~
vars MyVars = {
    Bee Bee_Data;
};


extension Bee = { // Extension to create Bee commands.

    function conf(options:object|string=void):any { // Function to configure Bee options
        /* C code. */
    }

    function buzz(n1:number):number { // Buzz
        /* C-code. */
        Bee_Data.buzzCnt += n1;
        Jsi_Number n = Bee_Data.buzzCnt;
        RETURN(n);
    }

    function sting(victim:string, times:number):string { // Sting
        /* C-code. */
        char buf[BUFSIZ];
        Bee_Data.stingCnt += times;
        snprintf(buf, sizeof(buf), "stung %s %g times", victim, times);
        RETURN(buf); //:string
    }

    function pollinate(flower:string='daisy', times:number=1):void { // Pollinate
        /* C-code. */
        Bee_Data.pollinateCnt += times;
        Jsi_Stzcpy(Bee_Data.flower, flower);
        RETURN(); //:undefined
    }

};
~~~~

Note how [function types](Types#types/typenames) are employed to simplify the interface.
Next we generate it with:

~~~~
jsish -c -outFile Bee.h Bee.jsc
~~~~

To compile as a standalone we can use:

~~~~
gcc -o Bee -I../.. -x c Bee.h -lpthread -ldl -lm -lz -DCDATA_MAIN
./Bee
~~~~

Or compile as a shared library using:

~~~~
gcc -o Bee.so -I../.. -x c Bee.h -fPIC -shared -lpthread -ldl -lm -lz -DCDATA_MAIN=1 -DCDATA_SHARED=1
~~~~

And run it like so:

~~~~
"jsish"
require('Bee');
Bee.pollinate();
Bee.sting('dog', 2);
Bee.conf({type:'Worker', buzzCnt:3});
Cdata.get('Bee_Data');
~~~~

Other examples in c-demos/cdata are:

- [Car.jsc](../c-demos/cdata/Car.jsc?mimetype=application/javascript): An object command.
- [Baker.jsc](../c-demos/cdata/Baker.jsc?mimetype=application/javascript): A private var command.
- [cdatatest.jsi](../c-demos/cdata/cdatatest.jsi?mimetype=application/javascript): A test script for Bee, Baker and Car.


### Parameters
Parameters are unloaded into function-local vars based on their type (singular).
[Type Unions](Types#types/unions) are currently unsupported and simply ignored.

Several function names are treated specially:

- "conf": is the configuration command.
- a function with the same name as the object will be treated as an Object-Command.
- a function with a trailing "options:object" supports per-command options (see [Baker.jsc](../c-demos/cdata/Baker.jsc?mimetype=application/javascript).

Also if you define a "var" with the same name, but ending with "_Data" (eg. Bee_Data above)
you can access the state via "Cdata.get()" etc (non-object commands only).


### Returned Value
To return a function values, the "RETURN()" statement is used on a line by itself.
The function return type determines what actually gets returned.  As with parameters, this must not be a
"Type Union".


### Vars
The example in [c-demos/cdata/demo0.jsc](../c-demos/cdata/demo0.jsc?mimetype=application/javascript)
defines two struct "Foo" and "Bar".

~~~~ C
vars MyVars = {
    Bar     bar;        // Struct Bar.
    Foo     foo;        // Struct Foo.

    Foo     foos[10];   // Array of Foo structs.
    Foo     foos2[FooSize];// Array of Foo structs with enum size.
    Bar     barss[10];  // Array of Bar.

    Bar     bars{};     // Map-Tree with string-key.
    Bar     BN{0};      // Map-Tree with integer-key.
    Bar     Bs{@Fidx};   // Map-Tree with struct-key.

    Bar     Bs2{#};     // Map-Hash with string-key
    Bar     BN2{#0};    // Map-Hash with integer key.
    Bar     Bs2a{#@Fidx};// Map-Hash with struct-key.
};
~~~~

Three different types of var-structs are declared:

- Simple structs
- Fixed size arrays of structs, denoted by square brackets.
- Maps of structs, denoted by curley braces.

For maps:

- The map type defaults "tree", but can be over-ridden to "hash" {#}.
- The key type defaults to "string", but can be over-ridden as "integer" {0} or "struct" {@Name}.

A map can readily store large quantities of data, while arrays are useful for database queries.

Examples in c-demos/cdata using vars are:

- [demo1.jsc](../c-demos/cdata/demo1.jsc?mimetype=text/plain): A second set of definitions.
- [demo2.jsc](../c-demos/cdata/demo2.jsc?mimetype=text/plain): A third set of definitions.
- [demo.c](../c-demos/cdata/demo.c?mimetype=text/plain): Compile all three definitions into one program.
- [demotest.jsi](../c-demos/cdata/demotest.jsi?mimetype=text/plain): A test script for demo.c.


### Signatures
If the first field of a struct has the name "sig", it will be treated as a signature.
This is a special integer value used to identify the struct.  If not given a value,
it will be a crc32 value calculated against the struct definition string without its field names and comments.

Signatures play a special role in data sent over the network.


### JSC Definitions
Each definition in a ".jsc" file ends with a single close curley brace + semicolon at the start of line.

Added doc/md/DBQuery.md.









































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
Db-Query
========

Db-Query uses [CData](CData) to define data to exchange data between C-structs and Sqlite.

Supported are **SELECT, INSERT, UPDATE, REPLACE, DELETE** with:

- Automatic data conversions.
- Statement caching.
- Minimal C-code.
- Minimal heap/CPU use.
- Shortcut bind to all struct fields: "%s".
- 1-1 array/row mapping (via a "rowid" field).
- A **dirty** bit to limit write-back to modified rows.
- Efficient handling for most strings.
- Arrays of structs.
- Optional use of Javascript.


Usage
----
To start, declare a struct for inputs and outputs, eg:

~~~~ C linenumbers
typedef struct { int id; double max; /*...*/ } MyData;
~~~~

The struct is described with a [specification](C-API#c-api/options):

~~~~ C linenumbers
static Jsi_OptionSpec MyOptions[] = {
    JSI_OPT(INT,        MyData, id,     .help="Int id", .userData="DEFAULT 0 CHECK(id>0)"),
    JSI_OPT(DOUBLE,     MyData, max,    .help="Max value"),
}
~~~~

These are passed to Jsi_DbCarray:

~~~~ C linenumbers
int Jsi_DbCarray(Jsi_Db *jdb, Jsi_OptionSpec *spec, void *data, int numData, const char *query, Jsi_DbOpts *opts);
~~~~

For example, to retrieve the results of a query:

~~~~ C linenumbers
MyData mydata = {.id=1};
Jsi_DbCarray(jdb, spec, &mydata, 1, "SELECT %s FROM mytbl WHERE id = :id", NULL)
Jsi_DbCarray(jdb, spec, &mydata, 1, "INSERT INTO mytbl %s", NULL)
~~~~


To incorporate Jsi_DbCarray into an application,
[download](https://jsish.org/jsi/download), extract jsi.c and include like so:

~~~~ C linenumbers
#define JSI__SQLITE 1
#include <sqlite3.h>
#include "jsi.c"

int main(int argc, char *argv) {
  Jsi_Db *jdb = Jsi_DbNew(...);
  Jsi_DbCarray(jdb, ... );
}
~~~~

- For a simple but complete Jsi_DbCarray example see [Jsi-Lite demo](../c-demos/litedemo.c).
- For a complete working example see [../c-demos/dbdemo.c]*.


Example
----
Given the following database-table:

~~~~ SQL linenumbers
CREATE TABLE mytable (
    name,
    id INT,
    max FLOAT,
    myTime TIMEINT,
    mark,
    markSet,
    desc
);
~~~~

and a corresponding struct definition:

~~~~ C linenumbers
typedef struct {
    char name[16];
    int id;
    double max;
    int64 myTime;
    int mark;
    int markSet;
    Jsi_DString desc;
    int64 rowid;
    char dirty;
} MyData;
~~~~

Define a descriptor array of type [Jsi_OptionSpec](C-API#c-api/options):

~~~~ C linenumbers
static const char *markStrs[] = {"","A","B","C","D","F",NULL};

static Jsi_OptionSpec MyOptions[] = {
JSI_OPT(STRBUF, MyData, name, .help="Char buf",.userData="DEFAULT ''" ),
JSI_OPT(DSTRING,MyData,desc,.help="Description field"),
JSI_OPT(INT,MyData,id,.help="Int id",.userData="DEFAULT 0 CHECK(id>0)"),
JSI_OPT(DOUBLE,MyData,max,.help="Max value"),
JSI_OPT(DATETIME,MyData,myTime,.help="JS time int64_t",.userData="DEFAULT" ),
JSI_OPT(CUSTOM,MyData,mark,.help="",.custom=Jsi_Opt_SwitchEnum,.data=markStrs ),
JSI_OPT(CUSTOM,MyData,markSet,.help="A set",.custom=Jsi_Opt_SwitchBitset,.data=markStrs ),
JSI_OPT(WIDE,MyData,rowid,.help="DB rowid for update/insert; not stored in db",.flags=JSI_OPT_DB_ROWID"),
JSI_OPT(BOOL,MyData,isdirty,.help="Dirty flag: not stored in db",.flags=JSI_OPT_DB_DIRTY"),
JSI_OPT_END( MyData,.help="This is a struct for dbdemo")
};
~~~~

Which we can then use to store/load data by calling Jsi_DbCarray():

~~~~ C linenumbers
Jsi_Db *jdb = Jsi_DbNew("~/mytables.db", 0);
MyData d = {"myname", 99, 9.0};
Jsi_DbCarray(jdb, MyOptions, &d, 1, "INSERT INTO mytable %s", NULL);
Jsi_DbCarray(jdb, MyOptions, &d, 1, "SELECT %s FROM mytable", NULL);
Jsi_DbCarray(jdb, MyOptions, &d, 1, "UPDATE mytable SET %s", NULL);
Jsi_DbCarray(jdb, MyOptions, &d, 1, "SELECT id,name FROM mytable WHERE rowid=:rowid", NULL);
~~~~

The return value is either the number of rows loaded, modified or stored, or:

- -1 : an error occurs.
- -2 : the database was locked.

Ideally, a function wrapper is used to improve readability and type-checking:

~~~~ C linenumbers
int db_MyData(MyData *data, const char *query) {
    return Jsi_DbCarray(jdb, MyOptions, data, 1, query, NULL);
}

db_MyData(&d, "INSERT INTO mytable %s")
db_MyData(&d, "UPDATE mytable SET %s")
~~~~


Tables
----
The above example operated on a single row.
Multiple rows of data can be handled in a couple of ways.


### Single Struct
The brute-force way to process a table of data is to iterate over a single struct:

~~~~ C linenumbers
MyData mydata = {.id=99, .max=100.0, .mark=MARK_A, .markSet=6};
mydata.myTime = time(NULL)*1000LL;
strcpy(mydata.name, "maryjane");
Jsi_DSSet(&mydata.desc, "Some stuff");

Jsi_DbCarray(jdb, 0, 0, 0, ";BEGIN", NULL);
for (i=0, n=1; i<10 && n==1; i++) {
    mydata.id++;
    mydata.max--;
    n = Jsi_DbCarray(jdb, MyOptions, &mydata, 1, "INSERT INTO mytable %s", NULL);
}
Jsi_DbCarray(jdb, 0, 0, 0, ";COMMIT", NULL);
~~~~

While this works, it requires a lot of C-code.


### Arrays of Structs
A simpler way to process a table is via an array of structs, with a single call.
Three forms of array are supported:

#### Static Arrays

A static array has all storage pre-allocated by the user:

~~~~ C linenumbers
MyData mydatas[10];
int cnt = Jsi_DbCarray(jdb, MyOptions, mydatas, 10, "SELECT %s FROM mytable", NULL);

for (i=0; i < cnt; i++)
    mydatas[i].id += i;
n = Jsi_DbCarray(jdb, MyOptions, mydatas, cnt, "UPDATE mytable SET %s WHERE rowid = :rowid", NULL);
~~~~

This example does 2 things:
- SELECT to load up to 10 rows into the array.
- UPDATE to iteratively stores the rows after modification.

!!! NOTE
    The "BEGIN/COMMIT" is implicitly performed for updates and inserts.

#### Jsi_DbOpts Flags

Following are the option-flags for Jsi_Db commands:

~~~~ C linenumbers
typedef struct {
    bool ptrs:1;        /* Data is a fixed length array of pointers to (allocated) structs. */
    bool ptr_to_ptrs:1; /* Address of data array is passed, and this is resized to fit results. */
    bool mem_clear:1;   /* Before a query previously used data is reset to empty (eg. DStrings). */
    bool mem_free:1;    /* Reset as per mem_clear, then free data items (query may be empty). */
    bool dirty_only:1;  /* Used by sqlite UPDATE/INSERT/REPLACE. */
    bool no_begin_commit:1;/* Do not wrap UPDATE in BEGIN/COMMIT. */
    bool no_cache:1;    /* Do not cache statement. */
    bool no_static:1;   /* Do not bind text with SQLITE_STATIC. */
    uint reserved:24;       /* Reserved for future use. */
} Jsi_DbOpts;
~~~~

These are used in the following calls.

#### Static Array of Pointers

To use an array of pointers we pass the ptrs flag.

~~~~ C linenumbers
static MyData *mdPtr[10] = {};
Jsi_DbOpts opts = {.ptrs=1};
n = Jsi_DbCarray(jdb, MyOptions, mdPtr, 10, "SELECT %s FROM mytable", &opts);
~~~~

In this mode, memory is allocated on demand (up to the maximum size).

!!! NOTE
    The data argument is now an array of pointers to structs (void *).

#### Dynamic Array of Pointers

For fully dynamic allocation, both of the array and the struct pointers,
we pass the "ptr_to_ptrs" flag:

~~~~ C linenumbers
MyData dynPtr = NULL;
Jsi_DbOpts opts = {.ptr_to_ptrs=1};
n = Jsi_DbCarray(jdb, MyOptions, &dynPtr, 0, "SELECT %s FROM mytable WHERE rowid < 5", &opts);
n = Jsi_DbCarray(jdb, MyOptions, &dynPtr, n, "SELECT %s FROM mytable LIMIT 1000", &opts);
~~~~

This mode will manage an extra NULL pointer at the end of the array
to make the current length detectable
(ie. when the "num" parameter is 0).

!!! NOTE
    The data argument has changed again; it is now a pointer to an array of pointers (void *).


### Function Wrappers
The above examples have several drawbacks:

- the "data" argument is not type-checked.
- calls to Jsi_DbCarray takes a lot of arguments (6).

The first problem is easily demonstrated:

~~~~ C linenumbers
Jsi_DbCarray(jdb, MyOptions, "oops, not a struct", n, "SELECT %s FROM mytable", NULL);  // Invalid, but no compiler warning!
~~~~

The simplest solution defines wrapper functions:

~~~~ C linenumbers
int My_Stat(MyData *data, int num, const char *query, Jsi_DbOpts *opts) {
    return Jsi_DbCarray(jdbPtr, MyOptions, data, num, query, opts);
}

static int My_Semi(MyData data, int num, const char *query, Jsi_DbOpts *opts) {
    Jsi_DbOpts o = {};
    if (opts)
        o = *opts;
    o.ptrs = 1;
    return Jsi_DbCarray(jdbPtr, MyOptions, data, 0, query, &o);
}

static int My_Dyn(MyData data, int num, const char *query, Jsi_DbOpts *opts) {
    Jsi_DbOpts o = {};
    if (opts)
        o = *opts;
    o.ptr_to_ptrs = 1;
    return Jsi_DbCarray(jdbPtr, MyOptions, data, 0, query, &o);
}

My_Stat(mydatas, n, "SELECT %s FROM mytable;", NULL);
My_Semi(mdPtr,   n, "SELECT %s FROM mytable;", NULL);
My_Dyn (&dynPtr, n, "SELECT %s FROM mytable;", NULL);
~~~~

In addition to the adding type checking, the resulting code is simpler.

### Multi-Struct Bind
Multiple structs can be bound to,
using a different bind character for each:

~~~~ C linenumbers
int My_Bind(MyData *data1, int num, MyData *data2, const char *query)
{
    Jsi_CarrayBind mybinds[] = {
        { ':', MyOptions, data1, num }, // Input/output array.
        { '$', MyOptions, data2, 1 },   // Input single struct.
        {}
    };
    Jsi_DbOpts opts = {.ptrs=1, .binds=mybinds};
    n = Jsi_DbCarray(jdb, NULL, NULL, 0, query, &opts);
}

mydata.max = -1;
n = My_Bind(mdPtr, num, mydata, "SELECT %s FROM mytable WHERE rowid=:rowid AND max=$max");
~~~~

This causes a single bind of "$max" to
*mydata*, then repeated array-binds to ":rowid",
allowing us to avoid input/output data collisions.


Spec Fields
----
A spec is an array used to describe all or part of an struct using [options](C-API#c-api/options).


### Supported Types
For database access, spec option types are limited to:

Name    | C-Type     | Description
--------|------------|--------------------------
BOOL    | int        | Boolean (uses a "char" variable).
INT     | int        | An integer.
WIDE    | Jsi_Wide   | A 64-bit integer (Jsi_Wide).
DOUBLE  | Jsi_Number | Double floating point.
DSTRING | Jsi_DString| A Jsi_DString value.
STRKEY  | char*      | A char* string key.
STRBUF  | char[]     | A fixed size char string buffer.
STRING  | Jsi_String | A Jsi_Value referring to a string (when not using JSI_LITE_ONLY)
DATETIME| Jsi_Wide   | A date variable, milliseconds since 1970 stored in a 64 bit integer.
CUSTOM  |            | Custom types, including Enum and Bitmap.


### Custom
The custom type supports defining parametrized handlers using the ".custom" and ".data" fields.
Several predefined handlers are available:

#### Custom Enum

It is a common database practice to store numeric values in one table, and then use a foreign key to reference
the string value from another table.  This is certainly a useful abstraction,
but it does add complexity to the schema.
It also typically results in overhead from the use of joins, views and sub-selects.

The alternative is to store the string in the table.  But this requires conversion code
when we wish to use an enum in C.

Now consider the "marks" field from above:

~~~~ C linenumbers
typedef enum { MARK_NONE, MARK_A, MARK_B, MARK_C, MARK_D, MARK_F } MarkType;
...
static const char *markStrs[] = {"","A","B","C","D","F",NULL};
...
    JSI_OPT(CUSTOM,     MyData, mark,   .help="Marks", .custom=Jsi_Opt_SwitchEnum, .data=markStrs ),
...
~~~~

The definition ensures that the *marks* value is stored as integer in memory, and as string
in the database:  No manual conversion is required.

The JSI_OPT_NOCASE flag can be used ignore case in the database string value.

#### Custom Bitset

The Jsi_Opt_SwitchBitset option provides access multiple bits in one integer field.
This works similar to the above enum, except that the C stored values are bits set in an integer:

~~~~ C linenumbers
JSI_OPT(CUSTOM,     MyData, markSet,   .help="Marks set", .custom=Jsi_Opt_SwitchBitset, .data=markStrs ),
~~~~

But in the database they are stored as a list of string attributes:

~~~~ SQL linenumbers
# SELECT markSet FROM mytable;
"A B"
"B C D"
~~~~

Like enum, Jsi automatically provides the translation to/from strings.

The JSI_OPT_NOCASE flag can be used ignore case in the database string value.


### Field/Column Mapping
The .extName field is used to map a C field name to a different database column name:

~~~~ C linenumbers
JSI_OPT(STRBUF,     MyData, name,   .extName=struct ),
~~~~

ie. in this example, struct is a reserved word in C.


### NULL Values
For SELECT, the following Sqlite C-API rules apply for NULL sqlite_column values:

- DSTRING and STRBUF: an empty string.
- STRKEY: a NULL pointer.
- Otherwise, the value is "0".


### Dirty Field
A dirty field is used to limit which rows get stored, which is
substantially faster than updating every row in the table.
It is either a BOOL or INT field using the "dirty_only" option flag.

~~~~ C linenumbers
JSI_OPT(BOOL,       MyData, isdirty, .help="Dirty flag: not stored in db", .flags=JSI_OPT_DB_DIRTY),
~~~~

The call must be made with the "dirty_only" flag:

~~~~ C linenumbers
for (i=1; i<=3; i++) {
    mydatas[i].isdirty = 1;
    mydatas[i].id += 100*i;
}
Jsi_DbOpts opts = {.dirty_only=1};
n = QueryMyStat(mydatas, cnt, "UPDATE mytable SET %s WHERE rowid = :rowid", &opts);
~~~~

!!! NOTE
    Unless an error occurs, dirty fields are cleared by the call.


### Rowid Field
A field for storing the "rowid" is indicated by a "JSI_OPT_DB_ROWID" option flag.

~~~~ C linenumbers
JSI_OPT(WIDE,       MyData, rowid,  .help="DB rowid for update/insert; not stored in db", .flags=JSI_OPT_DB_ROWID),
~~~~

The field will not be stored back to the database, but will be loaded during a SELECT, for use
in query bindings to enforce a 1-1 mapping with the database, eg.

~~~~ C linenumbers
QueryMyStat(mydatas, cnt, "UPDATE mytable SET %s WHERE rowid == :rowid", 0);
~~~~


Javascript
----
Javascript is available when "jsi.c" is not compiled with JSI_LITE_ONLY,
and the appropriate initialization used.


### Javascript Initialization
The following shows how to initialize Jsi to have full access to Javascript and the
[database scripting API](Sqlite).

~~~~ C linenumbers
Jsi_EvalString(interp, "var mydb = "new Sqlite"('~/mytest.db');", 0);
Jsi_Db *jdb = Jsi_UserObjDataFromVar(interp, "mydb");
sqlite3 *db = Jsi_DbHandle(interp, jdb);
~~~~


### The Cdata Command
Javascript can access data arrays created in C-code using
the [Cdata](CData) command.

The first step is to use Jsi_CarrayRegister() making mdPtr available as mydata.

~~~~ C linenumbers
Jsi_DbOpts opts = {.ptrs=1};
Jsi_CarrayRegister(interp, mydata, MyOptions, mdPtr, num, &opts);
~~~~

Then we can index data array elements with:

~~~~ js linenumbers
Cdata.get( 'mydata', 0, 'max' );
Cdata.set( 'mydata', 1, {'max':99} );
~~~~

### Queries With Cdata
Javascript queries can use "Cdata" targets to load and store data:

~~~~ js linenumbers
db = new Sqlite('~/mytable.db');

size = db.query('SELECT %s FROM mytable', {Cdata:'mydata'});
for (i = 0; i < size; i++) {
    max = Cdata.get('mydata', i, 'max');
    max += i*100;
    Cdata.set('mydata', i, {max:max});
}
db.query('UPDATE %s FROM mytable', {Cdata:'mydata'});

Cdata.get('mydata', 0);
Cdata.size('mydata');      // Get array allocated size.
Cdata.info('mydata');      // Struct info.
~~~~


### Schema Generation
The Javascript command "Cdata.schema()" returns the schema for a Cdata definition,
for example to create a table:

~~~~ js linenumbers
db.query("CREATE TABLE newtable(" + Cdata.schema('mydata') + ")";
~~~~

which can then be used to access data:

~~~~ js linenumbers
db.query('INSERT %s INTO newtable', {Cdata:'mydata'});
db.query('SELECT %s FROM newtable', {Cdata:'mydata'});
~~~~

The schema for the above would look something like:

~~~~ js linenumbers
CREATE TABLE newtable(
-- 'MyData': This is a struct for dbdemo
  name "TEXT DEFAULT ''" -- Fixed size char buf
 ,desc "TEXT" -- Description field of arbitrary length
 ,id "INT DEFAULT 0 CHECK(id>0)" -- Int id
 ,max "FLOAT" -- Max value
 ,myTime "TIMEINT DEFAULT"(round((julianday('now') - 2440587.5)86400.0)) -- A unix/javascript time field in milliseconds (64 bits)
 ,mark "TEXT" -- Marks
 ,markSet "TEXT" -- A set
-- MD5: bc2a7cfc68725e60396dd2a2a4113f75
);
~~~~

The schema is generated by appending:

- The column name from the "extName" or the "name" field.
- The generated type.
- The "userData" field (eg. "DEFAULT 1 CHECK (id>0)")
- A DATETIME field with a .userData="DEFAULT" will init to the current time.
- Lastly, the "help" field as a comment (ie. after a "-- " string).

The schema is completed using fields from JSI_OPT_END:

- "help" is prepended as a comment.
- "userData" is appended (eg. ", FORIEGN KEY(id) REFERENCES tkey(tid)").
- And a calculated MD5-comment.


### Schema Checking
One advantage of using generated schemas is that it provides a way to deal with data changes.

The calculated MD5 value (which ignores ".help" data) can be used in
tracking database compatibility as in the following example:

~~~~ js linenumbers
var md5 = "Cdata.schemaMd5"(*'mydata'*)
var schema = "db.onecolumn"("SELECT sql FROM sqlite_master WHERE name='newtable' AND type=='table'");
if (!schema.match(*'MD5: '*+md5))
    FixUpData(*'mydata','newtable'*); // Fixup function: eg. export, recreate table and import data.
~~~~


### Configuration
Option configuration is available thus:

~~~~ js linenumbers
var mydb = "new Sqlite"(*'~/mytest.db'*, {maxStmts:100});
mydb.conf({maxStmts:1000});
~~~~


Performance
----
Performance of Jsi_DbCarray() should be similar to that of hand generated C-code.
Heap memory requests are avoided where possible.
Except for Sqlite internal memory requests, heap allocation occurs only when:

- A JSI_DString field exceeds 200 bytes.
- A query string exceeds 2000 bytes (including generated argument lists).
- A query is first added to the cache.
- ptr* flags are used.


Some overhead arises from using sqlite3_bind_parameter_name(),
and string concatenation which is used in binding list creation.
However, being stack orientated together with caching compiled queries
gives reasonably good performance.

Following is output from the million row benchmark included in the "dbdemo" program:

~~~~
./c-demos/dbdemo -benchmark
~~~~ the output
-1.000000
TESTING 1000000 ROWS
INIT C:    0.198 secs
   6.704 sec,   149164 rows/sec    INSERT 1000000 ROWS
   2.883 sec,   346860 rows/sec    SELECT 1000000 ROWS
  10.029 sec,    99710 rows/sec    UPDATE 1000000 ROWS, 1 FIELD
  10.754 sec,    92988 rows/sec    UPDATE 1000000 ROWS, ALL FIELDS
   4.381 sec,    22825 rows/sec    UPDATE 100000 DIRTY ROWS
   1.412 sec,      708 rows/sec    UPDATE 1000 DIRTY ROWS
   0.272 sec,       36 rows/sec    UPDATE 10 DIRTY ROWS
~~~~

The last 3 use dirty row updates to demonstrate keeping a database in sync
with minimal overhead.


Miscellaneous
----
### BEGIN/COMMIT
Normally, any write query with size greater than one will implicilty add BEGIN/COMMITs.
The *JSI_DB_NO_BEGINCOMMIT* flag can be used to disable this behaviour.

Also, the "BEGIN" or "COMMIT" string can be overridden at compile time like so:

~~~~ C linenumbers
#define JSI_DBQUERY_BEGIN_STR "BEGIN TRANSACTION"
#define JSI_DBQUERY_COMMIT_STR "COMMIT"
#include "jsi.c"
~~~~


### Error Handling
A custom error handler can be invoked upon error using:

~~~~ C linenumbers
#define JSI_DBQUERY_ERRORCMD MyErrHandler
#include "jsi.c"

int MyErrHandler(Jsi_Db *jdb, "Jsi_OptionSpec" *specs, void *data, int numData, const char *query, int flags, int rc) {
   printf("Error in query: %s\n", query);
   return rc;
}
~~~~


Sqlite-C: Traditional
----
The standard Sqlite has an easy to use C-API with two groups of functions:

- "sqlite3_bind_*()"
- "sqlite3_column_*()"

which are employed as follows:

~~~~ C linenumbers
stmt = sqlite3_prepare(db, "SELECT a,b FROM mytbl WHERE a=?1 AND b=?2", ...)
sqlite_bind_int(stmt, 1, mydata.b);

sqlite3_step(stmt);
mydata.a = sqlite3_column_int(stmt, 2);
~~~~

While this approach is trivial with small schemas,
code validation gets to be an issue as database complexity increases because:

- There is no simple *programmatic* way to enforce bind/column indexes will match a query.
- Type mis-matches (between database and C fields) can easily occur.
- Correct handling of Sqlite return codes gets increasingly complex.
- Code maintainence increases proportionly with the number of tables/columns/queries.

For example, here is an except from real world code:

~~~~ C linenumbers
stmt = sqlite3_prepare(db, "INSERT INTO descriptions VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,?20);");
~~~~

Handling bindings reliably for such queries becomes increasingly tedious.

Added doc/md/Debug.md.

































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
Debugger
========

There are two debuggers in Jsi.  The command-line one:
~~~~
jsish -d myscr.jsi arg1
~~~~

and a web GUI one:
~~~~
jsish -D myscr.jsi arg1
~~~~

The debugger will stop at "debugger" statements, and
anytime an error or warning occurs (ie. in "strict mode").


Debugging
---------
For [example](../tests/simple.jsi?mimetype=application/javascript), given this file:

~~~~ JS linenumbers
// Simple test script for the debugger.
function foo2() {
  debugger;
}

function foo1() {
  var x = 99;
  foo2();
}

function foo() {
  var x = 88;
  foo1();
}

foo();
puts("SIMPLE");
~~~~

here is a sample debugging session:


~~~~
jsish -d /tmp/simple.js
~~~~ the output
#1:/tmp/simple.js:16  foo();
#1==> s
CMD: step
#2:/tmp/simple.js:12    var x = 88; <in function foo()>
#2==>
#2:/tmp/simple.js:13    foo1(); <in function foo()>
#2==>
#3:/tmp/simple.js:7    var x = 99; <in function foo1()>
#3==> c
CMD: continue
#4:/tmp/simple.js:3    debugger; <in function foo2()>
#4==> up
CMD: up
#3:/tmp/simple.js:8    foo2(); <in function foo1()>
#3==> p x
CMD: print
RESULT= "99"
#3==> up
CMD: up
#2:/tmp/simple.js:13    foo1(); <in function foo()>
#2==> p x
CMD: print
RESULT= "88"
#2==>
~~~~

Note that just like gdb, an empty commands repeats the previous command.


### Listing
Source file lines can be displayed with the list command:

~~~~
jsish -d /tmp/simple.js
~~~~ the output
#1:/tmp/simple.js:16  foo();
#1==> c
CMD: continue
#4:/tmp/simple.js:3    debugger; <in function foo2()>
#2==> l 1 20
CMD: list
FILE: /tmp/simple.js:1
1    : // Simple test script for the debugger.
2    : function foo2() {
3    :   debugger;
4    : }
5    :
6    : function foo1() {
7    :   var x = 99;
8    :   foo2();
9    : }
10   :
11   : function foo() {
12   :   var x = 88;
~~~~

Optional arguments are: **startLine|func numLines file**

With no arguments the default is to list 10 lines starting from the current line
in the current file.


### Print
The print command can be used to display the value of variables:

~~~~
jsish -d /tmp/simple.js
~~~~ the output
#1:/tmp/simple.js:16  foo();
#1==> s
CMD: step
#2:/tmp/simple.js:12    var x = 88; <in function foo()>
#2==> s
CMD: step
#2:/tmp/simple.js:13    foo1(); <in function foo()>
#2==> p x
CMD: print
RESULT= "88"
~~~~


### Eval
The eval command can be used to evaluate expresssions or modify values:

~~~~
jsish -d /tmp/simple.js
~~~~ the output
#1:/tmp/simple.js:16  foo();
#1==> c
CMD: continue
#4:/tmp/simple.js:3    debugger; <in function foo2()>
#4==> up
CMD: up
#3:/tmp/simple.js:8    foo2(); <in function foo1()>
#3==> p x
CMD: print
RESULT= "99"
#3==> ev x=9
CMD: eval
RESULT= 9
#3==> p x
CMD: print
RESULT= "9"
~~~~


### Breakpoints
Breakpoints can be set, cleared and queried as in the following example:

~~~~
jsish -d /tmp/simple.js
~~~~ the output
#1:/tmp/simple.js:16  foo();
#1==> s
CMD: step
#2:/tmp/simple.js:12    var x = 88; <in function foo()>
#2==> b
CMD: break
breakpoint #1 set: /tmp/simple.js:12
#2==> b foo
CMD: break
breakpoint #2 set: foo
#2==> c
CMD: continue
Stopped at breakpoint #2
#2:/tmp/simple.js:13    foo1(); <in function foo()>
#2==> info
CMD: info
#1    : enabled=true, hits=0, file=/tmp/simple.js:12
#2    : enabled=true, hits=1, func=foo
#2==> dis 1
CMD: disable
#2==> info
CMD: info
#1    : enabled=false, hits=0, file=/tmp/simple.js:12
#2    : enabled=true,  hits=1, func=foo
~~~~

Note that the debugger will stop whenever a debugger statement is encountered.


### Where
The where command outputs a stack backtrace:

~~~~
jsish -d /tmp/simple.js
~~~~ the output
#1:/tmp/simple.js:16  foo();
#1==> c
CMD: continue
#4:/tmp/simple.js:3    debugger; <in function foo2()>
#4==> wh
CMD: where
{ fileName:"/tmp/simple.js", funcName:"foo2", level:4, line:3 }
{ fileName:"/tmp/simple.js", funcName:"foo1", level:3, line:8 }
{ fileName:"/tmp/simple.js", funcName:"foo", level:2, line:13 }
{ fileName:"/tmp/simple.js", funcName:"", level:1, line:16 }
#4==> up 2
CMD: up
#2:/tmp/simple.js:13    foo1(); <in function foo()>
#2==> wh
CMD: where
{ fileName:"/tmp/simple.js", funcName:"foo", level:2, line:13 }
{ fileName:"/tmp/simple.js", funcName:"", level:1, line:16 }
#2==>
~~~~

Commands
--------
The usual debugger commands are supported:

| Command  | Argument           | Description                                          |
|----------|--------------------|------------------------------------------------------|
| break    | file:line/func     | Break execution at a named function, line or file    |
| continue |                    | Continue execution                                   |
| delete   | id                 | Delete one or all breakpoints                        |
| disable  | id                 | Disable one or all breakpoints                       |
| down     | count              | Move down one or more stack levels                   |
| enable   | id                 | Enable one or all breakpoints                        |
| eval     | expression         | Evaluate expression in program context               |
| finish   |                    | Run till return of current function                  |
| help     | string             | Display command usage                                |
| info     | args/locals/bp/var | Show status info                                     |
| list     | proc/line count    | List file lines                                      |
| next     |                    | Single-step over function calls                      |
| print    | name               | Print value of variable                              |
| quit     |                    | Quit debugging current program and exit              |
| step     |                    | Single-step into function calls                      |
| tbreak   |                    | Set a temporary breakpoint that is disabled when hit |
| up       | count              | Move up one or more stack levels                     |
| where    |                    | Display current location                             |
| xeval    | expression         | Does eval in debugger context                        |



Implementation
--------------

The debugger runs the target script in a [sub-interpreter](Interp).

The sources are here:

- [Command-line](../lib/Jsi_Debug.jsi)
- [Gui](../lib/Jsi_DebugUI/Jsi_DebugUI.jsi)

!!! WARNING
    Jsi bends the memory management rules of Sub-interps a bit to make debugging mode work,
    which means a small amount of memory may leak
    during debug sessions.

Debugging Aids
----

### Strict Mode
Strict mode helps find bugs in scripts.

For files with the .jsi extension, strict-checking [functions](Types) is enabled.
It can also be enabled adding "use strict" to the top of the file:

~~~~ JS linenumbers
"use strict";
function foo (a:number, b:string='ok'):number {}
~~~~

Among other things, this will generate an [error](Types#types/checkconfig) when a function:

- accidentally creates a global variable by forgetting to use var
- gets called with the wrong number of parameters

This directive should go on the files first line (or second if there is a #!).
It is not supported inside individual functions.


### Error Diagnostics
Jsi does not generate tracebacks upon error.  Instead it provides gcc style warnings that
contain the file and line number.
For example:

~~~~
var x = 1;
foo(x)
~~~~ the output
/home/user/myjsi/foo.js:2: error: 'foo', sub-commands are: Array Boolean Date File
   Function Interp JSON Math Number Object RegExp Sqlite String Websocket alert
   assert clearInterval console decodeURI encodeURI exit file format include info
   isFinite isNaN load parseFloat parseInt puts quote setInterval setTimeout signal
   sys util zvfs.
~~~~

The file and line number is reported, as well as an enumeration of known commands
in the given scope.
This allows errors to be [parsable by IDE's](Start#start/editors).


### Method Introspection
Upon error, an objects sub-methods are enumerated, if possible:

~~~~
Info.xx()
~~~~ the output
error: 'info', sub-commands are: cmds data error events executable funcs
    named platform revisions script vars version.    (at or near string "xx")
~~~~

### Displayed Arguments

And arguments to builtin methods part of the diagnostic:

~~~~
format();
~~~~ the output
error: missing args, expected "format(format?,arg,arg?)"
~~~~

Added doc/md/Demos.md.























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
Demos
=====

Jsish can be seen in action in these online demos:

- [Ledger](https://jsish.org/App10/Ledger): a personal accounting app ([Docs](Ledger)).
- [Sandbox](https://jsish.org/App11/Sandbox): for testing jsi scripts in a web browser.

!!!
    Tested on Chrome, Firefox and Safari.

If you have downloaded Jsish, it comes with builtin applications and demos.
For example, to use the Jsi debugger with its Web-UI interface use:
~~~~
jsish -D tests/while2.jsi
~~~~

Or try the Sqlite-UI with:
~~~~
jsish -S test.db
~~~~

These demos all run from fossil, eg:

~~~~
jsish -a jsi-app.fossil Ledger
~~~~

Here are some additional examples:

~~~~ SH
jsish -w http://jsish.org/index.html; # Download file
jsish -W index.html; # Serve out an html file.
jsish -S mydata.db;  # Open the Sqlite-db GUI.
jsish -D script.jsi; # Run gui based Jsi debugger
~~~~

Note, because the implementation is in from /zvfs,
no access to the filesystem is required.




Added doc/md/Deploy.md.

















































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
Deploy
======
Jsi code is deployed as an archive (<b>zip, sqlar</b>) or <b>fossil</b> repo which:

- contains one or more applications.
- provides all files required by application.
- supports remote update, syncing and multiple versions (fossil only).


Typically there is a file "main.jsi", plus zero or more directories
of [packages](Develop#develop/files) which can be run:

~~~~SH
jsish -a jsi-app.zip Ledger
~~~~

Fossil
------
Fossil archives offer the ability for safe software update:


        *********************************
        *              .--------------. *
        *              |   Internet   | *
        * .---------.  |   .-----.    | *
        * | Browser |  |  | Fossil|   | *
        * '----+----'  |   '--+--'    | *
        *      ^       |      |       | *
        *      |       '------+-------' *
        *      |              |         *
        *      v              v         *
        *  .-------.     .---------.    *
        * | Jsish+  |    | App     |    *
        * | Zvfs    |<---+ Fossil  |    *
        *  '-------'     '---------'    *
        *********************************

Now when an application starts it can issue a fossil pull.
This means that potentially all versions of software are available
at program start-up.

But if a script update occurs that requires a newer version of Jsish, this could be dangerous.
However we can avoid this problem by having the Fossil archive tag releases not only
with version numbers, but also with Jsish prerequisites.  Thus the archive
manager can mount the latest compatible version.

This is what is meant by safe update.  Although all releases (past and present) may be available,
the latest safe one will be chosen for us automatically.
Or we can choose to fallback previous versions without re-installing.
Thus like /zvfs, dependency issues are avoided.

Publishing
==========
During development of an App-Archive, there is the additional step
of application changes getting pushed up to the repos, with appropriate
tags to enforce dependencies.

        *********************************
        *              .--------------. *
        *              |   Internet   | *
        * .---------.  |   .-----.    | *
        * | Browser |  |  | Fossil|   | *
        * '----+----'  |   '--+--'    | *
        *      ^       |      ^       | *
        *      |       '------+-------' *
        *      |              |         *
        *      v              v         *
        *  .-------.     .---------.    *
        * | Jsish+  |    | App     |    *
        * | Zvfs    |<-->+ Fossil  |    *
        *  '-------'     '---------'    *
        *********************************

The publish script pulls the dependencies automatically from the
app files and adds the requisite tags.

A fossil-deploy is of particular interest as it can:

- run client apps that keep in sync with a master.
- host development that remotely syncs up the master.
- serve out Internet web applications from a master.

Most importantly, deploys automatically use the latest available
version at startup.

!!!
    For an example see the [jsi-app](https://jsish.org/jsi-app) deploy which
    hosts the [online Ledger demo](https://jsish.org/App10/Ledger/html/main.htmli).

Jsish can run applications directly from downloaded fossil repositories, eg:

~~~~SH
fossil clone http://jsish.org/jsi-app jsi-app.fossil
jsish -a jsi-app.fossil Ledger --help
~~~~

### Updating
To bring a repository up-to-date use:

~~~~SH
fossil pull -R jsi-app.fossil
~~~~

Or run with the update option:

~~~~SH
jsish -a -update true jsi-app.fossil Ledger
~~~~

### Versioning
Jsish makes use of special tags in the repository to determine which checkin to mount:

  *  ver-M.N: a release version tag.
  *  prereq-M.N: version of Jsish required for a ver-M.N tag.

A "prereq" tag may be associated only to commits with a "ver" tag.

!!! NOTE
    Version tags use the float format, rather than "XX.YY.ZZ".

  *  ver-1.02 ==> "1.2"
  *  ver-1.0203 ==> "1.2.3"

The rules for determining the default version to mount are:

  *  No **prereq** or **ver** tags, the **trunk** is mounted.
  *  No **prereq** tags higher than Jsish version, the highest **ver** tag is mounted.
  *  Else, the highest **ver** found where **prereq** is not > Jsish version.

This means it is generally safe to keep repositories up-to-date
without worrying about Jsish dependancies.

Explicit versions can be mounted using:

~~~~SH
jsish -a -version ver-1.0 jsi-app.fossil Ledger
jsish -a -version 2018-09-07 jsi-app.fossil Ledger
~~~~

For a list of available tags use:

~~~~SH
fossil tag list -R jsi-app.fossil
~~~~

Using fossil provides many more benefits.  Such as easy checking out of source,
apply fixes, and generating dif files to submit bug fixes.
More on this to come.

Archive
-------
Jsi supports mounting archives, to serve out content:

        *****************************
        * .---------.               *
        * | Browser |               *
        * '----+----'               *
        *      ^                    *
        *      |                    *
        *      v                    *
        *  .-------.    .----+----. *
        * | Jsish+  |   | App     | *
        * | Zvfs    |<--+ Archive | *
        *  '-------'    '---------' *
        *****************************
        
Archives are of interest because they can quickly be pulled from a fossil repository.

### Zip
Zip archive support is the simplest and oldest supported archive.  It features:

  *  Native support (ie. mounts without "-a").
  *  Mounts are visible within sub-interps.
  *  Zip format is widely used.

To pull down the head revision of **jsi-app** use:

~~~~SH
wget -O jsi-app.zip http://jsish.org/jsi-app/zip
~~~~

or jsish can be used:

~~~~SH
jsish --wget -O jsi-app.zip http://jsish.org/jsi-app/zip
~~~~

Alternatively the [fossil UI](http://jsish.org/jsi-app) supports
"Zip Archive" download.

!!! NOTE
    One downside to the zip format is it's readonly as it does not support in-place updates.


### Sqlar
The [sqlar](https://www.sqlite.org/sqlar/doc/trunk/README) stores compressed files in a small
sqlite database.  Although it is not widely used it is more easily supports updating in-place.

You can pull down the head reversion of "jsi-app" with:

~~~~SH
wget -O jsi-app.zip http://jsish.org/jsi-app/zip
~~~~

The [fossil API](http://jsish.org/jsi-app) provides
"Sql Archive" download.

Security
----
Jsi code can **NOT** be executed remotely as in:

~~~~
jsish http://host.com/foo.jsi;  # Does not work.
~~~~
This would be a huge security hole.
Although it would be simple enough to implement.

Added doc/md/Develop.md.























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
Develop
========
Code in Jsish is usually developed as modules, which are
usable either by code or the command-line.


Modules
----

The most basic Jsi module is a function with array parameter:

~~~~ JS linenumbers
function add(args) { return args.join(', '); }
~~~~

When the file rootname matches the function name, we can invoke it with "-m":

~~~~
jsish -m add.jsi Able Baker Charlie
~~~~ the output
Able, Baker, Charlie
~~~~

Note "-m" is equivalent to adding an explicit **runModule()**:

~~~~ JS linenumbers
function add(args) { return args.join(', '); }
runModule(add);
~~~~


### Options
A Jsi module that can handle switches uses <b>parseOpts</b>:

~~~~ JS linenumbers
function add(args, conf) {
    var self = {};
    parseOpts(self, {start:0, name:''}, conf);
    return self.name+args.slice(self.start).join(', ');
}
runModule(add);
~~~~

This places leading switches into **self**:

~~~~
jsish foo3.jsi -name 'Dog: ' -start 1 Able Baker Charlie
~~~~ the output
Dog: Baker, Charlie
~~~~

A Jsi module can:

- be invoked from the command-line, accepting arguments and switches, or
- be called programatically by other modules as a [package](#provide).
- display it's available options when called with -h.


### Templates
Creating a new module with a template is simple, eg:
~~~~
jsish -m -create append.jsi
~~~~

which produces:

~~~~ JS linenumbers
#!/usr/bin/env jsish

function append(args:array, conf:object=void) {
    var options = { // A Jsi template module.
        rootdir      :''      // Root directory.
    };
    var self = {
    };
    parseOpts(self, options, conf);

    function main() {
        LogDebug('Starting');
        if (self.rootdir === '')
            self.rootdir=Info.scriptDir();
        debugger;
        LogDebug('Done');
    }

    return main();
}

provide(append, 1);
if (isMain())
    runModule(append);
~~~~

For web modules there is a special template:

~~~~
jsish -m -create httpd.jsi -web true
~~~~


### Help
Modules support "-h" and "--help":
~~~~
jsish add.jsi -h
~~~~ the output
/home/user/src/ii/add.jsi:3: help: ...
.  Options are:
     -name -start
Use --help for long help.
~~~~


#### Long Help
For longer help we need a more complete module with type-checking and comments:

~~~~ JS linenumbers
function add(args:array, conf:object=void) {
    var self = {};
    var options = { // Concat args into list.
        start:0,    // Start position.
        name:''     // Name prefix.
    };
    parseOpts(self, options, conf);
    return self.name+args.slice(self.start).join(', ');
}
runModule(add);
~~~~

Then comments will be extracted from **options**:

~~~~
jsish add.jsi --help
~~~~ the output
/home/user/src/ii/add.jsi:7: help: ...
Concat args into list..  Options are:
    -name       ""      // Name prefix.
    -start      0       // Start position.

Accepted by all .jsi modules: -Debug, -Trace, -Test.
~~~~


#### Help Limitations

Help information gets extracted from options by parseOpts, with the following limitations:

- The package name will be extracted from the file base name, except when:
- All comments must use the // form.
- The first comment (which must be after the opening {) is the help title.
- There can be no closing } anywhere in the body of the options or comments.



### Structure
The various sections of a module are:

| Section     | Description                                                                                                           |
|-------------|-----------------------------------------------------------------------------------------------------------------------|
| function    | All code is wrapped in a function with the same name as the file basename, providing bindings for incoming arguments. |
| options     | The var options = object itemizes the all options that can come in via the opts parameter.                            |
| self        | Local object state is stored in the self object. All defined and incoming options will be copied into self.           |
| parseOpts   | The call parseOpts(self, options, opts) performs option parsing as well as implementing -h help output                |
| provide     | The provide statement makes the module available as a package.                                                        |
| Info.isMain | The isMain() command returns true if the current file was invoked from the command-line.                       |
| runModule   | The runModule() command parses command-line options and then invokes the current module.                              |



Files
----
Various commands are involved in dealing with files of source code.

### Source
function source(val:string|array, options:object=void):void
:   include file of javascript.

    First argument is either a file or array of files.
~~~~ JS linenumbers
source('scripts/foo.js');
source(['foo.js'*,*'bar.js']);
~~~~


A second parameter may be used for setting the following options:


| Option | Type | Description                           |
|--------|------|---------------------------------------|
| debug  | INT  | Debug level.</td> </tr>               |
| auto   | BOOL | Setup for load of autoload.jsi files. |
| isMain | BOOL | Force isMain() to true.               |


Various source related options can be configured/examined in the usual way:

~~~~ JS linenumbers
Interp.conf({maxIncDepth:10});
var cur = Info.script();
var dir = Info.scriptDir();
~~~~

The above changes the recursive maximum depth of source from it's default of 100.


### Load
function load(shlib:string):void
:   load shared library extension into memory.

    On Windows, the extensions .dll is used instead of .so.

~~~~
load('libsqlitejs.so');
~~~~

For more details see [Extensions](CData).



### Provide
function provide(name:string, version:number=1):void
:   Used to make the current file name available for a **require**.

    A package file is of the form name.EXT or name/name.EXT;
    name may contain only the characters _ 0-9 a-z A-Z.

This file is either a Jsi script (.jsi) containing a provide(name) call,
or shared library (.so/.dll)
containing a Jsi_PkgProvide().

### Require
function require(name:string=void, version:number=1):number|array|object
:   Require is used to load/query packages.

    If a version argument is given, and it is greater than package version, a warning is generated.

Arguments:
- zero arguments: returns the list of all loaded package names.
- one argument: loads package code (if necessary) and returns its version.
- two arguments: as above but checks the version and returns the version and loadFile in an object.


Package files are searched for in the following paths:

- Any directory in the [pkgDirs](#libpaths) Interp option.
- The executable directory of jsish.
- The directory of the current script.

### PkgDirs
Packages are searched for are specified by pkgDirs, which can be set pkgDirs using:

~~~~
Interp.conf({pkgDirs:['/usr/local/lib', '/usr/share']});
~~~~

When a package is not found in pkgDirs, the executable and main script directories are checked.

!!! NOTE
    Sub-interps inherit pkgDirs from the parent.


### Autoload
Because it is sometimes desirable to defer loading code until first invocation,
Jsi provides a mechanism
using  member values in the object Jsi_Auto to evaluate.
For example, given the file myfunc.js:

~~~~ JS linenumbers
provide('myfunc');
function double(n) { return n*2; };
function half(n)   { return n/2; };
~~~~

We can arrange for dynamic sourcing with:

~~~~ JS linenumbers
Jsi_Auto.double =
Jsi_Auto.half   = 'require("myfunc");';

double(4); // Load is triggered!
half(4);
~~~~

This also works for object commands:

~~~~ JS linenumbers
Jsi_Auto.MySql = "require('MySql')";

//...
var db = new MySql({user:'root', database:'jsitest'});
~~~~


### AutoFiles
Jsi_Auto can be dynamically setup.

When Jsi first tries to invoke an undefined function, it first
traverses a list of files given in the interp option autoFiles.

The default jsish value is:

~~~~
Interp.conf('autoFiles');
~~~~ the output
[ "/zvfs/lib/autoload.jsi" ]
~~~~

Which means it loads the file [Zip-FS](../lib/autoload.jsi], usually from the self-mounted [b ./js-zvfs.md.html) on /zvfs.

After this Jsi_Auto members are accessed to load the function.

Additionally, if an application is run from a [.zip file](../Start#start/files), it's autoload.jsi
file gets added to autoFiles automatically.

This 2-stage approach results in no overhead, until a function call fails.

#### Object Functions
Autoloading non-constructor object functions can use the following approach
(the example ignores the fact that JSON is not actually loadable):

~~~~ JS linenumbers
var JSON = {
   parse:     function() { require('JSON'); return JSON.parse.apply(this,arguments); },
   stringify: function() { require('JSON'); return JSON.stringify.apply(this,arguments); }
   //...
}
JSON.parse(str); // Load is triggered!
~~~~

Although that works, it suffers from one problem: the first call will not type-check functions.

We could fix this shortcoming with:

~~~~ JS linenumbers
var JSON = {
    check: function(str:string, strict:boolean=false):boolean { require('JSON'); return JSON.check.apply(this,arguments); },
    //...
};
~~~~

But correctly transcribing function signatures is more complicated.

Fortunately there is a helper:
:  function Jsi_AutoMake(objName:string):string

    This can be used to generate the auto file:

~~~~
File.write( Jsi_AutoMake('JSON'), 'autoload.jsi');
~~~~

The file autoload.jsi now contains calls and load and type-check correctly:

~~~~ JS linenumbers
var JSON = {
    check: function(str:string, strict:boolean=true):boolean { require('JSON'); return JSON.check.apply(this,arguments); },
    parse: function(str:string, strict:boolean=true):any { require('JSON'); return JSON.parse.apply(this,arguments); },
    stringify: function(obj:object, strict:boolean=true):string { require('JSON'); return JSON.stringify.apply(this,arguments); },
};
~~~~


Debugging
----

### Logging
LogDebug statements appear only when a module is run with "-Debug":

~~~~
function foo(args, conf) {
    var self = {};
    parseOpts(self, {start:0, name:''}, conf);
    LogDebug('Starting', args);
    return self.name+args.slice(self.start).join(', ');
}
runModule(foo);
~~~~

~~~~
jsish foo.jsi -Debug true  a b
~~~~ the output
"DEBUG: Starting [ "a", "b" ]", foo.jsi:4, foo()
a, b
~~~~

Similarly for LogTrace and LogTest.

When logging is disabled, log op-Codes are ignored by Jsi, so no resources get used.


### Debugger
There are two debuggers.  The command-line:
~~~~
jsish -d myscr.jsi arg1
~~~~

And the Web-GUI:
~~~~
jsish -D myscr.jsi arg1
~~~~

See [debuggers](Debug).


### Execution Trace
Jsi provides a number of program tracing options.  Perhaps the easiest to use is from the command-line with -t:

~~~~
jsish -t tests/module.js
~~~~ the output
/home/user/jsi/jsi/tests/module.js:12 #1: > mod([])
/home/user/jsi/jsi/tests/module.js:12 #1: < mod()  <-- { process:function (a) {...}, x:1, y:2 }
/home/user/jsi/jsi/tests/module.js:22 #1: > process([ 9 ])
/home/user/jsi/jsi/tests/module.js:17   #2: > sub([ 10 ])
/home/user/jsi/jsi/tests/module.js:17   #2: < sub()  <-- 20
/home/user/jsi/jsi/tests/module.js:22 #1: < process()  <-- 20
20
1
/home/user/jsi/jsi/tests/module.js:36 #1: > fmod([])
/home/user/jsi/jsi/tests/module.js:36 #1: < fmod()  <-- { process:function (a) {...}, x:1, y:2 }
/home/user/jsi/jsi/tests/module.js:37 #1: > process([ 9 ])
/home/user/jsi/jsi/tests/module.js:31   #2: > sub([ 10 ])
/home/user/jsi/jsi/tests/module.js:31   #2: < sub()  <-- 20
/home/user/jsi/jsi/tests/module.js:37 #1: < process()  <-- 20
20
1
~~~~

The output may seem overly verbose, but is advantageous when executed
from within geany (or vim) in that we can click to navigate through the file.

If simpler traces are desired, try:

~~~~
jsish -ItraceCall funcs,args tests/module.js
~~~~ the output
#1: > mod([]) in module.js:12
#1: > process([ 9 ]) in module.js:22
  #2: > sub([ 10 ]) in module.js:17
20
1
#1: > fmod([]) in module.js:36
#1: > process([ 9 ]) in module.js:37
  #2: > sub([ 10 ]) in module.js:31
20
1
~~~~


### Code Profile
Jsi can output detailed execution profile information for functions using:

~~~~
jsish -Iprofile true  SCRIPT
~~~~

The following demonstrates this on unix:

~~~~
jsish -Iprofile true  /tmp/while2.js   2>&1 | grep ^PROFILE: | sort -g -r -t= -k2
~~~~ the output
PROFILE: TOTAL: time=4.169039, func=3.099403, cmd=1.068323, #funcs=10003, #cmds=300001, cover=58.0%, #values=1860447, #objs=610397
PROFILE:  self=3.000902  all=4.069200  #calls=10000     self/call=0.000300  all/call=0.000407  cover=100.0%  func=foo file=/tmp/while2.js:29
PROFILE:  self=1.068298  all=1.068298  #calls=300000    self/call=0.000004  all/call=0.000004  cmd=Info.funcs
PROFILE:  self=0.098484  all=4.167684  #calls=1         self/call=0.098484  all/call=4.167684  cover= 75.0%  func=bar file=/tmp/while2.js:44
PROFILE:  self=0.000024  all=0.000024  #calls=1         self/call=0.000024  all/call=0.000024  cmd=puts
PROFILE:  self=0.000017  all=4.167700  #calls=1         self/call=0.000017  all/call=4.167700  cover=100.0%  func=aa file=/tmp/while2.js:27
PROFILE:  self=0.000002  all=0.000002  #calls=1         self/call=0.000002  all/call=0.000002  cover=  7.0%  func=cc file=/tmp/while2.js:7
~~~~

All times are in seconds, and output is sorted by self time (descending).

Following is a list of fields in the PROFILE: TOTAL: line:

| Field   | Description                                     |
|---------|-------------------------------------------------|
| time    | Total amount of CPU used by the program run     |
| func    | Total mount of CPU used by functions            |
| cmd     | Total mount of CPU used by commands             |
| #funcs  | Total number of function calls                  |
| #cmds   | Total number of command calls (non-functions)   |
| cover   | Total code coverage in percent (functions only) |
| #values | Total number of Jsi_Value allocations           |
| #objs   | Total number of Jsi_Obj allocations             |

Following is a list of fields in each PROFILE line:

| Field     | Description                                            |
|-----------|--------------------------------------------------------|
| self      | Amount of CPU used by the function                     |
| all       | Amount of CPU used by function and it's descendants    |
| #calls    | Number of times function was called                    |
| self/call | Per-call CPU used by the function                      |
| all/call  | Per-call CPU used by the function and it's descendants |
| cover     | Code coverage for function, in percent                 |
| func      | Name of the function                                   |
| cmd       | Name of the command                                    |
| file      | File and line number of function                       |


### Code Coverage
In addition to the simple coverage statistics available with profile,
detailed code coverage can be obtained with -Icoverage, eg:

~~~~
jsish -Icoverage true  /tmp/while2.js   2>&1 | grep ^COVERAGE: | sort -g -r -t= -k4
~~~~ the output
COVERAGE: func=bar  file=/tmp/while2.js:48  cover=75.0%  hits=6,  all=8,  misses=56-57
COVERAGE: func=cc  file=/tmp/while2.js:7  cover=30.0%  hits=4,  all=13,  misses=10-13,18-22
COVERAGE: func=bb  file=/tmp/while2.js:27  cover=0%
~~~~

Output is produced only for functions with less than 100% coverage.
Uncalled functions are indicated by cover=0% with remaining fields omitted.

Following is a list of the COVERAGE fields:

| Field  | Description                                       |
|--------|---------------------------------------------------|
| func   | Name of the function                              |
| file   | File and line number for start of function        |
| cover  | Code coverage in percent for the function         |
| hits   | Number of distinct lines executed in the function |
| all    | Total number of executable lines in the function  |
| misses | List of line-ranges not executed                  |



Web
----

### Client
To download a file from the web we can use:
~~~~
jsish -w -O jsi-app.zip http://jsish.org/jsi-app/zip
~~~~

### Server
The builtin web server can open it in a
local browser and serve out html to it, from the command-line:
~~~~
jsish -W index.html
~~~~

and programmatically:
~~~~
Jsi_Websrv('index.html');
update(-1);
~~~~

Sqlite
----


### Usage

Basic Sqlite usage:
~~~~
var db = new Sqlite('mydata.db');
db.query('SELECT * FROM tbl');
~~~~ the output
[ { a:99, b:1 }, { a:95, b:2 }, { a:91, b:3 } ]
~~~~

GUI
---
The database GUI is invoked using:
~~~~
jsish -S mydata.db
~~~~

### Database Dump
To dump a database use:
~~~~
jsish -S -dump true mydata.db
~~~~
which produces output like the sqlite .dump command.


Applications
----


### Ledger
~~~~
fossil clone http://jsish.org/jsi-app jsi-app.fossil
jsish -a jsi-app.fossil Ledger
~~~~

Added doc/md/Index.md.





















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
Index
=====

| NAME                   | DESCRIPTION                    |
|------------------------|--------------------------------|
| [Builtin](Builtin)     | The built-in commands          |
| [C-API](C-API)         | The C API                      |
| [CData](CData)         | Using C structs in Jsi         |
| [DBQuery](DBQuery)     | Sqlite database queries from C |
| [Debug](Debug)         | Debugging in Jsi               |
| [Demos](Demos)         | Demo applications              |
| [Deploy](Deploy)       | Deploying Jsi apps             |
| [Develop](Develop)     | Developing with Jsi            |
| [Interp](Interp)       | The Interp API                 |
| [Jsish](Jsish)         | The Jsi shell                  |
| [Ledger](Ledger)       | Financial app developed in Jsi |
| [Logging](Logging)     | Logging support                |
| [Misc](Misc)           | Various topics                 |
| [MySql](MySql)         | MySql database API             |
| [Reference](Reference) | Built-in commands (generated)  |
| [Sqlite](Sqlite)       | Sqlite database API            |
| [Start](Start)         | Getting started with Jsi       |
| [Testing](Testing)     | Testing facility for scripts   |
| [Types](Types)         | Function parameter types       |

                  

Added doc/md/Interp.md.























































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
Interp
====
An interp encapsulates the run-time state of a javascript interpreter.

We can dynamically configure and/or query
interp [options](Reference#new InterpOptions) as follows:

~~~~
Interp.conf();
~~~~

~~~~
{ args:[  ], callTrace:0, compat:false, ... }
~~~~

In Jsi there are 3 ways to change an interps options:

- Programmatically within a script:

~~~~
Interp.conf({typeCheck:["error"]});
~~~~

- From the command-line using -I:

~~~~ SH
jsish -ItypeCheck error foo.s
~~~~

- Environment variables:

~~~~ SH
JSI_INTERP_OPTS='{typeCheck:["parse"]}' jsish foo.js
~~~~


Sub-Interp
----
A sub-interp provides a separate environment in which to run scripts, eg.

~~~~
var interp1 = new Interp();
interp1.source('myfile.jsi');
interp1.eval('myfunc();');
delete interp1;
~~~~

When we use eval to execute code in an interp,
execution is normally in the foreground.

To use asynchronous execution we set the second argument to true.

~~~~
var i = new Interp();
i.eval("doWork();", true);
//...
update(100);
~~~~

See the [Interp Reference](Reference#Interp) for interface details about creating and using sub-interps.


Safe-Mode
----
Sub-interps may be created with the isSafe option
for restricting access to the file system:

~~~~
var interp1 = new Interp({isSafe:true});
File.write('/tmp/stuff.txt', abc'); // This will throw an error...
~~~~

Additional options are available to relax these limitations, such as:

~~~~
var interp1 = new Interp({isSafe:true, safeWriteDirs:['/tmp'], , safeReadDirs:['/tmp']});
~~~~

From the command-line, safe execution of scripts can be invoked via -S:

~~~~ SH
jsish -S tests/while.js a b c
jsish -S tests/file.js a b c;  // kicks a file access error.
jsish -S         // With no arguments runs the safe interp in interactive mode
~~~~

The implemenatation is [here](../lib/Jsi_Safe.jsi).


Thread-Interps
--------------
A sub-interp can be created in a threaded:

~~~~
var interp1 = new Interp({subthread:true, scriptFile:'mythrd.js'});
interp1.eval("doWork();", true);
~~~~

!!! NOTE
    If a scriptFile/scriptStr option is not given at create time, the thread blocks waiting for events.

Using a non-asynchronous eval() on a threaded interp, will block the caller until the thread calls
sleep() or update() with sleep time.  The same goes for source() and uplevel().

Several Interp commands are not supported with threads, such as:

- alias()
- value()



Aliases
----

Interpreters can create alias commands with prepended arguments.

~~~~ JS linenumbers
function foo(a,b) {
  puts('A='+a+' B='+b);
}
foo(1,2);

Interp.alias('bar', foo, [3]);
bar(4);
~~~~

Similarly an alias can be set in a sub-interp.

~~~~ JS linenumbers
var i = new Interp();

function myAlias(interp,name,arg1,arg2) {
   puts('myAlias: interp name arg1 '+arg2);
}

function myAlias2(arg1,arg2) {
   puts('myAlias2: arg1 '+arg2);
}

i.alias('foo', myAlias, [i, 'foo']);
i.alias('bar', myAlias2,null);
puts(i.alias()); puts(i.alias('foo'));
puts(i.alias('foo', myAlias));
i.eval('bar(1,2)');
i.eval('var bb = {x:1};');
i.alias('bb.fig', myAlias, [i, 'bb.fig']);
i.eval('bb.fig(1)');
i.alias('bb.fig', myAlias, [i, 'bb.FIG']);
i.eval('bb.fig(1)');
puts(i.alias());
~~~~

To delete an aliases just redefine as null.

~~~~ JS linenumbers
i.alias('bb.fig', null, null);
puts(i.alias());
try { i.eval('bb.fig(1)'); } catch(e) { puts("CAUGHT ERROR: "+e); }; puts("OK");
~~~~

!!! WARNING
    Aliases can not be create in a threaded sub-interpreter.


Options
-------

When we create an interpreter, we can pass in an number of options.
The available options are described [here](Reference#new InterpOptions).

We can also set or get [options](Start#options) for the current interpreter in the usual way:

!!! NOTE
    Options marked as initOnly may only be set at interpreter creation time.

~~~~
Interp.conf(); // Dump all options
Interp.conf('strict'); // Get one option
Interp.conf({strict:false, maxDepth:99}); // Set one or more options.
~~~~


Events
------
Events in javascript are traditionally created by the standard setTimeout()/setInterval() methods.

In Jsi, various commands and extensions (eg. [websockets](Builtin#websocket)) implement asynchronous
features that use events.

Events explicitly get processed by calls to update().
For more details see [Events](Builtin#event).


Call
----
Due to overhead of parsing, eval()
is actually the least efficient way to run commands.

The two other, faster alternatives are call() and send().

call() will invoke a method that is already defined in an interp.

This is much more efficient as there is no parsing involved,
and just like eval() it takes an async argument.

~~~~
var cmd = "
    function recv2(s) { puts('recv2: '+s.toString()); return {x:8, y:9}; };
    thrd = Info.interp().thread;
    puts(thrd);
    puts('Child starting: '+(thrd?'true':'false'));
    while (thrd) {
        update(1000);
        puts('Child-loop');
    };
";

var cnt=0, i = new Interp({subthread:true, scriptStr:cmd});

Sys.sleep(1000);
var obj = {AA:1,BB:2};
var aobj = [obj];
var ret;
while (cnt<10) {
  update(100);
  puts("Main-loop");
  if (((cnt++)%3)==0)
      i.eval("puts('Hello from main!!!!!!!!!!!!!!!!!!!!!!!!');");
  ret = i.call('recv2',aobj);
  puts(ret);
}
~~~~


Send
----
send() is used to pass messages via the function named
in the recvCallback option of the interps.

This asynchronous function, which simply receives an array of messages,
is actually the most efficient way to communicate between interps.

~~~~ JS linenumbers
var cmd = *"
    function recv(s) { puts('recv: '+s.toString()); };
    thrd = Info.interp().thread;
    puts(thrd);
    puts('Child starting: '+(thrd?'true':'false'));
    while (thrd) {
        update(500);
        puts('Child-loop');
        Interp.send({abc:1});
    };
"*;

function myrecv(s) { puts('>>>>>>>>>>MYRECV: '+s.toString()); };
Interp.conf({recvCallback:'myrecv'});

var cnt=0, i = new Interp({subthread:true, scriptStr:cmd, recvCallback:'recv'});

Sys.sleep(1000);
var obj = {AA:1,BB:2};
var aobj = [obj];
while (cnt<10) {
  update(100);
  puts("Main-loop");
  i.send(obj);
}
~~~~


Interps and Data
----------------
To reduce coupling and increase integrity within interpreters, data objects are never shared.

To accomplish this, all data is internally converted to and from [JSON](Builtin#json) at the Interp-Interp interface.

This all happens automatically, so normally users don't need to be worry about the details.

But there are potential performance issues with this JSON translation.



Environment
-----------
The environment variable JSI_INTERP_OPTS can be used for running scripts
with certain Interp options set:

~~~~ SH
JSI_INTERP_OPTS="{memDebug:1, maxOpCnt:1000000}" jsish myscript.js
~~~~

This is important as it allows options to be set before an interpreter starts.

For example, in the case of memDebug, it is vital that no Value memory be allocated before
hand, otherwise detection of memory leaks will not be accurate.

Thus this is the only way this particular option can be set.


Backward Compatibility
----------------------

The compat option is provided to support backward compatibility with code written
which uses options available in future releases of Jsi. Running such code with an older
Jsi can cause script parse errors due to unknown options. We can prevent this by adding to the script:

~~~~
Interp.conf({compat:true});
~~~~

This provides an alternative to adding conditionals with Info.version().

The application may still not work correctly, but at least parsing won't fail.

Added doc/md/Jsish.md.









































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
Jsish
=====
Jsish is a self-contained, dependency-free javascript interpreter and Web development environment.
No need to learn a new language:
its just javascript enhanced with 
[parameter types](Types), and everything required for web application development built-in.

    *********************************
    * .---------.                   *
    * | Browser |                   *
    * '----+----'                   *
    *      ^         .------.       *
    *      |   .--->|Database|      *
    *      v  |      '------'       *
    *  .------+.    .----+----.     *
    * |  Jsish  +   | Script  |     *
    * +---------+<--+  File   |     *
    * |  ZVFS   |   '---------'     *
    *  '-------'                    *
    *                               *
    *********************************

WebSocket and Sqlite are compiled in, and
support scripts are bundled in a ZVFS appended to the binary,
so writing code is easier, and deployment is trivial.


Deploys
------
A [Deploy](Deploy) is a zip/sqlar archive or fossil
repository containing one or more applications, which Jsi can mount and execute.

For example, this [Ledger](https://jsish.org/App10/Ledger) demo is
served out as follows
using [jsi-app](https://jsish.org/jsi-app) fossil
(via an Nginx reverse proxy).

        *************************************
        *  .-----------------------------.  *
        *  |     Server running Nginx    |  *
        *  | .---------.      .-----.    |  *
        *  | |  Jsish  |<----+ Fossil|   |  *
        *  | '----+----'      '--+--'    |  *
        *  |      ^              ^       |  *
        *  '------+--------------+-------'  *
        *         |              |          *
        *         v       :      |          *
        *    .---------.  :   .--+--.       *
        *    | Browser |  :  | Fossil|      *
        *    '----+----'  :   '--+--'       *
        *       User      :  Developer      *
        *                 :                 *
        *************************************

But deploying any application inevitably entails dealing with version dependencies.
Jsi handles this in a novel way by mounting **tagged** releases from a fossil repository.
If an application restart is set to automatically update the repository,
it ensures the latest supported release always gets run.
New code may be committed by developers at any time,
but only tagged releases will be used.


Security
----
As Jsi is self contained, running it standalone in a chroot-jail needs only
a few files from /dev and /etc.   For example, this [Ledger](https://jsish.org/App01/Ledger)
demo is run in an unprivileged chroot containing a single executable (*jsish*),
with non-data files made immutable with **chattr**.
And if this is not secure enough, Jsi offers [Safe-mode](Interp#interp/safe-mode).

In addition, serving content from a zip or fossil repository adds
another abstraction layer, making it that much harder to corrupt or hijack content.

An advantage of the chroot approach as compared with something like containers, is
that far less disk space and system memory is required.
Jsi and fossil together total around 10Meg of disk. Using hard links you
could create hundreds of chrooted apps with little-to-no additional disk space.
But more important is the 
reduction in [Attack Surface](https://en.wikipedia.org/wiki/Attack_surface).
There is far less code involved and far less complexity.


C-API
----
For embedded developers, Jsi provides a [C-API](C-API) that simplifies
connecting low level (**C**) with high level (**GUI**) code.
The use of C-Structs is intrinsically integrated into Jsi at all levels,
and is the main mechanism used for module state, option parsing and
C extensions.
This direct
interfacing with C-structs can potential be used to process very
large amounts of data with little overhead.
This includes the Sqlite interface which also supports mass data transfers to/from structs,
which is of particular importance for embedded applications.

The C coding aspect of Jsi however is purely optional. [Ledger](Ledger) and the
other demo applications neither use nor require it.



Added doc/md/Ledger.md.





























































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
Ledger
======

[Jsi](https://jsish.org/jsi) Ledger is a simple Web-UI based accounting program.

Account and transaction data is stored in an Sqlite database, which
is backed up as an exported checked into a fossil repository.

The database can be inspected via menu **Help/Database**.

Keyboard Shortcuts
------------------

- Alt-n : Create a new transaction
- Alt-a : Move ahead to next page of transactions
- Alt-b : Move back to previous page of transactions


Sorting
-------

To sort by columns, click on the column header.  Clicking again will
reverse the sort direction.

Search
------

An input shows the row offset in transactions.  This can be edited to
skip a specific number of rows.

The input can be prefixed (before the colon) by a pattern.
If pattern contains a "*", it uses case-sensitive **GLOB** matching.
Otherwise **LIKE** matching will be used, and if there is no "%" already,
it weill be added to the begining and end.

Reports
-------

Reports available under **Admin** include:

- Account Summary
- Trial Balance
- General Ledger
- Totals by Payee
- Reconcilation

Added doc/md/Logging.md.

















































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
Logging
=======

The low level output commands are:

| Command      | Description                                          |
|--------------|------------------------------------------------------|
| puts         | Quotes arguments to stdout                           |
| printf       | Formatted output to stdout                           |
| log          | Like puts but including file/line info               |
| console.puts | Like puts but outputs to stderr                      |
| console.log  | Like log but outputs to stderr                       |
| assert       | When expression is false throw error (or output msg) |

See [System](Reference#System) and [console](Reference#console) for details.
All commands except printf automatically add a newline.

puts(...)
:  Quotes all arguments and outputs them to stdout.

~~~~ js linenumbers
puts("Batman begins");
var X = {a:1, b:"two"};
puts(X);
puts(X.a, X.b);
~~~~ the output
Batman begins
{ a:1, b:"two" }
1 two
~~~~

printf(fmt,...)
: Processes arguments according to format and outputs to stdout.

~~~~
printf("Batman begins: %d %S\n", X.a, X.b);
~~~~


log(arg, ...)
: Works like **puts**, but includes the current file, line and function.

~~~~
log("Batman begins");
~~~~ the output
"Batman begins", file.jsi:12, func()
~~~~

assert( expr:boolean|number|function, msg:string, options:object=void)
:   The assert command is used for constraint checking.
    When disabled (the default) none of it's arguments get evaluted.

    When enabled and the expression evaluates to false, an error is thrown.

    When expression is a function, its returned value is used instead.


~~~~ js linenumbers
"use assert";
var m = 1, n = -1;
assert(m>0, "too small");
assert(n>=0 && n<100, "passed bad n", true);
~~~~

In fact there are several wasy to make assert use puts rather than throwing errors:

~~~~
jsish -T asserts
~~~~ the output
var m = 0;
assert(++m<0, 'bad shit', {mode:'puts'});
puts(m);
Interp.conf({assertMode:'puts'});
assert(++m<0, 'not bad');
assert(++m<0);
~~~~ the output
bad shit
1
not bad
assert(++m<0)
~~~~


It can also be made to output a message rather than throw an error.


LogDebug
--------

The main logging and debugging commands in Jsi are of the form **LogXXX**.

There output in Jsi include the file and line number, which is useful for
debugging applications:

~~~~ JS linenumbers
var i = 0;
while  (i++ < 3) {
    LogInfo("test loop: %d", i);
}
~~~~ the output
mytest.jsi:3, "INFO:  test loop: 1",
mytest.jsi:3, "INFO:  test loop: 2",
mytest.jsi:3, "INFO:  test loop: 3",
~~~~

One place this is useful is when running scripts from [geany](Start#geany), as you can navigate
through the messages just as you would compiler warnings.

The available Log commands are:

| Command  | Notes                                               |
|----------|-----------------------------------------------------|
| LogDebug | Outputs only when [logOpts.Debug](#logopts) is true |
| LogTrace | Outputs only when logOpts.Trace is true             |
| LogTest  | Outputs only when logOpts.Test is true              |
| LogInfo  |                                                     |
| LogWarn  |                                                     |
| LogError |                                                     |



Debug Logging
-------------
The commands LogDebug, LogTrace, and LogTest are special in that
unless enabled they produce no output (and in fact their op-codes are ignored).

We use enable LogDebug with "use Debug" as in this [example](../js-demos/log/mydebug.jsi):

~~~~ JS linenumbers
"use strict,Debug,Test";
var i = 0;
while  (i++ < 2) {
    LogDebug("test loop:", i);
    LogTrace("test loop:", i);
    LogTest("test loop:", i);
}
~~~~ the output
mydebug.jsi:4, "DEBUG: test loop: 1",
mydebug.jsi:5, "TRACE: test loop: 1",
mydebug.jsi:4, "DEBUG: test loop: 2",
mydebug.jsi:5, "TRACE: test loop: 2",
~~~~

In the discussion below, descriptions mentioning LogDebug
apply equally to LogTrace, and LogTest.


Enabling Logs
-------------
As shown above, one way logging can be enable is by inserting a use directive.

Modules directly accept the **-Debug** option, etc.
But there are several other ways from the command-line:

~~~~ js linenumbers
jsish -T Debug,Trace mydebug2.jsi
JSI_INTERP_OPTS='{logOpts:{Debug:true,Trace:true}}' jsish mydebug2.jsi
~~~~

!!! NOTE
    A "use" directive will always override command-line options:

~~~~ js linenumbers
jsish -T Debug
"use !Debug";
LogDebug("can't appear");
Interp.conf({logOpts:{Debug:true}});
LogDebug("can appear");
~~~~


Ignored Op-Codes
----------------
It is important to understand that when disabled, assert and the LogDebug, LogTrace and LogTest
functions and their arguments are essentially No-Ops:

~~~~ JS linenumbers
var i = 0, cnt = 0;
while  (i++ < 3) {
    LogDebug("test loop:", i, ":", cnt++);
}
printf("cnt=%d\n", cnt);
~~~~

Which can be confusing when **cnt** is 0 here:

~~~~
jsish myincr.jsi
~~~~ the output
cnt=0
~~~~

but **cnt** is 3 here:

~~~~
jsish -T Debug myincr.jsi
~~~~ the output
myincr.jsi:3, "DEBUG: test loop: 1 : 0",
myincr.jsi:3, "DEBUG: test loop: 2 : 1",
myincr.jsi:3, "DEBUG: test loop: 3 : 2",
cnt=3
~~~~

The explanation is that when disabled, argument op-codes are simply being ignored.

While potentially confusing it means that these commands exact no runtime penalty until enabled,
and so can be scattered liberally throughout code.


Module Logging
--------------
In a larger application it may be undesirable to turn on global logging.
Which is where module-local logging comes in.

Here is a minimal [module](Develop#modules) ([mycall](../js-demos/log/mycall.jsi)):

~~~~ JS linenumbers
function mycall(args:array=void, conf:object=void) {
    var options = {};
    var self = { cnt:0 };
    parseOpts(self,options,conf);

    LogDebug("MYLOC 1:", args.join(','));
    LogTrace("MYLOC 2:", args.join(','));
}

LogDebug("MYGLOB 1");
LogTrace("MYGLOB 2");
provide();
if (isMain())
    runModule(mycall);
~~~~

which we run with local logging:

~~~~ SH
jsish mycall.jsi -Debug true A B C
~~~~ the output
mycall.jsi:6,   "DEBUG: test 1: A,B,C", mycall()

jsish -T Debug mycall.jsi -Trace true A B C
mycall.jsi:10,  "DEBUG: MYGLOB 1"
mycall.jsi:6,   "DEBUG: MYLOC 1: A,B,C", mycall()
mycall.jsi:7,   "TRACE: MYLOC 2: A,B,C", mycall()
~~~~

This demonstrates that logging can be local, global or a mixture of both.

!!! NOTE
    The [parseOpts](Develop) implicitly accepts the boolean options **Debug, Trace, Test**.


More Modules
------------
Here is a more complete module [mytest1](../js-demos/log/mytest1.jsi) using parseOpts and logging:

~~~~ JS linenumbers
#!/usr/bin/env jsish
"use strict";
function mytest1(args:array, conf:object=void):object {
    var options = { // Here is my test.
        label       :''         // Some other argument
    };
    var self = {
        count: 0
    };

    parseOpts(self, options, conf);
    for (var msg of args) {
        self.count++;
        LogDebug("testing:", msg);
    }
    puts("Done");
    return self;
}

provide('mytest1');
LogDebug("Loading test1");

if (isMain())
    runModule(mytest1);
~~~~

This allows us to turn on module-local debugging from the command line:

~~~~
jsish mytest1.jsi a b c
~~~~ the output
Done
~~~~

and

~~~~
jsish mytest1.jsi -Debug true a b c
~~~~ the output
mytest1.jsi:17, "DEBUG: testing: a", mytest1()
mytest1.jsi:17, "DEBUG: testing: b", mytest1()
mytest1.jsi:17, "DEBUG: testing: c", mytest1()
Done
~~~~

and

~~~~
jsish mytest1.jsi -h
~~~~ the output
/home/user/mytest1.jsi:121: error: ...
OPTIONS FOR 'mytest1.jsi' // Here is my test.
    -label      ""          // Some other argument.
    -Test       false       // Testing output.
    -Trace      false       // Tracing output.

ERROR
~~~~


Exec
----
By making your script executable, you can run it directly from Geany with F9.

Then you can employ jsish as an replacement for /usr/bin/env,
with support for arguments:

~~~~ js linenumbers
#!/usr/local/bin/jsish -T Debug %s -Trace true myinput1.txt
puts(console.args.join(' '));
~~~~

See [Shell](Start#start/shell)


Interp logOpts
--------------
The interp option [logOpts](Reference#logOptsOptions) is used to control when the above commands are
to output the current file, line and function.

It can also arrange for all such messages to be directed to a log file.

If you are debugging a program and need to find where a puts is coming from, try adding to the top

~~~~
Interp.conf({tracePuts:true});
~~~~


The same can be done from the command-line [eg](../tests/assert.jsi?mimetype=application/javascript):

~~~~ sh
JSI_INTERP_OPTS='{tracePuts:true}'   jsish tests/assert.js
~~~~ the output
"caught error" --> assert.js:16
"K" --> assert.js:24
"caught error2" --> assert.js:28
"this assert failed" --> assert.js:31
"assert also failed" --> assert.js:34
"done" --> assert.js:36
~~~~

The Jsi_LogFile() command can be used to send logging to a file.

Added doc/md/Misc.md.





















































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
Miscellaneous
=============
Options
----
Options are parameters handled by builtin commands.
These get passed in an object:

~~~~
var db = new Sqlite('/tmp/testsql.db',{maxStmts:1000, readonly:true});
~~~~

Usually there is a conf() method providing access to options after creation:

~~~~
Interp.conf(); // Dump all options
Interp.conf('strict'); // Get one option
Interp.conf({strict:false, maxDepth:99}); // Set one or more options.
~~~~

!!! NOTE
    Some options may be readOnly. And an option that is initOnly
    may only be set at object creation time.

    Internally, option parsing is implemented via [C Options](C-API#options).



Introspection
----
There are several levels of introspection built-in to Jsi.
One is displayed when calling an invalid method:

~~~~
Array.xxx();
~~~~ the output
error: 'Array' sub-commands are: concat fill filter forEach indexOf join lastIndexOf map pop push reverse shift sizeOf slice some sort splice unshift.    (at or near "xxx")
~~~~

Another, is querying with the
[Info](Reference#Info) command:

~~~~
Info.platform();
~~~~ the output
{ crc:7, hasThreads:true, intSize:4, isBigEndian:false, numberSize:8, os:"linux", platform:"unix", pointerSize:8, timeSize:8, wideSize:8 }
~~~~

~~~~
Info.cmds();
~~~~ the output
[  "Array", "Boolean", "File", "FileIO", "Function", "Interp", "Info", "JSON", "Math", "Number",
   "Object", "RegExp", "Signal", "Sqlite", "String", "Sys", "Websocket", "Zvfs", "assert", "clearInterval",
   "console", "decodeURI", "encodeURI", "exit", "source",
   "isFinite", "isNaN", "load", "parseFloat", "parseInt", "puts", "quote",
   "setInterval", "setTimeout", "format" ]
~~~~

~~~~
Info.cmds('Array.*');
~~~~ the output
[ "concat", "fill", "filter", "forEach", "indexOf", "join", "lastIndexOf", "map", "pop", "push",
  "reverse", "shift", "sizeOf", "slice", "some", "sort", "splice", "unshift" ]
~~~~

~~~~
Info.named();
~~~~ the output
[ "Socket", "Channel", "MySql", "WebSocket", "Sqlite", "Interp" ]
~~~~

~~~~
var ii = new Interp();
Info.named('Interp');
~~~~ the output
[ "#Interp_1" ]
~~~~

and so on.



Web Servers
----
Although Jsi can serve web content directly, on the Internet
it is more common to use "nginx"
as a reverse proxy via localhost:

~~~~
location /App00/Ledger { proxy_pass http://localhost:8800; include proxy_params; }
~~~~

And jsish might be invoked as:

~~~~SH
jsish -a jsi-app.fossil Ledger -port 8800 -urlPrefix /App00/Ledger -noGui true
~~~~

Or run via chroot/su.


### Chroot Setup
Given it's small size, jsish is well suited for chroot deployments.
On the jsish.org site the user **jsiusr00** was created with directory contents:

~~~~SH
ls -RF jsiusr00
~~~~ the output
jsiusr00:
bin/  dev/  etc/  ledgerjs.db  tmp/  usr/

jsiusr00/bin:
fossil*  jsish*

jsiusr00/dev:
null  random  urandom

jsiusr00/etc:
hosts  resolv.conf

jsiusr00/tmp:

jsiusr00/usr:
jsi-app.fossil  jsi-app.sqlar  jsi-app.zip  ledgerjs.db
~~~~

To scale this up while saving space, multiple users "jsiusrNN" are created with
readonly files hard-linked to "jsiusr00".
Finally "chattr +i" is used make them immutable.
Thus incremental size for each additional
user is really only the data file "ledgerjs.db".

~~~~SH
du -s jsiusr*
~~~~ the output
12468   jsiusr00
980 jsiusr01
960 jsiusr02
960 jsiusr03
...
1224    jsiusr10
960 jsiuser11
...
960 jsiuser19
~~~~

All previous directories have no shell, so with the addition of quotas and ulimits we end up with a
deployment that is simple but secure.

!!! NOTE
    The slight bump in "jsiusr10" is due to the addition of "sh", to allow execing fossil in a chroot.


Syntax
----
The following syntax is implemented by Jsi (see [Reference](Reference) for commands):

~~~~
continue [IDENT] ;
break [IDENT] ;
debugger ;
delete IDENT ;
do { STMTS; } while( EXPR ) ;
for ([var] IDENT = EXPR; EXPR; EXPR) { STMTS; }
for ([var] IDENT in EXPR) { STMTS; }
for ([var] IDENT of EXPR) { STMTS; }
function [IDENT] ([IDENT, IDENT, ...]) { STMTS; }
function [IDENT] ([IDENT:*TYPE*[=*PRIMATIVE], IDENT*:*TYPE*[=PRIMATIVE], ...]):TYPE { STMTS; }
if (EXPR) { STMTS; } [ else { STMTS; } ]
IDENT instanceof IDENT ;
[new] FUNC( ARGS ) ;
return [EXPR] ;
switch (EXPR) { case EXPR: STMTS; [break;] case EXPR: STMTS; [break;]  ... [default EXPR;] }
throw EXPR ;
try { EXPR; } catch(IDENT) { STMTS; } [finally { STMTS; }]
typeof EXPR ;
var IDENT [ = EXPR ] [, ...] ;
with ( EXPR ) { STMTS; }
~~~~

- Square brackets indicate optional.
- Curley braces are just for illustrative purposes and except for function and switch are not required.


### Expressions
Expressions take the usual form:

~~~~
IDENT*.*IDENT
IDENT*[*EXPR]
IDENT*(*ARGS)
(EXPR)
EXPR ? STMTS :  STMTS
STMTS , STMTS [, ...]
EXPR OP EXPR
~~~~

where OP is one of the binary operators +, -, *, /, etc.


### Terminals
| Name      | Description                                                                       |
|-----------|-----------------------------------------------------------------------------------|
| ARGS      | Zero or more comma-seperated arguments                                            |
| EXPR      | An expression (see below)                                                         |
| FUNC      | A function value                                                                  |
| IDENT     | Is an valid identifier                                                            |
| PRIMITIVE | A primitive value acceptable as an [argument type](Types#types/availtypes). |
| STMTS     | Is zero or more statements                                                        |
| TYPE      | A type value acceptable as [defaults](Types#types/defaults)                    |


### Keywords
Keywords can be displayed using Info.keywords():

~~~~
  "...", "any", "arguments", "array", "boolean", "break", "case", "catch",
  "continue", "debugger", "default", "delete", "do", "else", "false",
  "finally", "for", "function", "if", "in", "instanceof", "new", "null",
  "number", "object", "of", "regexp", "return", "string", "switch",
  "this", "throw", "true", "try", "typeof", "undefined", "userobj", "var",
  "void", "while", "with"
~~~~


Language Comparisons
----

Following is a feature comparison of various languages with Jsi.

| Feature          | Jsi     | NodeJs | Tcl | Lua   | Perl | Python |
|------------------|---------|--------|-----|-------|------|--------|
| Standard         | ES 5.2+ | ES 6+  |     |       |      |        |
| Implemention     | C/C++   | C++    | C   | C/C++ | C    | C      |
| C++ Compatible   | Y       | Y      |     |       |      |        |
| Embeddable       | Y       | N      | Y   | Y     | Y    | Y      |
| C API            | Y       | N      | Y   | Y     | Y    | Y      |
| Non-Script API   | Y       |        |     |       |      |        |
| Standalone       | Y       |        | Y   | Y     |      |        |
| Type Checking    | Y       |        |     |       |      |        |
| Sub-Interps      | Y       |        |     |       |      |        |
| Introspection    | Y       |        |     |       |      |        |
| Error Navigation | Y       |        |     |       |      |        |
| Logging          | Y       |        |     |       |      |        |
| Builtin Debugger | Y       | Y      |     |       | Y    |        |
| Debugger GUI     | Y       |        |     |       |      |        |
| Web Ready        | Y       |        |     |       |      |        |
| Modular Apps     | Y       |        |     |       |      |        |
| Shrink Wrap      | Y       |        | Y   |       |      |        |
| Applications     | Y       |        |     |       |      |        |
| Self Configure   | Y       |        |     |       |      |        |

These comparisons are of software out-of-the-box.

!!! NOTE:
    As of version 5.3.4, Lua also supports native C++.


License
----
Jsi source is covered by the following MIT license:

~~~~
The MIT License (MIT)

Copyright (c) 2013 Peter MacDonald

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
~~~~


### Libwebsockets
Jsi links agains Libwebockets, which is covered by LGPL
with an extra clause allowing static linking.

~~~~
Libwebsockets and included programs are provided under the terms of the GNU
Library General Public License (LGPL) 2.1, with the following exceptions:

1) Static linking of programs with the libwebsockets library does not
constitute a derivative work and does not require the author to provide
source code for the program, use the shared libwebsockets libraries, or
link their program against a user-supplied version of libwebsockets.

If you link the program to a modified version of libwebsockets, then the
changes to libwebsockets must be provided under the terms of the LGPL in
sections 1, 2, and 4.

2) You do not have to provide a copy of the libwebsockets license with
programs that are linked to the libwebsockets library, nor do you have to
identify the libwebsockets license in your program or documentation as
required by section 6 of the LGPL.

However, programs must still identify their use of libwebsockets. The
following example statement can be included in user documentation to
satisfy this requirement:

"[program] is based in part on the work of the libwebsockets  project
(http://libwebsockets.org)"

                  GNU LESSER GENERAL PUBLIC LICENSE
                       Version 2.1, February 1999
...
~~~~

!!! NOTE
    This seems to to say that as long as libwebsockets is not modified,
    all that is required is an acknowledgement in your user documentation.


### Others
Other software including sqlite, miniz, jsmn, regex from musl,
etc are either public domain, BSD or MIT compatible.


Design and Origin
----
Jsi is a Javascript interpreter written in C.
Additional functionality, implemented with scripts is bundled as "jsish".

Jsi is a byte-code oriented interpreter originally designed for interfacing-with, and embedding into C.

This makes it very different from [Node-js](https://nodejs.org) which is a compiler written in **C++**,
and which is not designed to be embedded.

Jsi is **C**-centric whereas Node is **JS**-centric.  Meaning that with Jsi, the locus of
control can resides in C-code.

Although Jsi was originally was based off source from [quad-wheel](https://code.google.com/archive/p/quad-wheel),
it is now internally modelled after [Tcl](https://www.tcl.tk/doc/scripting.html).

ECMA Compatibilty
----
Jsi implements version 5.1 of the
[Ecma-script 262 standard](http://www.ecma-international.org/ecma-262/5.1/Ecma-262.pdf),
with the following deviations:

- Semicolons are not auto-inserted.
- Using empty array/object elements will kick an error, eg. **[1,,3]**.
- **delete** actually deletes things, not just object properties.
- **length** works for objects, as well as arrays/strings/functions.
- The value of **typeof[]** is **"array"** instead of **"object"**.
- The **Error** object is unimplemented: the argument to **catch()** is just a string.
- The **Date** object is unimplemented: use **strftime/strptime**.
- UTF support is only for strings (not code) and is lightly tested.
- Prototype and other inheritance related features are incomplete, and can even be disabled entirely.

Extensions include:

- Functions parameters may have [types and defaults](Types).
- This enables Jsi to provide type-checking and [testing](Testing) support.
- Select features from newer versions of the standard (eg. Array **of** and **forEach**).
- Non-standard object functions such as **merge** are available, eg. **o = o1.merge({a:1,b:2});**

Goals
----
Following are principle goals  Jsi:

- Support embedded development using plain **C** (C99).
- But should also be compilable by **native GNU g++**, without use of *"extern C"*.
- Have as few dependencies as possible.
- Be generally free of value/object/memory leaks (verified with -fsanitize).
- Provide amalgamated source for simplified [application integration](Start#amalgamation) .
- Low-level C-functions available in a **C-only** [Lite](C-API#jsi-lite) version.
- Come with a [Debugger](Debug).
- Support Web applications, particularly with database and websockets.
- Support [standalone applications](Builtin#zvfs) via self-mounting .zip.
- [Package](Develop.md.html) and extension support.

And while compiling as C++ is supported, it is mostly used for integrity checking.

!!! NOTE
    C-integration is the main priority in Jsi, not speed of script execution.

Shortcomings
----
Following is a partial list of things that are either incomplete or unimplemented:

- Creation of a complete test suite for code-coverage.
- Run applications directly from fossil.
- A PostgreSql extension.
- Extension for libmicrohttpd for use in post.
- Support for libevent/libev.


Rational
----
- Desktop applications are held hostage by their user interface, be it QT, GTK, IOS or .NET.
- Increasingly web browsers are becoming the GUI, usually over the Internet.
- Moderately complex applications often end up requiring some form of script support, eg. Lua.
- If an application employs a Web GUI, a script language already is being used: Javascript.
- Time, energy and resources can be saved by using the same language on both ends.
- In fact, the same scripts can even be run in both the browser and the app.
- JSON provides seamless data interchange, thus avoiding data structure compatibility issues.

Added doc/md/MySql.md.

















































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
MySql
=====
The MySql driver provides three basic commands for executing sql:

- eval() for running simple sql requiring no input/output.
- onecolumn() like eval, but returns a single value (first column of first row).
- query() for complex, parameterized queries.

A simple session might look like:

~~~~ js linenumbers
var db = new MySql({user:'root', password:'', database:'jsitest'});
db.eval('CREATE TABLE players(name VARCHAR(50),age INTEGER);');
db.query('INSERT INTO players VALUES(?,?);', {values:["Barry",44]});
var age = db.onecolumn('SELECT age FROM players WHERE name = "Barry";');
~~~~

A more complete example is [mysql.jsi](../js-demos/mysql.jsi?mimetype=application/javascript).

Options passed in the object argument to new MySql(), may specify any of the following:

| Option      | Type    | Description                                                | Default |
|-------------|---------|------------------------------------------------------------|---------|
| bindWarn    | BOOL    | Treat failed variable binds as a warning.                  | false   |
| database    | STRKEY  | Database to use.                                           |         |
| debug       | CUSTOM  | Enable debug trace for various operations.                 |         |
| enableMulti | BOOL    | Enable muilti-statements for eval().                       |         |
| queryOpts   | CUSTOM  | Default options for exec.                                  |         |
| forceInt    | BOOL    | Bind float as int if possible.                             | true    |
| host        | STRING  | IP address or host name for mysqld (default is 127.0.0.1). |         |
| maxStmts    | INT     | Max cache size for compiled statements.                    |         |
| name        | DSTRING | Name for this db handle.                                   |         |
| password    | STRKEY  | Database password..                                        |         |
| port        | INT     | IP port for mysqld.                                        |         |
| reconnect   | BOOL    | Reconnect.                                                 |         |
| sslKey      | STRING  | SSL key.                                                   |         |
| sslCert     | STRING  | SSL Cert.                                                  |         |
| sslCA       | STRING  | SSL CA.                                                    |         |
| sslCAPath   | STRING  | SSL CA path.                                               |         |
| sslCipher   | STRING  | SSL Cipher.                                                |         |
| user        | STRKEY  | Database user name. Default is current user-name..         |         |

Some of these options can later be changed
using the conf() method, eg.

~~~~
db.conf({maxStmts:100});
~~~~

Refer to the [Reference](Reference#MySql)</li> for details.


Eval
----
The eval() method is used to execute simple Sql.
It takes no options, and returns no values.

It can also be used to execute multiple semicolon-separated statements:

~~~~ js linenumbers
db.conf({enableMulti:true});
db.exec('CREATE TABLE foo(a,b);'+
'INSERT INTO foo VALUES(1,2);'+
'INSERT INTO foo VALUES("X","Y")');
~~~~

This makes it useful for bulk loading.


Oncolumn
----
onecolumn() provides no inputs or outputs.  It simply returns the first column
of the first row.  The mode and other options are ignored.

~~~~
var maxid = db.oncolumn('SELECT max(id) FROM foo');
~~~~


Query
----
The  workhorse method is query() which:

- compiles SQL into a compiled code, and caches it.
- binds parameters.
- executes the query.
- returns the results.

Here is an example:

~~~~
db.query('INSERT INTO players VALUES(?,?);', {values:["Barry",44]});
~~~~

### Query Options
Query options can be controlled either of two ways.  Per query, as in:

~~~~
db.query('SELECT * FROM test1', {mode:'json'});
~~~~

or we can change the defaults (for the connection) like so:

~~~~ js linenumbers
db.conf({queryOpts:{mode:'json'}});
db.query('SELECT * FROM test1');
db.query('SELECT * FROM test2');
~~~~

Here is a list of the available query() options:

| Option        | Type   | Description                                                  | Default |
|---------------|--------|--------------------------------------------------------------|---------|
| callback      | FUNC   | Function to call with each row result.                       |         |
| headers       | BOOL   | First row returned contains column labels.                   |         |
| limit         | INT    | Maximum number of returned values.                           |         |
| mapundef      | BOOL   | In variable binds, map an 'undefined' var to null.           |         |
| maxString     | INT    | If not using prefetch, the maximum string value size (0=8K). |         |
| mode          | CUSTOM | Set output mode of returned data.                            |         |
| nocache       | BOOL   | Query is not to be cached.                                   |         |
| noNamedParams | BOOL   | Disable translating sql to support named params.             |         |
| nullvalue     | STRKEY | Null string output (for non-json mode).                      |         |
| paramVar      | ARRAY  | Array var to use for parameters.                             |         |
| prefetch      | BOOL   | Let client library cache entire results.                     |         |
| separator     | STRKEY | Separator string (for csv and text mode).                    |         |
| table         | STRKEY | Table name for mode=insert.                                  |         |
| typeCheck     | CUSTOM | Type check mode.                                             | error   |
| values        | ARRAY  | Values for ? bind parameters.                                |         |
| varName       | STRBUF | String name of array var for ? bind parameters.              |         |
| width         | CUSTOM | In column mode, set column widths.                           |         |


### Outputs
The returned value from a query is determined by the chosen output mode.

The default mode (rows) just returns an array of objects, which looks like this:

~~~~ sh output
[ { a:1, b:2 }, { a:"X", b:"Y" } ]
~~~~

The choices for mode are a superset of those available in the sqlite3 command-line tool, namely:

| Mode    | Description                                      | Purpose |
|---------|--------------------------------------------------|---------|
| array1d | Flat array of values                             | script  |
| arrays  | An array of row-arrays                           | script  |
| column  | Column aligned text                              | text    |
| csv     | Comma (or separator) separated values            | export  |
| html    | Html table rows                                  | browser |
| insert  | Sql insert statements                            | export  |
| json    | JSON string as an array of objects               | browser |
| json2   | JSON string with names/values in separate arrays | browser |
| line    | One value per line in name=value form            | export  |
| list    | The default sqlite3 output                       | text    |
| none    | No output                                        |         |
| rows    | An array of row-objects (the default)            | script  |
| tabs    | Tab separator delineated values                  | script  |


We can change the output mode for a query() using:

~~~~
db.query('SELECT * FROM foo', {mode:'list'});
~~~~ the output
1|2|X
Y|3|Z
~~~~

!!! NOTE
    Output for some modes is affected by the headers and separator options.


#### JSON
The json modes are useful
when data is destined to be sent to a web browser, eg. via [websockets](Builtin#websocket).

~~~~ js linenumbers
db.exec('DROP TABLE IF EXISTS foo; CREATE TABLE foo(a,b);');
var n = 0, x = 99;
while (n++ < 3) {
    db.query('INSERT INTO foo VALUES(@x,@n)');
    x -= 4;
}
x=db.query('SELECT * FROM foo',{mode:'json'});
~~~~ the output
[ {"a":99, "b":1}, {"a":95, "b":2}, {"a":91, "b":3} ]
~~~~

Where large amounts of data are involved, the headers option can be used to reduce size:

~~~~
db.query('SELECT * FROM foo',{mode:'json', headers:true});
~~~~ the output
[ ["a", "b"], [99, 1], [95, 2], [91, 3] ]
~~~~

The "json2" mode is used to split headers and values out
into separate members:

~~~~
db.query('SELECT * FROM foo',{mode:'json2'});
~~~~ the output
{ "names": [ "a", "b" ], "values": [ [99, 1 ], [95, 2 ], [91, 3 ] ] }
~~~~


#### Callback Function

Normally, query() will execute an entire query before returning the result.
There are two ways to change this:

- set the callback option, or
- give a function as the second argument.

Either way invokes the callback with
each row result:

~~~~
function myfunc(n) { puts("a=" + n.a + ", b=" + n.b); }
db.query('SELECT * FROM foo',myfunc);
~~~~

If the callback function returns false, evaluation terminates immediately.

~~~~ js linenumbers
db.query('SELECT * FROM foo', function (n) {
    puts("a=" + n.a + ", b=" + n.b);
    if (a>1) return false;
  });
~~~~


### Inputs
Sql inputs can be easily formatted using strings:

~~~~ js linenumbers
var a=1, b='big';
db.query('INSERT INTO foo VALUES('+a+*','*+b+')');
~~~~

However this raises issues of security and predictability.
Fortunately variable binding is easy.


#### Bindings

MySql variable binding uses "?" placeholders to refer to array elements., eg:

~~~~ js linenumbers
db.query('INSERT INTO foo VALUES(?,?)', {values:[11,12]});

var vals = [9,10];
db.query('INSERT INTO foo VALUES(?,?)', {values:vals});
~~~~

This approach, for a small number of parameters, is more than adequate.


#### Named-Binds

Jsi enhances MySql's standard variable binding with named binding.
This is modelled after Sqlite named
binding, and works by extracting
named variables from the query, translating them internally into standard "?" form.

A named-bind begin with one of the characters: :,  @, and $.
For example:

~~~~ js linenumbers
var x1=24.5, x2="Barry", x3="Box";
db.query('INSERT INTO test2 VALUES( :x1, @x2, $x3 );');
~~~~

As in Sqlite, the $ bind may append round-brackets () to refer to compound variables.

This example binds to objects members:

~~~~ js linenumbers
var y = {a:4, b:"Perry", c:"Pack"};
db.query('INSERT INTO test2 VALUES( $y(a), $y(b), $y(c) );');
~~~~

And this one to arrays:

~~~~ js linenumbers
var Z = 2;
var y = [9, 'Figgy', 'Fall'];
db.query('INSERT INTO test2 VALUES( $y(0), $y(1), $y([Z]) );');
~~~~

Or more usefully:

~~~~ js linenumbers
var y = [
    {a:4, b:"Perry", c:"Pack"},
    {a:9, b:'Figgy', c:'Fall'}
];
for (var i=0; i < y.length; i++)
    db.query('INSERT INTO test2 VALUES($y([i].a), $y([i].b), $y([i].c);');
~~~~

The contents of the round-brackets can contain multiple levels of dereference (but not expressions).

Here are a few sample bindings, and their associated variables:

| Binding        | Variable    | Comment                         |
|----------------|-------------|---------------------------------|
| :X             | X           |                                 |
| @X             | X           |                                 |
| $X             | X           |                                 |
| $X(a)          | X.a         | Implicit object member          |
| $X(9)          | X&lsqb;9]   | Implicit array (leading digits) |
| $X(&lsqb;a])   | X&lsqb;a]   | Explicit array                  |
| $X(a.b)        | X.a.b       | Compound object                 |
| $X(&lsqb;a].b) | X&lsqb;a].b | Compound array + object, etc    |


#### Types
A  type specifier may also be included in a $X(Y) binding, as in:

~~~~
var y = {a:4, b:"Purry", c:"Pax"};
db.query('INSERT INTO test2 VALUES( $y(a:integer), $y(b:string), $y(c:string) );');
~~~~

The type is the part after the colon ":", and just before the close round-brace.

By default, a type is used to convert data sent to MySql to the correct type.

Type specifiers are supported for all variants of $X(Y) binding, such as:

~~~~ js linenumbers
var Z = 0;
var x = ['Figgy'];
var y = {c:'Fall'};
db.query('INSERT INTO test3 VALUES( $x(0:string), $y(c:string), $x([Z]:string) );');
~~~~

The supported type names are:

| Type      | Description           |
|-----------|-----------------------|
| bool      | A tiny/bit value      |
| double    | A double value        |
| integer   | A 64 bit wide integer |
| string    | A string              |
| blob      | A blob                |
| date      | A date value          |
| datetime  | A date+time value     |
| time      | A time value          |
| timestamp | A unix timestamp      |


We can also change the type-checking behaviour via the typeCheck query option:

For example, we can instead cause an error to be kicked an error with:

~~~~ js linenumbers
var x = [ 'bad' ];
db.query('UPDATE test SET n = $x(0:number) );', {typeCheck:'error'});
~~~~

The valid typeCheck modes are:

| Value   | Description                                      |
|---------|--------------------------------------------------|
| convert | Coerce value to the requested type (the default) |
| warn    | Generate a warning                               |
| error   | Generate an error                                |
| disable | Ignore type specifiers                           |


Miscellaneous
----

### User Functions
SQL functions can be defined in javascript using func():

~~~~ js linenumbers
db.func('bar',function(n) { return n+'.000'; });
puts(db.onecolumn('SELECT bar(a) FROM foo WHERE b == 2;'));
~~~~


### Timestamps
Javascript time functions use the unix epoch to store the number of milliseconds
since Jan 1, 1970 UTC.

MySql stores dates/time in an internal format with one of the following types:

| Value     | Description           |
|-----------|-----------------------|
| TIMESTAMP | A unix date/timestamp |
| DATETIME  | A date and time       |
| DATE      | A date                |
| TIME      | a time                |


### Caching
In the interest of efficiency, compiled queries are cached on a per connection basis.
The size of the cache is controlled by the maxStmts option.

You can also disable caching for individual querys with nocache.


### Differences From Sqlite
Differences include a greater dependance on types, and requiring a user, password and database.


### Building
The MySql driver does not (by default) come builtin to Jsi.

However, once you [download the source](Start) you can build it in with:

~~~~
make mysql
~~~~

Alternatively, the shared library can be built (for unix) with:

~~~~
make libmysql
~~~~

Added doc/md/Reference.md.









































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
<title>Reference</title>
<p>
<B>JSI REFERENCE</B> (See <a href="#System">System</a> for globals)
<a name="Array"></a>

<hr>


<h1>Array</h1>

<font color=red>Synopsis:new Array(...):array

</font><p>Provide access to array objects.


<h2>Methods for "Array"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Array</td><td>new Array(...):array </td><td>Array constructor.</td></tr>
<tr><td>concat</td><td>concat(...):array </td><td>Return array with args appended.</td></tr>
<tr><td>every</td><td>every(callback:function) </td><td>Returns true if every value in array satisfies the test.</td></tr>
<tr><td>fill</td><td>fill(value:any, start:number=0, end:number=-1):array </td><td>Fill an array with values.</td></tr>
<tr><td>filter</td><td>filter(callback:function, this:object=void):array </td><td>Return a filtered array.</td></tr>
<tr><td>find</td><td>find(callback:function) </td><td>Returns the value of the first element in the array that satisfies the test.</td></tr>
<tr><td>findIndex</td><td>findIndex(callback:function) </td><td>Returns the index of the first element in the array that satisfies the test.</td></tr>
<tr><td>forEach</td><td>forEach(callback:function, this:object=void):void </td><td>Invoke function with each item in object.</td></tr>
<tr><td>includes</td><td>includes(val:any) </td><td>Returns true if array contains value.</td></tr>
<tr><td>indexOf</td><td>indexOf(str:any, startIdx:number=0):number </td><td>Return index of first occurrance in array.</td></tr>
<tr><td>isArray</td><td>isArray(val:any):boolean </td><td>True if val array.</td></tr>
<tr><td>join</td><td>join(sep:string=''):string </td><td>Return elements joined by char.</td></tr>
<tr><td>lastIndexOf</td><td>lastIndexOf(val:any, start:number=0):number </td><td>Return index of last occurence in array.</td></tr>
<tr><td>map</td><td>map(callback:function, this:object=void):array </td><td>Creates a new array with the results of calling a provided function on every element in this array.</td></tr>
<tr><td>pop</td><td>pop() </td><td>Remove and return last element of array.</td></tr>
<tr><td>push</td><td>push(val:any, ...):number </td><td>Push one or more elements onto array and return size.</td></tr>
<tr><td>reverse</td><td>reverse():array </td><td>Reverse order of all elements in an array.</td></tr>
<tr><td>shift</td><td>shift() </td><td>Remove first element and shift downwards.</td></tr>
<tr><td>sizeOf</td><td>sizeOf():number </td><td>Return size of array.</td></tr>
<tr><td>slice</td><td>slice(start:number, end:number=void):array </td><td>Return sub-array.</td></tr>
<tr><td>some</td><td>some(callback:function, this:object=void):boolean </td><td>Return true if function returns true some element.</td></tr>
<tr><td>sort</td><td>sort(<a href='#Array.sortOptions'>options</a>:function|object=void):array </td><td>Sort an array.</td></tr>
<tr><td>splice</td><td>splice(start:number, howmany:number=void, ...):array </td><td>Change the content of an array, adding new elements while removing old elements.</td></tr>
<tr><td>unshift</td><td>unshift(...):number </td><td>Add new elements to start of array and return size.</td></tr>
</table>


<a name="Array.sortOptions"></a>
<a name="Array.confOptions"></a>
<h2>Options for "Array.sort"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>mode</td><td><i>STRKEY</i></td><td>Mode to sort by. (one of: <b>default</b>, <b>desc</b>, <b>dict</b>, <b>nocase</b>)</td><td><i></i></td></tr>
<tr><td>compare</td><td><i>FUNC</i></td><td>Function to do comparison. @function(val1,val2)</td><td><i></i></td></tr>
<tr><td>unique</td><td><i>BOOL</i></td><td>Eliminate duplicate items.</td><td><i></i></td></tr>
</table>
<a name="Arrayend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Boolean"></a>

<hr>


<h1>Boolean</h1>

<font color=red>Synopsis:new Boolean(bool:boolean=false):boolean

</font><p>A Boolean object.


<h2>Methods for "Boolean"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Boolean</td><td>new Boolean(bool:boolean=false):boolean </td><td>Boolean constructor.</td></tr>
</table>
<a name="Booleanend"></a>
<p><a href="#TOC">Return to top</a>
<a name="CData"></a>

<hr>


<h1>CData</h1>

<font color=red>Synopsis:new CData(options:string|object=void, inits:object=undefined):userobj

</font><p>
<h2>Methods for "CData"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>CData</td><td>new CData(<a href='#new CDataOptions'>options</a>:string|object=void, inits:object=undefined):userobj </td><td>Create a new struct or map/array of structs.The 2nd arg is used for function option parsing and will report errors at the callers file:line</td></tr>
<tr><td>conf</td><td>conf(<a href='#CData.confOptions'>options</a>:object|string=void) </td><td>Configure options for c-data.</td></tr>
<tr><td>get</td><td>get(key:string|number|object=null, field:string=void) </td><td>Get struct/map/array value.</td></tr>
<tr><td>incr</td><td>incr(key:string|number|object|null, field:object|string, value:number):number </td><td>Increment a numeric field: returns the new value.</td></tr>
<tr><td>info</td><td>info():object </td><td>Return info for data.</td></tr>
<tr><td>names</td><td>names():array </td><td>Return keys for map.</td></tr>
<tr><td>set</td><td>set(key:string|number|object|null, field:object|string, value:any=void) </td><td>Set a struct/map/array value.</td></tr>
<tr><td>unset</td><td>unset(key:string|number|object) </td><td>Remove entry from map/array.</td></tr>
</table>


<a name="new CDataOptions"></a>
<a name="CData.confOptions"></a>
<h2>Options for "new CData"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>arrSize</td><td><i>UINT</i></td><td>If an array, its size in elements.</td><td><i>initOnly</i></td></tr>
<tr><td>flags</td><td><i>UINT</i></td><td>Flags.</td><td><i>initOnly</i></td></tr>
<tr><td>help</td><td><i>STRKEY</i></td><td>Description of data.</td><td><i>initOnly</i></td></tr>
<tr><td>keyName</td><td><i>STRKEY</i></td><td>Key struct, for key struct maps.</td><td><i>initOnly</i></td></tr>
<tr><td>keyType</td><td><i>STRKEY</i></td><td>Key id. (one of: <b>string</b>, <b>strkey</b>, <b>number</b>)</td><td><i>initOnly</i></td></tr>
<tr><td>mapType</td><td><i>STRKEY</i></td><td>If a map, its type. (one of: <b>none</b>, <b>hash</b>, <b>tree</b>, <b>list</b>)</td><td><i>initOnly</i></td></tr>
<tr><td>maxSize</td><td><i>UINT</i></td><td>Limit the array size or number of keys in a map.</td><td><i></i></td></tr>
<tr><td>name</td><td><i>STRKEY</i></td><td>Name (eg. of var assigned to on create).</td><td><i>initOnly</i></td></tr>
<tr><td>noAuto</td><td><i>BOOL</i></td><td>Disable auto-create of map keys in set/incr.</td><td><i></i></td></tr>
<tr><td>structName</td><td><i>STRKEY</i></td><td>Struct used for storing data.</td><td><i>initOnly|required</i></td></tr>
<tr><td>user</td><td><i>INT64</i></td><td>User data.</td><td><i></i></td></tr>
<tr><td>varParam</td><td><i>STRKEY</i></td><td>Param for maps/array vars.</td><td><i>initOnly</i></td></tr>
</table>
<a name="CDataend"></a>
<p><a href="#TOC">Return to top</a>
<a name="CEnum"></a>

<hr>


<h1>CEnum</h1>

<font color=red>Synopsis:CEnum.method(...)

</font><p>Enum commands. Note: Enum() is a shortcut for Enum.add().


<h2>Methods for "CEnum"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>add</td><td>add(<a href='#CEnum.addOptions'>options</a>:object|string, fields:array|string) </td><td>Create a new enum: value of items same as in fieldconf.</td></tr>
<tr><td>conf</td><td>conf(enum:string, <a href='#CEnum.confOptions'>options</a>:object|string=void) </td><td>Configure options for enum.</td></tr>
<tr><td>fieldconf</td><td>fieldconf(enum:string, field:string, <a href='#CEnum.fieldconfOptions'>options</a>:object|string=void) </td><td>Configure options for fields.</td></tr>
<tr><td>find</td><td>find(enum:string, intValue:number):string </td><td>Find item with given value in enum.</td></tr>
<tr><td>get</td><td>get(enum:string):object </td><td>Return enum definition.</td></tr>
<tr><td>names</td><td>names(enum:string=void):array </td><td>Return name list of all enums, or items within one enum.</td></tr>
<tr><td>remove</td><td>remove(enum:string) </td><td>Remove an enum.</td></tr>
<tr><td>value</td><td>value(enum:string, item:string):number </td><td>Return value for given enum item.</td></tr>
</table>


<a name="CEnum.addOptions"></a>
<a name="CEnum.confOptions"></a>
<h2>Options for "CEnum.add"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>flags</td><td><i>INT64</i></td><td>Flags for enum.</td><td><i>initOnly</i></td></tr>
<tr><td>help</td><td><i>STRKEY</i></td><td>Description of enum.</td><td><i>initOnly</i></td></tr>
<tr><td>name</td><td><i>STRKEY</i></td><td>Name of enum.</td><td><i>initOnly</i></td></tr>
<tr><td>idx</td><td><i>UINT</i></td><td>Number of items in enum.</td><td><i>readOnly</i></td></tr>
</table>


<a name="CEnum.confOptions"></a>
<a name="CEnum.confOptions"></a>
<h2>Options for "CEnum.conf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>flags</td><td><i>INT64</i></td><td>Flags for enum.</td><td><i>initOnly</i></td></tr>
<tr><td>help</td><td><i>STRKEY</i></td><td>Description of enum.</td><td><i>initOnly</i></td></tr>
<tr><td>name</td><td><i>STRKEY</i></td><td>Name of enum.</td><td><i>initOnly</i></td></tr>
<tr><td>idx</td><td><i>UINT</i></td><td>Number of items in enum.</td><td><i>readOnly</i></td></tr>
</table>


<a name="CEnum.fieldconfOptions"></a>
<a name="CEnum.confOptions"></a>
<h2>Options for "CEnum.fieldconf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>flags</td><td><i>INT64</i></td><td>Flags for item.</td><td><i>initOnly</i></td></tr>
<tr><td>help</td><td><i>STRKEY</i></td><td>Desciption of item.</td><td><i>initOnly</i></td></tr>
<tr><td>name</td><td><i>STRKEY</i></td><td>Name of item.</td><td><i>initOnly</i></td></tr>
<tr><td>value</td><td><i>INT64</i></td><td>Value for item.</td><td><i>initOnly</i></td></tr>
<tr><td>idx</td><td><i>UINT</i></td><td>Index of item in enum.</td><td><i>readOnly</i></td></tr>
</table>
<a name="CEnumend"></a>
<p><a href="#TOC">Return to top</a>
<a name="CStruct"></a>

<hr>


<h1>CStruct</h1>

<font color=red>Synopsis:CStruct.method(...)

</font><p>Struct commands. Note: Struct() is a shortcut for Struct.add().


<h2>Methods for "CStruct"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>add</td><td>add(<a href='#CStruct.addOptions'>options</a>:object|string, fields:array|string) </td><td>Create a struct: field values same as in fieldconf.</td></tr>
<tr><td>conf</td><td>conf(struct:string, <a href='#CStruct.confOptions'>options</a>:object|string=void) </td><td>Configure options for struct.</td></tr>
<tr><td>fieldconf</td><td>fieldconf(struct:string, field:string, <a href='#CStruct.fieldconfOptions'>options</a>:object|string=void) </td><td>Configure options for fields.</td></tr>
<tr><td>get</td><td>get(struct, options:object=void):object </td><td>Return the struct definition.</td></tr>
<tr><td>names</td><td>names(struct:string=void):array </td><td>Return name list of all structs, or fields for one struct.</td></tr>
<tr><td>remove</td><td>remove(name:string) </td><td>Remove a struct.</td></tr>
<tr><td>schema</td><td>schema():string </td><td>Return database schema for struct.</td></tr>
</table>


<a name="CStruct.addOptions"></a>
<a name="CStruct.confOptions"></a>
<h2>Options for "CStruct.add"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>crc</td><td><i>UINT32</i></td><td>Crc for struct.</td><td><i>initOnly</i></td></tr>
<tr><td>flags</td><td><i>INT64</i></td><td>Flags for struct.</td><td><i>initOnly</i></td></tr>
<tr><td>help</td><td><i>STRKEY</i></td><td>Struct description.</td><td><i>initOnly</i></td></tr>
<tr><td>idx</td><td><i>UINT32</i></td><td>Number of fields in struct.</td><td><i>readOnly</i></td></tr>
<tr><td>name</td><td><i>STRKEY</i></td><td>Name of struct.</td><td><i>initOnly|required</i></td></tr>
<tr><td>size</td><td><i>UINT</i></td><td>Size of struct in bytes.</td><td><i>readOnly</i></td></tr>
<tr><td>ssig</td><td><i>UINT32</i></td><td>Signature for struct.</td><td><i>initOnly</i></td></tr>
<tr><td>value</td><td><i>INT64</i></td><td>Reference count.</td><td><i>readOnly</i></td></tr>
</table>


<a name="CStruct.confOptions"></a>
<a name="CStruct.confOptions"></a>
<h2>Options for "CStruct.conf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>crc</td><td><i>UINT32</i></td><td>Crc for struct.</td><td><i>initOnly</i></td></tr>
<tr><td>flags</td><td><i>INT64</i></td><td>Flags for struct.</td><td><i>initOnly</i></td></tr>
<tr><td>help</td><td><i>STRKEY</i></td><td>Struct description.</td><td><i>initOnly</i></td></tr>
<tr><td>idx</td><td><i>UINT32</i></td><td>Number of fields in struct.</td><td><i>readOnly</i></td></tr>
<tr><td>name</td><td><i>STRKEY</i></td><td>Name of struct.</td><td><i>initOnly|required</i></td></tr>
<tr><td>size</td><td><i>UINT</i></td><td>Size of struct in bytes.</td><td><i>readOnly</i></td></tr>
<tr><td>ssig</td><td><i>UINT32</i></td><td>Signature for struct.</td><td><i>initOnly</i></td></tr>
<tr><td>value</td><td><i>INT64</i></td><td>Reference count.</td><td><i>readOnly</i></td></tr>
</table>


<a name="CStruct.fieldconfOptions"></a>
<a name="CStruct.confOptions"></a>
<h2>Options for "CStruct.fieldconf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>arrSize</td><td><i>UINT</i></td><td>Size of field if an array.</td><td><i>initOnly</i></td></tr>
<tr><td>bits</td><td><i>UINT32</i></td><td>Size of bitfield.</td><td><i>initOnly</i></td></tr>
<tr><td>boffset</td><td><i>UINT32</i></td><td>Bit offset of field within struct.</td><td><i>readOnly</i></td></tr>
<tr><td>flags</td><td><i>INT64</i></td><td>Flags for field.</td><td><i>initOnly</i></td></tr>
<tr><td>idx</td><td><i>UINT32</i></td><td>Index of field in struct.</td><td><i>readOnly</i></td></tr>
<tr><td>help</td><td><i>STRKEY</i></td><td>Field description.</td><td><i>initOnly</i></td></tr>
<tr><td>info</td><td><i>STRKEY</i></td><td>Info for field.</td><td><i>initOnly</i></td></tr>
<tr><td>name</td><td><i>STRKEY</i></td><td>Name of field.</td><td><i>initOnly|required</i></td></tr>
<tr><td>offset</td><td><i>UINT</i></td><td>Offset of field within struct.</td><td><i>readOnly</i></td></tr>
<tr><td>size</td><td><i>UINT</i></td><td>Size of field in struct.</td><td><i>readOnly</i></td></tr>
<tr><td>type</td><td><i>CUSTOM</i></td><td>Type of field.</td><td><i>initOnly|required</i></td></tr>
<tr><td>init</td><td><i>CUSTOM</i></td><td>Initial value for field.</td><td><i>initOnly</i></td></tr>
</table>
<a name="CStructend"></a>
<p><a href="#TOC">Return to top</a>
<a name="CType"></a>

<hr>


<h1>CType</h1>

<font color=red>Synopsis:CType.method(...)

</font><p>Type commands. Note: Type() is a shortcut for Type.conf().


<h2>Methods for "CType"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>conf</td><td>conf(typ:string, <a href='#CType.confOptions'>options</a>:object|string=void) </td><td>Configure options for type.</td></tr>
<tr><td>names</td><td>names(ctype=false):array </td><td>Return type names.</td></tr>
</table>


<a name="CType.confOptions"></a>
<a name="CType.confOptions"></a>
<h2>Options for "CType.conf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>idName</td><td><i>STRKEY</i></td><td>The id name: usually upcased cName.</td><td><i>initOnly</i></td></tr>
<tr><td>cName</td><td><i>STRKEY</i></td><td>C type name.</td><td><i>initOnly</i></td></tr>
<tr><td>help</td><td><i>STRKEY</i></td><td>Description of id.</td><td><i>initOnly</i></td></tr>
<tr><td>fmt</td><td><i>STRKEY</i></td><td>Printf format for id.</td><td><i>initOnly</i></td></tr>
<tr><td>xfmt</td><td><i>STRKEY</i></td><td>Hex printf format for id.</td><td><i>initOnly</i></td></tr>
<tr><td>flags</td><td><i>INT64</i></td><td>Flags for id.</td><td><i>initOnly</i></td></tr>
<tr><td>size</td><td><i>INT</i></td><td>Size for id.</td><td><i>initOnly</i></td></tr>
<tr><td>user</td><td><i>INT64</i></td><td>User data.</td><td><i></i></td></tr>
</table>
<a name="CTypeend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Channel"></a>

<hr>


<h1>Channel</h1>

<font color=red>Synopsis:new Channel(file:string, mode:string='r'):userobj

</font><p>Commands for accessing Channel objects for file IO.


<h2>Methods for "Channel"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Channel</td><td>new Channel(file:string, mode:string='r'):userobj </td><td>A file input/output object. The mode string is r or w and an optional +.</td></tr>
<tr><td>close</td><td>close():boolean </td><td>Close the file.</td></tr>
<tr><td>eof</td><td>eof():boolean </td><td>Return true if read to end-of-file.</td></tr>
<tr><td>filename</td><td>filename():string </td><td>Get file name.</td></tr>
<tr><td>flush</td><td>flush():number </td><td>Flush file output.</td></tr>
<tr><td>gets</td><td>gets():string|void </td><td>Get one line of input.</td></tr>
<tr><td>lstat</td><td>lstat():object </td><td>Return status for file.</td></tr>
<tr><td>mode</td><td>mode():string </td><td>Get file mode used with open.</td></tr>
<tr><td>open</td><td>open(file:string, mode:string='r'):boolean </td><td>Open the file (after close).</td></tr>
<tr><td>puts</td><td>puts(str):boolean </td><td>Write one line of output.</td></tr>
<tr><td>read</td><td>read(size:number=-1):string|void </td><td>Read some or all of file.</td></tr>
<tr><td>seek</td><td>seek(pos:number, whence:string):number </td><td>Seek to position. Return 0 if ok.</td></tr>
<tr><td>stat</td><td>stat():object </td><td>Return status for file.</td></tr>
<tr><td>tell</td><td>tell():number </td><td>Return current position.</td></tr>
<tr><td>truncate</td><td>truncate(pos:number):number </td><td>Truncate file.</td></tr>
<tr><td>write</td><td>write(data):number </td><td>Write data to file.</td></tr>
</table>
<a name="Channelend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Debugger"></a>

<hr>


<h1>Debugger</h1>

<font color=red>Synopsis:Debugger.method(...)

</font><p>Debugger breakpoint management.


<h2>Methods for "Debugger"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>add</td><td>add(val:string|number, temp:boolean=false):number </td><td>Add a breakpoint for line, file:line or func.</td></tr>
<tr><td>enable</td><td>enable(id:number, on:boolean):void </td><td>Enable/disable breakpoint.</td></tr>
<tr><td>info</td><td>info(id:number=void):array|object </td><td>Return info about one breakpoint, or list of bp numbers.</td></tr>
<tr><td>remove</td><td>remove(id:number):void </td><td>Remove breakpoint.</td></tr>
</table>
<a name="Debuggerend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Event"></a>

<hr>


<h1>Event</h1>

<font color=red>Synopsis:Event.method(...)

</font><p>Event management.


<h2>Methods for "Event"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>clearInterval</td><td>clearInterval(id:number):void </td><td>Delete an event (created with setInterval/setTimeout).</td></tr>
<tr><td>info</td><td>info(id:number):object </td><td>Return info for the given event id.</td></tr>
<tr><td>names</td><td>names():array </td><td>Return list event ids (created with setTimeout/setInterval).</td></tr>
<tr><td>setInterval</td><td>setInterval(callback:function, millisecs:number):number </td><td>Setup recurring function to run every given millisecs.</td></tr>
<tr><td>setTimeout</td><td>setTimeout(callback:function, millisecs:number):number </td><td>Setup function to run after given millisecs.</td></tr>
<tr><td>update</td><td>update(<a href='#Event.updateOptions'>options</a>:number|object=void):number </td><td>Service all events, eg. setInterval/setTimeout. Returns the number of events processed. Events are processed until minTime (in milliseconds) is exceeded, or forever if -1.
The default minTime is 0, meaning return as soon as no events can be processed. A positive mintime will result in sleeps between event checks.</td></tr>
</table>


<a name="Event.updateOptions"></a>
<a name="Event.confOptions"></a>
<h2>Options for "Event.update"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>maxEvents</td><td><i>INT</i></td><td>Maximum number of events to process (or -1 for all).</td><td><i></i></td></tr>
<tr><td>maxPasses</td><td><i>INT</i></td><td>Maximum passes through event queue.</td><td><i></i></td></tr>
<tr><td>minTime</td><td><i>INT</i></td><td>Minimum milliseconds before returning, or -1 to loop forever (default is 0).</td><td><i></i></td></tr>
<tr><td>sleep</td><td><i>INT</i></td><td>Time to sleep time (in milliseconds) between event checks. Default is 1.</td><td><i></i></td></tr>
</table>
<a name="Eventend"></a>
<p><a href="#TOC">Return to top</a>
<a name="File"></a>

<hr>


<h1>File</h1>

<font color=red>Synopsis:File.method(...)

</font><p>Commands for accessing the filesystem.


<h2>Methods for "File"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>atime</td><td>atime(file:string):number </td><td>Return file Jsi_Access time.</td></tr>
<tr><td>chdir</td><td>chdir(file:string) </td><td>Change current directory.</td></tr>
<tr><td>chmod</td><td>chmod(file:string, mode:number) </td><td>Set file permissions.</td></tr>
<tr><td>copy</td><td>copy(src:string, dest:string, force:boolean=false) </td><td>Copy a file to destination. Directories are not handled.
The third argument if given is a boolean force value which if true allows overwrite of an existing file. </td></tr>
<tr><td>dirname</td><td>dirname(file:string):string </td><td>Return directory path.</td></tr>
<tr><td>executable</td><td>executable(file:string):boolean </td><td>Return true if file is executable.</td></tr>
<tr><td>exists</td><td>exists(file:string):boolean </td><td>Return true if file exists.</td></tr>
<tr><td>extension</td><td>extension(file:string):string </td><td>Return file extension.</td></tr>
<tr><td>glob</td><td>glob(pattern:regexp|string|null='*', <a href='#File.globOptions'>options</a>:function|object|null=void):array </td><td>Return list of files in dir with optional pattern match. With no arguments (or null) returns all files/directories in current directory.
The first argument can be a pattern (either a glob or regexp) of the files to return.
When the second argument is a function, it is called with each path, and filter on false.
Otherwise second arugment must be a set of options.</td></tr>
<tr><td>isdir</td><td>isdir(file:string):boolean </td><td>Return true if file is a directory.</td></tr>
<tr><td>isfile</td><td>isfile(file:string):boolean </td><td>Return true if file is a normal file.</td></tr>
<tr><td>isrelative</td><td>isrelative(file:string):boolean </td><td>Return true if file path is relative.</td></tr>
<tr><td>join</td><td>join(path:string, path:string):string </td><td>Join two file realpaths, or just second if an absolute path.</td></tr>
<tr><td>link</td><td>link(src:string, dest:string, ishard:boolean=false) </td><td>Link a file. The second argument is the destination file to be created. If a third bool argument is true, a hard link is created.</td></tr>
<tr><td>lstat</td><td>lstat(file:string):object </td><td>Return status info for file.</td></tr>
<tr><td>mkdir</td><td>mkdir(file:string) </td><td>Create a directory.</td></tr>
<tr><td>mtime</td><td>mtime(file:string):number </td><td>Return file modified time.</td></tr>
<tr><td>owned</td><td>owned(file:string):boolean </td><td>Return true if file is owned by user.</td></tr>
<tr><td>pwd</td><td>pwd():string </td><td>Return current directory.</td></tr>
<tr><td>read</td><td>read(file:string, mode:string='rb'):string </td><td>Read a file.</td></tr>
<tr><td>readable</td><td>readable(file:string):boolean </td><td>Return true if file is readable.</td></tr>
<tr><td>readlink</td><td>readlink(file:string):string </td><td>Read file link destination.</td></tr>
<tr><td>realpath</td><td>realpath(file:string):string </td><td>Return absolute file name minus .., ./ etc.</td></tr>
<tr><td>remove</td><td>remove(file:string, force:boolean=false) </td><td>Delete a file or direcotry.</td></tr>
<tr><td>rename</td><td>rename(src:string, dest:string, force:boolean=false) </td><td>Rename a file, with possible overwrite.</td></tr>
<tr><td>rootname</td><td>rootname(file:string):string </td><td>Return file name minus extension.</td></tr>
<tr><td>size</td><td>size(file:string):number </td><td>Return size for file.</td></tr>
<tr><td>stat</td><td>stat(file:string):object </td><td>Return status info for file.</td></tr>
<tr><td>tail</td><td>tail(file:string):string </td><td>Return file name minus dirname.</td></tr>
<tr><td>tempfile</td><td>tempfile(file:string) </td><td>Create a temp file.</td></tr>
<tr><td>truncate</td><td>truncate(file:string, size:number) </td><td>Truncate file.</td></tr>
<tr><td>type</td><td>type(file:string):string </td><td>Return type of file.</td></tr>
<tr><td>writable</td><td>writable(file:string):boolean </td><td>Return true if file is writable.</td></tr>
<tr><td>write</td><td>write(file:string, str:string, mode:string='wb+'):number </td><td>Write a file.</td></tr>
</table>


<a name="File.globOptions"></a>
<a name="File.confOptions"></a>
<h2>Options for "File.glob"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>dir</td><td><i>STRING</i></td><td>The start directory: this path will not be prepended to results.</td><td><i></i></td></tr>
<tr><td>maxDepth</td><td><i>INT</i></td><td>Maximum directory depth to recurse into.</td><td><i></i></td></tr>
<tr><td>maxDiscard</td><td><i>INT</i></td><td>Maximum number of items to discard before giving up.</td><td><i></i></td></tr>
<tr><td>dirFilter</td><td><i>FUNC</i></td><td>Filter function for directories, returning false to discard. @function(dir:string)</td><td><i></i></td></tr>
<tr><td>filter</td><td><i>FUNC</i></td><td>Filter function to call with each file, returning false to discard. @function(file:string)</td><td><i></i></td></tr>
<tr><td>limit</td><td><i>INT</i></td><td>The maximum number of results to return/count.</td><td><i></i></td></tr>
<tr><td>noTypes</td><td><i>STRKEY</i></td><td>Filter files to exclude these "types".</td><td><i></i></td></tr>
<tr><td>prefix</td><td><i>STRKEY</i></td><td>String prefix to add to each file in list.</td><td><i></i></td></tr>
<tr><td>recurse</td><td><i>BOOL</i></td><td>Recurse into sub-directories.</td><td><i></i></td></tr>
<tr><td>retCount</td><td><i>BOOL</i></td><td>Return only the count of matches.</td><td><i></i></td></tr>
<tr><td>tails</td><td><i>BOOL</i></td><td>Returned only tail of path.</td><td><i></i></td></tr>
<tr><td>types</td><td><i>STRKEY</i></td><td>Filter files to include type: one or more of chars 'fdlpsbc' for file, directory, link, etc.</td><td><i></i></td></tr>
</table>
<a name="Fileend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Function"></a>

<hr>


<h1>Function</h1>

<font color=red>Synopsis:new Function():function

</font><p>Commands for accessing functions.


<h2>Methods for "Function"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Function</td><td>new Function():function </td><td>Function constructor (unimplemented).</td></tr>
<tr><td>apply</td><td>apply(thisArg:null|object, args:array=void) </td><td>Call function passing args array.</td></tr>
<tr><td>bind</td><td>bind(thisArg:object=null,arg,...) </td><td>Return function that calls bound function prepended with thisArg+arguments.</td></tr>
<tr><td>call</td><td>call(thisArg:null|object, arg1, ...) </td><td>Call function with args.</td></tr>
</table>
<a name="Functionend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Info"></a>

<hr>


<h1>Info</h1>

<font color=red>Synopsis:Info.method(...)

</font><p>Commands for inspecting internal state information in JSI.


<h2>Methods for "Info"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>argv0</td><td>argv0():string|void </td><td>Return initial start script file name.</td></tr>
<tr><td>cmds</td><td>cmds(val:string|regexp='*', <a href='#Info.cmdsOptions'>options</a>:object=void):array|object </td><td>Return details or list of matching commands.</td></tr>
<tr><td>completions</td><td>completions(str:string, start:number=0, end:number=void):array </td><td>Return command completions on portion of string from start to end.</td></tr>
<tr><td>data</td><td>data(val:string|regexp|object=void):array </td><td>Return list of matching data (non-functions). Like info.vars(), but does not return function values.</td></tr>
<tr><td>error</td><td>error():object </td><td>Return file and line number of error (used inside catch).</td></tr>
<tr><td>event</td><td>event(id:number=void):array|object </td><td>List events or info for 1 event (setTimeout/setInterval). With no args, returns list of all outstanding events.  With one arg, returns infofor the given event id.</td></tr>
<tr><td>execZip</td><td>execZip():string|void </td><td>If executing a .zip file, return file name.</td></tr>
<tr><td>executable</td><td>executable():string </td><td>Return name of executable.</td></tr>
<tr><td>files</td><td>files():array </td><td>Return list of all sourced files.</td></tr>
<tr><td>funcs</td><td>funcs(string|regexp|object=void):array|object </td><td>Return details or list of matching functions.</td></tr>
<tr><td>interp</td><td>interp(interp:userobj=void):object </td><td>Return info on given or current interp.</td></tr>
<tr><td>isMain</td><td>isMain():boolean </td><td>Return true if current script was the main script invoked from command-line.</td></tr>
<tr><td>keywords</td><td>keywords():array </td><td>Return list of reserved jsi keywords.</td></tr>
<tr><td>level</td><td>level(level:number=void):number|array|object </td><td>Return current level or details of a call-stack frame. With no arg, returns the number of the current stack frame level.
Otherwise returns details on the specified level.
The topmost level is 1, and 0 is the current level, and a negative level translates as relative to the current level.</td></tr>
<tr><td>lookup</td><td>lookup(name:string) </td><td>Given string name, lookup and return value (eg. function).</td></tr>
<tr><td>methods</td><td>methods(val:string|regexp):array|object </td><td>Return functions and commands.</td></tr>
<tr><td>named</td><td>named(name:string=void):array|userobj </td><td>Returns command names for builtin Objects (eg. 'File', 'Interp'), sub-Object names, or the named object.</td></tr>
<tr><td>options</td><td>options(ctype:boolean=false):array </td><td>Return Option type name, or with true the C type).</td></tr>
<tr><td>package</td><td>package(pkgName:string):object|null </td><td>Return info about provided package if exists, else null.</td></tr>
<tr><td>platform</td><td>platform():object </td><td>N/A. Returns general platform information for JSI.</td></tr>
<tr><td>script</td><td>script(func:function|regexp=void):string|array|void </td><td>Get current script file name, or file containing function.</td></tr>
<tr><td>scriptDir</td><td>scriptDir():string|void </td><td>Get directory of current script.</td></tr>
<tr><td>vars</td><td>vars(val:string|regexp|object=void):array|object </td><td>Return details or list of matching variables. Returns all values, data or function.</td></tr>
<tr><td>version</td><td>version(bool:boolean=false):number|object </td><td>JSI version as: with bool not true a double, otherwise an object.</td></tr>
</table>


<a name="Info.cmdsOptions"></a>
<a name="Info.confOptions"></a>
<h2>Options for "Info.cmds"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>full</td><td><i>BOOL</i></td><td>Return full path.</td><td><i></i></td></tr>
<tr><td>constructor</td><td><i>BOOL</i></td><td>Do not exclude constructor.</td><td><i></i></td></tr>
</table>
<a name="Infoend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Interp"></a>

<hr>


<h1>Interp</h1>

<font color=red>Synopsis:new Interp(options:object=void):userobj

</font><p>Commands for accessing interps.


<h2>Methods for "Interp"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Interp</td><td>new Interp(<a href='#new InterpOptions'>options</a>:object=void):userobj </td><td>Create a new interp.</td></tr>
<tr><td>alias</td><td>alias(name:string=void, func:function|null=void, args:array|null=void) </td><td>Set/get global alias bindings for command in an interp. With 0 args, returns list of all aliases in interp.
With 1 arg returns func for given alias name.
With 2 args, returns args for given alias name (args must be null).
With 3 args, create/update an alias for func and args. Delete an alias by creating it with null for both func and args.</td></tr>
<tr><td>call</td><td>call(funcName:string, args:string|array, async:boolean=false) </td><td>Call named function in subinterp. Invoke function in sub-interp with arguments.
Since interps are not allowed to share objects, data is automatically cleansed by encoding/decoding to/from JSON if required.
Unless an 'async' parameter of true is given, we wait until the sub-interp is idle, make the call, and return the result.  Otherwise the call is acyncronous.</td></tr>
<tr><td>conf</td><td>conf(<a href='#Interp.confOptions'>options</a>:string|object=void) </td><td>Configure option(s).</td></tr>
<tr><td>eval</td><td>eval(js:string, async:boolean=false) </td><td>Interpret script within sub-interp. When the 'async' option is used on a threaded interp, the script is queued as an Event.</td></tr>
<tr><td>info</td><td>info():object </td><td>Returns internal statistics about interp.</td></tr>
<tr><td>send</td><td>send(msg:any) </td><td>Enqueue message onto a sub-interps recvCallback handler. Add messages to queue to be processed by the 'recvCallback' interp option.</td></tr>
<tr><td>source</td><td>source(file:string, async:boolean=false) </td><td>Interpret file within sub-interp. When the 'async' option is used on a threaded interp, the script is queued as an Event.</td></tr>
<tr><td>uplevel</td><td>uplevel(js:string, level:number=0) </td><td>Interpret code at the given stack level: see Info.level(). The level argument is as returned by Info.level().  Not supported with threads.</td></tr>
<tr><td>value</td><td>value(var:string, level:number=0) </td><td>Lookup value of variable at stack level: see Info.level().</td></tr>
</table>


<a name="new InterpOptions"></a>
<a name="Interp.confOptions"></a>
<h2>Options for "new Interp"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>args</td><td><i>ARRAY</i></td><td>The console.arguments for interp.</td><td><i>initOnly</i></td></tr>
<tr><td>asserts</td><td><i>BOOL</i></td><td>Enable assert.</td><td><i></i></td></tr>
<tr><td>assertMode</td><td><i>STRKEY</i></td><td>Action upon assert failure. (one of: <b>throw</b>, <b>log</b>, <b>puts</b>)</td><td><i></i></td></tr>
<tr><td>autoFiles</td><td><i>ARRAY</i></td><td>File(s) to source for loading Jsi_Auto to handle unknown commands.</td><td><i></i></td></tr>
<tr><td>busyCallback</td><td><i>CUSTOM</i></td><td>Command in parent interp (or noOp) to periodically call.</td><td><i></i></td></tr>
<tr><td>busyInterval</td><td><i>INT</i></td><td>Call busyCallback command after this many op-code evals (100000).</td><td><i></i></td></tr>
<tr><td>coverage</td><td><i>BOOL</i></td><td>On exit generate detailed code coverage for function calls (with profile).</td><td><i></i></td></tr>
<tr><td>debugOpts</td><td><i><a href='#debugOptsOptions'>options</a></i></td><td>Options for debugging.</td><td><i></i></td></tr>
<tr><td>hasOpenSSL</td><td><i>BOOL</i></td><td>Is SSL available in WebSocket.</td><td><i>initOnly</i></td></tr>
<tr><td>historyFile</td><td><i>STRKEY</i></td><td>In interactive mode, file to use for history (~/.jsish_history).</td><td><i>initOnly</i></td></tr>
<tr><td>isSafe</td><td><i>BOOL</i></td><td>Is this a safe interp (ie. with limited or no file access).</td><td><i>initOnly</i></td></tr>
<tr><td>jsppChars</td><td><i>STRKEY</i></td><td>Line preprocessor when sourcing files. Line starts with first char, and either ends with it, or matches string.</td><td><i></i></td></tr>
<tr><td>jsppCallback</td><td><i>FUNC</i></td><td>Command to preprocess lines that match jsppChars. Call func(interpName:string, opCnt:number).</td><td><i></i></td></tr>
<tr><td>lockTimeout</td><td><i>INT</i></td><td>Thread time-out for mutex lock acquires (milliseconds).</td><td><i></i></td></tr>
<tr><td>logOpts</td><td><i><a href='#logOptsOptions'>options</a></i></td><td>Options for log output to add file/line/time.</td><td><i></i></td></tr>
<tr><td>maxDepth</td><td><i>INT</i></td><td>Depth limit of recursive function calls (1000).</td><td><i></i></td></tr>
<tr><td>maxIncDepth</td><td><i>INT</i></td><td>Maximum allowed source/require nesting depth (50).</td><td><i></i></td></tr>
<tr><td>maxInterpDepth</td><td><i>INT</i></td><td>Maximum nested subinterp create depth (10).</td><td><i></i></td></tr>
<tr><td>maxUserObjs</td><td><i>INT</i></td><td>Maximum number of 'new' object calls, eg. File, RegExp, etc.</td><td><i></i></td></tr>
<tr><td>maxOpCnt</td><td><i>INT</i></td><td>Execution limit for op-code evaluation.</td><td><i>initOnly</i></td></tr>
<tr><td>memDebug</td><td><i>INT</i></td><td>Memory debugging level: 1=summary, 2=detail.</td><td><i></i></td></tr>
<tr><td>name</td><td><i>STRKEY</i></td><td>Optional text name for this interp.</td><td><i></i></td></tr>
<tr><td>noAutoLoad</td><td><i>BOOL</i></td><td>Disable autoload.</td><td><i></i></td></tr>
<tr><td>noConfig</td><td><i>BOOL</i></td><td>Disable use of Interp.conf to change options after create.</td><td><i>initOnly</i></td></tr>
<tr><td>noInput</td><td><i>BOOL</i></td><td>Disable use of console.input().</td><td><i></i></td></tr>
<tr><td>noLoad</td><td><i>BOOL</i></td><td>Disable load of shared libs.</td><td><i></i></td></tr>
<tr><td>noNetwork</td><td><i>BOOL</i></td><td>Disable new Socket/WebSocket, or load of builtin MySql.</td><td><i></i></td></tr>
<tr><td>noStderr</td><td><i>BOOL</i></td><td>Make puts, log, assert, etc use stdout.</td><td><i></i></td></tr>
<tr><td>noSubInterps</td><td><i>BOOL</i></td><td>Disallow sub-interp creation.</td><td><i></i></td></tr>
<tr><td>onComplete</td><td><i>FUNC</i></td><td>Function to return commands completions for interactive mode.  Default uses Info.completions . @function(prefix:string, start:number, end:number)</td><td><i></i></td></tr>
<tr><td>onEval</td><td><i>FUNC</i></td><td>Function to get control for interactive evals. @function(cmd:string)</td><td><i></i></td></tr>
<tr><td>onExit</td><td><i>FUNC</i></td><td>Command to call in parent on exit, returns true to continue. @function()</td><td><i>initOnly</i></td></tr>
<tr><td>opTrace</td><td><i>INT</i></td><td>Set debugging level for OPCODE execution.</td><td><i></i></td></tr>
<tr><td>pkgDirs</td><td><i>ARRAY</i></td><td>list of library directories for require() to search.</td><td><i></i></td></tr>
<tr><td>profile</td><td><i>BOOL</i></td><td>On exit generate profile of function calls.</td><td><i></i></td></tr>
<tr><td>recvCallback</td><td><i>CUSTOM</i></td><td>Command to recv 'send' msgs from parent interp.</td><td><i></i></td></tr>
<tr><td>retValue</td><td><i>VALUE</i></td><td>Return value from last eval.</td><td><i>readOnly</i></td></tr>
<tr><td>safeReadDirs</td><td><i>ARRAY</i></td><td>In safe mode, files/dirs to allow reads from.</td><td><i>initOnly</i></td></tr>
<tr><td>safeWriteDirs</td><td><i>ARRAY</i></td><td>In safe mode, files/dirs to allow writes to.</td><td><i>initOnly</i></td></tr>
<tr><td>safeExecPattern</td><td><i>STRKEY</i></td><td>In safe mode, regexp pattern allow exec of commands.</td><td><i>initOnly</i></td></tr>
<tr><td>scriptStr</td><td><i>STRKEY</i></td><td>Interp init script string.</td><td><i>initOnly</i></td></tr>
<tr><td>scriptFile</td><td><i>STRING</i></td><td>Interp init script file.</td><td><i></i></td></tr>
<tr><td>stdinStr</td><td><i>STRING</i></td><td>String to use as stdin for console.input().</td><td><i></i></td></tr>
<tr><td>stdoutStr</td><td><i>STRING</i></td><td>String to collect stdout for puts().</td><td><i></i></td></tr>
<tr><td>strict</td><td><i>BOOL</i></td><td>Globally enable strict: same as 'use strict' in main program.</td><td><i></i></td></tr>
<tr><td>subOpts</td><td><i><a href='#subOptsOptions'>options</a></i></td><td>Infrequently used sub-options.</td><td><i></i></td></tr>
<tr><td>subthread</td><td><i>BOOL</i></td><td>Create a threaded Interp.</td><td><i>initOnly</i></td></tr>
<tr><td>traceCall</td><td><i>ARRAY</i></td><td>Trace commands. (zero or more of: <b>funcs</b>, <b>cmds</b>, <b>new</b>, <b>return</b>, <b>args</b>, <b>notrunc</b>, <b>noparent</b>, <b>full</b>, <b>before</b>)</td><td><i></i></td></tr>
<tr><td>tracePuts</td><td><i>BOOL</i></td><td>Trace puts by making it use logOpts.</td><td><i></i></td></tr>
<tr><td>typeCheck</td><td><i>ARRAY</i></td><td>Type-check control options. (zero or more of: <b>parse</b>, <b>run</b>, <b>all</b>, <b>error</b>, <b>strict</b>, <b>noundef</b>, <b>nowith</b>, <b>funcsig</b>)</td><td><i></i></td></tr>
<tr><td>typeWarnMax</td><td><i>INT</i></td><td>Type checking is silently disabled after this many warnings (50).</td><td><i></i></td></tr>
<tr><td>unitTest</td><td><i>UINT</i></td><td>Unit test control bits: 1=subst ;;, 2=Puts with file:line prefix.</td><td><i></i></td></tr>
</table>


<a name="debugOptsOptions"></a>
<h2>Options for "debugOpts"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>debugCallback</td><td><i>CUSTOM</i></td><td>Command in parent interp for handling debugging.</td><td><i></i></td></tr>
<tr><td>doContinue</td><td><i>BOOL</i></td><td>Continue execution until breakpoint.</td><td><i></i></td></tr>
<tr><td>forceBreak</td><td><i>BOOL</i></td><td>Force debugger to break.</td><td><i></i></td></tr>
<tr><td>includeOnce</td><td><i>BOOL</i></td><td>Source the file only if not already sourced.</td><td><i></i></td></tr>
<tr><td>includeTrace</td><td><i>BOOL</i></td><td>Trace includes.</td><td><i></i></td></tr>
<tr><td>minLevel</td><td><i>INT</i></td><td>Disable eval callback for level higher than this.</td><td><i></i></td></tr>
<tr><td>msgCallback</td><td><i>CUSTOM</i></td><td>Comand in parent interp to handle Jsi_LogError/Jsi_LogWarn,...</td><td><i></i></td></tr>
<tr><td>pkgTrace</td><td><i>BOOL</i></td><td>Trace package loads.</td><td><i></i></td></tr>
<tr><td>putsCallback</td><td><i>CUSTOM</i></td><td>Comand in parent interp to handle puts output.</td><td><i></i></td></tr>
<tr><td>traceCallback</td><td><i>CUSTOM</i></td><td>Comand in parent interp to handle traceCall.</td><td><i></i></td></tr>
</table>


<a name="logOptsOptions"></a>
<h2>Options for "logOpts"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>Test</td><td><i>BOOL</i></td><td>Enable LogTest messages.</td><td><i></i></td></tr>
<tr><td>Debug</td><td><i>BOOL</i></td><td>Enable LogDebug messages.</td><td><i></i></td></tr>
<tr><td>Trace</td><td><i>BOOL</i></td><td>Enable LogTrace messages.</td><td><i></i></td></tr>
<tr><td>time</td><td><i>BOOL</i></td><td>Prefix with time.</td><td><i></i></td></tr>
<tr><td>date</td><td><i>BOOL</i></td><td>Prefix with date.</td><td><i></i></td></tr>
<tr><td>file</td><td><i>BOOL</i></td><td>Ouptut contains file:line.</td><td><i></i></td></tr>
<tr><td>func</td><td><i>BOOL</i></td><td>Output function.</td><td><i></i></td></tr>
<tr><td>full</td><td><i>BOOL</i></td><td>Show full file path.</td><td><i></i></td></tr>
<tr><td>before</td><td><i>BOOL</i></td><td>Output file:line before message string.</td><td><i></i></td></tr>
<tr><td>isUTC</td><td><i>BOOL</i></td><td>Time is to be UTC.</td><td><i></i></td></tr>
<tr><td>timeFmt</td><td><i>STRKEY</i></td><td>A format string to use with strftime.</td><td><i></i></td></tr>
<tr><td>chan</td><td><i>USEROBJ</i></td><td>Channel to send output to.</td><td><i></i></td></tr>
</table>


<a name="subOptsOptions"></a>
<h2>Options for "subOpts"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>blacklist</td><td><i>STRKEY</i></td><td>Comma separated modules to disable loading for.</td><td><i>initOnly</i></td></tr>
<tr><td>compat</td><td><i>BOOL</i></td><td>Ignore unknown options via JSI_OPTS_IGNORE_EXTRA in option parser.</td><td><i></i></td></tr>
<tr><td>dblPrec</td><td><i>INT</i></td><td>Format precision of double where 0=max, -1=max-1, ... (max-1).</td><td><i>initOnly</i></td></tr>
<tr><td>istty</td><td><i>BOOL</i></td><td>Indicates interp is in interactive mode.</td><td><i>readOnly</i></td></tr>
<tr><td>logColNums</td><td><i>BOOL</i></td><td>Display column numbers in error messages.</td><td><i></i></td></tr>
<tr><td>logAllowDups</td><td><i>BOOL</i></td><td>Log should not filter out duplicate messages.</td><td><i></i></td></tr>
<tr><td>mutexUnlock</td><td><i>BOOL</i></td><td>Unlock own mutex when evaling in other interps (true).</td><td><i>initOnly</i></td></tr>
<tr><td>noproto</td><td><i>BOOL</i></td><td>Disable support of the OOP symbols:  __proto__, prototype, constructor, etc.</td><td><i></i></td></tr>
<tr><td>noReadline</td><td><i>BOOL</i></td><td>In interactive mode disable use of readline.</td><td><i></i></td></tr>
<tr><td>outUndef</td><td><i>BOOL</i></td><td>In interactive mode output result values that are undefined.</td><td><i></i></td></tr>
</table>
<a name="Interpend"></a>
<p><a href="#TOC">Return to top</a>
<a name="JSON"></a>

<hr>


<h1>JSON</h1>

<font color=red>Synopsis:JSON.method(...)

</font><p>Commands for handling JSON data.


<h2>Methods for "JSON"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>check</td><td>check(str:string, strict:boolean=true):boolean </td><td>Return true if str is JSON.</td></tr>
<tr><td>parse</td><td>parse(str:string, strict:boolean=true) </td><td>Parse JSON and return js.</td></tr>
<tr><td>stringify</td><td>stringify(value:any,  strict:boolean=true):string </td><td>Return JSON from a js object.</td></tr>
</table>
<a name="JSONend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Math"></a>

<hr>


<h1>Math</h1>

<font color=red>Synopsis:Math.method(...)

</font><p>Commands performing math operations on numbers.


<h2>Methods for "Math"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>abs</td><td>abs(num:number):number </td><td>Returns the absolute value of x.</td></tr>
<tr><td>acos</td><td>acos(num:number):number </td><td>Returns the arccosine of x, in radians.</td></tr>
<tr><td>asin</td><td>asin(num:number):number </td><td>Returns the arcsine of x, in radians.</td></tr>
<tr><td>atan</td><td>atan(num:number):number </td><td>Returns the arctangent of x as a numeric value between -PI/2 and PI/2 radians.</td></tr>
<tr><td>atan2</td><td>atan2(x:number, y:number):number </td><td>Returns the arctangent of the quotient of its arguments.</td></tr>
<tr><td>ceil</td><td>ceil(num:number):number </td><td>Returns x, rounded upwards to the nearest integer.</td></tr>
<tr><td>cos</td><td>cos(num:number):number </td><td>Returns the cosine of x (x is in radians).</td></tr>
<tr><td>exp</td><td>exp(num:number):number </td><td>Returns the value of Ex.</td></tr>
<tr><td>floor</td><td>floor(num:number):number </td><td>Returns x, rounded downwards to the nearest integer.</td></tr>
<tr><td>log</td><td>log(num:number):number </td><td>Returns the natural logarithm (base E) of x.</td></tr>
<tr><td>max</td><td>max(x:number, y:number, ...):number </td><td>Returns the number with the highest value.</td></tr>
<tr><td>min</td><td>min(x:number, y:number, ...):number </td><td>Returns the number with the lowest value.</td></tr>
<tr><td>pow</td><td>pow(x:number, y:number):number </td><td>Returns the value of x to the power of y.</td></tr>
<tr><td>random</td><td>random():number </td><td>Returns a random number between 0 and 1.</td></tr>
<tr><td>round</td><td>round(num:number):number </td><td>Rounds x to the nearest integer.</td></tr>
<tr><td>sin</td><td>sin(num:number):number </td><td>Returns the sine of x (x is in radians).</td></tr>
<tr><td>sqrt</td><td>sqrt(num:number):number </td><td>Returns the square root of x.</td></tr>
<tr><td>tan</td><td>tan(num:number):number </td><td>Returns the tangent of an angle.</td></tr>
</table>
<a name="Mathend"></a>
<p><a href="#TOC">Return to top</a>
<a name="MySql"></a>

<hr>


<h1>MySql</h1>

<font color=red>Synopsis:new MySql(options:object=void):userobj

</font><p>Commands for accessing mysql databases.


<h2>Methods for "MySql"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>MySql</td><td>new MySql(<a href='#new MySqlOptions'>options</a>:object=void):userobj </td><td>Create a new db connection to a MySql database:.</td></tr>
<tr><td>affectedRows</td><td>affectedRows():number </td><td>Return affected rows.</td></tr>
<tr><td>complete</td><td>complete(sql:string):boolean </td><td>Return true if sql is complete.</td></tr>
<tr><td>conf</td><td>conf(<a href='#MySql.confOptions'>options</a>:string|object=void) </td><td>Configure options.</td></tr>
<tr><td>errorNo</td><td>errorNo():number </td><td>Return error code returned by most recent call to mysql3_exec().</td></tr>
<tr><td>errorState</td><td>errorState():string </td><td>Return the mysql error state str.</td></tr>
<tr><td>eval</td><td>eval(sql:string):number </td><td>Run sql commands without input/output.</td></tr>
<tr><td>exists</td><td>exists(sql:string):boolean </td><td>Execute sql, and return true if there is at least one result value.</td></tr>
<tr><td>info</td><td>info():object </td><td>Return info about last query.</td></tr>
<tr><td>lastQuery</td><td>lastQuery():string </td><td>Return info string about most recently executed statement.</td></tr>
<tr><td>lastRowid</td><td>lastRowid():number </td><td>Return rowid of last insert.</td></tr>
<tr><td>onecolumn</td><td>onecolumn(sql:string) </td><td>Execute sql, and return a single value.</td></tr>
<tr><td>ping</td><td>ping(noError:boolean=false):number </td><td>Ping connection.</td></tr>
<tr><td>query</td><td>query(sql:string, <a href='#MySql.queryOptions'>options</a>:function|object=void) </td><td>Run sql query with input and/or outputs..</td></tr>
<tr><td>reconnect</td><td>reconnect():void </td><td>Reconnect with current settings.</td></tr>
<tr><td>reset</td><td>reset():number </td><td>Reset connection.</td></tr>
</table>


<a name="new MySqlOptions"></a>
<a name="MySql.confOptions"></a>
<h2>Options for "new MySql"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>bindWarn</td><td><i>BOOL</i></td><td>Treat failed variable binds as a warning.</td><td><i>initOnly</i></td></tr>
<tr><td>database</td><td><i>STRKEY</i></td><td>Database to use.</td><td><i>initOnly</i></td></tr>
<tr><td>debug</td><td><i>ARRAY</i></td><td>Enable debug trace for various operations. (zero or more of: <b>eval</b>, <b>delete</b>, <b>prepare</b>, <b>step</b>)</td><td><i></i></td></tr>
<tr><td>enableMulti</td><td><i>BOOL</i></td><td>Accept muiltiple semi-colon separated statements in eval().</td><td><i>initOnly</i></td></tr>
<tr><td>errorCnt</td><td><i>INT</i></td><td>Count of errors.</td><td><i>readOnly</i></td></tr>
<tr><td>queryOpts</td><td><i><a href='#queryOptsOptions'>options</a></i></td><td>Default options for exec.</td><td><i></i></td></tr>
<tr><td>forceInt</td><td><i>BOOL</i></td><td>Bind float as int if possible.</td><td><i></i></td></tr>
<tr><td>host</td><td><i>STRING</i></td><td>IP address or host name for mysqld (default is 127.0.0.1).</td><td><i></i></td></tr>
<tr><td>maxStmts</td><td><i>INT</i></td><td>Max cache size for compiled statements.</td><td><i></i></td></tr>
<tr><td>name</td><td><i>DSTRING</i></td><td>Name for this db handle.</td><td><i></i></td></tr>
<tr><td>numStmts</td><td><i>INT</i></td><td>Current size of compiled statement cache.</td><td><i>readOnly</i></td></tr>
<tr><td>password</td><td><i>STRKEY</i></td><td>Database password..</td><td><i>initOnly</i></td></tr>
<tr><td>port</td><td><i>INT</i></td><td>IP port for mysqld.</td><td><i>initOnly</i></td></tr>
<tr><td>reconnect</td><td><i>BOOL</i></td><td>Reconnect.</td><td><i></i></td></tr>
<tr><td>sslKey</td><td><i>STRING</i></td><td>SSL key.</td><td><i></i></td></tr>
<tr><td>sslCert</td><td><i>STRING</i></td><td>SSL Cert.</td><td><i></i></td></tr>
<tr><td>sslCA</td><td><i>STRING</i></td><td>SSL CA.</td><td><i></i></td></tr>
<tr><td>sslCAPath</td><td><i>STRING</i></td><td>SSL CA path.</td><td><i></i></td></tr>
<tr><td>sslCipher</td><td><i>STRING</i></td><td>SSL Cipher.</td><td><i></i></td></tr>
<tr><td>user</td><td><i>STRKEY</i></td><td>Database user name. Default is current user-name..</td><td><i>initOnly</i></td></tr>
<tr><td>version</td><td><i>DOUBLE</i></td><td>Mysql version number.</td><td><i>readOnly</i></td></tr>
</table>


<a name="queryOptsOptions"></a>
<h2>Options for "queryOpts"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>callback</td><td><i>FUNC</i></td><td>Function to call with each row result. @function(values:object)</td><td><i></i></td></tr>
<tr><td>headers</td><td><i>BOOL</i></td><td>First row returned contains column labels.</td><td><i></i></td></tr>
<tr><td>limit</td><td><i>INT</i></td><td>Maximum number of returned values.</td><td><i></i></td></tr>
<tr><td>mapundef</td><td><i>BOOL</i></td><td>In variable binds, map an 'undefined' var to null.</td><td><i></i></td></tr>
<tr><td>maxString</td><td><i>INT</i></td><td>If not using prefetch, the maximum string value size (0=8K).</td><td><i></i></td></tr>
<tr><td>mode</td><td><i>STRKEY</i></td><td>Set output mode of returned data. (one of: <b>rows</b>, <b>arrays</b>, <b>array1d</b>, <b>list</b>, <b>column</b>, <b>json</b>, <b>json2</b>, <b>html</b>, <b>csv</b>, <b>insert</b>, <b>line</b>, <b>tabs</b>, <b>none</b>)</td><td><i></i></td></tr>
<tr><td>nocache</td><td><i>BOOL</i></td><td>Query is not to be cached.</td><td><i></i></td></tr>
<tr><td>noNamedParams</td><td><i>BOOL</i></td><td>Disable translating sql to support named params.</td><td><i></i></td></tr>
<tr><td>nullvalue</td><td><i>STRKEY</i></td><td>Null string output (for non-json mode).</td><td><i></i></td></tr>
<tr><td>paramVar</td><td><i>ARRAY</i></td><td>Array var to use for parameters.</td><td><i></i></td></tr>
<tr><td>prefetch</td><td><i>BOOL</i></td><td>Let client library cache entire results.</td><td><i></i></td></tr>
<tr><td>separator</td><td><i>STRKEY</i></td><td>Separator string (for csv and text mode).</td><td><i></i></td></tr>
<tr><td>table</td><td><i>STRKEY</i></td><td>Table name for mode=insert.</td><td><i></i></td></tr>
<tr><td>typeCheck</td><td><i>STRKEY</i></td><td>Type check mode (error). (one of: <b>convert</b>, <b>error</b>, <b>warn</b>, <b>disable</b>)</td><td><i></i></td></tr>
<tr><td>values</td><td><i>ARRAY</i></td><td>Values for ? bind parameters.</td><td><i></i></td></tr>
<tr><td>varName</td><td><i>STRKEY</i></td><td>String name of array var for ? bind parameters.</td><td><i></i></td></tr>
<tr><td>width</td><td><i>CUSTOM</i></td><td>In column mode, set column widths.</td><td><i></i></td></tr>
</table>


<a name="MySql.queryOptions"></a>
<a name="MySql.confOptions"></a>
<h2>Options for "MySql.query"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>callback</td><td><i>FUNC</i></td><td>Function to call with each row result. @function(values:object)</td><td><i></i></td></tr>
<tr><td>headers</td><td><i>BOOL</i></td><td>First row returned contains column labels.</td><td><i></i></td></tr>
<tr><td>limit</td><td><i>INT</i></td><td>Maximum number of returned values.</td><td><i></i></td></tr>
<tr><td>mapundef</td><td><i>BOOL</i></td><td>In variable binds, map an 'undefined' var to null.</td><td><i></i></td></tr>
<tr><td>maxString</td><td><i>INT</i></td><td>If not using prefetch, the maximum string value size (0=8K).</td><td><i></i></td></tr>
<tr><td>mode</td><td><i>STRKEY</i></td><td>Set output mode of returned data. (one of: <b>rows</b>, <b>arrays</b>, <b>array1d</b>, <b>list</b>, <b>column</b>, <b>json</b>, <b>json2</b>, <b>html</b>, <b>csv</b>, <b>insert</b>, <b>line</b>, <b>tabs</b>, <b>none</b>)</td><td><i></i></td></tr>
<tr><td>nocache</td><td><i>BOOL</i></td><td>Query is not to be cached.</td><td><i></i></td></tr>
<tr><td>noNamedParams</td><td><i>BOOL</i></td><td>Disable translating sql to support named params.</td><td><i></i></td></tr>
<tr><td>nullvalue</td><td><i>STRKEY</i></td><td>Null string output (for non-json mode).</td><td><i></i></td></tr>
<tr><td>paramVar</td><td><i>ARRAY</i></td><td>Array var to use for parameters.</td><td><i></i></td></tr>
<tr><td>prefetch</td><td><i>BOOL</i></td><td>Let client library cache entire results.</td><td><i></i></td></tr>
<tr><td>separator</td><td><i>STRKEY</i></td><td>Separator string (for csv and text mode).</td><td><i></i></td></tr>
<tr><td>table</td><td><i>STRKEY</i></td><td>Table name for mode=insert.</td><td><i></i></td></tr>
<tr><td>typeCheck</td><td><i>STRKEY</i></td><td>Type check mode (error). (one of: <b>convert</b>, <b>error</b>, <b>warn</b>, <b>disable</b>)</td><td><i></i></td></tr>
<tr><td>values</td><td><i>ARRAY</i></td><td>Values for ? bind parameters.</td><td><i></i></td></tr>
<tr><td>varName</td><td><i>STRKEY</i></td><td>String name of array var for ? bind parameters.</td><td><i></i></td></tr>
<tr><td>width</td><td><i>CUSTOM</i></td><td>In column mode, set column widths.</td><td><i></i></td></tr>
</table>
<a name="MySqlend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Number"></a>

<hr>


<h1>Number</h1>

<font color=red>Synopsis:new Number(num:string=0):number

</font><p>Commands for accessing number objects.


<h2>Methods for "Number"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Number</td><td>new Number(num:string=0):number </td><td>Number constructor.</td></tr>
<tr><td>isFinite</td><td>isFinite():boolean </td><td>Return true if is finite.</td></tr>
<tr><td>isInteger</td><td>isInteger():boolean </td><td>Return true if is an integer.</td></tr>
<tr><td>isNaN</td><td>isNaN():boolean </td><td>Return true if is NaN.</td></tr>
<tr><td>isSafeInteger</td><td>isSafeInteger():boolean </td><td>Return true if is a safe integer.</td></tr>
<tr><td>toExponential</td><td>toExponential(num:number):string </td><td>Converts a number into an exponential notation.</td></tr>
<tr><td>toFixed</td><td>toFixed(num:number=0):string </td><td>Formats a number with x numbers of digits after the decimal point.</td></tr>
<tr><td>toPrecision</td><td>toPrecision(num:number):string </td><td>Formats a number to x length.</td></tr>
<tr><td>toString</td><td>toString(radix:number=10):string </td><td>Convert to string.</td></tr>
</table>
<a name="Numberend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Object"></a>

<hr>


<h1>Object</h1>

<font color=red>Synopsis:new Object(val:object|null=void):object

</font><p>Commands for accessing Objects.


<h2>Methods for "Object"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Object</td><td>new Object(val:object|null=void):object </td><td>Object constructor.</td></tr>
<tr><td>create</td><td>create(proto:null|object, properties:object=void):object </td><td>Create a new object with prototype object and properties.</td></tr>
<tr><td>getPrototypeOf</td><td>getPrototypeOf(name:object):object </td><td>Return prototype of an object.</td></tr>
<tr><td>hasOwnProperty</td><td>hasOwnProperty(name:string):boolean </td><td>Returns a true if object has the specified property.</td></tr>
<tr><td>is</td><td>is(value1, value2):boolean </td><td>Tests if two values are equal.</td></tr>
<tr><td>isPrototypeOf</td><td>isPrototypeOf(name):boolean </td><td>Tests for an object in another object's prototype chain.</td></tr>
<tr><td>keys</td><td>keys(obj:object=void):array </td><td>Return the keys of an object or array.</td></tr>
<tr><td>merge</td><td>merge(obj:object):object </td><td>Return new object containing merged values.</td></tr>
<tr><td>propertyIsEnumerable</td><td>propertyIsEnumerable(name):boolean </td><td>Determine if a property is enumerable.</td></tr>
<tr><td>setPrototypeOf</td><td>setPrototypeOf(name:object, value:object) </td><td>Set prototype of an object.</td></tr>
<tr><td>toLocaleString</td><td>toLocaleString(quote:boolean=false):string </td><td>Convert to string.</td></tr>
<tr><td>toString</td><td>toString(quote:boolean=false):string </td><td>Convert to string.</td></tr>
<tr><td>valueOf</td><td>valueOf() </td><td>Returns primitive value.</td></tr>
</table>
<a name="Objectend"></a>
<p><a href="#TOC">Return to top</a>
<a name="RegExp"></a>

<hr>


<h1>RegExp</h1>

<font color=red>Synopsis:new RegExp(val:regexp|string, flags:string):regexp

</font><p>Commands for managing reqular expression objects.


<h2>Methods for "RegExp"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>RegExp</td><td>new RegExp(val:regexp|string, flags:string):regexp </td><td>Create a regexp object.</td></tr>
<tr><td>exec</td><td>exec(val:string):array|object|null </td><td>return matching string. Perform regexp match checking.  Returns the array of matches.With the global flag g, sets lastIndex and returns next match.</td></tr>
<tr><td>test</td><td>test(val:string):boolean </td><td>test if a string matches.</td></tr>
</table>
<a name="RegExpend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Signal"></a>

<hr>


<h1>Signal</h1>

<font color=red>Synopsis:Signal.method(...)

</font><p>Commands for handling unix signals.


<h2>Methods for "Signal"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>alarm</td><td>alarm(secs):number </td><td>Setup alarm in seconds.</td></tr>
<tr><td>callback</td><td>callback(func:function, sig:number|string):number </td><td>Setup callback handler for signal.</td></tr>
<tr><td>handle</td><td>handle(sig:number|string=void, ...) </td><td>Set named signals to handle action.</td></tr>
<tr><td>ignore</td><td>ignore(sig:number|string=void, ...) </td><td>Set named signals to ignore action.</td></tr>
<tr><td>kill</td><td>kill(pid:number, sig:number|string='SIGTERM'):void </td><td>Send signal to process id.</td></tr>
<tr><td>names</td><td>names():array </td><td>Return names of all signals.</td></tr>
<tr><td>reset</td><td>reset(sig:number|string=void, ...):array </td><td>Set named signals to default action.</td></tr>
</table>
<a name="Signalend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Socket"></a>

<hr>


<h1>Socket</h1>

<font color=red>Synopsis:new Socket(options:object=void):userobj

</font><p>Commands for managing Socket server/client connections.


<h2>Methods for "Socket"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Socket</td><td>new Socket(<a href='#new SocketOptions'>options</a>:object=void):userobj </td><td>Create socket server/client object.Create a socket server or client object.</td></tr>
<tr><td>close</td><td>close():void </td><td>Close socket(s).</td></tr>
<tr><td>conf</td><td>conf(<a href='#Socket.confOptions'>options</a>:string|object=void) </td><td>Configure options.</td></tr>
<tr><td>idconf</td><td>idconf(id:number=void, <a href='#Socket.idconfOptions'>options</a>:string|object=void) </td><td>Configure options for a connection id, or return list of ids.</td></tr>
<tr><td>names</td><td>names():array </td><td>Return list of active ids on server.</td></tr>
<tr><td>recv</td><td>recv(id:number=void):string </td><td>Recieve data.</td></tr>
<tr><td>send</td><td>send(id:number, data:string, <a href='#Socket.sendOptions'>options</a>:object=void):void </td><td>Send a socket message to id. Send a message to a (or all if -1) connection.</td></tr>
<tr><td>update</td><td>update():void </td><td>Service events for just this socket.</td></tr>
</table>


<a name="new SocketOptions"></a>
<a name="Socket.confOptions"></a>
<h2>Options for "new Socket"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>address</td><td><i>STRING</i></td><td>Client destination address (127.0.0.0).</td><td><i>initOnly</i></td></tr>
<tr><td>broadcast</td><td><i>BOOL</i></td><td>Enable broadcast.</td><td><i>initOnly</i></td></tr>
<tr><td>client</td><td><i>BOOL</i></td><td>Enable client mode.</td><td><i>initOnly</i></td></tr>
<tr><td>connectCnt</td><td><i>INT</i></td><td>Counter for number of active connections.</td><td><i>readOnly</i></td></tr>
<tr><td>createLast</td><td><i>TIME_T</i></td><td>Time of last create.</td><td><i>readOnly</i></td></tr>
<tr><td>debug</td><td><i>INT</i></td><td>Debugging level.</td><td><i></i></td></tr>
<tr><td>echo</td><td><i>BOOL</i></td><td>LogInfo outputs all socket Send/Recv messages.</td><td><i></i></td></tr>
<tr><td>interface</td><td><i>STRING</i></td><td>Interface for server to listen on, eg. 'eth0' or 'lo'.</td><td><i>initOnly</i></td></tr>
<tr><td>keepalive</td><td><i>BOOL</i></td><td>Enable keepalive.</td><td><i>initOnly</i></td></tr>
<tr><td>maxConnects</td><td><i>INT</i></td><td>In server mode, max number of client connections accepted.</td><td><i></i></td></tr>
<tr><td>mcastAddMember</td><td><i>STRING</i></td><td>Multicast add membership: address/interface ('127.0.0.1/0.0.0.0').</td><td><i>initOnly</i></td></tr>
<tr><td>mcastInterface</td><td><i>STRING</i></td><td>Multicast interface address.</td><td><i>initOnly</i></td></tr>
<tr><td>mcastNoLoop</td><td><i>BOOL</i></td><td>Multicast loopback disable.</td><td><i>initOnly</i></td></tr>
<tr><td>mcastTtl</td><td><i>INT</i></td><td>Multicast TTL.</td><td><i>initOnly</i></td></tr>
<tr><td>noAsync</td><td><i>BOOL</i></td><td>Send is not async.</td><td><i>initOnly</i></td></tr>
<tr><td>noUpdate</td><td><i>BOOL</i></td><td>Stop processing update events (eg. to exit).</td><td><i></i></td></tr>
<tr><td>onClose</td><td><i>FUNC</i></td><td>Function to call when connection closes. @function(s:userobj|null, id:number)</td><td><i></i></td></tr>
<tr><td>onCloseLast</td><td><i>FUNC</i></td><td>Function to call when last connection closes. On object delete arg is null. @function(s:userobj|null)</td><td><i></i></td></tr>
<tr><td>noConfig</td><td><i>BOOL</i></td><td>Disable use of Socket.conf to change options after create.</td><td><i>initOnly</i></td></tr>
<tr><td>onOpen</td><td><i>FUNC</i></td><td>Function to call when connection opens. @function(s:userobj, info:object)</td><td><i></i></td></tr>
<tr><td>onRecv</td><td><i>FUNC</i></td><td>Function to call with recieved data. @function(s:userobj, id:number, data:string)</td><td><i></i></td></tr>
<tr><td>port</td><td><i>INT</i></td><td>Port for client dest or server listen.</td><td><i>initOnly</i></td></tr>
<tr><td>quiet</td><td><i>BOOL</i></td><td>Suppress info messages.</td><td><i>initOnly</i></td></tr>
<tr><td>recvTimeout</td><td><i>UINT64</i></td><td>Timeout for receive, in microseconds.</td><td><i>initOnly</i></td></tr>
<tr><td>sendTimeout</td><td><i>UINT64</i></td><td>Timeout for send, in microseconds.</td><td><i>initOnly</i></td></tr>
<tr><td>srcAddress</td><td><i>STRING</i></td><td>Client source address.</td><td><i>initOnly</i></td></tr>
<tr><td>srcPort</td><td><i>INT</i></td><td>Client source port.</td><td><i>initOnly</i></td></tr>
<tr><td>startTime</td><td><i>TIME_T</i></td><td>Time of start.</td><td><i>readOnly</i></td></tr>
<tr><td>stats</td><td><i><a href='#statsOptions'>options</a></i></td><td>Statistical data.</td><td><i>readOnly</i></td></tr>
<tr><td>timeout</td><td><i>NUMBER</i></td><td>Timeout value in seconds (0.5).</td><td><i>initOnly</i></td></tr>
<tr><td>tos</td><td><i>INT8</i></td><td>Type-Of-Service value.</td><td><i>initOnly</i></td></tr>
<tr><td>ttl</td><td><i>INT</i></td><td>Time-To-Live value.</td><td><i>initOnly</i></td></tr>
<tr><td>udp</td><td><i>BOOL</i></td><td>Protocol is udp.</td><td><i>initOnly</i></td></tr>
</table>


<a name="statsOptions"></a>
<h2>Options for "stats"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>echo</td><td><i>BOOL</i></td><td>LogInfo outputs all socket Send/Recv messages.</td><td><i></i></td></tr>
<tr><td>eventCnt</td><td><i>INT</i></td><td>Number of events of any type.</td><td><i></i></td></tr>
<tr><td>eventLast</td><td><i>TIME_T</i></td><td>Time of last event of any type.</td><td><i></i></td></tr>
<tr><td>recvAddr</td><td><i>CUSTOM</i></td><td>Incoming port and address.</td><td><i></i></td></tr>
<tr><td>recvCnt</td><td><i>INT</i></td><td>Number of recieves.</td><td><i></i></td></tr>
<tr><td>recvLast</td><td><i>TIME_T</i></td><td>Time of last recv.</td><td><i></i></td></tr>
<tr><td>sentCnt</td><td><i>INT</i></td><td>Number of sends.</td><td><i></i></td></tr>
<tr><td>sentLast</td><td><i>TIME_T</i></td><td>Time of last send.</td><td><i></i></td></tr>
<tr><td>sentErrCnt</td><td><i>INT</i></td><td>Number of sends.</td><td><i></i></td></tr>
<tr><td>sentErrLast</td><td><i>TIME_T</i></td><td>Time of last sendErr.</td><td><i></i></td></tr>
</table>


<a name="Socket.idconfOptions"></a>
<a name="Socket.confOptions"></a>
<h2>Options for "Socket.idconf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>echo</td><td><i>BOOL</i></td><td>LogInfo outputs all socket Send/Recv messages.</td><td><i></i></td></tr>
<tr><td>eventCnt</td><td><i>INT</i></td><td>Number of events of any type.</td><td><i></i></td></tr>
<tr><td>eventLast</td><td><i>TIME_T</i></td><td>Time of last event of any type.</td><td><i></i></td></tr>
<tr><td>recvAddr</td><td><i>CUSTOM</i></td><td>Incoming port and address.</td><td><i></i></td></tr>
<tr><td>recvCnt</td><td><i>INT</i></td><td>Number of recieves.</td><td><i></i></td></tr>
<tr><td>recvLast</td><td><i>TIME_T</i></td><td>Time of last recv.</td><td><i></i></td></tr>
<tr><td>sentCnt</td><td><i>INT</i></td><td>Number of sends.</td><td><i></i></td></tr>
<tr><td>sentLast</td><td><i>TIME_T</i></td><td>Time of last send.</td><td><i></i></td></tr>
<tr><td>sentErrCnt</td><td><i>INT</i></td><td>Number of sends.</td><td><i></i></td></tr>
<tr><td>sentErrLast</td><td><i>TIME_T</i></td><td>Time of last sendErr.</td><td><i></i></td></tr>
</table>


<a name="Socket.sendOptions"></a>
<a name="Socket.confOptions"></a>
<h2>Options for "Socket.send"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>noAsync</td><td><i>BOOL</i></td><td>Send is not async.</td><td><i></i></td></tr>
</table>
<a name="Socketend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Sqlite"></a>

<hr>


<h1>Sqlite</h1>

<font color=red>Synopsis:new Sqlite(file:string=void, options:object=void):userobj

</font><p>Commands for accessing sqlite databases.


<h2>Methods for "Sqlite"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>Sqlite</td><td>new Sqlite(file:string=void, <a href='#new SqliteOptions'>options</a>:object=void):userobj </td><td>Create a new db connection to the named file or :memory:.</td></tr>
<tr><td>backup</td><td>backup(file:string, dbname:string='main'):void </td><td>Backup db to file. Open or create a database file named FILENAME.
Transfer the content of local database DATABASE (default: 'main') into the FILENAME database.</td></tr>
<tr><td>collate</td><td>collate(name:string, callback:function):void </td><td>Create new SQL collation command.</td></tr>
<tr><td>complete</td><td>complete(sql:string):boolean </td><td>Return true if sql is complete.</td></tr>
<tr><td>conf</td><td>conf(<a href='#Sqlite.confOptions'>options</a>:string|object=void) </td><td>Configure options.</td></tr>
<tr><td>eval</td><td>eval(sql:string):number </td><td>Run sql commands without input/output. Supports multiple semicolon seperated commands.
Variable binding is NOT performed, results are discarded, and  returns sqlite3_changes()</td></tr>
<tr><td>exists</td><td>exists(sql:string):boolean </td><td>Execute sql, and return true if there is at least one result value.</td></tr>
<tr><td>filename</td><td>filename(name:string='main'):string </td><td>Return filename for named or all attached databases.</td></tr>
<tr><td>func</td><td>func(name:string, callback:function, numArgs:number=void):void </td><td>Register a new function with database.</td></tr>
<tr><td>import</td><td>import(table:string, file:string, <a href='#Sqlite.importOptions'>options</a>:object=void):number </td><td>Import data from file into table . Import data from a file into table. SqlOptions include the 'separator' to use, which defaults to commas for csv, or tabs otherwise.
If a column contains a null string, or the value of 'nullvalue', a null is inserted for the column.
A 'conflict' is one of the sqlite conflict algorithms:    rollback, abort, fail, ignore, replace
On success, return the number of lines processed, not necessarily same as 'changeCnt' due to the conflict algorithm selected. </td></tr>
<tr><td>interrupt</td><td>interrupt():void </td><td>Interrupt in progress statement.</td></tr>
<tr><td>onecolumn</td><td>onecolumn(sql:string) </td><td>Execute sql, and return a single value.</td></tr>
<tr><td>query</td><td>query(sql:string, <a href='#Sqlite.queryOptions'>options</a>:function|object=void) </td><td>Evaluate an sql query with bindings. Return values in formatted as JSON, HTML, etc. , optionally calling function with a result object</td></tr>
<tr><td>restore</td><td>restore(file:string, dbname:string):void </td><td>Restore db from file (default db is 'main').    db.restore(FILENAME, ?,DATABASE? ) 
Open a database file named FILENAME.  Transfer the content of FILENAME into the local database DATABASE (default: 'main').</td></tr>
<tr><td>transaction</td><td>transaction(callback:function, type:string=void):void </td><td>Call function inside db tranasaction. Type is: 'deferred', 'exclusive', 'immediate'. Start a new transaction (if we are not already in the midst of a transaction) and execute the JS function FUNC.
After FUNC completes, either commit the transaction or roll it back if FUNC throws an exception.
Or if no new transation was started, do nothing. pass the exception on up the stack.</td></tr>
</table>


<a name="new SqliteOptions"></a>
<a name="Sqlite.confOptions"></a>
<h2>Options for "new Sqlite"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>bindWarn</td><td><i>BOOL</i></td><td>Treat failed variable binds as a warning.</td><td><i>initOnly</i></td></tr>
<tr><td>changeCnt</td><td><i>INT</i></td><td>The number of rows modified, inserted, or deleted by last command.</td><td><i></i></td></tr>
<tr><td>changeCntAll</td><td><i>INT</i></td><td>Total number of rows modified, inserted, or deleted since db opened.</td><td><i></i></td></tr>
<tr><td>debug</td><td><i>ARRAY</i></td><td>Enable debug trace for various operations. (zero or more of: <b>eval</b>, <b>delete</b>, <b>prepare</b>, <b>step</b>)</td><td><i></i></td></tr>
<tr><td>echo</td><td><i>BOOL</i></td><td>LogInfo outputs the string value of every query and eval.</td><td><i></i></td></tr>
<tr><td>errCnt</td><td><i>INT</i></td><td>Count of errors in script callbacks.</td><td><i>readOnly</i></td></tr>
<tr><td>errorCode</td><td><i>INT</i></td><td>Numeric error code returned by the most recent call to sqlite3_exec.</td><td><i></i></td></tr>
<tr><td>forceInt</td><td><i>BOOL</i></td><td>Bind float as int if possible.</td><td><i></i></td></tr>
<tr><td>lastInsertId</td><td><i>UINT64</i></td><td>The rowid of last insert.</td><td><i></i></td></tr>
<tr><td>load</td><td><i>BOOL</i></td><td>Extensions can be loaded.</td><td><i></i></td></tr>
<tr><td>mutex</td><td><i>STRKEY</i></td><td>Mutex type to use. (one of: <b>default</b>, <b>none</b>, <b>full</b>)</td><td><i>initOnly</i></td></tr>
<tr><td>name</td><td><i>DSTRING</i></td><td>Optional name field.</td><td><i></i></td></tr>
<tr><td>noConfig</td><td><i>BOOL</i></td><td>Disable use of Sqlite.conf to change options after create.</td><td><i>initOnly</i></td></tr>
<tr><td>noCreate</td><td><i>BOOL</i></td><td>Database is must already exist (false).</td><td><i>initOnly</i></td></tr>
<tr><td>onAuth</td><td><i>FUNC</i></td><td>Function to call for auth. @function(db:userobj, code:string, descr1:string, decr2:string, dbname:string, trigname:string)</td><td><i></i></td></tr>
<tr><td>onBusy</td><td><i>FUNC</i></td><td>Function to call when busy. @function(db:userobj, tries:number)</td><td><i></i></td></tr>
<tr><td>onCommit</td><td><i>FUNC</i></td><td>Function to call on commit. @function(db:userobj)</td><td><i></i></td></tr>
<tr><td>onNeedCollate</td><td><i>FUNC</i></td><td>Function to call for collation. @function(db:userobj, name:string)</td><td><i></i></td></tr>
<tr><td>onProfile</td><td><i>FUNC</i></td><td>Function to call for profile. @function(db:userobj, sql:string, time:number)</td><td><i></i></td></tr>
<tr><td>onProgress</td><td><i>FUNC</i></td><td>Function to call for progress: progressSteps must be >0. @function(db:userobj)</td><td><i></i></td></tr>
<tr><td>onRollback</td><td><i>FUNC</i></td><td>Function to call for rollback. @function(db:userobj)</td><td><i></i></td></tr>
<tr><td>onTrace</td><td><i>FUNC</i></td><td>Function to call for trace. @function(db:userobj, sql:string)</td><td><i></i></td></tr>
<tr><td>onUpdate</td><td><i>FUNC</i></td><td>Function to call for update. @function(db:userobj, op:string, dbname:string, table:string, rowid:number)</td><td><i></i></td></tr>
<tr><td>onWalHook</td><td><i>FUNC</i></td><td>Function to call for WAL. @function(db:userobj, dbname:string, entry:number)</td><td><i></i></td></tr>
<tr><td>progressSteps</td><td><i>UINT</i></td><td>Number of steps between calling onProgress: 0 is disabled.</td><td><i></i></td></tr>
<tr><td>queryOpts</td><td><i><a href='#queryOptsOptions'>options</a></i></td><td>Default options for to use with query().</td><td><i></i></td></tr>
<tr><td>readonly</td><td><i>BOOL</i></td><td>Database opened in readonly mode.</td><td><i>initOnly</i></td></tr>
<tr><td>sortCnt</td><td><i>INT</i></td><td>Number of sorts in most recent operation.</td><td><i>readOnly</i></td></tr>
<tr><td>stepCnt</td><td><i>INT</i></td><td>Number of steps in most recent operation.</td><td><i>readOnly</i></td></tr>
<tr><td>stmtCacheCnt</td><td><i>INT</i></td><td>Current size of compiled statement cache.</td><td><i>readOnly</i></td></tr>
<tr><td>stmtCacheMax</td><td><i>INT</i></td><td>Max cache size for compiled statements.</td><td><i></i></td></tr>
<tr><td>timeout</td><td><i>INT</i></td><td>Amount of time to wait when file is locked, in ms.</td><td><i></i></td></tr>
<tr><td>version</td><td><i>OBJ</i></td><td>Sqlite version info.</td><td><i>readOnly</i></td></tr>
<tr><td>vfs</td><td><i>STRING</i></td><td>VFS to use.</td><td><i>initOnly</i></td></tr>
</table>


<a name="queryOptsOptions"></a>
<h2>Options for "queryOpts"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>callback</td><td><i>FUNC</i></td><td>Function to call with each row result. @function(values:object)</td><td><i></i></td></tr>
<tr><td>cdata</td><td><i>STRKEY</i></td><td>Name of Cdata array object to use.</td><td><i></i></td></tr>
<tr><td>headers</td><td><i>BOOL</i></td><td>First row returned contains column labels.</td><td><i></i></td></tr>
<tr><td>limit</td><td><i>INT</i></td><td>Maximum number of returned values.</td><td><i></i></td></tr>
<tr><td>mapundef</td><td><i>BOOL</i></td><td>In variable bind, map an 'undefined' var to null.</td><td><i></i></td></tr>
<tr><td>mode</td><td><i>STRKEY</i></td><td>Set output mode of returned data. (one of: <b>rows</b>, <b>arrays</b>, <b>array1d</b>, <b>list</b>, <b>column</b>, <b>json</b>, <b>json2</b>, <b>html</b>, <b>csv</b>, <b>insert</b>, <b>line</b>, <b>tabs</b>, <b>none</b>)</td><td><i></i></td></tr>
<tr><td>nocache</td><td><i>BOOL</i></td><td>Query is not to be cached.</td><td><i></i></td></tr>
<tr><td>nullvalue</td><td><i>STRKEY</i></td><td>Null string output (for non js/json mode).</td><td><i></i></td></tr>
<tr><td>retChanged</td><td><i>BOOL</i></td><td>Query returns value of sqlite3_changed().</td><td><i></i></td></tr>
<tr><td>separator</td><td><i>STRKEY</i></td><td>Separator string (for csv and text mode).</td><td><i></i></td></tr>
<tr><td>typeCheck</td><td><i>STRKEY</i></td><td>Type check mode (warn). (one of: <b>convert</b>, <b>warn</b>, <b>error</b>, <b>disable</b>)</td><td><i></i></td></tr>
<tr><td>table</td><td><i>STRKEY</i></td><td>Table name for mode=insert.</td><td><i></i></td></tr>
<tr><td>values</td><td><i>ARRAY</i></td><td>Values for ? bind parameters.</td><td><i></i></td></tr>
<tr><td>varName</td><td><i>STRKEY</i></td><td>Array var for ? bind parameters.</td><td><i></i></td></tr>
<tr><td>width</td><td><i>CUSTOM</i></td><td>In column mode, set column widths.</td><td><i></i></td></tr>
</table>


<a name="Sqlite.importOptions"></a>
<a name="Sqlite.confOptions"></a>
<h2>Options for "Sqlite.import"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>headers</td><td><i>BOOL</i></td><td>First row contains column labels.</td><td><i></i></td></tr>
<tr><td>csv</td><td><i>BOOL</i></td><td>Treat input values as CSV.</td><td><i></i></td></tr>
<tr><td>conflict</td><td><i>STRKEY</i></td><td>Set conflict resolution. (one of: <b>ROLLBACK</b>, <b>ABORT</b>, <b>FAIL</b>, <b>IGNORE</b>, <b>REPLACE</b>)</td><td><i></i></td></tr>
<tr><td>limit</td><td><i>INT</i></td><td>Maximum number of lines to load.</td><td><i></i></td></tr>
<tr><td>nullvalue</td><td><i>STRKEY</i></td><td>Null string.</td><td><i></i></td></tr>
<tr><td>separator</td><td><i>STRKEY</i></td><td>Separator string; default is comma if csv, else tabs.</td><td><i></i></td></tr>
</table>


<a name="Sqlite.queryOptions"></a>
<a name="Sqlite.confOptions"></a>
<h2>Options for "Sqlite.query"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>callback</td><td><i>FUNC</i></td><td>Function to call with each row result. @function(values:object)</td><td><i></i></td></tr>
<tr><td>cdata</td><td><i>STRKEY</i></td><td>Name of Cdata array object to use.</td><td><i></i></td></tr>
<tr><td>headers</td><td><i>BOOL</i></td><td>First row returned contains column labels.</td><td><i></i></td></tr>
<tr><td>limit</td><td><i>INT</i></td><td>Maximum number of returned values.</td><td><i></i></td></tr>
<tr><td>mapundef</td><td><i>BOOL</i></td><td>In variable bind, map an 'undefined' var to null.</td><td><i></i></td></tr>
<tr><td>mode</td><td><i>STRKEY</i></td><td>Set output mode of returned data. (one of: <b>rows</b>, <b>arrays</b>, <b>array1d</b>, <b>list</b>, <b>column</b>, <b>json</b>, <b>json2</b>, <b>html</b>, <b>csv</b>, <b>insert</b>, <b>line</b>, <b>tabs</b>, <b>none</b>)</td><td><i></i></td></tr>
<tr><td>nocache</td><td><i>BOOL</i></td><td>Query is not to be cached.</td><td><i></i></td></tr>
<tr><td>nullvalue</td><td><i>STRKEY</i></td><td>Null string output (for non js/json mode).</td><td><i></i></td></tr>
<tr><td>retChanged</td><td><i>BOOL</i></td><td>Query returns value of sqlite3_changed().</td><td><i></i></td></tr>
<tr><td>separator</td><td><i>STRKEY</i></td><td>Separator string (for csv and text mode).</td><td><i></i></td></tr>
<tr><td>typeCheck</td><td><i>STRKEY</i></td><td>Type check mode (warn). (one of: <b>convert</b>, <b>warn</b>, <b>error</b>, <b>disable</b>)</td><td><i></i></td></tr>
<tr><td>table</td><td><i>STRKEY</i></td><td>Table name for mode=insert.</td><td><i></i></td></tr>
<tr><td>values</td><td><i>ARRAY</i></td><td>Values for ? bind parameters.</td><td><i></i></td></tr>
<tr><td>varName</td><td><i>STRKEY</i></td><td>Array var for ? bind parameters.</td><td><i></i></td></tr>
<tr><td>width</td><td><i>CUSTOM</i></td><td>In column mode, set column widths.</td><td><i></i></td></tr>
</table>
<a name="Sqliteend"></a>
<p><a href="#TOC">Return to top</a>
<a name="String"></a>

<hr>


<h1>String</h1>

<font color=red>Synopsis:new String(str):string

</font><p>Commands for accessing string objects..


<h2>Methods for "String"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>String</td><td>new String(str):string </td><td>String constructor.</td></tr>
<tr><td>charAt</td><td>charAt(index:number):string </td><td>Return char at index.</td></tr>
<tr><td>charCodeAt</td><td>charCodeAt(index:number):number </td><td>Return char code at index.</td></tr>
<tr><td>concat</td><td>concat(str:string, ...):string </td><td>Append one or more strings.</td></tr>
<tr><td>indexOf</td><td>indexOf(str:string, start:number):number </td><td>Return index of char.</td></tr>
<tr><td>lastIndexOf</td><td>lastIndexOf(str:string, start:number):number </td><td>Return index of last char.</td></tr>
<tr><td>map</td><td>map(strMap:array, nocase:boolean=false):string </td><td>Replaces characters in string based on the key-value pairs in strMap.</td></tr>
<tr><td>match</td><td>match(pattern:regexp|string):array|null </td><td>Return array of matches.</td></tr>
<tr><td>repeat</td><td>repeat(count:number):string </td><td>Return count copies of string.</td></tr>
<tr><td>replace</td><td>replace(pattern:regexp|string, replace:string|function):string </td><td>Regex/string replacement. If the replace argument is a function, it is called with match,p1,p2,...,offset,string.  If called function is known to have 1 argument, it is called with just the match.</td></tr>
<tr><td>search</td><td>search(pattern:regexp|string):number </td><td>Return index of first char matching pattern.</td></tr>
<tr><td>slice</td><td>slice(start:number, end:number):string </td><td>Return section of string.</td></tr>
<tr><td>split</td><td>split(char:string|null=void):array </td><td>Split on char and return Array: null removes empty elements.</td></tr>
<tr><td>substr</td><td>substr(start:number, length:number):string </td><td>Return substring.</td></tr>
<tr><td>substring</td><td>substring(start:number, end:number):string </td><td>Return substring.</td></tr>
<tr><td>toLocaleLowerCase</td><td>toLocaleLowerCase():string </td><td>Lower case.</td></tr>
<tr><td>toLocaleUpperCase</td><td>toLocaleUpperCase():string </td><td>Upper case.</td></tr>
<tr><td>toLowerCase</td><td>toLowerCase():string </td><td>Return lower cased string.</td></tr>
<tr><td>toTitle</td><td>toTitle(chars:string):string </td><td>Make first char upper case.</td></tr>
<tr><td>toUpperCase</td><td>toUpperCase():string </td><td>Return upper cased string.</td></tr>
<tr><td>trim</td><td>trim(chars:string):string </td><td>Trim chars.</td></tr>
<tr><td>trimLeft</td><td>trimLeft(chars:string):string </td><td>Trim chars from left.</td></tr>
<tr><td>trimRight</td><td>trimRight(chars:string):string </td><td>Trim chars from right.</td></tr>
</table>
<a name="Stringend"></a>
<p><a href="#TOC">Return to top</a>
<a name="System"></a>

<hr>


<h1>System</h1>

<font color=red>Synopsis:System.method(...)

</font><p>Builtin system commands. All methods are exported as global.


<h2>Methods for "System"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>assert</td><td>assert(expr:boolean|number|function, msg:string=void, <a href='#System.assertOptions'>options</a>:object=void):void </td><td>Throw or output msg if expr is false. Assert does nothing by default, but can be enabled with "use assert" or setting Interp.asserts.</td></tr>
<tr><td>clearInterval</td><td>clearInterval(id:number):void </td><td>Delete event id returned from setInterval/setTimeout/info.events().</td></tr>
<tr><td>decodeURI</td><td>decodeURI(val:string):string </td><td>Decode an HTTP URL.</td></tr>
<tr><td>encodeURI</td><td>encodeURI(val:string):string </td><td>Encode an HTTP URL.</td></tr>
<tr><td>exec</td><td>exec(val:string, <a href='#System.execOptions'>options</a>:string|object=void) </td><td>Execute an OS command. If the command ends with '&', set the 'bg' option to true.
The second argument can be a string, which is the same as setting the 'inputStr' option.
By default, returns the string output, unless the 'bg', 'inputStr', 'retCode' or 'retAll' options are used</td></tr>
<tr><td>exit</td><td>exit(code:number=0):void </td><td>Exit the current interpreter.</td></tr>
<tr><td>format</td><td>format(format:string, ...):string </td><td>Printf style formatting: adds %q and %S.</td></tr>
<tr><td>isFinite</td><td>isFinite(val):boolean </td><td>Return true if is a finite number.</td></tr>
<tr><td>isMain</td><td>isMain():boolean </td><td>Return true if current script was the main script invoked from command-line.</td></tr>
<tr><td>isNaN</td><td>isNaN(val):boolean </td><td>Return true if not a number.</td></tr>
<tr><td>load</td><td>load(shlib:string):void </td><td>Load a shared executable and invoke its _Init call.</td></tr>
<tr><td>log</td><td>log(val, ...):void </td><td>Same as puts, but includes file:line.</td></tr>
<tr><td>noOp</td><td>noOp() </td><td>A No-Op. A zero overhead command call that is useful for debugging.</td></tr>
<tr><td>parseFloat</td><td>parseFloat(val):number </td><td>Convert string to a double.</td></tr>
<tr><td>parseInt</td><td>parseInt(val:any, base:number=10):number </td><td>Convert string to an integer.</td></tr>
<tr><td>parseOpts</td><td>parseOpts(self:object|userobj, options:object, conf:object|null|undefined) </td><td>Parse options.</td></tr>
<tr><td>printf</td><td>printf(format:string, ...):void </td><td>Formatted output to stdout.</td></tr>
<tr><td>provide</td><td>provide(name:string|function=void, version:number|string=1.0):void </td><td>Provide a package for use with require. Default is the file tail-rootname.</td></tr>
<tr><td>puts</td><td>puts(val, ...):void </td><td>Output one or more values to stdout. Each argument is quoted.  Use Interp.logOpts to control source line and/or timestamps output.</td></tr>
<tr><td>quote</td><td>quote(val:string):string </td><td>Return quoted string.</td></tr>
<tr><td>require</td><td>require(name:string=void, version:number|string=1):number|array|object </td><td>Load/query packages. With no arguments, returns the list of all loaded packages.
With one argument, loads the package (if necessary) and returns its version.
With two arguments, returns object containing: version, loadFile, func.
An error is thrown if requested version is greater than actual version.</td></tr>
<tr><td>runModule</td><td>runModule(cmd:string|function=void, conf:array=undefined) </td><td>Invoke named module. If name is empty, uses file basename. If isMain and no args givine parses console.args.</td></tr>
<tr><td>setInterval</td><td>setInterval(callback:function, ms:number):number </td><td>Setup recurring function to run every given millisecs.</td></tr>
<tr><td>setTimeout</td><td>setTimeout(callback:function, ms:number):number </td><td>Setup function to run after given millisecs.</td></tr>
<tr><td>sleep</td><td>sleep(secs:number=1.0):void </td><td>sleep for N milliseconds, minimum .001.</td></tr>
<tr><td>source</td><td>source(val:string|array, <a href='#System.sourceOptions'>options</a>:object=void):void </td><td>Load and evaluate source files.</td></tr>
<tr><td>strftime</td><td>strftime(num:number=null, <a href='#System.strftimeOptions'>options</a>:string|object=void):string </td><td>Format numeric time (in ms) to a string. Null or no value will use current time.</td></tr>
<tr><td>strptime</td><td>strptime(val:string=void, <a href='#System.strptimeOptions'>options</a>:string|object=void):number </td><td>Parse time from string and return ms time since 1970-01-01 in UTC, or NaN.</td></tr>
<tr><td>unload</td><td>unload(shlib:string):void </td><td>Unload a shared executable and invoke its _Done call.</td></tr>
<tr><td>update</td><td>update(<a href='#System.updateOptions'>options</a>:number|object=void):number </td><td>Service all events, eg. setInterval/setTimeout. Returns the number of events processed. Events are processed until minTime (in milliseconds) is exceeded, or forever if -1.
The default minTime is 0, meaning return as soon as no events can be processed. A positive mintime will result in sleeps between event checks.</td></tr>
</table>


<a name="System.assertOptions"></a>
<a name="System.confOptions"></a>
<h2>Options for "System.assert"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>mode</td><td><i>STRKEY</i></td><td>Action when assertion is false. Default from Interp.assertMode. (one of: <b>throw</b>, <b>log</b>, <b>puts</b>)</td><td><i></i></td></tr>
<tr><td>noStderr</td><td><i>BOOL</i></td><td>Logged msg to stdout. Default from Interp.noStderr.</td><td><i></i></td></tr>
</table>


<a name="System.execOptions"></a>
<a name="System.confOptions"></a>
<h2>Options for "System.exec"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>bg</td><td><i>BOOL</i></td><td>Run command in background using system() and return OS code.</td><td><i></i></td></tr>
<tr><td>chdir</td><td><i>STRING</i></td><td>Change to directory.</td><td><i></i></td></tr>
<tr><td>inputStr</td><td><i>STRING</i></td><td>Use string as input and return OS code.</td><td><i></i></td></tr>
<tr><td>noError</td><td><i>BOOL</i></td><td>Suppress all OS errors.</td><td><i></i></td></tr>
<tr><td>noRedir</td><td><i>BOOL</i></td><td>Disable redirect and shell escapes in command.</td><td><i></i></td></tr>
<tr><td>trim</td><td><i>BOOL</i></td><td>Trim trailing whitespace from output.</td><td><i></i></td></tr>
<tr><td>retAll</td><td><i>BOOL</i></td><td>Return the OS return code and data as an object.</td><td><i></i></td></tr>
<tr><td>retCode</td><td><i>BOOL</i></td><td>Return only the OS return code.</td><td><i></i></td></tr>
</table>


<a name="System.sourceOptions"></a>
<a name="System.confOptions"></a>
<h2>Options for "System.source"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>autoIndex</td><td><i>BOOL</i></td><td>Look for and load Jsi_Auto.jsi auto-index file.</td><td><i></i></td></tr>
<tr><td>exists</td><td><i>BOOL</i></td><td>Source file only if exists.</td><td><i></i></td></tr>
<tr><td>global</td><td><i>BOOL</i></td><td>File is to be sourced in global frame rather than local.</td><td><i></i></td></tr>
<tr><td>isMain</td><td><i>BOOL</i></td><td>Coerce to true the value of Info.isMain().</td><td><i></i></td></tr>
<tr><td>level</td><td><i>UINT</i></td><td>Frame to source file in.</td><td><i></i></td></tr>
<tr><td>noError</td><td><i>BOOL</i></td><td>Ignore errors in sourced file.</td><td><i></i></td></tr>
<tr><td>once</td><td><i>BOOL</i></td><td>Source file only if not already sourced (Default: Interp.debugOpts.includeOnce).</td><td><i></i></td></tr>
<tr><td>trace</td><td><i>BOOL</i></td><td>Trace include statements (Default: Interp.debugOpts.includeTrace).</td><td><i></i></td></tr>
</table>


<a name="System.strftimeOptions"></a>
<a name="System.confOptions"></a>
<h2>Options for "System.strftime"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>secs</td><td><i>BOOL</i></td><td>Time is seconds (out for parse, in for format).</td><td><i></i></td></tr>
<tr><td>fmt</td><td><i>STRKEY</i></td><td>Format string for time.</td><td><i></i></td></tr>
<tr><td>utc</td><td><i>BOOL</i></td><td>Time is utc (in for parse, out for format).</td><td><i></i></td></tr>
</table>


<a name="System.strptimeOptions"></a>
<a name="System.confOptions"></a>
<h2>Options for "System.strptime"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>secs</td><td><i>BOOL</i></td><td>Time is seconds (out for parse, in for format).</td><td><i></i></td></tr>
<tr><td>fmt</td><td><i>STRKEY</i></td><td>Format string for time.</td><td><i></i></td></tr>
<tr><td>utc</td><td><i>BOOL</i></td><td>Time is utc (in for parse, out for format).</td><td><i></i></td></tr>
</table>


<a name="System.updateOptions"></a>
<a name="System.confOptions"></a>
<h2>Options for "System.update"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>maxEvents</td><td><i>INT</i></td><td>Maximum number of events to process (or -1 for all).</td><td><i></i></td></tr>
<tr><td>maxPasses</td><td><i>INT</i></td><td>Maximum passes through event queue.</td><td><i></i></td></tr>
<tr><td>minTime</td><td><i>INT</i></td><td>Minimum milliseconds before returning, or -1 to loop forever (default is 0).</td><td><i></i></td></tr>
<tr><td>sleep</td><td><i>INT</i></td><td>Time to sleep time (in milliseconds) between event checks. Default is 1.</td><td><i></i></td></tr>
</table>
<a name="Systemend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Util"></a>

<hr>


<h1>Util</h1>

<font color=red>Synopsis:Util.method(...)

</font><p>Utilities commands.


<h2>Methods for "Util"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>base64</td><td>base64(val:string, decode:boolean=false):string </td><td>Base64 encode/decode a string.</td></tr>
<tr><td>crc32</td><td>crc32(val:string, crcSeed=0):number </td><td>Calculate 32-bit CRC.</td></tr>
<tr><td>decrypt</td><td>decrypt(val:string, key:string):string </td><td>Decrypt data using BTEA encryption. Keys that are not 16 bytes use the MD5 hash of the key.</td></tr>
<tr><td>encrypt</td><td>encrypt(val:string, key:string):string </td><td>Encrypt data using BTEA encryption. Keys that are not 16 bytes use the MD5 hash of the key.</td></tr>
<tr><td>fromCharCode</td><td>fromCharCode(code:number):string </td><td>Return char with given character code.</td></tr>
<tr><td>getenv</td><td>getenv(name:string=void):string|object|void </td><td>Get one or all environment.</td></tr>
<tr><td>getpid</td><td>getpid(parent:boolean=false):number </td><td>Get process/parent id.</td></tr>
<tr><td>hash</td><td>hash(val:string, <a href='#Util.hashOptions'>options</a>|object=void):string </td><td>Return hash (default SHA256) of string/file.</td></tr>
<tr><td>hexStr</td><td>hexStr(val:string, decode:boolean=false):string </td><td>Hex encode/decode a string.</td></tr>
<tr><td>setenv</td><td>setenv(name:string, value:string=void) </td><td>Set/get an environment var.</td></tr>
<tr><td>times</td><td>times(callback:function, count:number=1):number </td><td>Call function count times and return execution time in microseconds.</td></tr>
<tr><td>verConvert</td><td>verConvert(ver:string|number, zeroTrim:number=0):number|string|null </td><td>Convert a version to/from a string/number, or return null if not a version. For string output zeroTrim says how many trailing .0 to trim (0-2).</td></tr>
</table>


<a name="Util.hashOptions"></a>
<a name="Util.confOptions"></a>
<h2>Options for "Util.hash"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>file</td><td><i>STRING</i></td><td>Read data from file and append to str.</td><td><i></i></td></tr>
<tr><td>hashcash</td><td><i>UINT</i></td><td>Search for a hash with this many leading zero bits by appending :nonce (Proof-Of-Work).</td><td><i></i></td></tr>
<tr><td>noHex</td><td><i>BOOL</i></td><td>Return binary digest, without conversion to hex chars.</td><td><i></i></td></tr>
<tr><td>type</td><td><i>STRKEY</i></td><td>Type of hash. (one of: <b>sha256</b>, <b>sha1</b>, <b>md5</b>, <b>sha3_224</b>, <b>sha3_384</b>, <b>sha3_512</b>, <b>sha3_256</b>)</td><td><i></i></td></tr>
</table>
<a name="Utilend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Vfs"></a>

<hr>


<h1>Vfs</h1>

<font color=red>Synopsis:Vfs.method(...)

</font><p>Commands for creating in memory readonly Virtual file-systems.


<h2>Methods for "Vfs"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>conf</td><td>conf(mount:string, string|<a href='#Vfs.confOptions'>options</a>:object|string=void) </td><td>Configure mount.</td></tr>
<tr><td>exec</td><td>exec(cmd:string) </td><td>Safe mode exec for VFS support cmds eg. fossil info/ls/cat.</td></tr>
<tr><td>fileconf</td><td>fileconf(mount:string, path:string, <a href='#Vfs.fileconfOptions'>options</a>:string|object=void) </td><td>Configure file info which is same info as in fileList.</td></tr>
<tr><td>list</td><td>list():array </td><td>Return list of all vfs mounts.</td></tr>
<tr><td>mount</td><td>mount(type:string, file:string, param:object=void):string </td><td>Mount fossil file as given VFS type name, returning the mount point: frontend for vmount.</td></tr>
<tr><td>type</td><td>type(type:string=void, <a href='#Vfs.typeOptions'>options</a>:object|null=void) </td><td>Set/get/delete VFS type name.</td></tr>
<tr><td>unmount</td><td>unmount(mount:string):void </td><td>Unmount a VFS.</td></tr>
<tr><td>vmount</td><td>vmount(<a href='#Vfs.vmountOptions'>options</a>:object=void):string </td><td>Create and mount a VFS, returning the mount point.</td></tr>
</table>


<a name="Vfs.confOptions"></a>
<a name="Vfs.confOptions"></a>
<h2>Options for "Vfs.conf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>callback</td><td><i>FUNC</i></td><td>Function implementing VFS. @function(op:string, mount:string, arg:string|object|null)</td><td><i></i></td></tr>
<tr><td>extra</td><td><i>OBJ</i></td><td>Extra info, typically used by predefined VFS type.</td><td><i></i></td></tr>
<tr><td>noAddDirs</td><td><i>BOOL</i></td><td>Disable auto-adding of directories; needed by File.glob.</td><td><i></i></td></tr>
<tr><td>file</td><td><i>STRING</i></td><td>Fossil file to mount.</td><td><i></i></td></tr>
<tr><td>fileList</td><td><i>ARRAY</i></td><td>List of files in the VFS (from listFunc).</td><td><i></i></td></tr>
<tr><td>info</td><td><i>OBJ</i></td><td>Info for VFS that is stored upon init.</td><td><i></i></td></tr>
<tr><td>mount</td><td><i>STRING</i></td><td>Mount point for the VFS.</td><td><i></i></td></tr>
<tr><td>noPatches</td><td><i>BOOL</i></td><td>Ignore patchlevel updates: accepts only X.Y releases.</td><td><i></i></td></tr>
<tr><td>param</td><td><i>OBJ</i></td><td>Optional 3rd argument passed to mount.</td><td><i></i></td></tr>
<tr><td>type</td><td><i>STRKEY</i></td><td>Type for predefined VFS.</td><td><i></i></td></tr>
<tr><td>user</td><td><i>OBJ</i></td><td>User data.</td><td><i></i></td></tr>
<tr><td>version</td><td><i>STRKEY</i></td><td>Version to mount.</td><td><i></i></td></tr>
</table>


<a name="Vfs.execOptions"></a>
<a name="Vfs.confOptions"></a>
<h2>Options for "Vfs.exec"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>data</td><td><i>STRING</i></td><td>Data for file.</td><td><i></i></td></tr>
<tr><td>file</td><td><i>STRKEY</i></td><td>File pathname.</td><td><i>required</i></td></tr>
<tr><td>perms</td><td><i>UINT32</i></td><td>Permissions for file.</td><td><i></i></td></tr>
<tr><td>size</td><td><i>SSIZE_T</i></td><td>Size of file.</td><td><i></i></td></tr>
<tr><td>timestamp</td><td><i>TIME_T</i></td><td>Timestamp of file.</td><td><i></i></td></tr>
</table>


<a name="Vfs.fileconfOptions"></a>
<a name="Vfs.confOptions"></a>
<h2>Options for "Vfs.fileconf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>data</td><td><i>STRING</i></td><td>Data for file.</td><td><i></i></td></tr>
<tr><td>file</td><td><i>STRKEY</i></td><td>File pathname.</td><td><i>required</i></td></tr>
<tr><td>perms</td><td><i>UINT32</i></td><td>Permissions for file.</td><td><i></i></td></tr>
<tr><td>size</td><td><i>SSIZE_T</i></td><td>Size of file.</td><td><i></i></td></tr>
<tr><td>timestamp</td><td><i>TIME_T</i></td><td>Timestamp of file.</td><td><i></i></td></tr>
</table>


<a name="Vfs.typeOptions"></a>
<a name="Vfs.confOptions"></a>
<h2>Options for "Vfs.type"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>callback</td><td><i>FUNC</i></td><td>Function implementing VFS. @function(op:string, mount:string, arg:string|object|null)</td><td><i>required</i></td></tr>
<tr><td>extra</td><td><i>OBJ</i></td><td>Extra info, typically used by predefined VFS type.</td><td><i></i></td></tr>
<tr><td>noAddDirs</td><td><i>BOOL</i></td><td>Disable auto-adding of directories; needed by File.glob.</td><td><i></i></td></tr>
</table>


<a name="Vfs.vmountOptions"></a>
<a name="Vfs.confOptions"></a>
<h2>Options for "Vfs.vmount"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>callback</td><td><i>FUNC</i></td><td>Function implementing VFS. @function(op:string, mount:string, arg:string|object|null)</td><td><i></i></td></tr>
<tr><td>extra</td><td><i>OBJ</i></td><td>Extra info, typically used by predefined VFS type.</td><td><i></i></td></tr>
<tr><td>noAddDirs</td><td><i>BOOL</i></td><td>Disable auto-adding of directories; needed by File.glob.</td><td><i></i></td></tr>
<tr><td>file</td><td><i>STRING</i></td><td>Fossil file to mount.</td><td><i></i></td></tr>
<tr><td>fileList</td><td><i>ARRAY</i></td><td>List of files in the VFS (from listFunc).</td><td><i></i></td></tr>
<tr><td>info</td><td><i>OBJ</i></td><td>Info for VFS that is stored upon init.</td><td><i></i></td></tr>
<tr><td>mount</td><td><i>STRING</i></td><td>Mount point for the VFS.</td><td><i></i></td></tr>
<tr><td>noPatches</td><td><i>BOOL</i></td><td>Ignore patchlevel updates: accepts only X.Y releases.</td><td><i></i></td></tr>
<tr><td>param</td><td><i>OBJ</i></td><td>Optional 3rd argument passed to mount.</td><td><i></i></td></tr>
<tr><td>type</td><td><i>STRKEY</i></td><td>Type for predefined VFS.</td><td><i></i></td></tr>
<tr><td>user</td><td><i>OBJ</i></td><td>User data.</td><td><i></i></td></tr>
<tr><td>version</td><td><i>STRKEY</i></td><td>Version to mount.</td><td><i></i></td></tr>
</table>
<a name="Vfsend"></a>
<p><a href="#TOC">Return to top</a>
<a name="WebSocket"></a>

<hr>


<h1>WebSocket</h1>

<font color=red>Synopsis:new WebSocket(options:object=void):userobj

</font><p>Commands for managing WebSocket server/client connections.


<h2>Methods for "WebSocket"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>WebSocket</td><td>new WebSocket(<a href='#new WebSocketOptions'>options</a>:object=void):userobj </td><td>Create websocket server/client object.Create a websocket server/client object.  The server serves out pages to a web browser,
which can use javascript to upgrade connection to a bidirectional websocket.</td></tr>
<tr><td>conf</td><td>conf(<a href='#WebSocket.confOptions'>options</a>:string|object=void) </td><td>Configure options.</td></tr>
<tr><td>handler</td><td>handler(extension:string=void, cmd:string|function=void, arg:string|null=void, flags:number=0):string|array|function|void </td><td>Get/Set handler command for an extension. With no args, returns list of handlers.  With one arg, returns value for that handler.
Otherwise, sets the handler. When cmd is a string, the call is via runModule([cmd], arg).
If a cmd is a function, it is called with a single arg: the file name.</td></tr>
<tr><td>header</td><td>header(id:number, name:string=void):string|object|void </td><td>Get one or all input headers for connect id.</td></tr>
<tr><td>idconf</td><td>idconf(id:number, <a href='#WebSocket.idconfOptions'>options</a>:string|object=void) </td><td>Configure options for connect id.</td></tr>
<tr><td>ids</td><td>ids():array </td><td>Return list of ids.</td></tr>
<tr><td>query</td><td>query(id:number, name:string=void):string|object|void </td><td>Get one or all query values for connect id.</td></tr>
<tr><td>send</td><td>send(id:number, data:any):void </td><td>Send a websocket message to id. Send a message to one (or all connections if -1). If not already a string, msg is formatted as JSON prior to the send.</td></tr>
<tr><td>status</td><td>status():object|void </td><td>Return libwebsocket server status.</td></tr>
<tr><td>update</td><td>update():void </td><td>Service events for just this websocket.</td></tr>
<tr><td>version</td><td>version():string </td><td>Runtime library version string.</td></tr>
</table>


<a name="new WebSocketOptions"></a>
<a name="WebSocket.confOptions"></a>
<h2>Options for "new WebSocket"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>address</td><td><i>STRING</i></td><td>In client-mode the address to connect to (127.0.0.1).</td><td><i></i></td></tr>
<tr><td>bufferPwr2</td><td><i>INT</i></td><td>Tune the recv/send buffer: value is a power of 2 in [0-20] (16).</td><td><i></i></td></tr>
<tr><td>client</td><td><i>BOOL</i></td><td>Run in client mode.</td><td><i>initOnly</i></td></tr>
<tr><td>clientHost</td><td><i>STRKEY</i></td><td>Override host name for client.</td><td><i></i></td></tr>
<tr><td>clientOrigin</td><td><i>STRKEY</i></td><td>Override client origin (origin).</td><td><i></i></td></tr>
<tr><td>debug</td><td><i>INT</i></td><td>Set debug level. Setting this to 512 will turn on max libwebsocket log levels.</td><td><i></i></td></tr>
<tr><td>echo</td><td><i>BOOL</i></td><td>LogInfo outputs all websock Send/Recv messages.</td><td><i></i></td></tr>
<tr><td>formParams</td><td><i>STRKEY</i></td><td>Comma seperated list of upload form param names ('text,send,file,upload').</td><td><i>readOnly</i></td></tr>
<tr><td>extArgs</td><td><i>OBJ</i></td><td>Arguments for extension handlers.</td><td><i>initOnly</i></td></tr>
<tr><td>extHandlers</td><td><i>BOOL</i></td><td>Enable builtin handlers for these extensions: .htmli, .cssi, .jsi, and .md.</td><td><i>initOnly</i></td></tr>
<tr><td>getRegexp</td><td><i>REGEXP</i></td><td>Call onGet() only if Url matches pattern.</td><td><i></i></td></tr>
<tr><td>headers</td><td><i>ARRAY</i></td><td>List of name/value output header pairs.</td><td><i>initOnly</i></td></tr>
<tr><td>interface</td><td><i>STRING</i></td><td>Interface for server to listen on, eg. 'eth0' or 'lo'.</td><td><i>initOnly</i></td></tr>
<tr><td>local</td><td><i>BOOL</i></td><td>Limit connections to localhost addresses on the 127 network.</td><td><i></i></td></tr>
<tr><td>localhostName</td><td><i>STRKEY</i></td><td>Client name used by localhost connections ('localhost').</td><td><i></i></td></tr>
<tr><td>maxConnects</td><td><i>INT</i></td><td>In server mode, max number of client connections accepted.</td><td><i></i></td></tr>
<tr><td>maxDownload</td><td><i>INT</i></td><td>Max size of file download.</td><td><i></i></td></tr>
<tr><td>maxUpload</td><td><i>INT</i></td><td>Max size of file upload will accept.</td><td><i></i></td></tr>
<tr><td>mimeTypes</td><td><i>OBJ</i></td><td>Object providing map of file extensions to mime types (eg. {txt:'text/plain', bb:'text/bb'}).</td><td><i>initOnly</i></td></tr>
<tr><td>noConfig</td><td><i>BOOL</i></td><td>Disable use of conf() to change options after options after create.</td><td><i>initOnly</i></td></tr>
<tr><td>noCompress</td><td><i>BOOL</i></td><td>Disable per-message-deflate extension which can truncate large msgs.</td><td><i></i></td></tr>
<tr><td>noUpdate</td><td><i>BOOL</i></td><td>Disable update event-processing (eg. to exit).</td><td><i></i></td></tr>
<tr><td>noWebsock</td><td><i>BOOL</i></td><td>Serve html, but disallow websocket upgrade.</td><td><i>initOnly</i></td></tr>
<tr><td>noWarn</td><td><i>BOOL</i></td><td>Quietly ignore file related errors.</td><td><i></i></td></tr>
<tr><td>onAuth</td><td><i>FUNC</i></td><td>Function to call for http basic authentication. @function(ws:userobj, id:number, url:string, userpass:string)</td><td><i></i></td></tr>
<tr><td>onClose</td><td><i>FUNC</i></td><td>Function to call when the websocket connection closes. @function(ws:userobj|null, id:number)</td><td><i></i></td></tr>
<tr><td>onCloseLast</td><td><i>FUNC</i></td><td>Function to call when last websock connection closes. On object delete arg is null. @function(ws:userobj|null)</td><td><i></i></td></tr>
<tr><td>onFilter</td><td><i>FUNC</i></td><td>Function to call on a new connection: return false to kill connection. @function(ws:userobj, id:number, url:string, ishttp:boolean)</td><td><i></i></td></tr>
<tr><td>onGet</td><td><i>FUNC</i></td><td>Function to call to server handle http-get. @function(ws:userobj, id:number, url:string, query:array)</td><td><i></i></td></tr>
<tr><td>onOpen</td><td><i>FUNC</i></td><td>Function to call when the websocket connection occurs. @function(ws:userobj, id:number)</td><td><i></i></td></tr>
<tr><td>onUnknown</td><td><i>FUNC</i></td><td>Function to call to server out content when no file exists. @function(ws:userobj, id:number, url:string, query:array)</td><td><i></i></td></tr>
<tr><td>onUpload</td><td><i>FUNC</i></td><td>Function to call handle http-post. @function(ws:userobj, id:number, filename:string, data:string, startpos:number, complete:boolean)</td><td><i></i></td></tr>
<tr><td>onRecv</td><td><i>FUNC</i></td><td>Function to call when websock data recieved. @function(ws:userobj, id:number, data:string)</td><td><i></i></td></tr>
<tr><td>port</td><td><i>INT</i></td><td>Port for server to listen on (8080).</td><td><i>initOnly</i></td></tr>
<tr><td>post</td><td><i>STRING</i></td><td>Post string to serve.</td><td><i>initOnly</i></td></tr>
<tr><td>protocol</td><td><i>STRKEY</i></td><td>Name of protocol (ws/wss).</td><td><i></i></td></tr>
<tr><td>realm</td><td><i>STRKEY</i></td><td>Realm for basic auth (jsish).</td><td><i></i></td></tr>
<tr><td>recvBufMax</td><td><i>INT</i></td><td>Size limit of a websocket message.</td><td><i>initOnly</i></td></tr>
<tr><td>recvBufTimeout</td><td><i>INT</i></td><td>Timeout for recv of a websock msg.</td><td><i>initOnly</i></td></tr>
<tr><td>redirMax</td><td><i>BOOL</i></td><td>Temporarily disable redirects when see more than this in 10 minutes.</td><td><i></i></td></tr>
<tr><td>rootdir</td><td><i>STRING</i></td><td>Directory to serve html from (".").</td><td><i></i></td></tr>
<tr><td>stats</td><td><i><a href='#statsOptions'>options</a></i></td><td>Statistical data.</td><td><i>readOnly</i></td></tr>
<tr><td>startTime</td><td><i>TIME_T</i></td><td>Time of websocket start.</td><td><i>readOnly</i></td></tr>
<tr><td>includeFile</td><td><i>STRKEY</i></td><td>Default file when no extension given (include.shtml).</td><td><i></i></td></tr>
<tr><td>urlPrefix</td><td><i>STRKEY</i></td><td>Prefix in url to strip from path; for reverse proxy.</td><td><i></i></td></tr>
<tr><td>urlRedirect</td><td><i>STRKEY</i></td><td>Redirect when no url or / is given. Must match urlPrefix, if given.</td><td><i></i></td></tr>
<tr><td>use_ssl</td><td><i>BOOL</i></td><td>Use https (for client).</td><td><i>initOnly</i></td></tr>
<tr><td>useridPass</td><td><i>STRKEY</i></td><td>The USERID:PASSWORD to use for basic authentication.</td><td><i></i></td></tr>
<tr><td>version</td><td><i>OBJ</i></td><td>WebSocket version info.</td><td><i>readOnly</i></td></tr>
</table>


<a name="WebSocket.idconfOptions"></a>
<a name="WebSocket.confOptions"></a>
<h2>Options for "WebSocket.idconf"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>clientIP</td><td><i>STRKEY</i></td><td>Client IP Address.</td><td><i>readOnly</i></td></tr>
<tr><td>clientName</td><td><i>STRKEY</i></td><td>Client hostname.</td><td><i>readOnly</i></td></tr>
<tr><td>echo</td><td><i>BOOL</i></td><td>LogInfo outputs all websock Send/Recv messages.</td><td><i></i></td></tr>
<tr><td>headers</td><td><i>ARRAY</i></td><td>List of name/value output header pairs.</td><td><i></i></td></tr>
<tr><td>isWebsock</td><td><i>BOOL</i></td><td>Socket has been upgraded to a websocket connection.</td><td><i></i></td></tr>
<tr><td>onClose</td><td><i>FUNC</i></td><td>Function to call when the websocket connection closes. @function(ws:userobj|null, id:number)</td><td><i></i></td></tr>
<tr><td>onGet</td><td><i>FUNC</i></td><td>Function to call to server handle http-get. @function(ws:userobj, id:number, url:string, query:array)</td><td><i></i></td></tr>
<tr><td>onUnknown</td><td><i>FUNC</i></td><td>Function to call to server out content when no file exists. @function(ws:userobj, id:number, url:string, args:array)</td><td><i></i></td></tr>
<tr><td>onRecv</td><td><i>FUNC</i></td><td>Function to call when websock data recieved. @function(ws:userobj, id:number, data:string)</td><td><i></i></td></tr>
<tr><td>onUpload</td><td><i>FUNC</i></td><td>Function to call handle http-post. @function(ws:userobj, id:number, filename:string, data:string, startpos:number, complete:boolean)</td><td><i></i></td></tr>
<tr><td>rootdir</td><td><i>STRING</i></td><td>Directory to serve html from (".").</td><td><i></i></td></tr>
<tr><td>stats</td><td><i><a href='#statsOptions'>options</a></i></td><td>Statistics for connection.</td><td><i>readOnly</i></td></tr>
<tr><td>query</td><td><i>ARRAY</i></td><td>Uri arg values for connection.</td><td><i></i></td></tr>
<tr><td>url</td><td><i>DSTRING</i></td><td>Url for connection.</td><td><i></i></td></tr>
</table>


<a name="statsOptions"></a>
<h2>Options for "stats"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>connectCnt</td><td><i>INT</i></td><td>Number of active connections.</td><td><i>readOnly</i></td></tr>
<tr><td>eventCnt</td><td><i>INT</i></td><td>Number of events of any type.</td><td><i></i></td></tr>
<tr><td>eventLast</td><td><i>TIME_T</i></td><td>Time of last event of any type.</td><td><i></i></td></tr>
<tr><td>httpCnt</td><td><i>INT</i></td><td>Number of http reqs.</td><td><i></i></td></tr>
<tr><td>httpLast</td><td><i>TIME_T</i></td><td>Time of last http reqs.</td><td><i></i></td></tr>
<tr><td>isBinary</td><td><i>BOOL</i></td><td>Connection recv data is binary.</td><td><i>readOnly</i></td></tr>
<tr><td>isFinal</td><td><i>BOOL</i></td><td>Final data for current message was recieved.</td><td><i>readOnly</i></td></tr>
<tr><td>msgQLen</td><td><i>INT</i></td><td>Number of messages in input queue.</td><td><i>readOnly</i></td></tr>
<tr><td>recvCnt</td><td><i>INT</i></td><td>Number of recieves.</td><td><i>readOnly</i></td></tr>
<tr><td>recvLast</td><td><i>TIME_T</i></td><td>Time of last recv.</td><td><i>readOnly</i></td></tr>
<tr><td>redirLast</td><td><i>TIME_T</i></td><td>Time of last redirect.</td><td><i>readOnly</i></td></tr>
<tr><td>redirCnt</td><td><i>INT</i></td><td>Count of redirects.</td><td><i>readOnly</i></td></tr>
<tr><td>sentCnt</td><td><i>INT</i></td><td>Number of sends.</td><td><i>readOnly</i></td></tr>
<tr><td>sentLast</td><td><i>TIME_T</i></td><td>Time of last send.</td><td><i>readOnly</i></td></tr>
<tr><td>sentErrCnt</td><td><i>INT</i></td><td>Number of sends.</td><td><i>readOnly</i></td></tr>
<tr><td>sentErrLast</td><td><i>TIME_T</i></td><td>Time of last sendErr.</td><td><i>readOnly</i></td></tr>
<tr><td>sentErrLast</td><td><i>TIME_T</i></td><td>Time of last sendErr.</td><td><i>readOnly</i></td></tr>
<tr><td>uploadCnt</td><td><i>INT</i></td><td>Number of uploads.</td><td><i>readOnly</i></td></tr>
<tr><td>uploadEnd</td><td><i>TIME_T</i></td><td>Time of upload end.</td><td><i>readOnly</i></td></tr>
<tr><td>uploadLast</td><td><i>TIME_T</i></td><td>Time of last upload input.</td><td><i>readOnly</i></td></tr>
<tr><td>uploadStart</td><td><i>TIME_T</i></td><td>Time of upload start.</td><td><i>readOnly</i></td></tr>
</table>
<a name="WebSocketend"></a>
<p><a href="#TOC">Return to top</a>
<a name="Zvfs"></a>

<hr>


<h1>Zvfs</h1>

<font color=red>Synopsis:Zvfs.method(...)

</font><p>Commands for mounting and accessing .zip files as a filesystem.


<h2>Methods for "Zvfs"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>append</td><td>append(archive:string, filelist:array, path:string|null=void, filelist:array=void, path:string|null=void, ...):void </td><td>Like 'create()', but appends to an existing archive (with no dup checking).</td></tr>
<tr><td>create</td><td>create(archive:string, filelist:array, path:string|null=void, filelist:array=void, path:string|null=void, ...):void </td><td>Create a zip with the given files in prefix path. This command creates a zip archive and adds files to it. Files are relative the given 'path', or the current directory. If the destignation file already exist but is not an archive (eg. an executable), zip data is appended to the end of the file. If the existing file is already an archive, an error will be thrown. To truncate an existing archive, use zvfs.truncate(). Or use zvfs.append() instead. 
   zvfs.create('foo.zip',['main.js', 'bar.js'], 'src', ['a.html', 'css/a.css'], 'html');</td></tr>
<tr><td>deflate</td><td>deflate(data:string):string </td><td>Compress string using zlib deflate.</td></tr>
<tr><td>inflate</td><td>inflate(data:string):string </td><td>Uncompress string using zlib inflate.</td></tr>
<tr><td>list</td><td>list(archive:string):array </td><td>List files in archive. Return contents of zip directory as an array of arrays. The first element contains the labels, ie: 
[ 'Name', 'Special', 'Offset', 'Bytes', 'BytesCompressed' ] </td></tr>
<tr><td>mount</td><td>mount(archive:string, mountdir:string=void):string </td><td>Mount zip on mount point. Read a ZIP archive and make entries in the virutal file hash table for all files contained therein.</td></tr>
<tr><td>names</td><td>names(mountdir:string=void):array </td><td>Return all zvfs mounted zips, or archive for specified mount. Given an mount point argument, returns the archive for it. Otherwise, returns an array of mount points</td></tr>
<tr><td>offset</td><td>offset(archive:string):number </td><td>Return the start offset of zip data. Opens and scans the file to determine start of zip data and truncate this off the end of the file.  For ordinary zip archives, the resulting truncated file will be of zero length. If an optional bool argument can disable errors. In any case, the start offset of zip data (or 0) is returned.</td></tr>
<tr><td>stat</td><td>stat(filename:string):object </td><td>Return details on file in zvfs mount. Return details about the given file in the ZVFS.  The information consists of (1) the name of the ZIP archive that contains the file, (2) the size of the file after decompressions, (3) the compressed size of the file, and (4) the offset of the compressed data in the archive.</td></tr>
<tr><td>truncate</td><td>truncate(archive:string, noerror:boolean=false):number </td><td>Truncate zip data from archive. Opens and scans the file to determine start of zip data and truncate this off the end of the file.  For ordinary zip archives, the resulting truncated file will be of zero length. If an optional bool argument can disable errors. In any case, the start offset of zip data (or 0) is returned.</td></tr>
<tr><td>unmount</td><td>unmount(archive:string):void </td><td>Unmount zip.</td></tr>
</table>
<a name="Zvfsend"></a>
<p><a href="#TOC">Return to top</a>
<a name="console"></a>

<hr>


<h1>console</h1>

<font color=red>Synopsis:console.method(...)

</font><p>Console input and output to stderr.


<h2>Methods for "console"</h2>
<table border="1"class="cmdstbl table">
<tr><th>Method</th><th>Prototype</th><th>Description</th></tr>
<tr><td>assert</td><td>assert(expr:boolean|number|function, msg:string=void, <a href='#console.assertOptions'>options</a>:object=void):void </td><td>Same as System.assert().</td></tr>
<tr><td>input</td><td>input():string|void </td><td>Read input from the console.</td></tr>
<tr><td>log</td><td>log(val, ...):void </td><td>Same as System.puts, but goes to stderr and includes file:line.</td></tr>
<tr><td>printf</td><td>printf(format:string, ...):void </td><td>Same as System.printf but goes to stderr.</td></tr>
<tr><td>puts</td><td>puts(val, ...):void </td><td>Same as System.puts, but goes to stderr.</td></tr>
</table>


<a name="console.assertOptions"></a>
<a name="console.confOptions"></a>
<h2>Options for "console.assert"</h2>
<table border="1" class="optstbl table">
<tr><th>Option</th> <th>Type</th> <th>Description</th><th>Flags</th></tr>
<tr><td>mode</td><td><i>STRKEY</i></td><td>Action when assertion is false. Default from Interp.assertMode. (one of: <b>throw</b>, <b>log</b>, <b>puts</b>)</td><td><i></i></td></tr>
<tr><td>noStderr</td><td><i>BOOL</i></td><td>Logged msg to stdout. Default from Interp.noStderr.</td><td><i></i></td></tr>
</table>
<a name="consoleend"></a>
<p><a href="#TOC">Return to top</a>
<p>

Added doc/md/Sqlite.md.

































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
[Sqlite](Reference#Sqlite)
====

The Sqlite driver (and [MySql](MySql)) provides various ways to execute sql:

- **eval**() for running simple sql requiring no input/output.
- **query**() for complex, parameterized queries.
- **onecolumn**() like *query*, but returns a single value.


Here is an example:

~~~~
var r, age, db  = new Sqlite('testsql.db');
db.eval('CREATE TABLE players(name TEXT,age INTEGER);');
db.query('INSERT INTO players VALUES(?,?)', {values:["Barry",44]});
age = db.onecolumn('SELECT age FROM players WHERE name = "Barry"');
r = db.query('SELECT * FROM players');
puts(r[0].name, r[0].age);
~~~~

Options passed in the object argument to new Sqlite(), may specify any of the following:

| Option    | Type    | Description                                | Default |
|-----------|---------|--------------------------------------------|---------|
| bindWarn  | BOOL    | Treat failed variable binds as a warning.  | false   |
| debug     | CUSTOM  | Enable debug trace for various operations. |         |
| queryOpts | CUSTOM  | Default options for exec.                  |         |
| forceInt  | CUSTOM  | Bind float as int if possible.             |         |
| maxStmts  | INT     | Max cache size for compiled statements.    |         |
| mutex     | CUSTOM  | Mutex type to use.                         |         |
| name      | DSTRING | Name for this db handle.                   |         |
| nocreate  | BOOL    | Database is must already exist.            | false   |
| readonly  | BOOL    | Database is readonly.                      | false   |
| vfs       | VALUE   | VFS to use.                                |         |

!!!
    Some options can later be changed using the conf() method, eg.

~~~~
db.conf({maxStmts:100});
~~~~

See [tests/sqlite.jsi](../tests/sqlite.jsi?mimetype=application/javascript) for a more complete example.


Eval
----
The eval() method is used to execute simple Sql.
It takes no options, and returns number of rows changed (sqlite3_changed).

It can also be used to execute multiple semicolon-separated statements:

~~~~
db.exec('CREATE TABLE foo(a,b);'+
'INSERT INTO foo VALUES(1,2);'+
'INSERT INTO foo VALUES("X","Y")');
~~~~

This makes it useful for bulk loading.


Oncolumn
----
onecolumn() provides no inputs or outputs.  It simply returns the first column
of the first row.  The mode and other options are ignored.

~~~~
var maxid = db.oncolumn('SELECT max(id) FROM foo');
~~~~

Query
----
The  workhorse method is query() which:

- compiles SQL into a prepared code and caches it.
- binds parameters.
- executes the query.
- returns the results.

Here is an example:

~~~~
var x = db.query('SELECT * FROM foo');
~~~~

### Query Options
Query options can be controlled either of two ways.  Per query, as in:

~~~~
db.query('SELECT * FROM test1', {mode:'json'});
~~~~

or we can change the defaults (for the connection) like so:

~~~~
db.conf({queryOpts:{mode:'json'}});
db.query('SELECT * FROM test1');
db.query('SELECT * FROM test2');
~~~~

Here is a list of the available query() options:


| Option    | Type   | Description                                       | Default |
|-----------|--------|---------------------------------------------------|---------|
| callback  | FUNC   | Function to call with each row result.            |         |
| cdata     | STRKEY | Name of Cdata array object to use.                |         |
| headers   | BOOL   | First row returned contains column labels.        |         |
| limit     | INT    | Maximum number of returned values.                |         |
| mapundef  | BOOL   | In variable bind, map an 'undefined' var to null. |         |
| mode      | CUSTOM | Set output mode of returned data.                 |         |
| nocache   | BOOL   | Query is not to be cached.                        |         |
| nullvalue | STRKEY | Null string output (for non js/json mode).        |         |
| retChanged| BOOL   | Query returns value of sqlite3_changed().         |         |
| separator | STRKEY | Separator string (for csv and text mode).         |         |
| typeCheck | CUSTOM | Type check mode.                                  | warn    |
| table     | STRKEY | Table name for mode=insert.                       |         |
| values    | ARRAY  | Values for ? bind parameters.                     |         |
| varName   | STRBUF | Array var for ? bind parameters.                  |         |
| width     | CUSTOM | In column mode, set column widths.                |         |


### Outputs
The returned value from a query is determined by the chosen output mode.

The default mode (rows) just returns an array of objects, which looks like this:

~~~~ the output
[ { a:1, b:2 }, { a:"X", b:"Y" } ]
~~~~

The choices for mode are a superset of those available in the sqlite3 command-line tool, namely:

| Mode    | Description                                      | Purpose |
|---------|--------------------------------------------------|---------|
| array1d | Flat array of values                             | script  |
| arrays  | An array of row-arrays                           | script  |
| column  | Column aligned text                              | text    |
| csv     | Comma (or separator) separated values            | export  |
| html    | Html table rows                                  | browser |
| insert  | Sql insert statements                            | export  |
| json    | JSON string as an array of objects               | browser |
| json2   | JSON string with names/values in separate arrays | browser |
| line    | One value per line in name=value form            | export  |
| list    | The default sqlite3 output                       | text    |
| none    | No output                                        |         |
| rows    | An array of row-objects (the default)            | script  |
| tabs    | Tab separator delineated values                  | script  |



We can change the output mode for a query() using:

~~~~
db.query('SELECT * FROM foo', {mode:'list'});
~~~~ the output
1|2|X
Y|3|Z
~~~~

!!! NOTE
    Output for some modes is affected by the headers and separator options.


#### JSON
The json modes are useful
when data is destined to be sent to a web browser, eg. via [websockets](Builtin#websocket).

~~~~
db.exec('DROP TABLE IF EXISTS foo; CREATE TABLE foo(a,b);');
var n = 0, x = 99;
while (n++ < 3) {
    db.query('INSERT INTO foo VALUES(@x,@n)');
    x -= 4;
}
x=db.query('SELECT * FROM foo',{mode:'json'});
~~~~ the output
[ {"a":99, "b":1}, {"a":95, "b":2}, {"a":91, "b":3} ]
~~~~

Where large amounts of data are involved, the headers option can be used to reduce size:

~~~~
db.query('SELECT * FROM foo',{mode:'json', headers:true});
~~~~ the output
[ ["a", "b"], [99, 1], [95, 2], [91, 3] ]
~~~~

The "json2" mode is used to split headers and values out
into separate members:

~~~~
db.query('SELECT * FROM foo',{mode:'json2'});
~~~~ the output
{ "names": [ "a", "b" ], "values": [ [99, 1 ], [95, 2 ], [91, 3 ] ] }
~~~~


#### Callback Function
Normally, query() will execute an entire query before returning the result.
There are two ways to change this:

- provide the callback option, or
- give a function as the second argument.

Either way this results in invocation of the callback for
each row result:

~~~~
function myfunc(n) { puts("a=" + n.a + ", b=" + n.b); }
db.query('SELECT * FROM foo',myfunc);
~~~~

If the callback function returns false, evaluation will terminate.

~~~~
db.query('SELECT * FROM foo', function (n) {
    puts("a=" + n.a + ", b=" + n.b);
    if (a>1) return false;
  });
~~~~


### Inputs
Sql inputs can be easily formatted using strings:

~~~~
var a=1, b='big';
db.query('INSERT INTO foo VALUES('+a+*','*+b+')');
~~~~

However this raises issues of security and predictability.
Fortunately variable binding is easy.

#### Bindings
Sqlite variable binding uses "?" placeholders to refer to array elements., eg:

~~~~
db.query('INSERT INTO foo VALUES(?,?)', {values:[11,12]});

var vals = [9,10];
db.query('INSERT INTO foo VALUES(?,?)', {values:vals});
~~~~

which for a small number of parameters is more than adequate.

#### Named-Binds
Sqlite named-bindings begin with the characters: :,  @, and $.

Here is an example:

~~~~
var x1=24.5, x2="Barry", x3="Box";
db.query('INSERT INTO test2 VALUES( :x1, @x2, $x3 );');
~~~~

The $ bind may append round-brackets () to refer to compound variables.

This example binds to objects members:

~~~~
var y = {a:4, b:"Perry", c:"Pack"};
db.query('INSERT INTO test2 VALUES( $y(a), $y(b), $y(c) );');
~~~~

And this one to arrays:

~~~~
var Z = 2;
var y = [9, 'Figgy', 'Fall'];
db.query('INSERT INTO test2 VALUES( $y(0), $y(1), $y([Z]) );');
~~~~

Or more usefully:

~~~~
var y = [
    {a:4, b:"Perry", c:"Pack"},
    {a:9, b:'Figgy', c:'Fall'}
];
for (var i=0; i < y.length; i++)
    db.query('INSERT INTO test2 VALUES($y([i].a), $y([i].b), $y([i].c);');
~~~~

The contents of the round-brackets can contain multiple levels of dereference (but not expressions).

Here is a selection of bindings, and their variables:

| Binding        | Variable    | Comment                         |
|----------------|-------------|---------------------------------|
| :X             | X           |                                 |
| @X             | X           |                                 |
| $X             | X           |                                 |
| $X(a)          | X.a         | Implicit object member          |
| $X(9)          | X&lsqb;9]   | Implicit array (leading digits) |
| $X(&lsqb;a])   | X&lsqb;a]   | Explicit array                  |
| $X(a.b)        | X.a.b       | Compound object                 |
| $X(&lsqb;a].b) | X&lsqb;a].b | Compound array + object, etc    |


#### Types
A  type specifier may also be included in a $X(Y) binding, as in:

~~~~
var y = {a:4, b:"Purry", c:"Pax"};
db.query('INSERT INTO test2 VALUES( $y(a:integer), $y(b:string), $y(c:string) );');
~~~~

The type is the part after the colon ":", and just before the close round-brace.

By default, a type is used to convert data sent to MySql to the correct type.

Type specifiers are supported for all variants of $X(Y) binding, such as:

~~~~
var Z = 0;
var x = ['Figgy'];
var y = {c:'Fall'};
db.query('INSERT INTO test3 VALUES( $x(0:string), $y(c:string), $x([Z]:string) );');
~~~~

The supported type names are:

| Type      | Description           |
|-----------|-----------------------|
| bool      | A tiny/bit value      |
| double    | A double value        |
| integer   | A 64 bit wide integer |
| string    | A string              |
| blob      | A blob                |
| date      | A date value          |
| datetime  | A date+time value     |
| time      | A time value          |
| timestamp | A unix timestamp      |

We can also change the type-checking behaviour via the typeCheck query option:

For example, we can instead cause an error to be kicked an error with:

~~~~
var x = [ 'bad' ];
db.query('UPDATE test SET n = $x(0:number) );', {typeCheck:'error'});
~~~~

The valid typeCheck modes are:

| Value   | Description                                      |
|---------|--------------------------------------------------|
| convert | Coerce value to the requested type (the default) |
| warn    | Generate a warning                               |
| error   | Generate an error                                |
| disable | Ignore type specifiers                           |


Miscellaneous
----

### User Functions
SQL functions can be defined in javascript using func():

~~~~
db.func('bar',function(n) { return n+'.000'; });
puts(db.onecolumn('SELECT bar(a) FROM foo WHERE b == 2;'));
~~~~


### Timestamps
Sqlite performs internal time calculations based on the Gregorian calendar.

But Javascript time functions use the unix epoch to store the number of milliseconds
since Jan 1, 1970 UTC.

We can create a table with a DEFAULT date field as a number using:

~~~~
CREATE TABLE mtable(
  name,
  mytime DATETIME DEFAULT(round((julianday('now') - 2440587.5)*86400000.0))
);
~~~~

We can output this as a text time stamp using:

~~~~
SELECT strftime('%Y-%m-%d %H:%M:%f',mytime/1000.0,'unixepoch') FROM mytable;
SELECT strftime('%Y-%m-%d %H:%M:%f',mytime/1000.0,*'unixepoch'*,'localtime') FROM mytable;
~~~~ the output
2015-01-12 13:46:40.252
2015-01-12 18:46:40.252
~~~~


### Caching
In the interest of efficiency, compiled queries are cached on a per connection basis.
The size of the cache is controlled by the maxStmts option.

You can also disable caching for individual querys with nocache.
And any query begining in ';' will not be cached.


### Building
The Sqlite driver comes (by default) builtin to Jsi.

It can also be built the shared library can be built (for unix) with:

~~~~
make libmysql
~~~~


C-API
----

See [DbQuery](DBQuery) for a Sqlite C-API.

Added doc/md/Start.md.





























































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
Start
====
This page describes getting started with Jsi.

Download
----
If you don't want to build Jsi from source,
limited binaries are available here: https://jsish.org/jsi-bin


### Source
To download the zipped source:
~~~~
mkdir jsi && cd jsi && wget https://jsish.org/jsi/zip -O jsish.zip && unzip jsish.zip
~~~~

Then follow [build](#buildingjsi) directions.

!!!
    Note: it is better to get the source with Fossil.


### Fossil
As Jsi is version-controlled with [fossil](http://www.fossil-scm.org),
the easiest way to keep up to date is by cloning the Jsi repository:

~~~~
mkdir jsi && cd jsi && fossil clone https://jsish.org/jsi jsi.fossil && fossil open jsi.fossil
~~~~

Then follow [build](#buildingjsi) directions.

To get fossil:

- On Ubuntu: "apt-get install fossil", or
- Get a binary from [http://www.fossil-scm.org/index.html/uv/download.html]


Building
----
Jsi is written in C, but can be compiled as either native C, or native C++ (does not use **extern C**).

On Debian a few packages are required:

~~~~SH
sudo apt-get install build-essential bison libreadline-dev libsqlite3-dev libwebsockets-dev libncurses-dev cmake libmysqlclient-dev
~~~~

!!!
    Some of these (eg. **cmake**) are only required for specific configurations.


### Linux
To build the Linux target there are are two steps:

~~~~SH
./configure
~~~~

Do not be surprised to see comiler output from [./configure](../tools/configure.js):
it compiles the stripped down shell "jsimin".

Next, run "make" to build the actual "jsish" executable.

~~~~SH
make
~~~~

If you want, you can see other available options using:

~~~~SH
./configure --help
~~~~

!!! NOTE
    The directory **Configs/**, which contains a number of predefined configurations
    which can be copied to **make.conf**

The last step is to run the test suite (optional):

~~~~SH
make test
~~~~


### Debian
If you are on a debian system, you can build then install as a package:

~~~~SH
cd tools
./makedep.sh
sudo dpkg -i jsish-*
~~~~


### FreeBSD
On FreeBSD use "gmake" instead of "make" and:

~~~~
pkg install fetch gmake bison
~~~~


### Windows
Jsi can be cross compiled from Linux to Windows using the Mingw32 package:

~~~~SH
sudo apt-get install gcc-mingw-w64
~~~~

The [sqlite](https://sqlite.org/download.html) and
[libwebsockets](https://libwebsockets.org/) source needs to be downloaded and unpacked in "../sqlite"
and "../websockets".  This now happens automatically.

Then configure using:

~~~~SH
./configure --config=win
~~~~

!!! WARNING
    Certain features (eg. signals) are disabled in the Windows build.
    There are also differences in some of the file-system access functions.


### Standalone
The **standalone** build produces a static binary that contains no external library references.
This is useful when you need a standalone executable with no external dependancies.

To create a static image, Jsi uses the Musl library.

The first step is to download [Musl](http://www.musl-libc.org) and unpack it.
Then change to the **musl** dir and run configure/make, eg:

~~~~
 ./configure --prefix=$HOME/usr && make install
~~~~

Ensure that *~/usr/bin* is in your path with export PATH=$PATH:$HOME/usr/bin.
Then back in the **jsi** dir do the following:

~~~~
echo '#define __P(x) x' > ~/usr/include/sys/cdefs.h
echo '#include <miniz/zlib.h>' >  ~/usr/include/zlib.h
cp -pr miniz ~/usr/include/
~~~~

The sqlite and libwebsockets source needs to be downloaded and unpacked in **../sqlite**
and **../websockets**.

The static jsish can then be built with:

~~~~
./configure --config=musl
make
~~~~


### Amalgamation
Amalgamated source is the easiest way to incorporate Jsi into an existing application:
Here is a simple example:

~~~~
#include "jsi.c"

int main(int argc, char *argv[])
{
    Jsi_Interp *interp = Jsi_InterpNew(NULL);
    Jsi_EvalString(interp, "for (var i=1; i<=3; i++)  puts('TEST:',i);", 0);
    Jsi_EvalFile(interp, argv[1], 0);
}
~~~~

which we compile with:

~~~~
gcc  myfile.c -lm -lz -ldl -lpthreads
~~~~


Another alternative that simplifies debugging Jsi is
using [jsiOne.c](../src/jsiOne.c)


Using
----
The following describes a few of the ways to use jsish.

The Jsish package comprises the scripts zipped to the end of the jsish
executable that implement command-line option/utilities such as the debugger
and parseOpts.


### Interactive
Interactive mode is the easiest way to try out code snippets, eg:

~~~~
  ./jsish
~~~~ the output
# var a = [1,2,3];
# for (var i in a) { puts(a[i]); }
1
2
3
...
~~~~


### Script
The script file to be executed is the first argument:

~~~~
jsish prog.js arg1 arg2
~~~~

Under unix, the first line of executable scripts can be #!:

~~~~
#!/usr/bin/env jsish
for (var i in console.args)
   puts(console.args[i]);
~~~~

The above allows jsish to be found in the path.

### Inline
Javascript can also be evaluated from the command-line with -e, eg:

~~~~
jsish -e 'var i = 0; while (i++<10) puts(i);'
~~~~


Help
----
To see the supported switches in jsish use -h

~~~~
jsish -h
~~~~ the output
usage: jsish -h/--help | -v/--version | -d/--debug | -D/--debugui | -u/--unittest | -U/-UU
    | -s/--safe | -Z/--zip | -S/--sqliteui | -w/--wget | -W/--websrv | -H/--htmli | -J/--jsi
    | -C/--cssi | -c/--cdata | -M/--module | -m/--make | -e/--eval | -t/--tracecall
    | -a/--archive | -T/--typecheck OPT | -IOPT VAL | FILE arg arg ...
Use --help for long help.
~~~~

### Switches
Available switches in Jsish are:

| Options              | Description                                                          |
|----------------------|----------------------------------------------------------------------|
| -a/--archive FILE    | Mount an archive (zip, sqlar or fossil repo) and run module.         |
| -c/--cdata FILE      | Generate .c or JSON output from a .jsc description.                  |
| -C/--cssi FILE       | Preprocess embedded CSS in .css file.                                |
| -d/--debug FILE      | Run console debugger on script.                                      |
| -D/--debugui FILE    | Run web-gui debugger on script.                                      |
| -e/--eval STRING     | Evaluate a javascript string and exit.                               |
| -h/--help            | Print help in short or long form.                                    |
| -H/--htmli FILE      | Preprocess embedded jsi in .htmli file.                              |
| -J/--jsi FILE        | Preprocess a .jsi file to typecheck in standard javascript.          |
| -m/--module FILE     | Source file and invoke runModule.                                    |
| -M/--make FILE       | Preprocess script as a Jsi Makefile.                                 |
| -s/--safe FILE       | Run script in safe sub-interp.                                       |
| -S/--sqliteui DBFILE | Run Sqlite web-gui.                                                  |
| -t/--tracecall       | Trace all function calls.                                            |
| -T/--typecheck OPT   | Enable typechecking.                                                 |
| -u/--unittest FILE   | Run unit-tests on a script file, or a dir containing .js/.jsi files. |
| -U/-UU SCRIPT        | Show output from unit-test mode, omitting pass/fail compare.         |
| -v/--version         | Print short/long version info and exit.                              |
| -Z/--zip             | Used to append/manage zip files at end of executable.                |
| -w/--wget URL        | Web client to download file from url.                                |
| -W/--websrv FILE     | Serve out file in web server, with preprocessing.                    |


### Modules
Help for module commands can similarly be displayed, eg:

~~~~
jsish -d -h
~~~~ the output
/zvfs/lib/Jsi_Debug.jsi:34: help: ...
A command-line Debugger for Jsi scripts..  Options are:
     -echoCmd -safe
Use --help for long help.
~~~~

or the long form:

~~~~ SH input
jsish -d --help
~~~~ the output
/zvfs/lib/Jsi_Debug.jsi:34: help: ...
A command-line Debugger for Jsi scripts..  Options are:
    -echoCmd    true        // Echo user cmds.
    -safe       false       // Debug program in a safe interp (untested)

Accepted by all .jsi modules: -Debug, -Trace, -Test
~~~~

and use as in:

~~~~
jsish -d -echoCmd true tests/while.js
~~~~



Apps
----

Jsi is distributed with several demonstration web applications:

- [DebugUI](app/debugui): a Debugger user interface for Jsi scripts.
- [SqliteUI](app/sqliteui): a web user interface to Sqlite.
- [LedgerJS](app/ledgerjs): an accounting program.

These can all be run as [standalone](Builtin#zvfs) applications.

Shell
----
You can use jsish
as an enhanced replacement for [#!/usr/bin/env](https://en.wikipedia.org/wiki/Shebang_(Unix)).
This lets you run scripts from the command line with default arguments:

~~~~
#!/usr/local/bin/jsish -T Debug %s -Trace true myinput1.txt
puts(console.args.join(' '));
~~~~

(there must be a %s and at least one argument)

From geany you can now run the script with F9, and step through
warnings and errors.

This also works for [logging](Logging) messages: [mytest2](../js-demos/log/mytest2.jsi)


Autocomplete
----
Autocompletion can be set-up on unix by adding to .bashrc:


~~~~
complete -W '-h --help -v --version -d --debug -D --debugui -u -unittest -s --safe -Z --zip \
  -S --sqliteui -W --websrv -H --htmli -J --jsi -c --cdata -C --cssi -m --module -e --eval \
  -T --typecheck -t --tracecall' -f '*.jsi'  'jsish'
~~~~

or running:

~~~~
make setup.
~~~~

Next time you login you can use autocompletion with TAB.

This can make running
jsish from the command-line a little nicer.


Editors
----


### Geany
[Geany](http://www.geany.org) is a convenient editor to use with Jsi.
To enable Jsi file completion with Geany:

- Copy tools/geany/filetypes.javascript to ~/.config/geany/filedefs/.
- Open geany and navigate to Tools->Configuration Files->filetypes_extensions.conf, then:
- Add ".jsi;.jsc" to Javascript, ".md.html;.htmli;" to HTML and "*.cssi;" to CSS
- Keep the file tools/protos.jsi open in the editor so Geany knows how to complete Jsi functions.

Geany can also navigate
through Jsi's gcc style scripting errors:

- Start by editing a .jsi file.
- From the Geany Build menu, select Set Build Commands.
- Click on the first blank label in Javascript and enter Jsish.
- In the command section enter the pathspec to jsish, eg. $HOME/bin/jsish %f
- Click Ok

Now hit F8 to run the script. Upon errors, you should be able to navigate to the
highlighted lines, and see warnings in the bottom pane.

Alternatively, you can just create a Makefile to run jsish.

Also, if using the [logging](Logging) feature in Jsi, you can also navigate through
debug messages when using [F9 or shell exec](#shell).

To run scripts that honor the [shebang](#shell), repeat the above but set the command section
to jsish -# %f.

Caveat: one limitation of Geany is
that a function with a return type will likely not show up in the symbols list.


### Vim
Here is how to setup/use vim with Jsi:

~~~~
:set filetype javascript
:set makeprg=jsish\ %
:copen
~~~~

Then to run scripts just use:

~~~~
:make
~~~~

And much of what was said about navigation in Geany also applies to Vim.

Added doc/md/Testing.md.













































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
Testing
====
Jsi test scripts invoked with "-u" produce output that is compared with text in an embedded comment:

~~~~ JS linenumbers
puts('FOO');
/*
=!EXPECTSTART!=
FOO
=!EXPECTEND!=
*/
~~~~

A test fails if program output does not match an embedded **EXPECT** comment, or an error occurs.

~~~~
jsish -u test.js
~~~~ the output
[PASS] test.js
~~~~

Here is larger example:

~~~~ JS linenumbers
#!/usr/local/bin/jsish -u %s

function foo(n) {
    return "FOO"+n;
}

puts(foo(1));
puts(foo(2));

/*
=!EXPECTSTART!=
FOO1
FOO2
=!EXPECTEND!=
*/
~~~~


Creation
----
After creating a test script
you can the auto-create the EXPECT comment with "-update":

~~~~
jsish -u -update true foo1.jsi foo2.jsi
jsish -u -update true tests/
~~~~

If script already contains an EXPECT comment, it will be updated in-place.

Echoing
----

To echo expressions in test-mode use a semi-colon in column one:
~~~~ JS linenumbers
var x;
;x=1;
~~~~

This outputs the expression and value, eg:

~~~~
jsish -U test2.js
~~~~ the output
x=1 ==> 1
~~~~

that is, lines are implicitly rewritten to:

~~~~
  "printf(\"%%s ==> \",\"%s\"),puts(%s);\n"
~~~~

Here is a larger example:
~~~~ JS linenumbers
#!/usr/local/bin/jsish -u %s

function a(n) {
    var sum = 0;
    for (var i = 0; i < n; i++)
        sum = sum + i;
    return sum;
};

;'===Begin Test===';
;a(10);
;a(100);
;a(1000);

/*
=!EXPECTSTART!=
'===Begin Test==='
a(10) ==>  45
a(100) ==>  4950
a(1000) ==>  499500
=!EXPECTEND!=
*/
~~~~

In the transformed code the expansion is:
~~~~
puts("'===Begin Test==='");
puts("a(10) ==> ", a(10));
puts("a(100) ==> ", a(100));
puts("a(1000) ==> ", a(1000));
~~~~

!!! NOTE
    Note the special case of single-quoted lines which are output verbatim

To see this output we can use the -U option:
~~~~
jsish -U tests/func.js
~~~~ the output
'===Begin Test==='
a(10) ==>  45
a(100) ==>  4950
a(1000) ==>  499500
~~~~

Implicit and explicit puts can freely be intermixed.

~~~~ JS linenumbers
#!/usr/local/bin/jsish -u %s

function foo() { return true; }
function bar() { return true; }

;bar();
puts('foo() ==> ', foo());
puts('DONE');

/*
=!EXPECTSTART!=
bar() ==>  true
foo() ==>  true
DONE
=!EXPECTEND!=
*/
~~~~

The following rules apply to implicit puts:
- Double-quotes are illegal.
- An expression starting and ending with a single-quote is output verbatim (unevaluated).
- Leading spaces after the first ';'  are stripped from the expression ([eg](../tests/call.jsi?mimetype=application/javascript)).
- Functions definitions will be scoped within the puts argument.
- Side-effect output may appear prior to the "==>" line (eg. from function calls).


Results
-------
By default, a test result is either PASS (when output matches)
else a FAIL followed by a description of the differences:

~~~~
jsish -u prob/testfail.js
~~~~ the output
[FAIL] prob/testfail.js
at line 2 of output:
    output: <bar failed>
    expect: <bar passed>
====================DIFFSTART
 foo passed
-bar passed
+bar failed
 baz passed

====================DIFFEND*
~~~~

The return code is zero if the test did not failed.


Directories
---
Arguments to jsish -u can either be one or more files, or a single directory in which to look for .js or .jsi
files:

~~~~
jsish -u tests/
~~~~ the output
[PASS] tests/49.js
[PASS] tests/99bottles.js
[PASS] tests/alias.js
[PASS] tests/apply.js
[FAIL] tests/arg.js
at line 9 of output:
    output: <4>
    expect: <5>
[FAIL] tests/arg2.js
at line 9 of output:
    output: <1>
    expect: <2>
[PASS] tests/alias.js
Test tests/array.js
...
[PASS] tests/yhsj.js
2 FAIL, 97 PASS: runtime 21551 ms
~~~~

The return code is the number of failed tests (up to a maximum of 127).

~~~~
echo $?
2
~~~~

There is a summary when multiple test are run, but no DIFF (unless context>3).


Inputs
------
A scripts requiring test input (to read from stdin) it can be specified as follows:

~~~~
/*
=!INPUTSTART!=
45
4950
=!INPUTEND!=
*/
~~~~

This can also be provided from the command-line using -inStr or -inFile.
The default is no input.


Arguments
---------
A may script may specify arguments with an ARGS comment:

~~~~
/*
=!ARGSSTART!=
-debug 1 able baker charlie
=!ARGSEND!=
*/
~~~~

This should be on a single line, from which newlines will be stripped.

This can also be provided from the command-line using -args.
The default is no arguments.


Assert
------
Asserts, which are normally off by default, are enabled
during unit-testing.
This can result in test scripts failing when assert throws an uncaught error.

There are various ways to adjust the behaviour of assert when working with test scripts,
eg. [assert.jsi](../tests/assert.jsi?mimetype=application/javascript):

~~~~ JS linenumbers
#!/usr/local/bin/jsish -u %s
"use asserts"; // note: test mode already enables this.

assert(true,'true');
assert(2*3 == 6,'math');
try {
    assert(false,'false');
} catch(e) {
    puts('caught error');
}
;Interp.conf({asserts:false});
var x = 1;
;x;
;assert(false,'false2');
;assert(false===true);
;Interp.conf({asserts:true});

var i=1, j=2;
;assert(function () { return (i < j); },'fail');

try {
    assert(false==true);
} catch(e) {
    puts('caught error2: '+e);
}
try {
;   assert(false,'false');
} catch(e) {
    puts('caught error2: '+e);
}

;assert(false,'this assert failed',{mode:'puts', noStderr:true});

;Interp.conf({assertMode:'puts', noStderr:true});

;assert(true===false);
;assert(false,'assert also failed');
~~~~

Options
-------
Option help can be dump with:
~~~~
 jsish -u -h:
~~~~ the output
Run script(s) as unit-tests setting return code to number of failed tests.

Options/defaults:
    -args       ""      // Argument string to call script with
    -context    3       // Number of context lines for DIFF (>3 forces dir diff).
    -debug      false   // Enable debugging messages.
    -echo       false   // Run with puts/assert output showing file:line number.
    -evalFile   ""      // File to source in subinterp before test.
    -exec       false   // Use exec instead of running test in a sub-interp.
    -expectFile null    // File to use for expected output.
    -failMax    0       // Quit after this many failed tests.
    -inFile     null    // File to use for stdin input.
    -silent     false   // Run quietly, showing only failures.
    -update     false   // In place update or create of EXPECT section.
    -verbose    false   // Echo values for inputs/outputs/args.
~~~~

A script can detect unit-test mode using:
~~~~
if (Interp.conf('unitTest') > 0)
    puts("We are testing...");
~~~~


Tracing
-------
When a test fails, -echo can show the source line associated with output:

~~~~
jsish -u -echo true prob/testfail.js
~~~~ the output
Test prob/testfail.js
/home/user/src/jsi/jsi/tests/prob/testfail.js:7:   "foo passed",
/home/user/src/jsi/jsi/tests/prob/testfail.js:8:   "bar failed",
/home/user/src/jsi/jsi/tests/prob/testfail.js:9:   "baz passed",
~~~~

Edit the script as follows to run from Geany with F9, so as to click-to-navigate the output:

~~~~
#!/usr/local/bin/jsish -u -echo true %s
~~~~

You can also try -verbose for even more detail.


Errors
----

In C code there are 4 basic classes of errors (from most to least severe):

- Seg Fault.
- Abort, due to assert failure.
- Hang, incorrect or unexpected result.
- Memory leak.


And for scripts:

- Non-interactive.
- Interactive mode.
- Errors not in try/catch resulting in program termination.

!!! NOTE
    The script tools/randtest.jsi calls all functions with various arguments.


Builtins
----
The builtin self-test files are here:  https://jsish.org/fossil/jsi/dir?name=tests

Added doc/md/Types.md.











































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
Types
========
Jsi function-parameters can have types and/or default values:

~~~~ JS linenumbers
function foo (a:number, b:string='ok'):number {
   return a+1;
}
foo('a', 9, 10);
~~~~

Which provides warnings upon execution:

~~~~
jsish foo.jsi
~~~~ sh output
/tmp/foo.js:4: warning: got 3 args, expected 1-2, calling function foo(a:number, b:string="ok")    (at or near "a")
/tmp/foo.js:4: warning: type mismatch for argument arg 1 'a':  "string" is not a "number"    (at or near "a")
/tmp/foo.js:4: warning: type mismatch for argument arg 2 'b':  "number" is not a "string"    (at or near "a")
/tmp/foo.js:2: warning: type mismatch returned from 'foo':  "string" is not a "number"    (at or near "a")
~~~~

Type-checking is provided at the function call interface only.


Type Names
----
Supported types are:

| Type      | Description                                |
|-----------|--------------------------------------------|
| number    | A double floating point value              |
| boolean   | A boolean value                            |
| string    | A string value                             |
| function  | A javascript function                      |
| object    | A javascript object                        |
| array     | A javascript array                         |
| regexp    | A regular expression                       |
| userobj   | A userobj command, eg. from new Socket() |
| null      | A javascript null                          |
| undefined | A value that is undefined                  |
| void      | Argument ommitted/no value returned        |
| any       | Means any value is accepted/returned       |


Here is a function that returns nothing:

~~~~ JS linenumbers
function foo (a:number):void {
   return;
}
~~~~

and another that can return anything:

~~~~ JS linenumbers
function foo (n):any {
   return (n?99:"Ok");
}
~~~~


Type Unions
----
It is not uncommon for a parameter to want to accept more than one type.
For that we can use type-unions, multiple types separated with a pipe "|" character. eg:

~~~~ JS linenumbers
function foo (a:number, b:number|string|boolean) {
    var typ = (typeof b);
    switch (typ) {
        case "number": return b+1;
        case "string": return b;
        case "boolean": return -1;
        default: throw "unhandled type: "+typ;
    }
}
foo(9,'a');
foo(9,9);
foo(9,true);
~~~~

Similarly return types can also use unions:

~~~~ JS linenumbers
function foo (x):number|string {
    return x+x;
}
~~~~

!!!
    Union types are used extensively by [builtin commands](Reference).


Argument Count
----
In standard javascript any number of arguments may be passed in a function call,
irregardless of the parameter list.

This is also true in Jsi when a function has no types.
But the addition of at least one type activates checking for that function:

~~~~ JS linenumbers
function bar (a, b) { return a+1; }
function foo (a, b):number { return a+1; }
bar(9);
foo(9);
~~~~ sh output
/tmp/foobar.js:4: warning: incorrect arg count in function call "foo()": got 1 args, but expected 2
~~~~

Extra argument warnings can be avoided by adding an ellipsis "...":

~~~~ JS linenumbers
function fool (a:number, b:number, ...) {
   return console.args.length;
}
foo(9,9,9);
~~~~

It is also possible to enable argument count checking for untyped functions,
by setting typeCheck mode [all](#checkconfig).


Defaults
----
Default values allow functions to be called with fewer parameters, as in:

~~~~ JS linenumbers
function foo (a, b=1) {}
function bar (a=1, b=2) {}
foo(9);
bar();
~~~~

A default value must be one of the primitives:

| Type      | Description                   |
|-----------|-------------------------------|
| number    | A double floating point value |
| boolean   | A boolean value               |
| string    | A string value                |
| null      | The null value                |
| undefined | An undefined value/var        |
| void      | Argument may be ommitted      |

Also note that when a parameter is given a default value, all following it must as well,
possibly using void:

~~~~
function bar (a=1, b=true, c='ok', d=null, e=void, f=void) {}
~~~~

Assigning a default value also implicitly assigns a type (except void).
Thus the following are equivalent:

~~~~
function quick(a:number=1) {}
function quick(a=1) {}
~~~~

Types are or'ed, meaning the following accepts string as well:

~~~~
function duck(a:number|boolean='quack') {}
~~~~


Void/Undefined
----

A default-value of void is used to indicate that an argument may be ommitted.

~~~~ JS linenumbers
function bag(a=void) {}
var xyz;
bag();
bag(xyz);
~~~~ sh output
/tmp/big.jsi:4: warning: call with undefined var for argument arg 1 'a
~~~~

To allow calls with an undefined argument, give a default-value of *undefined*:

~~~~ JS linenumbers
function fig(a=undefined) {}
var xyz;
fig(xyz);
~~~~

!!! WARNING
    A function return type can not be **undefined**.


Checking
----
Type checking is used to issue warnings/errors for incorrect calls to a typed function.

When checking is enabled,
it applies to any function containing at least one type or default value, eg:

~~~~ JS linenumbers
function foo (a:number, b:string='ok'):number {}
~~~~

In Jsi, the default checking state is:

- For interactive mode: **warn**.
- For ".jsi" scripts: **run**.

There are several way a script can control type-checking:

- File extension is ".jsi" will set checking to **run**
- The first line starting with **#!** will set checking to **parse,run,all**.
- The first line (or second after a #!) is a **use** directive.
- Run with the **-T** option.
- Run with the **JIS_INTERP_OPTS='{typeCheck:XXX}'** env var.
- Use **Interp.conf({typeCheck:XXX})** in the code.

The "use" directive
can take multiple comma separated options:

~~~~ JS linenumbers
"use run,error";
function foo (a:number, b:string='ok'):number {}
~~~~

To repeat **use** must be on the first line (or 2nd if using #!).

Here is the list of modes accepted by use:

| Type    | Description                                                                     |
|---------|---------------------------------------------------------------------------------|
| parse   | Turn on parse-time checking                                                     |
| run     | Turn-on run-time checking                                                       |
| all     | Both parse and run, plus argument checking for untyped functions                |
| error   | Runtime warnings are errors                                                     |
| strict  | Same as all and error                                                           |
| funcsig | Emit warnings for named function calls with no prior declaration                |
| noundef | Disable strict warnings for calls to function with void passed an undefined var |
| nowith  | Disable using with expressions                                                  |
| asserts | Enable run-time assert() checking. See [assert](Logging#assert)      |
| Debug   | Enable LogDebug() output. See [logging](/Logging)                    |
| Trace   | Enable LogTrace() output                                                        |
| Test    | Enable LogTest() output                                                         |


The "!" prefix can be used to invert a flag:

~~~~ JS
"use strict,!error";
~~~~

Modes may also be set from the command-line:

~~~~ SH
jsish -T parse,run foo.jsi;    # Perform checking at parse and run time.
~~~~


Type-check mode can also be changed via [Interp.conf()](Interp):

~~~~
Interp.conf({typeCheck:['error','run','parse']}); // Promote warnings to errors .
~~~~

And finally, via the environment:

~~~~ SH
JSI_INTERP_OPTS='{typeCheck:["error","run","parse"]}' jsish script.jsi
~~~~

!!! NOTE
    By default type checking is silently disabled after 50 warnings.
    But this can be changed with: "Interp.conf({typeWarnMax:0})"



Limitations
----
Although parse time checking is enabled with ["use strict"](Debug),
this is no-where close to the kind of type-checking available in C.

The first reason for this is that function arguments are frequently
javascript variables which are dynamically typed.
Thus only primitives arguments can be checked.

Secondly, at parse time calls to a function can not be checked prior to that functions definition.
One option is a forward declaration, as in:

~~~~ JS linenumbers
function f(a:number):number{} // Forward declaration.

function b() {
    f(1,2);
}

function f(a:number):number {
    return a+1;
}
~~~~

Builtin commands have prototypes, which is basically why there is no problem statically checking them.


Miscellaneous
----
The rules for defining types in functions are:

- Zero or all parameters should be given types.
- Default values must be primitives.
- If a parameter is given a default value, all parameters following it must as well.
- Only functions with at least one type will be type-checked (except in mode all).

If a function has no types or defaults, it is by default treated as normal javascript,
and there will be no type-checking.

!!! NOTE
    Builtin commands make extensive use of typed parameters.

In the Jsi code-base, these apply to:

- C-Command declarations via Jsi_CmdSpec.
- C-Option declarations via Jsi_OptionSpec.
- Sqlite and MySql parameter bindings.

Added lib/Jsi_GenDeep.jsi.



















































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
#!/usr/bin/env jsish

function Jsi_GenDeep(args:array=void, opts:object=void) {

    var self = {
        query   :'export=save',
        onRecv  :DeepFileSave,
        fileList:[]
    };
    var options = { // Load markdeep and export (via websocket) to local file.
        create  :false,             // Creates include.shtml and md dir and exits.
        inDir   :'md',              // Input directory.
        incFile :'include.shtml',   // Template file.
        extn    :'.html',           // File extension.
        force   :false,             // Do all files, even if unchanged.
        outDir  :'.',               // Output directory.
        strip   :false              // Strip trailing script.
    };
    var links = [];
    parseOpts(self, options, opts);

    function checkLinks() {
        for (var i of links) {
            var fh = i[0];
            if (!File.exists(fh))
                puts('link to unknown file',fh+' from '+i[2]+' in', i[1]);
        }
    }
    
    function repHref(str:string) {
        var reg = /^<a href="([-a-zA-Z1-9_]+)(#[-a-zA-Z1-9_\/]+)?">$/;
        var vals = reg.exec(str);
        var fnam = vals[1];
        var fh = self.outDir+'/'+fnam+'.html';
        if (!File.exists(fh))
            links.push([fh, str, self.curFile]);
        var sfx = vals[2];
        if (!sfx) sfx = '';
        return '<a href="'+fnam+'.html'+sfx+'">';
    }

    function DeepFileSave(ws:userobj, id:number, data:object) {
        if (data.cmd === 'save') {
            var file = File.tail(data.url);
            file = file.split('?')[0];
            file = File.rootname(file)+self.extn;
            var ndat = data.data;
            //ndat = ndat.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ' ');
            ndat = ndat.map(['<script', '<scriptO style="display:none"', '</script>', '</scriptO>']);
            // Old 1.03 nuked newlines as it used <code> (instead of <pre>).
            //ndat = ndat.map([' <span class="line"', ' \n<span class="line"',
            //    '<script src="dumpdeep.js"></script>', '']);
            if (self.strip) {
                var nend = ndat.lastIndexOf('<script>window.alreadyProcessedMarkdeep|');
                if (nend>0)
                    ndat = ndat.substr(0,nend-1)+'</div></div><p></p>&nbsp;<p></p>';
            }
            ndat += '<style>body{visibility:visible;}</style>';
            self.curFile = file;
            ndat = ndat.replace(/<a href="[-a-zA-Z1-9_]+(#[-a-zA-Z1-9_\/]+)?">/gm, repHref);
            File.write(self.outDir+'/'+file, ndat);
            puts('Saved file',file);
            if (!self.fileList.length) {
                ws.send(id, 'DONE!!!');
                checkLinks();
                setTimeout(function () { puts('DONE!!!'); exit(0); }, 1000);
                return;
            }
            file = self.fileList[0];
            self.fileList = self.fileList.slice(1);
            puts('Request file',file);
            ws.send(id, file);
        }
    }

    function main() {
        if (self.create) {
            File.copy('/zvfs/lib/web/markdeep/include.shtml', 'include.shtml');
            File.copy('/zvfs/lib/web/markdeep/nginx_deepdoc.conf', 'nginx_deepdoc.conf');
            File.copy('/zvfs/lib/web/markdeep/jsistyle.css', 'jsistyle.css');
            File.copy('/zvfs/lib/web/dumpdeep.js', 'dumpdeep.js');
            File.copy('/zvfs/lib/web/jsiweb.js', 'jsiweb.js');
            File.copy('/zvfs/lib/web/markdeep.min.js', 'markdeep.min.js');
            File.mkdir('md');
            File.copy('/zvfs/lib/web/markdeep/DeepDoc.md', 'md/DeepDoc.md');
            puts("DeepDoc created. View with 'jsish -W -url Deepdoc'");
            return;
        }
        var fl = [];
        if (args.length == 1 && File.isdir(args[0]))
            self.inDir = args[0];
        else
            for (var i of args)
                fl.push(i);
        if (!File.exists(self.outDir))
            throw('output directory does not exits: ' + self.outDir);
        if (!fl.length) {
            if (!File.isdir(self.inDir))
                throw('directory not found: '+self.inDir);
            if (!File.exists(self.incFile))
                throw('template not found: '+self.incFile);            
            for (i of File.glob('*.md', {dir:self.inDir})) {
                LogTrace('I',i);
                var fn = self.inDir + '/' + i,
                    fr = File.rootname(i), fo = self.outDir + '/' + fr + '.html';
                if (!self.force) { // Skip if unmodified.
                    if (File.exists(fo)
                        && File.mtime(fn) < File.mtime(fo)
                        && File.mtime(self.incFile) < File.mtime(fo)) {
                        LogDebug("Skip ",fo);
                        continue;
                    }
                }
                LogTrace('FF',fr);
                fl.push(fr);
            }
        }
        if (!fl.length)
            return puts("No changed files (try -force)");
        self.fileList = fl.slice(1);
        var wsopts = {
             onRecv  :DeepFileSave,
             query   :self.query,
             wsOpts  : {extArgs :{mdi:'{dumpScript:"dumpdeep.js"}'}}
             // htmlPrefix : '<p>',
             // htmlSuffix : '<p>',
         };
        wsopts.url = fl[0];
        Jsi_Websrv([], wsopts);
        return;
    }
    return main();
}

if (Info.isMain()) {
    runModule(Jsi_GenDeep);
}

Changes to lib/Jsi_Markdeep.jsi.

31
32
33
34
35
36
37

38
39
40
41
42
43
44
            add += '<script src="'+self.dumpScript+'"></script>';
        if (rc.indexOf(marker)<0)
            rc = title+'<link rel="stylesheet" href="jsistyle.css" type="text/css" media="screen" />\n'
                + marker + '<style class="fallback">body{visibility:hidden;}</style>\n'
                + '<script>window.markdeepOptions={tocStyle:"medium"}</script>\n'
                + rc
                + add

                + '<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>';
        rc += self.suffix;
        return rc;
    }
    if (typeof(files) === 'string')
        files = [files];
    if (files && files.length)







>







31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
            add += '<script src="'+self.dumpScript+'"></script>';
        if (rc.indexOf(marker)<0)
            rc = title+'<link rel="stylesheet" href="jsistyle.css" type="text/css" media="screen" />\n'
                + marker + '<style class="fallback">body{visibility:hidden;}</style>\n'
                + '<script>window.markdeepOptions={tocStyle:"medium"}</script>\n'
                + rc
                + add
                + '<script src="markdeep.min.js"></script>\n'
                + '<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>';
        rc += self.suffix;
        return rc;
    }
    if (typeof(files) === 'string')
        files = [files];
    if (files && files.length)

Changes to lib/Jsi_Websrv.jsi.

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
346
347
348
349
350
351
352


353
354
355
356
357
358
359
        query       :'',        // Query to append to url.
        rootdir     :null,      // Base directory.
        server      :false,     // Server mode: same as noGui=true and closeTimout=0
        srcFile     :'',        // File of code to source inside the Jsi_Websrv function.
        uploadDir   :'/tmp',    // Upload files go here
        timeout     :60000,     // Set timeout (in ms) to shutdown. This is idle time, unless negative.
        trace       :false,     // Tracing output.
        url         :"",        // The file to serve out.
        urlPrefix   :'/Websrv', // Prefix for urls
        useridPass  :'',        // USER:PASS for web GUI.
        wsdebug     :0,         // Debug option for websockets.
        wsOpts      :{},        // Websocket options.
        zip         :''         // A .zip, .sqlar, or .fossil file to mount and use as rootdir.
    };
    parseOpts(self, options, conf);
................................................................................
                self.rootdir = File.dirname(self.url);
            var rlen = self.rootdir.length;
            if (self.url.substr(0, rlen) === self.rootdir)
                self.url = self.url.substr(rlen);
        }
        if (!self.rootdir)
            self.rootdir = '.';


        if (!self.server && !self.pageStr && urlOrig=='' && (!self.url || !File.isfile(self.rootdir+'/'+self.url)))
            throw("url file empty or not found: "+self.url);
        
        // Provide default values for websocket.
        var wo = self.wsopts = {
            local:self.local,
            debug:self.wsdebug,







|







 







>
>







29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
...
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
        query       :'',        // Query to append to url.
        rootdir     :null,      // Base directory.
        server      :false,     // Server mode: same as noGui=true and closeTimout=0
        srcFile     :'',        // File of code to source inside the Jsi_Websrv function.
        uploadDir   :'/tmp',    // Upload files go here
        timeout     :60000,     // Set timeout (in ms) to shutdown. This is idle time, unless negative.
        trace       :false,     // Tracing output.
        url         :"",        // The file/url to serve out and clears timeout.
        urlPrefix   :'/Websrv', // Prefix for urls
        useridPass  :'',        // USER:PASS for web GUI.
        wsdebug     :0,         // Debug option for websockets.
        wsOpts      :{},        // Websocket options.
        zip         :''         // A .zip, .sqlar, or .fossil file to mount and use as rootdir.
    };
    parseOpts(self, options, conf);
................................................................................
                self.rootdir = File.dirname(self.url);
            var rlen = self.rootdir.length;
            if (self.url.substr(0, rlen) === self.rootdir)
                self.url = self.url.substr(rlen);
        }
        if (!self.rootdir)
            self.rootdir = '.';
        if (urlOrig!=='' && self.timeout === 60000)
            self.timeout = 0;
        if (!self.server && !self.pageStr && urlOrig=='' && (!self.url || !File.isfile(self.rootdir+'/'+self.url)))
            throw("url file empty or not found: "+self.url);
        
        // Provide default values for websocket.
        var wo = self.wsopts = {
            local:self.local,
            debug:self.wsdebug,

Changes to lib/autoload.jsi.

29
30
31
32
33
34
35

36
37
38
39
40
41
42
43
44
45
46
Jsi_Auto.Jsi_DebugUI    = 'source("'+Info.scriptDir()+'/Jsi_DebugUI/Jsi_DebugUI.jsi")';
Jsi_Auto.Jsi_SqliteUI   = 'source("'+Info.scriptDir()+'/Jsi_SqliteUI/Jsi_SqliteUI.jsi")';
Jsi_Auto.Jsi_Make       = 'source("'+Info.scriptDir()+'/Jsi_Make.jsi")';
Jsi_Auto.Jsi_Vfs        = 'source("'+Info.scriptDir()+'/Jsi_Vfs.jsi")';
Jsi_Auto.Jsi_Archive    = 'source("'+Info.scriptDir()+'/Jsi_Archive.jsi")';
Jsi_Auto.Jsi_Module     = 'source("'+Info.scriptDir()+'/Jsi_Module.jsi")';
Jsi_Auto.Jsi_Help       = 'source("'+Info.scriptDir()+'/Jsi_Help.jsi")';


Jsi_Auto.Sqlite         = 'require("Sqlite");';
Jsi_Auto.MySql          = 'require("MySql");';
Jsi_Auto.Websocket      = 'require("WebSocket");';
Jsi_Auto.Socket         = 'require("Socket");';
Jsi_Auto.DebugUI        = 'require("DebugUI");';

source(Info.scriptDir()+'/JsiCompat.jsi');

Jsi_Auto.__autoloaded__ =true;








>











29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Jsi_Auto.Jsi_DebugUI    = 'source("'+Info.scriptDir()+'/Jsi_DebugUI/Jsi_DebugUI.jsi")';
Jsi_Auto.Jsi_SqliteUI   = 'source("'+Info.scriptDir()+'/Jsi_SqliteUI/Jsi_SqliteUI.jsi")';
Jsi_Auto.Jsi_Make       = 'source("'+Info.scriptDir()+'/Jsi_Make.jsi")';
Jsi_Auto.Jsi_Vfs        = 'source("'+Info.scriptDir()+'/Jsi_Vfs.jsi")';
Jsi_Auto.Jsi_Archive    = 'source("'+Info.scriptDir()+'/Jsi_Archive.jsi")';
Jsi_Auto.Jsi_Module     = 'source("'+Info.scriptDir()+'/Jsi_Module.jsi")';
Jsi_Auto.Jsi_Help       = 'source("'+Info.scriptDir()+'/Jsi_Help.jsi")';
Jsi_Auto.Jsi_GenDeep    = 'source("'+Info.scriptDir()+'/Jsi_GenDeep.jsi")';

Jsi_Auto.Sqlite         = 'require("Sqlite");';
Jsi_Auto.MySql          = 'require("MySql");';
Jsi_Auto.Websocket      = 'require("WebSocket");';
Jsi_Auto.Socket         = 'require("Socket");';
Jsi_Auto.DebugUI        = 'require("DebugUI");';

source(Info.scriptDir()+'/JsiCompat.jsi');

Jsi_Auto.__autoloaded__ =true;

Added lib/web/dumpdeep.js.



















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
// Script to dump markdeep export via websocket.
console.log('dumpdeep.js');
function DeepDumpWs() {
    console.log('DeepDumpWsTO');
    if (document.URL.indexOf("?export=save")<0) return;
    var url = document.URL.replace(/^http/,"ws");
    var ws = new WebSocket(url, "ws");
    ws.onopen = function() {
        var data = document.querySelectorAll("body pre");
        if (!data[0]) data = document.querySelectorAll("body code");
        if (!data[0]) return;
        data = data[0].innerText;
        console.log('DATA '+data);
        clearInterval(DeepDumpWsTO);
        ws.send(JSON.stringify({cmd:"save", url:url, data:data}));
    };
    ws.onmessage = function(msg) {
        if (msg.data !== 'DONE!!!')
            document.location = msg.data+"?export=save";
        else
            document.body.innerHTML = '<style>body{background:#ddd;}</style>DOWNLOAD COMPLETE: PLEASE CLOSE THIS TAB';
    };
};
window.onload = DeepDumpWs;
var DeepDumpWsTO = setTimeout(DeepDumpWs, 10000);

Added lib/web/markdeep/DeepDoc.md.































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
DeepDoc
====
DeepDoc uses markdeep in conjunction with Server-Side-Include (SSI)
to implement a simple web framework.
This can be served out directly from nginx, and/or
Jsi can be used to generate static html web pages
that load lightning fast.


Local
----
To display use "jsish -W -url DeepDoc

Html
----
To convert all .md files to static .html files use:
~~~~
jsish -g
~~~~
Then the entire directory can be uploaded to a web site.  Test with:
~~~~
jsish -W DeepDoc.html
~~~~

Nginx
----
Nginx can be configured to serve ".md" markdeep files by
adding to nginx.conf:
~~~~
include nginx_deepdoc.conf;"
~~~~

Added lib/web/markdeep/include.shtml.























>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
<style class="fallback">body{visibility:hidden;}</style>
<script>window.markdeepOptions={tocStyle:"medium"}</script>

<!--#include file="$md"-->

<link rel="stylesheet" href="jsistyle.css" type="text/css" media="screen" />
<!-- Markdeep: --><style class="fallback">body{visibility:hidden;}</style><script src="markdeep.min.js"></script>
<script>var startOfMarkDeep=true; window.alreadyProcessedMarkdeep||(document.body.style.visibility='visible'); document.title=location.pathname.match(/\/([\w]+)[^\/]*$/)[1];</script>
<script src="dumpdeep.js"></script>


Added lib/web/markdeep/jsistyle.css.

















>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
.md .mediumTOC {background:#fff; border:1px solid #ccc; padding-right:3px;}
.md .mediumTOC center { display:none; }
h1,h2,h3,h4 { clear:none; padding:0px; }
.tocNumber {display:none; }
.md h1::before, .md h2::before, .md h3::before,.md h4::before { display:none; }
body { max-width:980px; }
.output { background:#000; color:#fff; }
.markdeepFooter { visibility:hidden; }

Added lib/web/markdeep/nginx_deepdoc.conf.

































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# To enable markdeep processing add to nginx.conf "include nginx_deepdoc.conf"

location ~ \.shtml$ {
    default_type text/html;
    ssi on;
}
location ~ ^(.*)/([-\w]+)$ {
    set $pref $1;
    set $md $pref/md/$2.md;
    if (!-f $document_root$md) {
        return 404;
    }
    rewrite ^ $pref/include.shtml last;
}

Changes to src/jsi.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
.....
19118
19119
19120
19121
19122
19123
19124

19125
19126
19127
19128
19129
19130
19131
.....
19175
19176
19177
19178
19179
19180
19181


19182
19183
19184
19185
19186
19187
19188
.....
25656
25657
25658
25659
25660
25661
25662
25663
25664
25665
25666
25667
25668
25669
25670
.....
25692
25693
25694
25695
25696
25697
25698
25699
25700
25701
25702
25703
25704
25705
25706
.....
25718
25719
25720
25721
25722
25723
25724









25725
25726
25727
25728
25729
25730
25731
.....
26599
26600
26601
26602
26603
26604
26605
26606
26607
26608
26609
26610
26611
26612
26613
26614
.....
26890
26891
26892
26893
26894
26895
26896
26897
26898
26899
26900
26901
26902
26903
26904
.....
50450
50451
50452
50453
50454
50455
50456
50457
50458
50459
50460
50461
50462
50463
50464
.....
50516
50517
50518
50519
50520
50521
50522
50523
50524
50525
50526
50527
50528
50529
50530
.....
50542
50543
50544
50545
50546
50547
50548
50549
50550
50551
50552
50553
50554
50555
50556
50557
.....
50641
50642
50643
50644
50645
50646
50647
50648
50649
50650
50651
50652
50653
50654
50655
.....
50684
50685
50686
50687
50688
50689
50690
50691
50692
50693
50694
50695
50696
50697
50698
50699
50700
50701
50702
50703
50704
.....
50744
50745
50746
50747
50748
50749
50750
50751
50752
50753
50754
50755
50756
50757
50758
.....
50799
50800
50801
50802
50803
50804
50805
50806
50807
50808
50809
50810
50811
50812
50813
.....
50962
50963
50964
50965
50966
50967
50968
50969
50970
50971
50972
50973
50974
50975
50976
.....
51054
51055
51056
51057
51058
51059
51060
51061
51062
51063
51064
51065
51066
51067
51068
.....
51104
51105
51106
51107
51108
51109
51110
51111
51112

51113
51114
51115
51116
51117
51118
51119
.....
51125
51126
51127
51128
51129
51130
51131
51132
51133
51134
51135
51136
51137
51138
51139
51140
51141
51142
51143
51144
51145

51146







51147
51148
51149
51150
51151
51152








51153
51154
51155
51156
51157
51158
51159
51160
51161
51162
51163
51164
51165
51166
51167
51168
51169
51170
51171
51172
51173
51174
51175
51176
51177
51178
51179
51180
51181
.....
51191
51192
51193
51194
51195
51196
51197
51198
51199
51200
51201
51202
51203
51204
51205
51206
51207
51208
51209
51210
51211
51212
51213
51214
51215
51216
51217
.....
51246
51247
51248
51249
51250
51251
51252
51253
51254
51255
51256
51257
51258
51259
51260
51261
51262
51263
51264
51265
51266
51267
51268
51269
.....
51271
51272
51273
51274
51275
51276
51277
51278
51279
51280
51281
51282
51283
51284
51285
.....
51293
51294
51295
51296
51297
51298
51299
51300
51301
51302
51303
51304
51305
51306
51307
.....
51345
51346
51347
51348
51349
51350
51351
51352
51353
51354
51355
51356
51357
51358
51359
51360
51361
51362
51363
51364
51365
51366
51367
51368
51369
51370
51371
51372
51373
51374
51375
51376
51377
51378
51379
51380
51381
51382
51383
51384
51385
51386
51387
51388
51389
51390
51391
51392
51393
51394
51395
51396
51397
51398
51399
51400
51401
51402
51403
51404
51405
51406
51407
51408
51409
51410
51411
51412
51413
51414
51415
51416
51417
51418
51419
51420
51421
51422
51423
51424
51425
51426
51427
51428
51429
51430
51431
51432
51433
51434
51435
51436
51437
51438
51439
51440
51441
51442
.....
51463
51464
51465
51466
51467
51468
51469
51470
51471
51472
51473
51474
51475
51476
51477
.....
51480
51481
51482
51483
51484
51485
51486
51487
51488
51489
51490
51491
51492
51493
51494
.....
51532
51533
51534
51535
51536
51537
51538
51539
51540
51541
51542
51543
51544
51545
51546
.....
51556
51557
51558
51559
51560
51561
51562
51563
51564
51565
51566
51567
51568
51569
51570
51571
51572
51573
51574
51575
51576
51577
51578
51579
51580
51581
51582
51583
.....
51616
51617
51618
51619
51620
51621
51622
51623
51624
51625
51626
51627
51628
51629
51630
.....
51636
51637
51638
51639
51640
51641
51642
51643
51644
51645
51646
51647
51648
51649
51650
51651
51652
51653
51654
.....
51665
51666
51667
51668
51669
51670
51671
51672
51673
51674
51675
51676
51677
51678
51679
.....
51725
51726
51727
51728
51729
51730
51731
51732
51733
51734
51735
51736
51737
51738
51739
.....
51774
51775
51776
51777
51778
51779
51780
51781
51782
51783
51784
51785
51786
51787
51788
51789
51790
51791
51792
51793
51794
51795
51796
51797
.....
51798
51799
51800
51801
51802
51803
51804
51805
51806
51807
51808
51809
51810
51811
51812
51813
51814
51815
51816
51817
.....
51859
51860
51861
51862
51863
51864
51865
51866
51867
51868
51869
51870
51871
51872
51873
51874
51875
51876
51877
51878
51879
51880
51881
51882
51883
51884
51885
51886
51887
51888
51889
51890
51891
51892
51893
51894
51895
51896
51897
.....
51917
51918
51919
51920
51921
51922
51923
51924
51925
51926
51927
51928
51929
51930
51931
.....
51957
51958
51959
51960
51961
51962
51963
51964
51965
51966
51967
51968
51969
51970
51971
.....
52019
52020
52021
52022
52023
52024
52025
52026
52027
52028
52029
52030
52031
52032
52033
.....
52108
52109
52110
52111
52112
52113
52114
52115
52116
52117
52118
52119
52120
52121
52122
52123
52124
52125
52126
52127
52128
52129
.....
52141
52142
52143
52144
52145
52146
52147
52148
52149
52150
52151
52152
52153
52154
52155
52156
52157
52158
52159
52160
52161
52162
52163
52164
52165
52166
52167
52168
52169
52170
52171
52172
52173
52174
52175
52176
52177
52178
52179
52180
52181
52182
52183
52184
52185
.....
52216
52217
52218
52219
52220
52221
52222
52223
52224
52225
52226
52227
52228
52229
52230
52231
52232
52233
52234
52235
52236
52237
52238
52239
52240
52241
52242
52243
52244
52245
52246
52247
52248
52249
52250
52251
52252
52253
.....
52277
52278
52279
52280
52281
52282
52283
52284
52285
52286
52287
52288
52289
52290
52291
.....
52294
52295
52296
52297
52298
52299
52300
52301
52302
52303
52304
52305
52306
52307
52308
.....
52343
52344
52345
52346
52347
52348
52349
52350
52351
52352
52353
52354
52355
52356
52357
52358
52359
52360
52361
52362
52363
52364
52365
52366
52367
52368
52369
52370
52371
52372
52373
52374
52375
52376
52377
52378
52379
52380
52381
52382
52383
52384
52385
52386
52387
52388
.....
52390
52391
52392
52393
52394
52395
52396
52397
52398
52399
52400
52401
52402
52403
52404
52405
52406
52407
52408
52409
52410
52411
.....
52428
52429
52430
52431
52432
52433
52434
52435
52436
52437
52438
52439
52440
52441
52442
.....
52478
52479
52480
52481
52482
52483
52484
52485
52486
52487
52488
52489
52490
52491
52492
.....
52516
52517
52518
52519
52520
52521
52522
52523
52524
52525
52526
52527
52528
52529
52530
52531
52532
52533
52534
52535
52536
.....
52552
52553
52554
52555
52556
52557
52558
52559
52560
52561
52562
52563
52564
52565
52566
52567
52568
52569
52570
52571
52572
52573
52574
52575
52576
52577
52578
52579
52580
52581
52582
52583
52584
52585
52586
52587
52588
52589
52590
52591
52592
52593
52594
52595
52596
52597
52598
52599
52600
52601
52602
52603
52604
52605
.....
52683
52684
52685
52686
52687
52688
52689
52690
52691
52692
52693
52694
52695
52696
52697
.....
52724
52725
52726
52727
52728
52729
52730
52731
52732
52733
52734
52735
52736
52737
52738
.....
52791
52792
52793
52794
52795
52796
52797
52798
52799
52800
52801
52802
52803
52804
52805
.....
52807
52808
52809
52810
52811
52812
52813
52814
52815
52816
52817
52818
52819
52820
52821
52822
52823
52824
52825
52826
.....
52856
52857
52858
52859
52860
52861
52862
52863
52864
52865
52866
52867
52868
52869
52870
52871
52872
52873
52874
52875
52876
52877
52878
52879
52880
52881
52882
52883
.....
52952
52953
52954
52955
52956
52957
52958
52959
52960
52961
52962
52963
52964
52965
52966
52967
52968
52969
.....
52973
52974
52975
52976
52977
52978
52979
52980
52981
52982
52983
52984
52985
52986
52987
52988
52989
52990
52991
52992
52993
52994
52995
52996
52997
52998
52999
53000
.....
53014
53015
53016
53017
53018
53019
53020
53021
53022
53023
53024
53025
53026
53027
53028
.....
53036
53037
53038
53039
53040
53041
53042
53043
53044
53045
53046
53047
53048
53049
53050
.....
65207
65208
65209
65210
65211
65212
65213
65214
65215
65216
65217
65218
65219
65220
65221
65222
65223
65224
65225
65226

65227
65228
65229
65230
65231
65232
65233
/* jsi.h : External API header file for Jsi. */
#ifndef __JSI_H__
#define __JSI_H__

#define JSI_VERSION_MAJOR   2
#define JSI_VERSION_MINOR   6
#define JSI_VERSION_RELEASE 1

#define JSI_VERSION (JSI_VERSION_MAJOR + ((Jsi_Number)JSI_VERSION_MINOR/100.0) + ((Jsi_Number)JSI_VERSION_RELEASE/10000.0))

#ifndef JSI_EXTERN
#define JSI_EXTERN extern
#endif

................................................................................
            puts("jsish arguments:\n"
              "  -a/--archive FILE\tMount an archive (zip, sqlar or fossil repo) and run module.\n"
              "  -c/--cdata FILE\tGenerate .c or JSON output from a .jsc description.\n"
              "  -C/--cssi FILE\tPreprocess embedded CSS in .css file.\n"
              "  -d/--debug FILE\tRun console debugger on script.\n"
              "  -D/--debugui FILE\tRun web-gui debugger on script.\n"
              "  -e/--eval STRING\tEvaluate a javascript string and exit.\n"

              "  -h/--help\t\tPrint help in short or long form.\n"
              "  -H/--htmli FILE\tPreprocess embedded jsi in .htmli file.\n"
              "  -J/--jsi FILE\t\tPreprocess a .jsi file to typecheck in standard javascript.\n"
              "  -m/--module FILE\tSource file and invoke runModule.\n"
              "  -M/--make FILE\tPreprocess script as a Jsi Makefile.\n"
              "  -s/--safe FILE\tRun script in safe sub-interp.\n"
              "  -S/--sqliteui DBFILE\tRun Sqlite web-gui.\n"
................................................................................
        } else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--safe")==0 || Jsi_Strcmp(argv[1], "-s" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Safe');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--debugui")==0 || Jsi_Strcmp(argv[1], "-D" )==0)) {
            interp->debugOpts.isDebugger = 1;
            rc = Jsi_EvalString(interp, "runModule('Jsi_DebugUI');", JSI_EVAL_ISMAIN);
        } else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--wget")==0 || Jsi_Strcmp(argv[1], "-w" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Wget');", JSI_EVAL_ISMAIN);


        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--websrv")==0 || Jsi_Strcmp(argv[1], "-W" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Websrv');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--archive")==0 || Jsi_Strcmp(argv[1], "-a" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Archive');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--sqliteui")==0 || Jsi_Strcmp(argv[1], "-S" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_SqliteUI');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--cdata")==0 || Jsi_Strcmp(argv[1], "-c" )==0))
................................................................................
    char *cp = path;
    *cp = 0;
    for (i=0; i<argc; i++) {
        if (i == 0 && argv[0][0] == 0) {
            continue;
        } else if (argv[i][0] == 0) {
            continue;
        } else if (!Jsi_Strcmp(argv[i],".")) {
            continue;
        } else if (!Jsi_Strcmp(argv[i],"..")) {
            char *pcp = Jsi_DSValue(&dStr), *lcp = pcp;
            pcp = Jsi_Strrchr(pcp, '/');
            if (pcp && pcp != Jsi_DSValue(&dStr)) {
                *pcp = 0;
                Jsi_DSSetLength(&dStr, Jsi_Strlen(lcp));
................................................................................
    DeBackSlashify(Jsi_DSValue(cwdPtr));
#endif
    return Jsi_DSValue(cwdPtr);
}

char *Jsi_FileRealpathStr(Jsi_Interp *interp, const char *path, char *newname)
{
    if (!path) return NULL;
    Jsi_DString dStr;
    char *npath = (char*)path, *apath;
    Jsi_DSInit(&dStr);
    if (*path == '~') {
#ifndef __WIN32
        struct passwd pw, *pwp; /* TODO: could fallback to using env HOME. */
        char buf[JSI_BUFSIZ];
................................................................................
    }
#ifdef __WIN32
    if (Jsi_Strncmp(path, JSI_ZVFS_DIR, sizeof(JSI_ZVFS_DIR)-1)==0 || Jsi_Strncmp(path, JSI_VFS_DIR, sizeof(JSI_VFS_DIR)-1)==0)
        apath = NULL;
    else
#endif
    apath = realpath(npath, newname);









    if (!apath) {
        if (newname)
            Jsi_Strcpy(newname, apath=npath);
        else
            apath = Jsi_Strdup(npath);
#ifndef __WIN32
        /* If path not exists on unix we try to eliminate ../ and /./ etc.*/
................................................................................

    Jsi_ValueMakeString(interp, ret, Jsi_DSFreeDup(&dStr));
    return JSI_OK;
}
    
#define FN_strreplace JSI_INFO("\
If the replace argument is a function, it is called with match,p1,p2,...,offset,string.  \
If called function is known to have 1 argument, it is called with just the match.\
Otherwise if the first argument is a regexp, the replace can contain the $ escapes: $&, $1, etc.")
static Jsi_RC StringReplaceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    /* Now handles perl regex flag extensions.*/
    const char *source_str;
    int source_len, bLen;
    const char *replace_str = NULL;
................................................................................
    { "charAt",     StringCharAtCmd,        1, 1, "index:number", .help="Return char at index", .retType=(uint)JSI_TT_STRING},
    { "charCodeAt", StringCharCodeAtCmd,    1, 1, "index:number", .help="Return char code at index", .retType=(uint)JSI_TT_NUMBER },
    { "concat",     StringConcatCmd,        0,-1, "str:string, ...", .help="Append one or more strings", .retType=(uint)JSI_TT_STRING },
    { "indexOf",    StringIndexOfCmd,       1, 2, "str:string, start:number", .help="Return index of char", .retType=(uint)JSI_TT_NUMBER },
    { "lastIndexOf",StringLastIndexOfCmd,   1, 2, "str:string, start:number", .help="Return index of last char", .retType=(uint)JSI_TT_NUMBER },
    { "match",      StringMatchCmd,         1, 1, "pattern:regexp|string", .help="Return array of matches", .retType=(uint)JSI_TT_ARRAY|JSI_TT_NULL },
    { "map",        StringMapCmd,           1, 2, "strMap:array, nocase:boolean=false", .help="Replaces characters in string based on the key-value pairs in strMap", .retType=(uint)JSI_TT_STRING },
    { "repeat",     StringRepeatCmd,        1, 1, "count:number", .help="Return count copies of string", .retType=(uint)JSI_TT_STRING, .flags=0, .info=FN_strreplace },
    { "replace",    StringReplaceCmd,       2, 2, "pattern:regexp|string, replace:string|function", .help="Regex/string replacement", .retType=(uint)JSI_TT_STRING, .flags=0, .info=FN_strreplace },
    { "search",     StringSearchCmd,        1, 1, "pattern:regexp|string", .help="Return index of first char matching pattern", .retType=(uint)JSI_TT_NUMBER },
    { "slice",      StringSliceCmd,         1, 2, "start:number, end:number", .help="Return section of string", .retType=(uint)JSI_TT_STRING },
    { "split",      StringSplitCmd,         0, 1, "char:string|null=void", .help="Split on char and return Array: null removes empty elements", .retType=(uint)JSI_TT_ARRAY },
    { "substr",     StringSubstrCmd,        0, 2, "start:number, length:number", .help="Return substring", .retType=(uint)JSI_TT_STRING },
    { "substring",  StringSubstringCmd,     0, 2, "start:number, end:number", .help="Return substring", .retType=(uint)JSI_TT_STRING },
    { "toLocaleLowerCase",StringToLowerCaseCmd,0, 0, "",.help="Lower case", .retType=(uint)JSI_TT_STRING },
................................................................................
    jsi_wsStatData stats;
    char *iface;
    const char* urlPrefix, *urlRedirect;
    const char *localhostName;
    const char *clientName;
    const char *clientIP;
    const char *useridPass;
    const char *realm, *templateFile;
    struct lws_context *instCtx;
    Jsi_Value *getRegexp, *post;
    unsigned int oldus;
    int opts;
    int hasOpts;
    int debug;
    int maxConnects;
................................................................................
    int wid;
    int sfd;
    bool isWebsock, echo;
    const char *clientName;
    const char *clientIP;
    int hdrSz[200]; // Space for up to 100 headers
    int hdrNum;     // Num of above.
    
    // Pointers to reset.
    Jsi_DString dHdrs; // Store header string here.
    Jsi_Stack *stack;
    Jsi_DString recvBuf; // To buffer recv when recvJSON is true.
    Jsi_Value *onClose, *onFilter, *onRecv, *onUpload, *onGet, *onUnknown, *rootdir, *headers;
    char *lastData;
#if (LWS_LIBRARY_VERSION_MAJOR>1)
................................................................................
typedef struct {
    Jsi_Value *val, *objVar;
    const char *arg;
    int triedLoad;
    int flags;
} jsi_wsHander;

static const char* const jsi_wsparam_names[] = { "text", "send", "file", "upload" }; 
static const char* jsi_wsparam_str = "text,send,file,upload"; 

#ifndef jsi_IIOF
#define jsi_IIOF .flags=JSI_OPT_INIT_ONLY
#define jsi_IIRO .flags=JSI_OPT_READ_ONLY
#endif

static Jsi_OptionSpec WPSStats[] =
................................................................................
    JSI_OPT(STRKEY, jsi_wsCmdObj, realm,      .help="Realm for basic auth (jsish)", ),
    JSI_OPT(INT,    jsi_wsCmdObj, recvBufMax, .help="Size limit of a websocket message", jsi_IIOF),
    JSI_OPT(INT,    jsi_wsCmdObj, recvBufTimeout,.help="Timeout for recv of a websock msg", jsi_IIOF),
    JSI_OPT(BOOL,   jsi_wsCmdObj, redirMax,   .help="Temporarily disable redirects when see more than this in 10 minutes"),
    JSI_OPT(STRING, jsi_wsCmdObj, rootdir,    .help="Directory to serve html from (\".\")"),
    JSI_OPT(CUSTOM, jsi_wsCmdObj, stats,      .help="Statistical data", jsi_IIRO, .custom=Jsi_Opt_SwitchSuboption, .data=WPSStats),
    JSI_OPT(TIME_T, jsi_wsCmdObj, startTime,  .help="Time of websocket start", jsi_IIRO),
    JSI_OPT(STRKEY, jsi_wsCmdObj, templateFile,.help="File name for markdeep template (template.shtml)"),
    JSI_OPT(STRKEY, jsi_wsCmdObj, urlPrefix,  .help="Prefix in url to strip from path; for reverse proxy"),
    JSI_OPT(STRKEY, jsi_wsCmdObj, urlRedirect,.help="Redirect when no url or / is given. Must match urlPrefix, if given"),
    JSI_OPT(BOOL,   jsi_wsCmdObj, use_ssl,    .help="Use https (for client)", jsi_IIOF),
    JSI_OPT(STRKEY, jsi_wsCmdObj, useridPass, .help="The USERID:PASSWORD to use for basic authentication"),
    JSI_OPT(OBJ,    jsi_wsCmdObj, version,    .help="WebSocket version info", jsi_IIRO),
    JSI_OPT_END(jsi_wsCmdObj, .help="Websocket options")
};
................................................................................
        return NULL;
    }
    int sid = ((sfd<<1)|ishttp);
    if (create)
        hPtr = Jsi_HashEntryNew(cmdPtr->pssTable, (void*)(intptr_t)sid, &isNew);
    else
        hPtr = Jsi_HashEntryFind(cmdPtr->pssTable, (void*)(intptr_t)sid);
    
    if (hPtr && !isNew)
        pss = (typeof(pss))Jsi_HashValueGet(hPtr);
    
    if (!pss) {
        if (!create)
            return NULL;        
        pss = (typeof(pss))Jsi_Calloc(1, sizeof(*pss));
        Jsi_HashValueSet(hPtr, pss);
        pss->isWebsock = !ishttp;
        pss->sig = JWS_SIG_PWS;
        pss->hPtr = hPtr;
        Jsi_HashValueSet(hPtr, pss);
        pss->cmdPtr = cmdPtr;
................................................................................
    if (pss->sig == 0)
        return;
    WSSIGASSERT(pss, PWS);
    if (pss->state == PWS_DEAD)
        return;
    if (pss->cmdPtr && pss->cmdPtr->debug>3)
        fprintf(stderr, "PSS DELETE: %p\n", pss);
        
    jsi_wsrecv_flush(pss->cmdPtr, pss);
    if (pss->hPtr) {
        Jsi_HashValueSet(pss->hPtr, NULL);
        Jsi_HashEntryDelete(pss->hPtr);
        pss->hPtr = NULL;
    }
    Jsi_Interp *interp = pss->cmdPtr->interp;
................................................................................
        pss->stats.sentLast = time(NULL);
    } else {
        pss->state = PWS_SENDERR;
        pss->stats.sentErrCnt++;
        pss->stats.sentErrLast = time(NULL);
        cmdPtr->stats.sentErrCnt++;
        cmdPtr->stats.sentErrLast = time(NULL);
    }    
    return m;
}

static int jsi_wsServeHeader(jsi_wsPss *pss, struct lws *wsi, int strLen,
    int code, const char *extra, const char *mime, Jsi_DString *jStr)
{
    uchar ubuf[JSI_BUFSIZ], *p=ubuf, *end = &ubuf[sizeof(ubuf)-1];
................................................................................
        }
        Jsi_ValueArraySet(interp, *vPtr, Jsi_ValueNewStringDup(interp, buf), n-1);
    }
}

static Jsi_RC jsi_wsGetCmd(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss* pss, struct lws *wsi,
    const char *inPtr, Jsi_Value *cmd, Jsi_DString *tStr)
{ 
    Jsi_RC jrc;
    Jsi_Value *retStr = Jsi_ValueNew1(interp);
    // 4 args: ws, id, url, query
    Jsi_Value *vpargs, *vargs[10];
    int n = 0;
    if (cmdPtr->deleted) return JSI_ERROR;
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
................................................................................
    MKLCBS(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ),
    MKLCBS(LWS_CALLBACK_RECEIVE_CLIENT_HTTP),
    MKLCBS(LWS_CALLBACK_COMPLETED_CLIENT_HTTP),
    MKLCBS(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE),
    MKLCBS(LWS_CALLBACK_HTTP_BIND_PROTOCOL),
    MKLCBS(LWS_CALLBACK_HTTP_DROP_PROTOCOL),
    MKLCBS(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION),
    MKLCBS(LWS_CALLBACK_RAW_RX_FILE), 
    MKLCBS(LWS_CALLBACK_RAW_WRITEABLE_FILE),
    MKLCBS(LWS_CALLBACK_RAW_CLOSE_FILE),
    MKLCBS(LWS_CALLBACK_USER),
#endif
#if (LWS_LIBRARY_VERSION_NUMBER>=3000000)
    MKLCBS(LWS_CALLBACK_SSL_INFO),
    MKLCBS(LWS_CALLBACK_CGI_PROCESS_ATTACH),
................................................................................
    return true;
}

static Jsi_RC jsi_wsTemplateFill(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, Jsi_Value *fn, Jsi_DString *dStr,
    int lvl) {
    Jsi_Value *fval;
    Jsi_RC rc = JSI_OK;
    char *cp, *ce, pref[] = "<!--#include file=\"", suffix[] = "\"-->";
    int flen, plen = sizeof(pref)-1, slen = sizeof(suffix)-1;

    Jsi_DString tStr = {};
    const char *cs = "<!-- Markdeep: --><style class=\"fallback\">body{visibility:hidden;}</style>\n"
                "<script>window.markdeepOptions={tocStyle:\"medium\"}</script>\n"
                "<!--#include file=\"$inc\"-->\n"
                "<script src=\"markdeep.min.js\"></script>\n"
                "<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility='visible')</script>";
    char *fs, *fname = Jsi_ValueString(interp, fn, &flen), *fend = fname;
................................................................................
        flen = fs-fname;
        fend = fs+1;
    }
    if (lvl>0) {
        rc = Jsi_FileRead(interp, fn, &tStr);
        cs = Jsi_DSValue(&tStr);
    } else {
        snprintf(fbuf, sizeof(fbuf), "%.*s%s", flen, fname, cmdPtr->templateFile);
        fval = Jsi_ValueNewStringConst(interp, fbuf, -1);
        Jsi_IncrRefCount(interp, fval);
        Jsi_StatBuf sb;
        int n = Jsi_Stat(interp, fval, &sb);
        if (!n && sb.st_size>0) {
            rc = Jsi_FileRead(interp, fval, &tStr);
            cs = Jsi_DSValue(&tStr);
        }
        Jsi_DecrRefCount(interp, fval);
    }
    
    while (rc == JSI_OK && cs) {
        char *ext = NULL;

        cp = Jsi_Strstr(cs, pref);







        if (!cp || !(ce=Jsi_Strstr(cp+plen, suffix))) {
            Jsi_DSAppend(dStr, cs, NULL);
            break;
        }
        Jsi_DSAppendLen(dStr, cs, cp-cs);
        if (cp[plen] == '$' && lvl == 0) {








            snprintf(fbuf, sizeof(fbuf), "%.*spages/%s.md", flen, fname, fend);
        } else {
            cp += plen;
            int elen = ce-cp;
            snprintf(fbuf, sizeof(fbuf), "%.*s/%.*s", flen, fname, elen, cp);
            ext = Jsi_Strrchr(fbuf, '.');
        }
        fval = Jsi_ValueNewStringConst(interp, fbuf, -1);
        Jsi_IncrRefCount(interp, fval);
        if (!ext || Jsi_Strcmp(ext, ".shtml"))
            rc = Jsi_FileRead(interp, fval, dStr);
        else
            rc = jsi_wsTemplateFill(interp, cmdPtr, fval, dStr, lvl+1);
        Jsi_DecrRefCount(interp, fval);
    
        cs = ce + slen;
        if (*cs == '\n')
            cs++;
    }
    Jsi_DSFree(&tStr);
    return rc;
    
}

// Handle http GET/POST
static int jsi_wsHttp(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, struct lws *wsi, void *user,
    struct lws_context *context, const char* inPtr, Jsi_DString *tStr, jsi_wsPss *pss)
{
    const char *ext = NULL;
................................................................................
    bool isMdi = 0;

    /* if a legal POST URL, let it continue and accept data */
    if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
        return 0;
    if (!pss)
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
  
    int uplen=(cmdPtr->urlPrefix?Jsi_Strlen(cmdPtr->urlPrefix):0);
    
    if (inPtr && cmdPtr->urlPrefix && !Jsi_Strncmp(inPtr, cmdPtr->urlPrefix, uplen))
        inPtr += uplen;

    if (cmdPtr->redirDisable) {// Try to defray redirect loops.
        if (difftime(now, cmdPtr->stats.redirLast)>=600)
            cmdPtr->redirDisable = 0;
        else
            cmdPtr->redirDisable--;
    }
    
    if ((cmdPtr->urlRedirect && (inPtr == 0 || *inPtr == 0 || !Jsi_Strcmp(inPtr, "/")) && !cmdPtr->redirDisable)
        && (inPtr = cmdPtr->urlRedirect) && inPtr[0]) {
        cmdPtr->stats.redirCnt++;
        // TODO: system time change can disrupt the following.
        if (cmdPtr->redirMax>0 && !cmdPtr->redirDisable && cmdPtr->redirMax>0 && cmdPtr->stats.redirLast
            && difftime(now, cmdPtr->stats.redirLast)<600 && ++cmdPtr->redirAllCnt>cmdPtr->redirMax)
            cmdPtr->redirDisable = 100;
................................................................................
                    vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
                    Jsi_IncrRefCount(interp, vpargs);
                    Jsi_Value *ret = Jsi_ValueNew1(interp);
                    bool rb = 0;
                    rc = Jsi_FunctionInvoke(interp, cmdPtr->onAuth, vpargs, &ret, NULL);
                    if (rc == JSI_OK)
                        rb = !Jsi_ValueIsFalse(interp, ret);
                        
                    Jsi_DecrRefCount(interp, vpargs);
                    Jsi_DecrRefCount(interp, ret);

                    if (rc != JSI_OK) {
                        Jsi_LogError("websock bad rcv eval");
                        return -1;
                    }
                    ok = rb;
                }        
            }
            Jsi_DSFree(&eStr);
            Jsi_DSFree(&bStr);
        }
        if (!ok) {
            const char *realm = (cmdPtr->realm?cmdPtr->realm:"jsish");
            int n = snprintf(buf, sizeof(buf), "Basic realm=\"%s\"", realm);
................................................................................
                    (unsigned char *)buf, n, &p, end))
                return -1;
            if (jsi_wsServeString(pss, wsi, "Password is required to access this page", 401, (char*)buffer, NULL)<0)
                return -1;
            return lws_http_transaction_completed(wsi);
        }
    }
    
    if (cmdPtr->onGet || pss->onGet) {
        Jsi_RC jrc;
        int rrv = 1;
        if (cmdPtr->getRegexp) {
            rrv = 0;
            jrc = Jsi_RegExpMatch(interp, cmdPtr->getRegexp, inPtr, &rrv, NULL);
            if (jrc != JSI_OK)
................................................................................
                case JSI_CONTINUE: inPtr = Jsi_DSValue(tStr); break;
                case JSI_BREAK: break;
                default: break;
            }
        }
    }
    ext = Jsi_Strrchr(inPtr, '.');
    
    Jsi_Value *rdir = (pss->rootdir?pss->rootdir:cmdPtr->rootdir);
    const char *rootDir = (rdir?Jsi_ValueString(cmdPtr->interp, rdir, NULL):"./");
    char statPath[PATH_MAX];
#if 0
    if (!Jsi_Strncmp(inPtr, "/jsi/", 5)) {
        // Get the path for system files, eg /zvfs or /usr/local/lib/jsi
        const char *loadFile = NULL;
................................................................................
            /* Lookup mime type in mimeTypes object. */
            Jsi_Value *mVal = Jsi_ValueObjLookup(interp, cmdPtr->mimeTypes, ext+1, 1);
            if (mVal)
                mime = Jsi_ValueString(interp, mVal, NULL);
        }
        if (!mime) {
            static const char* mtypes[] = {
                "html", "text/html", "js", "application/x-javascript", 
                "css", "text/css", "png", "image/png", "ico", "image/icon",
                "gif", "image/gif", "jpeg", "image/jpeg", 
                "jpg", "image/jpeg", "svg", "image/svg+xml",
                "json", "application/json", "txt", "text/plain",
                "jsi", "application/x-javascript", "cssi", "text/css",  
                0, 0
            };
            mime = "text/html";
            int i;
            for (i=0; mtypes[i]; i+=2)
                if (tolower(*eext) == mtypes[i][0] && !Jsi_Strncasecmp(eext, mtypes[i], -1)) {
                    mime = mtypes[i+1];
                    break;
                }
        }
        
        if (!Jsi_Strncasecmp(eext,"shtml", -1))
            essi = 1;
        
        if ((hPtr = Jsi_HashEntryFind(cmdPtr->handlers, ext)) && !cmdPtr->deleted) {
            /* Use interprete html eg. using jsi_wpp preprocessor */
            Jsi_DString jStr = {};
            Jsi_Value *vrc = NULL;
            int hrc = 0, strLen, evrc, isalloc=0;
            char *vStr, *hstr = NULL;
            jsi_wsHander *hdlPtr = (jsi_wsHander*)Jsi_HashValueGet(hPtr);
            Jsi_Value *hv = hdlPtr->val;
            
            if (Jsi_Strchr(buf, '\'') || Jsi_Strchr(buf, '\"')) {
                jsi_wsServeString(pss, wsi, "Can not handle quotes in url", 404, NULL, NULL);    
                return -1;                
            }
            cmdPtr->handlersPkg=1;
            
            // Attempt to load package and get function.
            if ((hdlPtr->flags&1) && cmdPtr->handlersPkg && Jsi_ValueIsString(interp, hv)
                && ((hstr = Jsi_ValueString(interp, hv, NULL)))) {
                vrc = Jsi_NameLookup(interp, hstr);
                if (!vrc) {
                    Jsi_Number pver = Jsi_PkgRequire(interp, hstr, 0);
                    if (pver >= 0)
                        vrc = Jsi_NameLookup(interp, hstr);
                }
                if (!vrc || !Jsi_ValueIsFunction(interp, vrc)) {
                    if (vrc)
                        Jsi_DecrRefCount(interp, vrc);
                    Jsi_LogError("Failed to autoload handle: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to autoload handler", 404, NULL, NULL);    
                    return -1;                
                }
                if (hdlPtr->val)
                    Jsi_DecrRefCount(interp, hdlPtr->val);
                hdlPtr->val = vrc;
                Jsi_IncrRefCount(interp, vrc);
                hv = vrc;
            }
            
            if ((hdlPtr->flags&2) && !hdlPtr->triedLoad && !hdlPtr->objVar && Jsi_ValueIsFunction(interp, hv)) {
                // Run command and from returned object get the parse function.
                hdlPtr->triedLoad = 1;
                Jsi_DSAppend(&jStr, "[null", NULL); 
                if (hdlPtr->arg)
                    Jsi_DSAppend(&jStr, ", ", hdlPtr->arg, NULL); // TODO: JSON encode.
                Jsi_DSAppend(&jStr, "]", NULL);
                vrc = Jsi_ValueNew1(interp);
                evrc = Jsi_FunctionInvokeJSON(interp, hv, Jsi_DSValue(&jStr), &vrc);
                if (Jsi_InterpGone(interp))
                    return -1;
                if (evrc != JSI_OK || !vrc || !Jsi_ValueIsObjType(interp, vrc, JSI_OT_OBJECT)) {
                    Jsi_LogError("Failed to load obj: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to load obj", 404, NULL, NULL);    
                    return -1;                
                }
                Jsi_Value *fvrc = Jsi_ValueObjLookup(interp, vrc, "parse", 0);
                if (!fvrc || !Jsi_ValueIsFunction(interp, fvrc)) {
                    Jsi_LogError("Failed to find parse: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to find parse", 404, NULL, NULL);    
                    return -1;                
                }
                hdlPtr->objVar = fvrc;
                Jsi_IncrRefCount(interp, fvrc);
                hv = vrc;
                
            }

            if (hdlPtr->objVar) {  // Call the obj.parse function.
                Jsi_DSAppend(&jStr, "[\"", buf, "\"]", NULL); // TODO: JSON encode.
                vrc = Jsi_ValueNew1(interp);
                evrc = Jsi_FunctionInvokeJSON(interp, hdlPtr->objVar, Jsi_DSValue(&jStr), &vrc);
                isalloc = 1;
................................................................................
            // Take result from vrc and return it.
            if (evrc != JSI_OK) {
                Jsi_LogError("failure in websocket handler");
            } else if ((!vrc) ||
                (!(vStr = Jsi_ValueString(interp, vrc, &strLen)))) {
                Jsi_LogError("failed to get result");
            } else {
                hrc = jsi_wsServeString(pss, wsi, vStr, 0, NULL, NULL);     
            }
            Jsi_DSFree(&jStr);
            if (isalloc)
                Jsi_DecrRefCount(interp, vrc);
            if (hrc<=0)
                return -1;
            return 1;
................................................................................
    if (!buf[0]) {
        if (cmdPtr->debug)
            fprintf(stderr, "empty file: %s\n", inPtr);
        return -1;
    }
    fname = Jsi_ValueNewStringDup(interp, buf);
    Jsi_IncrRefCount(interp, fname);
    
    Jsi_DString hStr = {};
    Jsi_StatBuf jsb;
    bool native = Jsi_FSNative(interp, fname);
    if (!ext || essi) {
        isMdi = 1;
        goto serve;
    }
................................................................................
        Jsi_DecrRefCount(interp, fname);
        goto done;
    }

serve:
    n = 0;
    // TODO: add automatic cookie mgmt?
/*      
    if (!strcmp((const char *)in, "/") &&
       !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
        gettimeofday(&tv, NULL);
        n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000",
            (unsigned int)tv.tv_sec,
            (unsigned int)tv.tv_usec);

................................................................................
                    (uchar *) stsStr,
                    sizeof(stsStr)-1, &p, (uchar *)buffer + sizeof(buffer)))
        goto bail;
    n = p - buffer;
    if (n>0)
        Jsi_DSAppendLen(&hStr, (char*)buffer, n);
    p = buffer;
    
    if (isgzip) {
        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING,
                    (unsigned char *)"gzip", n, &p, end))
            goto bail;
    }
    if (cmdPtr->headers && !jsi_wsAddHeader(interp, cmdPtr, wsi, cmdPtr->headers, &hStr))
        goto bail;
        
    if (pss->headers && !jsi_wsAddHeader(interp, cmdPtr, wsi, pss->headers, &hStr))
        goto bail;

    n = Jsi_DSLength(&hStr);
    
    if (native && !isMdi) {
        Jsi_DecrRefCount(interp, fname);

        int hrc = lws_serve_http_file(wsi, buf, mime, Jsi_DSValue(&hStr), Jsi_DSLength(&hStr));
        if (hrc<0) {
            if (cmdPtr->noWarn==0)
                fprintf(stderr, "can not serve file (%d): %s\n", hrc, buf);
................................................................................
    return rc;

bail:
    rc = 1;
    goto done;
}

static Jsi_RC jsi_wsrecv_callback(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss, 
    const char *inPtr, int nlen, bool isClose)
{
    Jsi_Value *vpargs, *vargs[10];
    Jsi_Value* func = NULL;
    if (Jsi_InterpGone(interp) || (cmdPtr->deleted && !isClose)) return JSI_ERROR;
    int n = 0;
    if (isClose)
................................................................................
    vargs[n++] = (cmdPtr->deleted?Jsi_ValueNewNull(interp):Jsi_ValueNewObj(interp, cmdPtr->fobj));
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss?pss->wid:0));
    if (!isClose) {
        if (nlen<=0)
            return JSI_OK;
        vargs[n++]  = Jsi_ValueNewBlob(interp, (uchar*)inPtr, nlen);
        if ((cmdPtr->echo||(pss && pss->echo)) && inPtr)
            Jsi_LogInfo("WS-RECV: %s\n", inPtr); 
    }
    vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, vargs, n, 0));
    Jsi_IncrRefCount(interp, vpargs);
    
    Jsi_Value *ret = Jsi_ValueNew1(interp);
    Jsi_ValueMakeUndef(interp, &ret);
    Jsi_RC rc;
        rc = Jsi_FunctionInvoke(interp, func, vpargs, &ret, NULL);
    if (rc == JSI_OK && Jsi_ValueIsUndef(interp, ret)==0 && !isClose) {
        /* TODO: should we handle callback return data??? */
    }
................................................................................
    jsi_wsPss *pss = (typeof(pss))data;
    jsi_wsCmdObj *cmdPtr = pss->cmdPtr;
    Jsi_Value* callPtr = (pss->onUpload?pss->onUpload:cmdPtr->onUpload);
    Jsi_Interp *interp = cmdPtr->interp;
    const char *str;
    int slen, n = 0;
    if (cmdPtr->deleted) return -1;
    
    Jsi_Obj *oarg1;
    Jsi_Value *vpargs, *vargs[10];
    if (state == LWS_UFS_OPEN)
        pss->file_length = 0;
    //id:number, filename:string, data:string, startpos:number, complete:boolean
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
................................................................................
    Jsi_Interp *interp = cmdPtr->interp;
    Jsi_Value* callPtr = NULL;
    int rc = 0, deflt = 0;

    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;
    
    if (cmdPtr->debug>=128)
        fprintf(stderr, "HTTP CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));

    switch (reason) {
#ifndef EXTERNAL_POLL
    case LWS_CALLBACK_GET_THREAD_ID:
    case LWS_CALLBACK_UNLOCK_POLL:
................................................................................
#endif

    default:
        deflt = 1;
        break;

    }
    
    if (deflt && cmdPtr->debug>16 && cmdPtr->debug<128) {
        fprintf(stderr, "HTTP CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
    }
        
    switch (reason) {
    case LWS_CALLBACK_WSI_DESTROY:
        break;

#if (LWS_LIBRARY_VERSION_MAJOR>1)    
    // Handle GET file download in client mode.
    case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: {
        char buffer[1024 + LWS_PRE];
        char *px = buffer + LWS_PRE;
        int lenx = sizeof(buffer) - LWS_PRE;

        if (lws_http_client_read(wsi, &px, &lenx) < 0)
................................................................................
            return -1;
        break;
    }
    case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
        if (jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 0) != JSI_OK)
            rc = 1;
        break;
        
    case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
        if (jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1) != JSI_OK)
            rc = 1;
        break;
        
    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
        if (cmdPtr->post) {
            unsigned char **p = (unsigned char **)in, *end = (*p) + len;
            int n = 0;
            char buf[100];
            Jsi_ValueString(interp, cmdPtr->post, &n);
            snprintf(buf, sizeof(buf), "%d", n);
................................................................................
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 1);
        if (pss)
            jsi_wsdeletePss(pss);
        break;
    case LWS_CALLBACK_WSI_CREATE:
        break;
        
    case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
        break;
        
    case LWS_CALLBACK_FILTER_HTTP_CONNECTION:
        if (cmdPtr->debug>1)
            fprintf(stderr, "FILTER CONNECTION: %s\n", inPtr);
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        jsi_wsgetUriArgValue(interp, wsi, &pss->query);
        
        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }
            
        Jsi_DSSetLength(&pss->dHdrs, 0);
        pss->hdrNum = jsi_wsGetHeaders(pss, wsi, &pss->dHdrs, pss->hdrSz, sizeof(pss->hdrSz)/sizeof(int));
    
        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            // 4 args: ws, id, url, bool
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);            
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 1);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onFilter, vpargs, &ret, NULL);
................................................................................
        client_ip[0] = 0;
        lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), client_name,
                                         sizeof(client_name), client_ip, sizeof(client_ip));
        if (client_name[0])
            cmdPtr->clientName = Jsi_KeyAdd(interp, client_name);
        if (client_ip[0])
            cmdPtr->clientIP = Jsi_KeyAdd(interp, client_ip);
        
        if (cmdPtr->clientName || cmdPtr->clientIP) {
            const char *loname = cmdPtr->localhostName;
            if (!loname) loname = "localhost";
            cmdPtr->instCtx = context;
            if (cmdPtr->debug>1)
                fprintf(stderr,  "Received network connect from %s (%s)\n",
                     cmdPtr->clientName, cmdPtr->clientIP);
................................................................................
        if (rc<0)
            return -1;
        if (rc==1) {
            goto try_to_reuse;
        }
        break;
    }
    
#if (LWS_LIBRARY_VERSION_MAJOR>1)
    case LWS_CALLBACK_HTTP_BODY: {
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 1);
        if (!pss) break;
        callPtr = (pss->onUpload?pss->onUpload:cmdPtr->onUpload);
        if (cmdPtr->maxUpload<=0 || !callPtr) {
................................................................................
        res = Jsi_DSValue(&pss->resultStr);
        if (!res[0]) {
            if (!pss->resultCode)
                res = "<html><body>Upload complete</body></html>";
            else
                res = "<html><body>Upload error</body></html>";
        }
        jsi_wsServeString(pss, wsi, res, pss->resultCode==JSI_OK?0:500, NULL, NULL);        
        if (cmdPtr->maxUpload<=0 || !callPtr) {
            if (cmdPtr->noWarn==0)
                fprintf(stderr, "Upload disabled: maxUpload=%d, onUpload=%p\n", cmdPtr->maxUpload, callPtr);
            return -1;
        }
        cmdPtr->stats.uploadEnd = pss->stats.uploadEnd = time(NULL);
        lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
................................................................................
    }

    default:
        break;
    }

    goto doret;
    
try_to_reuse:
    if (lws_http_transaction_completed(wsi))
         rc = -1;
    else
        rc = 0;
    goto doret;
      
doret:
    if (cmdPtr->debug>2)
        fprintf(stderr, "<---HTTP RET = %d\n", rc);
    return rc;
}

static int
................................................................................
    }
    Jsi_Interp *interp = cmdPtr->interp;
    char *inPtr = (char*)in;
    int sLen, n, rc =0;
    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;
    
    if (cmdPtr->debug>=32) {
        switch (reason) {
            case LWS_CALLBACK_SERVER_WRITEABLE:
            case LWS_CALLBACK_CLIENT_WRITEABLE:
                break;
            default:
                fprintf(stderr, "WS CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
        }
    }
        
    switch (reason) {
    case LWS_CALLBACK_PROTOCOL_INIT:
        if (cmdPtr->noWebsock)
            return 1;
        break;
        
    case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }
        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            if (!pss)
                pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 0);
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);
            
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 0);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
................................................................................
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            
            Jsi_Value *ret = Jsi_ValueNew1(interp);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onOpen, vpargs, &ret, NULL);

            Jsi_DecrRefCount(interp, vpargs);
            Jsi_DecrRefCount(interp, ret);
            if (rc != JSI_OK) 
                return Jsi_LogError("websock bad rcv eval");
        }        
        break;

    case LWS_CALLBACK_WSI_DESTROY:
        break;
        
    case LWS_CALLBACK_CLOSED:
    case LWS_CALLBACK_PROTOCOL_DESTROY:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;
        if (cmdPtr->onClose || pss->onClose) {
            rc = jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1);
            if (rc != JSI_OK) 
                return Jsi_LogError("websock bad rcv eval");
        }        
        jsi_wsdeletePss(pss);
        if (cmdPtr->stats.connectCnt<=0 && cmdPtr->onCloseLast && !Jsi_InterpGone(interp)) {
            Jsi_RC jrc;
            Jsi_Value *retStr = Jsi_ValueNew1(interp);
            // 1 args: ws
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
................................................................................
        pss->stats.msgQLen--;
        pss->state = PWS_SENT;
        p = (unsigned char *)data+LWS_PRE;
        sLen = Jsi_Strlen((char*)p);
        n = jsi_wswrite(pss, wsi, p, sLen, (pss->stats.isBinary?LWS_WRITE_BINARY:LWS_WRITE_TEXT));
        if (cmdPtr->debug>=10)
            fprintf(stderr, "WS:CLIENT WRITE(%p): %d=>%d\n", pss, sLen, n);
                               
        if (n >= 0) {
            cmdPtr->stats.sentCnt++;
            cmdPtr->stats.sentLast = time(NULL);
            pss->stats.sentCnt++;
            pss->stats.sentLast = time(NULL);
        } else {
            lwsl_err("ERROR %d writing to socket\n", n);
................................................................................
            pss->stats.sentErrLast = time(NULL);
            cmdPtr->stats.sentErrCnt++;
            cmdPtr->stats.sentErrLast = time(NULL);
            rc = 1;
        }
        break;
    }
        
    case LWS_CALLBACK_CLIENT_RECEIVE:
    case LWS_CALLBACK_RECEIVE:
    {
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;

        pss->stats.recvCnt++;
................................................................................
            if (rc != JSI_OK) {
                Jsi_LogError("websock bad rcv eval");
                return 1;
            }
        }
        lws_callback_on_writable_all_protocol(cmdPtr->context, lws_get_protocol(wsi));
        break;
 
    }
    default:
        break;
    }
    return rc;
}


static Jsi_RC WebSocketConfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
  
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_Value *opts = Jsi_ValueArrayIndex(interp, args, 0);
    if (cmdPtr->noConfig && opts && !Jsi_ValueIsString(interp, opts))
        return Jsi_LogError("WebSocket conf() is disabled for set");
    return Jsi_OptionsConf(interp, WSOptions, cmdPtr, opts, ret, 0);

}

static Jsi_RC WebSocketIdCmdOp(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr, int op)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_Value *valPtr = Jsi_ValueArrayIndex(interp, args, 0);
    Jsi_Number vid;
    if (Jsi_ValueGetNumber(interp, valPtr, &vid) != JSI_OK || vid < 0) 
        return Jsi_LogError("Expected connection number id");
    int id = (int)vid;
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
................................................................................
        WSSIGASSERT(tpss, PWS);
        if (tpss->wid == id && tpss->state != PWS_DEAD) {
            pss = tpss;
            break;
        }
    }

    if (!pss) 
        return Jsi_LogError("No such id: %d", id);
    switch (op) {
        case 0: return Jsi_OptionsConf(interp, WPSOptions, pss, Jsi_ValueArrayIndex(interp, args, 1), ret, 0);
        case 1: 
            jsi_wsDumpHeaders(cmdPtr, pss, Jsi_ValueArrayIndexToStr(interp, args, 1, NULL), ret);
            break;
        case 2: 
            if (!pss->spa) return JSI_OK;
            jsi_wsDumpQuery(cmdPtr, pss, Jsi_ValueArrayIndexToStr(interp, args, 1, NULL), ret);
            break;
    }
    return JSI_OK;
}

................................................................................
}


static Jsi_RC WebSocketIdsCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_DString dStr = {"["};
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    int cnt = 0;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
................................................................................
If a cmd is a function, it is called with a single arg: the file name.")
static Jsi_RC WebSocketHandlerCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_HashEntry *hPtr;
    jsi_wsHander *hdlPtr;
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    WSSIGASSERT(cmdPtr, OBJ);
    int argc = Jsi_ValueGetLength(interp, args);
    if (argc == 0) {
        Jsi_HashSearch search;
        Jsi_Obj* obj = Jsi_ObjNew(interp);
        for (hPtr = Jsi_HashSearchFirst(cmdPtr->handlers, &search); hPtr; hPtr = Jsi_HashSearchNext(&search)) {
................................................................................
            Jsi_DecrRefCount(interp, hdlPtr->val);
        Jsi_HashValueSet(hPtr, NULL);
        Jsi_HashEntryDelete(hPtr);
        Jsi_Free(hdlPtr);
        Jsi_ValueMakeStringDup(interp, ret, key);
        return JSI_OK;
    }
    if (Jsi_ValueIsFunction(interp, valPtr)==0 && Jsi_ValueIsString(interp, valPtr)==0) 
        return Jsi_LogError("expected string, function or null");
    Jsi_Value *argPtr = Jsi_ValueArrayIndex(interp, args, 2);
    if (argPtr) {
        if (Jsi_ValueIsNull(interp, argPtr))
            argPtr = NULL;
        else if (!Jsi_ValueIsString(interp, argPtr)) 
            return Jsi_LogError("expected a string");
    }
    hPtr = Jsi_HashEntryNew(cmdPtr->handlers, key, NULL);
    if (!hPtr)
        return JSI_ERROR;
    hdlPtr = (jsi_wsHander *)Jsi_Calloc(1, sizeof(*hdlPtr));
    Jsi_Value *flagPtr = Jsi_ValueArrayIndex(interp, args, 1);
................................................................................
#define FN_wssend JSI_INFO("\
Send a message to one (or all connections if -1). If not already a string, msg is formatted as JSON prior to the send.")

static Jsi_RC WebSocketSendCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    WSSIGASSERT(cmdPtr, OBJ);
    jsi_wsPss *pss;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 1);
    char *str = Jsi_ValueString(interp, arg, NULL);
    int id = -1, argc = Jsi_ValueGetLength(interp, args);
    Jsi_DString eStr = {};
    if (argc!=2)
        return Jsi_LogError("wrong args");
    Jsi_Number dnum;
    Jsi_Value *darg = Jsi_ValueArrayIndex(interp, args, 0);
    if (Jsi_ValueGetNumber(interp, darg, &dnum) != JSI_OK) 
        return Jsi_LogError("invalid id");
    id = (int)dnum;

    if (!str)
        str = (char*)Jsi_ValueGetDString(interp, arg, &eStr, JSI_OUTPUT_JSON);

    if (cmdPtr->echo)
        Jsi_LogInfo("WS-SEND: %s\n", str); 

    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
        pss = (jsi_wsPss*)Jsi_HashValueGet(hPtr);
        WSSIGASSERT(pss, PWS);
        if ((id<0 || pss->wid == id) && pss->state != PWS_DEAD) {
            if (!pss->stack)
                pss->stack = Jsi_StackNew();
            char *msg = (char*)Jsi_Malloc(LWS_PRE + Jsi_Strlen(str) + 1);
            Jsi_Strcpy(msg + LWS_PRE, str);
            Jsi_StackPush(pss->stack, msg);
            pss->stats.msgQLen++;
            if (!cmdPtr->echo && pss->echo)
                Jsi_LogInfo("WS-SEND: %s\n", str); 
        }
    }
   
    Jsi_DSFree(&eStr);
    return JSI_OK;
}

static Jsi_RC jsi_wsrecv_flush(jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss)
{
    int nlen = Jsi_DSLength(&pss->recvBuf);
................................................................................
    return n;
}

static Jsi_RC WebSocketUpdateCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply to non-websock object");
    if (!cmdPtr->noUpdate)
        jsi_wsService(cmdPtr);
    return JSI_OK;
}

static Jsi_RC jsi_wswebsockUpdate(Jsi_Interp *interp, void *data)
................................................................................

static Jsi_RC jsi_wswebsocketObjFree(Jsi_Interp *interp, void *data)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)data;
    WSSIGASSERT(cmdPtr,OBJ);
    cmdPtr->deleted = 1;
    struct lws_context *ctx = cmdPtr->context;
    if (ctx) 
        lws_context_destroy(ctx);
    cmdPtr->context = NULL;
    jsi_wswebsocketObjErase(cmdPtr);
    _JSI_MEMCLEAR(cmdPtr);
    Jsi_Free(cmdPtr);
    return JSI_OK;
}
................................................................................
    return JSI_OK;
}

static Jsi_RC WebSocketStatusCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply to non-websock object");
#ifndef OMIT_LWS_WITH_SERVER_STATUS
    char cbuf[JSI_BUFSIZ*2];
    lws_json_dump_context(cmdPtr->context, cbuf, sizeof(cbuf), 0);
    return Jsi_JSONParse(interp, cbuf, ret, 0);
#else
    return Jsi_LogError("unsupported");
................................................................................
}

#define FN_WebSocket JSI_INFO("\
Create a websocket server/client object.  The server serves out pages to a web browser,\n\
which can use javascript to upgrade connection to a bidirectional websocket.")
static Jsi_RC WebSocketConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);
    

static Jsi_CmdSpec websockCmds[] = {
    { "WebSocket",  WebSocketConstructor, 0,  1, "options:object=void", .help="Create websocket server/client object", .retType=(uint)JSI_TT_USEROBJ, .flags=JSI_CMD_IS_CONSTRUCTOR, .info=FN_WebSocket, .opts=WSOptions },
    { "conf",       WebSocketConfCmd,     0,  1, "options:string|object=void",.help="Configure options", .retType=(uint)JSI_TT_ANY, .flags=0, .info=0, .opts=WSOptions },
    { "handler",    WebSocketHandlerCmd,  0,  4, "extension:string=void, cmd:string|function=void, arg:string|null=void, flags:number=0", 
        .help="Get/Set handler command for an extension", .retType=(uint)JSI_TT_FUNCTION|JSI_TT_ARRAY|JSI_TT_STRING|JSI_TT_VOID, .flags=0, .info=FN_wshandler },
    { "ids",        WebSocketIdsCmd,      0,  0, "", .help="Return list of ids", .retType=(uint)JSI_TT_ARRAY},
    { "idconf",     WebSocketIdConfCmd,   1,  2, "id:number, options:string|object=void",.help="Configure options for connect id", .retType=(uint)JSI_TT_ANY, .flags=0, .info=0, .opts=WPSOptions },
    { "header",     WebSocketHeaderCmd,   1,  2, "id:number, name:string=void",.help="Get one or all input headers for connect id", .retType=(uint)JSI_TT_STRING|JSI_TT_OBJECT|JSI_TT_VOID },
    { "query",      WebSocketQueryCmd,    1,  2, "id:number, name:string=void",.help="Get one or all query values for connect id", .retType=(uint)JSI_TT_STRING|JSI_TT_OBJECT|JSI_TT_VOID },
    { "send",       WebSocketSendCmd,     2,  2, "id:number, data:any", .help="Send a websocket message to id", .retType=(uint)JSI_TT_VOID, .flags=0, .info=FN_wssend },
    { "status",     WebSocketStatusCmd,   0,  0, "", .help="Return libwebsocket server status", .retType=(uint)JSI_TT_OBJECT|JSI_TT_VOID},
................................................................................
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (Jsi_InterpAccess(interp, NULL, JSI_INTACCESS_NETWORK ) != JSI_OK)
        return Jsi_LogError("WebSocket disallowed by Interp.noNetwork option");
    jsi_wsCmdObj *cmdPtr;
    Jsi_Value *toacc = NULL;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 0);
    
    cmdPtr = (jsi_wsCmdObj*)Jsi_Calloc(1, sizeof(*cmdPtr));
    cmdPtr->sig = JWS_SIG_OBJ;
    cmdPtr->port = 8080;
    cmdPtr->formParams = jsi_wsparam_str;
    cmdPtr->maxUpload = 100000;
    cmdPtr->interp = interp;
    cmdPtr->ietf_version = -1;
    cmdPtr->bufferPwr2 = 0;
    cmdPtr->ws_gid = -1;
    cmdPtr->ws_uid = -1;
    cmdPtr->startTime = time(NULL);
    cmdPtr->hasOpts = 1;
    cmdPtr->templateFile = "template.shtml";
    if ((arg != NULL && !Jsi_ValueIsNull(interp,arg))
        && Jsi_OptionsProcess(interp, WSOptions, cmdPtr, arg, 0) < 0) {
bail:
        jsi_wswebsocketObjFree(interp, cmdPtr);
        return JSI_ERROR;
    }
    if (cmdPtr->headers && (Jsi_ValueGetLength(interp, cmdPtr->headers)%2)) {
................................................................................
    if (cmdPtr->context == NULL) {
fail:
        Jsi_LogError("libwebsocket init failed on port %d (try another port?)", cmdPtr->info.port);
        goto bail;
    }
    if (cmdPtr->info.options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) {
        cmdPtr->info.options &= ~LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
        if (!lws_create_vhost(cmdPtr->context, &cmdPtr->info)) 
            goto fail;
    }
        
    if (cmdPtr->client) {
        struct lws_client_connect_info lci = {};
        lci.context = cmdPtr->context;
        lci.address = cmdPtr->address ? Jsi_ValueString(cmdPtr->interp, cmdPtr->address, NULL) : "127.0.0.1";
        lci.port = cmdPtr->port;
        lci.ssl_connection = cmdPtr->use_ssl;
        lci.path = cmdPtr->rootdir?Jsi_ValueString(cmdPtr->interp, cmdPtr->rootdir, NULL):"/";
................................................................................
        lci.ietf_version_or_minus_one = cmdPtr->ietf_version;
#if (LWS_LIBRARY_VERSION_MAJOR>1)
        if (cmdPtr->post)
            lci.method = "POST";
        else if (!Jsi_Strcmp(subprot, "get"))
            lci.method = "GET";
#endif
        
        if (NULL == lws_client_connect_via_info(&lci))
        {
            Jsi_LogError("websock connect failed");
            jsi_wswebsocketObjFree(interp, cmdPtr);
            return JSI_ERROR;
        }
    } else if (cmdPtr->port == 0) {
        // Extract actually used port.
        char *cp, cbuf[JSI_BUFSIZ*2];
        cbuf[0] = 0;
        lws_json_dump_context(cmdPtr->context, cbuf, sizeof(cbuf), 0);
        cp = Jsi_Strstr(cbuf, "\"port\":\"");
        if (cp) 
            cmdPtr->port = atoi(cp+8);
    }

    cmdPtr->event = Jsi_EventNew(interp, jsi_wswebsockUpdate, cmdPtr);
    if (Jsi_FunctionIsConstructor(funcPtr)) {
        toacc = _this;
    } else {
................................................................................
        jsi_wsHandlerAdd(interp, cmdPtr, ".htmli", "Jsi_Htmlpp",   jsi_wsStrValGet(cmdPtr, "htmli"), 1);
        jsi_wsHandlerAdd(interp, cmdPtr, ".cssi",  "Jsi_Csspp",    jsi_wsStrValGet(cmdPtr, "cssi"),  1);
        jsi_wsHandlerAdd(interp, cmdPtr, ".mdi",   "Jsi_Markdeep", jsi_wsStrValGet(cmdPtr, "mdi"),   1);
    }
    cmdPtr->fobj = fobj;
#ifdef LWS_LIBRARY_VERSION_NUMBER
    Jsi_JSONParseFmt(interp, &cmdPtr->version, "{libVer:\"%s\", hdrVer:\"%s\", hdrNum:%d, pkgVer:%d}",
        (char *)lws_get_library_version(), LWS_LIBRARY_VERSION, LWS_LIBRARY_VERSION_NUMBER, jsi_WsPkgVersion); 
#endif
    return JSI_OK;
}

static Jsi_RC Jsi_DoneWebSocket(Jsi_Interp *interp)
{
    Jsi_UserObjUnregister(interp, &websockobject);
................................................................................
        return Jsi_DoneWebSocket(interp);
#ifdef LWS_OPENSSL_SUPPORT
    Jsi_InterpAccess(interp, NULL, JSI_INTACCESS_SETSSL )
#endif
    Jsi_Hash *wsys;
    const char *libver = lws_get_library_version();
    int lvlen = sizeof(LWS_LIBRARY_VERSION)-1;
    if (Jsi_Strncmp(libver, LWS_LIBRARY_VERSION, lvlen) || !isspace(libver[lvlen])) 
        return Jsi_LogError("Library version mismatch: HDR:%s != LIB:%s", LWS_LIBRARY_VERSION, lws_get_library_version());
#if JSI_USE_STUBS
  if (Jsi_StubsInit(interp, 0) != JSI_OK)
    return JSI_ERROR;
#endif
    if (Jsi_PkgProvide(interp, "WebSocket", jsi_WsPkgVersion, Jsi_InitWebSocket) != JSI_OK)
        return JSI_ERROR;
................................................................................
}

Jsi_RC jsi_evalStrFile(Jsi_Interp* interp, Jsi_Value *path, char *str, int flags, int level)
{
    Jsi_Channel tinput = NULL, input = Jsi_GetStdChannel(interp, 0);
    Jsi_Value *npath = path;
    Jsi_RC rc = JSI_ERROR;
    bool strict = 0;
    const char *ustr = NULL, *ostr = str;
    if (Jsi_MutexLock(interp, interp->Mutex) != JSI_OK)
        return rc;
    int oldSp, uskip = 0, fncOfs = 0;
    int oldef = interp->evalFlags;
    jsi_Pstate *oldps = interp->ps;
    const char *oldFile = interp->curFile;
    char *origFile = Jsi_ValueString(interp, path, NULL);
    const char *fname = origFile;
    char *oldDir = interp->curDir;
    char dirBuf[PATH_MAX];
    jsi_Pstate *ps = NULL;

    bool oldStrict = interp->framePtr->strict;
    jsi_FileInfo *fi = NULL;
    int exists = (flags&JSI_EVAL_EXISTS);
    int ignore = (flags&JSI_EVAL_ERRIGNORE);
    if (flags & JSI_EVAL_GLOBAL)
        level = 1;
    if (flags & JSI_EVAL_ISMAIN)






|







 







>







 







>
>







 







|







 







|







 







>
>
>
>
>
>
>
>
>







 







|
<







 







|







 







|







 







|







 







|
|







 







|







 







|


|


|







 







|







 







|







 







|







 







|







 







|
|
>







 







|










|

|
>

>
>
>
>
>
>
>






>
>
>
>
>
>
>
>
|



|









|






|







 







|

|









|







 







|








|







 







|







 







|







 







|

|


|










|


|








|

|
|


|













|
|







|



|









|
|




|
|




|







 







|







 







|







 







|







 







|







|




|







 







|







 







|



|







 







|







 







|







 







|



|




|







 







|




|







 







|


|






|




|


|





|







 







|







 







|







 







|







 







|






|







 







|









|





|













|







 







|






|

|




|






|

|







 







|







 







|







 







|












|
|












|



|







 







|



|


|







 







|







 







|







 







|





|







 







|













|







|













|


|







 







|







 







|







 







|







 







|




|







 







|












|







 







|


|







 







|












|







 







|







 







|







 







<












>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
.....
19118
19119
19120
19121
19122
19123
19124
19125
19126
19127
19128
19129
19130
19131
19132
.....
19176
19177
19178
19179
19180
19181
19182
19183
19184
19185
19186
19187
19188
19189
19190
19191
.....
25659
25660
25661
25662
25663
25664
25665
25666
25667
25668
25669
25670
25671
25672
25673
.....
25695
25696
25697
25698
25699
25700
25701
25702
25703
25704
25705
25706
25707
25708
25709
.....
25721
25722
25723
25724
25725
25726
25727
25728
25729
25730
25731
25732
25733
25734
25735
25736
25737
25738
25739
25740
25741
25742
25743
.....
26611
26612
26613
26614
26615
26616
26617
26618

26619
26620
26621
26622
26623
26624
26625
.....
26901
26902
26903
26904
26905
26906
26907
26908
26909
26910
26911
26912
26913
26914
26915
.....
50461
50462
50463
50464
50465
50466
50467
50468
50469
50470
50471
50472
50473
50474
50475
.....
50527
50528
50529
50530
50531
50532
50533
50534
50535
50536
50537
50538
50539
50540
50541
.....
50553
50554
50555
50556
50557
50558
50559
50560
50561
50562
50563
50564
50565
50566
50567
50568
.....
50652
50653
50654
50655
50656
50657
50658
50659
50660
50661
50662
50663
50664
50665
50666
.....
50695
50696
50697
50698
50699
50700
50701
50702
50703
50704
50705
50706
50707
50708
50709
50710
50711
50712
50713
50714
50715
.....
50755
50756
50757
50758
50759
50760
50761
50762
50763
50764
50765
50766
50767
50768
50769
.....
50810
50811
50812
50813
50814
50815
50816
50817
50818
50819
50820
50821
50822
50823
50824
.....
50973
50974
50975
50976
50977
50978
50979
50980
50981
50982
50983
50984
50985
50986
50987
.....
51065
51066
51067
51068
51069
51070
51071
51072
51073
51074
51075
51076
51077
51078
51079
.....
51115
51116
51117
51118
51119
51120
51121
51122
51123
51124
51125
51126
51127
51128
51129
51130
51131
.....
51137
51138
51139
51140
51141
51142
51143
51144
51145
51146
51147
51148
51149
51150
51151
51152
51153
51154
51155
51156
51157
51158
51159
51160
51161
51162
51163
51164
51165
51166
51167
51168
51169
51170
51171
51172
51173
51174
51175
51176
51177
51178
51179
51180
51181
51182
51183
51184
51185
51186
51187
51188
51189
51190
51191
51192
51193
51194
51195
51196
51197
51198
51199
51200
51201
51202
51203
51204
51205
51206
51207
51208
51209
.....
51219
51220
51221
51222
51223
51224
51225
51226
51227
51228
51229
51230
51231
51232
51233
51234
51235
51236
51237
51238
51239
51240
51241
51242
51243
51244
51245
.....
51274
51275
51276
51277
51278
51279
51280
51281
51282
51283
51284
51285
51286
51287
51288
51289
51290
51291
51292
51293
51294
51295
51296
51297
.....
51299
51300
51301
51302
51303
51304
51305
51306
51307
51308
51309
51310
51311
51312
51313
.....
51321
51322
51323
51324
51325
51326
51327
51328
51329
51330
51331
51332
51333
51334
51335
.....
51373
51374
51375
51376
51377
51378
51379
51380
51381
51382
51383
51384
51385
51386
51387
51388
51389
51390
51391
51392
51393
51394
51395
51396
51397
51398
51399
51400
51401
51402
51403
51404
51405
51406
51407
51408
51409
51410
51411
51412
51413
51414
51415
51416
51417
51418
51419
51420
51421
51422
51423
51424
51425
51426
51427
51428
51429
51430
51431
51432
51433
51434
51435
51436
51437
51438
51439
51440
51441
51442
51443
51444
51445
51446
51447
51448
51449
51450
51451
51452
51453
51454
51455
51456
51457
51458
51459
51460
51461
51462
51463
51464
51465
51466
51467
51468
51469
51470
.....
51491
51492
51493
51494
51495
51496
51497
51498
51499
51500
51501
51502
51503
51504
51505
.....
51508
51509
51510
51511
51512
51513
51514
51515
51516
51517
51518
51519
51520
51521
51522
.....
51560
51561
51562
51563
51564
51565
51566
51567
51568
51569
51570
51571
51572
51573
51574
.....
51584
51585
51586
51587
51588
51589
51590
51591
51592
51593
51594
51595
51596
51597
51598
51599
51600
51601
51602
51603
51604
51605
51606
51607
51608
51609
51610
51611
.....
51644
51645
51646
51647
51648
51649
51650
51651
51652
51653
51654
51655
51656
51657
51658
.....
51664
51665
51666
51667
51668
51669
51670
51671
51672
51673
51674
51675
51676
51677
51678
51679
51680
51681
51682
.....
51693
51694
51695
51696
51697
51698
51699
51700
51701
51702
51703
51704
51705
51706
51707
.....
51753
51754
51755
51756
51757
51758
51759
51760
51761
51762
51763
51764
51765
51766
51767
.....
51802
51803
51804
51805
51806
51807
51808
51809
51810
51811
51812
51813
51814
51815
51816
51817
51818
51819
51820
51821
51822
51823
51824
51825
.....
51826
51827
51828
51829
51830
51831
51832
51833
51834
51835
51836
51837
51838
51839
51840
51841
51842
51843
51844
51845
.....
51887
51888
51889
51890
51891
51892
51893
51894
51895
51896
51897
51898
51899
51900
51901
51902
51903
51904
51905
51906
51907
51908
51909
51910
51911
51912
51913
51914
51915
51916
51917
51918
51919
51920
51921
51922
51923
51924
51925
.....
51945
51946
51947
51948
51949
51950
51951
51952
51953
51954
51955
51956
51957
51958
51959
.....
51985
51986
51987
51988
51989
51990
51991
51992
51993
51994
51995
51996
51997
51998
51999
.....
52047
52048
52049
52050
52051
52052
52053
52054
52055
52056
52057
52058
52059
52060
52061
.....
52136
52137
52138
52139
52140
52141
52142
52143
52144
52145
52146
52147
52148
52149
52150
52151
52152
52153
52154
52155
52156
52157
.....
52169
52170
52171
52172
52173
52174
52175
52176
52177
52178
52179
52180
52181
52182
52183
52184
52185
52186
52187
52188
52189
52190
52191
52192
52193
52194
52195
52196
52197
52198
52199
52200
52201
52202
52203
52204
52205
52206
52207
52208
52209
52210
52211
52212
52213
.....
52244
52245
52246
52247
52248
52249
52250
52251
52252
52253
52254
52255
52256
52257
52258
52259
52260
52261
52262
52263
52264
52265
52266
52267
52268
52269
52270
52271
52272
52273
52274
52275
52276
52277
52278
52279
52280
52281
.....
52305
52306
52307
52308
52309
52310
52311
52312
52313
52314
52315
52316
52317
52318
52319
.....
52322
52323
52324
52325
52326
52327
52328
52329
52330
52331
52332
52333
52334
52335
52336
.....
52371
52372
52373
52374
52375
52376
52377
52378
52379
52380
52381
52382
52383
52384
52385
52386
52387
52388
52389
52390
52391
52392
52393
52394
52395
52396
52397
52398
52399
52400
52401
52402
52403
52404
52405
52406
52407
52408
52409
52410
52411
52412
52413
52414
52415
52416
.....
52418
52419
52420
52421
52422
52423
52424
52425
52426
52427
52428
52429
52430
52431
52432
52433
52434
52435
52436
52437
52438
52439
.....
52456
52457
52458
52459
52460
52461
52462
52463
52464
52465
52466
52467
52468
52469
52470
.....
52506
52507
52508
52509
52510
52511
52512
52513
52514
52515
52516
52517
52518
52519
52520
.....
52544
52545
52546
52547
52548
52549
52550
52551
52552
52553
52554
52555
52556
52557
52558
52559
52560
52561
52562
52563
52564
.....
52580
52581
52582
52583
52584
52585
52586
52587
52588
52589
52590
52591
52592
52593
52594
52595
52596
52597
52598
52599
52600
52601
52602
52603
52604
52605
52606
52607
52608
52609
52610
52611
52612
52613
52614
52615
52616
52617
52618
52619
52620
52621
52622
52623
52624
52625
52626
52627
52628
52629
52630
52631
52632
52633
.....
52711
52712
52713
52714
52715
52716
52717
52718
52719
52720
52721
52722
52723
52724
52725
.....
52752
52753
52754
52755
52756
52757
52758
52759
52760
52761
52762
52763
52764
52765
52766
.....
52819
52820
52821
52822
52823
52824
52825
52826
52827
52828
52829
52830
52831
52832
52833
.....
52835
52836
52837
52838
52839
52840
52841
52842
52843
52844
52845
52846
52847
52848
52849
52850
52851
52852
52853
52854
.....
52884
52885
52886
52887
52888
52889
52890
52891
52892
52893
52894
52895
52896
52897
52898
52899
52900
52901
52902
52903
52904
52905
52906
52907
52908
52909
52910
52911
.....
52980
52981
52982
52983
52984
52985
52986
52987
52988
52989
52990
52991
52992
52993
52994
52995
52996
52997
.....
53001
53002
53003
53004
53005
53006
53007
53008
53009
53010
53011
53012
53013
53014
53015
53016
53017
53018
53019
53020
53021
53022
53023
53024
53025
53026
53027
53028
.....
53042
53043
53044
53045
53046
53047
53048
53049
53050
53051
53052
53053
53054
53055
53056
.....
53064
53065
53066
53067
53068
53069
53070
53071
53072
53073
53074
53075
53076
53077
53078
.....
65235
65236
65237
65238
65239
65240
65241

65242
65243
65244
65245
65246
65247
65248
65249
65250
65251
65252
65253
65254
65255
65256
65257
65258
65259
65260
65261
/* jsi.h : External API header file for Jsi. */
#ifndef __JSI_H__
#define __JSI_H__

#define JSI_VERSION_MAJOR   2
#define JSI_VERSION_MINOR   6
#define JSI_VERSION_RELEASE 2

#define JSI_VERSION (JSI_VERSION_MAJOR + ((Jsi_Number)JSI_VERSION_MINOR/100.0) + ((Jsi_Number)JSI_VERSION_RELEASE/10000.0))

#ifndef JSI_EXTERN
#define JSI_EXTERN extern
#endif

................................................................................
            puts("jsish arguments:\n"
              "  -a/--archive FILE\tMount an archive (zip, sqlar or fossil repo) and run module.\n"
              "  -c/--cdata FILE\tGenerate .c or JSON output from a .jsc description.\n"
              "  -C/--cssi FILE\tPreprocess embedded CSS in .css file.\n"
              "  -d/--debug FILE\tRun console debugger on script.\n"
              "  -D/--debugui FILE\tRun web-gui debugger on script.\n"
              "  -e/--eval STRING\tEvaluate a javascript string and exit.\n"
              "  -g/--gendeep FILES\tGenerate html output from markdeep source.\n"
              "  -h/--help\t\tPrint help in short or long form.\n"
              "  -H/--htmli FILE\tPreprocess embedded jsi in .htmli file.\n"
              "  -J/--jsi FILE\t\tPreprocess a .jsi file to typecheck in standard javascript.\n"
              "  -m/--module FILE\tSource file and invoke runModule.\n"
              "  -M/--make FILE\tPreprocess script as a Jsi Makefile.\n"
              "  -s/--safe FILE\tRun script in safe sub-interp.\n"
              "  -S/--sqliteui DBFILE\tRun Sqlite web-gui.\n"
................................................................................
        } else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--safe")==0 || Jsi_Strcmp(argv[1], "-s" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Safe');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--debugui")==0 || Jsi_Strcmp(argv[1], "-D" )==0)) {
            interp->debugOpts.isDebugger = 1;
            rc = Jsi_EvalString(interp, "runModule('Jsi_DebugUI');", JSI_EVAL_ISMAIN);
        } else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--wget")==0 || Jsi_Strcmp(argv[1], "-w" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Wget');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--gendeep")==0 || Jsi_Strcmp(argv[1], "-g" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_GenDeep');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--websrv")==0 || Jsi_Strcmp(argv[1], "-W" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Websrv');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--archive")==0 || Jsi_Strcmp(argv[1], "-a" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Archive');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--sqliteui")==0 || Jsi_Strcmp(argv[1], "-S" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_SqliteUI');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--cdata")==0 || Jsi_Strcmp(argv[1], "-c" )==0))
................................................................................
    char *cp = path;
    *cp = 0;
    for (i=0; i<argc; i++) {
        if (i == 0 && argv[0][0] == 0) {
            continue;
        } else if (argv[i][0] == 0) {
            continue;
        } else if (i && !Jsi_Strcmp(argv[i],".")) {
            continue;
        } else if (!Jsi_Strcmp(argv[i],"..")) {
            char *pcp = Jsi_DSValue(&dStr), *lcp = pcp;
            pcp = Jsi_Strrchr(pcp, '/');
            if (pcp && pcp != Jsi_DSValue(&dStr)) {
                *pcp = 0;
                Jsi_DSSetLength(&dStr, Jsi_Strlen(lcp));
................................................................................
    DeBackSlashify(Jsi_DSValue(cwdPtr));
#endif
    return Jsi_DSValue(cwdPtr);
}

char *Jsi_FileRealpathStr(Jsi_Interp *interp, const char *path, char *newname)
{
    if (!path || !path[0]) return NULL;
    Jsi_DString dStr;
    char *npath = (char*)path, *apath;
    Jsi_DSInit(&dStr);
    if (*path == '~') {
#ifndef __WIN32
        struct passwd pw, *pwp; /* TODO: could fallback to using env HOME. */
        char buf[JSI_BUFSIZ];
................................................................................
    }
#ifdef __WIN32
    if (Jsi_Strncmp(path, JSI_ZVFS_DIR, sizeof(JSI_ZVFS_DIR)-1)==0 || Jsi_Strncmp(path, JSI_VFS_DIR, sizeof(JSI_VFS_DIR)-1)==0)
        apath = NULL;
    else
#endif
    apath = realpath(npath, newname);
    if (!apath) {
        if ((path[0] == '.' && path[1] == '/') || (path[0] != '/' && 
        !(path[0] == '.' && path[1] == '.') && path[1] != ':')) {
            Jsi_GetCwd(interp, &dStr);
            Jsi_DSAppend(&dStr, "/", path, NULL);
            npath = Jsi_DSValue(&dStr);
            apath = realpath(npath, newname);
        }
    }
    if (!apath) {
        if (newname)
            Jsi_Strcpy(newname, apath=npath);
        else
            apath = Jsi_Strdup(npath);
#ifndef __WIN32
        /* If path not exists on unix we try to eliminate ../ and /./ etc.*/
................................................................................

    Jsi_ValueMakeString(interp, ret, Jsi_DSFreeDup(&dStr));
    return JSI_OK;
}
    
#define FN_strreplace JSI_INFO("\
If the replace argument is a function, it is called with match,p1,p2,...,offset,string.  \
If called function is known to have 1 argument, it is called with just the match.")

static Jsi_RC StringReplaceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    /* Now handles perl regex flag extensions.*/
    const char *source_str;
    int source_len, bLen;
    const char *replace_str = NULL;
................................................................................
    { "charAt",     StringCharAtCmd,        1, 1, "index:number", .help="Return char at index", .retType=(uint)JSI_TT_STRING},
    { "charCodeAt", StringCharCodeAtCmd,    1, 1, "index:number", .help="Return char code at index", .retType=(uint)JSI_TT_NUMBER },
    { "concat",     StringConcatCmd,        0,-1, "str:string, ...", .help="Append one or more strings", .retType=(uint)JSI_TT_STRING },
    { "indexOf",    StringIndexOfCmd,       1, 2, "str:string, start:number", .help="Return index of char", .retType=(uint)JSI_TT_NUMBER },
    { "lastIndexOf",StringLastIndexOfCmd,   1, 2, "str:string, start:number", .help="Return index of last char", .retType=(uint)JSI_TT_NUMBER },
    { "match",      StringMatchCmd,         1, 1, "pattern:regexp|string", .help="Return array of matches", .retType=(uint)JSI_TT_ARRAY|JSI_TT_NULL },
    { "map",        StringMapCmd,           1, 2, "strMap:array, nocase:boolean=false", .help="Replaces characters in string based on the key-value pairs in strMap", .retType=(uint)JSI_TT_STRING },
    { "repeat",     StringRepeatCmd,        1, 1, "count:number", .help="Return count copies of string", .retType=(uint)JSI_TT_STRING, .flags=0, .info=0 },
    { "replace",    StringReplaceCmd,       2, 2, "pattern:regexp|string, replace:string|function", .help="Regex/string replacement", .retType=(uint)JSI_TT_STRING, .flags=0, .info=FN_strreplace },
    { "search",     StringSearchCmd,        1, 1, "pattern:regexp|string", .help="Return index of first char matching pattern", .retType=(uint)JSI_TT_NUMBER },
    { "slice",      StringSliceCmd,         1, 2, "start:number, end:number", .help="Return section of string", .retType=(uint)JSI_TT_STRING },
    { "split",      StringSplitCmd,         0, 1, "char:string|null=void", .help="Split on char and return Array: null removes empty elements", .retType=(uint)JSI_TT_ARRAY },
    { "substr",     StringSubstrCmd,        0, 2, "start:number, length:number", .help="Return substring", .retType=(uint)JSI_TT_STRING },
    { "substring",  StringSubstringCmd,     0, 2, "start:number, end:number", .help="Return substring", .retType=(uint)JSI_TT_STRING },
    { "toLocaleLowerCase",StringToLowerCaseCmd,0, 0, "",.help="Lower case", .retType=(uint)JSI_TT_STRING },
................................................................................
    jsi_wsStatData stats;
    char *iface;
    const char* urlPrefix, *urlRedirect;
    const char *localhostName;
    const char *clientName;
    const char *clientIP;
    const char *useridPass;
    const char *realm, *includeFile;
    struct lws_context *instCtx;
    Jsi_Value *getRegexp, *post;
    unsigned int oldus;
    int opts;
    int hasOpts;
    int debug;
    int maxConnects;
................................................................................
    int wid;
    int sfd;
    bool isWebsock, echo;
    const char *clientName;
    const char *clientIP;
    int hdrSz[200]; // Space for up to 100 headers
    int hdrNum;     // Num of above.

    // Pointers to reset.
    Jsi_DString dHdrs; // Store header string here.
    Jsi_Stack *stack;
    Jsi_DString recvBuf; // To buffer recv when recvJSON is true.
    Jsi_Value *onClose, *onFilter, *onRecv, *onUpload, *onGet, *onUnknown, *rootdir, *headers;
    char *lastData;
#if (LWS_LIBRARY_VERSION_MAJOR>1)
................................................................................
typedef struct {
    Jsi_Value *val, *objVar;
    const char *arg;
    int triedLoad;
    int flags;
} jsi_wsHander;

static const char* const jsi_wsparam_names[] = { "text", "send", "file", "upload" };
static const char* jsi_wsparam_str = "text,send,file,upload";

#ifndef jsi_IIOF
#define jsi_IIOF .flags=JSI_OPT_INIT_ONLY
#define jsi_IIRO .flags=JSI_OPT_READ_ONLY
#endif

static Jsi_OptionSpec WPSStats[] =
................................................................................
    JSI_OPT(STRKEY, jsi_wsCmdObj, realm,      .help="Realm for basic auth (jsish)", ),
    JSI_OPT(INT,    jsi_wsCmdObj, recvBufMax, .help="Size limit of a websocket message", jsi_IIOF),
    JSI_OPT(INT,    jsi_wsCmdObj, recvBufTimeout,.help="Timeout for recv of a websock msg", jsi_IIOF),
    JSI_OPT(BOOL,   jsi_wsCmdObj, redirMax,   .help="Temporarily disable redirects when see more than this in 10 minutes"),
    JSI_OPT(STRING, jsi_wsCmdObj, rootdir,    .help="Directory to serve html from (\".\")"),
    JSI_OPT(CUSTOM, jsi_wsCmdObj, stats,      .help="Statistical data", jsi_IIRO, .custom=Jsi_Opt_SwitchSuboption, .data=WPSStats),
    JSI_OPT(TIME_T, jsi_wsCmdObj, startTime,  .help="Time of websocket start", jsi_IIRO),
    JSI_OPT(STRKEY, jsi_wsCmdObj, includeFile, .help="Default file when no extension given (include.shtml)"),
    JSI_OPT(STRKEY, jsi_wsCmdObj, urlPrefix,  .help="Prefix in url to strip from path; for reverse proxy"),
    JSI_OPT(STRKEY, jsi_wsCmdObj, urlRedirect,.help="Redirect when no url or / is given. Must match urlPrefix, if given"),
    JSI_OPT(BOOL,   jsi_wsCmdObj, use_ssl,    .help="Use https (for client)", jsi_IIOF),
    JSI_OPT(STRKEY, jsi_wsCmdObj, useridPass, .help="The USERID:PASSWORD to use for basic authentication"),
    JSI_OPT(OBJ,    jsi_wsCmdObj, version,    .help="WebSocket version info", jsi_IIRO),
    JSI_OPT_END(jsi_wsCmdObj, .help="Websocket options")
};
................................................................................
        return NULL;
    }
    int sid = ((sfd<<1)|ishttp);
    if (create)
        hPtr = Jsi_HashEntryNew(cmdPtr->pssTable, (void*)(intptr_t)sid, &isNew);
    else
        hPtr = Jsi_HashEntryFind(cmdPtr->pssTable, (void*)(intptr_t)sid);

    if (hPtr && !isNew)
        pss = (typeof(pss))Jsi_HashValueGet(hPtr);

    if (!pss) {
        if (!create)
            return NULL;
        pss = (typeof(pss))Jsi_Calloc(1, sizeof(*pss));
        Jsi_HashValueSet(hPtr, pss);
        pss->isWebsock = !ishttp;
        pss->sig = JWS_SIG_PWS;
        pss->hPtr = hPtr;
        Jsi_HashValueSet(hPtr, pss);
        pss->cmdPtr = cmdPtr;
................................................................................
    if (pss->sig == 0)
        return;
    WSSIGASSERT(pss, PWS);
    if (pss->state == PWS_DEAD)
        return;
    if (pss->cmdPtr && pss->cmdPtr->debug>3)
        fprintf(stderr, "PSS DELETE: %p\n", pss);

    jsi_wsrecv_flush(pss->cmdPtr, pss);
    if (pss->hPtr) {
        Jsi_HashValueSet(pss->hPtr, NULL);
        Jsi_HashEntryDelete(pss->hPtr);
        pss->hPtr = NULL;
    }
    Jsi_Interp *interp = pss->cmdPtr->interp;
................................................................................
        pss->stats.sentLast = time(NULL);
    } else {
        pss->state = PWS_SENDERR;
        pss->stats.sentErrCnt++;
        pss->stats.sentErrLast = time(NULL);
        cmdPtr->stats.sentErrCnt++;
        cmdPtr->stats.sentErrLast = time(NULL);
    }
    return m;
}

static int jsi_wsServeHeader(jsi_wsPss *pss, struct lws *wsi, int strLen,
    int code, const char *extra, const char *mime, Jsi_DString *jStr)
{
    uchar ubuf[JSI_BUFSIZ], *p=ubuf, *end = &ubuf[sizeof(ubuf)-1];
................................................................................
        }
        Jsi_ValueArraySet(interp, *vPtr, Jsi_ValueNewStringDup(interp, buf), n-1);
    }
}

static Jsi_RC jsi_wsGetCmd(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss* pss, struct lws *wsi,
    const char *inPtr, Jsi_Value *cmd, Jsi_DString *tStr)
{
    Jsi_RC jrc;
    Jsi_Value *retStr = Jsi_ValueNew1(interp);
    // 4 args: ws, id, url, query
    Jsi_Value *vpargs, *vargs[10];
    int n = 0;
    if (cmdPtr->deleted) return JSI_ERROR;
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
................................................................................
    MKLCBS(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ),
    MKLCBS(LWS_CALLBACK_RECEIVE_CLIENT_HTTP),
    MKLCBS(LWS_CALLBACK_COMPLETED_CLIENT_HTTP),
    MKLCBS(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE),
    MKLCBS(LWS_CALLBACK_HTTP_BIND_PROTOCOL),
    MKLCBS(LWS_CALLBACK_HTTP_DROP_PROTOCOL),
    MKLCBS(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION),
    MKLCBS(LWS_CALLBACK_RAW_RX_FILE),
    MKLCBS(LWS_CALLBACK_RAW_WRITEABLE_FILE),
    MKLCBS(LWS_CALLBACK_RAW_CLOSE_FILE),
    MKLCBS(LWS_CALLBACK_USER),
#endif
#if (LWS_LIBRARY_VERSION_NUMBER>=3000000)
    MKLCBS(LWS_CALLBACK_SSL_INFO),
    MKLCBS(LWS_CALLBACK_CGI_PROCESS_ATTACH),
................................................................................
    return true;
}

static Jsi_RC jsi_wsTemplateFill(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, Jsi_Value *fn, Jsi_DString *dStr,
    int lvl) {
    Jsi_Value *fval;
    Jsi_RC rc = JSI_OK;
    char *cp, *cp2, *ce, pref[] = "<!--#include file=\"", suffix[] = "\"-->",
        pref2[] = "<!--#include virtual=\"";
    int flen, plen, plen1 = sizeof(pref)-1,  plen2 = sizeof(pref2)-1, slen = sizeof(suffix)-1;
    Jsi_DString tStr = {};
    const char *cs = "<!-- Markdeep: --><style class=\"fallback\">body{visibility:hidden;}</style>\n"
                "<script>window.markdeepOptions={tocStyle:\"medium\"}</script>\n"
                "<!--#include file=\"$inc\"-->\n"
                "<script src=\"markdeep.min.js\"></script>\n"
                "<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility='visible')</script>";
    char *fs, *fname = Jsi_ValueString(interp, fn, &flen), *fend = fname;
................................................................................
        flen = fs-fname;
        fend = fs+1;
    }
    if (lvl>0) {
        rc = Jsi_FileRead(interp, fn, &tStr);
        cs = Jsi_DSValue(&tStr);
    } else {
        snprintf(fbuf, sizeof(fbuf), "%.*s%s", flen, fname, cmdPtr->includeFile);
        fval = Jsi_ValueNewStringConst(interp, fbuf, -1);
        Jsi_IncrRefCount(interp, fval);
        Jsi_StatBuf sb;
        int n = Jsi_Stat(interp, fval, &sb);
        if (!n && sb.st_size>0) {
            rc = Jsi_FileRead(interp, fval, &tStr);
            cs = Jsi_DSValue(&tStr);
        }
        Jsi_DecrRefCount(interp, fval);
    }

    while (rc == JSI_OK && cs) {
        char *ext = NULL, *sfname = fname;
        int sflen = flen;
        cp = Jsi_Strstr(cs, pref);
        cp2 = Jsi_Strstr(cs, pref2);
        plen = plen1;
        if (cp2 && (!cp || cp2<cp)) {
            cp = cp2;
            plen = plen2;
            sfname = Jsi_ValueString(interp, cmdPtr->rootdir, &sflen);
        }
        if (!cp || !(ce=Jsi_Strstr(cp+plen, suffix))) {
            Jsi_DSAppend(dStr, cs, NULL);
            break;
        }
        Jsi_DSAppendLen(dStr, cs, cp-cs);
        if (cp[plen] == '$' && lvl == 0) {
            char sfx[20] = {};
            uint i;
            for (i=0; i<sizeof(sfx); i++) {
                if ((sfx[i] = cp[plen+i+1]) == '"' || !sfx[i]) {
                    sfx[i] = 0;
                    break;
                }
            }
            snprintf(fbuf, sizeof(fbuf), "%.*s%s/%s.%s", flen, fname, sfx, fend, sfx);
        } else {
            cp += plen;
            int elen = ce-cp;
            snprintf(fbuf, sizeof(fbuf), "%.*s/%.*s", sflen, sfname, elen, cp);
            ext = Jsi_Strrchr(fbuf, '.');
        }
        fval = Jsi_ValueNewStringConst(interp, fbuf, -1);
        Jsi_IncrRefCount(interp, fval);
        if (!ext || Jsi_Strcmp(ext, ".shtml"))
            rc = Jsi_FileRead(interp, fval, dStr);
        else
            rc = jsi_wsTemplateFill(interp, cmdPtr, fval, dStr, lvl+1);
        Jsi_DecrRefCount(interp, fval);

        cs = ce + slen;
        if (*cs == '\n')
            cs++;
    }
    Jsi_DSFree(&tStr);
    return rc;

}

// Handle http GET/POST
static int jsi_wsHttp(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, struct lws *wsi, void *user,
    struct lws_context *context, const char* inPtr, Jsi_DString *tStr, jsi_wsPss *pss)
{
    const char *ext = NULL;
................................................................................
    bool isMdi = 0;

    /* if a legal POST URL, let it continue and accept data */
    if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
        return 0;
    if (!pss)
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);

    int uplen=(cmdPtr->urlPrefix?Jsi_Strlen(cmdPtr->urlPrefix):0);

    if (inPtr && cmdPtr->urlPrefix && !Jsi_Strncmp(inPtr, cmdPtr->urlPrefix, uplen))
        inPtr += uplen;

    if (cmdPtr->redirDisable) {// Try to defray redirect loops.
        if (difftime(now, cmdPtr->stats.redirLast)>=600)
            cmdPtr->redirDisable = 0;
        else
            cmdPtr->redirDisable--;
    }

    if ((cmdPtr->urlRedirect && (inPtr == 0 || *inPtr == 0 || !Jsi_Strcmp(inPtr, "/")) && !cmdPtr->redirDisable)
        && (inPtr = cmdPtr->urlRedirect) && inPtr[0]) {
        cmdPtr->stats.redirCnt++;
        // TODO: system time change can disrupt the following.
        if (cmdPtr->redirMax>0 && !cmdPtr->redirDisable && cmdPtr->redirMax>0 && cmdPtr->stats.redirLast
            && difftime(now, cmdPtr->stats.redirLast)<600 && ++cmdPtr->redirAllCnt>cmdPtr->redirMax)
            cmdPtr->redirDisable = 100;
................................................................................
                    vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
                    Jsi_IncrRefCount(interp, vpargs);
                    Jsi_Value *ret = Jsi_ValueNew1(interp);
                    bool rb = 0;
                    rc = Jsi_FunctionInvoke(interp, cmdPtr->onAuth, vpargs, &ret, NULL);
                    if (rc == JSI_OK)
                        rb = !Jsi_ValueIsFalse(interp, ret);

                    Jsi_DecrRefCount(interp, vpargs);
                    Jsi_DecrRefCount(interp, ret);

                    if (rc != JSI_OK) {
                        Jsi_LogError("websock bad rcv eval");
                        return -1;
                    }
                    ok = rb;
                }
            }
            Jsi_DSFree(&eStr);
            Jsi_DSFree(&bStr);
        }
        if (!ok) {
            const char *realm = (cmdPtr->realm?cmdPtr->realm:"jsish");
            int n = snprintf(buf, sizeof(buf), "Basic realm=\"%s\"", realm);
................................................................................
                    (unsigned char *)buf, n, &p, end))
                return -1;
            if (jsi_wsServeString(pss, wsi, "Password is required to access this page", 401, (char*)buffer, NULL)<0)
                return -1;
            return lws_http_transaction_completed(wsi);
        }
    }

    if (cmdPtr->onGet || pss->onGet) {
        Jsi_RC jrc;
        int rrv = 1;
        if (cmdPtr->getRegexp) {
            rrv = 0;
            jrc = Jsi_RegExpMatch(interp, cmdPtr->getRegexp, inPtr, &rrv, NULL);
            if (jrc != JSI_OK)
................................................................................
                case JSI_CONTINUE: inPtr = Jsi_DSValue(tStr); break;
                case JSI_BREAK: break;
                default: break;
            }
        }
    }
    ext = Jsi_Strrchr(inPtr, '.');

    Jsi_Value *rdir = (pss->rootdir?pss->rootdir:cmdPtr->rootdir);
    const char *rootDir = (rdir?Jsi_ValueString(cmdPtr->interp, rdir, NULL):"./");
    char statPath[PATH_MAX];
#if 0
    if (!Jsi_Strncmp(inPtr, "/jsi/", 5)) {
        // Get the path for system files, eg /zvfs or /usr/local/lib/jsi
        const char *loadFile = NULL;
................................................................................
            /* Lookup mime type in mimeTypes object. */
            Jsi_Value *mVal = Jsi_ValueObjLookup(interp, cmdPtr->mimeTypes, ext+1, 1);
            if (mVal)
                mime = Jsi_ValueString(interp, mVal, NULL);
        }
        if (!mime) {
            static const char* mtypes[] = {
                "html", "text/html", "js", "application/x-javascript",
                "css", "text/css", "png", "image/png", "ico", "image/icon",
                "gif", "image/gif", "jpeg", "image/jpeg",
                "jpg", "image/jpeg", "svg", "image/svg+xml",
                "json", "application/json", "txt", "text/plain",
                "jsi", "application/x-javascript", "cssi", "text/css",
                0, 0
            };
            mime = "text/html";
            int i;
            for (i=0; mtypes[i]; i+=2)
                if (tolower(*eext) == mtypes[i][0] && !Jsi_Strncasecmp(eext, mtypes[i], -1)) {
                    mime = mtypes[i+1];
                    break;
                }
        }

        if (!Jsi_Strncasecmp(eext,"shtml", -1))
            essi = 1;

        if ((hPtr = Jsi_HashEntryFind(cmdPtr->handlers, ext)) && !cmdPtr->deleted) {
            /* Use interprete html eg. using jsi_wpp preprocessor */
            Jsi_DString jStr = {};
            Jsi_Value *vrc = NULL;
            int hrc = 0, strLen, evrc, isalloc=0;
            char *vStr, *hstr = NULL;
            jsi_wsHander *hdlPtr = (jsi_wsHander*)Jsi_HashValueGet(hPtr);
            Jsi_Value *hv = hdlPtr->val;

            if (Jsi_Strchr(buf, '\'') || Jsi_Strchr(buf, '\"')) {
                jsi_wsServeString(pss, wsi, "Can not handle quotes in url", 404, NULL, NULL);
                return -1;
            }
            cmdPtr->handlersPkg=1;

            // Attempt to load package and get function.
            if ((hdlPtr->flags&1) && cmdPtr->handlersPkg && Jsi_ValueIsString(interp, hv)
                && ((hstr = Jsi_ValueString(interp, hv, NULL)))) {
                vrc = Jsi_NameLookup(interp, hstr);
                if (!vrc) {
                    Jsi_Number pver = Jsi_PkgRequire(interp, hstr, 0);
                    if (pver >= 0)
                        vrc = Jsi_NameLookup(interp, hstr);
                }
                if (!vrc || !Jsi_ValueIsFunction(interp, vrc)) {
                    if (vrc)
                        Jsi_DecrRefCount(interp, vrc);
                    Jsi_LogError("Failed to autoload handle: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to autoload handler", 404, NULL, NULL);
                    return -1;
                }
                if (hdlPtr->val)
                    Jsi_DecrRefCount(interp, hdlPtr->val);
                hdlPtr->val = vrc;
                Jsi_IncrRefCount(interp, vrc);
                hv = vrc;
            }

            if ((hdlPtr->flags&2) && !hdlPtr->triedLoad && !hdlPtr->objVar && Jsi_ValueIsFunction(interp, hv)) {
                // Run command and from returned object get the parse function.
                hdlPtr->triedLoad = 1;
                Jsi_DSAppend(&jStr, "[null", NULL);
                if (hdlPtr->arg)
                    Jsi_DSAppend(&jStr, ", ", hdlPtr->arg, NULL); // TODO: JSON encode.
                Jsi_DSAppend(&jStr, "]", NULL);
                vrc = Jsi_ValueNew1(interp);
                evrc = Jsi_FunctionInvokeJSON(interp, hv, Jsi_DSValue(&jStr), &vrc);
                if (Jsi_InterpGone(interp))
                    return -1;
                if (evrc != JSI_OK || !vrc || !Jsi_ValueIsObjType(interp, vrc, JSI_OT_OBJECT)) {
                    Jsi_LogError("Failed to load obj: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to load obj", 404, NULL, NULL);
                    return -1;
                }
                Jsi_Value *fvrc = Jsi_ValueObjLookup(interp, vrc, "parse", 0);
                if (!fvrc || !Jsi_ValueIsFunction(interp, fvrc)) {
                    Jsi_LogError("Failed to find parse: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to find parse", 404, NULL, NULL);
                    return -1;
                }
                hdlPtr->objVar = fvrc;
                Jsi_IncrRefCount(interp, fvrc);
                hv = vrc;

            }

            if (hdlPtr->objVar) {  // Call the obj.parse function.
                Jsi_DSAppend(&jStr, "[\"", buf, "\"]", NULL); // TODO: JSON encode.
                vrc = Jsi_ValueNew1(interp);
                evrc = Jsi_FunctionInvokeJSON(interp, hdlPtr->objVar, Jsi_DSValue(&jStr), &vrc);
                isalloc = 1;
................................................................................
            // Take result from vrc and return it.
            if (evrc != JSI_OK) {
                Jsi_LogError("failure in websocket handler");
            } else if ((!vrc) ||
                (!(vStr = Jsi_ValueString(interp, vrc, &strLen)))) {
                Jsi_LogError("failed to get result");
            } else {
                hrc = jsi_wsServeString(pss, wsi, vStr, 0, NULL, NULL);
            }
            Jsi_DSFree(&jStr);
            if (isalloc)
                Jsi_DecrRefCount(interp, vrc);
            if (hrc<=0)
                return -1;
            return 1;
................................................................................
    if (!buf[0]) {
        if (cmdPtr->debug)
            fprintf(stderr, "empty file: %s\n", inPtr);
        return -1;
    }
    fname = Jsi_ValueNewStringDup(interp, buf);
    Jsi_IncrRefCount(interp, fname);

    Jsi_DString hStr = {};
    Jsi_StatBuf jsb;
    bool native = Jsi_FSNative(interp, fname);
    if (!ext || essi) {
        isMdi = 1;
        goto serve;
    }
................................................................................
        Jsi_DecrRefCount(interp, fname);
        goto done;
    }

serve:
    n = 0;
    // TODO: add automatic cookie mgmt?
/*
    if (!strcmp((const char *)in, "/") &&
       !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
        gettimeofday(&tv, NULL);
        n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000",
            (unsigned int)tv.tv_sec,
            (unsigned int)tv.tv_usec);

................................................................................
                    (uchar *) stsStr,
                    sizeof(stsStr)-1, &p, (uchar *)buffer + sizeof(buffer)))
        goto bail;
    n = p - buffer;
    if (n>0)
        Jsi_DSAppendLen(&hStr, (char*)buffer, n);
    p = buffer;

    if (isgzip) {
        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING,
                    (unsigned char *)"gzip", n, &p, end))
            goto bail;
    }
    if (cmdPtr->headers && !jsi_wsAddHeader(interp, cmdPtr, wsi, cmdPtr->headers, &hStr))
        goto bail;

    if (pss->headers && !jsi_wsAddHeader(interp, cmdPtr, wsi, pss->headers, &hStr))
        goto bail;

    n = Jsi_DSLength(&hStr);

    if (native && !isMdi) {
        Jsi_DecrRefCount(interp, fname);

        int hrc = lws_serve_http_file(wsi, buf, mime, Jsi_DSValue(&hStr), Jsi_DSLength(&hStr));
        if (hrc<0) {
            if (cmdPtr->noWarn==0)
                fprintf(stderr, "can not serve file (%d): %s\n", hrc, buf);
................................................................................
    return rc;

bail:
    rc = 1;
    goto done;
}

static Jsi_RC jsi_wsrecv_callback(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss,
    const char *inPtr, int nlen, bool isClose)
{
    Jsi_Value *vpargs, *vargs[10];
    Jsi_Value* func = NULL;
    if (Jsi_InterpGone(interp) || (cmdPtr->deleted && !isClose)) return JSI_ERROR;
    int n = 0;
    if (isClose)
................................................................................
    vargs[n++] = (cmdPtr->deleted?Jsi_ValueNewNull(interp):Jsi_ValueNewObj(interp, cmdPtr->fobj));
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss?pss->wid:0));
    if (!isClose) {
        if (nlen<=0)
            return JSI_OK;
        vargs[n++]  = Jsi_ValueNewBlob(interp, (uchar*)inPtr, nlen);
        if ((cmdPtr->echo||(pss && pss->echo)) && inPtr)
            Jsi_LogInfo("WS-RECV: %s\n", inPtr);
    }
    vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, vargs, n, 0));
    Jsi_IncrRefCount(interp, vpargs);

    Jsi_Value *ret = Jsi_ValueNew1(interp);
    Jsi_ValueMakeUndef(interp, &ret);
    Jsi_RC rc;
        rc = Jsi_FunctionInvoke(interp, func, vpargs, &ret, NULL);
    if (rc == JSI_OK && Jsi_ValueIsUndef(interp, ret)==0 && !isClose) {
        /* TODO: should we handle callback return data??? */
    }
................................................................................
    jsi_wsPss *pss = (typeof(pss))data;
    jsi_wsCmdObj *cmdPtr = pss->cmdPtr;
    Jsi_Value* callPtr = (pss->onUpload?pss->onUpload:cmdPtr->onUpload);
    Jsi_Interp *interp = cmdPtr->interp;
    const char *str;
    int slen, n = 0;
    if (cmdPtr->deleted) return -1;

    Jsi_Obj *oarg1;
    Jsi_Value *vpargs, *vargs[10];
    if (state == LWS_UFS_OPEN)
        pss->file_length = 0;
    //id:number, filename:string, data:string, startpos:number, complete:boolean
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
................................................................................
    Jsi_Interp *interp = cmdPtr->interp;
    Jsi_Value* callPtr = NULL;
    int rc = 0, deflt = 0;

    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;

    if (cmdPtr->debug>=128)
        fprintf(stderr, "HTTP CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));

    switch (reason) {
#ifndef EXTERNAL_POLL
    case LWS_CALLBACK_GET_THREAD_ID:
    case LWS_CALLBACK_UNLOCK_POLL:
................................................................................
#endif

    default:
        deflt = 1;
        break;

    }

    if (deflt && cmdPtr->debug>16 && cmdPtr->debug<128) {
        fprintf(stderr, "HTTP CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
    }

    switch (reason) {
    case LWS_CALLBACK_WSI_DESTROY:
        break;

#if (LWS_LIBRARY_VERSION_MAJOR>1)
    // Handle GET file download in client mode.
    case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: {
        char buffer[1024 + LWS_PRE];
        char *px = buffer + LWS_PRE;
        int lenx = sizeof(buffer) - LWS_PRE;

        if (lws_http_client_read(wsi, &px, &lenx) < 0)
................................................................................
            return -1;
        break;
    }
    case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
        if (jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 0) != JSI_OK)
            rc = 1;
        break;

    case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
        if (jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1) != JSI_OK)
            rc = 1;
        break;

    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
        if (cmdPtr->post) {
            unsigned char **p = (unsigned char **)in, *end = (*p) + len;
            int n = 0;
            char buf[100];
            Jsi_ValueString(interp, cmdPtr->post, &n);
            snprintf(buf, sizeof(buf), "%d", n);
................................................................................
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 1);
        if (pss)
            jsi_wsdeletePss(pss);
        break;
    case LWS_CALLBACK_WSI_CREATE:
        break;

    case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
        break;

    case LWS_CALLBACK_FILTER_HTTP_CONNECTION:
        if (cmdPtr->debug>1)
            fprintf(stderr, "FILTER CONNECTION: %s\n", inPtr);
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        jsi_wsgetUriArgValue(interp, wsi, &pss->query);

        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }

        Jsi_DSSetLength(&pss->dHdrs, 0);
        pss->hdrNum = jsi_wsGetHeaders(pss, wsi, &pss->dHdrs, pss->hdrSz, sizeof(pss->hdrSz)/sizeof(int));

        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            // 4 args: ws, id, url, bool
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 1);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onFilter, vpargs, &ret, NULL);
................................................................................
        client_ip[0] = 0;
        lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), client_name,
                                         sizeof(client_name), client_ip, sizeof(client_ip));
        if (client_name[0])
            cmdPtr->clientName = Jsi_KeyAdd(interp, client_name);
        if (client_ip[0])
            cmdPtr->clientIP = Jsi_KeyAdd(interp, client_ip);

        if (cmdPtr->clientName || cmdPtr->clientIP) {
            const char *loname = cmdPtr->localhostName;
            if (!loname) loname = "localhost";
            cmdPtr->instCtx = context;
            if (cmdPtr->debug>1)
                fprintf(stderr,  "Received network connect from %s (%s)\n",
                     cmdPtr->clientName, cmdPtr->clientIP);
................................................................................
        if (rc<0)
            return -1;
        if (rc==1) {
            goto try_to_reuse;
        }
        break;
    }

#if (LWS_LIBRARY_VERSION_MAJOR>1)
    case LWS_CALLBACK_HTTP_BODY: {
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 1);
        if (!pss) break;
        callPtr = (pss->onUpload?pss->onUpload:cmdPtr->onUpload);
        if (cmdPtr->maxUpload<=0 || !callPtr) {
................................................................................
        res = Jsi_DSValue(&pss->resultStr);
        if (!res[0]) {
            if (!pss->resultCode)
                res = "<html><body>Upload complete</body></html>";
            else
                res = "<html><body>Upload error</body></html>";
        }
        jsi_wsServeString(pss, wsi, res, pss->resultCode==JSI_OK?0:500, NULL, NULL);
        if (cmdPtr->maxUpload<=0 || !callPtr) {
            if (cmdPtr->noWarn==0)
                fprintf(stderr, "Upload disabled: maxUpload=%d, onUpload=%p\n", cmdPtr->maxUpload, callPtr);
            return -1;
        }
        cmdPtr->stats.uploadEnd = pss->stats.uploadEnd = time(NULL);
        lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
................................................................................
    }

    default:
        break;
    }

    goto doret;

try_to_reuse:
    if (lws_http_transaction_completed(wsi))
         rc = -1;
    else
        rc = 0;
    goto doret;

doret:
    if (cmdPtr->debug>2)
        fprintf(stderr, "<---HTTP RET = %d\n", rc);
    return rc;
}

static int
................................................................................
    }
    Jsi_Interp *interp = cmdPtr->interp;
    char *inPtr = (char*)in;
    int sLen, n, rc =0;
    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;

    if (cmdPtr->debug>=32) {
        switch (reason) {
            case LWS_CALLBACK_SERVER_WRITEABLE:
            case LWS_CALLBACK_CLIENT_WRITEABLE:
                break;
            default:
                fprintf(stderr, "WS CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
        }
    }

    switch (reason) {
    case LWS_CALLBACK_PROTOCOL_INIT:
        if (cmdPtr->noWebsock)
            return 1;
        break;

    case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }
        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            if (!pss)
                pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 0);
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);

            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 0);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
................................................................................
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);

            Jsi_Value *ret = Jsi_ValueNew1(interp);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onOpen, vpargs, &ret, NULL);

            Jsi_DecrRefCount(interp, vpargs);
            Jsi_DecrRefCount(interp, ret);
            if (rc != JSI_OK)
                return Jsi_LogError("websock bad rcv eval");
        }
        break;

    case LWS_CALLBACK_WSI_DESTROY:
        break;

    case LWS_CALLBACK_CLOSED:
    case LWS_CALLBACK_PROTOCOL_DESTROY:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;
        if (cmdPtr->onClose || pss->onClose) {
            rc = jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1);
            if (rc != JSI_OK)
                return Jsi_LogError("websock bad rcv eval");
        }
        jsi_wsdeletePss(pss);
        if (cmdPtr->stats.connectCnt<=0 && cmdPtr->onCloseLast && !Jsi_InterpGone(interp)) {
            Jsi_RC jrc;
            Jsi_Value *retStr = Jsi_ValueNew1(interp);
            // 1 args: ws
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
................................................................................
        pss->stats.msgQLen--;
        pss->state = PWS_SENT;
        p = (unsigned char *)data+LWS_PRE;
        sLen = Jsi_Strlen((char*)p);
        n = jsi_wswrite(pss, wsi, p, sLen, (pss->stats.isBinary?LWS_WRITE_BINARY:LWS_WRITE_TEXT));
        if (cmdPtr->debug>=10)
            fprintf(stderr, "WS:CLIENT WRITE(%p): %d=>%d\n", pss, sLen, n);

        if (n >= 0) {
            cmdPtr->stats.sentCnt++;
            cmdPtr->stats.sentLast = time(NULL);
            pss->stats.sentCnt++;
            pss->stats.sentLast = time(NULL);
        } else {
            lwsl_err("ERROR %d writing to socket\n", n);
................................................................................
            pss->stats.sentErrLast = time(NULL);
            cmdPtr->stats.sentErrCnt++;
            cmdPtr->stats.sentErrLast = time(NULL);
            rc = 1;
        }
        break;
    }

    case LWS_CALLBACK_CLIENT_RECEIVE:
    case LWS_CALLBACK_RECEIVE:
    {
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;

        pss->stats.recvCnt++;
................................................................................
            if (rc != JSI_OK) {
                Jsi_LogError("websock bad rcv eval");
                return 1;
            }
        }
        lws_callback_on_writable_all_protocol(cmdPtr->context, lws_get_protocol(wsi));
        break;

    }
    default:
        break;
    }
    return rc;
}


static Jsi_RC WebSocketConfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);

    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_Value *opts = Jsi_ValueArrayIndex(interp, args, 0);
    if (cmdPtr->noConfig && opts && !Jsi_ValueIsString(interp, opts))
        return Jsi_LogError("WebSocket conf() is disabled for set");
    return Jsi_OptionsConf(interp, WSOptions, cmdPtr, opts, ret, 0);

}

static Jsi_RC WebSocketIdCmdOp(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr, int op)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_Value *valPtr = Jsi_ValueArrayIndex(interp, args, 0);
    Jsi_Number vid;
    if (Jsi_ValueGetNumber(interp, valPtr, &vid) != JSI_OK || vid < 0)
        return Jsi_LogError("Expected connection number id");
    int id = (int)vid;
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
................................................................................
        WSSIGASSERT(tpss, PWS);
        if (tpss->wid == id && tpss->state != PWS_DEAD) {
            pss = tpss;
            break;
        }
    }

    if (!pss)
        return Jsi_LogError("No such id: %d", id);
    switch (op) {
        case 0: return Jsi_OptionsConf(interp, WPSOptions, pss, Jsi_ValueArrayIndex(interp, args, 1), ret, 0);
        case 1:
            jsi_wsDumpHeaders(cmdPtr, pss, Jsi_ValueArrayIndexToStr(interp, args, 1, NULL), ret);
            break;
        case 2:
            if (!pss->spa) return JSI_OK;
            jsi_wsDumpQuery(cmdPtr, pss, Jsi_ValueArrayIndexToStr(interp, args, 1, NULL), ret);
            break;
    }
    return JSI_OK;
}

................................................................................
}


static Jsi_RC WebSocketIdsCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_DString dStr = {"["};
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    int cnt = 0;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
................................................................................
If a cmd is a function, it is called with a single arg: the file name.")
static Jsi_RC WebSocketHandlerCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_HashEntry *hPtr;
    jsi_wsHander *hdlPtr;
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    WSSIGASSERT(cmdPtr, OBJ);
    int argc = Jsi_ValueGetLength(interp, args);
    if (argc == 0) {
        Jsi_HashSearch search;
        Jsi_Obj* obj = Jsi_ObjNew(interp);
        for (hPtr = Jsi_HashSearchFirst(cmdPtr->handlers, &search); hPtr; hPtr = Jsi_HashSearchNext(&search)) {
................................................................................
            Jsi_DecrRefCount(interp, hdlPtr->val);
        Jsi_HashValueSet(hPtr, NULL);
        Jsi_HashEntryDelete(hPtr);
        Jsi_Free(hdlPtr);
        Jsi_ValueMakeStringDup(interp, ret, key);
        return JSI_OK;
    }
    if (Jsi_ValueIsFunction(interp, valPtr)==0 && Jsi_ValueIsString(interp, valPtr)==0)
        return Jsi_LogError("expected string, function or null");
    Jsi_Value *argPtr = Jsi_ValueArrayIndex(interp, args, 2);
    if (argPtr) {
        if (Jsi_ValueIsNull(interp, argPtr))
            argPtr = NULL;
        else if (!Jsi_ValueIsString(interp, argPtr))
            return Jsi_LogError("expected a string");
    }
    hPtr = Jsi_HashEntryNew(cmdPtr->handlers, key, NULL);
    if (!hPtr)
        return JSI_ERROR;
    hdlPtr = (jsi_wsHander *)Jsi_Calloc(1, sizeof(*hdlPtr));
    Jsi_Value *flagPtr = Jsi_ValueArrayIndex(interp, args, 1);
................................................................................
#define FN_wssend JSI_INFO("\
Send a message to one (or all connections if -1). If not already a string, msg is formatted as JSON prior to the send.")

static Jsi_RC WebSocketSendCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    WSSIGASSERT(cmdPtr, OBJ);
    jsi_wsPss *pss;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 1);
    char *str = Jsi_ValueString(interp, arg, NULL);
    int id = -1, argc = Jsi_ValueGetLength(interp, args);
    Jsi_DString eStr = {};
    if (argc!=2)
        return Jsi_LogError("wrong args");
    Jsi_Number dnum;
    Jsi_Value *darg = Jsi_ValueArrayIndex(interp, args, 0);
    if (Jsi_ValueGetNumber(interp, darg, &dnum) != JSI_OK)
        return Jsi_LogError("invalid id");
    id = (int)dnum;

    if (!str)
        str = (char*)Jsi_ValueGetDString(interp, arg, &eStr, JSI_OUTPUT_JSON);

    if (cmdPtr->echo)
        Jsi_LogInfo("WS-SEND: %s\n", str);

    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
        pss = (jsi_wsPss*)Jsi_HashValueGet(hPtr);
        WSSIGASSERT(pss, PWS);
        if ((id<0 || pss->wid == id) && pss->state != PWS_DEAD) {
            if (!pss->stack)
                pss->stack = Jsi_StackNew();
            char *msg = (char*)Jsi_Malloc(LWS_PRE + Jsi_Strlen(str) + 1);
            Jsi_Strcpy(msg + LWS_PRE, str);
            Jsi_StackPush(pss->stack, msg);
            pss->stats.msgQLen++;
            if (!cmdPtr->echo && pss->echo)
                Jsi_LogInfo("WS-SEND: %s\n", str);
        }
    }

    Jsi_DSFree(&eStr);
    return JSI_OK;
}

static Jsi_RC jsi_wsrecv_flush(jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss)
{
    int nlen = Jsi_DSLength(&pss->recvBuf);
................................................................................
    return n;
}

static Jsi_RC WebSocketUpdateCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply to non-websock object");
    if (!cmdPtr->noUpdate)
        jsi_wsService(cmdPtr);
    return JSI_OK;
}

static Jsi_RC jsi_wswebsockUpdate(Jsi_Interp *interp, void *data)
................................................................................

static Jsi_RC jsi_wswebsocketObjFree(Jsi_Interp *interp, void *data)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)data;
    WSSIGASSERT(cmdPtr,OBJ);
    cmdPtr->deleted = 1;
    struct lws_context *ctx = cmdPtr->context;
    if (ctx)
        lws_context_destroy(ctx);
    cmdPtr->context = NULL;
    jsi_wswebsocketObjErase(cmdPtr);
    _JSI_MEMCLEAR(cmdPtr);
    Jsi_Free(cmdPtr);
    return JSI_OK;
}
................................................................................
    return JSI_OK;
}

static Jsi_RC WebSocketStatusCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply to non-websock object");
#ifndef OMIT_LWS_WITH_SERVER_STATUS
    char cbuf[JSI_BUFSIZ*2];
    lws_json_dump_context(cmdPtr->context, cbuf, sizeof(cbuf), 0);
    return Jsi_JSONParse(interp, cbuf, ret, 0);
#else
    return Jsi_LogError("unsupported");
................................................................................
}

#define FN_WebSocket JSI_INFO("\
Create a websocket server/client object.  The server serves out pages to a web browser,\n\
which can use javascript to upgrade connection to a bidirectional websocket.")
static Jsi_RC WebSocketConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);


static Jsi_CmdSpec websockCmds[] = {
    { "WebSocket",  WebSocketConstructor, 0,  1, "options:object=void", .help="Create websocket server/client object", .retType=(uint)JSI_TT_USEROBJ, .flags=JSI_CMD_IS_CONSTRUCTOR, .info=FN_WebSocket, .opts=WSOptions },
    { "conf",       WebSocketConfCmd,     0,  1, "options:string|object=void",.help="Configure options", .retType=(uint)JSI_TT_ANY, .flags=0, .info=0, .opts=WSOptions },
    { "handler",    WebSocketHandlerCmd,  0,  4, "extension:string=void, cmd:string|function=void, arg:string|null=void, flags:number=0",
        .help="Get/Set handler command for an extension", .retType=(uint)JSI_TT_FUNCTION|JSI_TT_ARRAY|JSI_TT_STRING|JSI_TT_VOID, .flags=0, .info=FN_wshandler },
    { "ids",        WebSocketIdsCmd,      0,  0, "", .help="Return list of ids", .retType=(uint)JSI_TT_ARRAY},
    { "idconf",     WebSocketIdConfCmd,   1,  2, "id:number, options:string|object=void",.help="Configure options for connect id", .retType=(uint)JSI_TT_ANY, .flags=0, .info=0, .opts=WPSOptions },
    { "header",     WebSocketHeaderCmd,   1,  2, "id:number, name:string=void",.help="Get one or all input headers for connect id", .retType=(uint)JSI_TT_STRING|JSI_TT_OBJECT|JSI_TT_VOID },
    { "query",      WebSocketQueryCmd,    1,  2, "id:number, name:string=void",.help="Get one or all query values for connect id", .retType=(uint)JSI_TT_STRING|JSI_TT_OBJECT|JSI_TT_VOID },
    { "send",       WebSocketSendCmd,     2,  2, "id:number, data:any", .help="Send a websocket message to id", .retType=(uint)JSI_TT_VOID, .flags=0, .info=FN_wssend },
    { "status",     WebSocketStatusCmd,   0,  0, "", .help="Return libwebsocket server status", .retType=(uint)JSI_TT_OBJECT|JSI_TT_VOID},
................................................................................
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (Jsi_InterpAccess(interp, NULL, JSI_INTACCESS_NETWORK ) != JSI_OK)
        return Jsi_LogError("WebSocket disallowed by Interp.noNetwork option");
    jsi_wsCmdObj *cmdPtr;
    Jsi_Value *toacc = NULL;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 0);

    cmdPtr = (jsi_wsCmdObj*)Jsi_Calloc(1, sizeof(*cmdPtr));
    cmdPtr->sig = JWS_SIG_OBJ;
    cmdPtr->port = 8080;
    cmdPtr->formParams = jsi_wsparam_str;
    cmdPtr->maxUpload = 100000;
    cmdPtr->interp = interp;
    cmdPtr->ietf_version = -1;
    cmdPtr->bufferPwr2 = 0;
    cmdPtr->ws_gid = -1;
    cmdPtr->ws_uid = -1;
    cmdPtr->startTime = time(NULL);
    cmdPtr->hasOpts = 1;
    cmdPtr->includeFile = "include.shtml";
    if ((arg != NULL && !Jsi_ValueIsNull(interp,arg))
        && Jsi_OptionsProcess(interp, WSOptions, cmdPtr, arg, 0) < 0) {
bail:
        jsi_wswebsocketObjFree(interp, cmdPtr);
        return JSI_ERROR;
    }
    if (cmdPtr->headers && (Jsi_ValueGetLength(interp, cmdPtr->headers)%2)) {
................................................................................
    if (cmdPtr->context == NULL) {
fail:
        Jsi_LogError("libwebsocket init failed on port %d (try another port?)", cmdPtr->info.port);
        goto bail;
    }
    if (cmdPtr->info.options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) {
        cmdPtr->info.options &= ~LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
        if (!lws_create_vhost(cmdPtr->context, &cmdPtr->info))
            goto fail;
    }

    if (cmdPtr->client) {
        struct lws_client_connect_info lci = {};
        lci.context = cmdPtr->context;
        lci.address = cmdPtr->address ? Jsi_ValueString(cmdPtr->interp, cmdPtr->address, NULL) : "127.0.0.1";
        lci.port = cmdPtr->port;
        lci.ssl_connection = cmdPtr->use_ssl;
        lci.path = cmdPtr->rootdir?Jsi_ValueString(cmdPtr->interp, cmdPtr->rootdir, NULL):"/";
................................................................................
        lci.ietf_version_or_minus_one = cmdPtr->ietf_version;
#if (LWS_LIBRARY_VERSION_MAJOR>1)
        if (cmdPtr->post)
            lci.method = "POST";
        else if (!Jsi_Strcmp(subprot, "get"))
            lci.method = "GET";
#endif

        if (NULL == lws_client_connect_via_info(&lci))
        {
            Jsi_LogError("websock connect failed");
            jsi_wswebsocketObjFree(interp, cmdPtr);
            return JSI_ERROR;
        }
    } else if (cmdPtr->port == 0) {
        // Extract actually used port.
        char *cp, cbuf[JSI_BUFSIZ*2];
        cbuf[0] = 0;
        lws_json_dump_context(cmdPtr->context, cbuf, sizeof(cbuf), 0);
        cp = Jsi_Strstr(cbuf, "\"port\":\"");
        if (cp)
            cmdPtr->port = atoi(cp+8);
    }

    cmdPtr->event = Jsi_EventNew(interp, jsi_wswebsockUpdate, cmdPtr);
    if (Jsi_FunctionIsConstructor(funcPtr)) {
        toacc = _this;
    } else {
................................................................................
        jsi_wsHandlerAdd(interp, cmdPtr, ".htmli", "Jsi_Htmlpp",   jsi_wsStrValGet(cmdPtr, "htmli"), 1);
        jsi_wsHandlerAdd(interp, cmdPtr, ".cssi",  "Jsi_Csspp",    jsi_wsStrValGet(cmdPtr, "cssi"),  1);
        jsi_wsHandlerAdd(interp, cmdPtr, ".mdi",   "Jsi_Markdeep", jsi_wsStrValGet(cmdPtr, "mdi"),   1);
    }
    cmdPtr->fobj = fobj;
#ifdef LWS_LIBRARY_VERSION_NUMBER
    Jsi_JSONParseFmt(interp, &cmdPtr->version, "{libVer:\"%s\", hdrVer:\"%s\", hdrNum:%d, pkgVer:%d}",
        (char *)lws_get_library_version(), LWS_LIBRARY_VERSION, LWS_LIBRARY_VERSION_NUMBER, jsi_WsPkgVersion);
#endif
    return JSI_OK;
}

static Jsi_RC Jsi_DoneWebSocket(Jsi_Interp *interp)
{
    Jsi_UserObjUnregister(interp, &websockobject);
................................................................................
        return Jsi_DoneWebSocket(interp);
#ifdef LWS_OPENSSL_SUPPORT
    Jsi_InterpAccess(interp, NULL, JSI_INTACCESS_SETSSL )
#endif
    Jsi_Hash *wsys;
    const char *libver = lws_get_library_version();
    int lvlen = sizeof(LWS_LIBRARY_VERSION)-1;
    if (Jsi_Strncmp(libver, LWS_LIBRARY_VERSION, lvlen) || !isspace(libver[lvlen]))
        return Jsi_LogError("Library version mismatch: HDR:%s != LIB:%s", LWS_LIBRARY_VERSION, lws_get_library_version());
#if JSI_USE_STUBS
  if (Jsi_StubsInit(interp, 0) != JSI_OK)
    return JSI_ERROR;
#endif
    if (Jsi_PkgProvide(interp, "WebSocket", jsi_WsPkgVersion, Jsi_InitWebSocket) != JSI_OK)
        return JSI_ERROR;
................................................................................
}

Jsi_RC jsi_evalStrFile(Jsi_Interp* interp, Jsi_Value *path, char *str, int flags, int level)
{
    Jsi_Channel tinput = NULL, input = Jsi_GetStdChannel(interp, 0);
    Jsi_Value *npath = path;
    Jsi_RC rc = JSI_ERROR;

    const char *ustr = NULL, *ostr = str;
    if (Jsi_MutexLock(interp, interp->Mutex) != JSI_OK)
        return rc;
    int oldSp, uskip = 0, fncOfs = 0;
    int oldef = interp->evalFlags;
    jsi_Pstate *oldps = interp->ps;
    const char *oldFile = interp->curFile;
    char *origFile = Jsi_ValueString(interp, path, NULL);
    const char *fname = origFile;
    char *oldDir = interp->curDir;
    char dirBuf[PATH_MAX];
    jsi_Pstate *ps = NULL;
    bool strict = 0;
    bool oldStrict = interp->framePtr->strict;
    jsi_FileInfo *fi = NULL;
    int exists = (flags&JSI_EVAL_EXISTS);
    int ignore = (flags&JSI_EVAL_ERRIGNORE);
    if (flags & JSI_EVAL_GLOBAL)
        level = 1;
    if (flags & JSI_EVAL_ISMAIN)

Changes to src/jsi.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* jsi.h : External API header file for Jsi. */
#ifndef __JSI_H__
#define __JSI_H__

#define JSI_VERSION_MAJOR   2
#define JSI_VERSION_MINOR   6
#define JSI_VERSION_RELEASE 1

#define JSI_VERSION (JSI_VERSION_MAJOR + ((Jsi_Number)JSI_VERSION_MINOR/100.0) + ((Jsi_Number)JSI_VERSION_RELEASE/10000.0))

#ifndef JSI_EXTERN
#define JSI_EXTERN extern
#endif







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* jsi.h : External API header file for Jsi. */
#ifndef __JSI_H__
#define __JSI_H__

#define JSI_VERSION_MAJOR   2
#define JSI_VERSION_MINOR   6
#define JSI_VERSION_RELEASE 2

#define JSI_VERSION (JSI_VERSION_MAJOR + ((Jsi_Number)JSI_VERSION_MINOR/100.0) + ((Jsi_Number)JSI_VERSION_RELEASE/10000.0))

#ifndef JSI_EXTERN
#define JSI_EXTERN extern
#endif

Changes to src/jsiFilesys.c.

1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
....
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
....
1357
1358
1359
1360
1361
1362
1363









1364
1365
1366
1367
1368
1369
1370
    char *cp = path;
    *cp = 0;
    for (i=0; i<argc; i++) {
        if (i == 0 && argv[0][0] == 0) {
            continue;
        } else if (argv[i][0] == 0) {
            continue;
        } else if (!Jsi_Strcmp(argv[i],".")) {
            continue;
        } else if (!Jsi_Strcmp(argv[i],"..")) {
            char *pcp = Jsi_DSValue(&dStr), *lcp = pcp;
            pcp = Jsi_Strrchr(pcp, '/');
            if (pcp && pcp != Jsi_DSValue(&dStr)) {
                *pcp = 0;
                Jsi_DSSetLength(&dStr, Jsi_Strlen(lcp));
................................................................................
    DeBackSlashify(Jsi_DSValue(cwdPtr));
#endif
    return Jsi_DSValue(cwdPtr);
}

char *Jsi_FileRealpathStr(Jsi_Interp *interp, const char *path, char *newname)
{
    if (!path) return NULL;
    Jsi_DString dStr;
    char *npath = (char*)path, *apath;
    Jsi_DSInit(&dStr);
    if (*path == '~') {
#ifndef __WIN32
        struct passwd pw, *pwp; /* TODO: could fallback to using env HOME. */
        char buf[JSI_BUFSIZ];
................................................................................
    }
#ifdef __WIN32
    if (Jsi_Strncmp(path, JSI_ZVFS_DIR, sizeof(JSI_ZVFS_DIR)-1)==0 || Jsi_Strncmp(path, JSI_VFS_DIR, sizeof(JSI_VFS_DIR)-1)==0)
        apath = NULL;
    else
#endif
    apath = realpath(npath, newname);









    if (!apath) {
        if (newname)
            Jsi_Strcpy(newname, apath=npath);
        else
            apath = Jsi_Strdup(npath);
#ifndef __WIN32
        /* If path not exists on unix we try to eliminate ../ and /./ etc.*/







|







 







|







 







>
>
>
>
>
>
>
>
>







1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
....
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
....
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
    char *cp = path;
    *cp = 0;
    for (i=0; i<argc; i++) {
        if (i == 0 && argv[0][0] == 0) {
            continue;
        } else if (argv[i][0] == 0) {
            continue;
        } else if (i && !Jsi_Strcmp(argv[i],".")) {
            continue;
        } else if (!Jsi_Strcmp(argv[i],"..")) {
            char *pcp = Jsi_DSValue(&dStr), *lcp = pcp;
            pcp = Jsi_Strrchr(pcp, '/');
            if (pcp && pcp != Jsi_DSValue(&dStr)) {
                *pcp = 0;
                Jsi_DSSetLength(&dStr, Jsi_Strlen(lcp));
................................................................................
    DeBackSlashify(Jsi_DSValue(cwdPtr));
#endif
    return Jsi_DSValue(cwdPtr);
}

char *Jsi_FileRealpathStr(Jsi_Interp *interp, const char *path, char *newname)
{
    if (!path || !path[0]) return NULL;
    Jsi_DString dStr;
    char *npath = (char*)path, *apath;
    Jsi_DSInit(&dStr);
    if (*path == '~') {
#ifndef __WIN32
        struct passwd pw, *pwp; /* TODO: could fallback to using env HOME. */
        char buf[JSI_BUFSIZ];
................................................................................
    }
#ifdef __WIN32
    if (Jsi_Strncmp(path, JSI_ZVFS_DIR, sizeof(JSI_ZVFS_DIR)-1)==0 || Jsi_Strncmp(path, JSI_VFS_DIR, sizeof(JSI_VFS_DIR)-1)==0)
        apath = NULL;
    else
#endif
    apath = realpath(npath, newname);
    if (!apath) {
        if ((path[0] == '.' && path[1] == '/') || (path[0] != '/' && 
        !(path[0] == '.' && path[1] == '.') && path[1] != ':')) {
            Jsi_GetCwd(interp, &dStr);
            Jsi_DSAppend(&dStr, "/", path, NULL);
            npath = Jsi_DSValue(&dStr);
            apath = realpath(npath, newname);
        }
    }
    if (!apath) {
        if (newname)
            Jsi_Strcpy(newname, apath=npath);
        else
            apath = Jsi_Strdup(npath);
#ifndef __WIN32
        /* If path not exists on unix we try to eliminate ../ and /./ etc.*/

Changes to src/jsiInterp.c.

688
689
690
691
692
693
694

695
696
697
698
699
700
701
...
745
746
747
748
749
750
751


752
753
754
755
756
757
758
            puts("jsish arguments:\n"
              "  -a/--archive FILE\tMount an archive (zip, sqlar or fossil repo) and run module.\n"
              "  -c/--cdata FILE\tGenerate .c or JSON output from a .jsc description.\n"
              "  -C/--cssi FILE\tPreprocess embedded CSS in .css file.\n"
              "  -d/--debug FILE\tRun console debugger on script.\n"
              "  -D/--debugui FILE\tRun web-gui debugger on script.\n"
              "  -e/--eval STRING\tEvaluate a javascript string and exit.\n"

              "  -h/--help\t\tPrint help in short or long form.\n"
              "  -H/--htmli FILE\tPreprocess embedded jsi in .htmli file.\n"
              "  -J/--jsi FILE\t\tPreprocess a .jsi file to typecheck in standard javascript.\n"
              "  -m/--module FILE\tSource file and invoke runModule.\n"
              "  -M/--make FILE\tPreprocess script as a Jsi Makefile.\n"
              "  -s/--safe FILE\tRun script in safe sub-interp.\n"
              "  -S/--sqliteui DBFILE\tRun Sqlite web-gui.\n"
................................................................................
        } else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--safe")==0 || Jsi_Strcmp(argv[1], "-s" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Safe');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--debugui")==0 || Jsi_Strcmp(argv[1], "-D" )==0)) {
            interp->debugOpts.isDebugger = 1;
            rc = Jsi_EvalString(interp, "runModule('Jsi_DebugUI');", JSI_EVAL_ISMAIN);
        } else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--wget")==0 || Jsi_Strcmp(argv[1], "-w" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Wget');", JSI_EVAL_ISMAIN);


        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--websrv")==0 || Jsi_Strcmp(argv[1], "-W" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Websrv');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--archive")==0 || Jsi_Strcmp(argv[1], "-a" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Archive');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--sqliteui")==0 || Jsi_Strcmp(argv[1], "-S" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_SqliteUI');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--cdata")==0 || Jsi_Strcmp(argv[1], "-c" )==0))







>







 







>
>







688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
...
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
            puts("jsish arguments:\n"
              "  -a/--archive FILE\tMount an archive (zip, sqlar or fossil repo) and run module.\n"
              "  -c/--cdata FILE\tGenerate .c or JSON output from a .jsc description.\n"
              "  -C/--cssi FILE\tPreprocess embedded CSS in .css file.\n"
              "  -d/--debug FILE\tRun console debugger on script.\n"
              "  -D/--debugui FILE\tRun web-gui debugger on script.\n"
              "  -e/--eval STRING\tEvaluate a javascript string and exit.\n"
              "  -g/--gendeep FILES\tGenerate html output from markdeep source.\n"
              "  -h/--help\t\tPrint help in short or long form.\n"
              "  -H/--htmli FILE\tPreprocess embedded jsi in .htmli file.\n"
              "  -J/--jsi FILE\t\tPreprocess a .jsi file to typecheck in standard javascript.\n"
              "  -m/--module FILE\tSource file and invoke runModule.\n"
              "  -M/--make FILE\tPreprocess script as a Jsi Makefile.\n"
              "  -s/--safe FILE\tRun script in safe sub-interp.\n"
              "  -S/--sqliteui DBFILE\tRun Sqlite web-gui.\n"
................................................................................
        } else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--safe")==0 || Jsi_Strcmp(argv[1], "-s" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Safe');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--debugui")==0 || Jsi_Strcmp(argv[1], "-D" )==0)) {
            interp->debugOpts.isDebugger = 1;
            rc = Jsi_EvalString(interp, "runModule('Jsi_DebugUI');", JSI_EVAL_ISMAIN);
        } else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--wget")==0 || Jsi_Strcmp(argv[1], "-w" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Wget');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--gendeep")==0 || Jsi_Strcmp(argv[1], "-g" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_GenDeep');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--websrv")==0 || Jsi_Strcmp(argv[1], "-W" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Websrv');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--archive")==0 || Jsi_Strcmp(argv[1], "-a" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_Archive');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--sqliteui")==0 || Jsi_Strcmp(argv[1], "-S" )==0))
            rc = Jsi_EvalString(interp, "runModule('Jsi_SqliteUI');", JSI_EVAL_ISMAIN);
        else if (interp->selfZvfs && argc > 1 && (Jsi_Strcmp(argv[1], "--cdata")==0 || Jsi_Strcmp(argv[1], "-c" )==0))

Changes to src/jsiString.c.

584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
...
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889

    Jsi_ValueMakeString(interp, ret, Jsi_DSFreeDup(&dStr));
    return JSI_OK;
}
    
#define FN_strreplace JSI_INFO("\
If the replace argument is a function, it is called with match,p1,p2,...,offset,string.  \
If called function is known to have 1 argument, it is called with just the match.\
Otherwise if the first argument is a regexp, the replace can contain the $ escapes: $&, $1, etc.")
static Jsi_RC StringReplaceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    /* Now handles perl regex flag extensions.*/
    const char *source_str;
    int source_len, bLen;
    const char *replace_str = NULL;
................................................................................
    { "charAt",     StringCharAtCmd,        1, 1, "index:number", .help="Return char at index", .retType=(uint)JSI_TT_STRING},
    { "charCodeAt", StringCharCodeAtCmd,    1, 1, "index:number", .help="Return char code at index", .retType=(uint)JSI_TT_NUMBER },
    { "concat",     StringConcatCmd,        0,-1, "str:string, ...", .help="Append one or more strings", .retType=(uint)JSI_TT_STRING },
    { "indexOf",    StringIndexOfCmd,       1, 2, "str:string, start:number", .help="Return index of char", .retType=(uint)JSI_TT_NUMBER },
    { "lastIndexOf",StringLastIndexOfCmd,   1, 2, "str:string, start:number", .help="Return index of last char", .retType=(uint)JSI_TT_NUMBER },
    { "match",      StringMatchCmd,         1, 1, "pattern:regexp|string", .help="Return array of matches", .retType=(uint)JSI_TT_ARRAY|JSI_TT_NULL },
    { "map",        StringMapCmd,           1, 2, "strMap:array, nocase:boolean=false", .help="Replaces characters in string based on the key-value pairs in strMap", .retType=(uint)JSI_TT_STRING },
    { "repeat",     StringRepeatCmd,        1, 1, "count:number", .help="Return count copies of string", .retType=(uint)JSI_TT_STRING, .flags=0, .info=FN_strreplace },
    { "replace",    StringReplaceCmd,       2, 2, "pattern:regexp|string, replace:string|function", .help="Regex/string replacement", .retType=(uint)JSI_TT_STRING, .flags=0, .info=FN_strreplace },
    { "search",     StringSearchCmd,        1, 1, "pattern:regexp|string", .help="Return index of first char matching pattern", .retType=(uint)JSI_TT_NUMBER },
    { "slice",      StringSliceCmd,         1, 2, "start:number, end:number", .help="Return section of string", .retType=(uint)JSI_TT_STRING },
    { "split",      StringSplitCmd,         0, 1, "char:string|null=void", .help="Split on char and return Array: null removes empty elements", .retType=(uint)JSI_TT_ARRAY },
    { "substr",     StringSubstrCmd,        0, 2, "start:number, length:number", .help="Return substring", .retType=(uint)JSI_TT_STRING },
    { "substring",  StringSubstringCmd,     0, 2, "start:number, end:number", .help="Return substring", .retType=(uint)JSI_TT_STRING },
    { "toLocaleLowerCase",StringToLowerCaseCmd,0, 0, "",.help="Lower case", .retType=(uint)JSI_TT_STRING },







|
<







 







|







584
585
586
587
588
589
590
591

592
593
594
595
596
597
598
...
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888

    Jsi_ValueMakeString(interp, ret, Jsi_DSFreeDup(&dStr));
    return JSI_OK;
}
    
#define FN_strreplace JSI_INFO("\
If the replace argument is a function, it is called with match,p1,p2,...,offset,string.  \
If called function is known to have 1 argument, it is called with just the match.")

static Jsi_RC StringReplaceCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    /* Now handles perl regex flag extensions.*/
    const char *source_str;
    int source_len, bLen;
    const char *replace_str = NULL;
................................................................................
    { "charAt",     StringCharAtCmd,        1, 1, "index:number", .help="Return char at index", .retType=(uint)JSI_TT_STRING},
    { "charCodeAt", StringCharCodeAtCmd,    1, 1, "index:number", .help="Return char code at index", .retType=(uint)JSI_TT_NUMBER },
    { "concat",     StringConcatCmd,        0,-1, "str:string, ...", .help="Append one or more strings", .retType=(uint)JSI_TT_STRING },
    { "indexOf",    StringIndexOfCmd,       1, 2, "str:string, start:number", .help="Return index of char", .retType=(uint)JSI_TT_NUMBER },
    { "lastIndexOf",StringLastIndexOfCmd,   1, 2, "str:string, start:number", .help="Return index of last char", .retType=(uint)JSI_TT_NUMBER },
    { "match",      StringMatchCmd,         1, 1, "pattern:regexp|string", .help="Return array of matches", .retType=(uint)JSI_TT_ARRAY|JSI_TT_NULL },
    { "map",        StringMapCmd,           1, 2, "strMap:array, nocase:boolean=false", .help="Replaces characters in string based on the key-value pairs in strMap", .retType=(uint)JSI_TT_STRING },
    { "repeat",     StringRepeatCmd,        1, 1, "count:number", .help="Return count copies of string", .retType=(uint)JSI_TT_STRING, .flags=0, .info=0 },
    { "replace",    StringReplaceCmd,       2, 2, "pattern:regexp|string, replace:string|function", .help="Regex/string replacement", .retType=(uint)JSI_TT_STRING, .flags=0, .info=FN_strreplace },
    { "search",     StringSearchCmd,        1, 1, "pattern:regexp|string", .help="Return index of first char matching pattern", .retType=(uint)JSI_TT_NUMBER },
    { "slice",      StringSliceCmd,         1, 2, "start:number, end:number", .help="Return section of string", .retType=(uint)JSI_TT_STRING },
    { "split",      StringSplitCmd,         0, 1, "char:string|null=void", .help="Split on char and return Array: null removes empty elements", .retType=(uint)JSI_TT_ARRAY },
    { "substr",     StringSubstrCmd,        0, 2, "start:number, length:number", .help="Return substring", .retType=(uint)JSI_TT_STRING },
    { "substring",  StringSubstringCmd,     0, 2, "start:number, end:number", .help="Return substring", .retType=(uint)JSI_TT_STRING },
    { "toLocaleLowerCase",StringToLowerCaseCmd,0, 0, "",.help="Lower case", .retType=(uint)JSI_TT_STRING },

Changes to src/jsiWebSocket.c.

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
...
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
...
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
...
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
...
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
...
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
...
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
...
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
...
778
779
780
781
782
783
784
785
786

787
788
789
790
791
792
793
...
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819

820







821
822
823
824
825
826








827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
...
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
...
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
...
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
...
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
....
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
....
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
....
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
....
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
....
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
....
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
....
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
....
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
....
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
....
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
....
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
....
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
....
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
....
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
....
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
....
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
....
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
....
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
....
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
....
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
....
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
....
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
....
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
....
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
....
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
....
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
....
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
....
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
....
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
....
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
....
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
....
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
....
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
....
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
....
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
    jsi_wsStatData stats;
    char *iface;
    const char* urlPrefix, *urlRedirect;
    const char *localhostName;
    const char *clientName;
    const char *clientIP;
    const char *useridPass;
    const char *realm, *templateFile;
    struct lws_context *instCtx;
    Jsi_Value *getRegexp, *post;
    unsigned int oldus;
    int opts;
    int hasOpts;
    int debug;
    int maxConnects;
................................................................................
    int wid;
    int sfd;
    bool isWebsock, echo;
    const char *clientName;
    const char *clientIP;
    int hdrSz[200]; // Space for up to 100 headers
    int hdrNum;     // Num of above.
    
    // Pointers to reset.
    Jsi_DString dHdrs; // Store header string here.
    Jsi_Stack *stack;
    Jsi_DString recvBuf; // To buffer recv when recvJSON is true.
    Jsi_Value *onClose, *onFilter, *onRecv, *onUpload, *onGet, *onUnknown, *rootdir, *headers;
    char *lastData;
#if (LWS_LIBRARY_VERSION_MAJOR>1)
................................................................................
typedef struct {
    Jsi_Value *val, *objVar;
    const char *arg;
    int triedLoad;
    int flags;
} jsi_wsHander;

static const char* const jsi_wsparam_names[] = { "text", "send", "file", "upload" }; 
static const char* jsi_wsparam_str = "text,send,file,upload"; 

#ifndef jsi_IIOF
#define jsi_IIOF .flags=JSI_OPT_INIT_ONLY
#define jsi_IIRO .flags=JSI_OPT_READ_ONLY
#endif

static Jsi_OptionSpec WPSStats[] =
................................................................................
    JSI_OPT(STRKEY, jsi_wsCmdObj, realm,      .help="Realm for basic auth (jsish)", ),
    JSI_OPT(INT,    jsi_wsCmdObj, recvBufMax, .help="Size limit of a websocket message", jsi_IIOF),
    JSI_OPT(INT,    jsi_wsCmdObj, recvBufTimeout,.help="Timeout for recv of a websock msg", jsi_IIOF),
    JSI_OPT(BOOL,   jsi_wsCmdObj, redirMax,   .help="Temporarily disable redirects when see more than this in 10 minutes"),
    JSI_OPT(STRING, jsi_wsCmdObj, rootdir,    .help="Directory to serve html from (\".\")"),
    JSI_OPT(CUSTOM, jsi_wsCmdObj, stats,      .help="Statistical data", jsi_IIRO, .custom=Jsi_Opt_SwitchSuboption, .data=WPSStats),
    JSI_OPT(TIME_T, jsi_wsCmdObj, startTime,  .help="Time of websocket start", jsi_IIRO),
    JSI_OPT(STRKEY, jsi_wsCmdObj, templateFile,.help="File name for markdeep template (template.shtml)"),
    JSI_OPT(STRKEY, jsi_wsCmdObj, urlPrefix,  .help="Prefix in url to strip from path; for reverse proxy"),
    JSI_OPT(STRKEY, jsi_wsCmdObj, urlRedirect,.help="Redirect when no url or / is given. Must match urlPrefix, if given"),
    JSI_OPT(BOOL,   jsi_wsCmdObj, use_ssl,    .help="Use https (for client)", jsi_IIOF),
    JSI_OPT(STRKEY, jsi_wsCmdObj, useridPass, .help="The USERID:PASSWORD to use for basic authentication"),
    JSI_OPT(OBJ,    jsi_wsCmdObj, version,    .help="WebSocket version info", jsi_IIRO),
    JSI_OPT_END(jsi_wsCmdObj, .help="Websocket options")
};
................................................................................
        return NULL;
    }
    int sid = ((sfd<<1)|ishttp);
    if (create)
        hPtr = Jsi_HashEntryNew(cmdPtr->pssTable, (void*)(intptr_t)sid, &isNew);
    else
        hPtr = Jsi_HashEntryFind(cmdPtr->pssTable, (void*)(intptr_t)sid);
    
    if (hPtr && !isNew)
        pss = (typeof(pss))Jsi_HashValueGet(hPtr);
    
    if (!pss) {
        if (!create)
            return NULL;        
        pss = (typeof(pss))Jsi_Calloc(1, sizeof(*pss));
        Jsi_HashValueSet(hPtr, pss);
        pss->isWebsock = !ishttp;
        pss->sig = JWS_SIG_PWS;
        pss->hPtr = hPtr;
        Jsi_HashValueSet(hPtr, pss);
        pss->cmdPtr = cmdPtr;
................................................................................
    if (pss->sig == 0)
        return;
    WSSIGASSERT(pss, PWS);
    if (pss->state == PWS_DEAD)
        return;
    if (pss->cmdPtr && pss->cmdPtr->debug>3)
        fprintf(stderr, "PSS DELETE: %p\n", pss);
        
    jsi_wsrecv_flush(pss->cmdPtr, pss);
    if (pss->hPtr) {
        Jsi_HashValueSet(pss->hPtr, NULL);
        Jsi_HashEntryDelete(pss->hPtr);
        pss->hPtr = NULL;
    }
    Jsi_Interp *interp = pss->cmdPtr->interp;
................................................................................
        pss->stats.sentLast = time(NULL);
    } else {
        pss->state = PWS_SENDERR;
        pss->stats.sentErrCnt++;
        pss->stats.sentErrLast = time(NULL);
        cmdPtr->stats.sentErrCnt++;
        cmdPtr->stats.sentErrLast = time(NULL);
    }    
    return m;
}

static int jsi_wsServeHeader(jsi_wsPss *pss, struct lws *wsi, int strLen,
    int code, const char *extra, const char *mime, Jsi_DString *jStr)
{
    uchar ubuf[JSI_BUFSIZ], *p=ubuf, *end = &ubuf[sizeof(ubuf)-1];
................................................................................
        }
        Jsi_ValueArraySet(interp, *vPtr, Jsi_ValueNewStringDup(interp, buf), n-1);
    }
}

static Jsi_RC jsi_wsGetCmd(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss* pss, struct lws *wsi,
    const char *inPtr, Jsi_Value *cmd, Jsi_DString *tStr)
{ 
    Jsi_RC jrc;
    Jsi_Value *retStr = Jsi_ValueNew1(interp);
    // 4 args: ws, id, url, query
    Jsi_Value *vpargs, *vargs[10];
    int n = 0;
    if (cmdPtr->deleted) return JSI_ERROR;
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
................................................................................
    MKLCBS(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ),
    MKLCBS(LWS_CALLBACK_RECEIVE_CLIENT_HTTP),
    MKLCBS(LWS_CALLBACK_COMPLETED_CLIENT_HTTP),
    MKLCBS(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE),
    MKLCBS(LWS_CALLBACK_HTTP_BIND_PROTOCOL),
    MKLCBS(LWS_CALLBACK_HTTP_DROP_PROTOCOL),
    MKLCBS(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION),
    MKLCBS(LWS_CALLBACK_RAW_RX_FILE), 
    MKLCBS(LWS_CALLBACK_RAW_WRITEABLE_FILE),
    MKLCBS(LWS_CALLBACK_RAW_CLOSE_FILE),
    MKLCBS(LWS_CALLBACK_USER),
#endif
#if (LWS_LIBRARY_VERSION_NUMBER>=3000000)
    MKLCBS(LWS_CALLBACK_SSL_INFO),
    MKLCBS(LWS_CALLBACK_CGI_PROCESS_ATTACH),
................................................................................
    return true;
}

static Jsi_RC jsi_wsTemplateFill(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, Jsi_Value *fn, Jsi_DString *dStr,
    int lvl) {
    Jsi_Value *fval;
    Jsi_RC rc = JSI_OK;
    char *cp, *ce, pref[] = "<!--#include file=\"", suffix[] = "\"-->";
    int flen, plen = sizeof(pref)-1, slen = sizeof(suffix)-1;

    Jsi_DString tStr = {};
    const char *cs = "<!-- Markdeep: --><style class=\"fallback\">body{visibility:hidden;}</style>\n"
                "<script>window.markdeepOptions={tocStyle:\"medium\"}</script>\n"
                "<!--#include file=\"$inc\"-->\n"
                "<script src=\"markdeep.min.js\"></script>\n"
                "<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility='visible')</script>";
    char *fs, *fname = Jsi_ValueString(interp, fn, &flen), *fend = fname;
................................................................................
        flen = fs-fname;
        fend = fs+1;
    }
    if (lvl>0) {
        rc = Jsi_FileRead(interp, fn, &tStr);
        cs = Jsi_DSValue(&tStr);
    } else {
        snprintf(fbuf, sizeof(fbuf), "%.*s%s", flen, fname, cmdPtr->templateFile);
        fval = Jsi_ValueNewStringConst(interp, fbuf, -1);
        Jsi_IncrRefCount(interp, fval);
        Jsi_StatBuf sb;
        int n = Jsi_Stat(interp, fval, &sb);
        if (!n && sb.st_size>0) {
            rc = Jsi_FileRead(interp, fval, &tStr);
            cs = Jsi_DSValue(&tStr);
        }
        Jsi_DecrRefCount(interp, fval);
    }
    
    while (rc == JSI_OK && cs) {
        char *ext = NULL;

        cp = Jsi_Strstr(cs, pref);







        if (!cp || !(ce=Jsi_Strstr(cp+plen, suffix))) {
            Jsi_DSAppend(dStr, cs, NULL);
            break;
        }
        Jsi_DSAppendLen(dStr, cs, cp-cs);
        if (cp[plen] == '$' && lvl == 0) {








            snprintf(fbuf, sizeof(fbuf), "%.*spages/%s.md", flen, fname, fend);
        } else {
            cp += plen;
            int elen = ce-cp;
            snprintf(fbuf, sizeof(fbuf), "%.*s/%.*s", flen, fname, elen, cp);
            ext = Jsi_Strrchr(fbuf, '.');
        }
        fval = Jsi_ValueNewStringConst(interp, fbuf, -1);
        Jsi_IncrRefCount(interp, fval);
        if (!ext || Jsi_Strcmp(ext, ".shtml"))
            rc = Jsi_FileRead(interp, fval, dStr);
        else
            rc = jsi_wsTemplateFill(interp, cmdPtr, fval, dStr, lvl+1);
        Jsi_DecrRefCount(interp, fval);
    
        cs = ce + slen;
        if (*cs == '\n')
            cs++;
    }
    Jsi_DSFree(&tStr);
    return rc;
    
}

// Handle http GET/POST
static int jsi_wsHttp(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, struct lws *wsi, void *user,
    struct lws_context *context, const char* inPtr, Jsi_DString *tStr, jsi_wsPss *pss)
{
    const char *ext = NULL;
................................................................................
    bool isMdi = 0;

    /* if a legal POST URL, let it continue and accept data */
    if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
        return 0;
    if (!pss)
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
  
    int uplen=(cmdPtr->urlPrefix?Jsi_Strlen(cmdPtr->urlPrefix):0);
    
    if (inPtr && cmdPtr->urlPrefix && !Jsi_Strncmp(inPtr, cmdPtr->urlPrefix, uplen))
        inPtr += uplen;

    if (cmdPtr->redirDisable) {// Try to defray redirect loops.
        if (difftime(now, cmdPtr->stats.redirLast)>=600)
            cmdPtr->redirDisable = 0;
        else
            cmdPtr->redirDisable--;
    }
    
    if ((cmdPtr->urlRedirect && (inPtr == 0 || *inPtr == 0 || !Jsi_Strcmp(inPtr, "/")) && !cmdPtr->redirDisable)
        && (inPtr = cmdPtr->urlRedirect) && inPtr[0]) {
        cmdPtr->stats.redirCnt++;
        // TODO: system time change can disrupt the following.
        if (cmdPtr->redirMax>0 && !cmdPtr->redirDisable && cmdPtr->redirMax>0 && cmdPtr->stats.redirLast
            && difftime(now, cmdPtr->stats.redirLast)<600 && ++cmdPtr->redirAllCnt>cmdPtr->redirMax)
            cmdPtr->redirDisable = 100;
................................................................................
                    vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
                    Jsi_IncrRefCount(interp, vpargs);
                    Jsi_Value *ret = Jsi_ValueNew1(interp);
                    bool rb = 0;
                    rc = Jsi_FunctionInvoke(interp, cmdPtr->onAuth, vpargs, &ret, NULL);
                    if (rc == JSI_OK)
                        rb = !Jsi_ValueIsFalse(interp, ret);
                        
                    Jsi_DecrRefCount(interp, vpargs);
                    Jsi_DecrRefCount(interp, ret);

                    if (rc != JSI_OK) {
                        Jsi_LogError("websock bad rcv eval");
                        return -1;
                    }
                    ok = rb;
                }        
            }
            Jsi_DSFree(&eStr);
            Jsi_DSFree(&bStr);
        }
        if (!ok) {
            const char *realm = (cmdPtr->realm?cmdPtr->realm:"jsish");
            int n = snprintf(buf, sizeof(buf), "Basic realm=\"%s\"", realm);
................................................................................
                    (unsigned char *)buf, n, &p, end))
                return -1;
            if (jsi_wsServeString(pss, wsi, "Password is required to access this page", 401, (char*)buffer, NULL)<0)
                return -1;
            return lws_http_transaction_completed(wsi);
        }
    }
    
    if (cmdPtr->onGet || pss->onGet) {
        Jsi_RC jrc;
        int rrv = 1;
        if (cmdPtr->getRegexp) {
            rrv = 0;
            jrc = Jsi_RegExpMatch(interp, cmdPtr->getRegexp, inPtr, &rrv, NULL);
            if (jrc != JSI_OK)
................................................................................
                case JSI_CONTINUE: inPtr = Jsi_DSValue(tStr); break;
                case JSI_BREAK: break;
                default: break;
            }
        }
    }
    ext = Jsi_Strrchr(inPtr, '.');
    
    Jsi_Value *rdir = (pss->rootdir?pss->rootdir:cmdPtr->rootdir);
    const char *rootDir = (rdir?Jsi_ValueString(cmdPtr->interp, rdir, NULL):"./");
    char statPath[PATH_MAX];
#if 0
    if (!Jsi_Strncmp(inPtr, "/jsi/", 5)) {
        // Get the path for system files, eg /zvfs or /usr/local/lib/jsi
        const char *loadFile = NULL;
................................................................................
            /* Lookup mime type in mimeTypes object. */
            Jsi_Value *mVal = Jsi_ValueObjLookup(interp, cmdPtr->mimeTypes, ext+1, 1);
            if (mVal)
                mime = Jsi_ValueString(interp, mVal, NULL);
        }
        if (!mime) {
            static const char* mtypes[] = {
                "html", "text/html", "js", "application/x-javascript", 
                "css", "text/css", "png", "image/png", "ico", "image/icon",
                "gif", "image/gif", "jpeg", "image/jpeg", 
                "jpg", "image/jpeg", "svg", "image/svg+xml",
                "json", "application/json", "txt", "text/plain",
                "jsi", "application/x-javascript", "cssi", "text/css",  
                0, 0
            };
            mime = "text/html";
            int i;
            for (i=0; mtypes[i]; i+=2)
                if (tolower(*eext) == mtypes[i][0] && !Jsi_Strncasecmp(eext, mtypes[i], -1)) {
                    mime = mtypes[i+1];
                    break;
                }
        }
        
        if (!Jsi_Strncasecmp(eext,"shtml", -1))
            essi = 1;
        
        if ((hPtr = Jsi_HashEntryFind(cmdPtr->handlers, ext)) && !cmdPtr->deleted) {
            /* Use interprete html eg. using jsi_wpp preprocessor */
            Jsi_DString jStr = {};
            Jsi_Value *vrc = NULL;
            int hrc = 0, strLen, evrc, isalloc=0;
            char *vStr, *hstr = NULL;
            jsi_wsHander *hdlPtr = (jsi_wsHander*)Jsi_HashValueGet(hPtr);
            Jsi_Value *hv = hdlPtr->val;
            
            if (Jsi_Strchr(buf, '\'') || Jsi_Strchr(buf, '\"')) {
                jsi_wsServeString(pss, wsi, "Can not handle quotes in url", 404, NULL, NULL);    
                return -1;                
            }
            cmdPtr->handlersPkg=1;
            
            // Attempt to load package and get function.
            if ((hdlPtr->flags&1) && cmdPtr->handlersPkg && Jsi_ValueIsString(interp, hv)
                && ((hstr = Jsi_ValueString(interp, hv, NULL)))) {
                vrc = Jsi_NameLookup(interp, hstr);
                if (!vrc) {
                    Jsi_Number pver = Jsi_PkgRequire(interp, hstr, 0);
                    if (pver >= 0)
                        vrc = Jsi_NameLookup(interp, hstr);
                }
                if (!vrc || !Jsi_ValueIsFunction(interp, vrc)) {
                    if (vrc)
                        Jsi_DecrRefCount(interp, vrc);
                    Jsi_LogError("Failed to autoload handle: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to autoload handler", 404, NULL, NULL);    
                    return -1;                
                }
                if (hdlPtr->val)
                    Jsi_DecrRefCount(interp, hdlPtr->val);
                hdlPtr->val = vrc;
                Jsi_IncrRefCount(interp, vrc);
                hv = vrc;
            }
            
            if ((hdlPtr->flags&2) && !hdlPtr->triedLoad && !hdlPtr->objVar && Jsi_ValueIsFunction(interp, hv)) {
                // Run command and from returned object get the parse function.
                hdlPtr->triedLoad = 1;
                Jsi_DSAppend(&jStr, "[null", NULL); 
                if (hdlPtr->arg)
                    Jsi_DSAppend(&jStr, ", ", hdlPtr->arg, NULL); // TODO: JSON encode.
                Jsi_DSAppend(&jStr, "]", NULL);
                vrc = Jsi_ValueNew1(interp);
                evrc = Jsi_FunctionInvokeJSON(interp, hv, Jsi_DSValue(&jStr), &vrc);
                if (Jsi_InterpGone(interp))
                    return -1;
                if (evrc != JSI_OK || !vrc || !Jsi_ValueIsObjType(interp, vrc, JSI_OT_OBJECT)) {
                    Jsi_LogError("Failed to load obj: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to load obj", 404, NULL, NULL);    
                    return -1;                
                }
                Jsi_Value *fvrc = Jsi_ValueObjLookup(interp, vrc, "parse", 0);
                if (!fvrc || !Jsi_ValueIsFunction(interp, fvrc)) {
                    Jsi_LogError("Failed to find parse: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to find parse", 404, NULL, NULL);    
                    return -1;                
                }
                hdlPtr->objVar = fvrc;
                Jsi_IncrRefCount(interp, fvrc);
                hv = vrc;
                
            }

            if (hdlPtr->objVar) {  // Call the obj.parse function.
                Jsi_DSAppend(&jStr, "[\"", buf, "\"]", NULL); // TODO: JSON encode.
                vrc = Jsi_ValueNew1(interp);
                evrc = Jsi_FunctionInvokeJSON(interp, hdlPtr->objVar, Jsi_DSValue(&jStr), &vrc);
                isalloc = 1;
................................................................................
            // Take result from vrc and return it.
            if (evrc != JSI_OK) {
                Jsi_LogError("failure in websocket handler");
            } else if ((!vrc) ||
                (!(vStr = Jsi_ValueString(interp, vrc, &strLen)))) {
                Jsi_LogError("failed to get result");
            } else {
                hrc = jsi_wsServeString(pss, wsi, vStr, 0, NULL, NULL);     
            }
            Jsi_DSFree(&jStr);
            if (isalloc)
                Jsi_DecrRefCount(interp, vrc);
            if (hrc<=0)
                return -1;
            return 1;
................................................................................
    if (!buf[0]) {
        if (cmdPtr->debug)
            fprintf(stderr, "empty file: %s\n", inPtr);
        return -1;
    }
    fname = Jsi_ValueNewStringDup(interp, buf);
    Jsi_IncrRefCount(interp, fname);
    
    Jsi_DString hStr = {};
    Jsi_StatBuf jsb;
    bool native = Jsi_FSNative(interp, fname);
    if (!ext || essi) {
        isMdi = 1;
        goto serve;
    }
................................................................................
        Jsi_DecrRefCount(interp, fname);
        goto done;
    }

serve:
    n = 0;
    // TODO: add automatic cookie mgmt?
/*      
    if (!strcmp((const char *)in, "/") &&
       !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
        gettimeofday(&tv, NULL);
        n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000",
            (unsigned int)tv.tv_sec,
            (unsigned int)tv.tv_usec);

................................................................................
                    (uchar *) stsStr,
                    sizeof(stsStr)-1, &p, (uchar *)buffer + sizeof(buffer)))
        goto bail;
    n = p - buffer;
    if (n>0)
        Jsi_DSAppendLen(&hStr, (char*)buffer, n);
    p = buffer;
    
    if (isgzip) {
        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING,
                    (unsigned char *)"gzip", n, &p, end))
            goto bail;
    }
    if (cmdPtr->headers && !jsi_wsAddHeader(interp, cmdPtr, wsi, cmdPtr->headers, &hStr))
        goto bail;
        
    if (pss->headers && !jsi_wsAddHeader(interp, cmdPtr, wsi, pss->headers, &hStr))
        goto bail;

    n = Jsi_DSLength(&hStr);
    
    if (native && !isMdi) {
        Jsi_DecrRefCount(interp, fname);

        int hrc = lws_serve_http_file(wsi, buf, mime, Jsi_DSValue(&hStr), Jsi_DSLength(&hStr));
        if (hrc<0) {
            if (cmdPtr->noWarn==0)
                fprintf(stderr, "can not serve file (%d): %s\n", hrc, buf);
................................................................................
    return rc;

bail:
    rc = 1;
    goto done;
}

static Jsi_RC jsi_wsrecv_callback(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss, 
    const char *inPtr, int nlen, bool isClose)
{
    Jsi_Value *vpargs, *vargs[10];
    Jsi_Value* func = NULL;
    if (Jsi_InterpGone(interp) || (cmdPtr->deleted && !isClose)) return JSI_ERROR;
    int n = 0;
    if (isClose)
................................................................................
    vargs[n++] = (cmdPtr->deleted?Jsi_ValueNewNull(interp):Jsi_ValueNewObj(interp, cmdPtr->fobj));
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss?pss->wid:0));
    if (!isClose) {
        if (nlen<=0)
            return JSI_OK;
        vargs[n++]  = Jsi_ValueNewBlob(interp, (uchar*)inPtr, nlen);
        if ((cmdPtr->echo||(pss && pss->echo)) && inPtr)
            Jsi_LogInfo("WS-RECV: %s\n", inPtr); 
    }
    vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, vargs, n, 0));
    Jsi_IncrRefCount(interp, vpargs);
    
    Jsi_Value *ret = Jsi_ValueNew1(interp);
    Jsi_ValueMakeUndef(interp, &ret);
    Jsi_RC rc;
        rc = Jsi_FunctionInvoke(interp, func, vpargs, &ret, NULL);
    if (rc == JSI_OK && Jsi_ValueIsUndef(interp, ret)==0 && !isClose) {
        /* TODO: should we handle callback return data??? */
    }
................................................................................
    jsi_wsPss *pss = (typeof(pss))data;
    jsi_wsCmdObj *cmdPtr = pss->cmdPtr;
    Jsi_Value* callPtr = (pss->onUpload?pss->onUpload:cmdPtr->onUpload);
    Jsi_Interp *interp = cmdPtr->interp;
    const char *str;
    int slen, n = 0;
    if (cmdPtr->deleted) return -1;
    
    Jsi_Obj *oarg1;
    Jsi_Value *vpargs, *vargs[10];
    if (state == LWS_UFS_OPEN)
        pss->file_length = 0;
    //id:number, filename:string, data:string, startpos:number, complete:boolean
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
................................................................................
    Jsi_Interp *interp = cmdPtr->interp;
    Jsi_Value* callPtr = NULL;
    int rc = 0, deflt = 0;

    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;
    
    if (cmdPtr->debug>=128)
        fprintf(stderr, "HTTP CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));

    switch (reason) {
#ifndef EXTERNAL_POLL
    case LWS_CALLBACK_GET_THREAD_ID:
    case LWS_CALLBACK_UNLOCK_POLL:
................................................................................
#endif

    default:
        deflt = 1;
        break;

    }
    
    if (deflt && cmdPtr->debug>16 && cmdPtr->debug<128) {
        fprintf(stderr, "HTTP CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
    }
        
    switch (reason) {
    case LWS_CALLBACK_WSI_DESTROY:
        break;

#if (LWS_LIBRARY_VERSION_MAJOR>1)    
    // Handle GET file download in client mode.
    case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: {
        char buffer[1024 + LWS_PRE];
        char *px = buffer + LWS_PRE;
        int lenx = sizeof(buffer) - LWS_PRE;

        if (lws_http_client_read(wsi, &px, &lenx) < 0)
................................................................................
            return -1;
        break;
    }
    case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
        if (jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 0) != JSI_OK)
            rc = 1;
        break;
        
    case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
        if (jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1) != JSI_OK)
            rc = 1;
        break;
        
    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
        if (cmdPtr->post) {
            unsigned char **p = (unsigned char **)in, *end = (*p) + len;
            int n = 0;
            char buf[100];
            Jsi_ValueString(interp, cmdPtr->post, &n);
            snprintf(buf, sizeof(buf), "%d", n);
................................................................................
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 1);
        if (pss)
            jsi_wsdeletePss(pss);
        break;
    case LWS_CALLBACK_WSI_CREATE:
        break;
        
    case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
        break;
        
    case LWS_CALLBACK_FILTER_HTTP_CONNECTION:
        if (cmdPtr->debug>1)
            fprintf(stderr, "FILTER CONNECTION: %s\n", inPtr);
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        jsi_wsgetUriArgValue(interp, wsi, &pss->query);
        
        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }
            
        Jsi_DSSetLength(&pss->dHdrs, 0);
        pss->hdrNum = jsi_wsGetHeaders(pss, wsi, &pss->dHdrs, pss->hdrSz, sizeof(pss->hdrSz)/sizeof(int));
    
        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            // 4 args: ws, id, url, bool
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);            
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 1);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onFilter, vpargs, &ret, NULL);
................................................................................
        client_ip[0] = 0;
        lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), client_name,
                                         sizeof(client_name), client_ip, sizeof(client_ip));
        if (client_name[0])
            cmdPtr->clientName = Jsi_KeyAdd(interp, client_name);
        if (client_ip[0])
            cmdPtr->clientIP = Jsi_KeyAdd(interp, client_ip);
        
        if (cmdPtr->clientName || cmdPtr->clientIP) {
            const char *loname = cmdPtr->localhostName;
            if (!loname) loname = "localhost";
            cmdPtr->instCtx = context;
            if (cmdPtr->debug>1)
                fprintf(stderr,  "Received network connect from %s (%s)\n",
                     cmdPtr->clientName, cmdPtr->clientIP);
................................................................................
        if (rc<0)
            return -1;
        if (rc==1) {
            goto try_to_reuse;
        }
        break;
    }
    
#if (LWS_LIBRARY_VERSION_MAJOR>1)
    case LWS_CALLBACK_HTTP_BODY: {
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 1);
        if (!pss) break;
        callPtr = (pss->onUpload?pss->onUpload:cmdPtr->onUpload);
        if (cmdPtr->maxUpload<=0 || !callPtr) {
................................................................................
        res = Jsi_DSValue(&pss->resultStr);
        if (!res[0]) {
            if (!pss->resultCode)
                res = "<html><body>Upload complete</body></html>";
            else
                res = "<html><body>Upload error</body></html>";
        }
        jsi_wsServeString(pss, wsi, res, pss->resultCode==JSI_OK?0:500, NULL, NULL);        
        if (cmdPtr->maxUpload<=0 || !callPtr) {
            if (cmdPtr->noWarn==0)
                fprintf(stderr, "Upload disabled: maxUpload=%d, onUpload=%p\n", cmdPtr->maxUpload, callPtr);
            return -1;
        }
        cmdPtr->stats.uploadEnd = pss->stats.uploadEnd = time(NULL);
        lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
................................................................................
    }

    default:
        break;
    }

    goto doret;
    
try_to_reuse:
    if (lws_http_transaction_completed(wsi))
         rc = -1;
    else
        rc = 0;
    goto doret;
      
doret:
    if (cmdPtr->debug>2)
        fprintf(stderr, "<---HTTP RET = %d\n", rc);
    return rc;
}

static int
................................................................................
    }
    Jsi_Interp *interp = cmdPtr->interp;
    char *inPtr = (char*)in;
    int sLen, n, rc =0;
    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;
    
    if (cmdPtr->debug>=32) {
        switch (reason) {
            case LWS_CALLBACK_SERVER_WRITEABLE:
            case LWS_CALLBACK_CLIENT_WRITEABLE:
                break;
            default:
                fprintf(stderr, "WS CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
        }
    }
        
    switch (reason) {
    case LWS_CALLBACK_PROTOCOL_INIT:
        if (cmdPtr->noWebsock)
            return 1;
        break;
        
    case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }
        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            if (!pss)
                pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 0);
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);
            
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 0);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
................................................................................
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            
            Jsi_Value *ret = Jsi_ValueNew1(interp);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onOpen, vpargs, &ret, NULL);

            Jsi_DecrRefCount(interp, vpargs);
            Jsi_DecrRefCount(interp, ret);
            if (rc != JSI_OK) 
                return Jsi_LogError("websock bad rcv eval");
        }        
        break;

    case LWS_CALLBACK_WSI_DESTROY:
        break;
        
    case LWS_CALLBACK_CLOSED:
    case LWS_CALLBACK_PROTOCOL_DESTROY:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;
        if (cmdPtr->onClose || pss->onClose) {
            rc = jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1);
            if (rc != JSI_OK) 
                return Jsi_LogError("websock bad rcv eval");
        }        
        jsi_wsdeletePss(pss);
        if (cmdPtr->stats.connectCnt<=0 && cmdPtr->onCloseLast && !Jsi_InterpGone(interp)) {
            Jsi_RC jrc;
            Jsi_Value *retStr = Jsi_ValueNew1(interp);
            // 1 args: ws
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
................................................................................
        pss->stats.msgQLen--;
        pss->state = PWS_SENT;
        p = (unsigned char *)data+LWS_PRE;
        sLen = Jsi_Strlen((char*)p);
        n = jsi_wswrite(pss, wsi, p, sLen, (pss->stats.isBinary?LWS_WRITE_BINARY:LWS_WRITE_TEXT));
        if (cmdPtr->debug>=10)
            fprintf(stderr, "WS:CLIENT WRITE(%p): %d=>%d\n", pss, sLen, n);
                               
        if (n >= 0) {
            cmdPtr->stats.sentCnt++;
            cmdPtr->stats.sentLast = time(NULL);
            pss->stats.sentCnt++;
            pss->stats.sentLast = time(NULL);
        } else {
            lwsl_err("ERROR %d writing to socket\n", n);
................................................................................
            pss->stats.sentErrLast = time(NULL);
            cmdPtr->stats.sentErrCnt++;
            cmdPtr->stats.sentErrLast = time(NULL);
            rc = 1;
        }
        break;
    }
        
    case LWS_CALLBACK_CLIENT_RECEIVE:
    case LWS_CALLBACK_RECEIVE:
    {
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;

        pss->stats.recvCnt++;
................................................................................
            if (rc != JSI_OK) {
                Jsi_LogError("websock bad rcv eval");
                return 1;
            }
        }
        lws_callback_on_writable_all_protocol(cmdPtr->context, lws_get_protocol(wsi));
        break;
 
    }
    default:
        break;
    }
    return rc;
}


static Jsi_RC WebSocketConfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
  
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_Value *opts = Jsi_ValueArrayIndex(interp, args, 0);
    if (cmdPtr->noConfig && opts && !Jsi_ValueIsString(interp, opts))
        return Jsi_LogError("WebSocket conf() is disabled for set");
    return Jsi_OptionsConf(interp, WSOptions, cmdPtr, opts, ret, 0);

}

static Jsi_RC WebSocketIdCmdOp(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr, int op)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_Value *valPtr = Jsi_ValueArrayIndex(interp, args, 0);
    Jsi_Number vid;
    if (Jsi_ValueGetNumber(interp, valPtr, &vid) != JSI_OK || vid < 0) 
        return Jsi_LogError("Expected connection number id");
    int id = (int)vid;
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
................................................................................
        WSSIGASSERT(tpss, PWS);
        if (tpss->wid == id && tpss->state != PWS_DEAD) {
            pss = tpss;
            break;
        }
    }

    if (!pss) 
        return Jsi_LogError("No such id: %d", id);
    switch (op) {
        case 0: return Jsi_OptionsConf(interp, WPSOptions, pss, Jsi_ValueArrayIndex(interp, args, 1), ret, 0);
        case 1: 
            jsi_wsDumpHeaders(cmdPtr, pss, Jsi_ValueArrayIndexToStr(interp, args, 1, NULL), ret);
            break;
        case 2: 
            if (!pss->spa) return JSI_OK;
            jsi_wsDumpQuery(cmdPtr, pss, Jsi_ValueArrayIndexToStr(interp, args, 1, NULL), ret);
            break;
    }
    return JSI_OK;
}

................................................................................
}


static Jsi_RC WebSocketIdsCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_DString dStr = {"["};
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    int cnt = 0;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
................................................................................
If a cmd is a function, it is called with a single arg: the file name.")
static Jsi_RC WebSocketHandlerCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_HashEntry *hPtr;
    jsi_wsHander *hdlPtr;
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    WSSIGASSERT(cmdPtr, OBJ);
    int argc = Jsi_ValueGetLength(interp, args);
    if (argc == 0) {
        Jsi_HashSearch search;
        Jsi_Obj* obj = Jsi_ObjNew(interp);
        for (hPtr = Jsi_HashSearchFirst(cmdPtr->handlers, &search); hPtr; hPtr = Jsi_HashSearchNext(&search)) {
................................................................................
            Jsi_DecrRefCount(interp, hdlPtr->val);
        Jsi_HashValueSet(hPtr, NULL);
        Jsi_HashEntryDelete(hPtr);
        Jsi_Free(hdlPtr);
        Jsi_ValueMakeStringDup(interp, ret, key);
        return JSI_OK;
    }
    if (Jsi_ValueIsFunction(interp, valPtr)==0 && Jsi_ValueIsString(interp, valPtr)==0) 
        return Jsi_LogError("expected string, function or null");
    Jsi_Value *argPtr = Jsi_ValueArrayIndex(interp, args, 2);
    if (argPtr) {
        if (Jsi_ValueIsNull(interp, argPtr))
            argPtr = NULL;
        else if (!Jsi_ValueIsString(interp, argPtr)) 
            return Jsi_LogError("expected a string");
    }
    hPtr = Jsi_HashEntryNew(cmdPtr->handlers, key, NULL);
    if (!hPtr)
        return JSI_ERROR;
    hdlPtr = (jsi_wsHander *)Jsi_Calloc(1, sizeof(*hdlPtr));
    Jsi_Value *flagPtr = Jsi_ValueArrayIndex(interp, args, 1);
................................................................................
#define FN_wssend JSI_INFO("\
Send a message to one (or all connections if -1). If not already a string, msg is formatted as JSON prior to the send.")

static Jsi_RC WebSocketSendCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply in a non-websock object");
    WSSIGASSERT(cmdPtr, OBJ);
    jsi_wsPss *pss;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 1);
    char *str = Jsi_ValueString(interp, arg, NULL);
    int id = -1, argc = Jsi_ValueGetLength(interp, args);
    Jsi_DString eStr = {};
    if (argc!=2)
        return Jsi_LogError("wrong args");
    Jsi_Number dnum;
    Jsi_Value *darg = Jsi_ValueArrayIndex(interp, args, 0);
    if (Jsi_ValueGetNumber(interp, darg, &dnum) != JSI_OK) 
        return Jsi_LogError("invalid id");
    id = (int)dnum;

    if (!str)
        str = (char*)Jsi_ValueGetDString(interp, arg, &eStr, JSI_OUTPUT_JSON);

    if (cmdPtr->echo)
        Jsi_LogInfo("WS-SEND: %s\n", str); 

    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
        pss = (jsi_wsPss*)Jsi_HashValueGet(hPtr);
        WSSIGASSERT(pss, PWS);
        if ((id<0 || pss->wid == id) && pss->state != PWS_DEAD) {
            if (!pss->stack)
                pss->stack = Jsi_StackNew();
            char *msg = (char*)Jsi_Malloc(LWS_PRE + Jsi_Strlen(str) + 1);
            Jsi_Strcpy(msg + LWS_PRE, str);
            Jsi_StackPush(pss->stack, msg);
            pss->stats.msgQLen++;
            if (!cmdPtr->echo && pss->echo)
                Jsi_LogInfo("WS-SEND: %s\n", str); 
        }
    }
   
    Jsi_DSFree(&eStr);
    return JSI_OK;
}

static Jsi_RC jsi_wsrecv_flush(jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss)
{
    int nlen = Jsi_DSLength(&pss->recvBuf);
................................................................................
    return n;
}

static Jsi_RC WebSocketUpdateCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply to non-websock object");
    if (!cmdPtr->noUpdate)
        jsi_wsService(cmdPtr);
    return JSI_OK;
}

static Jsi_RC jsi_wswebsockUpdate(Jsi_Interp *interp, void *data)
................................................................................

static Jsi_RC jsi_wswebsocketObjFree(Jsi_Interp *interp, void *data)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)data;
    WSSIGASSERT(cmdPtr,OBJ);
    cmdPtr->deleted = 1;
    struct lws_context *ctx = cmdPtr->context;
    if (ctx) 
        lws_context_destroy(ctx);
    cmdPtr->context = NULL;
    jsi_wswebsocketObjErase(cmdPtr);
    _JSI_MEMCLEAR(cmdPtr);
    Jsi_Free(cmdPtr);
    return JSI_OK;
}
................................................................................
    return JSI_OK;
}

static Jsi_RC WebSocketStatusCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr) 
        return Jsi_LogError("Apply to non-websock object");
#ifndef OMIT_LWS_WITH_SERVER_STATUS
    char cbuf[JSI_BUFSIZ*2];
    lws_json_dump_context(cmdPtr->context, cbuf, sizeof(cbuf), 0);
    return Jsi_JSONParse(interp, cbuf, ret, 0);
#else
    return Jsi_LogError("unsupported");
................................................................................
}

#define FN_WebSocket JSI_INFO("\
Create a websocket server/client object.  The server serves out pages to a web browser,\n\
which can use javascript to upgrade connection to a bidirectional websocket.")
static Jsi_RC WebSocketConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);
    

static Jsi_CmdSpec websockCmds[] = {
    { "WebSocket",  WebSocketConstructor, 0,  1, "options:object=void", .help="Create websocket server/client object", .retType=(uint)JSI_TT_USEROBJ, .flags=JSI_CMD_IS_CONSTRUCTOR, .info=FN_WebSocket, .opts=WSOptions },
    { "conf",       WebSocketConfCmd,     0,  1, "options:string|object=void",.help="Configure options", .retType=(uint)JSI_TT_ANY, .flags=0, .info=0, .opts=WSOptions },
    { "handler",    WebSocketHandlerCmd,  0,  4, "extension:string=void, cmd:string|function=void, arg:string|null=void, flags:number=0", 
        .help="Get/Set handler command for an extension", .retType=(uint)JSI_TT_FUNCTION|JSI_TT_ARRAY|JSI_TT_STRING|JSI_TT_VOID, .flags=0, .info=FN_wshandler },
    { "ids",        WebSocketIdsCmd,      0,  0, "", .help="Return list of ids", .retType=(uint)JSI_TT_ARRAY},
    { "idconf",     WebSocketIdConfCmd,   1,  2, "id:number, options:string|object=void",.help="Configure options for connect id", .retType=(uint)JSI_TT_ANY, .flags=0, .info=0, .opts=WPSOptions },
    { "header",     WebSocketHeaderCmd,   1,  2, "id:number, name:string=void",.help="Get one or all input headers for connect id", .retType=(uint)JSI_TT_STRING|JSI_TT_OBJECT|JSI_TT_VOID },
    { "query",      WebSocketQueryCmd,    1,  2, "id:number, name:string=void",.help="Get one or all query values for connect id", .retType=(uint)JSI_TT_STRING|JSI_TT_OBJECT|JSI_TT_VOID },
    { "send",       WebSocketSendCmd,     2,  2, "id:number, data:any", .help="Send a websocket message to id", .retType=(uint)JSI_TT_VOID, .flags=0, .info=FN_wssend },
    { "status",     WebSocketStatusCmd,   0,  0, "", .help="Return libwebsocket server status", .retType=(uint)JSI_TT_OBJECT|JSI_TT_VOID},
................................................................................
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (Jsi_InterpAccess(interp, NULL, JSI_INTACCESS_NETWORK ) != JSI_OK)
        return Jsi_LogError("WebSocket disallowed by Interp.noNetwork option");
    jsi_wsCmdObj *cmdPtr;
    Jsi_Value *toacc = NULL;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 0);
    
    cmdPtr = (jsi_wsCmdObj*)Jsi_Calloc(1, sizeof(*cmdPtr));
    cmdPtr->sig = JWS_SIG_OBJ;
    cmdPtr->port = 8080;
    cmdPtr->formParams = jsi_wsparam_str;
    cmdPtr->maxUpload = 100000;
    cmdPtr->interp = interp;
    cmdPtr->ietf_version = -1;
    cmdPtr->bufferPwr2 = 0;
    cmdPtr->ws_gid = -1;
    cmdPtr->ws_uid = -1;
    cmdPtr->startTime = time(NULL);
    cmdPtr->hasOpts = 1;
    cmdPtr->templateFile = "template.shtml";
    if ((arg != NULL && !Jsi_ValueIsNull(interp,arg))
        && Jsi_OptionsProcess(interp, WSOptions, cmdPtr, arg, 0) < 0) {
bail:
        jsi_wswebsocketObjFree(interp, cmdPtr);
        return JSI_ERROR;
    }
    if (cmdPtr->headers && (Jsi_ValueGetLength(interp, cmdPtr->headers)%2)) {
................................................................................
    if (cmdPtr->context == NULL) {
fail:
        Jsi_LogError("libwebsocket init failed on port %d (try another port?)", cmdPtr->info.port);
        goto bail;
    }
    if (cmdPtr->info.options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) {
        cmdPtr->info.options &= ~LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
        if (!lws_create_vhost(cmdPtr->context, &cmdPtr->info)) 
            goto fail;
    }
        
    if (cmdPtr->client) {
        struct lws_client_connect_info lci = {};
        lci.context = cmdPtr->context;
        lci.address = cmdPtr->address ? Jsi_ValueString(cmdPtr->interp, cmdPtr->address, NULL) : "127.0.0.1";
        lci.port = cmdPtr->port;
        lci.ssl_connection = cmdPtr->use_ssl;
        lci.path = cmdPtr->rootdir?Jsi_ValueString(cmdPtr->interp, cmdPtr->rootdir, NULL):"/";
................................................................................
        lci.ietf_version_or_minus_one = cmdPtr->ietf_version;
#if (LWS_LIBRARY_VERSION_MAJOR>1)
        if (cmdPtr->post)
            lci.method = "POST";
        else if (!Jsi_Strcmp(subprot, "get"))
            lci.method = "GET";
#endif
        
        if (NULL == lws_client_connect_via_info(&lci))
        {
            Jsi_LogError("websock connect failed");
            jsi_wswebsocketObjFree(interp, cmdPtr);
            return JSI_ERROR;
        }
    } else if (cmdPtr->port == 0) {
        // Extract actually used port.
        char *cp, cbuf[JSI_BUFSIZ*2];
        cbuf[0] = 0;
        lws_json_dump_context(cmdPtr->context, cbuf, sizeof(cbuf), 0);
        cp = Jsi_Strstr(cbuf, "\"port\":\"");
        if (cp) 
            cmdPtr->port = atoi(cp+8);
    }

    cmdPtr->event = Jsi_EventNew(interp, jsi_wswebsockUpdate, cmdPtr);
    if (Jsi_FunctionIsConstructor(funcPtr)) {
        toacc = _this;
    } else {
................................................................................
        jsi_wsHandlerAdd(interp, cmdPtr, ".htmli", "Jsi_Htmlpp",   jsi_wsStrValGet(cmdPtr, "htmli"), 1);
        jsi_wsHandlerAdd(interp, cmdPtr, ".cssi",  "Jsi_Csspp",    jsi_wsStrValGet(cmdPtr, "cssi"),  1);
        jsi_wsHandlerAdd(interp, cmdPtr, ".mdi",   "Jsi_Markdeep", jsi_wsStrValGet(cmdPtr, "mdi"),   1);
    }
    cmdPtr->fobj = fobj;
#ifdef LWS_LIBRARY_VERSION_NUMBER
    Jsi_JSONParseFmt(interp, &cmdPtr->version, "{libVer:\"%s\", hdrVer:\"%s\", hdrNum:%d, pkgVer:%d}",
        (char *)lws_get_library_version(), LWS_LIBRARY_VERSION, LWS_LIBRARY_VERSION_NUMBER, jsi_WsPkgVersion); 
#endif
    return JSI_OK;
}

static Jsi_RC Jsi_DoneWebSocket(Jsi_Interp *interp)
{
    Jsi_UserObjUnregister(interp, &websockobject);
................................................................................
        return Jsi_DoneWebSocket(interp);
#ifdef LWS_OPENSSL_SUPPORT
    Jsi_InterpAccess(interp, NULL, JSI_INTACCESS_SETSSL )
#endif
    Jsi_Hash *wsys;
    const char *libver = lws_get_library_version();
    int lvlen = sizeof(LWS_LIBRARY_VERSION)-1;
    if (Jsi_Strncmp(libver, LWS_LIBRARY_VERSION, lvlen) || !isspace(libver[lvlen])) 
        return Jsi_LogError("Library version mismatch: HDR:%s != LIB:%s", LWS_LIBRARY_VERSION, lws_get_library_version());
#if JSI_USE_STUBS
  if (Jsi_StubsInit(interp, 0) != JSI_OK)
    return JSI_ERROR;
#endif
    if (Jsi_PkgProvide(interp, "WebSocket", jsi_WsPkgVersion, Jsi_InitWebSocket) != JSI_OK)
        return JSI_ERROR;







|







 







|







 







|
|







 







|







 







|


|


|







 







|







 







|







 







|







 







|







 







|
|
>







 







|










|

|
>

>
>
>
>
>
>
>






>
>
>
>
>
>
>
>
|



|









|






|







 







|

|









|







 







|








|







 







|







 







|







 







|

|


|










|


|








|

|
|


|













|
|







|



|









|
|




|
|




|







 







|







 







|







 







|







 







|







|




|







 







|







 







|



|







 







|







 







|







 







|



|




|







 







|




|







 







|


|






|




|


|





|







 







|







 







|







 







|







 







|






|







 







|









|





|













|







 







|






|

|




|






|

|







 







|







 







|







 







|












|
|












|



|







 







|



|


|







 







|







 







|







 







|





|







 







|













|







|













|


|







 







|







 







|







 







|







 







|




|







 







|












|







 







|


|







 







|












|







 







|







 







|







124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
...
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
...
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
...
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
...
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
...
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
...
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
...
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
...
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
...
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
...
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
...
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
...
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
...
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
...
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
....
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
....
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
....
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
....
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
....
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
....
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
....
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
....
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
....
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
....
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
....
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
....
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
....
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
....
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
....
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
....
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
....
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
....
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
....
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
....
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
....
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
....
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
....
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
....
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
....
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
....
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
....
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
....
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
....
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
....
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
....
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
....
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
....
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
....
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
....
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
    jsi_wsStatData stats;
    char *iface;
    const char* urlPrefix, *urlRedirect;
    const char *localhostName;
    const char *clientName;
    const char *clientIP;
    const char *useridPass;
    const char *realm, *includeFile;
    struct lws_context *instCtx;
    Jsi_Value *getRegexp, *post;
    unsigned int oldus;
    int opts;
    int hasOpts;
    int debug;
    int maxConnects;
................................................................................
    int wid;
    int sfd;
    bool isWebsock, echo;
    const char *clientName;
    const char *clientIP;
    int hdrSz[200]; // Space for up to 100 headers
    int hdrNum;     // Num of above.

    // Pointers to reset.
    Jsi_DString dHdrs; // Store header string here.
    Jsi_Stack *stack;
    Jsi_DString recvBuf; // To buffer recv when recvJSON is true.
    Jsi_Value *onClose, *onFilter, *onRecv, *onUpload, *onGet, *onUnknown, *rootdir, *headers;
    char *lastData;
#if (LWS_LIBRARY_VERSION_MAJOR>1)
................................................................................
typedef struct {
    Jsi_Value *val, *objVar;
    const char *arg;
    int triedLoad;
    int flags;
} jsi_wsHander;

static const char* const jsi_wsparam_names[] = { "text", "send", "file", "upload" };
static const char* jsi_wsparam_str = "text,send,file,upload";

#ifndef jsi_IIOF
#define jsi_IIOF .flags=JSI_OPT_INIT_ONLY
#define jsi_IIRO .flags=JSI_OPT_READ_ONLY
#endif

static Jsi_OptionSpec WPSStats[] =
................................................................................
    JSI_OPT(STRKEY, jsi_wsCmdObj, realm,      .help="Realm for basic auth (jsish)", ),
    JSI_OPT(INT,    jsi_wsCmdObj, recvBufMax, .help="Size limit of a websocket message", jsi_IIOF),
    JSI_OPT(INT,    jsi_wsCmdObj, recvBufTimeout,.help="Timeout for recv of a websock msg", jsi_IIOF),
    JSI_OPT(BOOL,   jsi_wsCmdObj, redirMax,   .help="Temporarily disable redirects when see more than this in 10 minutes"),
    JSI_OPT(STRING, jsi_wsCmdObj, rootdir,    .help="Directory to serve html from (\".\")"),
    JSI_OPT(CUSTOM, jsi_wsCmdObj, stats,      .help="Statistical data", jsi_IIRO, .custom=Jsi_Opt_SwitchSuboption, .data=WPSStats),
    JSI_OPT(TIME_T, jsi_wsCmdObj, startTime,  .help="Time of websocket start", jsi_IIRO),
    JSI_OPT(STRKEY, jsi_wsCmdObj, includeFile, .help="Default file when no extension given (include.shtml)"),
    JSI_OPT(STRKEY, jsi_wsCmdObj, urlPrefix,  .help="Prefix in url to strip from path; for reverse proxy"),
    JSI_OPT(STRKEY, jsi_wsCmdObj, urlRedirect,.help="Redirect when no url or / is given. Must match urlPrefix, if given"),
    JSI_OPT(BOOL,   jsi_wsCmdObj, use_ssl,    .help="Use https (for client)", jsi_IIOF),
    JSI_OPT(STRKEY, jsi_wsCmdObj, useridPass, .help="The USERID:PASSWORD to use for basic authentication"),
    JSI_OPT(OBJ,    jsi_wsCmdObj, version,    .help="WebSocket version info", jsi_IIRO),
    JSI_OPT_END(jsi_wsCmdObj, .help="Websocket options")
};
................................................................................
        return NULL;
    }
    int sid = ((sfd<<1)|ishttp);
    if (create)
        hPtr = Jsi_HashEntryNew(cmdPtr->pssTable, (void*)(intptr_t)sid, &isNew);
    else
        hPtr = Jsi_HashEntryFind(cmdPtr->pssTable, (void*)(intptr_t)sid);

    if (hPtr && !isNew)
        pss = (typeof(pss))Jsi_HashValueGet(hPtr);

    if (!pss) {
        if (!create)
            return NULL;
        pss = (typeof(pss))Jsi_Calloc(1, sizeof(*pss));
        Jsi_HashValueSet(hPtr, pss);
        pss->isWebsock = !ishttp;
        pss->sig = JWS_SIG_PWS;
        pss->hPtr = hPtr;
        Jsi_HashValueSet(hPtr, pss);
        pss->cmdPtr = cmdPtr;
................................................................................
    if (pss->sig == 0)
        return;
    WSSIGASSERT(pss, PWS);
    if (pss->state == PWS_DEAD)
        return;
    if (pss->cmdPtr && pss->cmdPtr->debug>3)
        fprintf(stderr, "PSS DELETE: %p\n", pss);

    jsi_wsrecv_flush(pss->cmdPtr, pss);
    if (pss->hPtr) {
        Jsi_HashValueSet(pss->hPtr, NULL);
        Jsi_HashEntryDelete(pss->hPtr);
        pss->hPtr = NULL;
    }
    Jsi_Interp *interp = pss->cmdPtr->interp;
................................................................................
        pss->stats.sentLast = time(NULL);
    } else {
        pss->state = PWS_SENDERR;
        pss->stats.sentErrCnt++;
        pss->stats.sentErrLast = time(NULL);
        cmdPtr->stats.sentErrCnt++;
        cmdPtr->stats.sentErrLast = time(NULL);
    }
    return m;
}

static int jsi_wsServeHeader(jsi_wsPss *pss, struct lws *wsi, int strLen,
    int code, const char *extra, const char *mime, Jsi_DString *jStr)
{
    uchar ubuf[JSI_BUFSIZ], *p=ubuf, *end = &ubuf[sizeof(ubuf)-1];
................................................................................
        }
        Jsi_ValueArraySet(interp, *vPtr, Jsi_ValueNewStringDup(interp, buf), n-1);
    }
}

static Jsi_RC jsi_wsGetCmd(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss* pss, struct lws *wsi,
    const char *inPtr, Jsi_Value *cmd, Jsi_DString *tStr)
{
    Jsi_RC jrc;
    Jsi_Value *retStr = Jsi_ValueNew1(interp);
    // 4 args: ws, id, url, query
    Jsi_Value *vpargs, *vargs[10];
    int n = 0;
    if (cmdPtr->deleted) return JSI_ERROR;
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
................................................................................
    MKLCBS(LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ),
    MKLCBS(LWS_CALLBACK_RECEIVE_CLIENT_HTTP),
    MKLCBS(LWS_CALLBACK_COMPLETED_CLIENT_HTTP),
    MKLCBS(LWS_CALLBACK_CLIENT_HTTP_WRITEABLE),
    MKLCBS(LWS_CALLBACK_HTTP_BIND_PROTOCOL),
    MKLCBS(LWS_CALLBACK_HTTP_DROP_PROTOCOL),
    MKLCBS(LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION),
    MKLCBS(LWS_CALLBACK_RAW_RX_FILE),
    MKLCBS(LWS_CALLBACK_RAW_WRITEABLE_FILE),
    MKLCBS(LWS_CALLBACK_RAW_CLOSE_FILE),
    MKLCBS(LWS_CALLBACK_USER),
#endif
#if (LWS_LIBRARY_VERSION_NUMBER>=3000000)
    MKLCBS(LWS_CALLBACK_SSL_INFO),
    MKLCBS(LWS_CALLBACK_CGI_PROCESS_ATTACH),
................................................................................
    return true;
}

static Jsi_RC jsi_wsTemplateFill(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, Jsi_Value *fn, Jsi_DString *dStr,
    int lvl) {
    Jsi_Value *fval;
    Jsi_RC rc = JSI_OK;
    char *cp, *cp2, *ce, pref[] = "<!--#include file=\"", suffix[] = "\"-->",
        pref2[] = "<!--#include virtual=\"";
    int flen, plen, plen1 = sizeof(pref)-1,  plen2 = sizeof(pref2)-1, slen = sizeof(suffix)-1;
    Jsi_DString tStr = {};
    const char *cs = "<!-- Markdeep: --><style class=\"fallback\">body{visibility:hidden;}</style>\n"
                "<script>window.markdeepOptions={tocStyle:\"medium\"}</script>\n"
                "<!--#include file=\"$inc\"-->\n"
                "<script src=\"markdeep.min.js\"></script>\n"
                "<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility='visible')</script>";
    char *fs, *fname = Jsi_ValueString(interp, fn, &flen), *fend = fname;
................................................................................
        flen = fs-fname;
        fend = fs+1;
    }
    if (lvl>0) {
        rc = Jsi_FileRead(interp, fn, &tStr);
        cs = Jsi_DSValue(&tStr);
    } else {
        snprintf(fbuf, sizeof(fbuf), "%.*s%s", flen, fname, cmdPtr->includeFile);
        fval = Jsi_ValueNewStringConst(interp, fbuf, -1);
        Jsi_IncrRefCount(interp, fval);
        Jsi_StatBuf sb;
        int n = Jsi_Stat(interp, fval, &sb);
        if (!n && sb.st_size>0) {
            rc = Jsi_FileRead(interp, fval, &tStr);
            cs = Jsi_DSValue(&tStr);
        }
        Jsi_DecrRefCount(interp, fval);
    }

    while (rc == JSI_OK && cs) {
        char *ext = NULL, *sfname = fname;
        int sflen = flen;
        cp = Jsi_Strstr(cs, pref);
        cp2 = Jsi_Strstr(cs, pref2);
        plen = plen1;
        if (cp2 && (!cp || cp2<cp)) {
            cp = cp2;
            plen = plen2;
            sfname = Jsi_ValueString(interp, cmdPtr->rootdir, &sflen);
        }
        if (!cp || !(ce=Jsi_Strstr(cp+plen, suffix))) {
            Jsi_DSAppend(dStr, cs, NULL);
            break;
        }
        Jsi_DSAppendLen(dStr, cs, cp-cs);
        if (cp[plen] == '$' && lvl == 0) {
            char sfx[20] = {};
            uint i;
            for (i=0; i<sizeof(sfx); i++) {
                if ((sfx[i] = cp[plen+i+1]) == '"' || !sfx[i]) {
                    sfx[i] = 0;
                    break;
                }
            }
            snprintf(fbuf, sizeof(fbuf), "%.*s%s/%s.%s", flen, fname, sfx, fend, sfx);
        } else {
            cp += plen;
            int elen = ce-cp;
            snprintf(fbuf, sizeof(fbuf), "%.*s/%.*s", sflen, sfname, elen, cp);
            ext = Jsi_Strrchr(fbuf, '.');
        }
        fval = Jsi_ValueNewStringConst(interp, fbuf, -1);
        Jsi_IncrRefCount(interp, fval);
        if (!ext || Jsi_Strcmp(ext, ".shtml"))
            rc = Jsi_FileRead(interp, fval, dStr);
        else
            rc = jsi_wsTemplateFill(interp, cmdPtr, fval, dStr, lvl+1);
        Jsi_DecrRefCount(interp, fval);

        cs = ce + slen;
        if (*cs == '\n')
            cs++;
    }
    Jsi_DSFree(&tStr);
    return rc;

}

// Handle http GET/POST
static int jsi_wsHttp(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, struct lws *wsi, void *user,
    struct lws_context *context, const char* inPtr, Jsi_DString *tStr, jsi_wsPss *pss)
{
    const char *ext = NULL;
................................................................................
    bool isMdi = 0;

    /* if a legal POST URL, let it continue and accept data */
    if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI))
        return 0;
    if (!pss)
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);

    int uplen=(cmdPtr->urlPrefix?Jsi_Strlen(cmdPtr->urlPrefix):0);

    if (inPtr && cmdPtr->urlPrefix && !Jsi_Strncmp(inPtr, cmdPtr->urlPrefix, uplen))
        inPtr += uplen;

    if (cmdPtr->redirDisable) {// Try to defray redirect loops.
        if (difftime(now, cmdPtr->stats.redirLast)>=600)
            cmdPtr->redirDisable = 0;
        else
            cmdPtr->redirDisable--;
    }

    if ((cmdPtr->urlRedirect && (inPtr == 0 || *inPtr == 0 || !Jsi_Strcmp(inPtr, "/")) && !cmdPtr->redirDisable)
        && (inPtr = cmdPtr->urlRedirect) && inPtr[0]) {
        cmdPtr->stats.redirCnt++;
        // TODO: system time change can disrupt the following.
        if (cmdPtr->redirMax>0 && !cmdPtr->redirDisable && cmdPtr->redirMax>0 && cmdPtr->stats.redirLast
            && difftime(now, cmdPtr->stats.redirLast)<600 && ++cmdPtr->redirAllCnt>cmdPtr->redirMax)
            cmdPtr->redirDisable = 100;
................................................................................
                    vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
                    Jsi_IncrRefCount(interp, vpargs);
                    Jsi_Value *ret = Jsi_ValueNew1(interp);
                    bool rb = 0;
                    rc = Jsi_FunctionInvoke(interp, cmdPtr->onAuth, vpargs, &ret, NULL);
                    if (rc == JSI_OK)
                        rb = !Jsi_ValueIsFalse(interp, ret);

                    Jsi_DecrRefCount(interp, vpargs);
                    Jsi_DecrRefCount(interp, ret);

                    if (rc != JSI_OK) {
                        Jsi_LogError("websock bad rcv eval");
                        return -1;
                    }
                    ok = rb;
                }
            }
            Jsi_DSFree(&eStr);
            Jsi_DSFree(&bStr);
        }
        if (!ok) {
            const char *realm = (cmdPtr->realm?cmdPtr->realm:"jsish");
            int n = snprintf(buf, sizeof(buf), "Basic realm=\"%s\"", realm);
................................................................................
                    (unsigned char *)buf, n, &p, end))
                return -1;
            if (jsi_wsServeString(pss, wsi, "Password is required to access this page", 401, (char*)buffer, NULL)<0)
                return -1;
            return lws_http_transaction_completed(wsi);
        }
    }

    if (cmdPtr->onGet || pss->onGet) {
        Jsi_RC jrc;
        int rrv = 1;
        if (cmdPtr->getRegexp) {
            rrv = 0;
            jrc = Jsi_RegExpMatch(interp, cmdPtr->getRegexp, inPtr, &rrv, NULL);
            if (jrc != JSI_OK)
................................................................................
                case JSI_CONTINUE: inPtr = Jsi_DSValue(tStr); break;
                case JSI_BREAK: break;
                default: break;
            }
        }
    }
    ext = Jsi_Strrchr(inPtr, '.');

    Jsi_Value *rdir = (pss->rootdir?pss->rootdir:cmdPtr->rootdir);
    const char *rootDir = (rdir?Jsi_ValueString(cmdPtr->interp, rdir, NULL):"./");
    char statPath[PATH_MAX];
#if 0
    if (!Jsi_Strncmp(inPtr, "/jsi/", 5)) {
        // Get the path for system files, eg /zvfs or /usr/local/lib/jsi
        const char *loadFile = NULL;
................................................................................
            /* Lookup mime type in mimeTypes object. */
            Jsi_Value *mVal = Jsi_ValueObjLookup(interp, cmdPtr->mimeTypes, ext+1, 1);
            if (mVal)
                mime = Jsi_ValueString(interp, mVal, NULL);
        }
        if (!mime) {
            static const char* mtypes[] = {
                "html", "text/html", "js", "application/x-javascript",
                "css", "text/css", "png", "image/png", "ico", "image/icon",
                "gif", "image/gif", "jpeg", "image/jpeg",
                "jpg", "image/jpeg", "svg", "image/svg+xml",
                "json", "application/json", "txt", "text/plain",
                "jsi", "application/x-javascript", "cssi", "text/css",
                0, 0
            };
            mime = "text/html";
            int i;
            for (i=0; mtypes[i]; i+=2)
                if (tolower(*eext) == mtypes[i][0] && !Jsi_Strncasecmp(eext, mtypes[i], -1)) {
                    mime = mtypes[i+1];
                    break;
                }
        }

        if (!Jsi_Strncasecmp(eext,"shtml", -1))
            essi = 1;

        if ((hPtr = Jsi_HashEntryFind(cmdPtr->handlers, ext)) && !cmdPtr->deleted) {
            /* Use interprete html eg. using jsi_wpp preprocessor */
            Jsi_DString jStr = {};
            Jsi_Value *vrc = NULL;
            int hrc = 0, strLen, evrc, isalloc=0;
            char *vStr, *hstr = NULL;
            jsi_wsHander *hdlPtr = (jsi_wsHander*)Jsi_HashValueGet(hPtr);
            Jsi_Value *hv = hdlPtr->val;

            if (Jsi_Strchr(buf, '\'') || Jsi_Strchr(buf, '\"')) {
                jsi_wsServeString(pss, wsi, "Can not handle quotes in url", 404, NULL, NULL);
                return -1;
            }
            cmdPtr->handlersPkg=1;

            // Attempt to load package and get function.
            if ((hdlPtr->flags&1) && cmdPtr->handlersPkg && Jsi_ValueIsString(interp, hv)
                && ((hstr = Jsi_ValueString(interp, hv, NULL)))) {
                vrc = Jsi_NameLookup(interp, hstr);
                if (!vrc) {
                    Jsi_Number pver = Jsi_PkgRequire(interp, hstr, 0);
                    if (pver >= 0)
                        vrc = Jsi_NameLookup(interp, hstr);
                }
                if (!vrc || !Jsi_ValueIsFunction(interp, vrc)) {
                    if (vrc)
                        Jsi_DecrRefCount(interp, vrc);
                    Jsi_LogError("Failed to autoload handle: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to autoload handler", 404, NULL, NULL);
                    return -1;
                }
                if (hdlPtr->val)
                    Jsi_DecrRefCount(interp, hdlPtr->val);
                hdlPtr->val = vrc;
                Jsi_IncrRefCount(interp, vrc);
                hv = vrc;
            }

            if ((hdlPtr->flags&2) && !hdlPtr->triedLoad && !hdlPtr->objVar && Jsi_ValueIsFunction(interp, hv)) {
                // Run command and from returned object get the parse function.
                hdlPtr->triedLoad = 1;
                Jsi_DSAppend(&jStr, "[null", NULL);
                if (hdlPtr->arg)
                    Jsi_DSAppend(&jStr, ", ", hdlPtr->arg, NULL); // TODO: JSON encode.
                Jsi_DSAppend(&jStr, "]", NULL);
                vrc = Jsi_ValueNew1(interp);
                evrc = Jsi_FunctionInvokeJSON(interp, hv, Jsi_DSValue(&jStr), &vrc);
                if (Jsi_InterpGone(interp))
                    return -1;
                if (evrc != JSI_OK || !vrc || !Jsi_ValueIsObjType(interp, vrc, JSI_OT_OBJECT)) {
                    Jsi_LogError("Failed to load obj: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to load obj", 404, NULL, NULL);
                    return -1;
                }
                Jsi_Value *fvrc = Jsi_ValueObjLookup(interp, vrc, "parse", 0);
                if (!fvrc || !Jsi_ValueIsFunction(interp, fvrc)) {
                    Jsi_LogError("Failed to find parse: %s", hstr);
                    jsi_wsServeString(pss, wsi, "Failed to find parse", 404, NULL, NULL);
                    return -1;
                }
                hdlPtr->objVar = fvrc;
                Jsi_IncrRefCount(interp, fvrc);
                hv = vrc;

            }

            if (hdlPtr->objVar) {  // Call the obj.parse function.
                Jsi_DSAppend(&jStr, "[\"", buf, "\"]", NULL); // TODO: JSON encode.
                vrc = Jsi_ValueNew1(interp);
                evrc = Jsi_FunctionInvokeJSON(interp, hdlPtr->objVar, Jsi_DSValue(&jStr), &vrc);
                isalloc = 1;
................................................................................
            // Take result from vrc and return it.
            if (evrc != JSI_OK) {
                Jsi_LogError("failure in websocket handler");
            } else if ((!vrc) ||
                (!(vStr = Jsi_ValueString(interp, vrc, &strLen)))) {
                Jsi_LogError("failed to get result");
            } else {
                hrc = jsi_wsServeString(pss, wsi, vStr, 0, NULL, NULL);
            }
            Jsi_DSFree(&jStr);
            if (isalloc)
                Jsi_DecrRefCount(interp, vrc);
            if (hrc<=0)
                return -1;
            return 1;
................................................................................
    if (!buf[0]) {
        if (cmdPtr->debug)
            fprintf(stderr, "empty file: %s\n", inPtr);
        return -1;
    }
    fname = Jsi_ValueNewStringDup(interp, buf);
    Jsi_IncrRefCount(interp, fname);

    Jsi_DString hStr = {};
    Jsi_StatBuf jsb;
    bool native = Jsi_FSNative(interp, fname);
    if (!ext || essi) {
        isMdi = 1;
        goto serve;
    }
................................................................................
        Jsi_DecrRefCount(interp, fname);
        goto done;
    }

serve:
    n = 0;
    // TODO: add automatic cookie mgmt?
/*
    if (!strcmp((const char *)in, "/") &&
       !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
        gettimeofday(&tv, NULL);
        n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000",
            (unsigned int)tv.tv_sec,
            (unsigned int)tv.tv_usec);

................................................................................
                    (uchar *) stsStr,
                    sizeof(stsStr)-1, &p, (uchar *)buffer + sizeof(buffer)))
        goto bail;
    n = p - buffer;
    if (n>0)
        Jsi_DSAppendLen(&hStr, (char*)buffer, n);
    p = buffer;

    if (isgzip) {
        if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING,
                    (unsigned char *)"gzip", n, &p, end))
            goto bail;
    }
    if (cmdPtr->headers && !jsi_wsAddHeader(interp, cmdPtr, wsi, cmdPtr->headers, &hStr))
        goto bail;

    if (pss->headers && !jsi_wsAddHeader(interp, cmdPtr, wsi, pss->headers, &hStr))
        goto bail;

    n = Jsi_DSLength(&hStr);

    if (native && !isMdi) {
        Jsi_DecrRefCount(interp, fname);

        int hrc = lws_serve_http_file(wsi, buf, mime, Jsi_DSValue(&hStr), Jsi_DSLength(&hStr));
        if (hrc<0) {
            if (cmdPtr->noWarn==0)
                fprintf(stderr, "can not serve file (%d): %s\n", hrc, buf);
................................................................................
    return rc;

bail:
    rc = 1;
    goto done;
}

static Jsi_RC jsi_wsrecv_callback(Jsi_Interp *interp, jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss,
    const char *inPtr, int nlen, bool isClose)
{
    Jsi_Value *vpargs, *vargs[10];
    Jsi_Value* func = NULL;
    if (Jsi_InterpGone(interp) || (cmdPtr->deleted && !isClose)) return JSI_ERROR;
    int n = 0;
    if (isClose)
................................................................................
    vargs[n++] = (cmdPtr->deleted?Jsi_ValueNewNull(interp):Jsi_ValueNewObj(interp, cmdPtr->fobj));
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss?pss->wid:0));
    if (!isClose) {
        if (nlen<=0)
            return JSI_OK;
        vargs[n++]  = Jsi_ValueNewBlob(interp, (uchar*)inPtr, nlen);
        if ((cmdPtr->echo||(pss && pss->echo)) && inPtr)
            Jsi_LogInfo("WS-RECV: %s\n", inPtr);
    }
    vpargs = Jsi_ValueMakeObject(interp, NULL, Jsi_ObjNewArray(interp, vargs, n, 0));
    Jsi_IncrRefCount(interp, vpargs);

    Jsi_Value *ret = Jsi_ValueNew1(interp);
    Jsi_ValueMakeUndef(interp, &ret);
    Jsi_RC rc;
        rc = Jsi_FunctionInvoke(interp, func, vpargs, &ret, NULL);
    if (rc == JSI_OK && Jsi_ValueIsUndef(interp, ret)==0 && !isClose) {
        /* TODO: should we handle callback return data??? */
    }
................................................................................
    jsi_wsPss *pss = (typeof(pss))data;
    jsi_wsCmdObj *cmdPtr = pss->cmdPtr;
    Jsi_Value* callPtr = (pss->onUpload?pss->onUpload:cmdPtr->onUpload);
    Jsi_Interp *interp = cmdPtr->interp;
    const char *str;
    int slen, n = 0;
    if (cmdPtr->deleted) return -1;

    Jsi_Obj *oarg1;
    Jsi_Value *vpargs, *vargs[10];
    if (state == LWS_UFS_OPEN)
        pss->file_length = 0;
    //id:number, filename:string, data:string, startpos:number, complete:boolean
    vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
    vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
................................................................................
    Jsi_Interp *interp = cmdPtr->interp;
    Jsi_Value* callPtr = NULL;
    int rc = 0, deflt = 0;

    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;

    if (cmdPtr->debug>=128)
        fprintf(stderr, "HTTP CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));

    switch (reason) {
#ifndef EXTERNAL_POLL
    case LWS_CALLBACK_GET_THREAD_ID:
    case LWS_CALLBACK_UNLOCK_POLL:
................................................................................
#endif

    default:
        deflt = 1;
        break;

    }

    if (deflt && cmdPtr->debug>16 && cmdPtr->debug<128) {
        fprintf(stderr, "HTTP CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
    }

    switch (reason) {
    case LWS_CALLBACK_WSI_DESTROY:
        break;

#if (LWS_LIBRARY_VERSION_MAJOR>1)
    // Handle GET file download in client mode.
    case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: {
        char buffer[1024 + LWS_PRE];
        char *px = buffer + LWS_PRE;
        int lenx = sizeof(buffer) - LWS_PRE;

        if (lws_http_client_read(wsi, &px, &lenx) < 0)
................................................................................
            return -1;
        break;
    }
    case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
        if (jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 0) != JSI_OK)
            rc = 1;
        break;

    case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
        if (jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1) != JSI_OK)
            rc = 1;
        break;

    case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
        if (cmdPtr->post) {
            unsigned char **p = (unsigned char **)in, *end = (*p) + len;
            int n = 0;
            char buf[100];
            Jsi_ValueString(interp, cmdPtr->post, &n);
            snprintf(buf, sizeof(buf), "%d", n);
................................................................................
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 1);
        if (pss)
            jsi_wsdeletePss(pss);
        break;
    case LWS_CALLBACK_WSI_CREATE:
        break;

    case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
        break;

    case LWS_CALLBACK_FILTER_HTTP_CONNECTION:
        if (cmdPtr->debug>1)
            fprintf(stderr, "FILTER CONNECTION: %s\n", inPtr);
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        jsi_wsgetUriArgValue(interp, wsi, &pss->query);

        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }

        Jsi_DSSetLength(&pss->dHdrs, 0);
        pss->hdrNum = jsi_wsGetHeaders(pss, wsi, &pss->dHdrs, pss->hdrSz, sizeof(pss->hdrSz)/sizeof(int));

        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            // 4 args: ws, id, url, bool
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 1);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onFilter, vpargs, &ret, NULL);
................................................................................
        client_ip[0] = 0;
        lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), client_name,
                                         sizeof(client_name), client_ip, sizeof(client_ip));
        if (client_name[0])
            cmdPtr->clientName = Jsi_KeyAdd(interp, client_name);
        if (client_ip[0])
            cmdPtr->clientIP = Jsi_KeyAdd(interp, client_ip);

        if (cmdPtr->clientName || cmdPtr->clientIP) {
            const char *loname = cmdPtr->localhostName;
            if (!loname) loname = "localhost";
            cmdPtr->instCtx = context;
            if (cmdPtr->debug>1)
                fprintf(stderr,  "Received network connect from %s (%s)\n",
                     cmdPtr->clientName, cmdPtr->clientIP);
................................................................................
        if (rc<0)
            return -1;
        if (rc==1) {
            goto try_to_reuse;
        }
        break;
    }

#if (LWS_LIBRARY_VERSION_MAJOR>1)
    case LWS_CALLBACK_HTTP_BODY: {
        if (!pss)
            pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 1);
        if (!pss) break;
        callPtr = (pss->onUpload?pss->onUpload:cmdPtr->onUpload);
        if (cmdPtr->maxUpload<=0 || !callPtr) {
................................................................................
        res = Jsi_DSValue(&pss->resultStr);
        if (!res[0]) {
            if (!pss->resultCode)
                res = "<html><body>Upload complete</body></html>";
            else
                res = "<html><body>Upload error</body></html>";
        }
        jsi_wsServeString(pss, wsi, res, pss->resultCode==JSI_OK?0:500, NULL, NULL);
        if (cmdPtr->maxUpload<=0 || !callPtr) {
            if (cmdPtr->noWarn==0)
                fprintf(stderr, "Upload disabled: maxUpload=%d, onUpload=%p\n", cmdPtr->maxUpload, callPtr);
            return -1;
        }
        cmdPtr->stats.uploadEnd = pss->stats.uploadEnd = time(NULL);
        lws_return_http_status(wsi, HTTP_STATUS_OK, NULL);
................................................................................
    }

    default:
        break;
    }

    goto doret;

try_to_reuse:
    if (lws_http_transaction_completed(wsi))
         rc = -1;
    else
        rc = 0;
    goto doret;

doret:
    if (cmdPtr->debug>2)
        fprintf(stderr, "<---HTTP RET = %d\n", rc);
    return rc;
}

static int
................................................................................
    }
    Jsi_Interp *interp = cmdPtr->interp;
    char *inPtr = (char*)in;
    int sLen, n, rc =0;
    WSSIGASSERT(cmdPtr, OBJ);
    if (Jsi_InterpGone(interp))
        cmdPtr->deleted = 1;

    if (cmdPtr->debug>=32) {
        switch (reason) {
            case LWS_CALLBACK_SERVER_WRITEABLE:
            case LWS_CALLBACK_CLIENT_WRITEABLE:
                break;
            default:
                fprintf(stderr, "WS CALLBACK: len=%d, %p %d:%s\n", (int)len, user, reason, jsw_getReasonStr(reason));
        }
    }

    switch (reason) {
    case LWS_CALLBACK_PROTOCOL_INIT:
        if (cmdPtr->noWebsock)
            return 1;
        break;

    case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 1);
        Jsi_DSSet(&pss->url, inPtr);
        if (cmdPtr->instCtx == context && (cmdPtr->clientName[0] || cmdPtr->clientIP[0])) {
            pss->clientName = cmdPtr->clientName;
            pss->clientIP = cmdPtr->clientIP;
        }
        if (cmdPtr->onFilter && !cmdPtr->deleted) {
            if (!pss)
                pss = jsi_wsgetPss(cmdPtr, wsi, user, 1, 0);
            int killcon = 0, n = 0;
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10], *ret = Jsi_ValueNew1(interp);

            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vargs[n++] = Jsi_ValueNewBlob(interp, (uchar*)in, len);
            vargs[n++] = Jsi_ValueNewBoolean(interp, 0);
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);
            Jsi_ValueMakeUndef(interp, &ret);
................................................................................
            Jsi_Obj *oarg1;
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
            vargs[n++] = Jsi_ValueNewObj(interp, cmdPtr->fobj);
            vargs[n++] = Jsi_ValueNewNumber(interp, (Jsi_Number)(pss->wid));
            vpargs = Jsi_ValueMakeObject(interp, NULL, oarg1 = Jsi_ObjNewArray(interp, vargs, n, 0));
            Jsi_IncrRefCount(interp, vpargs);

            Jsi_Value *ret = Jsi_ValueNew1(interp);
            Jsi_ValueMakeUndef(interp, &ret);
            rc = Jsi_FunctionInvoke(interp, cmdPtr->onOpen, vpargs, &ret, NULL);

            Jsi_DecrRefCount(interp, vpargs);
            Jsi_DecrRefCount(interp, ret);
            if (rc != JSI_OK)
                return Jsi_LogError("websock bad rcv eval");
        }
        break;

    case LWS_CALLBACK_WSI_DESTROY:
        break;

    case LWS_CALLBACK_CLOSED:
    case LWS_CALLBACK_PROTOCOL_DESTROY:
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;
        if (cmdPtr->onClose || pss->onClose) {
            rc = jsi_wsrecv_callback(interp, cmdPtr, pss, inPtr, len, 1);
            if (rc != JSI_OK)
                return Jsi_LogError("websock bad rcv eval");
        }
        jsi_wsdeletePss(pss);
        if (cmdPtr->stats.connectCnt<=0 && cmdPtr->onCloseLast && !Jsi_InterpGone(interp)) {
            Jsi_RC jrc;
            Jsi_Value *retStr = Jsi_ValueNew1(interp);
            // 1 args: ws
            Jsi_Value *vpargs, *vargs[10];
            int n = 0;
................................................................................
        pss->stats.msgQLen--;
        pss->state = PWS_SENT;
        p = (unsigned char *)data+LWS_PRE;
        sLen = Jsi_Strlen((char*)p);
        n = jsi_wswrite(pss, wsi, p, sLen, (pss->stats.isBinary?LWS_WRITE_BINARY:LWS_WRITE_TEXT));
        if (cmdPtr->debug>=10)
            fprintf(stderr, "WS:CLIENT WRITE(%p): %d=>%d\n", pss, sLen, n);

        if (n >= 0) {
            cmdPtr->stats.sentCnt++;
            cmdPtr->stats.sentLast = time(NULL);
            pss->stats.sentCnt++;
            pss->stats.sentLast = time(NULL);
        } else {
            lwsl_err("ERROR %d writing to socket\n", n);
................................................................................
            pss->stats.sentErrLast = time(NULL);
            cmdPtr->stats.sentErrCnt++;
            cmdPtr->stats.sentErrLast = time(NULL);
            rc = 1;
        }
        break;
    }

    case LWS_CALLBACK_CLIENT_RECEIVE:
    case LWS_CALLBACK_RECEIVE:
    {
        pss = jsi_wsgetPss(cmdPtr, wsi, user, 0, 0);
        if (!pss) break;

        pss->stats.recvCnt++;
................................................................................
            if (rc != JSI_OK) {
                Jsi_LogError("websock bad rcv eval");
                return 1;
            }
        }
        lws_callback_on_writable_all_protocol(cmdPtr->context, lws_get_protocol(wsi));
        break;

    }
    default:
        break;
    }
    return rc;
}


static Jsi_RC WebSocketConfCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);

    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_Value *opts = Jsi_ValueArrayIndex(interp, args, 0);
    if (cmdPtr->noConfig && opts && !Jsi_ValueIsString(interp, opts))
        return Jsi_LogError("WebSocket conf() is disabled for set");
    return Jsi_OptionsConf(interp, WSOptions, cmdPtr, opts, ret, 0);

}

static Jsi_RC WebSocketIdCmdOp(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr, int op)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_Value *valPtr = Jsi_ValueArrayIndex(interp, args, 0);
    Jsi_Number vid;
    if (Jsi_ValueGetNumber(interp, valPtr, &vid) != JSI_OK || vid < 0)
        return Jsi_LogError("Expected connection number id");
    int id = (int)vid;
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
................................................................................
        WSSIGASSERT(tpss, PWS);
        if (tpss->wid == id && tpss->state != PWS_DEAD) {
            pss = tpss;
            break;
        }
    }

    if (!pss)
        return Jsi_LogError("No such id: %d", id);
    switch (op) {
        case 0: return Jsi_OptionsConf(interp, WPSOptions, pss, Jsi_ValueArrayIndex(interp, args, 1), ret, 0);
        case 1:
            jsi_wsDumpHeaders(cmdPtr, pss, Jsi_ValueArrayIndexToStr(interp, args, 1, NULL), ret);
            break;
        case 2:
            if (!pss->spa) return JSI_OK;
            jsi_wsDumpQuery(cmdPtr, pss, Jsi_ValueArrayIndexToStr(interp, args, 1, NULL), ret);
            break;
    }
    return JSI_OK;
}

................................................................................
}


static Jsi_RC WebSocketIdsCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    Jsi_DString dStr = {"["};
    jsi_wsPss *pss = NULL;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    int cnt = 0;
    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
................................................................................
If a cmd is a function, it is called with a single arg: the file name.")
static Jsi_RC WebSocketHandlerCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    Jsi_HashEntry *hPtr;
    jsi_wsHander *hdlPtr;
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    WSSIGASSERT(cmdPtr, OBJ);
    int argc = Jsi_ValueGetLength(interp, args);
    if (argc == 0) {
        Jsi_HashSearch search;
        Jsi_Obj* obj = Jsi_ObjNew(interp);
        for (hPtr = Jsi_HashSearchFirst(cmdPtr->handlers, &search); hPtr; hPtr = Jsi_HashSearchNext(&search)) {
................................................................................
            Jsi_DecrRefCount(interp, hdlPtr->val);
        Jsi_HashValueSet(hPtr, NULL);
        Jsi_HashEntryDelete(hPtr);
        Jsi_Free(hdlPtr);
        Jsi_ValueMakeStringDup(interp, ret, key);
        return JSI_OK;
    }
    if (Jsi_ValueIsFunction(interp, valPtr)==0 && Jsi_ValueIsString(interp, valPtr)==0)
        return Jsi_LogError("expected string, function or null");
    Jsi_Value *argPtr = Jsi_ValueArrayIndex(interp, args, 2);
    if (argPtr) {
        if (Jsi_ValueIsNull(interp, argPtr))
            argPtr = NULL;
        else if (!Jsi_ValueIsString(interp, argPtr))
            return Jsi_LogError("expected a string");
    }
    hPtr = Jsi_HashEntryNew(cmdPtr->handlers, key, NULL);
    if (!hPtr)
        return JSI_ERROR;
    hdlPtr = (jsi_wsHander *)Jsi_Calloc(1, sizeof(*hdlPtr));
    Jsi_Value *flagPtr = Jsi_ValueArrayIndex(interp, args, 1);
................................................................................
#define FN_wssend JSI_INFO("\
Send a message to one (or all connections if -1). If not already a string, msg is formatted as JSON prior to the send.")

static Jsi_RC WebSocketSendCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply in a non-websock object");
    WSSIGASSERT(cmdPtr, OBJ);
    jsi_wsPss *pss;
    Jsi_HashEntry *hPtr;
    Jsi_HashSearch cursor;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 1);
    char *str = Jsi_ValueString(interp, arg, NULL);
    int id = -1, argc = Jsi_ValueGetLength(interp, args);
    Jsi_DString eStr = {};
    if (argc!=2)
        return Jsi_LogError("wrong args");
    Jsi_Number dnum;
    Jsi_Value *darg = Jsi_ValueArrayIndex(interp, args, 0);
    if (Jsi_ValueGetNumber(interp, darg, &dnum) != JSI_OK)
        return Jsi_LogError("invalid id");
    id = (int)dnum;

    if (!str)
        str = (char*)Jsi_ValueGetDString(interp, arg, &eStr, JSI_OUTPUT_JSON);

    if (cmdPtr->echo)
        Jsi_LogInfo("WS-SEND: %s\n", str);

    for (hPtr = Jsi_HashSearchFirst(cmdPtr->pssTable, &cursor);
        hPtr != NULL; hPtr = Jsi_HashSearchNext(&cursor)) {
        pss = (jsi_wsPss*)Jsi_HashValueGet(hPtr);
        WSSIGASSERT(pss, PWS);
        if ((id<0 || pss->wid == id) && pss->state != PWS_DEAD) {
            if (!pss->stack)
                pss->stack = Jsi_StackNew();
            char *msg = (char*)Jsi_Malloc(LWS_PRE + Jsi_Strlen(str) + 1);
            Jsi_Strcpy(msg + LWS_PRE, str);
            Jsi_StackPush(pss->stack, msg);
            pss->stats.msgQLen++;
            if (!cmdPtr->echo && pss->echo)
                Jsi_LogInfo("WS-SEND: %s\n", str);
        }
    }

    Jsi_DSFree(&eStr);
    return JSI_OK;
}

static Jsi_RC jsi_wsrecv_flush(jsi_wsCmdObj *cmdPtr, jsi_wsPss *pss)
{
    int nlen = Jsi_DSLength(&pss->recvBuf);
................................................................................
    return n;
}

static Jsi_RC WebSocketUpdateCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply to non-websock object");
    if (!cmdPtr->noUpdate)
        jsi_wsService(cmdPtr);
    return JSI_OK;
}

static Jsi_RC jsi_wswebsockUpdate(Jsi_Interp *interp, void *data)
................................................................................

static Jsi_RC jsi_wswebsocketObjFree(Jsi_Interp *interp, void *data)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)data;
    WSSIGASSERT(cmdPtr,OBJ);
    cmdPtr->deleted = 1;
    struct lws_context *ctx = cmdPtr->context;
    if (ctx)
        lws_context_destroy(ctx);
    cmdPtr->context = NULL;
    jsi_wswebsocketObjErase(cmdPtr);
    _JSI_MEMCLEAR(cmdPtr);
    Jsi_Free(cmdPtr);
    return JSI_OK;
}
................................................................................
    return JSI_OK;
}

static Jsi_RC WebSocketStatusCmd(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    jsi_wsCmdObj *cmdPtr = (jsi_wsCmdObj*)Jsi_UserObjGetData(interp, _this, funcPtr);
    if (!cmdPtr)
        return Jsi_LogError("Apply to non-websock object");
#ifndef OMIT_LWS_WITH_SERVER_STATUS
    char cbuf[JSI_BUFSIZ*2];
    lws_json_dump_context(cmdPtr->context, cbuf, sizeof(cbuf), 0);
    return Jsi_JSONParse(interp, cbuf, ret, 0);
#else
    return Jsi_LogError("unsupported");
................................................................................
}

#define FN_WebSocket JSI_INFO("\
Create a websocket server/client object.  The server serves out pages to a web browser,\n\
which can use javascript to upgrade connection to a bidirectional websocket.")
static Jsi_RC WebSocketConstructor(Jsi_Interp *interp, Jsi_Value *args, Jsi_Value *_this,
    Jsi_Value **ret, Jsi_Func *funcPtr);


static Jsi_CmdSpec websockCmds[] = {
    { "WebSocket",  WebSocketConstructor, 0,  1, "options:object=void", .help="Create websocket server/client object", .retType=(uint)JSI_TT_USEROBJ, .flags=JSI_CMD_IS_CONSTRUCTOR, .info=FN_WebSocket, .opts=WSOptions },
    { "conf",       WebSocketConfCmd,     0,  1, "options:string|object=void",.help="Configure options", .retType=(uint)JSI_TT_ANY, .flags=0, .info=0, .opts=WSOptions },
    { "handler",    WebSocketHandlerCmd,  0,  4, "extension:string=void, cmd:string|function=void, arg:string|null=void, flags:number=0",
        .help="Get/Set handler command for an extension", .retType=(uint)JSI_TT_FUNCTION|JSI_TT_ARRAY|JSI_TT_STRING|JSI_TT_VOID, .flags=0, .info=FN_wshandler },
    { "ids",        WebSocketIdsCmd,      0,  0, "", .help="Return list of ids", .retType=(uint)JSI_TT_ARRAY},
    { "idconf",     WebSocketIdConfCmd,   1,  2, "id:number, options:string|object=void",.help="Configure options for connect id", .retType=(uint)JSI_TT_ANY, .flags=0, .info=0, .opts=WPSOptions },
    { "header",     WebSocketHeaderCmd,   1,  2, "id:number, name:string=void",.help="Get one or all input headers for connect id", .retType=(uint)JSI_TT_STRING|JSI_TT_OBJECT|JSI_TT_VOID },
    { "query",      WebSocketQueryCmd,    1,  2, "id:number, name:string=void",.help="Get one or all query values for connect id", .retType=(uint)JSI_TT_STRING|JSI_TT_OBJECT|JSI_TT_VOID },
    { "send",       WebSocketSendCmd,     2,  2, "id:number, data:any", .help="Send a websocket message to id", .retType=(uint)JSI_TT_VOID, .flags=0, .info=FN_wssend },
    { "status",     WebSocketStatusCmd,   0,  0, "", .help="Return libwebsocket server status", .retType=(uint)JSI_TT_OBJECT|JSI_TT_VOID},
................................................................................
    Jsi_Value **ret, Jsi_Func *funcPtr)
{
    if (Jsi_InterpAccess(interp, NULL, JSI_INTACCESS_NETWORK ) != JSI_OK)
        return Jsi_LogError("WebSocket disallowed by Interp.noNetwork option");
    jsi_wsCmdObj *cmdPtr;
    Jsi_Value *toacc = NULL;
    Jsi_Value *arg = Jsi_ValueArrayIndex(interp, args, 0);

    cmdPtr = (jsi_wsCmdObj*)Jsi_Calloc(1, sizeof(*cmdPtr));
    cmdPtr->sig = JWS_SIG_OBJ;
    cmdPtr->port = 8080;
    cmdPtr->formParams = jsi_wsparam_str;
    cmdPtr->maxUpload = 100000;
    cmdPtr->interp = interp;
    cmdPtr->ietf_version = -1;
    cmdPtr->bufferPwr2 = 0;
    cmdPtr->ws_gid = -1;
    cmdPtr->ws_uid = -1;
    cmdPtr->startTime = time(NULL);
    cmdPtr->hasOpts = 1;
    cmdPtr->includeFile = "include.shtml";
    if ((arg != NULL && !Jsi_ValueIsNull(interp,arg))
        && Jsi_OptionsProcess(interp, WSOptions, cmdPtr, arg, 0) < 0) {
bail:
        jsi_wswebsocketObjFree(interp, cmdPtr);
        return JSI_ERROR;
    }
    if (cmdPtr->headers && (Jsi_ValueGetLength(interp, cmdPtr->headers)%2)) {
................................................................................
    if (cmdPtr->context == NULL) {
fail:
        Jsi_LogError("libwebsocket init failed on port %d (try another port?)", cmdPtr->info.port);
        goto bail;
    }
    if (cmdPtr->info.options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) {
        cmdPtr->info.options &= ~LWS_SERVER_OPTION_EXPLICIT_VHOSTS;
        if (!lws_create_vhost(cmdPtr->context, &cmdPtr->info))
            goto fail;
    }

    if (cmdPtr->client) {
        struct lws_client_connect_info lci = {};
        lci.context = cmdPtr->context;
        lci.address = cmdPtr->address ? Jsi_ValueString(cmdPtr->interp, cmdPtr->address, NULL) : "127.0.0.1";
        lci.port = cmdPtr->port;
        lci.ssl_connection = cmdPtr->use_ssl;
        lci.path = cmdPtr->rootdir?Jsi_ValueString(cmdPtr->interp, cmdPtr->rootdir, NULL):"/";
................................................................................
        lci.ietf_version_or_minus_one = cmdPtr->ietf_version;
#if (LWS_LIBRARY_VERSION_MAJOR>1)
        if (cmdPtr->post)
            lci.method = "POST";
        else if (!Jsi_Strcmp(subprot, "get"))
            lci.method = "GET";
#endif

        if (NULL == lws_client_connect_via_info(&lci))
        {
            Jsi_LogError("websock connect failed");
            jsi_wswebsocketObjFree(interp, cmdPtr);
            return JSI_ERROR;
        }
    } else if (cmdPtr->port == 0) {
        // Extract actually used port.
        char *cp, cbuf[JSI_BUFSIZ*2];
        cbuf[0] = 0;
        lws_json_dump_context(cmdPtr->context, cbuf, sizeof(cbuf), 0);
        cp = Jsi_Strstr(cbuf, "\"port\":\"");
        if (cp)
            cmdPtr->port = atoi(cp+8);
    }

    cmdPtr->event = Jsi_EventNew(interp, jsi_wswebsockUpdate, cmdPtr);
    if (Jsi_FunctionIsConstructor(funcPtr)) {
        toacc = _this;
    } else {
................................................................................
        jsi_wsHandlerAdd(interp, cmdPtr, ".htmli", "Jsi_Htmlpp",   jsi_wsStrValGet(cmdPtr, "htmli"), 1);
        jsi_wsHandlerAdd(interp, cmdPtr, ".cssi",  "Jsi_Csspp",    jsi_wsStrValGet(cmdPtr, "cssi"),  1);
        jsi_wsHandlerAdd(interp, cmdPtr, ".mdi",   "Jsi_Markdeep", jsi_wsStrValGet(cmdPtr, "mdi"),   1);
    }
    cmdPtr->fobj = fobj;
#ifdef LWS_LIBRARY_VERSION_NUMBER
    Jsi_JSONParseFmt(interp, &cmdPtr->version, "{libVer:\"%s\", hdrVer:\"%s\", hdrNum:%d, pkgVer:%d}",
        (char *)lws_get_library_version(), LWS_LIBRARY_VERSION, LWS_LIBRARY_VERSION_NUMBER, jsi_WsPkgVersion);
#endif
    return JSI_OK;
}

static Jsi_RC Jsi_DoneWebSocket(Jsi_Interp *interp)
{
    Jsi_UserObjUnregister(interp, &websockobject);
................................................................................
        return Jsi_DoneWebSocket(interp);
#ifdef LWS_OPENSSL_SUPPORT
    Jsi_InterpAccess(interp, NULL, JSI_INTACCESS_SETSSL )
#endif
    Jsi_Hash *wsys;
    const char *libver = lws_get_library_version();
    int lvlen = sizeof(LWS_LIBRARY_VERSION)-1;
    if (Jsi_Strncmp(libver, LWS_LIBRARY_VERSION, lvlen) || !isspace(libver[lvlen]))
        return Jsi_LogError("Library version mismatch: HDR:%s != LIB:%s", LWS_LIBRARY_VERSION, lws_get_library_version());
#if JSI_USE_STUBS
  if (Jsi_StubsInit(interp, 0) != JSI_OK)
    return JSI_ERROR;
#endif
    if (Jsi_PkgProvide(interp, "WebSocket", jsi_WsPkgVersion, Jsi_InitWebSocket) != JSI_OK)
        return JSI_ERROR;

Changes to tools/mkref.jsi.

203
204
205
206
207
208
209
210
211

212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
            continue;
        }
    }
    index + "\n";
    var rlnks = '', resfx = '', repre = '<B>JSI REFERENCE</B> (See <a href="#System">System</a> for globals)\n';
    if (self.md) {
        //rlnks = '(Related: [Functions](functions.wiki), [Syntax](language.wiki)).\n';
        repre = '<a name="TOC"></a>\n(insert mainmenu.md.html here)\n'+repre;
        index = '';

        resfx = '\n<!-- Markdeep: --><style class="fallback">body{visibility:hidden;}</style>'
           // + '<script>window.markdeepOptions={tocStyle:"medium"}</script>'
            + '<script src="markdeep.min.js"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>';
    } else {
        rlnks = '(Related: [./functions.wiki|Functions], [./language.wiki|Syntax]).\n'
            + '<p>\n<a name="TOC"></a>\n';
        index += '<nowiki>\n';
        rv += "</nowiki>";
    }
    return repre + rlnks + index + rv + "<p>" + resfx;
}

if (Info.isMain()) {
    runModule(jsi_mkref);
}







|

>
|

|












203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
            continue;
        }
    }
    index + "\n";
    var rlnks = '', resfx = '', repre = '<B>JSI REFERENCE</B> (See <a href="#System">System</a> for globals)\n';
    if (self.md) {
        //rlnks = '(Related: [Functions](functions.wiki), [Syntax](language.wiki)).\n';
        //repre = '<a name="TOC"></a>\n(insert mainmenu.md.html here)\n'+repre;
        index = '';
        //rv = rv.map(['$', '\\$']);
        //resfx = '\n<!-- Markdeep: --><style class="fallback">body{visibility:hidden;}</style>';
           // + '<script>window.markdeepOptions={tocStyle:"medium"}</script>'
           // + '<script src="markdeep.min.js"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>';
    } else {
        rlnks = '(Related: [./functions.wiki|Functions], [./language.wiki|Syntax]).\n'
            + '<p>\n<a name="TOC"></a>\n';
        index += '<nowiki>\n';
        rv += "</nowiki>";
    }
    return repre + rlnks + index + rv + "<p>" + resfx;
}

if (Info.isMain()) {
    runModule(jsi_mkref);
}

Changes to tools/protos.jsi.

1
2
3
4
5
6
7
8
//JSI Command Prototypes: version 2.6.1
throw("NOT EXECUTABLE: USE FILE IN GEANY EDITOR FOR CMD LINE COMPLETION + GOTO TAG");

var Array = function(cmd,args) {};
Array.prototype.concat = function(...):array {};
Array.prototype.every = function(callback:function):any {};
Array.prototype.fill = function(value:any, start:number=0, end:number=-1):array {};
Array.prototype.filter = function(callback:function, this:object=void):array {};
|







1
2
3
4
5
6
7
8
//JSI Command Prototypes: version 2.6.2
throw("NOT EXECUTABLE: USE FILE IN GEANY EDITOR FOR CMD LINE COMPLETION + GOTO TAG");

var Array = function(cmd,args) {};
Array.prototype.concat = function(...):array {};
Array.prototype.every = function(callback:function):any {};
Array.prototype.fill = function(value:any, start:number=0, end:number=-1):array {};
Array.prototype.filter = function(callback:function, this:object=void):array {};

Changes to www/reference.wiki.

1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
....
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
<tr><td>charAt</td><td>charAt(index:number):string </td><td>Return char at index.</td></tr>
<tr><td>charCodeAt</td><td>charCodeAt(index:number):number </td><td>Return char code at index.</td></tr>
<tr><td>concat</td><td>concat(str:string, ...):string </td><td>Append one or more strings.</td></tr>
<tr><td>indexOf</td><td>indexOf(str:string, start:number):number </td><td>Return index of char.</td></tr>
<tr><td>lastIndexOf</td><td>lastIndexOf(str:string, start:number):number </td><td>Return index of last char.</td></tr>
<tr><td>map</td><td>map(strMap:array, nocase:boolean=false):string </td><td>Replaces characters in string based on the key-value pairs in strMap.</td></tr>
<tr><td>match</td><td>match(pattern:regexp|string):array|null </td><td>Return array of matches.</td></tr>
<tr><td>repeat</td><td>repeat(count:number):string </td><td>Return count copies of string. If the replace argument is a function, it is called with match,p1,p2,...,offset,string.  If called function is known to have 1 argument, it is called with just the match.Otherwise if the first argument is a regexp, the replace can contain the $ escapes: $&, $1, etc.</td></tr>
<tr><td>replace</td><td>replace(pattern:regexp|string, replace:string|function):string </td><td>Regex/string replacement. If the replace argument is a function, it is called with match,p1,p2,...,offset,string.  If called function is known to have 1 argument, it is called with just the match.Otherwise if the first argument is a regexp, the replace can contain the $ escapes: $&, $1, etc.</td></tr>
<tr><td>search</td><td>search(pattern:regexp|string):number </td><td>Return index of first char matching pattern.</td></tr>
<tr><td>slice</td><td>slice(start:number, end:number):string </td><td>Return section of string.</td></tr>
<tr><td>split