fnc

Check-in Differences
Login

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

Difference From 6eaea2465f To 96e142ed8e

2021-08-28 15:43
[39f43fcefb] Bump version number: version 0.2 alpha. (user: mark tags: trunk, version-0.2)
2021-08-28 15:31
[96e142ed8e] Merge [9d412bdfacee14e3|libf-amalgamation] into trunk. (user: mark tags: trunk, version-0.2)
2021-08-27 14:42
[9d412bdfac] Closed-Leaf: Bring branch up to parity with [trunk]. (user: mark tags: libf-amalgamation)
2021-08-27 13:28
[b413600889] Implement simpler, more robust fix for line wrap. (user: mark tags: trunk)
2021-08-05 11:32
[7dfce7ca8d] Add commit branch/tags to search results. (user: mark tags: trunk)
2021-08-05 08:12
[6eaea2465f] Initial commit of fnc. (user: mark tags: trunk)

Changes to Makefile.in.

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








-
+


-
-
+
+



-
-
-









-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
















-
+


-
-
-
-
-
-
-
-
-







#!/usr/bin/make # help out emacs
#
# Top-level autosetup-filtered Makefile for libfossil. This particular
# build is for Unix platforms with GNU Make 3.81+.
all:
.NOTPARALLEL: # stop subdir makes and reconfigure from launching
              # multiple times concurrently.
include config.make
ShakeNMake.CISH_SOURCES := $(wildcard *.c) $(wildcard $(SRC.DIR)/*.c)
ShakeNMake.CISH_SOURCES := $(wildcard *.c)
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
clean-.: clean-doc clean-fnc clean-bindings
distclean-.: distclean-doc distclean-fnc distclean-bindings
clean-.: clean-fnc clean-src
distclean-.: distclean-fnc distclean-src
include shakenmake.make
MAIN_MAKEFILES := $(PACKAGE.MAKEFILE) $(ShakeNMake.MAKEFILE) @AUTODEPS@

AUTOCONFIG_H := @srcdir@/include/fossil-scm/autoconfig.h
#SRCDIR := @top_srcdir@/src
SRCDIR := @srcdir@/src

define CALL.SUBDIR
endef
SUBDIRS := src fnc
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
#$(eval $(call ShakeNMake.CALL.SUBDIR,src))
subdir-fnc: subdir-src
all: subdir-src subdir-fnc


ifeq (1,@LIBFOSSIL_STATIC@)
  THELIB.LIB := libfossil$(ShakeNMake.EXTENSIONS.LIB)
else
  THELIB.LIB :=
endif
ifeq (1,@LIBFOSSIL_SHARED@)
  THELIB.DLL := libfossil$(ShakeNMake.EXTENSIONS.DLL)
else
  THELIB.DLL :=
endif

ifneq (,$(THELIB.DLL)$(THELIB.LIB))
src/$(THELIB.DLL):
	$(MAKE) -C src
src/$(THELIB.LIB):
	$(MAKE) -C src
$(THELIB.DLL): src/$(THELIB.DLL)
	ln -sf src/$(THELIB.DLL) .
$(THELIB.LIB): src/$(THELIB.LIB)
	ln -sf src/$(THELIB.LIB) .
all: $(THELIB.DLL) $(THELIB.LIB)
CLEAN_FILES += $(wildcard libfossil.so libfossil.dll libfossil.a)
endif
DISTCLEAN_FILES += config.make

########################################################################
# Other stuff...

ifeq ($(MAKE_COMPILATION_DB),yes)
all: compile_commands.json
compile_commands.json:
	@$(RM) $@
	sed -e '1s/^/[\'$$'\n''/' -e '$$s/,$$/\'$$'\n'']/' $(compdb_dir)/*.o.json > $@+
	@if test -s $@+; then mv $@+ $@; else $(RM) $@+; fi
endif

install: all
	@echo 'No installation rules yet.'

DISTCLEAN_FILES += $(AUTOCONFIG_H) Makefile config.log autosetup/jimsh0 \
DISTCLEAN_FILES += Makefile config.log autosetup/jimsh0 \
	$(wildcard compile_commands/*) compile_commands.json+

# automake compatibility. do nothing for all these targets
#EMPTY_AUTOMAKE_TARGETS := dvi pdf ps info html tags ctags mostlyclean maintainer-clean check installcheck installdirs \
# install-pdf install-ps install-info install-html -install-dvi uninstall install-exec install-data distdir
#.PHONY: $(EMPTY_AUTOMAKE_TARGETS)
#$(EMPTY_AUTOMAKE_TARGETS):

## @top_srcdir@/Makefile.in: # b/c AUTODEPS contains this name (it probably shouldn't)
#$(FSL.OBJ): @AUTODEPS@ @top_srcdir@/Makefile @top_srcdir@/config.make

# Reconfigure if needed
ifeq ($(findstring clean,$(MAKECMDGOALS)),)
@top_srcdir@/config.make: @AUTODEPS@ @top_srcdir@/config.make.in
	@@AUTOREMAKE@
@top_srcdir@/Makefile: @AUTODEPS@ @top_srcdir@/Makefile.in
	chmod +w $@
	@@AUTOREMAKE@

Changes to auto.def.

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





-

-
-







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+








-
-
-
-







# vim:se syn=tcl:
#
use cc cc-shared cc-lib

options {
    shared=1 => "Disable build of a shared library."
    no-debug=0 => "Disable debug build options."
    amal => "Generates a conservative config file for the amalgamation build."
    static=1 => "Disable build of a static library."
    loud=0 => "Enables 'loud' build mode."
}


# autosetup interceps 'debug' and 'enable-debug' flags :/
#    prefix:=[get-env HOME /usr/local] -> "Installation prefix."

define FSL_PACKAGE_NAME "libfossil"
define FSL_LIBRARY_VERSION 0.0.1-alphabeta

########################################################################
# See if we can get the fossil schema version from the current
# checkout. If so, use that one, otherwise fall back to some hard-coded
# default.
set auxSchema {}
set contentSchema {}

set fossilBin [find-an-executable fossil]
if {[string length $fossilBin] > 0} {
    puts "Found fossil binary: $fossilBin"
    catch {
        set auxSchema [string trimright \
                       [exec echo \
                            {SELECT value FROM config WHERE name='aux-schema';} \
                            "|" $fossilBin sqlite3] ]
        set contentSchema [string trimright \
                       [exec echo \
                            {SELECT value FROM config WHERE name='content-schema';} \
                            "|" $fossilBin sqlite3] ]
    } ex
#    puts "exception=$ex"
}

#set uname [exec $fossilBin version -v "|" grep "Schema version"]
#puts "uname=$uname"
#return

if {[string length $auxSchema] == 16} {
    puts "Got aux-schema value from current repo: $auxSchema"
    puts "Got content-schema value from current repo: $contentSchema"
} else {
    set auxSchema "2015-01-24"
    # "2011-04-25 19:50"
    set contentSchema 2
    puts "Using hard-coded aux-schema: $auxSchema"
    puts "Using hard-coded content-schema: $contentSchema"
}
define FSL_AUX_SCHEMA $auxSchema
define FSL_CONTENT_SCHEMA $contentSchema

########################################################################
# Grab the code version and timestamp from manifest.uuid and manifest
# files...
#
# This requires that the repo is NOT generating delta manifests. As
# of 2020-03, the libfossil server uses the forbid-delta-manifest
# setting.
set timestamp [clock format [clock seconds] \
                   -gmt 1 -format "%Y-%m-%d %H:%M"]
set fp [open "manifest.uuid" r]
set libVersionHash [string trimright [read $fp]]
close $fp
set fp [open "manifest"]
# Get C-card:
gets $fp
# Get D-card:
gets $fp line
close $fp
unset fp
set manifestTimestamp [string map {"D " "" T " "} $line]
unset line
puts "libVersionHash = $libVersionHash"
puts "manifestTimestamp = $manifestTimestamp"

set FSL_PLATFORM_CONFIG_H "
\#define FSL_LIB_VERSION_HASH \"$libVersionHash\"
\#define FSL_LIB_VERSION_TIMESTAMP \"$manifestTimestamp UTC\"
\#define FSL_LIB_CONFIG_TIME \"$timestamp GMT\"
\#if defined(_MSC_VER)
\#define FSL_PLATFORM_OS \"windows\"
\#define FSL_PLATFORM_IS_WINDOWS 1
\#define FSL_PLATFORM_IS_UNIX 0
\#define FSL_PLATFORM_PLATFORM \"windows\"
\#define FSL_PLATFORM_PATH_SEPARATOR \";\"
\#define FSL_CHECKOUTDB_NAME \"./_FOSSIL_\"
\/* define a __func__ compatibility macro *\/
\#if _MSC_VER < 1500    /* (vc9.0; dev studio 2008) */
/* sorry; cant do much better than nothing at all on those earlier ones */
\#define __func__ \"(func)\"
\#else
\#define __func__ __FUNCTION__
\#endif
/* for the time being at least, don't complain about there being secure crt alternatives: */
\#ifndef _CRT_SECURE_NO_WARNINGS
\#define _CRT_SECURE_NO_WARNINGS
\#endif
/* for the time being at least, don't complain about using POSIX names instead of ISO C++: */
\#pragma warning ( disable : 4996 )
/* for the time being at least, suppresss some int conversion warnings */
\#pragma warning ( disable : 4244 )     /*'fsl_size_t' to 'int'; this masks other problems that should be fixed*/
\#pragma warning ( disable : 4761 )     /*'integral size mismatch in argument'; more size_t problems*/
\#pragma warning ( disable : 4267 )     /*'size_t' to 'int'; crops up especially in 64-bit builds*/
/* these were extracted from fossil's unistd.h */
\#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
\#include <io.h>
\#elif defined(__MINGW32__)
\#define FSL_PLATFORM_OS \"mingw\"
\#define FSL_PLATFORM_IS_WINDOWS 1
\#define FSL_PLATFORM_IS_UNIX 0
\#define FSL_PLATFORM_PLATFORM \"windows\"
\#define FSL_PLATFORM_PATH_SEPARATOR \";\"
\#define FSL_CHECKOUTDB_NAME \"./.fslckout\"
\#elif defined(__CYGWIN__)
\#define FSL_PLATFORM_OS \"cygwin\"
\#define FSL_PLATFORM_IS_WINDOWS 0
\#define FSL_PLATFORM_IS_UNIX 1
\#define FSL_PLATFORM_PLATFORM \"unix\"
\#define FSL_PLATFORM_PATH_SEPARATOR \":\"
\#define FSL_CHECKOUTDB_NAME \"./_FOSSIL_\"
\#else
\#define FSL_PLATFORM_OS \"unknown\"
\#define FSL_PLATFORM_IS_WINDOWS 0
\#define FSL_PLATFORM_IS_UNIX 1
\#define FSL_PLATFORM_PLATFORM \"unix\"
\#define FSL_PLATFORM_PATH_SEPARATOR \":\"
\#define FSL_CHECKOUTDB_NAME \"./.fslckout\"
\#endif
"

if {[opt-bool amal]} {
    puts "Generating conservative config for the amalgamation build..."
    set incGuard _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_
    set ofile libfossil-config.h
    set out [open $ofile w]
    puts $out "\#if !defined($incGuard)
\#define $incGuard 1
\#define FSL_AUX_SCHEMA \"$auxSchema\"
\#define FSL_CONTENT_SCHEMA \"$contentSchema\"
\#define FSL_PACKAGE_NAME \"[get-define FSL_PACKAGE_NAME]\"
\#define FSL_LIBRARY_VERSION \"[get-define FSL_LIBRARY_VERSION]\"
/* Tweak the following for your system... */
\#if !defined(HAVE_COMPRESS)
\#  define HAVE_COMPRESS 1
\#endif
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H 0
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN 0
\#endif
\#if !defined(HAVE_GETADDRINFO)
\#  define HAVE_GETADDRINFO 0
\#endif
\#if !defined(HAVE_INET_NTOP)
\#  define HAVE_INET_NTOP 0
\#endif
\#if !defined(HAVE_INTTYPES_H)
\#  define HAVE_INTTYPES_H 0
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL 0
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL 0
\#endif
\#if !defined(_WIN32)
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 1
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 1
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 1
\#endif
\#if !defined(HAVE_STDINT_H)
\#  define HAVE_STDINT_H 0
\#endif
\#if !defined(_DEFAULT_SOURCE)
\#  define _DEFAULT_SOURCE 1
\#endif
\#if !defined(_XOPEN_SOURCE)
\#  define _XOPEN_SOURCE 500
\#endif
\#else
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 0
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 0
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 0
\#endif
\#if !defined(HAVE_STDINT_H)
\#  define HAVE_STDINT_H 0
\#endif
\#endif
/* _WIN32 */

$FSL_PLATFORM_CONFIG_H

\#endif
/* $incGuard */
"
    close $out
    puts "Generated $ofile."
    return
}
# end of --amal bootstrap config generation

cc-check-c11
#cc-check-c11

cc-check-sizeof "void *"

if {![cc-check-includes zlib.h] ||
    ![cc-check-function-in-lib compress z]} {
    user-error "Missing functional zlib"
}
cc-check-function-in-lib iconv iconv
#if {![cc-check-functions iconv] &&
#    ![cc-check-function-in-lib iconv iconv]} {
#    user-error "Cannot find iconv(3) in libc or libiconv"
#}

########################################################################
# Checks for C99 via (__STDC_VERSION__ >= 199901L). Returns 1 if so, 0
# 0 if not.
proc cc-check-c99 {} {
    msg-checking "Checking for C99 via __STDC_VERSION__... "
    if {[cctest -code {
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
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







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-











-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







    set CC_FLAG_C99 {}
}
if {![cc-check-c99]} {
    user-error "As of 2021-02-21, libfossil requires C99."
}

define CC_FLAG_C99 $CC_FLAG_C99
########################################################################
# Module loader is currently used only by the s2 binding.
define HAVE_LIBLTDL 0
define HAVE_LIBDL 0
define LDFLAGS_MODULE_LOADER ""
define FSL_ENABLE_MODULE_LOADER 0
if {[cc-check-includes ltdl.h] && [cc-check-function-in-lib lt_dlopen ltdl]} {
    define HAVE_LIBLTDL 1
    define LDFLAGS_MODULE_LOADER "-lltdl"
    define FSL_ENABLE_MODULE_LOADER 1
} elseif {[cc-with {-includes dlfcn.h} {
	  cctest -link 1 -declare "extern char* dlerror(void);" -code "dlerror();"}]} {
    msg-result "This system can use dlopen() w/o -ldl"
    define HAVE_LIBDL 1
    define LDFLAGS_MODULE_LOADER ""
    define FSL_ENABLE_MODULE_LOADER 1
} elseif {[cc-check-includes dlfcn.h]} {
    msg-result "Found dlfcn.h."
    define HAVE_LIBDL 1
    if {[cc-check-function-in-lib dlopen dl]} {
      msg-result "Found libdl."
      define LDFLAGS_MODULE_LOADER "-ldl"
    } else {
      msg-result "No libdl found. Assuming dlopen is built-in."
      define LDFLAGS_MODULE_LOADER ""
    }
    define FSL_ENABLE_MODULE_LOADER 1
}

if {![get-define FSL_ENABLE_MODULE_LOADER]} {
    msg-result {No usable module loading library found. No worries, because we won't have a module system yet. :-D}
} else {
    msg-result {Found a module loader. Now we just need something to do with it.}
}

########################################################################
# A proxy for cc-check-function-in-lib which "undoes" any changes that
# routine makes to the LIBS define.
proc my-check-function-in-lib {function libs {otherlibs {}}} {
    set _LIBS [get-define LIBS]
    set found [cc-check-function-in-lib $function $libs $otherlibs]
    define LIBS $_LIBS
    return $found
}

########################################################################
# readline is only used (if at all) by s2sh. If it's not available, we
# fall back to a tree-local copy of the BSD-licensed linenoise editing
# library.
if {[cc-check-includes readline/readline.h] &&
    [my-check-function-in-lib readline readline]} {
    define FSL_ENABLE_READLINE 1
    define FSL_ENABLE_LINENOISE 0
    msg-result "Enabling libreadline for f-s2sh line editing."
} else {
    msg-result "libreadline not found. f-s2sh will use the linenoise line editor."
    define FSL_ENABLE_READLINE 0
    define FSL_ENABLE_LINENOISE 1
    define lib_readline ""
}

# cc-check-functions getcwd fopen
cc-check-functions opendir stat pipe inet_ntop getaddrinfo
#msg-result [cc-check-functions lstat]

if {[cc-check-functions lstat]} {
    # for lstat() on Linux and FreeBSD:
    define _XOPEN_SOURCE 600
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
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


















































































































+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+











-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-








-
-
-
-
-
-
-
-
-












-


-
-













-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
if {[opt-bool no-debug]} {
    msg-result "Non-debug build."
    set cFlags "$cFlags -O2"
} else {
    msg-result "Debug build enabled. Use --no-debug to build in non-debug mode."
    set cFlags "$cFlags -g -DDEBUG -O0"
}


set pcBin [find-an-executable pkg-config]
if {"" eq $pcBin} {
    puts {pkg-config not found, so making some guesses about
available packages.}
}
########################################################################
# Curses!
set LIB_CURSES ""
set CFLAGS_CURSES ""
puts "Looking for \[n]curses..."
if {"" ne $pcBin && $::tcl_platform(os)!="Darwin"} {
# Some macOS pkg-config configurations alter library search paths, which make
# the compiler unable to find lib iconv, so don't use pkg-config on macOS.
    set np ""
    foreach p {ncursesw ncurses} {
        if {[catch {exec $pcBin --exists $p}]} {
            continue
        }
        set np $p
        puts "Using pkg-config curses package \[$p]"
        break
    }
    if {"" ne $np} {
        set ppanel ""
        if {"ncursesw" eq $np} {
            if {![catch {exec $pcBin --exists panelw}]} {
                set ppanel panelw
            }
        }
        if {"" eq $ppanel && ![catch {exec $pcBin --exists panel}]} {
            set ppanel panel
        }
        set CFLAGS_CURSES [exec $pcBin --cflags $np]
        set LIB_CURSES [exec $pcBin --libs $np]
        if {"" eq $ppanel} {
            # Apparently Mac brew has pkg-config for ncursesw but not
            # panel/panelw, but hard-coding -lpanel seems to work on
            # that platform.
            append LIB_CURSES " -lpanel"
        } else {
            append LIB_CURSES " " [exec $pcBin --libs $ppanel]
            # append CFLAGS_CURSES " " [exec $pcBin --cflags $ppanel]
            # ^^^^ appending the panel cflags will end up duplicating
            # at least one -D flag from $np's cflags, leading to
            # "already defined" errors at compile-time. Sigh. Note, however,
            # that $ppanel's cflags have flags which $np's do not, so we
            # may need to include those flags anyway and manually perform
            # surgery on the list to remove dupes. Sigh.
        }
    }
}

if {"" eq $LIB_CURSES} {
    puts "Guessing curses location (will fail for exotic locations)..."
    define HAVE_CURSES_H [cc-check-includes curses.h]
    if {[get-define HAVE_CURSES_H]} {
        # Linux has -lncurses, BSD -lcurses. Both have <curses.h>
        msg-result "Found curses.h"
        if {[my-check-function-in-lib waddnwstr ncursesw]} {
            msg-result "Found -lncursesw"
            set LIB_CURSES "-lncursesw -lpanelw"
        } elseif {[my-check-function-in-lib initscr ncurses]} {
            msg-result "Found -lncurses"
            set LIB_CURSES "-lncurses -lpanel"
        } elseif {[my-check-function-in-lib initscr curses]} {
            msg-result "Found -lcurses"
            set LIB_CURSES "-lcurses -lpanel"
        }
    }
}
if {"" eq $LIB_CURSES} {
    user-error "!Curses! Foiled again!"
} else {
    puts {
        ************************************************************
        If your build fails due to missing functions such as
        waddwstr(), make sure you have the ncursesW development
        package installed. Some platforms combine the "w" and non-w
        curses builds and some don't.

        The package may have a name such as libncursesw5-dev or
        some such.
        ************************************************************
    }
}
define LIB_CURSES $LIB_CURSES
define CFLAGS_CURSES $CFLAGS_CURSES
unset LIB_CURSES
unset CFLAGS_CURSES


puts "Checking for compile_commands.json support..."
if {[cctest -lang c -cflags {/dev/null -MJ} -source {}]} {
    msg-result "Compiler supports compile_commands.json."
    define MAKE_COMPILATION_DB yes
} else {
    msg-result "Compiler does not support compile_commands.json."
    define MAKE_COMPILATION_DB no
}

define CFLAGS $cFlags

if {[opt-bool shared]} {
    msg-result "Enabling build of shared library."
    define LIBFOSSIL_SHARED 1
} else {
    define LIBFOSSIL_SHARED 0
    msg-result "Disabling build of shared library."
}

if {[opt-bool static]} {
    msg-result "Enabling build of static library."
    define LIBFOSSIL_STATIC 1
} else {
    define LIBFOSSIL_STATIC 0
    msg-result "Disabling build of static library."
}


if {[opt-bool loud]} {
    define BUILD_QUIETLY 0
    puts "Using 'loud' build mode."
} else {
    define BUILD_QUIETLY 1
    puts "Enabling quiet build mode. Use --loud to enable loud mode."
}

set dotBin [find-an-executable dot]
if {[string length $dotBin]} {
    define DOXYGEN_HAVE_DOT YES
    msg-result "Adding HAVE_DOT=YES to doxyfile."
} else {
    define DOXYGEN_HAVE_DOT NO
}


# Creates mkefile(-like) file $name from $name.in but explicitly makes
# the output read-only, to avoid inadvertent editing (who, me?).
proc makeFromDotIn {name} {
    catch { exec chmod u+w $name }
    make-template $name.in $name
    catch { exec chmod u-w $name }
}
# Each generated Makefile requires an input file with a .in extension:
set makefiles {
    config.make
    Makefile
    doc/Doxyfile
    src/Makefile
    fnc/Makefile
    bindings/Makefile
    bindings/cpp/Makefile
}
foreach {f} $makefiles {
    makeFromDotIn $f
}

if {0} {
    # Achtung: ordering of the -bare/-str options here is important
    # because of the mixed use of strings and integers for #defines...
    make-config-header include/fossil-scm/autoconfig.h \
        -none {DOXYGEN_*} \
        -bare {HAVE_*  FSL_ENABLE_* _DEFAULT_SOURCE _XOPEN_SOURCE} \
        -str {FSL_* PACKAGE_*}
}

########################################################################
# Generate our autconf header by hand to allow finer control
# over the structure....
set confH include/fossil-scm/autoconfig.h
set incGuard _NET_FOSSIL_SCM_FSL_AUTO_CONFIG_H_INCLUDED_
if {[opt-bool amal]} {
    set confH libfossil-config.h
    set incGuard _NET_FOSSIL_SCM_FSL_AMALGAMATION_CONFIG_H_INCLUDED_
}
puts "Generating config header $confH"
    set out [open $confH w]
    puts $out "\#if !defined($incGuard)
\#define $incGuard 1
\#define FSL_AUX_SCHEMA \"$auxSchema\"
\#define FSL_CONTENT_SCHEMA \"$contentSchema\"
\#define FSL_PACKAGE_NAME \"[get-define FSL_PACKAGE_NAME]\"
\#define FSL_LIBRARY_VERSION \"[get-define FSL_LIBRARY_VERSION]\"
\#define FSL_SHA1_HARDENED 1
/* Tweak the following for your system... */
\#if !defined(HAVE_GETADDRINFO)
\#  define HAVE_GETADDRINFO [get-define HAVE_GETADDRINFO 0]
\#endif
\#if !defined(HAVE_INET_NTOP)
\#  define HAVE_INET_NTOP [get-define HAVE_INET_NTOP 0]
\#endif
\#if !defined(_WIN32)
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H [get-define HAVE_DLFCN_H 0]
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN [get-define HAVE_DLOPEN 0]
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL [get-define HAVE_LIBDL 0]
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL [get-define HAVE_LIBLTDL 0]
\#endif
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT [get-define HAVE_LSTAT 1]
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H [get-define HAVE_LTDL_H 0]
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN [get-define HAVE_LT_DLOPEN 0]
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR [get-define HAVE_OPENDIR 1]
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE [get-define HAVE_PIPE 1]
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT [get-define HAVE_STAT 1]
\#endif
\#if !defined(_DEFAULT_SOURCE)
\#  define _DEFAULT_SOURCE [get-define _DEFAULT_SOURCE 1]
\#endif
\#if !defined(_XOPEN_SOURCE)
\#  define _XOPEN_SOURCE [get-define _XOPEN_SOURCE 500]
\#endif
\#else
/* _WIN32: */
\#if !defined(HAVE_DLFCN_H)
\#  define HAVE_DLFCN_H 0
\#endif
\#if !defined(HAVE_DLOPEN)
\#  define HAVE_DLOPEN 0
\#endif
\#if !defined(HAVE_LIBDL)
\#  define HAVE_LIBDL 0
\#endif
\#if !defined(HAVE_LIBLTDL)
\#  define HAVE_LIBLTDL 0
\#endif
\#if !defined(HAVE_LSTAT)
\#  define HAVE_LSTAT 0
\#endif
\#if !defined(HAVE_LTDL_H)
\#  define HAVE_LTDL_H 0
\#endif
\#if !defined(HAVE_LT_DLOPEN)
\#  define HAVE_LT_DLOPEN 0
\#endif
\#if !defined(HAVE_OPENDIR)
\#  define HAVE_OPENDIR 1
\#endif
\#if !defined(HAVE_PIPE)
\#  define HAVE_PIPE 0
\#endif
\#if !defined(HAVE_STAT)
\#  define HAVE_STAT 0
\#endif
\#endif
/*_WIN32*/

$FSL_PLATFORM_CONFIG_H

\#endif
/* $incGuard */
"
    close $out
    puts "Generated $confH."
    return
}

Deleted bindings/Makefile.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/usr/bin/make # help out emacs
all:
SUBDIRS :=
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
ifneq (,$(CXX))
clean-.: clean-cpp
distclean-.: distclean-cpp
SUBDIRS += cpp
all: subdir-cpp
endif

clean-.: clean-s2
distclean-.: distclean-s2
include ../subdir-inc.make
SUBDIRS += s2
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
all: subdir-s2

Deleted bindings/Makefile.in.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/usr/bin/make # help out emacs
all:
SUBDIRS :=
# Subdir cleanup rules and deps list must come before shakenmake.make is included
# or they must be set up manually afterwards...
ifneq (,$(CXX))
clean-.: clean-cpp
distclean-.: distclean-cpp
SUBDIRS += cpp
all: subdir-cpp
endif

clean-.: clean-s2
distclean-.: distclean-s2
include ../subdir-inc.make
SUBDIRS += s2
$(eval $(call ShakeNMake.CALL.SUBDIRS,$(SUBDIRS)))
all: subdir-s2

Deleted bindings/cpp/Context.cpp.

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



































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Context::~Context(){
    if(this->ownsCx){
      fsl_cx_finalize(this->f);
    }
  }

  void Context::setup(fsl_cx_init_opt const * opt){
    //this->f = fsl_cx_malloc();
    //if(!this->f) throw OOMException();
    assert(!this->f);
    int const rc = fsl_cx_init(&this->f, opt);
    if(rc){
      fsl_error err = fsl_error_empty;
      if(this->f){
        fsl_error_move( &this->f->error, &err );
        fsl_cx_finalize(this->f);
        this->f = NULL;
      }else{
        fsl_error_set( &err, rc,
                       "fsl_cx_init() failed with code %s",
                       fsl_rc_cstr(rc) );
      }
      throw Exception(err);
    }
  }

  Context::Context()
    : f(NULL),
      ownsCx(true),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    this->setup(NULL);
  }
  
  Context::Context(fsl_cx_init_opt const & opt)
    : f(NULL),
      ownsCx(true),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    this->setup(&opt);
  }

  Context::Context(fsl_cx * f, bool ownsHandle)
    : f(f),
      ownsCx(ownsHandle),
      dbCkout(),
      dbRe(),
      dbMain()
  {
    
  }

  Context::operator fsl_cx * () throw(){
    return this->f;
  }

  Context::operator fsl_cx const * () const throw() {
    return this->f;
  }

  void Context::propagateError() const{
    if(this->f){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      if(err->code) throw Exception(err);
    }
  }

  void Context::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  void Context::assertHasRepo(){
    assert(this->f);
    if(!fsl_needs_repo(this->f)){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      assert(err->code);
      throw Exception(err);
    }
  }

  void Context::assertHasCheckout(){
    assert(this->f);
    if(!fsl_needs_ckout(this->f)){
      fsl_error const * err = fsl_cx_err_get_e(this->f);
      assert(err->code);
      throw Exception(err);
    }
  }

  fsl_cx * Context::handle() throw(){
    return this->f;
  }

  fsl_cx const * Context::handle() const throw(){
    return this->f;
  }

  Db & Context::db() throw() {
    if(!this->dbMain.handle()){
      fsl_db * db = fsl_cx_db(*this);
      if(db) this->dbMain.handle(db, false);
    }
    return this->dbMain;
  }


  Db & Context::dbRepo() throw() {
    if(!this->dbRe.handle()){
      fsl_db * db = fsl_cx_db_repo(*this);
      if(db) this->dbRe.handle(db, false);
    }
    return this->dbRe;
  }

  Db & Context::dbCheckout() throw() {
    if(!this->dbCkout.handle()){
      fsl_db * db = fsl_cx_db_ckout(*this);
      if(db) this->dbCkout.handle(db, false);
    }
    return this->dbCkout;
  }

  Context & Context::openCheckout( char const * dirName ){
    this->assertRC( "openCheckout()",
                    fsl_ckout_open_dir(this->f, dirName, true) );
    return *this;
  }

  Context & Context::openRepo( char const * dbName ){
    this->assertRC( "openRepo()",
                    fsl_repo_open(this->f, dbName) );
    return *this;
  }

  Context & Context::closeDbs() throw(){
    fsl_cx_close_dbs(this->f)
      /*
        Reminder to self: the 3 fsl_cx db handles are stored as
        complete fsl_db instances (not pointers) in fsl_cx, with the
        exception of the "main" db, which is just a pointer to one of
        the other 3. What does that mean? It means that when we use
        fsl_cx_close_dbs(), this->dbRe and friends will (if
        initialized) still be pointing to those pointers...  which are
        (due to internal details) actually still valid, they just
        refer to closed fsl_db handles.

        That's actually good for us here, except that certain
        combinations of C-level ops "might" get our checkout/repo db
        pointers cross a bit.
      */
      ;
    assert(!this->dbRe.ownsHandle());
    assert(!this->dbMain.ownsHandle());
    assert(!this->dbCkout.ownsHandle());
    this->dbRe.close();
    this->dbCkout.close();
    this->dbMain.close();
    if(this->dbRe.handle()){
      assert(!this->dbRe.handle()->dbh);
    }
    return *this;
  }

  bool Context::ownsHandle() const throw(){
    return this->ownsCx;
  }

  std::string Context::ridToArtifactUuid(fsl_id_t rid,
                                         fsl_satype_e type){
    this->assertHasRepo();
    fsl_uuid_str uuid = fsl_rid_to_artifact_uuid(*this, rid, type);
    if(!uuid){
      this->propagateError();
      throw Exception(FSL_RC_NOT_FOUND,
                      "Could not resolve RID %" FSL_ID_T_PFMT
                      " as artifact type %s.",
                      (fsl_id_t)rid, fsl_satype_cstr(type));
    }
    std::string const & rc = uuid;
    fsl_free(uuid);
    return rc;
  }

  std::string Context::ridToUuid(fsl_id_t rid){
    this->assertHasRepo();
    fsl_uuid_str uuid = fsl_rid_to_uuid(*this, rid);
    if(!uuid){
      this->propagateError();
      throw Exception(FSL_RC_NOT_FOUND, "Could not resolve RID %" FSL_ID_T_PFMT ".",
                      (fsl_id_t)rid);
    }
    std::string const & rc = uuid;
    fsl_free(uuid);
    return rc;
  }

  std::string Context::symToUuid(char const * symbolicName,
                                 fsl_id_t * rid,
                                 fsl_satype_e type){
    this->assertHasRepo();
    fsl_uuid_str uuid = NULL;
    int const rc = fsl_sym_to_uuid(*this, symbolicName, type, &uuid, rid);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    std::string const & rv = uuid;
    fsl_free(uuid);
    return rv;
  }


  fsl_id_t Context::symToRid(char const * symbolicName, fsl_satype_e type){
    this->assertHasRepo();
    fsl_id_t rv = 0;
    int const rc = fsl_sym_to_rid(*this, symbolicName, type, &rv);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    assert(rv>0);
    return rv;
  }

  fsl_id_t Context::symToRid(std::string const & symbolicName,
                             fsl_satype_e type){
    return this->symToRid( symbolicName.c_str(), type );
  }

  Context::Transaction::Transaction(Context &cx)
    : tr( cx.db() ),
      level(tr.level()){
  }

  Context::Transaction::~Transaction() throw(){
    if(this->level) this->tr.rollback();
  }

  void Context::Transaction::commit(){
    if(this->level){
      this->level = 0;
      this->tr.commit();
    }else{
      throw Exception(FSL_RC_MISUSE,
                      "commit() called multiple times.");
    }
  }

  Context & Context::getContent( fsl_id_t rid, Buffer & dest ){
    int const rc = fsl_content_get( *this, rid, dest );
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    return *this;
  }

  Context & Context::getContent( char const * sym, Buffer & dest ){
    int const rc = fsl_content_get_sym( *this, sym, dest );
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }
    return *this;
  }

  Context & Context::getContent( std::string const & sym, Buffer & dest ){
    return this->getContent( sym.c_str(), dest );
  }

} // namespace fsl

#undef CERR

Deleted bindings/cpp/Db.cpp.

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




















































































































































































































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {
  Stmt::~Stmt() throw(){
    fsl_stmt_finalize(*this);
  }

  Stmt::Stmt(Db & db) throw() :
    db(db),
    stmt(fsl_stmt_empty){
  }

  Stmt::operator fsl_stmt * () throw(){
    return &this->stmt;
  }

  Stmt::operator fsl_stmt const * () const throw() {
    return &this->stmt;
  }


  void Stmt::assertPrepared() const{
    if(!this->stmt.stmt){
      throw Exception(FSL_RC_MISUSE,
                      "Statement is not prepared.");
    }
    assert(this->stmt.db);
  }

  void Stmt::assertRange(short col, short base) const{
    this->assertPrepared();
    char const * errMsg = NULL;
    if(col<0){
      errMsg = "column/parameter index %d is invalid";
    }else if(base){
      assert(1==base);
      if(!col || col > fsl_stmt_param_count(*this)){
        errMsg = "parameter index %d is out of range";
      }
    }else{
      assert(0==base);
      if(col >= fsl_stmt_col_count(*this)){
        errMsg = "column index %d is out of range";
      }
    }
    if(errMsg) throw Exception(FSL_RC_RANGE, errMsg, col+base);
  }

  void Stmt::propagateError() const{
    this->db.propagateError();
  }

  void Stmt::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  Stmt & Stmt::prepare(std::string const & sql){
    return this->prepare("%s", sql.c_str());
  }

  Stmt & Stmt::prepare(Buffer const & sql){
    return this->prepare("%s", sql.c_str());
  }

  Stmt & Stmt::prepare(char const * sql, ... ){
    va_list vargs;
    if(this->stmt.stmt) throw Exception(FSL_RC_MISUSE,
                                        "Statement is prepared and not "
                                        "yet finalized. Cowardly refusing "
                                        "to re-prepare() non-finalized statement: %s",
                                        this->sql());
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_preparev( this->db.handle(), *this,
                          sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL preparation failed for: %s", sql);
    }else{
      assert(this->stmt.db == this->db.handle());
      return *this;
    }
  }

  int Stmt::stepCount() const throw(){
    return this->stmt.rowCount;
  }

  int Stmt::paramCount() const throw(){
    return this->stmt.paramCount;
  }

  int Stmt::columnCount() const throw(){
    return this->stmt.colCount;
  }

  char const * Stmt::sql() const throw() {
    return stmt.stmt
      ? fsl_buffer_cstr(&stmt.sql)
      : NULL;
  }

  fsl_stmt * Stmt::handle() throw(){
    return &this->stmt;
  }
  
  fsl_stmt const * Stmt::handle() const throw() {
    return &this->stmt;
  }

  Stmt & Stmt::reset(bool resetStepCounterToo){
    this->assertRC("reset()",
                   fsl_stmt_reset2(*this,
                                   resetStepCounterToo));
    return *this;
  }

  Stmt & Stmt::finalize() throw(){
    fsl_stmt_finalize(*this);
    return *this;
  }

  bool Stmt::step(){
    this->assertPrepared();
    int const rc = fsl_stmt_step(*this);
    switch(rc){
      case FSL_RC_STEP_ROW: return true;
      case FSL_RC_STEP_DONE: return false;
      default:
        this->propagateError();
        throw Exception(rc,"No idea what went wrong.");
    }
  }

  Stmt & Stmt::stepExpectDone(){
    if(this->step()){
      throw Exception(FSL_RC_ERROR,
                      "Expecting statement to return no rows: %s",
                      this->sql());
    }
    return *this;
  }

  int32_t Stmt::getInt32(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_int32( *this, col );
  }

  int64_t Stmt::getInt64(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_int64( *this, col );
  }

  double Stmt::getDouble(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_double( *this, col );
  }

  fsl_id_t Stmt::getId(short col){
    this->assertRange(col, 0);
    return fsl_stmt_g_id( *this, col );
  }

  char const * Stmt::columnName(short col){
    this->assertRange(col, 0);
    return fsl_stmt_col_name(*this, col);
  }

  char const * Stmt::getText(short col, fsl_size_t * length){
    this->assertRange(col, 0);
    return fsl_stmt_g_text(*this, col, length);
  }

  void const * Stmt::getBlob(short col, fsl_size_t * length){
    void const * v = NULL;
    this->assertRange(col, 0);
    fsl_stmt_get_blob(*this, col, &v, length);
    return v;
  }

  Stmt & Stmt::bind(short col){
    this->assertRange(col, 1);
    this->assertRC("bind NULL",
                   fsl_stmt_bind_null(*this, col));
    return *this;
  }
  
  Stmt & Stmt::bind(short col, int32_t v){
    this->assertRange(col, 1);
    this->assertRC("bind int32",
                   fsl_stmt_bind_int32(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, int64_t v){
    this->assertRange(col, 1);
    this->assertRC("bind int64",
                   fsl_stmt_bind_int64(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, double v){
    this->assertRange(col, 1);
    this->assertRC("bind double",
                   fsl_stmt_bind_double(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(short col, char const * str,
                    fsl_int_t len, bool copyBytes){
    this->assertRange(col, 1);
    this->assertRC("bind text",
                   fsl_stmt_bind_text(*this, col, str, len, copyBytes));
    return *this;
  }

  Stmt & Stmt::bind(short col, std::string const & str){
    return this->bind(col, str.c_str(), (fsl_int_t)str.size(), 1);
  }
  
  Stmt & Stmt::bind(short col, void const * v,
                    fsl_size_t len, bool copyBytes){
    this->assertRange(col, 1);
    this->assertRC("bind blob",
                   fsl_stmt_bind_blob(*this, col, v, len, copyBytes));
    return *this;
  }

  int Stmt::paramIndex(char const * name){
    this->assertPrepared();
    return fsl_stmt_param_index(*this, name);
  }

  Stmt & Stmt::bind(char const * col){
    this->assertRC("bind NULL",
                   fsl_stmt_bind_null_name(*this, col));
    return *this;
  }
  
  Stmt & Stmt::bind(char const * col, int32_t v){
    this->assertRC("bind int32",
                   fsl_stmt_bind_int32_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, int64_t v){
    this->assertRC("bind int64",
                   fsl_stmt_bind_int64_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, double v){
    this->assertRC("bind double",
                   fsl_stmt_bind_double_name(*this, col, v));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, char const * str,
                    fsl_int_t len, bool copyBytes){
    this->assertRC("bind text",
                   fsl_stmt_bind_text_name(*this, col, str, len, copyBytes));
    return *this;
  }

  Stmt & Stmt::bind(char const * col, std::string const & str){
    return this->bind(col, str.c_str(), (fsl_int_t)str.size(), 1);
  }
  
  Stmt & Stmt::bind(char const * col, void const * v,
                    fsl_size_t len, bool copyBytes){
    this->assertRC("bind blob",
                   fsl_stmt_bind_blob_name(*this, col, v, len, copyBytes));
    return *this;
  }

  StmtBinder::~StmtBinder(){}

  StmtBinder::StmtBinder(Stmt &s) : st(s), col(0)
  {}

  Stmt & StmtBinder::stmt(){
    return this->st;
  }

  StmtBinder & StmtBinder::operator()(){
    st.bind(++this->col);
    return *this;
  }

  StmtBinder & StmtBinder::operator()(char const * v, fsl_int_t len,
                                      bool copyBytes){
    st.bind(++this->col, v, len, copyBytes);
    return *this;
  }

  StmtBinder & StmtBinder::operator()(void const * v, fsl_size_t len,
                                      bool copyBytes){
    st.bind(++this->col, v, len, copyBytes);
    return *this;
  }

  StmtBinder & StmtBinder::reset(bool alsoStatement) {
    this->col = 0;
    if(alsoStatement) this->st.reset();
    return *this;
  }

  bool StmtBinder::step(){
    return this->st.step();
  }

  StmtBinder & StmtBinder::once(){
    this->st.stepExpectDone();
    return this->reset();
  }

  Db::~Db() throw(){
    this->close();
  }

  void Db::setup(){
    assert(!this->db);
    this->db = fsl_db_malloc();
    if(!this->db) throw OOMException();
    this->ownsDb = true;
  }

  Db::Db(): db(NULL),
            ownsDb(true){
  }

  Db::Db(char const * filename, int openFlags)
    : db(NULL),
      ownsDb(true){
    try{
      this->open(filename, openFlags);
    }catch(...){
      if(this->db) fsl_db_close(this->db);
      throw;
    }
  }

  void Db::propagateError() const{
    if(this->db && this->db->error.code){
      throw Exception(this->db->error);
    }
  }

  void Db::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  void Db::assertOpened() const{
    if(!this->db || !this->db->dbh){
      throw Exception(FSL_RC_MISUSE,
                      "Db is not opened.");
    }
  }

  Db::operator fsl_db * () throw(){
    return this->db;
  }

  Db::operator fsl_db const * () const throw() {
    return this->db;
  }
  
  Db & Db::handle( fsl_db * db, bool ownsHandle )throw(){
    if(this->db != db){
      this->close();
      this->db = db;
      this->ownsDb = ownsHandle;
    }
    return *this;
  }

  Db & Db::close() throw(){
    if(this->db){
      if(this->ownsDb){
        fsl_db_close(*this);
      }
      this->db = NULL;
    }
    return *this;
  }

  char const * Db::filename() throw(){
    return this->db
      ? fsl_db_filename(*this, NULL)
      : NULL;
  }

  Db & Db::open(char const * filename, int openFlags){
    if(!this->db) this->setup();
    else if(this->db->dbh){
      throw Exception(FSL_RC_MISUSE,
                      "Db is already opened: %s",
                      this->filename());
    }
    int const rc = fsl_db_open(*this, filename, openFlags);
    if(rc){
      /*
         The problem here is that open() can be called from the ctor,
         and if the ctor throws then the dtor is not called, so we
         have to free up this->db. Oh, wait...  the open() ctor does
         that for us, so this gets easier.

         But... if it throws from outside the ctor then we DO have to
         clean up.
       */
      Exception const & ex = this->db->error.code
        ? Exception(this->db->error)
        : Exception(rc,"fsl_db_open(%s) failed: %s",
                    filename, fsl_rc_cstr(rc));
      this->close();
      throw ex;
    }
    return *this;
  }

  bool Db::isOpened() const throw(){
    return (this->db && this->db->dbh) ? true : false;
  }

  bool Db::ownsHandle() const throw(){
    return this->ownsDb;
  }

  fsl_db * Db::handle() throw(){
    return this->db;
  }
  
  fsl_db const * Db::handle() const throw() {
    return this->db;
  }

  Db & Db::begin(){
    this->assertOpened();
    this->assertRC( 
                   "begin()",
                    fsl_db_transaction_begin(*this) );
    return *this;
  }

  Db & Db::commit(){
    this->assertOpened();
    this->assertRC( "commit()",
                    fsl_db_transaction_end(*this, 0) );
    return *this;
  }

  Db & Db::rollback() throw() {
    this->assertOpened();
    fsl_db_transaction_end(*this, 1);
    return *this;
  }

  Db & Db::exec(std::string const & sql){
    return this->exec("%s", sql.c_str());
  }

  Db & Db::exec(char const * sql, ...){
    this->assertOpened();
    va_list vargs;
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_execv( *this, sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL execution failed for: %s", sql);
    }
    else return *this;
  }

  Db & Db::execMulti(char const * sql, ...){
    this->assertOpened();
    va_list vargs;
    int rc = 0;    
    va_start(vargs,sql);
    rc = fsl_db_exec_multiv( *this, sql, vargs );
    va_end(vargs);
    if(rc){
      this->propagateError();
      throw Exception(rc,"SQL multi-exec failed for: %s", sql);
    }
    else return *this;
  }

  Db & Db::execMulti(std::string const & sql){
    return this->execMulti("%s", sql.c_str());
  }


  Db & Db::attach(char const * filename, char const * label){
    this->assertRC( "attach()", fsl_db_attach(*this, filename, label) );
    return *this;
  }

  Db & Db::detach(char const * label){
    this->assertRC( "detach()", fsl_db_detach(*this, label) );
    return *this;
  }

  int Db::transactionLevel() const throw(){
    return this->db
      ? this->db->beginCount
      : 0;
  }

  Db::Transaction::Transaction(Db & db)
  : db(db), inTrans(false){
    db.begin();
    inTrans = db.transactionLevel();
  }

  Db::Transaction::~Transaction() throw(){
    if(inTrans) this->rollback();
  }

  void Db::Transaction::commit(){
    assert(inTrans);
    if(inTrans>0){
      inTrans = 0;
      db.commit();
    }
  }

  void Db::Transaction::rollback() throw(){
    assert(inTrans);
    if(inTrans){
#if 1
      inTrans = 0;
      db.rollback();
#else
      /* sane? */
      while(inTrans < db.transactionLevel()){
        db.rollback();
      }
#endif
    }
  }

  int Db::Transaction::level() const throw(){
    return db.transactionLevel();
  }

}// namespace fsl

#undef CERR

Deleted bindings/cpp/Deck.cpp.

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






























































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Deck::~Deck() throw(){
    delete this->deltaBase;
    if(this->ownsDeck){
      assert(this->d->allocStamp);
      fsl_deck_finalize(this->d);
    }
  }

  void Deck::setup(fsl_deck * mf, fsl_satype_e type){
    if(!mf){
      assert(!this->d);
      assert(this->ownsDeck);
      this->d = fsl_deck_malloc();
      if(!this->d) throw OOMException();
    }else{
      assert(this->d == mf);
      if(d && d->f && (d->f != this->cx.handle())){
        throw Exception(FSL_RC_MISUSE,
                        "Mis-matched fsl_cx contexts for deck.");
      }
    }
    if(ownsDeck){
      fsl_deck_init( this->cx, d, type );
    }
  }

  Deck::Deck(Context & cx, fsl_satype_e type)
    : cx(cx),
      d(NULL),
      deltaBase(NULL),
      ownsDeck(true){
    this->setup( NULL, type );
  }

  Deck::Deck(Context & cx, fsl_deck * d, bool ownsDeck)
    : cx(cx),
      d(d),
      deltaBase(NULL),
      ownsDeck(ownsDeck){
    if(!d){
      throw Exception(FSL_RC_MISUSE,
                      "A proxied Deck may not be NULL.");
    }
    this->setup( d, d->type );
  }

  void Deck::propagateError() const{
    fsl_error const * err = fsl_cx_err_get_e(this->cx);
    if(err->code){
      throw Exception(err);
    }
  }

  void Deck::assertRC(char const * context, int rc) const{
    if(rc){
      this->propagateError();
      throw Exception(rc, "%s: %s", context, fsl_rc_cstr(rc));
    }
  }

  Deck & Deck::cleanup() throw(){
    fsl_deck_clean(*this);
    return *this;
  }

  fsl_satype_e Deck::type() const throw(){
    return this->d->type;
  }

  fsl_id_t Deck::rid() const throw(){
    return this->d->rid;
  }

  fsl_uuid_cstr Deck::uuid() const throw(){
    return this->d->uuid;
  }

  Deck::operator fsl_deck *() throw(){
    return this->d;
  }

  Deck::operator fsl_deck const *() const throw(){
    return this->d;
  }

  Context & Deck::context() throw() { return this->cx; }
  Context const & Deck::context() const throw() { return this->cx; }

  fsl_deck * Deck::handle() throw(){
    return this->d;
  }

  fsl_deck const * Deck::handle() const throw(){
    return this->d;
  }

  bool Deck::hasAllRequiredCards() const throw(){
    return fsl_deck_has_required_cards(*this);
  }

  Deck const & Deck::assertHasRequiredCards() const{
    if(!fsl_deck_has_required_cards(*this)){
      throw Exception(fsl_cx_err_get_e(this->cx));
    }
    else return *this;
  }

  bool Deck::cardIsLegal(char cardLetter) const throw(){
    return fsl_card_is_legal( this->d->type, cardLetter );
  }

  Deck & Deck::unshuffle(bool calcRCard){
    int const rc = fsl_deck_unshuffle(*this, calcRCard);
    if(rc){
      this->propagateError();
      throw Exception(rc);
    }else return *this;
  }

  Deck const & Deck::output( fsl_output_f f, void * outState ) const{
    int const rc = fsl_deck_output( this->d, f, outState );
    if(rc) throw Exception(this->d->f->error);
    else return *this;
  }

  Deck const & Deck::output( std::ostream & os ) const{
    return this->output( fsl_output_f_std_ostream, &os );
  }

  Deck & Deck::save(bool isPrivate){
    int const rc = fsl_deck_save( *this, isPrivate );
    if(rc){
      this->propagateError();
      throw Exception(rc,"fsl_deck_save() failed: %s",
                      fsl_rc_cstr(rc));
    }
    else return *this;
  }

  Deck & Deck::load( fsl_id_t rid, fsl_satype_e type ){
    this->cleanup();
    int const rc = fsl_deck_load_rid( this->cx, *this,
                                      rid, type );
    if(rc){
      this->propagateError();
      throw Exception(rc, "fsl_deck_load_rid() failed "
                      "with %s for symbol: %" FSL_ID_T_PFMT,
                      fsl_rc_cstr(rc), (fsl_id_t)rid);
    }else return *this;
  }

  Deck & Deck::load( char const * symbolicName, fsl_satype_e type){
    this->cleanup();
    int const rc = fsl_deck_load_sym( this->cx, *this,
                                      symbolicName, type );
    if(rc){
      this->propagateError();
      throw Exception(rc, "fsl_deck_load_sym() failed "
                      "with %s for symbol: %s",
                      fsl_rc_cstr(rc), symbolicName);
    }else return *this;
  }

  Deck & Deck::load( std::string const & symbolicName, fsl_satype_e type){
    return this->load( symbolicName.c_str(), type );
  }

  Deck & Deck::setCardA( char const * name,
                         char const * tgt,
                         fsl_uuid_cstr uuid ){
    int const rc = fsl_deck_A_set(*this, name, tgt, uuid);
    this->assertRC("fsl_deck_A_set()", rc);
    return *this;
  }

  Deck & Deck::setCardB(fsl_uuid_cstr uuid){
    if(this->deltaBase){
      delete this->deltaBase;
      this->deltaBase = NULL;
    }
    int const rc = fsl_deck_B_set(*this, uuid);
    this->assertRC("fsl_deck_B_add()", rc);
    return *this;
  }


  Deck & Deck::setCardC( char const * comment ){
    int const rc = fsl_deck_C_set(*this, comment, -1);
    this->assertRC("fsl_deck_C_set()", rc);
    return *this;
  }

  Deck & Deck::setCardD(double julianDay){
    int const rc = fsl_deck_D_set(*this,
                                  julianDay<0
                                  ? fsl_julian_now()
                                  : julianDay);
    this->assertRC("fsl_deck_D_set()", rc);
    return *this;
  }

  Deck & Deck::setCardE( fsl_uuid_cstr uuid, double julian ){
    int const rc = fsl_deck_E_set(*this,
                                  julian<0
                                  ? fsl_julian_now()
                                  : julian,
                                  uuid );
    this->assertRC("fsl_deck_E_set()", rc);
    return *this;
  }


  Deck & Deck::addCardF(char const * name,
                        fsl_uuid_cstr uuid,
                        fsl_fileperm_e perm, 
                        char const * oldName ){
    int const rc = fsl_deck_F_add(*this, name, uuid, perm, oldName);
    this->assertRC("fsl_deck_F_add()", rc);
    return *this;
  }

  Deck & Deck::addCardJ( char isAppend, char const * key, char const * value ){
    int const rc = fsl_deck_J_add(*this, isAppend, key, value);
    this->assertRC("fsl_deck_J_add()", rc);
    return *this;
  }


  Deck & Deck::setCardK(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_K_set(*this, uuid);
    this->assertRC("fsl_deck_K_set()", rc);
    return *this;
  }

  Deck & Deck::setCardL( char const * title ){
    int const rc = fsl_deck_L_set(*this, title, -1);
    this->assertRC("fsl_deck_L_set()", rc);
    return *this;
  }

  Deck & Deck::addCardM(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_M_add(*this, uuid);
    this->assertRC("fsl_deck_M_add()", rc);
    return *this;
  }


  Deck & Deck::setCardN(char const * name){
    int const rc = fsl_deck_N_set(*this, name, -1);
    this->assertRC("fsl_deck_N_set()", rc);
    return *this;
  }

  Deck & Deck::addCardP(fsl_uuid_cstr uuid){
    int const rc = fsl_deck_P_add(*this, uuid);
    this->assertRC("fsl_deck_P_add()", rc);
    return *this;
  }

  Deck & Deck::addCardQ(char type, fsl_uuid_cstr target,
                        fsl_uuid_cstr baseline){
    int const rc = fsl_deck_Q_add(*this, type, target, baseline);
    this->assertRC("fsl_deck_Q_add()", rc);
    return *this;
  }

  Deck & Deck::addCardT(fsl_tagtype_e tagType,
                        char const * name,
                        fsl_uuid_cstr uuid,
                        char const * value){
    int const rc = fsl_deck_T_add( *this, tagType, uuid, name, value );
    this->assertRC("fsl_deck_T_add()", rc);
    return *this;
  }


  Deck & Deck::setCardU(char const * user){
    if(!user || !*user){
      user = fsl_cx_user_get(this->cx);
    }
    if(!user || !*user){
      throw Exception(FSL_RC_MISUSE,
                      "setCardU(): NULL/empty user name is not legal.");
    }
    int const rc = fsl_deck_U_set(*this, user);
    this->assertRC("fsl_deck_U_set()", rc);
    return *this;
  }

  Deck & Deck::setCardW(char const * content, fsl_int_t len){
    int const rc = fsl_deck_W_set(*this, content, len);
    this->assertRC("fsl_deck_W_set()", rc);
    return *this;
  }

  Deck::FCardIterator::~FCardIterator() throw()
  {}

  Deck::FCardIterator::FCardIterator(Deck & d, bool skipDeletedFiles)
    : d(&d),
      fc(NULL),
      skipDeleted(skipDeletedFiles)
  {
    int const rc = fsl_deck_F_rewind(d);
    if(rc) throw Exception(rc, "fsl_deck_F_rewind() failed: %s",
                           fsl_rc_cstr(rc));
    fsl_deck_F_next(d, &this->fc);
  }

  Deck::FCardIterator::FCardIterator() throw()
    : d(NULL),
      fc(NULL),
      skipDeleted(false)
  {}

  void Deck::FCardIterator::assertHasDeck(){
    if(!this->d) throw Exception(FSL_RC_MISUSE,
                                 "Iterator requires a deck object.");
  }

  Deck::FCardIterator & Deck::FCardIterator::operator++(){
    if(this->fc){
      do{
        int const rc = fsl_deck_F_next(*this->d, &this->fc);
        if(rc) throw Exception(rc);
      }while(skipDeleted && (this->fc && !this->fc->uuid));
    }
    return *this;
  }

  fsl_card_F const * Deck::FCardIterator::operator*(){
    return this->fc;
  }

  fsl_card_F const * Deck::FCardIterator::operator->(){
    if(!this->fc) throw Exception(FSL_RC_MISUSE,
                                  "Throwing to avoid "
                                  "dereferencing a NULL fsl_card_F.");
    return this->fc;
  }


  bool Deck::FCardIterator::operator==(FCardIterator const &rhs) const throw(){
    if(this->fc==rhs.fc) return true;
    else if(!this->fc || !rhs.fc) return false;
    return 0==fsl_strcmp(this->fc->name, rhs.fc->name);
  }
    
  bool Deck::FCardIterator::operator!=(FCardIterator const &rhs) const throw(){
    if(this->fc==rhs.fc) return false;
    else if(!this->fc || !rhs.fc) return true;
    return 0!=fsl_strcmp(this->fc->name, rhs.fc->name);
  }

  bool Deck::FCardIterator::operator<(FCardIterator const &rhs) const throw(){
    if(!this->fc) return rhs.fc ? true : false;
    else if(!rhs.fc) return false;
    return 0 > fsl_strcmp(this->fc->name, rhs.fc->name);
  }

  Deck * Deck::baseline(){
    if(FSL_SATYPE_CHECKIN==this->d->type){
      if(this->deltaBase) return this->deltaBase;
      else if(!this->d->B.uuid) return NULL;
      else if(!this->d->B.baseline){
        int const rc = fsl_deck_F_rewind(*this);
        this->assertRC("fsl_deck_rewind()", rc);
        assert(this->d->B.baseline);
      }
      return this->deltaBase = new Deck(this->cx,
                                        this->d->B.baseline,
                                        false);
    }else{
      return NULL;
    }
  }

  Deck::TCardIterator::TCardIterator(Deck & d)
  : ParentType(d.handle()->T)
  {}

  Deck::TCardIterator::TCardIterator()
  : ParentType()
  {}

  Deck::TCardIterator::~TCardIterator() throw()
  {}

  fsl_card_T const * Deck::TCardIterator::operator->() const{
    fsl_card_T const * rv = this->currentValue();
    if( !rv ) throw Exception(FSL_RC_MISUSE,
                              "Throwing to avoid dereferencing a NULL "
                              "fsl_card_T.");
    else return rv;
  }
  std::ostream & operator<<( std::ostream & os, Deck const & d ){
    d.output(os);
    return os;
  }

} // namespace fsl

#undef CERR

Deleted bindings/cpp/Exception.cpp.

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

























































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"
#include <cassert>

/* only for debugging */
#include <iostream>
#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "

namespace fsl {

  Exception::~Exception() throw() {
    fsl_error_clear(*this);
  }

  Exception::Exception(Exception const &other) throw()
    : err(fsl_error_empty){
    fsl_error_copy(&other.err, *this);
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
  }

  Exception & Exception::operator=(Exception const &other) throw(){
    if(&other != this){
      fsl_error_clear(*this);
      fsl_error_copy(&other.err, *this);
    }
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
    return *this;
  }

  Exception::Exception(int code, std::string const & msg) throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, code, "%s",
                  msg.empty()
                  ? fsl_rc_cstr(code)
                  : msg.c_str());
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
    /* Reminder: we have to ignore any error here :/ */
  }

  Exception::Exception(int code) throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, code, "%s", fsl_rc_cstr(code));
  }

  Exception::Exception() throw()
    : err(fsl_error_empty){
    fsl_error_set(*this, FSL_RC_ERROR, NULL);
  }

  Exception::Exception( fsl_error & err ) throw()
    : err(fsl_error_empty)
  {
    assert(err.code);
    fsl_error_move( &err, *this );
  }

  Exception::Exception( fsl_error const * err ) throw()
    : err(fsl_error_empty)
  {
    if(err && err->code){
      fsl_error_copy( err, *this );
    }else{
      fsl_error_set( *this, FSL_RC_MISUSE,
                     "Exception(fsl_error const *) ctor passed a %s!",
                     err ? "fsl_error with code==0" : "NULL");
    }
  }

  void Exception::error(int code, char const * fmt, va_list vargs) throw(){
    fsl_error_setv(*this, code, fmt, vargs);
    //CERR << "Exception("<<err.code<<"): " << (char const *)err.msg.mem << '\n';
  }

  Exception::Exception(int code, char const * fmt, ...) throw()
    : err(fsl_error_empty){
    va_list vargs;
    va_start(vargs,fmt);
    this->error(code, fmt, vargs);
    va_end(vargs);
  }

  Exception::Exception(int code, char const * fmt, va_list vargs) throw()
    : err(fsl_error_empty){
    this->error(code, fmt, vargs);
  }

  Exception::operator fsl_error * () throw(){
    return &this->err;
  }

  Exception::operator fsl_error const * () const throw(){
    return &this->err;
  }

  char const * Exception::messsage() const throw(){
    return this->what();
  }

  char const * Exception::what() const throw() {
    return (FSL_RC_OOM==this->err.code)
      ? fsl_rc_cstr(this->err.code)
      : fsl_buffer_cstr(&this->err.msg);
  }

  char const * Exception::codeString() const throw(){
    return fsl_rc_cstr(this->err.code);
  }

  int Exception::code() const throw(){
    return this->err.code;
  }

  OOMException::OOMException() throw()
    : Exception(FSL_RC_OOM)
  {
  }

} // namespace fsl

#undef CERR

Deleted bindings/cpp/Fossil.cpp.

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

































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"

/**

 */
namespace fsl {


  Buffer::~Buffer() throw(){
    this->clear();
  }

  Buffer::Buffer(fsl_size_t startingSize)
    : buf(fsl_buffer_empty)
  {
    if(startingSize){
      if(fsl_buffer_reserve(&this->buf, startingSize)){
        throw OOMException();
      }
    }
  }

  Buffer::Buffer()
    : buf(fsl_buffer_empty)
  {}

  Buffer & Buffer::operator=(Buffer const & other){
    fsl_size_t const oldSize = this->buf.capacity;
    this->reset();
    if((&other != this)
       && !other.empty()
       && fsl_buffer_append(*this, other.mem(), other.used())
       ){
      throw OOMException();
    }
    /* A rather arbitrary heuristic to determine whether or not
       we should free up some spce by to resizing buf()
       after re-using it... */
    if((this->buf.capacity == oldSize)
       && (this->buf.capacity > 20)
       && (this->buf.used < (this->buf.capacity/2))){
      fsl_buffer_resize( *this, this->buf.used );
    }
    return *this;
  }

  Buffer::Buffer(Buffer const & other)
  : buf() {
    if(!other.empty()
       && fsl_buffer_append(*this, other.mem(), other.used())
       ){
      throw OOMException();
    }
  }

  Buffer::operator fsl_buffer *() throw(){
    return &this->buf;
  }

  Buffer::operator fsl_buffer const *() const throw(){
    return &this->buf;
  }

  fsl_buffer * Buffer::handle() throw(){
    return &this->buf;
  }
  
  fsl_buffer const * Buffer::handle() const throw(){
    return &this->buf;
  }

  bool Buffer::empty() const throw() { return 0==this->buf.used; }

  fsl_size_t Buffer::used() const throw() { return this->buf.used; }

  fsl_size_t Buffer::capacity() const throw() { return this->buf.capacity; }

  unsigned char const * Buffer::mem() const throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  unsigned char * Buffer::mem() throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  Buffer & Buffer::clear() throw() {
    fsl_buffer_clear(*this);
    return *this;
  }

  Buffer & Buffer::reset() throw() {
    fsl_buffer_reuse(*this);
    return *this;
  }

  Buffer & Buffer::reserve(fsl_size_t n){
    int const rc = fsl_buffer_reserve(*this, n);
    if(rc) throw Exception(rc, "Buffer::reserve() failed");
    return *this;
  }

  Buffer & Buffer::resize(fsl_size_t n){
    int const rc = fsl_buffer_resize(*this, n);
    if(rc) throw Exception(rc, "Buffer::resize() failed");
    return *this;
  }

  Buffer::iterator Buffer::begin() throw() {
    return this->buf.used
      ? this->buf.mem
      : NULL;
  }

  Buffer::iterator Buffer::end() throw() {
    return this->buf.used
      ? this->buf.mem + this->buf.used
      : NULL;
  }

  Buffer::const_iterator Buffer::begin() const throw() {
    return this->buf.mem;
  }

  Buffer::const_iterator Buffer::end() const throw() {
    return this->buf.used
      ? this->buf.mem + this->buf.used
      : NULL;
  }

  std::ostream & operator<<( std::ostream & os, Buffer const & b ){
    fsl_size_t n = 0;
    char const * s = fsl_buffer_cstr2(b, &n);
    if(n) os.write(s, n);
    return os;
  }

  Buffer & Buffer::appendf(char const * fmt, ...){
    va_list vargs;
    int rc = 0;
    va_start(vargs,fmt);
    rc = fsl_buffer_appendfv(*this, fmt, vargs);
    va_end(vargs);
    if(rc){
      throw Exception(rc, "fsl_appendfv() failed: %s",
                      fsl_rc_cstr(rc));
    }
    return *this;
  }

  char const * Buffer::c_str() const throw(){
    return fsl_buffer_cstr(*this);
  }

  void Buffer::toss(int errorCode) const{
    throw Exception(errorCode, "%s", fsl_buffer_cstr(*this));
  }

  int fsl_input_f_std_istream( void * state, void * dest, fsl_size_t * n ){
    std::istream * is = static_cast<std::istream *>(state);
    try{
      /**
         We have to read byte-by-byte to fullfil the requirement that
         we write the number of read bytes to *n. std::istream::read()
         does not give us a (direct) way to know exactly how many
         bytes were read before EOF.
      */
      int rc;
      fsl_size_t i;
      unsigned char * out = (unsigned char *) dest;
      for( i = 0; i < *n; ++i ){
        rc = is->get();
        if(is->eof()){
          *n = i;
          return 0;
        }else if(!is->good()){
          return FSL_RC_IO;
        }else{
          *out++ = rc & 0xFF;
        }
      }
      return 0;
    }catch(...){
      return FSL_RC_IO;
    }
  }

} // namespace fsl

Deleted bindings/cpp/Makefile.in.

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

































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
all:
include ../../subdir-inc.make
#$(error $(TOP_SRCDIR))
#CPPFLAGS += -I$(TOP_INCDIR)
#CPPFLAGS += -I$(TOP_SRCDIR)/include
CPPFLAGS += -I$(TOP_SRCDIR)/src# workaround for in-tree sqlite3.h
CXXFLAGS += -fPIC -Wall -Werror

#INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include

LIBFOSSIL.LDFLAGS := -L$(TOP_SRCDIR) -lfossil -lz

#ifeq (,$(strip $(filter distclean clean,$(MAKECMDGOALS))))
#$(LIBFOSSIL.LDFLAGS):
#	$(MAKE) -C ..
#endif

CPP.SRC := 	\
	Context.cpp \
	Db.cpp \
	Deck.cpp \
	Exception.cpp \
	Fossil.cpp \
	OStream.cpp

OBJECTS := $(patsubst %.cpp,%.o,$(CPP.SRC))

libfossil++.DLL.OBJECTS := $(OBJECTS)
libfossil++.BIN.LDFLAGS := -lstdc++ -fPIC $(LIBFOSSIL.LDFLAGS)# -lsqlite3 -lz
libfossil++.LIB.OBJECTS := $(libfossil++.DLL.OBJECTS)
Makefile: Makefile.in
$(libfossil++.DLL.OBJECTS): Makefile
########################################################################
# Shared lib
$(eval $(call ShakeNMake.CALL.RULES.DLLS,libfossil++))
all: $(libfossil++.DLL)
$(libfossil++.DLL): $(libfossil++.DLL.OBJECTS)
#Static lib (don't work well with _some_ C++ template uses)
#$(eval $(call ShakeNMake.CALL.RULES.LIBS,libfossil++))
#all: $(libfossil++.LIB)
#$(libfossil++.LIB): $(libfossil++.LIB.OBJECTS)

test.BIN.OBJECTS := test.o
ifeq (0,1)
test.BIN.LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) -lstdc++ -L. -lfossil++ @SH_LINKFLAGS@
else
test.BIN.OBJECTS += $(libfossil++.DLL.OBJECTS)
test.BIN.LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(EXTRA_LIBS) -lstdc++ @SH_LINKFLAGS@
endif
$(eval $(call ShakeNMake.CALL.RULES.BINS,test))
all: $(test.BIN)


########################################################################
# A quick-n-dirty amalgamation build...
INCD := $(TOP_INCDIR)/fossil-scm
AMAL_D := $(TOP_SRCDIR_REL)
AMAL_C := $(AMAL_D)/libfossil.c
AMAL_H := $(AMAL_D)/libfossil.h
AMAL_CPP := $(AMAL_D)/libfossil++.cpp
AMAL_HPP := $(AMAL_D)/libfossil++.hpp
AMAL_CONF.H := $(AMAL_D)/libfossil-config.h
CLEAN_FILES += $(AMAL_HPP) $(AMAL_CPP)

$(AMAL_C) $(AMAL_H):
	$(MAKE) -C $(TOP_SRCDIR_REL)/src amal

AMAL_CPP.SRC := \
	$(CPP.SRC)

# ACHTUNG: in AMAL_H.SRC, $(AMAL_CONF.H) MUST be included first.
AMAL_HPP.SRC := \
	$(INCD)/fossil.hpp

$(AMAL_HPP.SRC):
$(AMAL_CPP.SRC):
$(AMAL_HPP): Makefile $(AMAL_HPP.SRC)
$(AMAL_CPP): Makefile $(AMAL_HPP) $(AMAL_CPP.SRC)
$(AMAL_CONF.H): $(MAIN_MAKEFILES)
	@echo "Generating $@ ..."
	cd $(TOP_SRCDIR_REL) && sh configure --amal

$(AMAL_HPP):
	@echo "Creating $@..."
	@{ \
		echo '#if !defined(FSLPP_AMALGAMATION_BUILD)'; \
		echo '#define FSLPP_AMALGAMATION_BUILD 1'; \
		echo '#endif'; \
		echo '#include "$(notdir $(AMAL_H))"'; \
	} > $@
	@{ \
		for i in $(AMAL_HPP.SRC); do \
			echo "/* start of file $$i */"; \
			cat $$i; \
			echo "/* end of file $$i */"; \
		done; \
	} | sed  \
		 -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h[>"]/d' \
		 -e '/[ ]*#[ ]*include[ ]*.*".*config\.h[>"]/d' \
		>> $@

$(AMAL_CPP): $(AMAL_HPP)
	@echo "Creating $@..."
	@echo '#include "$(notdir $(AMAL_HPP))"' > $@
	@{ \
		for i in $(AMAL_CPP.SRC); do \
			echo "/* start of file $$i */"; \
			cat $$i; \
			echo "/* end of file $$i */"; \
		done; \
	} | sed \
		 -e '/[ ]*#[ ]*include[ ]*.*fossil.*\.h.*[>"]/d' \
		>> $@

AMAL_CPP_FLAGS := -I$(TOP_INCDIR) -I$(TOP_SRCDIR_REL) -I. -I$(TOP_SRCDIR_REL)/src# for sqlite3.h :/
.PHONY: amal
amal: $(AMAL_CPP)
	@ls -ls $(AMAL_CPP) $(AMAL_HPP)
	@if which g++ >/dev/null; then \
		echo "Trying GCC..."; \
		g++ -c $(AMAL_CPP) -pedantic -Wstrict-aliasing -Wall -Werror -Wno-long-long $(AMAL_CPP_FLAGS); \
	fi; true
	@if which clang >/dev/null; then \
		echo "Trying clang..."; \
		clang -c $(AMAL_CPP) -pedantic -Wstrict-aliasing -Wall -Werror -Wno-long-long $(AMAL_CPP_FLAGS); \
	fi; true

# /amalgamation
########################################################################

Deleted bindings/cpp/OStream.cpp.

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














































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp"

/**

 */
namespace fsl {


  fsl_int_t fsl_appendf_f_std_ostream( void * state,
                                       char const * data,
                                       fsl_int_t n ){
    std::ostream * os = static_cast<std::ostream *>(state);
    try{
      os->write( data, (std::streamsize)n );
      return (*os) ? 0 : FSL_RC_IO;
    }catch(...){
      return FSL_RC_IO;
    }
  }

  int fsl_output_f_std_ostream( void * state,
                                void const * data,
                                fsl_size_t n ){
    std::ostream * os = static_cast<std::ostream *>(state);
    try{
      os->write( (char const *)data, (std::streamsize)n );
      return (*os) ? 0 : FSL_RC_IO;
    }catch(...){
      return FSL_RC_IO;
    }
  }


  ContextOStreamBuf::~ContextOStreamBuf() throw(){
    this->sync();
    if( this->m_os ){
      this->m_os->rdbuf( this->m_old );
    }
  }

  void ContextOStreamBuf::setup( fsl_cx * fx ){
    if(!fx) throw Exception(FSL_RC_MISUSE,
                            "ContextOStreamBuf requires "
                            "a non-NULL fsl_cx.");
    this->f = fx;
    this->setp( 0, 0 );
    this->setg( 0, 0, 0 );
    if(m_os) m_os->rdbuf( this );
  }

  ContextOStreamBuf::ContextOStreamBuf( fsl_cx * f, std::ostream & os )
    : f(NULL),
      m_os(&os),
      m_old(os.rdbuf()){
    this->setup(f);
  }

  ContextOStreamBuf::ContextOStreamBuf( fsl_cx * f )
    : f(NULL),
      m_os(NULL),
      m_old(NULL){
    this->setup(f);
  }

  ContextOStreamBuf::ContextOStreamBuf( Context & cx, std::ostream & os )
    : f(NULL),
      m_os(&os),
      m_old(os.rdbuf()){
    this->setup(cx.handle());
  }

  ContextOStreamBuf::ContextOStreamBuf( Context & cx )
    : f(NULL),
      m_os(NULL),
      m_old(NULL){
    this->setup(cx.handle());
  }


  int ContextOStreamBuf::overflow( int c ){
    char const ch = c & 0xFF;
    int const rc = fsl_output(this->f, &ch, 1);
    if(rc) throw Exception(FSL_RC_IO,
                           "fsl_output() failed with code %d (%s)",
                           rc, fsl_rc_cstr(rc));
    else return 0;
  }

  int ContextOStreamBuf::sync(){
    return this->f
      ? fsl_flush(this->f)
      : FSL_RC_IO;
  }

  ContextOStream::~ContextOStream() throw(){
    delete this->sb;
  }

  ContextOStream::ContextOStream( fsl_cx * f )
    :f(f),
     sb(new ContextOStreamBuf(f, *this)){
  }

  ContextOStream::ContextOStream( Context & cx )
    :f(cx.handle()),
     sb(new ContextOStreamBuf(f, *this)){
  }

  ContextOStream & ContextOStream::appendf(char const * fmt, ...){
    va_list vargs;
    int rc = 0;
    va_start(vargs,fmt);
    rc = fsl_outputfv(this->f, fmt, vargs);
    va_end(vargs);
    if(rc){
      throw Exception(rc, "fsl_outputfv() failed: %s",
                      fsl_rc_cstr(rc));
    }
    return *this;
  }

  FslOutputFStreamBuf::~FslOutputFStreamBuf() throw(){
    if( this->m_os ){
      this->m_os->rdbuf( this->m_old );
    }
  }

  void FslOutputFStreamBuf::setup( fsl_output_f f, void * state ){
    if(!f) throw Exception(FSL_RC_MISUSE,
                           "FslOutputFStream output function may "
                           "not be NULL.");
    this->out = f;
    this->outState = state;
    this->setp( 0, 0 );
    this->setg( 0, 0, 0 );
    if(m_os) m_os->rdbuf( this );
  }

  FslOutputFStreamBuf::FslOutputFStreamBuf( fsl_output_f f, void * state )
    : out(NULL), outState(NULL),
      m_os(NULL), m_old(NULL)
  {
    this->setup(f, state);
  }

  FslOutputFStreamBuf::FslOutputFStreamBuf( fsl_output_f f, void * state,
                                            std::ostream & os)
    : out(NULL), outState(NULL),
      m_os(&os), m_old(os.rdbuf())
  {
    this->setup(f, state);
  }

  int FslOutputFStreamBuf::overflow( int c ){
    char const ch = c & 0xFF;
    int const rc = this->out( this->outState, &ch, 1);
    if(rc) throw Exception(FSL_RC_IO,
                           "fsl_output_f() proxy failed with code "
                           "%d (%s)", rc, fsl_rc_cstr(rc));
    else return 0;
  }

  int FslOutputFStreamBuf::sync(){
    return 0;
  }

  FslOutputFStream::~FslOutputFStream() throw(){
    delete this->sb;
  }

  FslOutputFStream::FslOutputFStream( fsl_output_f out, void * outState )
    : sb(new FslOutputFStreamBuf(out, outState, *this)){
  }

  fsl_int_t FslOutputFStream::fslAppendfF( void * state,
                                           char const * s, fsl_int_t n ){
    FslOutputFStream * out = static_cast<FslOutputFStream*>(state);
    if(out->good()){
      out->write( s, (std::streamsize)n );
      return out->good() ? 0 : -1;
    }else return -1;
  }

  FslOutputFStream & FslOutputFStream::appendf(char const * fmt, ...){
    va_list vargs;
    fsl_int_t rc = 0;
    va_start(vargs,fmt);
    rc = fsl_appendfv(FslOutputFStream::fslAppendfF, this, fmt, vargs);
    va_end(vargs);
    if(rc<0) throw Exception(FSL_RC_IO,
                             "fsl_appendfv() failed mysteriously.");
    return *this;
  }

  BufferOStream::BufferOStream(fsl_buffer * b) :
    FslOutputFStream(fsl_output_f_buffer, b){
    if(!b) throw Exception(FSL_RC_MISUSE,
                           "fsl_buffer argument may not be NULL.");
  }

  BufferOStream::~BufferOStream() throw(){
  }

} // namespace fsl

Deleted bindings/cpp/test.cpp.

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















































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#include "fossil-scm/fossil.hpp" /* MUST come first b/c of config bits. */
#include <cassert>
#include <iostream>
#include <fstream>
#include <list>
#include <cstdlib> /* EXIT_SUCCESS, EXIT_FAILURE */

#define CERR std::cerr << __FILE__ << ":" << std::dec << __LINE__ << " : "
#define COUT std::cout << __FILE__ << ":" << std::dec << __LINE__ << " : "

#define RCStr(C) fsl_rc_cstr(C)

/**
   A functor for use with fsl::Stmt::eachRow().
*/
struct RowDumper {
  std::ostream & os;
  char const * separator;
  bool showHeader;
  explicit RowDumper(std::ostream & os = std::cout)
    : os(os),
      separator("\t"),
      showHeader(true)
  {}
  void operator()(fsl::Stmt &s) const{
    int i;
    int const n = s.columnCount();
    if(this->showHeader && (1==s.stepCount())){
      for(i = 0; i < n; ++i ){
        this->os << (i ? this->separator : "") << s.columnName(i);
      }
      this->os << '\n';
    }
    for(i = 0; i < n; ++i ){
      this->os << (i ? this->separator : "") << s.getText(i);
    }
    this->os << '\n';
  }
};

static void test_db_1(){
  namespace f = fsl;
  char const * dbName = 0
    ? "/fail"
    : ":memory:"
    ;
  //using fsl::Exception;
  //throw Exception(FSL_RC_NYI, "Just %s", "testing");
  f::Db db;
  f::Stmt st(db);
  db.open(dbName, FSL_OPEN_F_RWC);
  COUT << "Opened db: "<<db.filename()<<'\n';
  db.begin();
  st.prepare("CREATE TABLE t(a INTEGER,b TEXT)").stepExpectDone();
  
  st.finalize().prepare("INSERT INTO t(a,b) VALUES(?1,?2)");
  // Try out the StmtBinder for insertions...
  f::StmtBinder b(st);
  b
    (1)("once").insert()
    (2)("twice").insert()
    (3.3)("Quite possibly thrice-point-thrice")
    .insert();

  int threw = 0;
  try{
    b(7)(8)
      ("must throw - too many bind() values");
  }catch(f::Exception const & ex){
    threw = ex.code();
  }
  assert(FSL_RC_RANGE==threw);

  // Bind a std::list of parameters... (not terribly useful, probably)
  typedef std::list<std::string> LI;
  LI li;
  li.push_back("4");
  li.push_back("fource. Or fice.");
  b.bindList(li).insert();

  st.finalize()
    .prepare("SELECT rowid, a, b FROM t ORDER BY a")
    .eachRow( RowDumper() )
    .finalize();

  f::Buffer sb;
  (sb << "SELECT ").appendf("%Q", "percent 'Q'");
  st.prepare(sb)
    .eachRow( RowDumper() );

}

static void test_stream_1(fsl::Context & cx){
  namespace f = fsl;

  f::ContextOStream fout(cx);
  {
    char const * sym = "rid:1";
    fout << "UUID of ["<<sym<<"]: " << cx.symToUuid(sym)<<'\n';
  }

  {
    fsl_uuid_cstr uuid = NULL;
    fsl_id_t rid = 0;
    fsl_ckout_version_info(cx, &rid, &uuid);
    fout << "Checkout version: "<<rid << " ==> "<<uuid<<'\n';
  }

  char const * filename = 1
    ? __FILE__
    : "/fail"
    ;
  std::ifstream is(filename);
  if(!is.good()){
    throw f::Exception(FSL_RC_IO,"Cannot open input file: %s",
                       filename);
  }
  f::Buffer buf;
  int rc = fsl_buffer_fill_from( buf,
                                 f::fsl_input_f_std_istream,
                                 &is );
  if(rc){
    throw f::Exception(rc,"Error (%d) %s reading from file: %s",
                       rc, fsl_rc_cstr(rc), filename);
  }
  assert(buf.used());
  COUT << "Read "<< buf.used()
       <<" bytes from "<<filename
       <<" via std::istream proxy.\n";
  buf.reset();
  assert(0==buf.used());
  assert(0<buf.capacity());
  buf << "Hi, world! ";
  buf.appendf("%Q", "sql 'escaped'") << '\n';
  COUT << "Buffer contents: " << buf;

  buf.reset();
  assert(0==buf.used());
  assert(0<buf.capacity());
  f::FslOutputFStream ops(fsl_output_f_buffer, buf.handle());
  ops << "Hi, world!";
  assert(10 == buf.used());

  buf.clear();
  assert(0==buf.used());
  assert(0==buf.capacity());
  assert(NULL==buf.mem());
}

static void test_deck_1(fsl::Context &cx){
  namespace f = fsl;
  int threw = 0;
  f::Deck d(cx, FSL_SATYPE_CONTROL);
  f::ContextOStream fout(cx);

  try {
    d.assertHasRequiredCards();
  }catch(f::Exception const &ex){
    threw = ex.code();
    fout << "Got expected exception: " << ex.what() << '\n';
  }
  assert(FSL_RC_SYNTAX==threw);

  {
    f::Context::Transaction tr(cx)
      /* We'll roll back everything done here... */
      ;
    const std::string tgtArty(cx.symToUuid("rid:1"));
      /* self-referencing tag in a FSL_SATYPE_CONTROL is not legal, so
         we arbitrarily choose the initial empty checking. */;
    fout << "Custom "<< fsl_satype_cstr(d.type()) << " deck:\n";
    d.setCardD()
      .setCardU()
      .addCardT(FSL_TAGTYPE_ADD, "myTag", tgtArty.c_str(), "its value")
      .addCardT(FSL_TAGTYPE_PROPAGATING, "myOtherTag", tgtArty.c_str())
      .addCardT(FSL_TAGTYPE_CANCEL, "sumdumtag", tgtArty.c_str())
      //.unshuffle().output(fout)
      ;
    d.save();
    fout << "RID/UUID after save (will be rolled back): "<<d.rid()
         << ' ' << d.uuid()<<'\n';

    {

      typedef f::Deck::TCardIterator TagIter;
      TagIter it(d);
      TagIter end;
      fout << "Iterating over tags:";
      for( ; it != end; ++it ){
        fsl_card_T const * tag = *it;
        fout << ' ' << fsl_tag_prefix_char(tag->type)
             << tag->name;
      }
      fout << '\n';
    }

    fout << "Re-read artifact via Deck::load():\n";
    fout << f::Deck(cx).load( d.rid() );

    f::Buffer content;
    cx.getContent( d.rid(), content );
    fout << "And again via Context::getContent():\n" << content;
  }

  if(0){
    fsl_id_t rid = 1;
    fout << "Loading checkin #"<<rid<<"...\n";
    d.load( rid ).output( fout );
  }

  { /* FCardIterator... */
    f::Deck withF(cx);
    withF.load("current");
    f::Deck::FCardIterator fit(withF);
    f::Deck::FCardIterator end;
    fout << "Iterating over F-cards...\n";
    assert(*fit);
    assert(!*end);
    assert(fit!=end);
    int counter = 0;
    for( ; fit != end; ++fit, ++counter ){
      fsl_card_F const * fc = *fit;
      assert(fc);
      assert(fc->name);
      assert(fit->name == fc->name);
    }
    fout << "Traversed "
         <<counter<<" F-card(s) in deck #"
         <<withF.rid()<<".\n";

  }

}

int main(int argc, char const * const * argv ){
  int rc = EXIT_SUCCESS;
  try {
    namespace f = fsl;
    f::Context cx;
    f::ContextOStream fout(cx);
    fout << "ContextOStream...\n";
    f::ContextOStreamBuf cout2(cx, std::cerr);
    f::ContextOStreamBuf cerr2(cx, std::cout);
    CERR << "cerr through fsl_output()\n";
    COUT << "cout through fsl_output()\n";
    cx.openCheckout();

    test_db_1();
    test_stream_1(cx);
    test_deck_1(cx);


    /* Manually closing the Context is not generally required,
       we're just testing an assertion or three... */
    assert(cx.dbRepo().isOpened());
    assert(cx.dbCheckout().isOpened());
    cx.closeDbs();
    assert(!cx.dbRepo().isOpened());
    assert(!cx.dbCheckout().isOpened());
  }catch(fsl::Exception const &fex){
    CERR << "EXCEPTION: " << RCStr(fex.code()) << ": " << fex.what() << '\n';
    rc = EXIT_FAILURE;
  }catch(std::exception const &ex){
    CERR << "EXCEPTION: " << ex.what() << '\n';
    rc = EXIT_FAILURE;
  }
  COUT << "Exiting. Result = " << (rc ? ":`(" : ":-D") << '\n';
  return rc;
}

Deleted bindings/s2/Makefile.

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
















































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
all:
include ../../subdir-inc.make

CPPFLAGS += -I$(TOP_SRCDIR)/src# workaround for in-tree sqlite3.h
CPPFLAGS += -fPIC
CPPFLAGS+=-DCWAL_ENABLE_TRACE=0

LIBFOSSIL.LDFLAGS := -L$(TOP_SRCDIR) -lfossil
ifeq (1,1)
  # In static build, we need some extra flags...
  LIBFOSSIL.LDFLAGS += $(LDFLAGS_MODULE_LOADER) -lpthread -lz
endif

ifeq (,$(strip $(filter distclean clean,$(MAKECMDGOALS))))
$(LIBFOSSIL.LDFLAGS):
	$(MAKE) -C ..
endif

S2_CLIENT_LDFLAGS := $(LIBFOSSIL.LDFLAGS) $(LDFLAGS_MODULE_LOADER)

########################################################################
# INC_SEARCH: $(call)able function. $(1) should be the the name of
# a C header file to search for under $(INCLUDES_PATH).
INCLUDES_PATH ?= $(HOME)/include /usr/local/include /usr/include
define INC_SEARCH
$(call ShakeNMake.CALL.FIND_FILE,$(1),$(INCLUDES_PATH))
endef

#$(error ltdl=$(HAVE_LT_DLOPEN) dl=$(HAVE_DLOPEN) mods=$(ENABLE_MODULES))
########################################################################
# Bins and libs...

########################################################################
# Check for readline
ENABLE_READLINE := $(FSL_ENABLE_READLINE)
ifeq (1,$(ENABLE_READLINE))
  READLINE_H := $(call INC_SEARCH,readline/readline.h)
  ifneq (,$(READLINE_H))
#    READLINE_IMPL := readline
    $(info Enabling GNU Readline)
    LDFLAGS_READLINE := -lreadline
  else
    $(info Not enabling GNU Readline)
    ENABLE_READLINE := 0
  endif
endif
# /readline
########################################################################

########################################
# Linenoise (terminal input lib)...
LN_OBJ :=
ENABLE_LINENOISE := 1
ifeq (1,$(ENABLE_READLINE))
  $(info Not enabling linenoise)
  ENABLE_LINENOISE := 0
else
  LN_DIR := linenoise
  ifneq (,$(wildcard $(LN_DIR)/*.c))
    ENABLE_LINENOISE := 1
    $(info Enabling linenoise)
  else
    $(info Not enabling linenoise)
    ENABLE_LINENOISE := 0
  endif
  ifeq (1,$(ENABLE_LINENOISE))
    LN_OBJ := $(LN_DIR)/linenoise.o $(LN_DIR)/utf8.o
    CLEAN_FILES += $(LN_OBJ)
    $(LN_DIR)/utf8.o: CPPFLAGS+=-std=c99 -DUSE_UTF8=1
    $(LN_DIR)/linenoise.o: CPPFLAGS+=-std=c99 -DUSE_UTF8=1 -D_BSD_SOURCE
  else
    LN_OBJ :=
  endif
endif
# /linenoise
########################################

########################################
# s2sh a.k.a. f-s2sh
shell2.o: CPPFLAGS+=-DS2_SHELL_EXTEND
shell2.o: CPPFLAGS+=-DS2SH_FOR_UNIT_TESTS=1
cliapp.o: CPPFLAGS+=-DCLIAPP_ENABLE_LINENOISE=$(ENABLE_LINENOISE)
cliapp.o: CPPFLAGS+=-DCLIAPP_ENABLE_READLINE=$(ENABLE_READLINE)
shell2.o shell_extend.o: CPPFLAGS+=-DS2_AMALGAMATION_BUILD


f-s2sh.BIN.OBJECTS := shell2.o shell_extend.o cliapp.o $(LN_OBJ) s2_amalgamation.o
f-s2sh.BIN.LDFLAGS := $(S2_CLIENT_LDFLAGS) $(LDFLAGS_READLINE)
$(eval $(call ShakeNMake.CALL.RULES.BINS,f-s2sh))
all: $(f-s2sh.BIN)
# s2's inclusion of miniz breaks its policy of building with
# the (-pedantic -std=c89) flags, and requires that we be less
# stringent with compilation flags :(...
s2_amalgamation.o: CFLAGS=-Wall -Werror -Wsign-compare -g -UNDEBUG -DDEBUG=1
s2_amalgamation.o shell2.o shell_extend.o: CPPFLAGS+=-DS2_ENABLE_ZLIB=1 -DHAVE_CONFIG_H
s2_amalgamation.o: config.h

# end bins and libs
########################################################################

########################################
# Set up file-specific CPPFLAGS/LDFLAGS.
# basic module setup
ifeq (1,$(ENABLE_MODULES))
  s2_amalgamation.o: CPPFLAGS+=-DS2_ENABLE_MODULES=1
  f-s2sh.BIN.LDFLAGS += $(LDFLAGS_MODULE_LOADER)
  ifeq (1,$(HAVE_LIBDL)) # use libdl
    s2_amalgamation.o: CPPFLAGS+=-DS2_HAVE_DLOPEN=1
  else # use libltdl
    s2_amalgamation.o: CPPFLAGS+=-DS2_HAVE_LTDLOPEN=1
  endif
endif
# end modules
########################################################################

########################################################################
# Unit test stuff...
UNIT_SCRIPT_LIST := $(sort $(subst ./,,$(wildcard unit/???-???-*.s2 unit2/???-*.s2)))
$(UNIT_SCRIPT_LIST):
S2SH.SHELL.FLAGS ?= --no-init-script
# -w
UNIT_RUN_CMD = ./$(f-s2sh.BIN) $(S2SH.SHELL.FLAGS)
UNIT_MEGA.S2 := UNIT.s2
UNIT_MEGA2.S2 := UNIT-import.s2
UNIT_GENERATED := $(UNIT_MEGA.S2) $(UNIT_MEGA2.S2)
CLEAN_FILES += $(UNIT_GENERATED)
$(UNIT_MEGA.S2): $(UNIT_SCRIPT_LIST) Makefile
	@echo "Generating $@..."
	@{ \
		false && echo "const INTERN_THESE=['object','array','integer','double','string','function','bool'];"; \
		for i in $(UNIT_SCRIPT_LIST); do \
			echo "scope {/* begin file: $$i */"; \
			cat $$i; \
			echo "/* end file: $$i */;;}"; \
			echo ""; \
		done; \
	} > $@
$(UNIT_MEGA2.S2): $(UNIT_SCRIPT_LIST) Makefile
	@echo "Generating $@..."
	@{ \
		false && echo "const INTERN_THESE=['object','array','integer','double','string','function','bool'];"; \
		for i in $(UNIT_SCRIPT_LIST); do \
			echo "import(false,'$$i');"; \
		done; \
	} > $@

.PHONY: unit unit-proxy unit2
unit-proxy: $(f-s2sh.BIN) $(UNIT_GENERATED)
	@for i in $(UNIT_SCRIPTS_ALL); do \
		cmd="$(UNIT_RUN_CMD) -f $$i"; \
		echo "****************************** Script [$$i]"; \
		echo $$cmd; $$cmd || exit $$?; \
		echo "****************************** Done [$$i]"; \
	done
	@echo "Done running through unit test scripts."

unit: UNIT_SCRIPTS_ALL:=$(UNIT_SCRIPT_LIST) $(UNIT_GENERATED)
unit: unit-proxy
unit2: UNIT_SCRIPTS_ALL:=$(filter unit2/%,$(UNIT_SCRIPT_LIST))
unit2: unit-proxy
.PHONY: unit-r
.PHONY: unit-rc
unit-r: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc
unit-r: unit
unit-s: S2SH.SHELL.FLAGS:=--no-init-script -rc -rc -nosi
unit-s: unit
unit-rc: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc
unit-rc: unit
unit-rsc: S2SH.SHELL.FLAGS:=--no-init-script -norv -norc -nosi
unit-rsc: unit
units:
	@for i in unit unit-r unit-rc unit-s unit-rsc; do \
		echo "Making $$i ..."; \
		$(MAKE) $$i || exit $$?; \
	done
include vg.make

Deleted bindings/s2/cliapp.c.

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




































































































































































































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif
#include "cliapp.h"
#include <string.h>
#include <assert.h>
#include <stdio.h> /* vsprintf() */
#include <stdlib.h> /* abort() */

#ifndef CLIAPP_ENABLE_READLINE
#  define CLIAPP_ENABLE_READLINE 0
#endif

#ifndef CLIAPP_ENABLE_LINENOISE
#  define CLIAPP_ENABLE_LINENOISE 0
#endif

#if CLIAPP_ENABLE_READLINE && CLIAPP_ENABLE_LINENOISE
#  error "Use *one* of CLIAPP_ENABLE_READLINE or CLIAPP_ENABLE_LINENOISE, not both."
#endif

#if CLIAPP_ENABLE_LINENOISE
#  include "linenoise/linenoise.h"
#elif CLIAPP_ENABLE_READLINE
#  include "readline/readline.h"
#  include "readline/history.h"
#endif


#if 1
#define MARKER(pfexp) if(1) printf("%s:%d:\t",__FILE__,__LINE__); \
  if(1) printf pfexp
#else
static void noop_printf(char const * fmt, ...) {}
#define MARKER(pfexp) if(0) noop_printf pfexp
#endif
#define MESSAGE(pfexp) printf pfexp

struct CliApp cliApp = {
0/*argc*/, 0/*argv*/,
0/*cursorNonflag*/,
0/*errMsg*/,
0/*flags*/,
vprintf/*print*/,
0/*argCallback*/,
{/*doubleDash*/
  0/*argc*/,
  0/*argv*/
},
{/*lineread*/
  CLIAPP_ENABLE_LINENOISE ? 1 : (CLIAPP_ENABLE_READLINE ? 2 : 0) /*enabled*/,
  0 /* historyFile */, 0 /*needsSave*/
}
};

enum {
/** Buffer size for cliapp_errmsg() */
CLIAPP_MSGBUF_SIZE = 1024 * 4,
/** Buffer size for cliapp_process_argv() argument keys. */
CLIAPP_ARGV_KBUF_SIZE = 1024 * 2,
/** Maximum cliapp_process_argv() arg count. */
CLIAPP_ARGV_COUNT = (int)(1024 * 2 / sizeof(CliAppArg))
};

static char cliAppErrBuf[CLIAPP_MSGBUF_SIZE] = {0};

static char const * cliapp_verrmsg(char const * fmt, va_list args){
  int rc;
  char * buf = cliAppErrBuf;
  rc = vsprintf(buf, fmt, args)
    /* Noting that vsnprintf() requires C99 and s2 aims to compile in
       strict C89 mode. */;
  if(rc >= CLIAPP_MSGBUF_SIZE){
    fprintf(stderr,"%s:%d: Internal misuse of error message buffer. "
            "Dangerous buffer overrun!\n",
            __FILE__, __LINE__);
    abort();
  }
  return cliApp.errMsg = buf;
}
static char const * cliapp_errmsg(char const * fmt, ...){
  /** Stores error messages, at least until it's overwritten. */
  char const * rc;
  va_list args;
  va_start(args,fmt);
  rc = cliapp_verrmsg(fmt, args);
  va_end(args);
  return rc;
}

void cliapp_err_clear(){
  cliApp.errMsg = 0;
  cliAppErrBuf[0] = 0;
}

char const * cliapp_err_get(){
  return cliApp.errMsg;
}

static const CliAppSwitch CliAppSwitch_end = CliAppSwitch_sentinel;
#define cliapp__switch_is_end(S) \
  (0==memcmp(S, &CliAppSwitch_end, sizeof(CliAppSwitch)))
int cliapp_switch_is_end(CliAppSwitch const *s){
  return cliapp__switch_is_end(s);
}

CliAppSwitch const * cliapp_switch_for_arg(CliAppArg const * arg,
                                           int alsoFlag){
  CliAppSwitch const * a = cliApp.switches;
  for(; !cliapp__switch_is_end(a); ++a){
    if((alsoFlag==0 || arg->dash==a->dash)
       && 0==strcmp(a->key, arg->key)){
       return a;
    }
  }
  return 0;
}

CliAppArg const * cliapp_arg_next_same(CliAppArg const * arg){
  CliAppArg const * a = arg;
  CliAppArg const * tail = cliApp.argv + cliApp.argc;
  if(arg < cliApp.argv || arg >= tail){
    return 0;
  }
  for( ++a; a < tail; ++a ){
    if(a->key && 0==strcmp(a->key, arg->key)) return a;
  }
  return 0;
}

CliAppArg const * cliapp_arg_nonflag(){
  CliAppArg const * p = 0;
  for(; cliApp.cursorNonflag < cliApp.argc;){
    p = cliApp.argv + cliApp.cursorNonflag;
    ++cliApp.cursorNonflag;
    if(p->key && 0==p->dash){
      return p;
    }
  }
  return 0;
}

void cliapp_arg_nonflag_rewind(){
  cliApp.cursorNonflag = 1;
}

CliAppArg const * cliapp_arg_flag(char const * key1,
                                  char const * key2,
                                  int *atPos){
  int i = atPos ? *atPos : 1;
  assert(key1 || key2);
  if(!key1 & !key2) return 0;
  for( ; i < cliApp.argc; ++i ){
    CliAppArg const * p = cliApp.argv + i;
    if(!p->key) continue;
    else if((key1 && 0==strcmp(key1,p->key))
             ||
             (key2 && 0==strcmp(key2,p->key))){
      if(atPos) *atPos = i;
      return p;
    }
  }
  return 0;
}

char const * cliapp_flag_prefix( int flag ){
  char const * flags[4] = {"+",  "", "-", "--"};
  assert(flag>=-1 && flag<=2);
  return flag>=-1 && flag<=2 ? flags[flag+1] : "";
}

void cliapp_switches_visit( CliAppSwitch_visitor_f visitor,
                            void * state ){
  CliAppSwitch const * s = cliApp.switches;
  assert(s);
  for( ; !cliapp__switch_is_end(s); ++s){
    if(visitor(s, state)) break;
  }  
}

void cliapp_args_visit( CliAppArg_visitor_f visitor, void * state,
                        unsigned short skipArgs ){
  int i = skipArgs;
  CliAppArg const * a = i <  cliApp.argc
    ? &cliApp.argv[i] : NULL;
  for( ; i < cliApp.argc; ++i, ++a ){
    if(a->key && visitor(a, i, state)) break;
  }
}

void cliapp_printv(char const *fmt, va_list vargs){
  if(cliApp.print){
    cliApp.print(fmt, vargs);
  }
}

void cliapp_print(char const *fmt, ...){
  if(cliApp.print){
    va_list vargs;
    va_start(vargs,fmt);
    cliApp.print(fmt, vargs);
    va_end(vargs);
  }    
}

void cliapp_warn(char const *fmt, ...){
    va_list vargs;
    va_start(vargs,fmt);
    vfprintf(stderr, fmt, vargs);
    va_end(vargs);
}

#if 0
static void cliapp_perr(char const *fmt, ...){
    va_list vargs;
    va_start(vargs,fmt);
    cliapp_verrmsg(fmt,vargs);
    va_end(vargs);
    va_start(vargs,fmt);
    vfprintf(stderr, fmt, vargs);
    va_end(vargs);
}
#endif

/**
   The list of arguments pointed to by cliApp.argv.
*/
static CliAppArg cliAppArgv[CLIAPP_ARGV_COUNT];
/**
   An internal buffer used to store --flag keys for those keys which
   require transformation. This is: for --flag=value, we copy the
   "flag" part to this buffer so we can NUL-terminate it. For flags
   with no '=' we simply refer to the original argv string pointers,
   as those are NUL terminated. (We "could" modify the original
   globals instead, but the thought of doing so makes me a ill.)
*/
static char cliAppKeyBuf[CLIAPP_ARGV_KBUF_SIZE]
/* Store all NUL-terminated flag keys here, stripped of their
   leading dashes and terminated at their '=' (if they had
   one). */;

/**
   Internal helper for cliapp_process_argv() which checks list to see
   if s is contained in it. list must be terminated by a NULL pointer.
*/
static char cliapp_check_seen( CliAppSwitch const * const * list,
                               CliAppSwitch const * s ){
  int i = 0;
  CliAppSwitch const * p;
  assert(s);
  while(1){
    p = list[i++];
    if(p == s) return 1;
    else if(!p) break;
  }
  return 0;
}

int cliapp_process_argv(int argc, char const * const * argv,
                        unsigned int reserved ){
  /*static CliAppSwitch const * sPending = 0;
    static CliAppArg * aPending = 0;*/
  int i, rc = 0, doubleDashPos = 0;
  char * k = &cliAppKeyBuf[0];
  char const *errMsg = 0;
  CliAppSwitch const * switchPendingVal = 0
    /* Switch with the CLIAPP_F_SPACE_VALUE which is expecting
       a value in the next argument. */;
  CliAppArg * argPendingVal = 0
    /* arg counterpart of switchPendingVal */;
  CliAppSwitch const * appSwitch = 0;
  CliAppSwitch const * seenList[CLIAPP_ARGV_COUNT+1]
    /* switches we've seen so far, so we can check for/enforce
       the CLIAPP_F_ONCE flag */;
  int seenCount = 0 /* number of entries in seenList */;
  if(reserved){/*unused*/}
  memset(seenList, 0, sizeof(seenList));
  assert(cliApp.switches
         && "cliApp.switches must be set by the client "
         "before calling this.");
  cliApp.argv = &cliAppArgv[0];
  memset(&cliAppArgv[0], 0, sizeof(cliAppArgv));
#define KCHECK if(k>=&cliAppKeyBuf[0]+CLIAPP_ARGV_KBUF_SIZE) goto err_overflow
#define BADFLAG(MSG) errMsg=MSG; goto misuse
  for(i = 0; i < argc && 0==doubleDashPos; ++i){
    CliAppArg * p;
    char const * arg;
    int dashes = 0 /* number of dashes on the current flag */;
    int callbackIndex = i
      /* index to pass to callbacks. Gets modified in one case */;
    if(i == CLIAPP_ARGV_COUNT){
      cliapp_errmsg("Too many (%d) arguments: internal buffer "
                    "limit (%d) would be reached.", argc,
                    CLIAPP_ARGV_COUNT);
      return CLIAPP_RC_RANGE;
    }
    appSwitch = 0;
    p = cliApp.argv + i;
    arg = argv[i];
    if(*arg=='+'){
      dashes = -1;
      ++arg;
    }
    while(*arg && '-'==*arg && dashes<4){
      if(-1==dashes){
        dashes = 3 /* trigger error below */;
        break;
      }
      ++dashes;
      ++arg;
    }
    if(dashes>2){
      BADFLAG("Too many dashes on flag.");
    }
    p->dash = dashes;
    p->key = dashes ? k : arg
      /* dashed args get stored, without dashes, in cliAppKeyBuf so
         that we can terminate them with a NUL at their '='.  Args
         with no dashes are referenced to as-is - there's no need to
         copy them for termination purposes.  We don't know, at this
         point, whether a '=' is pending, but we pessimistically
         (optimistically?) assume there is.
      */;
    while(*arg && '='!=*arg){
      if(dashes){
        *k++ = *arg++;
        KCHECK;
      }      
      else ++arg;
    }
    if(*arg && !*p->key){
      BADFLAG("Empty flag name.");
    }
    KCHECK;
    if(*arg){
      assert('='==*arg);
      if(-1==dashes && '='==*arg){
        BADFLAG("+flags may not have a value.");
      }
      p->value = ++arg;
    }else if(!*p->key && 2==dashes && !doubleDashPos){
      doubleDashPos = i;
      p->key = p->value = 0;
    }
    if(dashes){
      *k++ = 0;
    }
    if(doubleDashPos && !switchPendingVal){
      /* Once we've encountered --, we must not continue to process
         flags because a flag after -- is typically intended for some
         downstream process, not the app on whose behalf we're
         running, and might semantically collide with flags from our
         own app. Also, such flags might have syntaxes we cannot
         handle, so even processing them as flags is potentially a
         bug. Rather than process them, we point cliApp.doubleDash.*
         to that state so the client can deal with it (possibly be
         passing it back into this function!).

         If switchPendingVal is not NULL we need to fall through to
         catch the error case of a missing value at the end of the
         arguments.
      */
      break;
    }else if(!doubleDashPos){
      ++cliApp.argc;
    }
    if(0){
      MARKER(("Arg #%d %d %s %s %s\n",
              i, p->dash, p->key,
              p->value ? "=" : "",
              p->value ? p->value : ""));
    }
    if(switchPendingVal){
      assert(argPendingVal);
      if(p->dash){
        appSwitch = switchPendingVal /* for error string */;
        BADFLAG("Got a flag while the previous flag was expecting "
                "a value");
      }else{
        argPendingVal->value = p->key;
        p->key = p->value = 0;
        p->dash = 0;
        p = argPendingVal /* for the upcoming callback(s) */;
        argPendingVal = 0;
        switchPendingVal = 0;
        --callbackIndex;
      }
    }
    assert(!doubleDashPos);
    if(p->dash && p->key){ /* -flag/--flag/+flag */
#if defined(DEBUG)
      static int check = 0;
      check = callbackIndex;
      assert(cliapp_arg_flag(p->key, 0, &check));
      assert(check == callbackIndex);
#endif
      appSwitch = cliapp_switch_for_arg(p, 1);
      if(!appSwitch){
        BADFLAG("Unknown flag");
      }
      else if((appSwitch->pflags & CLIAPP_F_ONCE)
              && cliapp_check_seen(seenList, appSwitch)){
        BADFLAG("Flag may only be provided once.");
      }
      else if(appSwitch->value && !p->value){
        if(i==argc-1){
          BADFLAG("Flag expecting a value at the end of the arguments");
        }
        switchPendingVal = appSwitch;
        argPendingVal = p;
      }else if(appSwitch->callback){
        rc = appSwitch->callback(callbackIndex, appSwitch, p);
        if(rc) return rc;
      }
      if(!switchPendingVal){
        seenList[seenCount++] = appSwitch;
      }
    }else{
      assert(cliapp_arg_nonflag()==p);
    }
    if(!switchPendingVal && cliApp.argCallack){
      rc = cliApp.argCallack(callbackIndex, appSwitch, p);
      if(rc) return rc;
    }
  }/*for(each arg)*/
  if(cliApp.argCallack){
    rc = cliApp.argCallack(i, 0, 0);
    if(rc) return rc;
  }

#undef BADFLAG
#undef KCHECK
  cliApp.cursorNonflag = 1 /* skip argv[0] */;
  if(doubleDashPos && doubleDashPos+1<argc){
    cliApp.doubleDash.argc = argc - doubleDashPos - 1;
    cliApp.doubleDash.argv = &argv[doubleDashPos+1];
  }
  return 0;
  misuse:
  cliapp_errmsg("Argument #%d: %s%s%s" /* (1) */
                "%s" /*(2)*/
                "%s%.*s" /*(3)*/
                "%s%.*s" /*(4)*/,
                /*(1)*/
                i+1, errMsg, (i<argc) ? ": " : "",
                (i<argc) ? argv[i] : "",
                /*(2)*/ 
                appSwitch ? ", flag: " : "",
                /*(3)*/ 
                appSwitch ? cliapp_flag_prefix(appSwitch->dash) : "",
                (CLIAPP_MSGBUF_SIZE/4),
                appSwitch ? appSwitch->key : "",
                /*(4)*/ 
                appSwitch && appSwitch->value ? "=" : "",
                CLIAPP_MSGBUF_SIZE/4,
                appSwitch ? appSwitch->value : "");
  return CLIAPP_RC_FLAG;
  err_overflow:
  cliapp_errmsg("Too many CLI flag keys: their accumulated names "
                "would overrun our %d-byte buffer.",
                CLIAPP_ARGV_KBUF_SIZE);
  return CLIAPP_RC_RANGE;
}

char * cliapp_lineedit_read(char const * prompt){
#if CLIAPP_ENABLE_LINENOISE
  return linenoise(prompt);
#elif CLIAPP_ENABLE_READLINE
  return readline(prompt);
#else
  if(prompt){/*avoid unused param warning*/}
  return 0;
#endif
}

void cliapp_lineedit_free(char * line){
#if CLIAPP_ENABLE_LINENOISE || CLIAPP_ENABLE_READLINE
  free(line);
#else
  if(line){
    assert(!"Where did this memory come from?");
  }
#endif
}

int cliapp_lineedit_load(char const * fname){
  if(!fname) fname = cliApp.lineread.historyFile;
  if(!fname || !*fname) return 0;
#if CLIAPP_ENABLE_LINENOISE
  return linenoiseHistoryLoad(fname);
#elif CLIAPP_ENABLE_READLINE
  return read_history(fname);
#else
  if(fname){/*avoid unused param warning*/}
  return CLIAPP_RC_UNSUPPORTED;
#endif
}

int cliapp_lineedit_save(char const * fname){
  int rc = 0;
  if(!fname) fname = cliApp.lineread.historyFile;
  if(!cliApp.lineread.needsSave
     || !fname || !*fname) return 0;
#if CLIAPP_ENABLE_LINENOISE
  rc = linenoiseHistorySave(fname) ? CLIAPP_RC_IO : 0;
#elif CLIAPP_ENABLE_READLINE
  rc = write_history(fname) ? CLIAPP_RC_IO : 0;
#else
  if(fname){/*avoid unused param warning*/}
  rc = CLIAPP_RC_UNSUPPORTED;
#endif
  if(!rc) cliApp.lineread.needsSave = 0;
  return rc;
}

int cliapp_lineedit_add(char const * line){
  assert(line);
#if CLIAPP_ENABLE_LINENOISE
  cliApp.lineread.needsSave = 1;
  linenoiseHistoryAdd(line);
  return 0;
#elif CLIAPP_ENABLE_READLINE
  cliApp.lineread.needsSave = 1;
  add_history(line);
  return 0;
#else
  if(line){/*avoid unused param warning*/}
  return CLIAPP_RC_UNSUPPORTED;
#endif
}

int cliapp_repl(CliApp_repl_f f, char const * const * prompt,
                int addHistoryPolicy, void * state){
  char * line;
  int rc = 0;
  while( !rc && (line = cliapp_lineedit_read(*prompt)) ){
    if(addHistoryPolicy<0) cliapp_lineedit_add(line);
    rc = f(line, state);
    if(!rc && addHistoryPolicy>0) cliapp_lineedit_add(line);
    cliapp_lineedit_free(line);
  }
  return rc;
}

#undef cliapp__switch_is_end

Deleted bindings/s2/cliapp.h.

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















































































































































































































































































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
#ifndef NET_WH_CLIAPP_H_INCLUDED
#define NET_WH_CLIAPP_H_INCLUDED
/**
   A mini-framework for handling some of the grunt work required by
   CLI apps. It's main intent is to provide a halfway sane system for
   handling CLI flags. It also provides an abstraction for CLI
   editing, supporting either libreadline or liblinenoise, but only
   supporting the most basic of editing facilities, not
   library-specific customizations (e.g. custom key bindings).

   This API has no required dependencies beyond the C89 standard
   libraries and requires no dynamic memory unless it's configured to
   use an interactive line-reading backend.

   License: Public Domain

   Author: Stephan Beal <stephan@wanderinghorse.net>
*/

#include <stdarg.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
   Flags for use with the CliAppSwitch::pflags field to modify
   how cliapp_process_argv() handles the switch.
*/
enum cliapp_switch_flags {
/**
   Indicates that a CliAppSwitch is only allowed to be provided once.
   If encountered more than once by cliapp_process_argv(), an error
   is triggered.
*/
CLIAPP_F_ONCE = 1,
/**
   Indicates that a given CliAppSwitch requires a value and accepts
   its value either in the form (-switch=value) or (-switch value).
   When such a flag is encountered, cliapp_process_argv() will report
   an error if the switch has no value (even if the flag appears at
   the end of the arguments list or immediately before the
   special-case "--" flag).

   Without this flag, switch values are only recognized in the form
   (-switch=flag).
*/
CLIAPP_F_SPACE_VALUE = 2
/*TODO?: CLIAPP_F_VALUE_REQUIRED = 4 (implied by CLIAPP_F_SPACE_VALUE) */
};

/**
   Result codes used by various library routines.
*/
enum cliapp_rc {
/** The non-error code */
CLIAPP_RC_OK = 0,
/** Indicates an error in flag processing. */
CLIAPP_RC_FLAG = -1,
/** Indicates a range-related error, e.g. buffer overrun or too many
    CLI arguments. */
CLIAPP_RC_RANGE = -2,
/** Indicates that an unsupported operation was requested. */
CLIAPP_RC_UNSUPPORTED = -3,
/** Indicates some sort of I/O error. */
CLIAPP_RC_IO = -4
};


/**
   Holds state for a single CLI argument, be it a flag or non-flag.
*/
struct CliAppArg {
  /**
     Must be 1 for single-dash flags, 2 for double-dash flags, and -1
     for '+' flags, and 0 for non-flags.
  */
  int dash;
  /**
     Fow switches, this holds the switch's key, without dashes. For
     non-switches, it holds the argument's value.
  **/
  char const * key;
  /**
     For switches, the description of their value (if any) is stored here.
     For non-switches, this is 0.
  */
  char const * value;
  /**
     Arbitrary value which may be set/used by the client. It's not
     used/modifed by this API.
  */
  int opaque;
};
typedef struct CliAppArg CliAppArg;

struct CliAppSwitch;

/**
   A callback type used by cliapp_process_argv() to notify the app
   when a CLI argument is processed which matches one of the app's
   defined flags. It is passed the argument index, the switch (if any)
   and the argument.

   Note that when processing switches flagged with
   CLIAPP_F_SPACE_VALUE, the indexes passed to this function might
   have gaps, as they skip over the VAL part of (-f VAL), instead
   effectively transforming that to (-f=VAL) before calling the
   callback for the -f switch.

   If the switch argument is NULL, the argument will be a non-flag
   value. Note that arguments starting at the special-case "--" flag
   are not passed on to the callback. Instead, such arguments get
   reported via the cliApp.doubleDash member.

   It gets passed two NULL values one time at the end of processing in
   order to allow the client code to do any final validation.

   If it returns non-0, cliapp_process_argv() will fail and return the
   callback's result.
*/
typedef int (*CliAppSwitch_callback_f)(int ndx, struct CliAppSwitch const * appSwitch,
                                       CliAppArg * arg);
/**
   Models a single flag/switch for a CLI app. It's not called
   CliAppFlag because that proved confusing together with CliAppArg.
*/
struct CliAppSwitch {
  /**
     Can be used by clients to, e.g. group help items by type or
     set various levels of help verbosity.
  */
  int opaque;
  /**
     As documented for CliAppArg::dash.
  */
  int dash;
  /**
     The flag name, without leading dashes.
  */
  char const * key;
  /**
     A human-readable description of its expected value, or 0 if the
     flag does not require a value.

     BUG? It may still be assigned a value by the caller. We
     currently require that behaviour for a special-case arg handler
     in s2sh2.
  */
  char const * value;
  /**
     Brief help text.
  */
  char const * brief;
  /**
     Optional detailed help text.
  */
  char const * details;

  /**
     Optional callback to be passed a CliAppArg instance after it's been
     initialized and confirmed as being a valid arg (defined in
     cliApp.switches). If cliApp.argCallack is also used, both
     callbacks are called, but this one is called first.
  */
  CliAppSwitch_callback_f callback;

  /**
     Reserved for future use by the cliapp interface, e.g. marking
     "has seen this flag before" in order to implement only-once
     behaviour.
  */
  int pflags;
};
typedef struct CliAppSwitch CliAppSwitch;

/**
   Client-defined CliApp.argv arrays MUST end with an entry identical
   to this one. The iteration-related APIs treat any entry which
   memcmp()'s as equivalent to this entry as being the end of th list.

   @see cliapp_switch_is_end()
*/
#define CliAppSwitch_sentinel {0,0,0,0,0,0,0,0}

/**
   Returns true (non-0) if the given object memcmp()'s as equivalent
   to CliAppSwitch_sentinel.
 */
int cliapp_switch_is_end(CliAppSwitch const *s);

/**
   vprintf()-compatible logging/printing interface for use with
   CliApp.
*/
typedef int (*CliApp_print_f)(char const *, va_list);

/**
   A callback for use with cliapp_switches_visit(). It is passed the
   switch object and an arbitrary state pointer provided by the
   caller of that function.   
*/
typedef int (*CliAppSwitch_visitor_f)(CliAppSwitch const *, void *);

/**
   Global app state. This class is intended to represent a singleton,
   the cliApp object.
*/
struct CliApp {
  /**
     Number of arguments in this->argv. It is modified as
     cliapp_process_argv() executes and only counts arguments
     up to, but not including the special-case "--" flag.
  */
  int argc;

  /**
     Arguments processed by cliapp_process_argv(). Contains
     this->argc entries. This memory is not valid until
     cliapp_process_argv() has succeeded.
  */
  CliAppArg * argv;

  /**
     Internal cursor for traversing non-flag arguments using
     cliapp_arg_nonflag(). Holds the *next* index to be used by that
     function.
  */
  int cursorNonflag;

  /**
     May be set to an error description by certain APIs and it may
     point to memory which can mutate.
  */
  char const * errMsg;

  /**
    Must be set up by the client *before* calling
    cliapp_process_argv() and its final entry MUST be an object for
    which cliapp_switch_is_end() returns true (that's how we know when
    to stop processing).
  */
  CliAppSwitch const * switches;

  /**
     If this is non-NULL, cliapp_print() and friends will use it for
     output, otherwise they will elide all output. This defaults to
     vprintf().
  */
  CliApp_print_f print;

  /**
     If set, it gets called each time cliapp_process_argv() processes
     an argument. If it returns non-0, processing fails.

     After processing successfully completes, the callback is called
     one final time with NULL arguments so that the callback can
     perform any end-of-list validation or whatnot.

     Using this callback effectively turns cliapp_process_argv() into
     a push parser, which turns out to be a pretty convenient way to
     handle CLI flags.
  */
  CliAppSwitch_callback_f argCallack;

  /**
     If cliapp_process_argv() encounters the "--" flag, and additional
     arguments follow it, this object gets filled out with information
     about them.

     Note that encountering "--" with no following arugments is not
     considered an error.
  */
  struct {
    /**
       If cliapp_process_argv() encounters "--", this value gets set
       to the number of arguments available in the original argv array
       immediately following (but not including) the "--" flag
    */
    int argc;
    /**
       If cliapp_process_argv() encounters "--", and there are
       arguments after it, this value is set to the list of arguments
       (from the original argv array) immediately following the "--"
       flag. If "--" is not encountered, or there are no arguments
       after it, this member's value is 0.
    */
    char const * const * argv;
  } doubleDash;
  
  /**
     State related to interactive line-editing/reading.
  */
  struct {
    /**
       If enabled at compile-time, this has a value of 1 (for
       linenoise) or 2 (for readline), else it has a value of 0.

       To use libreadline, compile this code's C file with
       CLIAPP_ENABLE_READLINE set to a true value. To use linenoise,
       build with CLIAPP_ENABLE_LINENOISE set to a true value.
    */
    int const enabled;
    /**
       If non-NULL, cliapp_lineedit_save(NULL) will use this name for
       saving.
    */
    char const * historyFile;
    /**
       Specifies whether or not the line editing history has been
       modified since the last save.

       This initially has a value of 0 and it gets set to non-0 if
       cliapp_lineedit_add() is called.
    */
    int needsSave;
  } lineread;
};

/**
   Behold! The One True Instance of CliApp!
*/
extern struct CliApp cliApp;

/**
   Visits all switches in cliApp.switches, calling
   visitor(theSwitch,state) for each one. If the visitor returns
   non-0, visitation halts without an error.

   It stops iterating when it encounters an entry for which
   cliapp_switch_is_end() returns true.
*/
void cliapp_switches_visit( CliAppSwitch_visitor_f visitor,
                            void * state );

/**
   Callback signature for use with cliapp_args_visit().

   It gets passed the CLI argument, the index of that argument in
   cliApp.argv, and an optional client-specified state pointer.
*/
typedef int (*CliAppArg_visitor_f)(CliAppArg const *, int ndx, void *);

/**
   Visits all args in cliApp.argv, calling visitor(theSwitch,itsIndex,state)
   for each one. If skipArgs is greater than 0, that many are skipped
   over before visiting. Behaviour is undefined if a visitor modifies
   cliApp.argv or cliApp.argc. If the visitor returns non-0,
   visitation halts without an error.

   CliAppArg entries with a NULL key are skipped over, under the assumption
   that the client app has marked them as "removed".
*/
void cliapp_args_visit( CliAppArg_visitor_f visitor, void * state,
                        unsigned short skipArgs );

/**
   Initializes the argument-processing parts of the cliApp global
   object with. It is intended to be passed the conventional argc/argv
   arguments which are passed to the application's main().

   The final parameter is reserved for future use in providing flags
   to change this function's behaviour. A value of 0 is reserved as
   meaning "the default behaviour."

   cliApp.switches must have been assigned to non-NULL before calling
   this, or behaviour is undefined. If any given switch has a callback
   assigned to it, it will be called when that switch is processed,
   and processing fails if it returns non-0. (Potential TODO: allow a
   NULL switches value to simply treat all flags a known switches.)

   If cliApp.argCallack is not-NULL, it is called for every
   argument. It will be passed the CLI argument and, if it's a flag,
   its corresponding CliAppSwitch instance (extracted from
   cliApp.switches). For non-flag arguments, a NULL CliAppSwitch is
   passed to it. If it returns non-0, processing fails. If processes
   completes successfully, the callback is called one additional time
   with NULL pointer values to indicate that the end has been
   reached. This can be used to handle post-argument cleanup, perform
   app-specific argument validation, or similar.

   If callbacks are set both on the switch and cliApp, both are called
   in that order, but only the cliApp callback is called one final
   time after processing is done.

   If this function returns 0, the client may manipulate the contents
   of cliApp.argv, within reason, but must be certain to keep
   cliApp.argc in sync with that list's entries.

   On error a non-0 code is returned, either propagated from a
   callback or (if the error originates from this function) an entry
   from the cliapp_rc enum. In the latter case, cliapp_err_get() will
   contain information about why it failed.

   Encountering an argument which is neither a non-flag nor a flag
   defined in cliApp.switches results in an error.

   Quirks:

   - Arguments after "--" are NOT processed by this
   function. Processing them would be a bug-in-waiting because those
   flags might collide with app-level flags and/or require syntaxes
   which this code treats as an error, e.g. using three dashes instead
   of 1 or 2. Instead, if "--" is encounter, cliApp.doubleDash is
   populated with information about the flags so the client may deal
   with them (which might mean passing them back into this routine!).

   - All argv-related cliApp state is reset on each call, so if this
   function is called multiple times, any client-side pointers
   referring to cliApp's state may then point to different information
   than they expect and/or may become stale pointers. (cliApp-held
   data, e.g. cliApp.argv, keeps the same pointers but re-populates
   the state, but the lifetime of external pointers,
   e.g. cliApp.doubleDash.argv, is client-dependent.)
*/
int cliapp_process_argv(int argc, char const * const * argv,
                        unsigned int reserved);

/**
   If cliApp.print is not NULL, this passes on its arguments to that
   function, else this is a no-op.
*/
void cliapp_printv(char const *fmt, va_list);

/**
   Elipses-args form of cliapp_printv().
*/
void cliapp_print(char const *fmt, ...);

/**
   Outputs a printf-formatted message to stderr.
*/
void cliapp_warn(char const *fmt, ...);

/**
   Returns the next entry in cliApp.argv which is a non-flag argument,
   skipping over argv[0]. Returns 0 when the end of the list is
   reached.
*/
CliAppArg const * cliapp_arg_nonflag();

/**
   Resets the traversal of cliapp_arg_nonflag() to start from
   the beginning.
*/
void cliapp_arg_nonflag_rewind();

/**
   If the given argument matches an app-configured flag, that flag is
   returned, else 0 is returned.

   If alsoFlag is true, the first argument and the corresponding
   switch must also have matching flag values to be considered a
   match.
*/
CliAppSwitch const * cliapp_switch_for_arg(CliAppArg const * arg,
                                           int alsoFlag);

/**
   Searches for a flag matching one of the given keys. Each entry
   in cliApp.argv is checked, in order, against both of the given
   keys, in the order they are provided.

   The conventional way to call it is to pass the short-form flag,
   then the long-form flag, but that's just a convention.

   Either of the first two arguments may be NULL but both may not be
   NULL.

   If the 3rd parameter is not NULL then:

   1) *atPos indicates an index position to start the search at. (Note
   that it should initially be 1, not 0, in order to skip over the
   app's name, stored in argv[0].)

   2) If non-NULL is returned, *atPos is set to the index at which the
   argument was found. If NULL is returned, *argPos is not modified.

   Thus atPos can be used to iterate through multiple copies of a
   flag, noting that its value points to the index at which the
   previous entry was found, so needs to be incremented by 1 before
   each subsequent iteration

   On a match, the corresponding CliAppArg is returned, else 0 is
   returned.
*/
CliAppArg const * cliapp_arg_flag(char const * key1, char const * key2,
                                  int * atPos);

/**
   Given a flag value for a CliAppArg or CliAppSwitch, this
   returns a prefix string depending on that value:

   1 = "-", 2 = "--", 3 = "+"

   Anything else = "". The returned bytes are static.
*/
char const * cliapp_flag_prefix( int flag );

/**
   Given a CliAppArg, presumably one from cliapp_arg_flag() or
   cliapp_arg_nonflag(), this searches for the next argument with the
   same key.

   If the given argument is from outside cliApp.argv's memory range,
   or is the last element in that list, 0 is returned.

   Bug? For non-flag arguments this does not update the internal
   non-flag traversal cursor.
*/
CliAppArg const * cliapp_arg_next_same(CliAppArg const * arg);

/**
   Clears any error state in the cliApp object.
*/
void cliapp_err_clear();

/**
   If cliApp has a current error message set, it is returned, else 0
   is returned. The memory is static and its contents may be modified
   by any calls into this API.
*/
char const * cliapp_err_get();


/**
   Tries to save the line-editing history to the given filename, or to
   cliApp.lineedit.historyFile if fname is NULL. If both are NULL or
   empty, or if cliApp.lineedit.needsSave is 0, this is a no-op and
   returns 0. Returns CLIAPP_RC_UNSUPPORTED if line-editing is not
   enabled.
*/
int cliapp_lineedit_save(char const * fname);

/**
   Adds the given line to the line-edit history. If this function
   returns 0, it also sets cliApp.lineedit.needsSave to a non-0 value.

   Returns 0 on success or CLIAPP_RC_UNSUPPORTED if line-editing is
   not enabled.
*/
int cliapp_lineedit_add(char const * line);

/**
   Tries to load the line-editing history from the given filename, or
   to cliApp.lineedit.historyFile if fname is NULL. If both are NULL
   or empty, this is a no-op. Returns CLIAPP_RC_UNSUPPORTED if
   line-editing is not enabled. If the underlying line-editing backend
   returns an error, CLIAPP_RC_IO is returned, under the assumption
   that there was a problem with reading the file (e.g. unreadable),
   as opposed to an allocation error or similar.
*/
int cliapp_lineedit_load(char const * fname);

/**
   If cliApp.lineedit.enabled is true, this function passes its
   argument to free(3), else it will (in debug builds) trigger an
   assert if passed non-NULL. This must be called once for each line
   fetched via cliapp_lineedit_read().
*/
void cliapp_lineedit_free(char * line);

/**
   If line-editing is enabled, this reads a single line using that
   back-end and returns the new string, which must be passed to
   cliapp_lineedit_free() after the caller is done with it.

   Returns 0 if line-editing is not enabled or if the caller taps the
   platform's EOF sequence (Ctrl-D on Unix) at the start of the
   line. Returns an empty string if the user simply taps ENTER.

   TODO: if no line-editing backend is built in, fall back to fgets()
   on stdin. It ain't pretty, but it'll do in a pinch.
*/
char * cliapp_lineedit_read(char const * prompt);

/**
   Callback type for use with cliapp_repl().
*/
typedef int (*CliApp_repl_f)(char const * line, void * state);

/**
   Enters a REPL (Read, Eval, Print Loop). Each iteration does
   the following:

   1) Fetch an input line using cliapp_lineedit_read(), passing it
   *prompt. If that returns NULL, this function returns 0.

   2) If addHistoryPolicy is <0 then the read line is added to the
   history.

   3) Calls callback(theReadLine, state).

   4) If (3) returns 0 and addHistoryPolicy is >0, the read line
   is added to the history.

   5) Passes the read line to cliapp_lineedit_free().

   6) If (3) returns non-0, this function returns that value.

   Notes:

   - The prompt is a pointer to a pointer so that the caller may
   modify it between loop iterations. This function derefences *prompt
   on each iteration.

   - An addHistoryPolicy of 0 means that this function will not
   automatically add input lines to the history. The callback is free
   to do so.

   - This function never passes a NULL line value to the callback but
   it may pass an empty line.
*/
int cliapp_repl(CliApp_repl_f callback, char const * const * prompt,
                int addHistoryPolicy, void * state);

#ifdef __cplusplus
}
#endif
#endif /* NET_WH_CLIAPP_H_INCLUDED */

Deleted bindings/s2/config.h.

1
2
3
4
5





-
-
-
-
-
#define S2_OS_UNIX 1
#define S2_HAVE_REALPATH 1
#define S2_HAVE_STAT 1
#define S2_HAVE_CHDIR 1
#define CWAL_OBASE_ISA_HASH 1

Deleted bindings/s2/f-s2sh.s2.

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
























































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/*
  Initialization script for s2sh. If it is named the same as the binary
  (minus any ".exe" extension), with a ".s2" extension (and in the same directory),
  s2sh will autoload this file at startup and fail if processing it fails.
*/

assert Fossil;
assert Fossil.Context;

assert s2 && 'function' === typename s2.loadModule;
/**
  An s2.loadModule() proxy which uses a PathFinder
  instance to search for DLLs. The name argument must be the
  base name part, optionally with a partial leading (sub-)path.

  The dest argument is passed as the last argument to s2.loadModule(),
  and is returned to the caller on success. Well-behaved modules will
  install their features in that object.

  If the S2_MODULE_PATH and/or S2_MODULE_EXTENSION environment
  variables are set, they are treated as a semicolon- or
  colon-separated list of directories resp. file extensions. If not
  specified, some default set is used.

*/
s2.loadModule2 = function(name, dest = {}){
    affirm 'string' === typename name;
    const fn = pf.search( name );
    fn || throw "Cannot find '".
        concat(name, "' in search path ", pf.prefix.toJSONString());
    //print("Importing",name, '==>', fn);
    return loadModule.call(this, realpath ? realpath(fn) : fn, dest);
}.importSymbols({
    loadModule: s2.loadModule,
    realpath: s2.io ? s2.io.realpath : undefined,
    pf: s2.PathFinder.new(
        // Directories...
        ('string' === typename (var s = s2.getenv('S2_MODULE_PATH')))
            ? s.split(s.indexOf(';') >= 0 ? ';' : ':')
        : ['.'],
        // Extensions...
        ('string' === typename (s = s2.getenv('S2_MODULE_EXTENSIONS')))
       ? s.split(s.indexOf(';') >= 0 ? ';' : ':')
        : ['.so','.dll']
    )
});


/**
   For the given container, v, this displays (via s2.io.output() a
   listing of its properties. Intended for getting an overview of an
   object's API.
*/
s2.vls = proc(v,label){
    label && print(label);
    typeinfo(iscontainer v) && this.eachProperty.call(v,eachProp);
}.importSymbols({
    eachProp:proc(k,v){
        print('\t'+typeinfo(name k), k, '=', typeinfo(name v), v);
    }
});

/**
   For the given container, v, this displays (via s2.io.output() a
   tree listing, recursively, of its properties. Intended for getting
   an overview of an object's API.

   If includeProto is truthy, v.prototype is also dumped. If it is
   less than 0, that is done recursively for all entries and their
   prototypes.
*/
s2.vtree = proc vtree(v,label,includeProto){
    typeinfo(iscontainer v) || return;
    if(!typeinfo(isstring label) && undefined===includeProto){
        includeProto= label;
        label = undefined;
    }
    const doMyProto = includeProto;
    includeProto < 0 || (includeProto=0);
    //print(__FLC,'argv =',argv);
    label ?: (label = "%1$p".applyFormat(v));
    label && out(buf.toString(),label," [type: ",typeinfo(name v),'] ==>\n');
    ++buf.level;
    buf.length(buf.level*4);
    buf.fill(' ');
    var ex = catch{
        // ^^^^ s2 bug: braces around this catch should not be necessary.
        // Symptom: if the first if() passes then the 'else' is getting seen
        // after catch resolves.
        if(typeinfo(isarray v)){
            //out(' array ',v.toJSONString(0,true/*output cycles as strings*/),'\n');
            v.eachIndex(eachIndex);
        }else{
            buf.prototype.eachProperty.call(v,eachProp);
        }
    };
    ex = catch if(!ex && v.prototype){
        doMyProto
        ? vtree(v.prototype,label?label+'.prototype':0, includeProto)
        : out(buf.toString(),label ? label+'.' : '',
              'prototype ==> [type: ',
              typeinfo(name v.prototype),']\n');
    };
    --buf.level;
    buf.length(buf.level * 4);
    ex && throw ex;
}.importSymbols({
    buf: eval {
        var b = new s2.Buffer(20);
        b.level = 0;
        b;
    },
    out: s2.io.output,
    eachProp:proc(k,v){
        if(typeinfo(isfunction v)){
            out(buf.toString(), (label ? label+'.'+k :k),'()\n');
        }
        else if(typeinfo(iscontainer v)
                && typeinfo(isfunction v.mayIterate)){
            v.mayIterate()
                ? vtree(v,(label ? label+'.'+k : k), includeProto)
                : out(buf.toString(),(label ? label+'.'+k : k),
                      " = <cyclic: %1$p>\n".applyFormat(v));
        }else{
            out(buf.toString(), (label ? label+'.'+k :k),
                ' = ', typeinfo(name v), ' ', v, '\n');
        }
    },
    eachIndex:proc(v,k){
        if(typeinfo(isfunction v)){
            out(buf.toString(), (label ? label+'['+k+']','()\n'));
        }
        else if(typeinfo(iscontainer v)
                && typeinfo(isfunction v.mayIterate)){
            v.mayIterate()
                ? vtree(v,(label ? label+'['+k+']' : k), includeProto)
                : out(buf.toString(),(label ? label+'['+k+']' : k),
                      " = <cyclic: %1$p>\n".applyFormat(v));
        }else{
            out(buf.toString(), (label ? label+'['+k+']' :k),
                ' = ', typeinfo(name v), ' ', v, '\n');
        }
    }
});

/**
   Add Fossil.require(), used in loading Fossil-aware modules.
*/
Fossil.require = import( false, 'require.d/require.s2' );

// The rest of the initialization happens via here:
Fossil.require(['nocache!fsl/extendFossil'],proc(){});

Deleted bindings/s2/fslcgi.

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































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/bin/sh
########################################################################
# Intended to be run as a CGI script. It is currently hard-coded to
# live in same dir as s2sh and the ./fslcgi.d directory and f-s2sh
# binary.
#
# A sample Apache vhost for this script looks like:
#
#<VirtualHost *:80>
#    ServerName s2.local
#    ScriptAlias /cgi-bin/ /path/to/libfossil/s2
#    DocumentRoot /path/to/libfossil/s2
#    Options +FollowSymLinks
#</VirtualHost>
#<Directory "/path/to/libfossil/s2">
#        AllowOverride All
#        Options +ExecCGI +Indexes
#        Order allow,deny
#        Allow from all
#        Require all granted
#        DirectoryIndex fslcgi
#        SetHandler cgi-script
#</Directory>
########################################################################

S2_HOME=`dirname "$0"`
# PROXY_SCRIPT is the script-side entry point for the CGI app
PROXY_SCRIPT=$0.s2
# Interpreter-level flags for s2sh:
S2_SHELL_FLAGS="-S -a -R"
S2_SHELL=$S2_HOME/f-s2sh
#S2_RC_DIR = "resource dir" for cgi bits. Passed via the --resource-dir CLI flag.
S2_RC_DIR=${S2_HOME}/fslcgi.d

S2_MODULE_PATH=".:$S2_HOME:${S2_RC_DIR}/lib"
S2_INCLUDES_PATH=".:$S2_HOME:${S2_RC_DIR}/include"
#NYI: S2_AUTOLOAD_MODULES="module1,module2,module3"

export S2_MODULE_PATH S2_INCLUDES_PATH S2_AUTOLOAD_MODULES

#export S2_HOME

# For libfossil.so:
LD_LIBRARY_PATH="${S2_HOME}/..:${LD_LIBRARY_PATH}"
export LD_LIBRARY_PATH

# Tell the script world which repository file to use...
if [ -d ~/fossil ]; then
    S2_CGI_REPO="~/fossil/libfossil.fsl"
    true
else
    S2_CGI_REPO="$S2_HOME/../../libfossil.fsl"
    #S2_CGI_REPO="-R=$S2_HOME/../../fossil.fsl"
fi

#S2_CGI_REP0=''
# Misc flags intended for consumption by scripts...
S2_REPO_URL=http://fossil.wanderinghorse.net/repos/libfossil/
#S2_CGI_REPO=-C
S2_SCRIPT_FLAGS="--repo-db=${S2_CGI_REPO} --repo-url=${S2_REPO_URL} --resource-dir=${S2_RC_DIR}"
#S2_SCRIPT_FLAGS="${S2_SCRIPT_FLAGS} --repo-url=http://url-to-your/repo[.cgi]"

exec $S2_SHELL ${S2_SHELL_FLAGS} -f $PROXY_SCRIPT -- $S2_SCRIPT_FLAGS

Deleted bindings/s2/fslcgi.d/init.s2.

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





































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   Post-bootstrap initialization code for s2's fslcgi. Must live in the
   root of "resource directory", but that (with sufficient hacking) may
   live outside of the web-root.
*/
affirm 'undefined' !== typename $CGI /* expecting CGI module to be loaded under this name */;
affirm Fossil.require;


$CGI.config = {
    resourceDir: Fossil.file.canonicalName(__FILEDIR,true),
    cgiRoot: '<TODO:cgiRoot>'
};
scope {
    const C = $CGI;
    var uri = s2.getenv('SCRIPT_NAME');
    // Set up cgiRoot (the "standard" way of getting the root dir
    // for link-building purposes). This kludgery is not generic...
    if(uri) { C.config.cgiRoot = uri + '/' }
    else{
        uri = s2.getenv('REQUEST_URI');
        C.request.ENV || (C.request.REQUEST_URI = uri);
        if(uri){
            var head = uri.split('fslcgi',2).0;//, head = adj.0;
            C.config.cgiRoot = head ||| '/empty';
            //('/' === head) ? head : head + '/'; //.concat('fslcgi', '/');
        }
    }
    
    C.config.localServerMode =
        (var serverName = s2.getenv('SERVER_NAME'))
        && (0<serverName.indexOf(".local"));
    ;;
}

/**
   A convenience alias for s2.io.output (or functionally
   equivalent).
*/
$CGI.out = s2.io.output;

/**
   Sends its RHS argument output to the (buffered) response body.
   Returns this object (so it can be chained).
*/
$CGI.'operator<<' = proc(self,arg){
    return s2.io.output(arg), this;
};

/**
    Removes "potentially security-relevant" properties from the
    exception ex. Returns ex.
*/
$CGI.scrubException = proc(ex){
    if(this.config.scrubExceptions){
        unset ex.script, ex.stackTrace, ex.line, ex.column // security-relevant
    }
    return ex;
};

$CGI.util = {
    /**
       Converts all key/value pairs from obj to a string of urlencoded
       key value pairs, separated by '&'. If addQMark is true then the
       result is prefixed by a '?', otherwise it is not.

       If obj has no properties then an empty string is returned,
       regardless of addQMark.

       Property key order in the result string is unspecified.
    */
    objToUrlOpt: proc(obj, addQMark = false){
        affirm obj && obj.eachProperty /* expecting an Object */;
        buf.reset();
        var counter = 0;
        obj.eachProperty(each);
        return buf.isEmpty()
            ? ''
            : (addQMark ? '?' : '')+buf.toString();
    }.importSymbols({
        buf: s2.Buffer.new(),
        each: proc(k,v){
            counter++ && buf.append('&');
            buf.append($CGI.urlencode(''+k),'=',$CGI.urlencode(''+v));
            /* Reminder to self: $CGI.urlencode() and friends require
               that $CGI be their 'this' because they reuse an internal
               buffer on each call to save on allocations.
            */
        }
    }),

    absoluteLink: proc(path,label=path, urlOpt){
        affirm 'string' === typename path;
        const hasQ = path.indexOf('?')>=0,
          optStr = this.objToUrlOpt(urlOpt, !hasQ );
        return fmt.applyFormat(
            $CGI.config.cgiRoot,
            $CGI.urlencode(path)+(optStr ? hasQ ? '&' + optStr : optStr : ''),
            label);
    }.importSymbols({
        fmt: "<a href='%1$s%2$s'>%3$s</a>"
    });
};


$CGI.resourcePath = proc(name){
    return this.config.resourceDir + name;
};

$CGI.resolveResource = proc(name){
    const path = this.config.resourceDir + name;
    return Fossil.file.isFile(path) ? path : undefined;
};

// incomplete... not yet sure what i want.
$CGI.request.pathInfoList = scope {
    var ps, p = s2.getenv('PATH_INFO');
    if(p && '/'!==p){
        ps = p.split('/');
        // Trim leading/trailing null entries caused
        // by leading/trailing slashes:
        ps.0 || ps.shift();
        ps[ps.length()-1] || ps.pop();
    }
    ps;
};

if(0){ // only for testing
    $CGI.setContentType('application/json');
    print({request: $CGI.request,
          config: $CGI.config}.toJSONString(2));
}else if(1){
    const c = $CGI;
    c.setContentType('text/plain');
    print("$CGI sanity checks...");
    c << 'Fossil.time.cpuTime() says: '
        << Fossil.time.cpuTime()
        << '\n'
    ;

    const f = c.fossilInstance;
    f.db || f.openCheckout();
    c << 'Fossil context: ' << f << ' repo db: ' << f.db.repo.filename << '\n';
    c << "pathInfoList = "<<c.request.pathInfoList<<'\n';
    c << "resourcePath(foo/bar.baz)==>"
        << c.resourcePath('foo/bar.baz')
        << '\n';
    c << "resolveResource('init.s2')==>"
        << c.resolveResource('init.s2')
        << '\n';
    c << "resolveResource('x')==>"
        << c.resolveResource('x')
        << '\n';

    if(1) {
        c << '$CGI.config = ' << $CGI.config.toJSONString(2) << '\n';
    }

    c << "Most recent timeline event: "
        << f.db.selectRow('SELECT * FROM event ORDER BY mtime DESC')
        .toJSONString(2)
        << '\n';

    if(0){
        // Workaround: c is a Native, and those are rejected
        // by the C-level toJSON bits:
        var obj = {};
        c.eachProperty(proc(k,v){obj[k]=v});
        c << '$CGI properties:\n' << obj.toJSONString(4) << '\n';
    }
    if(1){
        c << 'objToUrlOpt: '
            << c.util.objToUrlOpt({a:3,c:'a b'})
            << '\n';
        c << 'objToUrlOpt again (should be empty): '
            << c.util.objToUrlOpt({},true)
            << '\n';
        c << 'absoluteLink: '
            << c.util.absoluteLink('foo/bar/baz?x=y', 'link', {a:1,b:'hi !'})
            << '\n';
        c << 'absoluteLink again: '
            << c.util.absoluteLink('foo/bar/', 'link', {a:1,b:'hi !'})
            << '\n';
        c << 'Fossil.artifactTypes = '<<Fossil.artifactTypes<<'\n';
        
    }

    0 && Fossil.require(['tmpl-compiled!layout/default.tmpl.s2'],proc(t){
        t.evalContents('default layout');
    });

    c << '\nThe end. Fossil.time.cpuTime() says: '
        << Fossil.time.cpuTime()
        << '\n';

    //throw exception(-666,"testing discarding of buffered output");
}

Deleted bindings/s2/fslcgi.s2.

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































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   Bootstrap script for the s2/libfossil CGI bits.
*/
const $CGI = scope {
    /* Load the CGI module and bail out with an HTTP-correct response if it fails. */
    var c;
    var err = catch{
        c = s2.loadModule2('cgimod').cgi;
        affirm c;
        affirm 'function' === typename c.send;
    };
    if(err){
        const out = s2.io.output;
        out("Status: 500 Could not load CGI module.\r\n");
        out("Content-type: text/plain\r\n");
        out("\r\n");
        if(1){
            // potentially security-relevant (absolute paths, etc)
            out(err.toJSONString(2));
        }else{
            out("Could not load CGI module.");
        }
        exit;
    }
    c;
};
//print('cgi =',typename $CGI);
/* Start output buffering before the page gets a chance to output anything. */
const $ob = s2.ob;
$ob.push();
var err = catch Fossil.require(
['cliargs','fsl/context', 'fsl/db/repoOrCheckout'/*to get it opened*/],
proc(cliargs, F){
    // TODO: get our routing/dispatching in place, and import/call it here...
    const resDir = cliargs.takeFlag('resource-dir');
    typeinfo(isstring resDir) || throw "Set the --resource-dir=/path SCRIPT flag (after --) in the main CGI script.";
    $CGI.fossilInstance = F;
    requireS2.getPlugin('default').prefix[] = resDir;
    requireS2.getPlugin('tmpl').suffix[] = '.tmpl.s2';
    requireS2.getPlugin('tmpl-compiled').suffix[] = '.tmpl.s2';
    s2.import(Fossil.file.canonicalName(resDir,false)+'/init.s2');
    //$CGI.dispatchRoute();
    //$CGI.setContentType('text/plain');
    //$CGI.request.PATH_INFO = s2.getenv('PATH_INFO') ||| 'try adding a path';
    //print('(ammended) $CGI.request:');
    //print($CGI.request.toJSONString(2));
    //throw "This causes generated (buffered) output to be discarded.";
});

if(err){ // transform exception to a JSON object.
    // discard accumulated output, but keep one buffering level for our own use...
    var obLevel = $ob.level();
    for( ; obLevel > 1; $ob.pop(), --obLevel) {}
    if(obLevel){ $ob.clear() }
    else { $ob.push() /* so our headers get sent properly */ }
    if($CGI.httpStatus() < 500){
        $CGI.httpStatus(500, "Caught Exception");
    }
    $CGI.scrubException && (err = $CGI.scrubException(err));
    $CGI.setContentType('application/json');
    print({exception:err}.toJSONString(-1));
}
$CGI.send() /* will flush all $ob buffers after emiting headers */;

Deleted bindings/s2/linenoise/linenoise.c.

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

























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* linenoise.c -- guerrilla line editing library against the idea that a
 * line editing lib needs to be 20,000 lines of C code.
 *
 * You can find the latest source code at:
 *
 *   http://github.com/msteveb/linenoise
 *   (forked from http://github.com/antirez/linenoise)
 *
 * Does a number of crazy assumptions that happen to be true in 99.9999% of
 * the 2010 UNIX computers around.
 *
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
 * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 * Copyright (c) 2011, Steve Bennett <steveb at workware dot net dot au>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  *  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  *  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * ------------------------------------------------------------------------
 *
 * References:
 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
 *
 * Bloat:
 * - Completion?
 *
 * Unix/termios
 * ------------
 * List of escape sequences used by this program, we do everything just
 * a few sequences. In order to be so cheap we may have some
 * flickering effect with some slow terminal, but the lesser sequences
 * the more compatible.
 *
 * EL (Erase Line)
 *    Sequence: ESC [ n K
 *    Effect: if n is 0 or missing, clear from cursor to end of line
 *    Effect: if n is 1, clear from beginning of line to cursor
 *    Effect: if n is 2, clear entire line
 *
 * CUF (CUrsor Forward)
 *    Sequence: ESC [ n C
 *    Effect: moves cursor forward of n chars
 *
 * CR (Carriage Return)
 *    Sequence: \r
 *    Effect: moves cursor to column 1
 *
 * The following are used to clear the screen: ESC [ H ESC [ 2 J
 * This is actually composed of two sequences:
 *
 * cursorhome
 *    Sequence: ESC [ H
 *    Effect: moves the cursor to upper left corner
 *
 * ED2 (Clear entire screen)
 *    Sequence: ESC [ 2 J
 *    Effect: clear the whole screen
 *
 * == For highlighting control characters, we also use the following two ==
 * SO (enter StandOut)
 *    Sequence: ESC [ 7 m
 *    Effect: Uses some standout mode such as reverse video
 *
 * SE (Standout End)
 *    Sequence: ESC [ 0 m
 *    Effect: Exit standout mode
 *
 * == Only used if TIOCGWINSZ fails ==
 * DSR/CPR (Report cursor position)
 *    Sequence: ESC [ 6 n
 *    Effect: reports current cursor position as ESC [ NNN ; MMM R
 *
 * win32/console
 * -------------
 * If __MINGW32__ is defined, the win32 console API is used.
 * This could probably be made to work for the msvc compiler too.
 * This support based in part on work by Jon Griffiths.
 */

#ifdef _WIN32 /* Windows platform, either MinGW or Visual Studio (MSVC) */
#include <windows.h>
#include <fcntl.h>
#define USE_WINCONSOLE
#ifdef __MINGW32__
#define HAVE_UNISTD_H
#else
/* Microsoft headers don't like old POSIX names */
#define strdup _strdup
#define snprintf _snprintf
#endif
#else
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#define USE_TERMIOS
#define HAVE_UNISTD_H
#endif

#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>

#include "linenoise.h"
#include "utf8.h"

#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
#define LINENOISE_MAX_LINE 4096

#define ctrl(C) ((C) - '@')

/* Use -ve numbers here to co-exist with normal unicode chars */
enum {
    SPECIAL_NONE,
    SPECIAL_UP = -20,
    SPECIAL_DOWN = -21,
    SPECIAL_LEFT = -22,
    SPECIAL_RIGHT = -23,
    SPECIAL_DELETE = -24,
    SPECIAL_HOME = -25,
    SPECIAL_END = -26,
    SPECIAL_INSERT = -27,
    SPECIAL_PAGE_UP = -28,
    SPECIAL_PAGE_DOWN = -29
};

static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
static int history_len = 0;
static char **history = NULL;

/* Structure to contain the status of the current (being edited) line */
struct current {
    char *buf;  /* Current buffer. Always null terminated */
    int bufmax; /* Size of the buffer, including space for the null termination */
    int len;    /* Number of bytes in 'buf' */
    int chars;  /* Number of chars in 'buf' (utf-8 chars) */
    int pos;    /* Cursor position, measured in chars */
    int cols;   /* Size of the window, in chars */
    const char *prompt;
    char *capture; /* Allocated capture buffer, or NULL for none. Always null terminated */
#if defined(USE_TERMIOS)
    int fd;     /* Terminal fd */
#elif defined(USE_WINCONSOLE)
    HANDLE outh; /* Console output handle */
    HANDLE inh; /* Console input handle */
    int rows;   /* Screen rows */
    int x;      /* Current column during output */
    int y;      /* Current row */
#endif
};

static int fd_read(struct current *current);
static int getWindowSize(struct current *current);

void linenoiseHistoryFree(void) {
    if (history) {
        int j;

        for (j = 0; j < history_len; j++)
            free(history[j]);
        free(history);
        history = NULL;
        history_len = 0;
    }
}

#if defined(USE_TERMIOS)
static void linenoiseAtExit(void);
static struct termios orig_termios; /* in order to restore at exit */
static int rawmode = 0; /* for atexit() function to check if restore is needed*/
static int atexit_registered = 0; /* register atexit just 1 time */

static const char *unsupported_term[] = {"dumb","cons25",NULL};

static int isUnsupportedTerm(void) {
    char *term = getenv("TERM");

    if (term) {
        int j;
        for (j = 0; unsupported_term[j]; j++) {
            if (strcmp(term, unsupported_term[j]) == 0) {
                return 1;
            }
        }
    }
    return 0;
}

static int enableRawMode(struct current *current) {
    struct termios raw;

    current->fd = STDIN_FILENO;
    current->cols = 0;

    if (!isatty(current->fd) || isUnsupportedTerm() ||
        tcgetattr(current->fd, &orig_termios) == -1) {
fatal:
        errno = ENOTTY;
        return -1;
    }

    if (!atexit_registered) {
        atexit(linenoiseAtExit);
        atexit_registered = 1;
    }

    raw = orig_termios;  /* modify the original mode */
    /* input modes: no break, no CR to NL, no parity check, no strip char,
     * no start/stop output control. */
    raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /* output modes - disable post processing */
    raw.c_oflag &= ~(OPOST);
    /* control modes - set 8 bit chars */
    raw.c_cflag |= (CS8);
    /* local modes - choing off, canonical off, no extended functions,
     * no signal chars (^Z,^C) */
    raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
    /* control chars - set return condition: min number of bytes and timer.
     * We want read to return every single byte, without timeout. */
    raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */

    /* put terminal in raw mode after flushing */
    if (tcsetattr(current->fd,TCSADRAIN,&raw) < 0) {
        goto fatal;
    }
    rawmode = 1;
    return 0;
}

static void disableRawMode(struct current *current) {
    /* Don't even check the return value as it's too late. */
    if (rawmode && tcsetattr(current->fd,TCSADRAIN,&orig_termios) != -1)
        rawmode = 0;
}

/* At exit we'll try to fix the terminal to the initial conditions. */
static void linenoiseAtExit(void) {
    if (rawmode) {
        tcsetattr(STDIN_FILENO, TCSADRAIN, &orig_termios);
    }
    linenoiseHistoryFree();
}

/* gcc/glibc insists that we care about the return code of write!
 * Clarification: This means that a void-cast like "(void) (EXPR)"
 * does not work.
 */
#define IGNORE_RC(EXPR) if (EXPR) {}

/* This is fdprintf() on some systems, but use a different
 * name to avoid conflicts
 */
static void fd_printf(int fd, const char *format, ...)
{
    va_list args;
    char buf[64];
    int n;

    va_start(args, format);
    n = vsnprintf(buf, sizeof(buf), format, args);
    va_end(args);
    IGNORE_RC(write(fd, buf, n));
}

static void clearScreen(struct current *current)
{
    fd_printf(current->fd, "\x1b[H\x1b[2J");
}

static void cursorToLeft(struct current *current)
{
    fd_printf(current->fd, "\r");
}

static int outputChars(struct current *current, const char *buf, int len)
{
    return write(current->fd, buf, len);
}

static void outputControlChar(struct current *current, char ch)
{
    fd_printf(current->fd, "\x1b[7m^%c\x1b[0m", ch);
}

static void eraseEol(struct current *current)
{
    fd_printf(current->fd, "\x1b[0K");
}

static void setCursorPos(struct current *current, int x)
{
    fd_printf(current->fd, "\r\x1b[%dC", x);
}

/**
 * Reads a char from 'fd', waiting at most 'timeout' milliseconds.
 *
 * A timeout of -1 means to wait forever.
 *
 * Returns -1 if no char is received within the time or an error occurs.
 */
static int fd_read_char(int fd, int timeout)
{
    struct pollfd p;
    unsigned char c;

    p.fd = fd;
    p.events = POLLIN;

    if (poll(&p, 1, timeout) == 0) {
        /* timeout */
        return -1;
    }
    if (read(fd, &c, 1) != 1) {
        return -1;
    }
    return c;
}

/**
 * Reads a complete utf-8 character
 * and returns the unicode value, or -1 on error.
 */
static int fd_read(struct current *current)
{
#ifdef USE_UTF8
    char buf[4];
    int n;
    int i;
    int c;

    if (read(current->fd, &buf[0], 1) != 1) {
        return -1;
    }
    n = utf8_charlen(buf[0]);
    if (n < 1 || n > 3) {
        return -1;
    }
    for (i = 1; i < n; i++) {
        if (read(current->fd, &buf[i], 1) != 1) {
            return -1;
        }
    }
    buf[n] = 0;
    /* decode and return the character */
    utf8_tounicode(buf, &c);
    return c;
#else
    return fd_read_char(current->fd, -1);
#endif
}

static int countColorControlChars(const char* prompt)
{
    /* ANSI color control sequences have the form:
     * "\x1b" "[" [0-9;]* "m"
     * We parse them with a simple state machine.
     */

    enum {
        search_esc,
        expect_bracket,
        expect_trail
    } state = search_esc;
    int len = 0, found = 0;
    char ch;

    /* XXX: Strictly we should be checking utf8 chars rather than
     *      bytes in case of the extremely unlikely scenario where
     *      an ANSI sequence is part of a utf8 sequence.
     */
    while ((ch = *prompt++) != 0) {
        switch (state) {
        case search_esc:
            if (ch == '\x1b') {
                state = expect_bracket;
            }
            break;
        case expect_bracket:
            if (ch == '[') {
                state = expect_trail;
                /* 3 for "\e[ ... m" */
                len = 3;
                break;
            }
            state = search_esc;
            break;
        case expect_trail:
            if ((ch == ';') || ((ch >= '0' && ch <= '9'))) {
                /* 0-9, or semicolon */
                len++;
                break;
            }
            if (ch == 'm') {
                found += len;
            }
            state = search_esc;
            break;
        }
    }

    return found;
}

/**
 * Stores the current cursor column in '*cols'.
 * Returns 1 if OK, or 0 if failed to determine cursor pos.
 */
static int queryCursor(int fd, int* cols)
{
    /* control sequence - report cursor location */
    fd_printf(fd, "\x1b[6n");

    /* Parse the response: ESC [ rows ; cols R */
    if (fd_read_char(fd, 100) == 0x1b &&
        fd_read_char(fd, 100) == '[') {

        int n = 0;
        while (1) {
            int ch = fd_read_char(fd, 100);
            if (ch == ';') {
                /* Ignore rows */
                n = 0;
            }
            else if (ch == 'R') {
                /* Got cols */
                if (n != 0 && n < 1000) {
                    *cols = n;
                }
                break;
            }
            else if (ch >= 0 && ch <= '9') {
                n = n * 10 + ch - '0';
            }
            else {
                break;
            }
        }
        return 1;
    }

    return 0;
}

/**
 * Updates current->cols with the current window size (width)
 */
static int getWindowSize(struct current *current)
{
    struct winsize ws;

    if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col != 0) {
        current->cols = ws.ws_col;
        return 0;
    }

    /* Failed to query the window size. Perhaps we are on a serial terminal.
     * Try to query the width by sending the cursor as far to the right
     * and reading back the cursor position.
     * Note that this is only done once per call to linenoise rather than
     * every time the line is refreshed for efficiency reasons.
     *
     * In more detail, we:
     * (a) request current cursor position,
     * (b) move cursor far right,
     * (c) request cursor position again,
     * (d) at last move back to the old position.
     * This gives us the width without messing with the externally
     * visible cursor position.
     */

    if (current->cols == 0) {
        int here;

        current->cols = 80;

        /* (a) */
        if (queryCursor (current->fd, &here)) {
            /* (b) */
            fd_printf(current->fd, "\x1b[999C");

            /* (c). Note: If (a) succeeded, then (c) should as well.
             * For paranoia we still check and have a fallback action
             * for (d) in case of failure..
             */
            if (!queryCursor (current->fd, &current->cols)) {
                /* (d') Unable to get accurate position data, reset
                 * the cursor to the far left. While this may not
                 * restore the exact original position it should not
                 * be too bad.
                 */
                fd_printf(current->fd, "\r");
            } else {
                /* (d) Reset the cursor back to the original location. */
                if (current->cols > here) {
                    fd_printf(current->fd, "\x1b[%dD", current->cols - here);
                }
            }
        } /* 1st query failed, doing nothing => default 80 */
    }

    return 0;
}

/**
 * If escape (27) was received, reads subsequent
 * chars to determine if this is a known special key.
 *
 * Returns SPECIAL_NONE if unrecognised, or -1 if EOF.
 *
 * If no additional char is received within a short time,
 * 27 is returned.
 */
static int check_special(int fd)
{
    int c = fd_read_char(fd, 50);
    int c2;

    if (c < 0) {
        return 27;
    }

    c2 = fd_read_char(fd, 50);
    if (c2 < 0) {
        return c2;
    }
    if (c == '[' || c == 'O') {
        /* Potential arrow key */
        switch (c2) {
            case 'A':
                return SPECIAL_UP;
            case 'B':
                return SPECIAL_DOWN;
            case 'C':
                return SPECIAL_RIGHT;
            case 'D':
                return SPECIAL_LEFT;
            case 'F':
                return SPECIAL_END;
            case 'H':
                return SPECIAL_HOME;
        }
    }
    if (c == '[' && c2 >= '1' && c2 <= '8') {
        /* extended escape */
        c = fd_read_char(fd, 50);
        if (c == '~') {
            switch (c2) {
                case '2':
                    return SPECIAL_INSERT;
                case '3':
                    return SPECIAL_DELETE;
                case '5':
                    return SPECIAL_PAGE_UP;
                case '6':
                    return SPECIAL_PAGE_DOWN;
                case '7':
                    return SPECIAL_HOME;
                case '8':
                    return SPECIAL_END;
            }
        }
        while (c != -1 && c != '~') {
            /* .e.g \e[12~ or '\e[11;2~   discard the complete sequence */
            c = fd_read_char(fd, 50);
        }
    }

    return SPECIAL_NONE;
}
#elif defined(USE_WINCONSOLE)

static DWORD orig_consolemode = 0;

static int enableRawMode(struct current *current) {
    DWORD n;
    INPUT_RECORD irec;

    current->outh = GetStdHandle(STD_OUTPUT_HANDLE);
    current->inh = GetStdHandle(STD_INPUT_HANDLE);

    if (!PeekConsoleInput(current->inh, &irec, 1, &n)) {
        return -1;
    }
    if (getWindowSize(current) != 0) {
        return -1;
    }
    if (GetConsoleMode(current->inh, &orig_consolemode)) {
        SetConsoleMode(current->inh, ENABLE_PROCESSED_INPUT);
    }
    return 0;
}

static void disableRawMode(struct current *current)
{
    SetConsoleMode(current->inh, orig_consolemode);
}

static void clearScreen(struct current *current)
{
    COORD topleft = { 0, 0 };
    DWORD n;

    FillConsoleOutputCharacter(current->outh, ' ',
        current->cols * current->rows, topleft, &n);
    FillConsoleOutputAttribute(current->outh,
        FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN,
        current->cols * current->rows, topleft, &n);
    SetConsoleCursorPosition(current->outh, topleft);
}

static void cursorToLeft(struct current *current)
{
    COORD pos = { 0, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputAttribute(current->outh,
        FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN, current->cols, pos, &n);
    current->x = 0;
}

static int outputChars(struct current *current, const char *buf, int len)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    WriteConsoleOutputCharacter(current->outh, buf, len, pos, &n);
    current->x += len;
    return 0;
}

static void outputControlChar(struct current *current, char ch)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputAttribute(current->outh, BACKGROUND_INTENSITY, 2, pos, &n);
    outputChars(current, "^", 1);
    outputChars(current, &ch, 1);
}

static void eraseEol(struct current *current)
{
    COORD pos = { (SHORT)current->x, (SHORT)current->y };
    DWORD n;

    FillConsoleOutputCharacter(current->outh, ' ', current->cols - current->x, pos, &n);
}

static void setCursorPos(struct current *current, int x)
{
    COORD pos = { (SHORT)x, (SHORT)current->y };

    SetConsoleCursorPosition(current->outh, pos);
    current->x = x;
}

static int fd_read(struct current *current)
{
    while (1) {
        INPUT_RECORD irec;
        DWORD n;
        if (WaitForSingleObject(current->inh, INFINITE) != WAIT_OBJECT_0) {
            break;
        }
        if (!ReadConsoleInput (current->inh, &irec, 1, &n)) {
            break;
        }
        if (irec.EventType == KEY_EVENT && irec.Event.KeyEvent.bKeyDown) {
            KEY_EVENT_RECORD *k = &irec.Event.KeyEvent;
            if (k->dwControlKeyState & ENHANCED_KEY) {
                switch (k->wVirtualKeyCode) {
                 case VK_LEFT:
                    return SPECIAL_LEFT;
                 case VK_RIGHT:
                    return SPECIAL_RIGHT;
                 case VK_UP:
                    return SPECIAL_UP;
                 case VK_DOWN:
                    return SPECIAL_DOWN;
                 case VK_INSERT:
                    return SPECIAL_INSERT;
                 case VK_DELETE:
                    return SPECIAL_DELETE;
                 case VK_HOME:
                    return SPECIAL_HOME;
                 case VK_END:
                    return SPECIAL_END;
                 case VK_PRIOR:
                    return SPECIAL_PAGE_UP;
                 case VK_NEXT:
                    return SPECIAL_PAGE_DOWN;
                }
            }
            /* Note that control characters are already translated in AsciiChar */
            else if (k->wVirtualKeyCode == VK_CONTROL)
	        continue;
            else {
#ifdef USE_UTF8
                return k->uChar.UnicodeChar;
#else
                return k->uChar.AsciiChar;
#endif
            }
        }
    }
    return -1;
}

static int countColorControlChars(const char* prompt)
{
    /* For windows we assume that there are no embedded ansi color
     * control sequences.
     */
    return 0;
}

static int getWindowSize(struct current *current)
{
    CONSOLE_SCREEN_BUFFER_INFO info;
    if (!GetConsoleScreenBufferInfo(current->outh, &info)) {
        return -1;
    }
    current->cols = info.dwSize.X;
    current->rows = info.dwSize.Y;
    if (current->cols <= 0 || current->rows <= 0) {
        current->cols = 80;
        return -1;
    }
    current->y = info.dwCursorPosition.Y;
    current->x = info.dwCursorPosition.X;
    return 0;
}
#endif

static int utf8_getchars(char *buf, int c)
{
#ifdef USE_UTF8
    return utf8_fromunicode(buf, c);
#else
    *buf = c;
    return 1;
#endif
}

/**
 * Returns the unicode character at the given offset,
 * or -1 if none.
 */
static int get_char(struct current *current, int pos)
{
    if (pos >= 0 && pos < current->chars) {
        int c;
        int i = utf8_index(current->buf, pos);
        (void)utf8_tounicode(current->buf + i, &c);
        return c;
    }
    return -1;
}

static void refreshLine(const char *prompt, struct current *current)
{
    int plen;
    int pchars;
    int backup = 0;
    int i;
    const char *buf = current->buf;
    int chars = current->chars;
    int pos = current->pos;
    int b;
    int ch;
    int n;

    /* Should intercept SIGWINCH. For now, just get the size every time */
    getWindowSize(current);

    plen = strlen(prompt);
    pchars = utf8_strlen(prompt, plen);

    /* Scan the prompt for embedded ansi color control sequences and
     * discount them as characters/columns.
     */
    pchars -= countColorControlChars(prompt);

    /* Account for a line which is too long to fit in the window.
     * Note that control chars require an extra column
     */

    /* How many cols are required to the left of 'pos'?
     * The prompt, plus one extra for each control char
     */
    n = pchars + utf8_strlen(buf, current->len);
    b = 0;
    for (i = 0; i < pos; i++) {
        b += utf8_tounicode(buf + b, &ch);
        if (ch < ' ') {
            n++;
        }
    }

    /* If too many are needed, strip chars off the front of 'buf'
     * until it fits. Note that if the current char is a control character,
     * we need one extra col.
     */
    if (current->pos < current->chars && get_char(current, current->pos) < ' ') {
        n++;
    }

    while (n >= current->cols && pos > 0) {
        b = utf8_tounicode(buf, &ch);
        if (ch < ' ') {
            n--;
        }
        n--;
        buf += b;
        pos--;
        chars--;
    }

    /* Cursor to left edge, then the prompt */
    cursorToLeft(current);
    outputChars(current, prompt, plen);

    /* Now the current buffer content */

    /* Need special handling for control characters.
     * If we hit 'cols', stop.
     */
    b = 0; /* unwritted bytes */
    n = 0; /* How many control chars were written */
    for (i = 0; i < chars; i++) {
        int ch;
        int w = utf8_tounicode(buf + b, &ch);
        if (ch < ' ') {
            n++;
        }
        if (pchars + i + n >= current->cols) {
            break;
        }
        if (ch < ' ') {
            /* A control character, so write the buffer so far */
            outputChars(current, buf, b);
            buf += b + w;
            b = 0;
            outputControlChar(current, ch + '@');
            if (i < pos) {
                backup++;
            }
        }
        else {
            b += w;
        }
    }
    outputChars(current, buf, b);

    /* Erase to right, move cursor to original position */
    eraseEol(current);
    setCursorPos(current, pos + pchars + backup);
}

static void set_current(struct current *current, const char *str)
{
    strncpy(current->buf, str, current->bufmax);
    current->buf[current->bufmax - 1] = 0;
    current->len = strlen(current->buf);
    current->pos = current->chars = utf8_strlen(current->buf, current->len);
}

static int has_room(struct current *current, int bytes)
{
    return current->len + bytes < current->bufmax - 1;
}

/**
 * Removes the char at 'pos'.
 *
 * Returns 1 if the line needs to be refreshed, 2 if not
 * and 0 if nothing was removed
 */
static int remove_char(struct current *current, int pos)
{
    if (pos >= 0 && pos < current->chars) {
        int p1, p2;
        int ret = 1;
        p1 = utf8_index(current->buf, pos);
        p2 = p1 + utf8_index(current->buf + p1, 1);

#ifdef USE_TERMIOS
        /* optimise remove char in the case of removing the last char */
        if (current->pos == pos + 1 && current->pos == current->chars) {
            if (current->buf[pos] >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) {
                ret = 2;
                fd_printf(current->fd, "\b \b");
            }
        }
#endif

        /* Move the null char too */
        memmove(current->buf + p1, current->buf + p2, current->len - p2 + 1);
        current->len -= (p2 - p1);
        current->chars--;

        if (current->pos > pos) {
            current->pos--;
        }
        return ret;
    }
    return 0;
}

/**
 * Insert 'ch' at position 'pos'
 *
 * Returns 1 if the line needs to be refreshed, 2 if not
 * and 0 if nothing was inserted (no room)
 */
static int insert_char(struct current *current, int pos, int ch)
{
    char buf[3];
    int n = utf8_getchars(buf, ch);

    if (has_room(current, n) && pos >= 0 && pos <= current->chars) {
        int p1, p2;
        int ret = 1;
        p1 = utf8_index(current->buf, pos);
        p2 = p1 + n;

#ifdef USE_TERMIOS
        /* optimise the case where adding a single char to the end and no scrolling is needed */
        if (current->pos == pos && current->chars == pos) {
            if (ch >= ' ' && utf8_strlen(current->prompt, -1) + utf8_strlen(current->buf, current->len) < current->cols - 1) {
                IGNORE_RC(write(current->fd, buf, n));
                ret = 2;
            }
        }
#endif

        memmove(current->buf + p2, current->buf + p1, current->len - p1);
        memcpy(current->buf + p1, buf, n);
        current->len += n;

        current->chars++;
        if (current->pos >= pos) {
            current->pos++;
        }
        return ret;
    }
    return 0;
}

/**
 * Captures up to 'n' characters starting at 'pos' for the cut buffer.
 *
 * This replaces any existing characters in the cut buffer.
 */
static void capture_chars(struct current *current, int pos, int n)
{
    if (pos >= 0 && (pos + n - 1) < current->chars) {
        int p1 = utf8_index(current->buf, pos);
        int nbytes = utf8_index(current->buf + p1, n);

        if (nbytes) {
            free(current->capture);
            /* Include space for the null terminator */
            current->capture = (char *)malloc(nbytes + 1);
            memcpy(current->capture, current->buf + p1, nbytes);
            current->capture[nbytes] = '\0';
        }
    }
}

/**
 * Removes up to 'n' characters at cursor position 'pos'.
 *
 * Returns 0 if no chars were removed or non-zero otherwise.
 */
static int remove_chars(struct current *current, int pos, int n)
{
    int removed = 0;

    /* First save any chars which will be removed */
    capture_chars(current, pos, n);

    while (n-- && remove_char(current, pos)) {
        removed++;
    }
    return removed;
}
/**
 * Inserts the characters (string) 'chars' at the cursor position 'pos'.
 *
 * Returns 0 if no chars were inserted or non-zero otherwise.
 */
static int insert_chars(struct current *current, int pos, const char *chars)
{
    int inserted = 0;

    while (*chars) {
        int ch;
        int n = utf8_tounicode(chars, &ch);
        if (insert_char(current, pos, ch) == 0) {
            break;
        }
        inserted++;
        pos++;
        chars += n;
    }
    return inserted;
}

#ifndef NO_COMPLETION
static linenoiseCompletionCallback *completionCallback = NULL;

static void beep() {
#ifdef USE_TERMIOS
    fprintf(stderr, "\x7");
    fflush(stderr);
#endif
}

static void freeCompletions(linenoiseCompletions *lc) {
    size_t i;
    for (i = 0; i < lc->len; i++)
        free(lc->cvec[i]);
    free(lc->cvec);
}

static int completeLine(struct current *current) {
    linenoiseCompletions lc = { 0, NULL };
    int c = 0;

    completionCallback(current->buf,&lc);
    if (lc.len == 0) {
        beep();
    } else {
        size_t stop = 0, i = 0;

        while(!stop) {
            /* Show completion or original buffer */
            if (i < lc.len) {
                struct current tmp = *current;
                tmp.buf = lc.cvec[i];
                tmp.pos = tmp.len = strlen(tmp.buf);
                tmp.chars = utf8_strlen(tmp.buf, tmp.len);
                refreshLine(current->prompt, &tmp);
            } else {
                refreshLine(current->prompt, current);
            }

            c = fd_read(current);
            if (c == -1) {
                break;
            }

            switch(c) {
                case '\t': /* tab */
                    i = (i+1) % (lc.len+1);
                    if (i == lc.len) beep();
                    break;
                case 27: /* escape */
                    /* Re-show original buffer */
                    if (i < lc.len) {
                        refreshLine(current->prompt, current);
                    }
                    stop = 1;
                    break;
                default:
                    /* Update buffer and return */
                    if (i < lc.len) {
                        set_current(current,lc.cvec[i]);
                    }
                    stop = 1;
                    break;
            }
        }
    }

    freeCompletions(&lc);
    return c; /* Return last read character */
}

/* Register a callback function to be called for tab-completion.
   Returns the prior callback so that the caller may (if needed)
   restore it when done. */
linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
    linenoiseCompletionCallback * old = completionCallback;
    completionCallback = fn;
    return old;
}

void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
    lc->cvec = (char **)realloc(lc->cvec,sizeof(char*)*(lc->len+1));
    lc->cvec[lc->len++] = strdup(str);
}

#endif

static int linenoiseEdit(struct current *current) {
    int history_index = 0;

    /* The latest history entry is always our current buffer, that
     * initially is just an empty string. */
    linenoiseHistoryAdd("");

    set_current(current, "");
    refreshLine(current->prompt, current);

    while(1) {
        int dir = -1;
        int c = fd_read(current);

#ifndef NO_COMPLETION
        /* Only autocomplete when the callback is set. It returns < 0 when
         * there was an error reading from fd. Otherwise it will return the
         * character that should be handled next. */
        if (c == '\t' && current->pos == current->chars && completionCallback != NULL) {
            c = completeLine(current);
            /* Return on errors */
            if (c < 0) return current->len;
            /* Read next character when 0 */
            if (c == 0) continue;
        }
#endif

process_char:
        if (c == -1) return current->len;
#ifdef USE_TERMIOS
        if (c == 27) {   /* escape sequence */
            c = check_special(current->fd);
        }
#endif
        switch(c) {
        case '\r':    /* enter */
            history_len--;
            free(history[history_len]);
            return current->len;
        case ctrl('C'):     /* ctrl-c */
            errno = EAGAIN;
            return -1;
        case 127:   /* backspace */
        case ctrl('H'):
            if (remove_char(current, current->pos - 1) == 1) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('D'):     /* ctrl-d */
            if (current->len == 0) {
                /* Empty line, so EOF */
                history_len--;
                free(history[history_len]);
                return -1;
            }
            /* Otherwise fall through to delete char to right of cursor */
        case SPECIAL_DELETE:
            if (remove_char(current, current->pos) == 1) {
                refreshLine(current->prompt, current);
            }
            break;
        case SPECIAL_INSERT:
            /* Ignore. Expansion Hook.
             * Future possibility: Toggle Insert/Overwrite Modes
             */
            break;
        case ctrl('W'):    /* ctrl-w, delete word at left. save deleted chars */
            /* eat any spaces on the left */
            {
                int pos = current->pos;
                while (pos > 0 && get_char(current, pos - 1) == ' ') {
                    pos--;
                }

                /* now eat any non-spaces on the left */
                while (pos > 0 && get_char(current, pos - 1) != ' ') {
                    pos--;
                }

                if (remove_chars(current, pos, current->pos - pos)) {
                    refreshLine(current->prompt, current);
                }
            }
            break;
        case ctrl('R'):    /* ctrl-r */
            {
                /* Display the reverse-i-search prompt and process chars */
                char rbuf[50];
                char rprompt[80];
                int rchars = 0;
                int rlen = 0;
                int searchpos = history_len - 1;

                rbuf[0] = 0;
                while (1) {
                    int n = 0;
                    const char *p = NULL;
                    int skipsame = 0;
                    int searchdir = -1;

                    snprintf(rprompt, sizeof(rprompt), "(reverse-i-search)'%s': ", rbuf);
                    refreshLine(rprompt, current);
                    c = fd_read(current);
                    if (c == ctrl('H') || c == 127) {
                        if (rchars) {
                            int p = utf8_index(rbuf, --rchars);
                            rbuf[p] = 0;
                            rlen = strlen(rbuf);
                        }
                        continue;
                    }
#ifdef USE_TERMIOS
                    if (c == 27) {
                        c = check_special(current->fd);
                    }
#endif
                    if (c == ctrl('P') || c == SPECIAL_UP) {
                        /* Search for the previous (earlier) match */
                        if (searchpos > 0) {
                            searchpos--;
                        }
                        skipsame = 1;
                    }
                    else if (c == ctrl('N') || c == SPECIAL_DOWN) {
                        /* Search for the next (later) match */
                        if (searchpos < history_len) {
                            searchpos++;
                        }
                        searchdir = 1;
                        skipsame = 1;
                    }
                    else if (c >= ' ') {
                        if (rlen >= (int)sizeof(rbuf) + 3) {
                            continue;
                        }

                        n = utf8_getchars(rbuf + rlen, c);
                        rlen += n;
                        rchars++;
                        rbuf[rlen] = 0;

                        /* Adding a new char resets the search location */
                        searchpos = history_len - 1;
                    }
                    else {
                        /* Exit from incremental search mode */
                        break;
                    }

                    /* Now search through the history for a match */
                    for (; searchpos >= 0 && searchpos < history_len; searchpos += searchdir) {
                        p = strstr(history[searchpos], rbuf);
                        if (p) {
                            /* Found a match */
                            if (skipsame && strcmp(history[searchpos], current->buf) == 0) {
                                /* But it is identical, so skip it */
                                continue;
                            }
                            /* Copy the matching line and set the cursor position */
                            set_current(current,history[searchpos]);
                            current->pos = utf8_strlen(history[searchpos], p - history[searchpos]);
                            break;
                        }
                    }
                    if (!p && n) {
                        /* No match, so don't add it */
                        rchars--;
                        rlen -= n;
                        rbuf[rlen] = 0;
                    }
                }
                if (c == ctrl('G') || c == ctrl('C')) {
                    /* ctrl-g terminates the search with no effect */
                    set_current(current, "");
                    c = 0;
                }
                else if (c == ctrl('J')) {
                    /* ctrl-j terminates the search leaving the buffer in place */
                    c = 0;
                }
                /* Go process the char normally */
                refreshLine(current->prompt, current);
                goto process_char;
            }
            break;
        case ctrl('T'):    /* ctrl-t */
            if (current->pos > 0 && current->pos <= current->chars) {
                /* If cursor is at end, transpose the previous two chars */
                int fixer = (current->pos == current->chars);
                c = get_char(current, current->pos - fixer);
                remove_char(current, current->pos - fixer);
                insert_char(current, current->pos - 1, c);
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('V'):    /* ctrl-v */
            if (has_room(current, 3)) {
                /* Insert the ^V first */
                if (insert_char(current, current->pos, c)) {
                    refreshLine(current->prompt, current);
                    /* Now wait for the next char. Can insert anything except \0 */
                    c = fd_read(current);

                    /* Remove the ^V first */
                    remove_char(current, current->pos - 1);
                    if (c != -1) {
                        /* Insert the actual char */
                        insert_char(current, current->pos, c);
                    }
                    refreshLine(current->prompt, current);
                }
            }
            break;
        case ctrl('B'):
        case SPECIAL_LEFT:
            if (current->pos > 0) {
                current->pos--;
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('F'):
        case SPECIAL_RIGHT:
            if (current->pos < current->chars) {
                current->pos++;
                refreshLine(current->prompt, current);
            }
            break;
        case SPECIAL_PAGE_UP:
          dir = history_len - history_index - 1; /* move to start of history */
          goto history_navigation;
        case SPECIAL_PAGE_DOWN:
          dir = -history_index; /* move to 0 == end of history, i.e. current */
          goto history_navigation;
        case ctrl('P'):
        case SPECIAL_UP:
            dir = 1;
          goto history_navigation;
        case ctrl('N'):
        case SPECIAL_DOWN:
history_navigation:
            if (history_len > 1) {
                /* Update the current history entry before to
                 * overwrite it with tne next one. */
                free(history[history_len - 1 - history_index]);
                history[history_len - 1 - history_index] = strdup(current->buf);
                /* Show the new entry */
                history_index += dir;
                if (history_index < 0) {
                    history_index = 0;
                    break;
                } else if (history_index >= history_len) {
                    history_index = history_len - 1;
                    break;
                }
                set_current(current, history[history_len - 1 - history_index]);
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('A'): /* Ctrl+a, go to the start of the line */
        case SPECIAL_HOME:
            current->pos = 0;
            refreshLine(current->prompt, current);
            break;
        case ctrl('E'): /* ctrl+e, go to the end of the line */
        case SPECIAL_END:
            current->pos = current->chars;
            refreshLine(current->prompt, current);
            break;
        case ctrl('U'): /* Ctrl+u, delete to beginning of line, save deleted chars. */
            if (remove_chars(current, 0, current->pos)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('K'): /* Ctrl+k, delete from current to end of line, save deleted chars. */
            if (remove_chars(current, current->pos, current->chars - current->pos)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('Y'): /* Ctrl+y, insert saved chars at current position */
            if (current->capture && insert_chars(current, current->pos, current->capture)) {
                refreshLine(current->prompt, current);
            }
            break;
        case ctrl('L'): /* Ctrl+L, clear screen */
            clearScreen(current);
            /* Force recalc of window size for serial terminals */
            current->cols = 0;
            refreshLine(current->prompt, current);
            break;
        default:
            /* Only tab is allowed without ^V */
            if (c == '\t' || c >= ' ') {
                if (insert_char(current, current->pos, c) == 1) {
                    refreshLine(current->prompt, current);
                }
            }
            break;
        }
    }
    return current->len;
}

int linenoiseColumns(void)
{
    struct current current;
    enableRawMode (&current);
    getWindowSize (&current);
    disableRawMode (&current);
    return current.cols;
}

char *linenoise(const char *prompt)
{
    int count;
    struct current current;
    char buf[LINENOISE_MAX_LINE];

    if (enableRawMode(&current) == -1) {
        printf("%s", prompt);
        fflush(stdout);
        if (fgets(buf, sizeof(buf), stdin) == NULL) {
            return NULL;
        }
        count = strlen(buf);
        if (count && buf[count-1] == '\n') {
            count--;
            buf[count] = '\0';
        }
    }
    else
    {
        current.buf = buf;
        current.bufmax = sizeof(buf);
        current.len = 0;
        current.chars = 0;
        current.pos = 0;
        current.prompt = prompt;
        current.capture = NULL;

        count = linenoiseEdit(&current);

        disableRawMode(&current);
        printf("\n");

        free(current.capture);
        if (count == -1) {
            return NULL;
        }
    }
    return strdup(buf);
}

/* Using a circular buffer is smarter, but a bit more complex to handle. */
int linenoiseHistoryAdd(const char *line) {
    char *linecopy;

    if (history_max_len == 0) return 0;
    if (history == NULL) {
        history = (char **)malloc(sizeof(char*)*history_max_len);
        if (history == NULL) return 0;
        memset(history,0,(sizeof(char*)*history_max_len));
    }

    /* do not insert duplicate lines into history */
    if (history_len > 0 && strcmp(line, history[history_len - 1]) == 0) {
        return 0;
    }

    linecopy = strdup(line);
    if (!linecopy) return 0;
    if (history_len == history_max_len) {
        free(history[0]);
        memmove(history,history+1,sizeof(char*)*(history_max_len-1));
        history_len--;
    }
    history[history_len] = linecopy;
    history_len++;
    return 1;
}

int linenoiseHistoryGetMaxLen(void) {
    return history_max_len;
}

int linenoiseHistorySetMaxLen(int len) {
    char **newHistory;

    if (len < 1) return 0;
    if (history) {
        int tocopy = history_len;

        newHistory = (char **)malloc(sizeof(char*)*len);
        if (newHistory == NULL) return 0;

        /* If we can't copy everything, free the elements we'll not use. */
        if (len < tocopy) {
            int j;

            for (j = 0; j < tocopy-len; j++) free(history[j]);
            tocopy = len;
        }
        memset(newHistory,0,sizeof(char*)*len);
        memcpy(newHistory,history+(history_len-tocopy), sizeof(char*)*tocopy);
        free(history);
        history = newHistory;
    }
    history_max_len = len;
    if (history_len > history_max_len)
        history_len = history_max_len;
    return 1;
}

/* Save the history in the specified file. On success 0 is returned
 * otherwise -1 is returned. */
int linenoiseHistorySave(const char *filename) {
    FILE *fp = fopen(filename,"w");
    int j;

    if (fp == NULL) return -1;
    for (j = 0; j < history_len; j++) {
        const char *str = history[j];
        /* Need to encode backslash, nl and cr */
        while (*str) {
            if (*str == '\\') {
                fputs("\\\\", fp);
            }
            else if (*str == '\n') {
                fputs("\\n", fp);
            }
            else if (*str == '\r') {
                fputs("\\r", fp);
            }
            else {
                fputc(*str, fp);
            }
            str++;
        }
        fputc('\n', fp);
    }

    fclose(fp);
    return 0;
}

/* Load the history from the specified file. If the file does not exist
 * zero is returned and no operation is performed.
 *
 * If the file exists and the operation succeeded 0 is returned, otherwise
 * on error -1 is returned. */
int linenoiseHistoryLoad(const char *filename) {
    FILE *fp = fopen(filename,"r");
    char buf[LINENOISE_MAX_LINE];

    if (fp == NULL) return -1;

    while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
        char *src, *dest;

        /* Decode backslash escaped values */
        for (src = dest = buf; *src; src++) {
            char ch = *src;

            if (ch == '\\') {
                src++;
                if (*src == 'n') {
                    ch = '\n';
                }
                else if (*src == 'r') {
                    ch = '\r';
                } else {
                    ch = *src;
                }
            }
            *dest++ = ch;
        }
        /* Remove trailing newline */
        if (dest != buf && (dest[-1] == '\n' || dest[-1] == '\r')) {
            dest--;
        }
        *dest = 0;

        linenoiseHistoryAdd(buf);
    }
    fclose(fp);
    return 0;
}

/* Provide access to the history buffer.
 *
 * If 'len' is not NULL, the length is stored in *len.
 */
char **linenoiseHistory(int *len) {
    if (len) {
        *len = history_len;
    }
    return history;
}

Deleted bindings/s2/linenoise/linenoise.h.

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
























































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* linenoise.h -- guerrilla line editing library against the idea that a
 * line editing lib needs to be 20,000 lines of C code.
 *
 * See linenoise.c for more information.
 *
 * ------------------------------------------------------------------------
 *
 * Copyright (c) 2010, Salvatore Sanfilippo <antirez at gmail dot com>
 * Copyright (c) 2010, Pieter Noordhuis <pcnoordhuis at gmail dot com>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *  *  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *  *  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef __LINENOISE_H
#define __LINENOISE_H

#ifndef NO_COMPLETION
typedef struct linenoiseCompletions {
  size_t len;
  char **cvec;
} linenoiseCompletions;

/*
 * The callback type for tab completion handlers.
 */
typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);

/*
 * Sets the current tab completion handler and returns the previous one, or NULL
 * if no prior one has been set.
 */
linenoiseCompletionCallback * linenoiseSetCompletionCallback(linenoiseCompletionCallback *);

/*
 * Adds a copy of the given string to the given completion list. The copy is owned
 * by the linenoiseCompletions object.
 */
void linenoiseAddCompletion(linenoiseCompletions *, const char *);
#endif

/*
 * Prompts for input using the given string as the input
 * prompt. Returns when the user has tapped ENTER or (on an empty
 * line) EOF (Ctrl-D on Unix, Ctrl-Z on Windows). Returns either
 * a copy of the entered string (for ENTER) or NULL (on EOF).  The
 * caller owns the returned string and must eventually free() it.
 */
char *linenoise(const char *prompt);

/*
 * Adds a copy of the given line of the command history.
 */
int linenoiseHistoryAdd(const char *line);

/*
 * Sets the maximum length of the command history, in lines.
 * If the history is currently longer, it will be trimmed,
 * retaining only the most recent entries. If len is 0 or less
 * then this function does nothing.
 */
int linenoiseHistorySetMaxLen(int len);

/*
 * Returns the current maximum length of the history, in lines.
 */
int linenoiseHistoryGetMaxLen(void);

/*
 * Saves the current contents of the history to the given file.
 * Returns 0 on success.
 */
int linenoiseHistorySave(const char *filename);

/*
 * Replaces the current history with the contents
 * of the given file.  Returns 0 on success.
 */
int linenoiseHistoryLoad(const char *filename);

/*
 * Frees all history entries, clearing the history.
 */
void linenoiseHistoryFree(void);

/*
 * Returns a pointer to the list of history entries, writing its
 * length to *len if len is not NULL. The memory is owned by linenoise
 * and must not be freed.
 */
char **linenoiseHistory(int *len);

/*
 * Returns the number of display columns in the current terminal.
 */
int linenoiseColumns(void);

#endif /* __LINENOISE_H */

Deleted bindings/s2/linenoise/utf8.c.

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



















































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
 * UTF-8 utility functions
 *
 * (c) 2010 Steve Bennett <steveb@workware.net.au>
 *
 * See LICENCE for licence details.
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "utf8.h"

#ifdef USE_UTF8
int utf8_fromunicode(char *p, unsigned short uc)
{
    if (uc <= 0x7f) {
        *p = uc;
        return 1;
    }
    else if (uc <= 0x7ff) {
        *p++ = 0xc0 | ((uc & 0x7c0) >> 6);
        *p = 0x80 | (uc & 0x3f);
        return 2;
    }
    else {
        *p++ = 0xe0 | ((uc & 0xf000) >> 12);
        *p++ = 0x80 | ((uc & 0xfc0) >> 6);
        *p = 0x80 | (uc & 0x3f);
        return 3;
    }
}

int utf8_charlen(int c)
{
    if ((c & 0x80) == 0) {
        return 1;
    }
    if ((c & 0xe0) == 0xc0) {
        return 2;
    }
    if ((c & 0xf0) == 0xe0) {
        return 3;
    }
    if ((c & 0xf8) == 0xf0) {
        return 4;
    }
    /* Invalid sequence */
    return -1;
}

int utf8_strlen(const char *str, int bytelen)
{
    int charlen = 0;
    if (bytelen < 0) {
        bytelen = strlen(str);
    }
    while (bytelen) {
        int c;
        int l = utf8_tounicode(str, &c);
        charlen++;
        str += l;
        bytelen -= l;
    }
    return charlen;
}

int utf8_index(const char *str, int index)
{
    const char *s = str;
    while (index--) {
        int c;
        s += utf8_tounicode(s, &c);
    }
    return s - str;
}

int utf8_charequal(const char *s1, const char *s2)
{
    int c1, c2;

    utf8_tounicode(s1, &c1);
    utf8_tounicode(s2, &c2);

    return c1 == c2;
}

int utf8_tounicode(const char *str, int *uc)
{
    unsigned const char *s = (unsigned const char *)str;

    if (s[0] < 0xc0) {
        *uc = s[0];
        return 1;
    }
    if (s[0] < 0xe0) {
        if ((s[1] & 0xc0) == 0x80) {
            *uc = ((s[0] & ~0xc0) << 6) | (s[1] & ~0x80);
            return 2;
        }
    }
    else if (s[0] < 0xf0) {
        if (((str[1] & 0xc0) == 0x80) && ((str[2] & 0xc0) == 0x80)) {
            *uc = ((s[0] & ~0xe0) << 12) | ((s[1] & ~0x80) << 6) | (s[2] & ~0x80);
            return 3;
        }
    }

    /* Invalid sequence, so just return the byte */
    *uc = *s;
    return 1;
}

#endif

Deleted bindings/s2/linenoise/utf8.h.

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















































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#ifndef UTF8_UTIL_H
#define UTF8_UTIL_H
/**
 * UTF-8 utility functions
 *
 * (c) 2010 Steve Bennett <steveb@workware.net.au>
 *
 * See LICENCE for licence details.
 */

#ifndef USE_UTF8
#include <ctype.h>

/* No utf-8 support. 1 byte = 1 char */
#define utf8_strlen(S, B) ((B) < 0 ? (int)strlen(S) : (B))
#define utf8_tounicode(S, CP) (*(CP) = (unsigned char)*(S), 1)
#define utf8_index(C, I) (I)
#define utf8_charlen(C) 1

#else
/**
 * Converts the given unicode codepoint (0 - 0xffff) to utf-8
 * and stores the result at 'p'.
 * 
 * Returns the number of utf-8 characters (1-3).
 */
int utf8_fromunicode(char *p, unsigned short uc);

/**
 * Returns the length of the utf-8 sequence starting with 'c'.
 * 
 * Returns 1-4, or -1 if this is not a valid start byte.
 *
 * Note that charlen=4 is not supported by the rest of the API.
 */
int utf8_charlen(int c);

/**
 * Returns the number of characters in the utf-8 
 * string of the given byte length.
 *
 * Any bytes which are not part of an valid utf-8
 * sequence are treated as individual characters.
 *
 * The string *must* be null terminated.
 *
 * Does not support unicode code points > \uffff
 */
int utf8_strlen(const char *str, int bytelen);

/**
 * Returns the byte index of the given character in the utf-8 string.
 * 
 * The string *must* be null terminated.
 *
 * This will return the byte length of a utf-8 string
 * if given the char length.
 */
int utf8_index(const char *str, int charindex);

/**
 * Returns the unicode codepoint corresponding to the
 * utf-8 sequence 'str'.
 * 
 * Stores the result in *uc and returns the number of bytes
 * consumed.
 *
 * If 'str' is null terminated, then an invalid utf-8 sequence
 * at the end of the string will be returned as individual bytes.
 *
 * If it is not null terminated, the length *must* be checked first.
 *
 * Does not support unicode code points > \uffff
 */
int utf8_tounicode(const char *str, int *uc);

#endif

#endif

Deleted bindings/s2/r-tester.sh.

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




















































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/bin/bash
#HELP>#####################################################################
# Runs require.s2 unit test scripts.
#
# Usages:
#
# 1) $0 [flags]
# searches for *.test.s2 in $(dirname $0)/require.d and runs them.
#
# 2) $0 [flags] moduleName ...moduleNameN
# Expects that each argument is a require.s2 module name and
# expects moduleName.test.s2 to exist in the require.s2 module
# search path.
#
# Runs each test in its own s2sh instance and exits with non-0 if
# any test fails.
#
# Flags:
#
# -V enables VERBOSE mode, which simply outputs some extra output.
#
# -vg enables valgrind/massif tests IF valgrind is found in the path.
# These generate some files which one may peruse to collect allocation
# metrics.
#
# Any flags not handled by this script are passed on to the underlying
# s2sh call (quoting might be a problem - avoid complex flags).
#
#HELP<###################################################################

dir=$(dirname $0)
#[[ '.' = "${dir}" ]] && dir=$PWD
s2sh=$dir/f-s2sh

[[ -x "$s2sh" ]] || s2sh=$(which s2sh 2>/dev/null)
[[ -x "$s2sh" ]] || {
    echo "s2sh not found :(. Looked in [${dir}] and \$PATH." 1>&2
    exit 1
}
echo "s2sh = ${s2sh}"
rdir=${REQUIRES2_HOME}
[[ -d "$rdir" ]] || rdir=$dir/require.d
[[ -d "$rdir" ]] || {
    echo "Missing 'required' dir: $rdir" 1>&2
    exit 2
}
rdir=$(cd $rdir>/dev/null; echo $PWD)
export S2_HOME=$PWD # used by require.s2

requireCall="(s2.require ||| import('${rdir}/require.s2'))"

DO_VG=0
VERBOSE=0
S2SH_ADD_FLAGS="${S2SH_XFLAGS}"
DO_DEBUG=0

list=''
for i in "$@"; do
    case $i in
        -V) VERBOSE=1
            ;;
        -vg) DO_VG=1
            ;;
        -debug)
            DO_DEBUG=1
            ;;
        -\?|--help)
            echo "$0:"
            sed -n '/^#HELP/,/^#HELP/p' < $0 | grep -v -e '^#HELP' \
                | sed -e 's/^#/    /g'
            exit 0;
            ;;
        -*)
            S2SH_ADD_FLAGS="${S2SH_ADD_FLAGS} $i"
            ;;
        *) list="$list $i.test"
        ;;        
    esac
done

[[ x = "x${list}" ]] && {
    cd $rdir || exit $? # oh, come on, steve, this isn't C!
    list=$(find . -name '*.test.s2' | cut -c3- | sed -e 's/\.s2$//g' | sort)
    cd - >/dev/null
}

[[ "x" = "x${list}" ]] && {
    echo "Didn't find any *.test.s2 scripts :(" 1>&2
    exit 3
}
list=$(echo $list) # remove newlines
#echo "Unit test list: $list"

function verbose(){
    [[ x1 = "x${VERBOSE}" ]] && echo $@
}

function vgTest(){
    local test=$1
    shift
    local flags="$@"
    local tmp=tmp.$test
    local vgout=vg.$test
    cat <<EOF > $tmp
${requireCall}(['nocache!${test}']);
EOF
    local cmd="$vg --leak-check=full -v --show-reachable=yes --track-origins=yes $s2sh ${S2SHFLAGS} -f ${tmp} ${flags}"
    echo "Valgrind: $test"
    verbose -e "\t$cmd"
    $cmd &> $vgout || {
        rc=$?
        rm -f $tmp
        echo "Valgrind failed. Output is in ${vgout}"
        exit $rc
    }
    #rm -f $vgout
    echo "Valground: $test [==>$vgout]"
    vgout=massif.$test
    local msp=ms_print.$test
    cmd="$massif --massif-out-file=$msp $s2sh ${S2SHFLAGS} -f ${tmp} ${flags}"
    echo "Massifying: $test"
    verbose -e "\t$cmd"
    $cmd &> $vgout || {
        rc=$?
        rm -f $tmp
        echo "Massif failed. Output is in ${vgout}"
        exit $rc
    }
    echo "Massified $test: try: ms_print ${msp} | less"
}

#if [ -e f-s2sh ]; then
#    # kludge for the libfossil source tree
#    S2SH_ADD_FLAGS="${S2SH_ADD_FLAGS}"
#fi
if [[ x1 = x${DO_DEBUG} ]]; then
    s2sh="gdb --args $s2sh"
fi
S2SHFLAGS="-rv -si ${S2SH_ADD_FLAGS}"
# Reminder: some fsl modules rely on code set up by s2sh init script,
# so we cannot use the --a option in libfossil.
echo S2SHFLAGS=$S2SHFLAGS
for test in $list; do
    echo "Running require.s2 test: ${test}"
    outfile=${rdir}/${test}.test_out
    verbose "Output going to: $outfile"
    rm -f "$outfile"
    tmpfile=${rdir}/${test}.tmp
    echo "${requireCall}(['nocache!${test}']);" > $tmpfile
    cmd="$s2sh ${S2SHFLAGS} -o ${outfile} -f ${tmpfile}"
    echo "Running test [${test}]: $cmd"
    $cmd
    rc=$?
    [[ 0 -eq $rc ]] || {
        echo "Test '${test}' failed. Script output (if any) saved to [${outfile}]" 1>&2
        exit $rc
    }
    #echo "Test did not fail: ${test}"
    rm -f $tmpfile
    if [[ -s "$outfile" ]]; then
        verbose -e "\tOutput is in: ${outfile}"
    else
        rm -f "${outfile}"
    fi
done

if [[ x1 = "x$DO_VG" ]]; then
   vg=$(which valgrind)
    if [[ -x "$vg" ]]; then
        echo "Runing test(s) through valgrind..."
        massif="${vg} --tool=massif --time-unit=ms --heap-admin=0"
        for test in $list; do
            outfile=$rdir/${test}.test_out
            rm -f "$outfile"
            vgTest $test -o "${outfile}"
        done
    fi
fi

echo "Done! Tests run: ${list}"

Deleted bindings/s2/require-demo.s2.

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










































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
// Alias our requirejs workalike for convenience...
assert Fossil.require /* gets set via f-s2sh.s2 */;
const R = Fossil.require;
if(!R.fsl){
    /* kludge to avoid requiring a CLI flag to provide a repo */
    const f = R(['fsl/context']).0;
    affirm f === R.fsl;
    affirm R.fsl.db;
    R.fsl.openCheckout();
}/* else a downstream module will open the repo as needed */


print('require() plugins:', R.plugins.propertyKeys());

// Use it like requirejs:
R([// Resources to load: those with a '!' are "plugins", not script files
    'fsl/wikiByName!download',
    'text!require-demo.s2',
    'fsl/blob!rid:1',
    'fsl/manifest!trunk'
  ], // Callback to pass the resources to:
  function(page, thisScript, rid1, trunk){
      print(__FLC, typeinfo(name page), typeinfo(name thisScript), typeinfo(name rid1));
      print("Wiki page [", page.L,"] is", page.W.lengthBytes(),"bytes long.");
      print("This script is",thisScript.lengthBytes(),"bytes long.");
      print("RID 1's manifest is", rid1.length(), "bytes long.");
      print("trunk has", trunk.F.length(), "F-cards.");
  });


R(['fsl/context', // shared/cached Fossil.Context instance
   'fsl/wiki/util' // various Wiki utilities
  ],
  function(fsl, wikiUtil){
      var pages = [];
      wikiUtil.getPageNames().
          eachIndex(proc(name){
              pages[] = fsl.loadManifest( wikiUtil.getLatestRid(name) );
          });
      print(pages.length(),"wiki pages found:");
      const j2h = Fossil.time.julianToHuman;
      0 && pages.sort(function(l,r){
          // It seems we have a bug in/around cwal_array_sort_stateful(), as
          // the sorted result here is very unexpected. But i'm too tired
          // to fight it.
          // print('',j2h(r.D), '\n', j2h(l.D), j2h(r.D).compare(j2h(l.D)));
          //return j2h(r.D).compare(j2h(l.D));
          //print(r.D, l.D, r.D.compare(l.D));
          //return (r.D * 10000000).toInt() - (l.D * 10000000).toInt();
          return r.D.compare(l.D);
      });
      pages.eachIndex(proc(page){
          print( "    %1$-25s @ %2$s by %3$s %4$d bytes".
                 applyFormat(page.L,
                             j2h(page.D),
                             page.U,
                             page.W.lengthBytes()));
      });
      print("");
  });

R(['fsl/timeline/basic', // list of most recent rows from the event table
   'ostream' // output utility for fans of C++
  ],
  proc(tl, os){
      os << "Most recent timeline entries:\n";
      tl.eachIndex(proc(v){
          os << v.type << ' ' << v.uuid.substr(0,10)
              << ' @ ' << Fossil.time.julianToHuman(v.mtime)
              << ' by '
              << (v.euser ||| v.user)
              << '\n\t'
              << (v.ecomment ||| v.comment)
              << '\n';
      });
      os << '\n';
  });

// An auto-loaded (not pre-registered) plugin:
R(['demo!foo',
   'demo!bar?a=1&b&c=hi there'
  ],
  proc(demo, d2){
      print("Demo auto-loaded plugin:",demo);
      assert d2 === demo /* because of how this particular plugin works */;
      assert 2 === demo.counter;
      assert 'bar' === demo.lastArgs.0;
      assert 'hi there' === demo.lastArgs.1.c;
  });

if(Fossil.file.isFile('cgimod.so')){
    R(['dll!cgimod', // the CGI API (loadable module)
       'dll!cgimod', // same instance (cached)
       'dll!cgimod?checkingCache' // params bypass caching
       /* reminder: 2 instances of this module is semantically
          invalid in a real CGI context. We're just testing that
          R()'s docs regarding caching matches its behaviour. */
      ],
      proc(cgi, cgi2, cgi3){
          assert cgi === cgi2;
          assert cgi !== cgi3;
             print("Loaded cgi module: "+cgi);
      });
}else{
    print("cgimod DLL not found.");
}

Deleted bindings/s2/require.d/BufferFactory.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   A require.s2 module which returns an object containing "factory
   methods" for creating Buffers.
*/
return {
    /**
       Usage:

       var buffer = thisObj.new([reservedMemorySize=0]);
    */
    new: s2.Buffer.new,
    /**
       Usage:

       var buffer = thisObj.readFile(filename);
    */
    readFile: s2.Buffer.readFile
}

Deleted bindings/s2/require.d/BufferFactory.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14














-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   A require.s2 module which returns an object containing "factory
   methods" for creating Buffers.
*/
requireS2(
['BufferFactory'],
proc(bfac){
    var b = bfac.new(100);
    affirm b.capacity()>=100;
    
    b = bfac.readFile(__FILE);
    affirm 'buffer' === typename b;
    affirm b.length()>50;
});

Deleted bindings/s2/require.d/DataModels/TestModel.s2.

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











































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   Test/demo require.s2 DataModel module the modeler module
   and/or the DataModel plugin.
*/
return {
    //__typename: 'Testing that this will not get overwritten',
    /**
       Gets called by the modeler framework when this module's
       (inherited) new() method is called. In the context of this
       function, "this" will be the new Model instance, which inherits
       this object.
       
       The return value is ignored by the modeler framework.
    */
    initialize: proc(a,b,c){
        print(__FLC, 'initialize()ing', typename this, this);
        print(__FLC, 'argv =', argv);
        print(__FLC, 'super =',super);
        assert __FILE.indexOf(typename this)>=0
        /* __typename gets automatically set from the file name
           when the model is loaded if it does not set one itself. */;
        this.attr('a',a)
            .attr('b',b)
            .attr('c',c);
    },
    /**
       Attributes defined in the prototype are visible via the
       inherited attr() method, but do not get serialized via the
       inherited toJSON() method unless their values have been
       explicitly set in the most-derived model instance.
    */
    attributes:{
        foo: -1
    }
};
/*.withThis(proc(){
    return ('undefined' === typename requireS2)
        ? this
        : ((const that=this),
           requireS2(['modeler'],proc(M){
               return M.extendModel(that)
           }));
});*/

Deleted bindings/s2/require.d/Ticker.s2.

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



























































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   A require.s2 module implementing an abstract timer class. See the
   inlined docs (down below) for details.

   Example usage:


   const Ticker = require(['Ticker']).0;
   const t = new Ticker();
   t.addEvent(1, true, proc(){print("repeating event")});
   t.addEvent(3, proc(){print("one-time event")});

   t.tick();
   t.tick();
   t.tick();
   // or: t.tick(3)

   Will output the following, though the order of events at firing at
   the same logical time (the last two lines here) is not defined and
   may change during the lifetime of repeating events or differ
   depending on whether the clock is incremented one tick at a time or
   more than one:

   repeating event
   repeating event
   one-time event
   repeating event
*/
/**
   Ticker is a utility class implementing an abstract
   timer. It doesn't know anything about time - it keeps
   track of time in abstract ticks. Events can be added
   to it which are fired after a certain number of ticks,
   optionally repeating every N ticks.
    
   To create a new instance, call this function. Its return
   value inherits this function.

   It's functionally similar to JavaScript's setTimeout()
   and setInterval() except for:

   a) It is strictly synchronous.

   b) It has no clock. The client has to tell it that X amount of
   (abstract) time has passed.

   c) It currently has no way to properly remove events (and doing so
   from an event handler may mess up its iteration for the time being).
*/


return {
    __typename: 'Ticker',
    /** Internal list of events */
    //tlist: [],

    /** Current timestamp (tick counter) */
    //ts: 0,

    /** Internal helper to sort event entries by their timestamp.
        Empty/null entries are sorted as less than any others
        because this make them easy to remove.
    */
    sortEvents:function f(){
        f.sorter || (f.sorter=function(l,r){
            // sort nulls to the LEFT (easier (but more memmov'ing) to lop them off that way)
            l || return r ? -1 : 0;
            r || return 1;
            return compare(l.when, r.when) |||
                compare(l.priority, r.priority) |||
                compare(l.ts,r.ts) |||
                compare(l.id,r.id);
        });
        this.tlist.sort(f.sorter);
    }.importSymbols({compare: function(l,r){
        return (l<r) ? -1 : ((l>r) ? 1 : 0);
	}}),

    /**
       Returns an incremental integer value on each call.
    */
    nextEventId: function(){
        this.idCounter || (this.idCounter = 0);
        return ++this.idCounter;
    },
    
    /**
       Adds a timer event to (potentially) be triggered
       via advancement of the timer (see tick()).

       'when' is the relative tick number (in the future) in which
       to fire the event. Must be a positive value.

       'repeats' is a boolean. Repeating events trigger every 'when'
       ticks.

       'what' is a Function which gets called when the event timer
       is triggered. When called, what's 'this' will be the
       event Object containing 'what'.

       If called with two arguments then it is treated as if
       (when, false, what) are passed in.

       If called with one argument then it is equivalent to calling
       addEvent2().
    
       Returns an Object describing the event. Clients may add
       their own properties to it and use those from the
       what() handler (accessible via this.PROPERTY).

       Callers can control the ordering of events fired at
       the same time by setting a priority numeric property
       on the returned event object. Priority sorts using
       normal numeric comparison, so lower values sort first.
       The default priority is 0 and negative values are legal.

       This class supports the following event object properties
       (and ignores all others):

       id: has no meaning but is used as a fallback option when sorting.
       Clients may set this to what they wish, and a default value
       (incremental integers) is set.

       ts: the current "timestamp", in ticks, of this ticker. It is
       incremented by calling tick().

       when: the tick time at which the event will next fire.

       what: the function to call when the event fires.

       interval: an integer specifying that the event should repeat
       every this-many ticks. If not set, or set to a falsy value,
       the event is a one-time event and will be removed after firing
       by the tick() process.

       priority: an integer value which determines run order for
       events firing at the same tick (lower values sort first). For
       "overlapping" ticks (with a time span of more than one tick)
       this order might be somewhat intuitive: all "overlap" runs of
       a given event handler are processed before the next
       event. i.e. the priority order is maintained, but each event
       may be run multiple times in succession before another event
       with a higher (or the same) priority value.

       client: this property name is reserved solely for use by
       client code. This API promises never to use that property
       key in event objects.
    */
    addEvent:function(when, repeats, what ){
        const argc = argv.length();
        (1===argc) && return this.addEvent2(when);
        if(2===argc && typeinfo(isfunction repeats)){
            what = repeats;
            repeats = false;
        }
        affirm when>0;
        affirm typeinfo(isfunction what);
        this.idCounter || (this.idCounter = 0);
        const ev = {
            id: this.nextEventId(),
            ts: this.ts,
            when: this.ts + when,
            what: what,
            priority: 0
        };
        repeats && (ev.interval = when);
        this.tlist.push(ev);
        return ev;
    },

    /**
       An alternate form of addEvent() which takes an object.

       Returns the object passed to it, after enriching it
       with event state and adding it to the list.

       If the object has a an 'interval' property but no 'when'
       property then its 'when' is set to the interval value.
       Likewise, if 'when' is set and 'repeats' is set to a truthy
       value, 'interval' gets set to 'when'.

       Example:

       ticker.addEvent({interval: 3, what:myFunc});

       will set up a repeating event, firing first in 3 ticks,
       and then ever 3 ticks after that.
    */
    addEvent2:function(obj){
        affirm typeinfo(iscontainer obj);
        affirm typeinfo(isfunction obj.what);
        if(obj.repeats && !obj.interval){
            obj.interval = obj.when;
        }
        unset obj.repeats;
        if(obj.interval && !obj.when){
            obj.when = obj.interval;
        }
        affirm obj.when > 0;
        obj.ts = this.ts;
        obj.priority || (obj.priority = 0);
        obj.id || (obj.id = this.nextEventId());
        this.tlist[] = obj;
        return obj;
    },

    /**
       Removes all event handlers and resets the tick counter to 0.
    */
    reset: function(){
        this.ts = 0;
        this.tlist.clear();
        return this;
    },

    /**
       Increments the tick count by incr (default=1) and triggers any
       events whose time comes. If incr is greater than one and a
       repeating event would normally have been triggered multiple
       times within that span, this function calls it the number of
       times it would have been called had tick() been called in
       increments of 1. The order of event callbacks is unspecified
       by default - they continually get re-sorted based on their
       trigger time. Clients can control the order of like-timed
       events by setting a priority level on an event - see
       addEvent() for details.

       Events added to this object by an event handler will NOT
       be called in this invocation of tick() - they are queued up
       and added to the event list after tick() has finished processing
       any pending handlers.

       Returns this object.
    */
    tick: function(incr=1){
        affirm typeinfo(isinteger incr);
        affirm incr >= 0; // a tick value of 0 might be interesting for something
        const li = this.tlist;
        var n = li.length();
        affirm typeinfo(isarray li);
        this.ts += incr;
        n || return this;

        /* Remove any expired-and-fired events here (as opposed to afterwards)
           because (it turns out) this simplifies things. */
        this.sortEvents(); // moves nulls to the left
        while(n && !li.0){
            /* Remove any expired events */
            li.shift();
            --n;
        }
        n || return this;

        // Move this.tlist so that adding events while looping does not affect us.        
        const listSentry = this.tlist;
        this.tlist = [];

        /*
          Loop over events until we find one with a 'when'
          set in the future. All current events will be to the
          left of that one.
        */
        const tm = this.ts;
        for(var i = 0, e, shiftCount = 0, repeater = 0;
            i<n; ++i){

            e = repeater ||| li[i];
            repeater = 0;
            if(e.when>tm){ break } /* first in-the-future event. */
            else {
                affirm e.when > e.ts;
                if(var err=catch{e.what()}){
                    print(__FLC,"WARNING: tick handler threw. Removing it.",e,err);
                    unset e.interval;
                }
                if(e.interval){ /* Set this one up to run again */
                    e.ts = e.ts + e.interval;
                    e.when = e.ts + e.interval;
                    if(e.when <= tm){
                        /* timespan skipped one or more intervals. Run them now. */
                        //print(__FLC,"firing again for overlap:",e);
                        --i /* fudge the loop counter to repeat this entry.
                               May break this.tlist is modified by
                               callback. */;
                        repeater = e;
                        continue;
                    }
                }else{ /* remove the event */
                    li[i] = undefined;
                    ++shiftCount;
                }
            }
        }
        /* Move this.tlist back... */
        if(this.tlist.isEmpty()){ /* no changes made during iteration */
            this.tlist = listSentry;
        }else{
            /* Events added while looping. Integrate them now.
               We swap tlist here so that we can keep the original
               array (as a minor potential allocation optimization). */
            this.tlist.eachIndex(integrateTlist);
            this.tlist = listSentry;
        }
        return this;
    }/*tick()*/.importSymbols({
        integrateTlist: proc(i,v){ listSentry.push(v) }
    }),
    /**
       Constructor function for new instances.
    */
    __new: proc(){
        this.tlist = [];
        this.ts = 0;
    }
};

Deleted bindings/s2/require.d/Ticker.test.s2.

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
































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
requireS2(['time','nocache!Ticker'], proc(time, Ticker){
    affirm 'Ticker' === typeinfo(name Ticker);
    const t = new Ticker();
    affirm t inherits Ticker;
    affirm 'Ticker' === typeinfo(name t);
    const callback = proc(){print('fired:',this.name)};
    var ev;
    affirm typeinfo(isfunction callback);
    // Repeating event, every 3rd tick:
    ev = t.addEvent(3, true, callback);

    ev.name = "threeer";
    ev.priority = 3;
    /*
      Event priority is used to break ties for events firing at
      the same virtual time. It sorts by value, so a lower value
      has a higher priority. Events have a default priority of 0.
    */

    // Add a repeating event which fires every tick:
    ev = t.addEvent(1, true, callback);
    ev.name = "oner";
    ev.priority = -1;


    // Add a one-time event, fired once, four ticks from now:
    affirm typeinfo(isfunction callback);
    ev = t.addEvent(4, callback);
    ev.name = "fourer";
    ev.priority = 4;

    // Add a repeating event, firing every 2nd tick:
    ev = t.addEvent({interval: 2, what: callback, name: "twoer"});

    print(__FLC,typeinfo(name t),':',t);

    // Now advance our virtual clock a few times...
    var i = 1, max = 10, sleepTimeMs = 100;

    print("Ticking",max,"times, sleeping",sleepTimeMs," millis between ticks...");
    //const timeFmt = {%Y-%m-%d %H:%M:%S}
    //const startTime = strftime(timeFmt, time())
    for( ;i<=max; i=i+1){
        print("TICK #"+i);
        time.mssleep(sleepTimeMs);
        t.tick();
    }
    //const endTime = strftime(timeFmt, time());
    print('After loop: Tick =',t.ts);
    //print('Start time:',startTime,'\nEnd time  :',endTime);
    if(0) {
        print('"Manually" ticking...');
        t.tick(6); // note how priority sorting kinda breaks down here
        // potential workaround: run ticks>1 in a loop of smaller ticks,
        // but how small? Maybe set tickerInstance.timeUnit=integer to
        // set the smallest time unit, and loop in increments of that unit?
    }
    //t.tick(5);
    //print(__FLC,"Done:",t);

    //dumpInternedStrings();
    t.reset();
    affirm t.tlist.isEmpty();
});

Deleted bindings/s2/require.d/cliargs.s2.

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





































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   A require.s2 module which returns a utility object for working with
   the CLI arguments provided via s2.ARGV.

   "Script arguments" are those passes to s2sh after any '--' flag.
   Those get imported into s2.ARGV (which is not set if no script
   flags are provided).

   s2.ARGV, if set, is an Array containing all flags passed after
   '--', plus possibly containing these two properties:

   .flags: an object of --flag=value pairs, where --flags with no
   value are treated as boolean true.

   .nonFlags: any script argument which does not start with '-' is
   assumed to be a filename or some other string, and is appended
   to this array.

   Either property will be undefined if no flags resp. non-flags
   are provided.
*/
return {

    /**
       Holds an Array of all arguments passed after '--' to s2sh
       resp. any app which installs that particular binding. It is
       undefined (not an empty array) if there are no such flags.
    */
    args: s2.ARGV,
    /**
       An object (or not) containing any -flags. The keys are stripped
       of any number of leading dashes and if a given flag is
       duplicated, the last one currently wins (as opposed to getting
       an array of values, though that might be a useful addition).
    */
    flags: s2.ARGV ? s2.ARGV.flags : undefined,

    /**
       An array (or not) of any non-flags passed after --,
       in the order they were passed in.
    */
    nonFlags: s2.ARGV ? s2.ARGV.nonFlags : undefined,

    /**
       If the given flag (minus any number of prefixing "-") was passed in
       the "script flags" (any flags passed after '--' to the s2sh
       interpreter), its value is returned, otherwise dflt is returned.
    */
    getFlag: proc(flag, dflt){
        return this.flags
            ? (this.flags[flag] ?: dflt)
            : dflt;
    },

    /**
       If the given flag (minus any number of prefixing "-") was
       passed in the "script flags" (any flags passed after '--' to
       the s2sh interpreter), its is removed from the flags and its
       value is returned, otherwise dflt is returned.

       When the last flag is removed, this.flags is unset.
    */
    takeFlag: proc(flag, dflt){
        this.flags || return dflt;
        if(undefined !== const v = this.flags[flag]){
            unset this.flags[flag];
            this.flags.# || unset this.flags;
            return rc;
        }else return dflt;
    },

    /**
       Returns true if the CLI flags (still) contain any flags,
       otherwise false.
    */
    hasFlags: proc(){
        return this.flags ? this.flags.#>0 : false;
    },

    /**
       Returns true if the CLI flags (still) contain any non-flags,
       otherwise false.
    */
    hasNonFlags: proc(){
        return this.nonFlags ? !this.nonFlags.isEmpty() : false;
    },

    /**
       Removes the first non-flag from the list and returns
       it. Returns undefined if there are no non-flags (or none
       remaining).

       When the last entry is removed, this.nonFlags is unset.
    */
    nextNonFlag: proc(){
        this.nonFlags || return undefined;
        var rc = this.nonFlags.shift();
        (0===this.nonFlags.length()) && unset this.nonFlags;
        return rc;
    }
};

Deleted bindings/s2/require.d/fsl/context.s2.

1
2
3
4
5
6
7







-
-
-
-
-
-
-
/**
   require() plugin which simply resolves to the shared Fossil.Context
   instance set up by the f-s2sh bootstrap code. This instance should
   be used by all other modules so that everybody is using the same
   database handles.
*/
return requireS2.fsl ||| (requireS2.fsl = new Fossil.Context());

Deleted bindings/s2/require.d/fsl/db/checkout.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14














-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no checkout has been
   opened when this is called, openCheckout() is called on the
   context, otherwise there are no side effects.
*/
return requireS2(['fsl/context'], proc(F){
    affirm const d = F.db;
    if(!d.checkout){
        F.openCheckout();
    }
    affirm d.repo;
    return d.repo;
});

Deleted bindings/s2/require.d/fsl/db/config.s2.

1
2
3
4
5
6
7
8
9
10
11











-
-
-
-
-
-
-
-
-
-
-
/* require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no repo has been opened
   when this is called, it opens the repo specified via the
   --repo-db|-R=FILENAME CLI script flag.
*/
return requireS2(['fsl/context'], proc(F,args){
    affirm const d = F.db;
    d.config || F.openConfig();
    affirm d.config;
    return d.config;
});

Deleted bindings/s2/require.d/fsl/db/main.s2.

1
2
3
4
5





-
-
-
-
-
/**
   require.s2 module which returns the "main" db handle of
   the shared Fossil.Context instance.
*/
return requireS2(['fsl/context']).0.db;

Deleted bindings/s2/require.d/fsl/db/repo.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18


















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* require() plugin which returns a handle to the repo Db object for
   Fossil.require's shared Fossil.Context.  If no repo has been opened
   when this is called, it opens the repo specified via the
   --repo-db|-R=FILENAME CLI script flag.
*/
return requireS2(['fsl/context','cliargs'], proc(F,args){
    affirm const d = F.db;
    if(!d.repo){
        const f = args.takeFlag('repo-db',args.takeFlag('R'))
            ||| throw "No repo db specified. "+
            "Pass the --repo-db|-R=filename "+
            "SCRIPT flag (after --) to the interpreter or open a checkout.";
        ('string' === typename f) || throw "Invalid argument type for the --repo-db|-R=DBFILE flag";
        F.openRepo(f);
    }
    affirm d.repo;
    return d.repo;
});

Deleted bindings/s2/require.d/fsl/db/repoOrCheckout.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   require.s2 module which returns either the repo db (specified by
   the -R|--repo-db=DBFILE script flags) or (if that fails) the repo
   db associated with the current checkout. Throws if it can neither
   open a checkout nor figure out the db from the CLI args.
*/
return requireS2(['fsl/context'], proc(F){
    affirm F.db;
    F.db.repo && return F.db.repo;
    F.db.checkout && return F.db.checkout;
    catch return requireS2(['fsl/db/repo']).0; // fails if no -R|--repo-db=DBFILE flag specified
    requireS2(['fsl/db/checkout']) /* opens the checkout */;
    assert F.db.checkout;
    return F.db.repo;
});

Deleted bindings/s2/require.d/fsl/extendFossil.s2.

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

























































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/*
  A require.s2 module which adds some functionality
  to the various Fossil classes.

  This module is not intended to be used more than once and should not
  be cached. Thus, to use it:

  Fossil.require(['nocache!fsl/extendFossil'],proc(){})
*/


assert Fossil;
assert Fossil.Context;
Fossil.artifactTypes && return Fossil /* already ran this module */;


//Fossil.loadModule = s2.loadModule2;
//Fossil.importScript = s2.import2;

/**
  Counterpart of the C-level fsl_catype_e enum.
*/
Fossil.artifactTypes = {
    ANY: 0,
    CHECKIN: 1,
    CLUSTER: 2,
    CONTROL: 3,
    WIKI: 4,
    TICKET: 5,
    ATTACHMENT: 6,
    EVENT: 7
};

/**
    Fetches the first row from the given SQL statement (String or
    Buffer) and returns it as an Object (if asArray is false) or an
    Array (if asArray is true).

    If the bind argument is passed in then it is passed on to the
    Stmt.bind() method of the underlying statement. Use an array to
    bind multiple values. To specify the third parameter when there is
    nothing to bind, pass the undefined value as the second argument.
    i.e. bind===undefined is treated as "nothing to bind," instead of
    binding undefined/null.

    Throws on error. Returns undefined if no row is found.
*/
Fossil.Db.selectRow = proc(sql, bind = undefined, asArray = false){
    affirm this inherits Fossil.Db;
    const st = this.prepare(sql);
    var rc;
    const ex = catch {
        st.bind(bind);
        rc = asArray ? st.stepArray() : st.stepObject();
    };
    st.finalize();
    ex ? throw ex : return rc;
};

/**
   Returns an array containing the complete results of each row in the
   given SQL's result set. If asArray is true, each row is returned as
   an array of column values in the same order as the result set's,
   else each is returned an an Object of column name/value pairs with
   columns in an an unspecified order.

   The bind parameter may hold value(s) to bind to the given SQL.
*/
Fossil.Db.selectAll = proc(sql, bind = undefined, asArray = true){
    affirm this inherits Fossil.Db;
    const rc = [];
    this.each({
        sql: sql,
        bind: bind,
        mode: asArray ? 1 : 0,
        callback: 'rc[] = this',
    });
    return rc;
};

/**
    Given a SELECT-style query and optional bind parameters
    (either a single value for a single param or an array
    of multiple params), this routine simply dumps out
    the results of the query. bind===undefined is treated
    as "nothing to bind," instead of binding undefined/null.
*/
Fossil.Db.dumpQuery = proc(sql,bind = undefined, separator='\t'){
    affirm this inherits Fossil.Db;
    return this.each({
        sql:sql,
        bind:bind, // note that undefined value is treated as non-existent here
        callback:proc(){
            (1===rowNumber) && print(columnNames.join(separator));
            print(this.join(separator));
        }
    });
};

/**
    Stmt.each() loops over this.step(), calling func(N) on each
    iteration, where N is the current row number (1-based).  If func()
    returns a literal false, looping stops without an error. In the
    context of the call, 'this' is the Stmt object.

    Returns this object.
*/
Fossil.Db.Stmt.each = proc(func){
    affirm this inherits Fossil.Db.Stmt;
    affirm typeinfo(iscallable func) && typeinfo(iscallable func.call);
    for(var rowNum = 1; this.step(); ++rowNum){
        (false === func.call(this, rowNum)) && break;
        //^^^^^^^^ emulate Db.each()
    }
    return this;
};


/**
    Changes to the given dir and pushes the (old) current dir
    to the directory stack. To change back to the pre-pushd()
    directory, call Fossil.file.popd(). This function relies
    on this 'this' object beeing Fossil.file. Throws if it
    cannot change directories.

    The array of pushed directory names is available after calling
    this function one time via the property
    Fossil.file.pushd.dirStack. The most recent directory is at the
    end of that array. If that property is undefined, pushd() has
    never been called (or someone removed the property). If it is an
    empty array, there are currently no directories in the stack.
*/
Fossil.file.pushd = proc callee(dir){
    affirm 'string' === typename dir;
    const curdir = this.currentDir();
    this.chdir(dir);
    (callee.dirStack ||| (callee.dirStack = [])).push( curdir );
};

/**
    Pops the directory mostly recently pushed by Fossil.file.pushd()
    off of the directory stack and changes to that directory. Throws
    if called when no directories can be popped.
*/
Fossil.file.popd = proc(){
    const list = this.pushd.dirStack;
    const len = (list ? list.length() : 0)
        ||| throw "Directory stack is empty.";
    this.chdir(list[len-1]);
    return list.pop()
};

return Fossil;

Deleted bindings/s2/require.d/fsl/reports/common.s2.

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



































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   INCOMPLETE helper module for pending timeline/reporting bits.
*/
return requireS2(['fsl/db/repo'],
proc(db){
    const mod = {
        db: db,

        /*eventTypeMap:{
            '*': 'all',
            ci: 'checkins',
            w: 'wiki',
            g: 'tags',
            e: 'events',
            t: 'tickets'
        },*/

        /**
           Returns an array of years (integers) representing
           the years for which there is timeline activity in
           this repo. The entries are sorted ascending.
        */
        getActiveYears:proc(){
            return this.db.selectValues(<<<EOSQL
            SELECT DISTINCT CAST(strftime('%Y',mtime) AS INT) AS t
            FROM /*v_reports*/ event GROUP BY t ORDER BY t EOSQL);
        },

        /**
           Given a year in string or integer form, this function
           returns an array of integers holding the calendar week
           numbers (as determined by sqlite) of all "active" weeks
           during the give year. Returns an empty array if there is
           no timeline activity for that year.
         */
        getActiveWeeksForYear: proc(year){
            year || throw "Expecting a year (string|integer) value.";
            return this.db.selectValues(<<<EOSQL
                SELECT DISTINCT CAST(strftime('%W',mtime) AS INT) AS t
                FROM /*v_reports*/ event
                WHERE strftime('%Y',mtime)=?
                GROUP BY t ORDER BY t
                EOSQL,
                ''+year/*has to be a string for comparison to work*/);
        },

        /**
           Returns an array of objects, each with the properties
           (year, weeks), with year being the year (integer) for that
           entry and weeks being an array of the calendar weeks
           (integers, as determined by sqlite) which had activity
           during that year.
         */
        getActiveYearsAndWeeks: proc(){
            const rc = [], that = this;
            this.getActiveYears().eachIndex(proc(v){
                rc[] = {
                    year: v,
                    weeks: that.getActiveWeeksForYear(v)
                };
            });
            return rc;
        }
    };

    return mod;
});

Deleted bindings/s2/require.d/fsl/reports/common.test.s2.

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



































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
requireS2([
    'fsl/db/repoOrCheckout',
    'fsl/util/repo',
    'fsl/reports/common'],
proc(ignored, rutil, rcom){
    affirm rcom.db;

    var x = rcom.getActiveYears();
    affirm 'array' === typename x;
    affirm x.length() > 0;

    const isLibfossil = 'libfossil' === rutil.getConfig('project-name');

    if(isLibfossil){
        affirm x.indexOf(2014) >= 0;
        affirm x.indexOf(1972) < 0;
    }

    x = rcom.getActiveWeeksForYear(2014);
    affirm 'array' === typename x;
    affirm x.length() > 0;

    if(isLibfossil){
        affirm 1 === x.0;
        affirm x.indexOf(22) < 0 /* no activity that week */;
        affirm x.indexOf(41) > 0 /* when this test was added */;
    }

    x = rcom.getActiveYearsAndWeeks();
    affirm 'array' === typename x;
    affirm x.length() > 0;
    affirm 'integer' === typename x.0.year;
    affirm 'array' === typename x.0.weeks;

});

Deleted bindings/s2/require.d/fsl/timeline/basic.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20




















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   require() module which returns an array of Objects from the event
   table. Each includes all fields from the event table plus the
   associated blob's uuid.
*/
return requireS2(
    ['fsl/db/repo'],
    proc(repo){
        var rc = [];
        repo.each({
            mode: 0,
            sql:<<<_SQL
            SELECT e.*, b.uuid uuid
            FROM event e JOIN blob b ON e.objid=b.rid
            ORDER BY e.mtime DESC LIMIT 5
            _SQL,
            callback:'rc[] = this'
        });
        return rc;
});

Deleted bindings/s2/require.d/fsl/util/repo.s2.

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

























-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   In-progress set of utilities for working with repo db tables.
*/
requireS2(['fsl/db/repoOrCheckout'], proc(repo){
    return {
        db: repo,
        /**
           Fetches an array of strings from this.db, each one being
           the name of a user in the current repository. They are
           sorted by name.
        */
        getUserList: proc(){
            return this.db.selectValues('select login from user order by login');
        },

        /**
           Returns the value of the repoDb.config field with the given key,
           or undefined if no such row is found.
        */
        getConfig: proc(key){
            affirm 'string' === typename key /* expecting a legal repo.config.key value */;
            return this.db.selectValue('SELECT value FROM config WHERE name=?',key);
        }
    };
});

Deleted bindings/s2/require.d/fsl/util/repo.test.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19



















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   In-progress set of utilities for working with repo db tables.
*/
requireS2(['fsl/db/checkout'/*force repo db open*/,'fsl/util/repo'],
proc(ignored, repo){
    const db = repo.db;
    affirm db;
    affirm repo.getUserList;
    var x = repo.getUserList();
    affirm 'array' === typename x;
    affirm x.length() > 0;
    affirm x.indexOf('anonymous') >= 0;

    x = repo.getConfig('project-name');
    affirm 'string' === typename x;
    x = repo.getConfig('no-such-key');
    affirm undefined === x;
    affirm catch{repo.getConfig()}.message.indexOf("'string'") >0;
});

Deleted bindings/s2/require.d/fsl/wiki/pageNames.s2.

1
2
3
4




-
-
-
-
return requireS2(
    ['fsl/wiki/util'],
    proc(util){ return util.getPageNames(); }
);

Deleted bindings/s2/require.d/fsl/wiki/util.s2.

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







































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   A set of wiki-related utilities.
*/
return requireS2(
    ['fsl/context', 'fsl/db/repo'],
    proc(fsl, repo){
        return {
            getPageNames: proc( caseSensitive ){
                const st = repo.prepare(<<<_SQL
                                        select distinct(substr(tagname,6)) name from tag t,
                                        tagxref x where x.tagid=t.tagid and t.tagname
                                        like 'wiki-%' order by name _SQL
                                        + (caseSensitive
                                           ? '' : ' collate nocase'));
                var rc = [];
                const ex = catch {
                    while(st.step()) rc[] = st.get(0);
                };
                st.finalize();
                ex ? throw ex : return rc;
            }.importSymbols(nameof repo),
            
            getLatestRid: proc(pageName){
                affirm pageName && 'string' === typename pageName;
                return repo.selectValue(<<<_SQL
                                        SELECT x.rid FROM tag t, tagxref x 
                                        WHERE x.tagid=t.tagid
                                        AND t.tagname=?
                                        ORDER BY mtime DESC LIMIT 1
                                        _SQL,
                                        'wiki-'+pageName
                                       );
            }.importSymbols(nameof repo),
            
            loadPageArtifact: proc(pageName){
                return fsl.loadManifest( this.getLatestRid(pageName) );
            }.importSymbols(nameof fsl)
        }
    });

Deleted bindings/s2/require.d/io.s2.

1
2
3
4
5





-
-
-
-
-
/* a require.s2 module which "hides" s2.io via the module
   interface. It could optionally load 'dll!mod_io', but that's
   an internal impl detail.
*/
return s2.io;

Deleted bindings/s2/require.d/json.s2.

1
2
3
4
5





-
-
-
-
-
/* a require.s2 module which "hides" s2.json via the module
   interface. It could optionally load 'dll!mod_json', but that's
   an internal impl detail.
*/
return s2.json;

Deleted bindings/s2/require.d/json2.s2.

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












































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
affirm s2.json /* we need this as our basis */;
affirm s2.json.stringify /* was added later, might not be in all (two?) trees yet (ha!) */;
/**
   This object basically acts as a (mostly) drop-in replacement for
   s2.json, and it uses s2.json to implement most of its
   functionality. It uses a custom, script-side stringify() which is
   orders of magnitude less efficient (on several levels) than
   s2.json.stringify() (which is implemented in C), but allows
   overriding of to-JSON behaviour on a per-container or per-prototype
   basis.
*/
return {
    /** See s2.json.parse(). */
    parse: s2.json.parse,
    /** See s2.json.parseFile(). */
    parseFile: s2.json.parseFile,
    /**
       Converts the value v into a JSON string (or throws while trying).

       indention may be either a falsy value (for no intenation), a
       string (which gets prepended N times for N levels of
       indentation), or an integer: a positive value indents that many
       spaces and a negative value indents that many tabs.

       v need not be a root-level value (Object or Array), but may be
       a string, number, or boolean.

       Returns a string on success, throws on error.

       Notes about special cases:

       - If v or a prototype of v contains a function property named
       toJSON() then v.toJSON() is used in place of v for
       to-JSON-string conversion. The function must return some
       JSON-able form of v. e.g. an implementation for a Hashtable
       might return an Object in the form {keys:[...], values:[...]}.

       - Object _keys_ which are _not_ of type (string, integer, double)
       are elided from the output. Keys of numeric types are converted
       to strings for JSON key purposes.

       - Objects elide any keys which have value counterpart of
       undefined. JSON does not know 'undefined'. We "could" translate
       it to null here, but we instead opt to elide it.

       - The undefined value: if passed to this function, the string
       'null' is returned. undefined is also translated to 'null' in
       the context of array empty entries.
    */
    stringify: proc stringify(v, indention = stringify.config.indention){
        affirm ++stringify.level > 0;
        const ex = catch{
            typeinfo(isderefable v) && typeinfo(iscallable v.toJSON) && (v = v.toJSON());
            const f = tmap # typename v;
            affirm f /* Argument must be a known JSON-able type or have a toJSON() method. */;
            if('string'===typename f){
                affirm --stringify.level>=0;
                return f;
            }else if(!f.buffered){
                /* "Simple" conversions which do not recurse */
                const rc = f(v);
                affirm --stringify.level>=0;
                return rc;
            }else if(stringify.level>stringify.config.maxOutputDepth){
                throw exception('CWAL_RC_RANGE',
                                "Output depth limit ("+stringify.config.maxOutputDepth+
                                ") exceeded while generating JSON.");
            }else{
                affirm f.buffered /* f.buffered is set, so... */;
                const jbuf = s2.Buffer.new() /* gets appended to by f() */;
                f(v) /* appends all output to jbuf */;
                affirm --stringify.level>=0;
                affirm !jbuf.isEmpty();
                return jbuf.takeString();
            }
        };
        affirm --stringify.level>=0;
        assert ex /* or we couldn't have gotten this far */;
        throw ex;
    }.withThis(proc(){
        /**
           Public configuration for stringify(). Change these
           options to modify the defaults.
        */
        this.config = {
            /* Default indention used by stringify(). */
            indention: undefined,
            /* Separator for entries in arrays and object lists. */
            commaSeparator: ', ',
            /* Separator for keys and value in objects. */
            keyValSeparator: ': ',
            /* Max object/array depth to allow before erroring
               out. Remember that cycles will generally be detected
               before this happens, so this doesn't necessarily
               indicate that any cycles were encountered.
            */
            maxOutputDepth: 15
        };
        this.level = 0;
        return this;
    }).importSymbols({
        // some crazy scoping and var accesses going on here...
        /**
           Indents the output, if appropriate, based on the current
           call level (or the level specified by the 2nd
           parameter). If addNL is true, a newline is appended before
           the indentation. This is a no-op if stringify() is called
           with a falsy indention parameter.
         */
        indent: proc callee(addNL=true, level = stringify.level){
            indention || return;
            callee.idbuf || (callee.idbuf = s2.Buffer.new(64));
            if(callee.prevLevel !== level){
                callee.prevLevel = level;
                if('integer'===typename indention){
                    const len = (indention<0) ? -indention : indention;
                    affirm len >= 0;
                    callee.idbuf.length( len * level )
                        .fill((indention<0) ? 0x09 : 0x20);
                }else if('string' === typename indention){
                    callee.idbuf.reset();
                    for(var i = 0; i < level; ++i){
                        callee.idbuf << indention;
                    }
                }
            }
            addNL && (jbuf << '\n');
            jbuf << callee.idbuf;
        },
        /**
           A hashtable mapping typenames to either strings (for static
           conversions) or a function taking a value parameter. Those
           functions normally return a string, but if the function has
           a 'buffered' property which is truthy then its return
           result is ignored and instead a Buffer value named jbuf is
           made available to them, and they are expected to append all
           output there.
         */
        tmap: scope {
            const proxy4Obj = proc(v){
                v.mayIterate() || throw exception('CWAL_RC_CYCLES_DETECTED',"Cycles detected.");
                jbuf << '{';
                proxyEachProp.first = true;
                //proxyEachProp('LEVEL', stringify.level);
                const ex = catch v.eachProperty(proxyEachProp);
                indention && indent(true,stringify.level-1);
                jbuf << '}';
                ex && throw ex;
            }.importSymbols({
                // Object.eachProperty() proxy.
                proxyEachProp: proc callee(k,v){
                    undefined === v && return;
                    callee.first
                        ? callee.first = false
                        : jbuf << stringify.config.commaSeparator;
                    indention && indent();
                    const tk = typename k;
                    if('string'===tk){
                        jbuf << k.toJSONString();
                    }else if('integer'===tk||'double'===tk){
                        jbuf << '"' << k << '"';
                    }else{
                        return;
                    }
                    jbuf << stringify.config.keyValSeparator
                        << stringify(v,indention);
                }
            });
            const proxy4Array = proc(v){
                v.mayIterate() || throw exception('CWAL_RC_CYCLES_DETECTED',"Cycles detected.");
                jbuf << '[';
                proxyEachIndex.first = true;
                v.eachIndex(proxyEachIndex);
                indention && indent(true,stringify.level-1);
                jbuf << ']';
            }.importSymbols({
                proxyEachIndex: proc callee(v){
                    callee.first
                        ? callee.first = false
                        : jbuf << stringify.config.commaSeparator;
                    indention && indent();
                    jbuf << stringify(v,indention);
                }
            });

            proxy4Obj.buffered =
                proxy4Array.buffered = true
            /* tells stringify() to set up a buffer to send the
               results to. */;
            const nativeImpl = s2.json.stringify;
            {#
                array: proxy4Array,
                bool: nativeImpl,
                double: nativeImpl,
                exception: proxy4Obj,
                integer: nativeImpl,
                null: 'null',
                object: proxy4Obj,
                string: nativeImpl,
                undefined: 'null'
            }; // scope result
        }
    })/*stringify()*/
};

Deleted bindings/s2/require.d/json2.test.s2.

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










































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
requireS2(['json2'],proc(JSON){
    //JSON.stringify.config.indention = -1;
    const str = proc(v,indent=-1){
        return JSON.stringify(v,indent);
    };
    var s;
    assert '1' === str(1);
    assert '1.23' === str(1.23);
    assert '"hi, \\"there\\""' === str('hi, "there"');
    assert 'null' === str(null);
    assert 'null' === str(undefined)
    /* undefined will be elided in some contexts (object properties),
       translated to null in others (e.g. arrays). */;
    assert 'false' === str(false);
    assert 'true' === str(true);
    s = str({x:1, y:{z:'hi "there"', a:[1,2,"yo"]}, u: undefined, n: null});
    assert s.indexOf('"u":') < 0;
    assert s.indexOf('"n": null')>0;

    s = JSON.parse(str(exception(-1,"not an error")));
    assert 'not an error' === s.message;
    assert -1 === s.code;

    s = JSON.parse(str({a:{b:{1:2,3:4}}}));
    assert 2 === s.a.b.1;
    assert 4 === s.a.b.3
    /**
       BUT ACHTUNG: the integer _keys_ got converted to strings in the
       round trip because JSON only supports strings as
       keys. Supporting round-trip fideltity for non-string keys
       requires a layer of indirection, as demonstrated next...
    */;

    // Customizing toJSON for a non-POD type...
    var h = s2.Hash.new();

    // A stringify()-compliant toJSON() impl for Hashtables.
    h.toJSON = proc(){
        return {
            keys: this.entryKeys(),
            values: this.entryValues()
        }
    };

    // Just for symmetry (not used by the JSON API)...
    h.fromJSON = proc(jsonObj){
        ('string' === typename jsonObj) && (jsonObj = JSON.parse(jsonObj));
        this.clearEntries();
        if(('array' === typename jsonObj.keys) &&
           ('array' === typename jsonObj.values)){
            const that = this;
            jsonObj.keys.eachIndex(proc(v,i){
                that.insert(v, jsonObj.values[i]);
            });
        }
        return this;
    }.importSymbols(nameof JSON);

    // Now try serializing a hash...
    h.insert(1, "one");
    h.insert(2, "two");
    s = JSON.parse(str(h));
    assert 2 === s.keys.length();
    assert 2 === s.values.length();
    assert s.keys.indexOf(2)>=0;
    assert s.values.indexOf('two')>=0;

    h.clearEntries();
    assert undefined === h # 2;

    h.fromJSON(s);
    assert 'two' === h # 2;
    
});

Deleted bindings/s2/require.d/ob.s2.

1
2
3
4
5





-
-
-
-
-
/* a require.s2 module which "hides" s2.ob via the module
   interface. It could optionally load 'dll!mod_ob', but that's
   an internal impl detail.
*/
return s2.ob;

Deleted bindings/s2/require.d/ostream.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13













-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   A require() module providing an "ostream" object. It overloads the
   << operator and sends all arguments to s2out.

   This module was written before the s2out keyword existed. That
   keyword does the same thing but does so more efficiently, making
   this module entirely superfluous. It is retained solely as a basic
   example of how to write a require.s2 module.
*/
affirm typeinfo(iscallable s2out);
return {
    'operator<<': proc(){s2out<<argv.0; return this}
};

Deleted bindings/s2/require.d/plugins/demo.s2.

1
2
3
4
5
6
7
8
9
10
11
12












-
-
-
-
-
-
-
-
-
-
-
-
return {
    isVirtual: true,
    foo: true,
    cacheIt: false,
    counter: 0,
    load: proc(name){
        print(__FLC,"Demo plugin: argv=",argv);
        this.lastArgs = argv;
        ++this.counter;
        return this;
    }
};

Deleted bindings/s2/require.d/plugins/dll.s2.

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
































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   Uses s2.loadModule() to load s2 loadable modules. Usage:

   dll!moduleName?options...

   The return value: if moduleProperty option is NOT provided
   and the module produces only a single property (as most
   do), that property's value is returned, otherwise the whole
   "namespace object" passed to the module init routine is
   returned. That possibly has potential backfire cases, but
   none affecting any current modules.

   Options:

   ACHTUNG: Be aware that require() caching will hold the
   first result from this plugin's load() call (and
   generically _not_ caching DLL results is dangergous), so
   any options provided to this function via a require() call
   here are only honored on the first call. On subsequent
   calls require() will use the cached result.

   entryPoint=string: if set, it is used as the second param
   to s2.loadModule().

   moduleProperty=string: if set, the result of the call is
   the given property from the DLL module. The majority of
   DLLs install only a single property, and this function will
   return only that property in such cases (as described
   above).
*/
const mod = {
    cacheIt: true /* _not_ caching DLL-loaded resources is a
                     Reall Bad Idea, as the DLL can change
                     between invocations, leaving us with
                     different binary signatures. Also,
                     functions injected via DLLs need access
                     to the C side, which disappears if a DLL
                     is closed. */,
    prefix: ('string' === typename (var dllTmp = s2.getenv('S2_MODULE_PATH')))
        ? dllTmp.split(dllTmp.indexOf(';') >= 0 ? ';' : ':')
    : ['.'],
    suffix: ('string' === typename (dllTmp = s2.getenv('S2_MODULE_EXTENSIONS')))
        ? dllTmp.split(dllTmp.indexOf(';') >= 0 ? ';' : ':')
    : ['.so','.dll'],
    load: function(name,opt){
        var rc;
        affirm name && 'string' === typename name;
        rc = (opt && opt.entryPoint)
            ? s2.loadModule(name, opt.entryPoint, {})
            : s2.loadModule(name, {});
        affirm rc;
        if(opt && opt.hasOwnProperty('moduleProperty')){
            rc = rc[opt.moduleProperty];
        }else if(var keys=rc.propertyKeys();
                 1===keys.length()){
            rc = rc[keys.0];
        }
        return rc;
    }
};

mod.prefix.push( requireS2.home + s2.io.dirSeparator + 'dll'  );

return mod;

Deleted bindings/s2/require.d/plugins/fsl/blob.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22






















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   require() plugin which fetches a blob from the repo db. It
   interprets its argument as a symbolic blob name (anything
   libfossil's symbol-to-RID conversions support).
*/
return requireS2(
    ['fsl/context',
     'fsl/db/repo' // so that we are ensured that repo is opened by this point
    ],         
    proc(fsl){
        return {
            isVirtual: true,
            _F: fsl,
            load: function(sym){
                return this._F.loadBlob(sym)
            }
            /* If we wanted to be really pedantic, we would
               require(['fsl/db/repo']...) inside load().
               Overkill.
            */
        }
    });

Deleted bindings/s2/require.d/plugins/fsl/manifest.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21





















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   require() plugin which fetches a Manifest from the repo db,
   in the form of an Object which mimics the fsl_deck structure
   (see Fossil.Context.loadManifest()).

   It interprets its argument as a symbolic blob name (anything
   libfossil's symbol-to-RID conversions support).
*/
return requireS2(
    ['fsl/context',
     'fsl/db/repo' // so that we are ensured that repo is opened by this point
    ],
    proc(fsl){
        return {
        isVirtual: true,
            _F: fsl,
            load: function(sym){
                return this._F.loadManifest(sym);
            }
        }
    });

Deleted bindings/s2/require.d/plugins/fsl/wikiByName.s2.

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

























-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   require() plugin which fetches a wiki page from the repo db,
   in the form of an Object which mimics the fsl_deck structure
   (see Fossil.Context.loadManifest()).

   It interprets its argument as a wiki page name and throws if not
   found.
*/
return requireS2(['fsl/context', 'fsl/db/repo'], proc(fsl, repo){
    return {
        isVirtual: true,
        _F: fsl,
        _sql: <<<_SQL
        SELECT x.rid FROM tag t, tagxref x
        WHERE x.tagid=t.tagid 
        AND t.tagname='wiki-%1$q'
        ORDER BY mtime DESC LIMIT 1 _SQL,
        load: function(name){
            return this._F.loadManifest(
                this._F.db.selectValue(this._sql.applyFormat(name))
                ||| throw "No wiki such wiki page: "+name
            );
        }
    };
});

Deleted bindings/s2/require.d/plugins/json-cached.s2.

1
2
3
4




-
-
-
-
/** A clone of the json plugin, except that cacheIt is set to true. */
const mod = requireS2.getPlugin('json').copyPropertiesTo({});
mod.cacheIt = true;
return mod;

Deleted bindings/s2/require.d/plugins/json.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21





















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/** A require.s2 plugin which loads file content as JSON,
    using s2.json.parseFile(). It does _not_ cache its
    results. Use the json-cached plugin for the same
    effect but with cached results.
*/
return {
    cacheIt: false /*
                     We _generally_ don't want these cached because
                     they are "probably" used only once in most cases.

                     Use the 'json-cached' plugin if you want cached
                     JSON files.
                   */,
    suffix: ['.json'],
    prefix: // Append '/json' to all paths in the default plugin's path
        ((const p = [], suffix = s2.io.dirSeparator+'json'),
         requireS2.getPlugin('default').prefix.eachIndex(proc(v){
             p[] = v+suffix;
         }), p),
    load: proc(file){return s2.json.parseFile(file)}
};

Deleted bindings/s2/require.d/plugins/placeholder.s2.

1
2
3
4
5
6






-
-
-
-
-
-
/* a placeholder. you'll know if you need it. */
return {
    isVirtual: true,
    cacheIt: true,
    load: proc(){ return {} }
};

Deleted bindings/s2/require.d/plugins/tmpl-compiled.s2.

1
2
3
4
5
6
7
8
9
10
11
12












-
-
-
-
-
-
-
-
-
-
-
-
/**
   Works like the tmpl plugin, but passes the file's contents
   throughs s2.tmpl() before returning it.
*/
return {
    cacheIt: false,
    // prefix: uses the defaults
    suffix: ['.tmpl'],
    load: proc(fn){
        return s2.tmpl(s2.Buffer.readFile(fn))
    }
};

Deleted bindings/s2/require.d/plugins/tmpl.s2.

1
2
3
4
5
6
7
8
9
10










-
-
-
-
-
-
-
-
-
-
/** Loads file content as a buffer, intended
    for use with .tmpl files (for s2.tmpl()). */
return {
    cacheIt: false,
    // prefix: uses the defaults
    suffix: ['.tmpl'],
    load: function(name){
        return s2.Buffer.readFile(name);
    }
};

Deleted bindings/s2/require.d/pubsub.s2.

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





















































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
  A require.s2 module implementing a basic publish-subscriber
  manager.

  Example usage:

  http://fossil.wanderinghorse.net/repos/cwal/index.cgi/finfo?name=s2/require.d/pubsub.test.s2

  Returns a class, each instance of which manages its own list
  of publishers and subscribers.
*/
return {
    __typename: 'PubSub',
    prototype: undefined,
    
    /**
       Constructor for use with the 'new' keyword.
       
       var pubber = new thisObj();
       assert pubber inherits thisObj; // this will hold

       Each instance maintains its own, independent list of
       subscriptions.
    */
    __new: proc(){
        this.reset();
    },

    /**
       Subscribes a callback to events published for a given key.
       key may be of any value type. func must be-a Function.
       
       Returns a unique-per-subscription value (of an unspecified
       type) which can be passed to unsub() to opt out of a
       subscription. For a permanent subscription, simply ignore the
       result value.
    */
    sub:proc callee(key, func){
        affirm typeinfo(iscallable func) /* is Function-like */;
        const m = (this.$map[key] ||| (this.$map[key]={prototype:null})),
              i = enum{k:key}.k;
        m[i] = func;
        return i;
    },

    /**
       Expects id to be a value returned by this.sub() and
       unsubscribes a subscriber registered with that id.

       Returns this object.
    */
    unsub: proc(id){
        affirm typeinfo(isunique id);
        (const c = this.$map[id.value]) && unset c[id];
        return this;
    },

    /**
       Publishes an event to all subscribers (if any) of the key
       (event type) given as the first argument.
       Important notes:

       a) Subscribers are notified in an UNSPECIFIED and (very)
       POSSIBLY CHANGING order.

       b) Any arguments given after the event key are passed to each
       subscriber callback function, in the order they are passed in
       here. e.g. if this function is passed ('foo', 'bar', 1) then
       each subscriber will be called with ('bar', 1). Pedantic
       side-note: each callback gets its own copy of the arguments
       array, to avoid unintended side-effects if a callback modifies
       its argv.

       Returns this object (for lack of a better option).

       It propagates any exceptions thrown by a subscriber, and any
       pending subscribers won't get called.

       Pedantic side-node: each subscriber gets its own copy of the
       arguments array, so it's safe to change it or keep a reference
       to it in the subscribers without affecting others.
    */
    pub:proc(/*key, event args...*/){
        return this.pubWithThis(argv.shift(), undefined, @argv);
    },

    /**
       A special case of pub() useful in certain code constellations.

       For each listener of event type e, its callback is called using
       t as the callback's "this" and passing on all arguments after
       the second. If t is undefined then each callback is its own
       'this' (as is conventional for s2).

       Returns this object.

       See pub() for more details.
    */
    pubWithThis: proc(e,t/*...*/){
        const m = this.$map[e] ||| return this;
        affirm typeinfo(isobject m);
        argv.shift(2);
        foreach(m=>k,f) f.apply(t?:f, argv.slice());
        return this;
    },

    /**
       Removes all subscriptions for the given key or (if no arguments
       are passed) all subscriptions for all keys.
       
       Returns this object (for lack of a better option).
    */
    reset:proc(/*key*/){
        argv.# ? unset this.$map[argv.0] : this.$map = {prototype:null};
        return this;
    }
};

Deleted bindings/s2/require.d/pubsub.test.s2.

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























































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* Short demo of the pubsub require.s2 module. */
requireS2(
    ['nocache!pubsub'],
    proc(P){
        const p = new P();
        assert 'object' === typename p.$map /* testing internals */;
        print(__FLC, 'pubsub:');
        foreach(p=>k,v) print('\t',k,v);
        var counter = 0;
        const id = p.sub('hi', proc(){
            ++counter;
            print(__FLC,'hi handler 1',argv);
        });
        const id2 = p.sub('bye', proc(){
            ++counter;
            print(__FLC,'bye handler',argv);
        });

        const id3 = p.sub('hi', proc(){
            ++counter;
            print(__FLC,'hi handler 2',argv);
        });

        print(__FLC, 'subscription IDs =',id, id2, id3);
        print("Publishing events...");
        p.pub('hi',0, __FLC);
        assert 2 === counter;

        p.pub('nope',1, __FLC);
        assert 2 === counter;

        p.pub('bye', 2, __FLC);
        assert 3 === counter;

        p.pub('hi',3, __FLC);
        assert 5 === counter;
        p.unsub(id);
        p.pub('hi',4, __FLC);
        assert 6 === counter;
        p.pub('bye', 5, __FLC);
        assert 7 === counter;
        print(__FLC, 'done');

        var p2 = new P();
        assert p2.$map;
        assert p2.$map !== p.map;
        assert p2 !== p;
        assert !(p2 inherits p);
        assert p2 inherits p.prototype;
        assert p2.sub === p.sub;

        print(__FLC, 'really done');
        return p;
    }
);

Deleted bindings/s2/require.d/require.s2.

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















































































































































































































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   Implements a basic script module loading API similar to
   require.js (http://requirejs.org).

   In short, this module acts as a loader for arbitrary other content,
   be it s2 script code, raw file content, or "virtual" (non-file)
   content plugged in by the user. Its usage is modelled very much of
   off requirejs, and it will look familiar to anyone who has used
   that library.

   See the API docs in this module for more details. For even more
   details (or other details, in any case), see this public Google
   Doc:

   https://docs.google.com/document/d/14gRP4f-WWgWNS64KM_BI7YjQqBLl4WT3jIrmNmFefYU/view

   Example usage:

   Assume the files r1.s2 and r2.s2 live somewhere in this module's
   search path and that both return an Object with a foo() method.
   This is how we might import and use them:

   @code
   const R = import('path/to/require.s2');

   R(['r1', 'r2'], proc(r1, r2){
     r1.counter = r2.counter = 1;
     print(__FLC,r1);
     print(__FLC,r2);
     r1.foo();
     r2.foo();
     ++r1.counter;
     ++r2.counter;
   });

   R(['r2', 'r1'], proc(r2, r1){
     assert 2 === r2.counter;
     assert 2 === r1.counter;
     // ^^^ because imported script results are cached
   });
   @endcode

*/
affirm s2.fs;
affirm s2.Buffer;
affirm typeinfo(isfunction s2.getenv);
affirm typeinfo(isfunction s2.fs.realpath);
affirm typeinfo(isfunction s2.Buffer.readFile);
const  /* saves some var lookup time */
s2 = s2,
realpath = s2.fs.realpath,
getenv = s2.getenv,
cliFlags = (s2.ARGV ? s2.ARGV.flags : 0) ||| {prototype:null}
;

const importFileBuffer = s2.Buffer.readFile;
const importFileText = proc(fn) using(importFileBuffer) {
    return importFileBuffer(fn).takeString();
};
/**
   If a string-type CLI flag OR environment variable (in that
   order) with the given name is found, this function returns an
   array of its contents, tokenized using
   s2.PathFinder.tokenizePath(). If no flag/environment variable
   is found, or a CLI flag with a non-string value is found,
   undefined is returned.

   Interpretation of the arguments is as follows:

   - If only 1 argument is provided, this routine looks for both a
   CLI flag and environment var (in that order) with that name.

   - If 2 arguments are provided, the 1st is checked as a CLI flag
   and the 2nd as an environment variable (in that order).
*/
const pathFromEnv = proc(f,e=f){
    const p = F[f] ||| E(e);
    return typeinfo(isstring p)
        ? P.tokenizePath(p)
        : undefined;
} using {
    E: getenv,
    P: s2.PathFinder,
    F: cliFlags
};

/**
   Internal utility to convert almost-URL-encoded plugin options
   to some value form. Treats non-existent values (===undefined)
   as a boolean true flag.
*/
const convertQValue = proc(v){
    undefined === v && return true;
    'true' === v && return true;
    'false' === v && return false;
    'null' === v && return null;
    return 0.parseNumber(v) ?: v;
};

/**
   Module-internal cache of import() responses.
*/
const modCache = new s2.Hash(0.nthPrime(15));

/**
   The main "require" module object.
*/
const mod = {
    __typename: 'require.s2',
    /**
       Used for doing file lookups. Its search path and extensions
       get continually swapped out by require() and friends to use
       whatever search path the current context requires.
    */
    pf: new s2.PathFinder(),
    pathFromEnv,
    /* Exposed for corner-case use by plugins. */
    modCache
};

/**
   This "default" plugin (A) acts as the default
   implementation for fetching require()'d files, (B) is
   where the interface for other plugins is documented.
*/
const PluginModel ={
    /**
       Clear the prototype - we don't really need it for
       this object.
    */
    prototype: undefined,
    /** If true, require() does not search for the filename it
        is given. (It is assumed to be some form of virtual
        unique identifier, and may be empty.)
    */
    isVirtual: false,
    
    /**
       cacheIt tells require whether or not to cache the results
       of requests to this plugin. If it is true, then this.load()
       will only be called for the first request, and its result
       will be returned on subsequent requests. EXCEPTION: if
       a plugin is called with arguments (see load()) the cache
       is ignored/bypassed.

       The default is expected (by client code) to always be true
       for the default plugin!
    */
    cacheIt: true,

    /**
       For file-based plugins, prefix specifies the search
       directories (array of strings, excluding any trailing slash
       on the directory parts unless they refer to the root
       directory).
    */
    prefix: pathFromEnv('s2.require.path', 'S2_REQUIRE_PATH') ||| ['.'],
    /**
       For file-based plugins, suffix specifies the search
       extensions (array of strings, including any '.' part,
       e.g. use ".foo" instead of "foo" unless you're searching
       for partial extensions).
    */
    suffix: pathFromEnv('s2.require.extensions', 'S2_REQUIRE_EXTENSIONS') ||| ['.s2'],

    /**
       Called by require() to "load" a given module.  load() must
       accept a module name, "load" that module, for a given
       definition of "load", and return its contents or
       result. (It need not actually load anything from anywhere,
       much less a file: it might simply return a predefined
       object or other value.)

       In the call context, 'this' will be this plugin
       configuration object.

       The first argument is the "name" part of the string passed
       to require.

       The second argument part is either undefined or an object:
       if the file string contains '?', anything after the ? is
       assumed to be encoded in the form a=b&c=d... (URL-like, but
       _without_ the URL encoding), and that gets transformed into
       an Object of key/value pairs before passing them to this
       function (the default value, if no "=" is provided, is boolean
       true). HOWEVER: passing any arguments after '?' will
       cause caching to be bypassed, because the arguments presumably
       change how the plugin works. Note that if nothing follows
       the '?' then no options object is created.
       
       Caveats regarding the opt parameter:

       1) all of the values in the opt object will originally be
       of type string, but numeric-looking strings and the strings
       ("true", "false", "null") get converted to their
       script-native type.

       2) Only one level of opt object is supported, not nested
       objects, arrays, etc.
    */
    load: proc(f/*, opt*/) using({
        b: s2.Buffer.readFile,
        r: mod
    }) {
        return b(f).evalContents(f,{requireS2: r});
    }
    /* Reminder to self: we cannot simply alias to s2.import() because
       any extra non-string arguments would be passed to it (doing the
       wrong thing). Reminder #2: adding comments outside of function
       bodies, instead of if them, uses less memory, since they don't
       get allocated as part of the function body. That's especially
       significant when the length of the comments outweigh the rest
       of the source, as in this case. */
};

/**
   The funkiness we do with 'this' vs 'mod' in may places places
   below is so that this stuff still works when clients copy the
   returned module into a property of another object. i.e. the following
   usages are equivalent:

   var x = mod;
   obj.x = mod;
   x([...],...);
   obj.x([...],...);

   Both calls (because of these extra bindings) use the same "this"
   inside the call (the local 'mod' symbol), which is important for
   identical/correct semantics in both uses.
*/

/**
   All the plugins are stored in this object, and any number may
   potentially be loaded (and added here) via client-side use.
*/
mod.plugins = {
    /**
       The name 'default' is magic and assumes certain
       plugin-level defaults. It also provides the default .prefix
       and .suffix properties for other non-isVirtual (file-using)
       plugins (used only if a given plugin does not define them
       itself).
    */
    default: PluginModel,
    /**
       Works just like the default plugin but bypasses
       the cache.
    */
    nocache: {
        cacheIt: false,
        load: PluginModel.load
    },
    /** Loads file content as a string. */
    text: {
        cacheIt: false,
        // prefix: uses the defaults
        suffix: ['.txt', '.s2', '.html'],
        load: importFileText
    },
    /** Loads file content as a buffer. */
    buffer: {
        cacheIt: false,
        // prefix: uses the defaults
        suffix: ['.txt', '.s2', '.html'],
        load: importFileBuffer
    }

    /**
       Demonstration of a "virtual" plugin (one which does not
       use files).
       
       virtualDemo:{
       isVirtual: true,
       cacheIt: true, // not strictly necessary, plugin-dependent
       load: proc f(fn,opt){
       print("Example of a 'virtual' handler. Arguments:",argv);
       return opt ||| this;
       }
       }
    */

};

/**
   Installs one or more plugins into this object.

   If called with an initial string arugment then:

   - Adds a new plugin. name must be a string and pluginObj must
   be an object which follows the PluginModel interface. Returns
   pluginObj on success.

   If called with a non-string argument then:

   - name is assumed to be a container. Each of its properties is
   assumed to be a module definition, and each one gets installed
   via a call back into this function, passing it each key and
   value. Returns this object.

   Throws on error.
*/
mod.addPlugin = proc callee(name, pluginObj){
    if(name && !pluginObj && typeinfo(iscontainer name)){
        foreach(name=>k,v) callee(k,v);
        return this;
    }
    affirm 'string' === typeinfo(name name);
    affirm name /* name must be non-empty */;
    affirm typeinfo(iscontainer pluginObj) && typeinfo(iscallable pluginObj.load);
    mod.plugins[name] = pluginObj;
    return mod;
} using(mod);

/**
   Searches for a plugin script by using this.plugins.default's
   search path and the name ("plugins/"+name). Returns undefined
   if not found, else the result of s2.import()'ing that file. It
   does not check if the plugin is already installed, but installs
   (or overwrites) it into mod.plugins[name].
*/
mod.searchAndInstallPlugin = proc(name){
    mod.pf.prefix = mod.plugins.default.prefix;
    mod.pf.suffix = mod.plugins.default.suffix;
    const fn = mod.pf.search('plugins/'+name);
    return fn
        ? mod.plugins[name] = ((const requireS2=mod), import(false,R(fn)))
        : undefined;
} using {mod, R: realpath},

/**
   If the given plugin name is already installed, it is returned,
   otherwise it is sought for, installed, and returned. An
   exception is thrown if it cannot be found or if installing it
   fails.
*/
mod.getPlugin = proc(name) using(mod) {
    return mod.plugins[name] ||| mod.searchAndInstallPlugin(name);
},

/**
   Attempts to resolve a file name using a given plugin's search
   path. basename is the unresolved name of the file to search for
   and forPlugin is either a plugin object or the name of a
   plugin. The search path/extensions use are those of the given
   plugin or (if that plugin has none), the default plugin. If the
   given plugin has the isVirtual flag, no search is performed and
   the undefined value is returned.
*/
mod.resolveFilename = proc(basename,forPlugin='default') using(mod){
    const pConf = typeinfo(isobject forPlugin) ? forPlugin : mod.getPlugin(forPlugin);
    affirm typeinfo(isobject pConf);
    const pf = (pConf.isVirtual ? undefined : mod.pf) ||| return;
    pf.prefix = pConf.prefix ||| mod.plugins.default.prefix;
    pf.suffix = pConf.suffix ||| mod.plugins.default.suffix;
    return pf.search(basename,0);
};

/**
   Given a base filename...

   1) If the given name does not contain a '!' character,
   it searches for an exact-match name in the cache. If it
   finds one, it returns that value.

   2) It searches for the file using the configured search
   paths/extensions (from this.plugins). If found, it is passed to
   the import() function specified for the import type (see
   below).

   By default this.plugins.default is used to search for and
   import the file. If a "special" type of name is provided to
   this function, though (meaning the base name looks like
   with "SOMETHING!basename"), then this.plugins[SOMETHING] is
   used (if set), which may change the caching behaviour and
   how the content of the file is interpreted.

   Depending on the configuration options, requests might get
   cached. Subsequent calls which expand to the same file name
   will return the same (cached) result value on each
   subsequent call.
*/
mod.import = proc(basename){
    affirm 'string' === typeinfo(name basename);
    if(basename.indexOf('!')<0 && const c = C#basename){
        return c;
    }
    var pluginName;
    /* Check for pluginName!... */
    if((var ndx = basename.indexOf('!'))>0){
        pluginName = basename.substr(0,ndx);
        basename = basename.substr(ndx+1);
    }
    pluginName || (pluginName ='default');
    /* Configuration for this plugin... */
    const pConf = mod.getPlugin(pluginName);
    pConf || throw "Could not load plugin '"+pluginName+"'.";
    const pf = pConf.isVirtual ? undefined : mod.pf /* PathFinder */;
    if(pf){
        // Set up/reset file lookup paths/suffixes...
        pf.prefix = pConf.prefix ||| mod.plugins.default.prefix;
        pf.suffix = pConf.suffix ||| mod.plugins.default.suffix;
    }
    /* Treat ?a=b&c=d... almost like a URL-encoded
       as query string, but without the URL encoding.
    */
    var qArgs, useCache = pConf.cacheIt;
    if(basename.indexOf('?')>=0){
        /* Parse args. If any are provided, bypass the cache. */
        const sp = basename.split('?',2);
        basename = sp.0;
        (qArgs = Q(sp.1)) && (useCache = false);
    }
    /* Find the file, if necessary... */
    var fn = pConf.isVirtual
        ? basename
        : (basename ? pf.search(basename, 0) : false)
        ||| throw "Plugin '%1$s' cannot find '%2$s' in search path %3$J with extensions %4$J.".
        applyFormat(pluginName, basename, pf.prefix, pf.suffix);
    // expand to the fully qualified path for non-virtual plugins...
    pConf.isVirtual || (fn = P(fn));
    //print(__FLC,"fn=",fn,"useCache=",useCache,"qArgs =",qArgs,pluginName,pConf);
    //print(__FLC,'pConf.cacheIt=',pConf.cacheIt,', fn=',fn);
    //print(__FLC,cache.toJSONString(2));
    const requireS2 = mod /* public API symbol, potentially
                             needed by anything which
                             uses anyPlugin.load() */;
    return useCache
        ? (const k =
                 (pluginName ? pluginName+'!'+fn : fn),
                 cacheCheck = (C # k))
        ? cacheCheck
        : C.insert(k, pConf.load(fn, qArgs))
    : pConf.load(fn, qArgs);
} using(mod, {
    C: modCache,
    P: realpath,
    Q: proc(str) using {q:convertQValue} {
        str || return str;
        var r;
        foreach(@str.split('&')=>v){
            const s = v.split('=',2);
            (r ||| (r = {prototype:null}))[s.0] = s.1 ? q(s.1) : (v.indexOf('=')>0 ? s.1 : true);
        };
        return r;
    }
});

/**
   For each base filename in the given array/tuple, this function
   calls this.import(basename) and appends the results to a new
   array.  Returns an array of the results to each import() call,
   the order of the array's elements being the same as the calls
   to import(). Throws if list is not an array or is empty or if
   loading any resources fails. Propagates any exceptions.
*/
mod.importList = function(list) using(mod) {
    affirm typeinfo(islist list) /* expecting an Array or Tuple */;
    affirm !list.isEmpty() /* expecting a non-empty list of module names */;
    const imps = [];
    foreach(@list=>v) imps.push(mod.import(v));
    return imps;
};

/**
   Imports a list of script modules and optionally calls a
   callback after loading all of them.

   list is an array of strings - script file base names to import.
   func is an optional function or code string which gets called
   resp. eval'd after importing all of the scripts. If it's a
   function, it is passed one argument for each entry in the list,
   in the same order they are imported. If it is a code string
   then it is eval'd in a scope with the array 'modules' defined
   to the resolved list of modules.

   In either case, it declares the const symbol requireS2 to be
   the require() module, so that these callbacks may recursively
   invoke require() via a call to requireS2(). (It is often useful
   to do so, it turns out, and this consolidates the convention
   across modules originally written for different code bases.)

   Returns the result of calling the function (if any) or the
   array of loaded modules (if passed no function/string).

   Example:

   assert 42 === thisObj(
   ['module1', 'module2'],
   function(mod1, mod2){
   print('result of module1 import:' mod1);
   print('result of module2 import:' mod2);
   return 42;
   }
   );


   _Potential_ TODOs:

   - If passed a non-string value where a name is expected, use it
   as-is as the result of the loading. i suspect we might have
   some interesting uses for that, but want a use case before trying
   it out.
*/
mod.require = function(list, func) using(mod) {
    func && affirm (typeinfo(iscallable func) || typeinfo(isstring func));
    list = mod.importList(list);
    func || return list;
    if(typeinfo(iscallable func)){
        const requireS2=mod, s2 = s2;
        return func.apply(func, list);
    }else{
        return func.evalContents('require() script',{
            s2: s2,
            requireS2: mod,
            modules: list
        });
    }
};

/**
   Installs a cached entry for the given module name, such that
   future calls to import() or require() which use that exact name
   will return the given result object. Note that the name need
   not be filesystem-friendly. e.g. "<my-identifier>" is perfectly
   legal. The only limitation is that it "really should not"
   contain an exclamation point, as that may confuse import()
   because that character is used to denote a plugin.
*/
mod.installModule = function(name, result) using(modCache){
    modCache.insert(name, result);
    return this;
};


// Try to determine some useful directories to search for scripts in...
if(typeinfo(isstring var d = cliFlags['s2.require.home'])){
    mod.home = realpath(d);
    mod.plugins.default.prefix.push( mod.home );
}
else if((d=(cliFlags['s2.home']|||getenv('S2_HOME')))
        && (d=realpath(d+'/require.d'))){
    mod.plugins.default.prefix.push( mod.home = d );
}
if((var d = __FILEDIR ? realpath(__FILEDIR) : 0)
   && (mod.home !== d)){
    mod.plugins.default.prefix.push( d );
    mod.home || (mod.home = d);
}
/* __FILEDIR may be a synthetic __FILE name
   (e.g. via eval or Buffer.evalContents()) */
mod.home || (mod.home="");

/* We set mod.require.home so that plugins can construct paths via
   requireS2.home. */

/* Make it so that call()ing this object calls mod.require() */
mod.prototype = mod.require /** Holy cow! We've just inherited our
                                own member function. */;
mod /* script result */;

Deleted bindings/s2/require.d/require.test.s2.

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


















































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

affirm requireS2;
affirm 'string' === typename requireS2.home;
affirm requireS2.home.indexOf('/')>-1 || requireS2.home.indexOf('\\')>-1;
//print(__FLC,'requireS2.home =',requireS2.home);
/**
   A quite incomplete test of require.s2. Since this cannot even
   be loaded by require.s2 if require.s2 isn't at least basically
   working, just getting here already tells us much.
*/
requireS2.addPlugin('dummy',{
    isVirtual: true,
    cacheIt: false,
    load: proc(n,opt){
        return {
            name: n,
            opt: opt
        };
    }
});

requireS2.addPlugin({
    dummy2:{
        isVirtual: true,
        chachIt: true,
        load: proc(n,opt){
            return 1;
        }
    },
    dummy3:{
        isVirtual: true,
        chachIt: true,
        load: proc(n,opt){
            return 0;
        }
    }
});

requireS2(['dummy!fred?a=1'],proc(obj){
    affirm 'fred' === obj.name;
    affirm 1 === obj.opt.a;

    assert true === requireS2(['dummy!barny', 'dummy2!', 'dummy3!'],proc(d, d2, d3){
        assert 'barny' === d.name;
        assert !d.opt;
        assert 1 === d2;
        assert 0 === d3;
        return true;
    });
});

Deleted bindings/s2/require.d/time.s2.

1
2
3
4
5





-
-
-
-
-
/* a require.s2 module which "hides" s2.time via the module
   interface. It could optionally load 'dll!mod_time', but that's
   an internal impl detail.
*/
return s2.time;

Deleted bindings/s2/require.d/tmpl.s2.

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




























































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/**
   The tmpl module (as distinct from the tmpl plugin!) provides
   utilities for working with s2.tmpl().

*/
return {
    /**
       "Processes" a s2.tmpl() template as follows...

       The first argument is a tmpl()-compiled script (of type Buffer)
       or a non-compiled script of type String (in which case this
       function compiles it and uses the original value as the 3rd
       parameter).

       The second parameter is an optional container holding key/value
       pairs which get imported into the current scope before
       evaluating the script. This allows one to easily create
       mini-templates for use in loops and such. If you _have_
       to pass a value but don't have an object, any falsy value
       will do.

       The final argument is intended to hold the uncompiled script
       and is only used in error reporting, and is stored in any
       exception propagated via evaluating a template. It is ignored
       when the first argument has a typename of 'string'.
    */
    process: proc(template, opt, tmplUncompiled){
        if(typeinfo(isstring template)){
            tmplUncompiled = template;
            template = this.compile(template);
        }
        affirm typeinfo(isbuffer template);
        if(const ex = catch template.evalContents(opt|||{})){
            ex && throw {
            message: "Error evaluating compiled template (location info is relative to the compiled script).",
            exception: ex,
                template: {
                    compiled: template.toString(),
                    uncompiled: tmplUncompiled ? tmplUncompiled.toString() : undefined
                }
            };
        }
    },

    processFile: proc(fn, opt){
        return this.process( this.load(fn, true), opt );
    },

    /**
       A proxy for s2.tmpl().
    */
    compile: s2.tmpl,
    /**
       Uses the tmpl! plugin to load the given file
       and optionally compile it using this.compile()
    */
    load: proc(fn, compile){
        return requireS2([(compile ? 'tmpl-compiled!' : 'tmpl!')+fn]).0;
    } using (requireS2)
};

Deleted bindings/s2/require.d/tmpl.test.s2.

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










































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* A brief test of the 'tmpl' require.s2 module. */
requireS2(
['ob',
 'nocache!tmpl'
 // bug:  ^^^^ reading a directory (by accident) is reported as OOM.
 // Fixed by not allowing PathFinder to resolve directory names, but
 // that support is missing for Windows (patches welcomed).
],
proc(ob, t){
    const src = 'a=<% a %>, b=<%b%>, c=<%c%>\n';
    const obLevel = ob.level();
    var str;
    ob.push();
    var ex = catch{
        t.process(src,{
            a:'hi',
            b:'there',
            c:'world'
        });
        str = ob.pop(-1);
    };
    while(ob.level()>obLevel) ob.pop();
    ex && throw ex;
    affirm "a=hi, b=there, c=world\n" === str;

    scope {
        const src2 = '<%a%>,<%b%>,<%c%>\n',
          compiled = t.compile(src2),
          opt = { a:-1, b:0, c:1 }
          ,TMPLOUT = proc(){} /* eval'd template uses this func, if defined */
        ;
        const XYZ = 'hi';
        t.process('<% XYZ %>\n', null);
        for(var i = 0; i < 5; ++i, ++opt.a, ++opt.b, ++opt.c ){
            t.process(compiled, opt, src);
        }
        affirm 4==opt.a;
        affirm 5==opt.b;
        affirm 6==opt.c;
    };
    print(__FLC,'done!');
});

Deleted bindings/s2/s2_amalgamation.c.

more than 10,000 changes

Deleted bindings/s2/s2_amalgamation.h.

more than 10,000 changes

Deleted bindings/s2/shell2.c.

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

















































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/**
   s2sh2 - a cleanup of s2sh.

   s2sh2 is functionally *nearly* identical to s2sh, the primary
   difference being that s2sh2 has more conventional/cleaned-up CLI
   argument handling. Many of the flags which are toggles in s2sh have
   only a single form in s2sh2, to turn something on or off, but not
   toggle it.

   Other minor differences:

   - $2 is a UKWD referring to the global s2 object.
*/

#define S2SH_VERSION 2
#include "shell_common.c"

/**
   gcc bug: if this function is marked static here, it does
   not recognize that shell_common.c uses it and declares that
   it's unused.
*/
static CliAppSwitch const * s2sh_cliapp_switches(){
  static const CliAppSwitch cliAppSwitches[] = {
  /* {opaque, dash, key, value, brief, details, callback, reserved} */
  /** Fills out most of a CliAppSwitch entry */
#define F6(IDFLAGS,DASHES,KEY,VAL,BRIEF,PFLAGS)     \
  {IDFLAGS, DASHES, KEY, VAL, BRIEF, 0, 0, PFLAGS}
  /** Continues the help text for the previous entry. */
#define FCONTINUE(LABEL) {SW_CONTINUE, 0, 0, 0, LABEL, 0, 0, 0}
  /** Adds an "additional info" line, not indented. */
#define FINFOLINE(LABEL) {SW_INFOLINE, 0, 0, 0, LABEL, 0, 0, 0}
  /** -short | --long[=VALUE]*/
#define FX(IDFLAGS,SHORT,LONG,VALUE,BRIEF,PFLAGS)   \
  F6(IDFLAGS,2,LONG,VALUE,BRIEF,PFLAGS),            \
  F6(IDFLAGS,1,SHORT,VALUE,0,PFLAGS)
  /** -short, --long on/off switches ...*/
#define F01(IDFLAGS,SHORT,LONG,BRIEF)                       \
  {IDFLAGS+1, 2, LONG,   0, "Enable "  BRIEF, 0, 0, 0},     \
  {IDFLAGS+1, 1, SHORT,  0, "Enable "  BRIEF, 0, 0, 0},     \
  {IDFLAGS,   2, "no-"LONG, 0, "Disable " BRIEF, 0, 0, 0},  \
  {IDFLAGS,   1, "no"SHORT, 0, "Disable " BRIEF, 0, 0, 0}
  /** Start the next group (for --help grouping). */
#define GROUP(GID,DESC) {GID, 0, 0, 0, DESC, 0, 0, 0}

  /***********************************************************************/
  GROUP(SW_GROUP_0,0),

  /**
     Keep these arranged so that flags with short and long forms are
     ordered consecutively, long one first. The help system uses that to
     collapse the flags into a single listing. In such cases, the help
     text from the first entry is used and the second is ignored.
  */
  FX(SW_HELP, "?", "help", 0,
     "Send help text to stdout and exit with code 0.", 0),
  FCONTINUE("The -v flag can be used once or twice to get more info."),

  FX(SW_VERBOSE,"v","verbose",0,
     "Increases verbosity level by 1.", 0),

  FX(SW_VERSION,"V","version",0,
     "Show version info and exit with code 0. ", 0),
  FCONTINUE("Use with -v for more details."),

  FX(SW_ESCRIPT,"e","eval","SCRIPT",
     "Evaluates the given script code in the global scope. "
     "May be used multiple times.", CLIAPP_F_SPACE_VALUE),

  FX(SW_INFILE,
     "f","file","infile",
     "Evaluates the given file. The first non-flag argument is used "
     "by default.", CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  FX(SW_OUTFILE, "o", "output", "outfile",
     "Sets cwal's output channel to the given file. Default is stdout.",
     CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  /***********************************************************************/
  GROUP(SW_GROUP_1, "Less-common options:"),

  FX(SW_ASSERT_1,"A", "assert", 0, "Increase assertion trace level by one: "
     "1=trace assert, 2=also trace affirm.",0),

  FX(SW_INTERACTIVE,"i", "interactive", 0,
     "Force-enter interactive mode after running -e/-f scripts.",0),

  FX(SW_AUTOLOAD_0,"I","no-init-script", 0,
     "Disables auto-loading of the init script.",0),
  FCONTINUE("Init script name = same as this binary plus \".s2\" "),
  FCONTINUE("or set via the S2SH_INIT_SCRIPT environment variable."),
  
  /***********************************************************************/
  GROUP(SW_GROUP_2, "Esoteric options:"),

  FX(SW_METRICS, "m", "metrics", 0,
     "Show various cwal/s2 metrics before shutdown.",0),
  FCONTINUE("Use -v once or twice for more info."),

  F01(SW_MOD_INIT_0, "mi", "module-init",
      "automatic initialization of static modules."),

  F6(SW_SHELL_API_1,2,"s2.shell",0,
     "Forces installation of the s2.shell API unless trumped by --cleanroom.",0),
  FCONTINUE("By default s2.shell is only installed in interactive mode."),

  F6(SW_TRACE_CWAL,2,"trace-cwal", 0,
     "Enables tremendous amounts of cwal_engine "
     "tracing if it's compiled in.",0),
  
  F01(SW_SCOPE_H0, "hash", "scope-hashes",
      "hashes for scope property storage (else objects)."),

  FX(SW_HIST_FILE_1,
     "h", "history", "filename",
     "Sets the interactive-mode edit history filename.",
     CLIAPP_F_SPACE_VALUE | CLIAPP_F_ONCE),

  F6(SW_HIST_FILE_0,2, "no-history", 0,
     "Disables interactive-mode edit history.", 0),
  F6(SW_HIST_FILE_0,1, "noh", 0, 0, 0),

  F6(SW_DISABLE, 2, "s2-disable", "comma,list",
     "Disables certain s2 API features by name.",0),
  FCONTINUE("Use a comma-and/or-space-separated list with any of "
            "the following entries:"),
  FCONTINUE("fs-stat, fs-read, fs-write, fs-io, fs-all"),
  
  F6(SW_CLEANROOM,2,"cleanroom",0,
     "Only core language functionality with no prototype-level methods.",0),
  FCONTINUE("Disables the s2/$2 global object and auto-loading of the "
            "init script."),

  FINFOLINE("Recycling-related options:"),
  F01(SW_RE_V0,"rv", "recycle-values", "recycling of value instances."),
  F01(SW_RE_C0,"rc","recycle-chunks", "the memory chunk recycler."),
  F01(SW_RE_S0,"si","string-interning", "string interning."),

  FINFOLINE("Memory-tracking/capping options:"),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-ta", "memcap-total-allocs", "N",
     "* Limit the (T)otal number of (A)llocations to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-tb", "memcap-total-bytes", "N",
     "* Limit the (T)otal number of allocated (B)ytes to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-ca", "memcap-concurrent-allocs", "N",
     "Limit the (C)oncurrent number of (A)llocations to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-cb", "memcap-concurrent-bytes", "N",
     "Limit the (C)oncurrent number of allocated (B)ytes to N.",
     CLIAPP_F_SPACE_VALUE),
  FX(SW_MCAP_TOTAL_ALLOCS, "cap-sb", "memcap-single-alloc", "N",
     "Limit the size of any (S)ingle allocation to N (B)ytes.",
     CLIAPP_F_SPACE_VALUE),
  FINFOLINE("The allocator starts failing (returning NULL) when a capping constraint is violated."),
  FCONTINUE("The 'tb' and 'ta' constraint violations are permanent (recovery requires resetting the engine)."),
  FCONTINUE("The 'ca' and 'cb' constraints are recoverable once some cwal-managed memory gets freed."),
  FCONTINUE("Capping only applies to memory allocated by/via the scripting\n"
            "engine, not 3rd-party APIs."),

  F6(SW_MEM_FAST1,2,"fast",0,"(F)orce (A)lloc (S)ize (T)racking.",0),
  FCONTINUE("Enables more precise tracking and reuse of recycled memory."),
  FCONTINUE("Several memcap options enable --fast implicitly."),
  
#undef GROUP
#undef F01
#undef F6
#undef FX
#undef FPLUS
#undef FPLUSX
#undef FINFOLINE
    CliAppSwitch_sentinel /* MUST be the last entry in the list */
  };
  return cliAppSwitches;
}

Deleted bindings/s2/shell_common.c.

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
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
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
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
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
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
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
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075



























































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */

/************************************************************************

This file is intended to be included directly into shell.c
(a.k.a. s2sh) and shell2.c (a.k.a. s2sh2), not compiled by itself. It
contains all of the code which is common to both versions of the shell
(which is everything but their --help bits).

************************************************************************/

#ifndef S2SH_VERSION
#  error define S2SH_VERSION to 1 or 2 before including this file.
#elif (S2SH_VERSION!=1) && (S2SH_VERSION!=2)
#  error S2SH_VERSION must bet to 1 or 2.
#endif

#include <assert.h>

#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#if defined(S2_AMALGAMATION_BUILD) || 2==S2SH_VERSION
#  include "s2_amalgamation.h"
#else
#  include "s2.h"
#endif
/* ^^^^ may include config stuff used by system headers */

#if !defined(S2SH_FOR_UNIT_TESTS)
/* This option enables options which are only in place for
   purposes of s2's own unit test suite. e.g. to communicate
   compile-time options which may change test paths.
*/
#  define S2SH_FOR_UNIT_TESTS 0
#endif

#include "cliapp.h"

#if defined(S2_OS_UNIX)
#  include <unistd.h> /* isatty() and friends */
#endif
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <locale.h> /* setlocale() */

/* Must be defined by shell.c/shell2.c in a way compatible
   with cliApp.switches. */
static CliAppSwitch const * s2sh_cliapp_switches();

/**
   ID values and flags for CLI switches. Some are only used by one of
   s2sh or s2sh2.
*/
enum s2sh_switch_ids {
/** Mask of the bits used as flag IDs. The remainder are flag/modifier
    bits. */
SW_ID_MASK = 0xFFFF,
SW_ID_MASK_bits = 16,
#define ID(X) (X<< SW_ID_MASK_bits)

/**
   An entry flagged as SW_INFOLINE is intended to be a standalone line
   of information unrelated to a specific flag. If it needs to span
   lines, it must embed \n characters. --help outputs its "brief"
   member.
*/
SW_INFOLINE = ID(1),
/**
   An entry flagged with SW_CONTINUE must immediately follow either an
   SW_INFOLINE, a switch entry (SW_ID_MASK), or another entry with
   this flag. --help outputs its "brief" member, indented or not,
   depending on the previous entry.
*/
SW_CONTINUE = ID(2),

/**
   SW_GROUP_x are used for grouping related elements into tiers.
*/
SW_GROUP_0 = ID(4),
SW_GROUP_1 = ID(8),
SW_GROUP_2 = ID(0x10),
SW_GROUPS = SW_GROUP_0 | SW_GROUP_1 | SW_GROUP_2,

/**
   Switches with this bit set are not output by --help.
   (Workaround for s2sh -help/--help/-? having 3 options!)
*/
SW_UNDOCUMENTED = ID(0x20),

#undef ID

SW_switch_start = 0,
/*
  Entries for individual CLI switches...

  DO NOT bitmask the SW_GROUP entries directly with the
  switch-specific entries. They are handled in a separate step. Long
  story.

  The SW_ID_MASK bits of the switch ID values need not be bitmasks.
  i.e. they may have bits which overlap with other entries, provided that
  the SW_ID_MASK part is unique.

  All switches which *require* a value, as opposed to optionally
  accept a value, should, for consistency, have their pflags member
  OR'd with CLIAPP_F_SPACE_VALUE.

  All switches which may only be provided once must have their pflags
  field OR'd with CLIAPP_F_ONCE.
*/
SW_CLAMPDOWN/*v1. No longer allowed.*/,
SW_CLEANROOM,
SW_DISABLE,
SW_ESCRIPT,
SW_HELP,
SW_INFILE,
SW_INTERACTIVE,
SW_INTERNAL_FU,
SW_METRICS,
SW_OUTFILE,
SW_SHOW_SIZEOFS,
SW_SWEEP_INTERVAL,
SW_VAC_INTERVAL,
SW_VERBOSE,
SW_VERSION,

/**
  The entries ending with 0 or 1 are toggles. The #1 part of each pair
  MUST have a value equal to the corresponding #0 entry plus 1.
*/
SW_MEM_FAST0, SW_MEM_FAST1,
/** SW_RE_xxx = recycling-related options */
/* [no-]recycle-chunks */
SW_RE_C0, SW_RE_C1,
/* [no-]string-interning */
SW_RE_S0, SW_RE_S1,
/* [no-]recycle-values */
SW_RE_V0, SW_RE_V1,
/* [no-]scope-hashes */
SW_SCOPE_H0, SW_SCOPE_H1,
/* --T/-T */
SW_TRACE_STACKS_0, SW_TRACE_STACKS_1,
/* v1: --W/-w, v2 */
SW_TRACE_SWEEP_0, SW_TRACE_SWEEP_1,

/* v1: --s2.shell/-s2.shell, v2: na/--s2.shell */
SW_SHELL_API_0, SW_SHELL_API_1,
/* v1: --M/-M, v2: --no-module-init/na */
SW_MOD_INIT_0, SW_MOD_INIT_1,
/** v1: --A/-A */
SW_ASSERT_0, SW_ASSERT_1,
/** v1: --h/-h */
SW_HIST_FILE_0, SW_HIST_FILE_1,
/** v1: --a/-a, v2: na/-I
*/
SW_AUTOLOAD_0, SW_AUTOLOAD_1,

SW_TRACE_CWAL,

/* Memory-capping options... */
SW_MCAP_TOTAL_ALLOCS,
SW_MCAP_TOTAL_BYTES,
SW_MCAP_CONC_ALLOCS,
SW_MCAP_CONC_BYTES,
SW_MCAP_SINGLE_BYTES,

SW_end /* MUST be the last entry */
};


#if !defined(S2_SHELL_EXTEND_FUNC_NAME)
#  define S2_SHELL_EXTEND_FUNC_NAME s2_shell_extend
#endif
/*
  If S2_SHELL_EXTEND is defined, the client must define
  s2_shell_extend() (see shell_extend.c for one implementation) and
  link it in with this app. It will be called relatively early in the
  initialization process so that any auto-loaded scripts can make use
  of it. It must, on error, return one of the non-0 CWAL_RC_xxx error
  codes (NOT a code from outside the range! EVER!). An error is
  treated as fatal to the shell.

  See shell_extend.c for a documented example of how to use this
  approach to extending this app.
*/
#if defined(S2_SHELL_EXTEND)
extern int S2_SHELL_EXTEND_FUNC_NAME(s2_engine * se, cwal_value * mainNamespace,
                                     int argc, char const * const * argv);
#endif

/*
  MARKER((...)) is an Internally-used output routine which
  includes file/line/column info.
*/
#define MARKER(pfexp) \
  if(1) cliapp_print("%s:%d:  ",__FILE__,__LINE__); \
  if(1) cliapp_print pfexp
#define MESSAGE(pfexp) cliapp_print pfexp
#define WARN(pfexp) \
    if(1) cliapp_warn("%s:  ",App.appName); \
    if(1) cliapp_warn pfexp
#define VERBOSE(LEVEL,pfexp) if(App.verbosity >= LEVEL) { cliapp_print("verbose: "); MESSAGE(pfexp); }(void)0

/**
   S2_AUTOINIT_STATIC_MODULES tells the shell whether to initialize, or
   not, statically-linked-in modules by default.
*/
#if !defined(S2_AUTOINIT_STATIC_MODULES)
# define S2_AUTOINIT_STATIC_MODULES 1
#endif

/**
   S2SH_MAX_SCRIPTS = max number of scripts to run, including the -e
   scripts and the main script filename, but not the autoload script.
*/
#define S2SH_MAX_SCRIPTS 10

/**
   Global app-level state.
*/
static struct {
  char const * appName;
  char enableArg0Autoload;
  int enableValueRecycling;
  /**
     Max chunk size for mem chunk recycler. Set to 0 to disable.  Must
     allocate sizeof(void*) times this number, so don't set it too
     high.
  */
  cwal_size_t maxChunkCount;
  int enableStringInterning;
  int showMetrics;
  int verbosity;
  int traceAssertions;
  int traceStacks;
  int traceSweeps;
  char scopesUseHashes;
  char const * inFile;
  char const * outFile;
  char const * const * scriptArgv;
  int scriptArgc;
  int addSweepInterval;
  int addVacuumInterval;
  /**
     Name of interactive most history file. Currently
     looks in/writes to only the current dir.
  */
  char const * editHistoryFile;
  /**
     Default prompt string for the line-reading functions.
  */
  char const * lnPrompt;
  /**
     0=non-interactive mode, >0=interactive mode, <0=as-yet-unknown.
  */
  int interactive;
  /**
     This is the "s2" global object. It's not generally kosher to hold
     global cwal_values, but (A) we're careful in how we do so and (B)
     it simplifies a couple of our implementations.
  */
  cwal_value * s2Global;
  /**
     When shellExitFlag!=0, interactive readline looping stops.
  */
  int shellExitFlag;

  /**
     If true, do not install prototypes or global functionality.  Only
     "raw s2" is left.
   */
  int cleanroom;

  /**
     Flags for use with s2_disable_set_cstr().
  */
  char const * zDisableFlags;

  /**
     If true AND if "static modules" have been built in, this
     initializes those modules.
  */
  int initStaticModules;

  /**
     A flag used to tell us not to save the history - we avoid saving
     when the session has no commands, so that we don't get empty
     history files laying around. Once s2sh_history_add() is called,
     this flag gets set to non-0.

  */
  int saveHistory;

  /**
     A flag to force installation of the s2.shell API, even in non-interactive
     mode. <0 = determine automatically. 0 = force off. >0 = force on unless
     -cleanroom trumps it.
  */
  int installShellApi;

  /**
     Don't look at this unless you know _exactly_ what you're doing
     with s2's internals. It's for my eyes only.
   */
  int enableInternalFu;

  /**
     Memory-capping configuration. Search this file for App.memcap for
     how to set it up.
  */
  cwal_memcap_config memcap;

  /**
     Scripts for the -e/-f flags.
  */
  struct {
    int nScripts /*current # of entries in this->e*/;
    struct {
      /* True if this->src is a filename, else it's script code. */
      int isFile;
      /* Filename or script code. */
      char const * src;
    } e[S2SH_MAX_SCRIPTS];
  } scripts;
  int32_t cwalTraceFlags;
} App = {
0/*appName*/,
1/*enableArg0Autoload*/,
1/*enableValueRecycling*/,
50/*maxChunkCount*/,
1/*enableStringInterning*/,
0/*showMetrics*/,
0/*verbosity*/,
0/*traceAssertions*/,
0/*traceStacks*/,
0/*traceSweeps*/,
0/*scopesUseHashes*/,
0/*inFile*/,
0/*outFile*/,
0/*scriptArgv*/,
0/*scriptArgc*/,
0/*addSweepInterval*/,
0/*addVacuumInterval*/,
"s2sh.history"/*editHistoryFile*/,
#if 1==S2SH_VERSION
"s2sh> "/*lnPrompt*/,
#else
"s2sh2> "/*lnPrompt*/,
#endif
-1/*interactive*/,
0/*s2Global*/,
0/*shellExitFlag*/,
0/*cleanroom*/,
0/*zDisableFlags*/,
S2_AUTOINIT_STATIC_MODULES/*initStaticModules*/,
0/*saveHistory*/,
-1/*installShellApi*/,
0/*enableInternalFu*/,
cwal_memcap_config_empty_m/*memcap*/,
{0/*nScripts*/, {/*e*/{0,0}}},
0/*cwalTraceFlags*/
};


static cwal_engine * printfEngine = 0;
static void s2sh_print(char const * fmt, ...) {
  va_list args;
  va_start(args,fmt);
  if(printfEngine){
    cwal_outputfv(printfEngine, fmt, args);
  }else{
      cliapp_print(fmt,args);
  }
  va_end(args);
}

/**
   Called by cwal_engine_init(). This does the lower-level (pre-s2)
   parts of the cwal_engine configuration. This part must not create
   any values, as s2 will destroy them right after this is called so
   that it can take over ownership of the top-most scope. Thus only
   configurable options are set here, and new functionality is added
   during the s2 part of the initialization.
*/
static int s2sh_init_engine(cwal_engine *e, cwal_engine_vtab * vtab){
  int rc = 0;
  int32_t featureFlags = 1
    ? 0
    : CWAL_FEATURE_ZERO_STRINGS_AT_CLEANUP;

  cwal_engine_trace_flags(e, App.cwalTraceFlags);
  if(App.enableStringInterning){
    featureFlags |= CWAL_FEATURE_INTERN_STRINGS;
  }
  if(App.scopesUseHashes){
    /* If enabled, scopes will use hashtables for property storage.
       This are potentially more costly, in terms of memory, but
       are much faster. */
    featureFlags |= CWAL_FEATURE_SCOPE_STORAGE_HASH;
    if(vtab){/*avoid unused param warning*/}
  }else{
    featureFlags &= ~CWAL_FEATURE_SCOPE_STORAGE_HASH;
  }
  cwal_engine_feature_flags(e, featureFlags);

  {
    /* Configure the memory chunk recycler... */
    cwal_memchunk_config conf = cwal_memchunk_config_empty;
    conf.maxChunkCount = App.maxChunkCount;
    conf.maxChunkSize = 1024 * 32;
#if 16 == CWAL_SIZE_T_BITS
    conf.maxTotalSize = 1024 * 63;
#else
    conf.maxTotalSize = 1024 * 256;
#endif
    conf.useForValues = 0 /* micro-optimization: true tends towards
                             sub-1% reduction in mallocs but
                             potentially has aggregate slower value
                             allocation for most cases */ ;
    rc = cwal_engine_memchunk_config(e, &conf);
    assert(!rc);
  }

#define REMAX(T,N) cwal_engine_recycle_max( e, CWAL_TYPE_ ## T, (N) )
  REMAX(UNDEF,0) /* disables recycling for all types */;
  if(App.enableValueRecycling){
    /* A close guess based on the post-20141129 model...  List them in
       "priority order," highest priority last.  Lower prio ones might
       get trumped by a higher prio one: they get grouped based on the
       platform's sizeof() of their concrete underlying bytes.
    */
    REMAX(UNIQUE,20)/* will end up being trumped by integer (32-bit)
                       and/or double (64-bit) */;
    REMAX(KVP,80) /* guaranteed individual recycler */;
    REMAX(WEAK_REF,30) /* guaranteed individual recycler */;
    REMAX(STRING,50) /* guaranteed individual recycler */;
    REMAX(EXCEPTION,3);
    REMAX(HASH,15) /* might also include: function, native, buffer */;
    REMAX(BUFFER,20) /* might also include: function, native, buffer, hash */ ;
    REMAX(XSTRING,20 /* also Z-strings and tuples, might also include doubles */);
    REMAX(NATIVE,20)  /* might also include: function, hash */;
    REMAX(DOUBLE,50)/* might also include z-/x-strings,
                       integer (64-bit), unique (64-bit)*/;
    REMAX(FUNCTION,50) /* might include: hash, native, buffer */;
    REMAX(ARRAY,30);
    REMAX(OBJECT,30) /* might include: buffer */;
    REMAX(INTEGER,80) /* might include: double, unique */;
    REMAX(TUPLE,30) /* also x-/z-strings, might include: double */;
  }
#undef REMAX
  /*
    Reminder to self: we cannot install functions from here because
    the s2_engine's Function prototype will not get installed by that
    time, so the functions we added would not have an 'undefined'
    prototype property. We must wait until the s2_engine is set up.

    s2 will also nuke the top scope so that it can push one of its
    own, so don't allocate any values here.
  */
  return rc;
}

/** String-is-internable predicate for cwal_engine. */
static bool cstr_is_internable( void * state,
                                char const * str_,
                                cwal_size_t len ){
  enum { MaxLen = 32U };
  unsigned char const * str = (unsigned char const *)str_;
  assert(str);
  assert(len>0);
  if(len>MaxLen){
    if(state){/*avoid unused param warning*/}
    return 0;
  }else if(1==len){
    /*if(*str<32) return 0;
      else
    */
    return (*str<=127) ? 1 : 0;
  }
  /* else if(len<4) return 1; */
  else{
    /* Read a UTF8 identifier... */
    char const * zEnd = str_+len;
    char const * tail = str_;
    s2_read_identifier(str_, zEnd, &tail);
    if((tail>str_)
       && (len==(cwal_size_t)(tail-str_))
       ){
      /* MARKER(("internable: %.*s\n", (int)len, str_)); */
      return 1;
    }
    /* MARKER(("Not internable: %.*s\n", (int)len, str_)); */
    return 0;
  }
}

static int s2_cb_s2sh_line_read(cwal_callback_args const * args, cwal_value ** rv){
  char const * prompt = 0;
  char * line = 0;
  if(args->argc){
    if(!(prompt = cwal_value_get_cstr(args->argv[0], 0))){
      return s2_cb_throw(args, CWAL_RC_MISUSE,
                         "Expecting a string or no arguments.");
    }
  }else{
    prompt = "prompt >";
  }
  if(!(line = cliapp_lineedit_read(prompt))){
    *rv = cwal_value_undefined();
    return 0;
  }else{
    *rv = cwal_new_string_value(args->engine, line, cwal_strlen(line));
    cliapp_lineedit_free(line)
      /* if we knew with absolute certainty that args->engine is
         configured for the same allocator as the readline bits, we
         could use a Z-string and save a copy. But we don't, generically
         speaking, even though it's likely to be the same in all but the
         weirdest configurations. Even so, cwal may "massage" memory a
         bit to add some metadata, so we cannot pass it memory from
         another allocator, even if we know they both use the same
         underlying allocator.
      */;
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

/**
   Callback implementation for s2_cb_s2sh_history_(load|add|save)().
*/
static int s2_cb_s2sh_history_las(cwal_callback_args const * args,
                                  cwal_value ** rv,
                                  char const * descr,
                                  int (*func)(char const * str) ){
  char const * str = args->argc
    ? cwal_value_get_cstr(args->argv[0], 0)
    : 0;
  if(!str){
    return s2_cb_throw(args, CWAL_RC_MISUSE,
                       "Expecting a string argument.");
  }
  else{
    int rc = (*func)(str);
    if(rc){
      rc = s2_cb_throw(args, rc,
                       "Native %s op failed with code %d/%s.",
                       descr, rc, cwal_rc_cstr(rc));
    }else{
      *rv = cwal_value_undefined();
    }
    return rc;
  }
}

static int s2_cb_s2sh_history_save(cwal_callback_args const * args, cwal_value ** rv){
  return s2_cb_s2sh_history_las( args, rv, "history-save", cliapp_lineedit_save );
}

static int s2_cb_s2sh_history_load(cwal_callback_args const * args, cwal_value ** rv){
  return s2_cb_s2sh_history_las( args, rv, "history-load", cliapp_lineedit_load);
}

static int s2_cb_s2sh_history_add(cwal_callback_args const * args, cwal_value ** rv){
  return s2_cb_s2sh_history_las( args, rv, "history-add",cliapp_lineedit_add);
}

/**
   cwal_value_visitor_f() impl. Internal helper for s2_dump_metrics().
*/
static int s2sh_metrics_dump_funcStash_keys( cwal_value * v, void * state ){
  cwal_size_t sz = 0;
  char const * n = cwal_value_get_cstr(v, &sz);
  if(state){/*unused*/}
  s2sh_print("\t%.*s\n", (int)sz, n);
  return 0;
}

static void s2_dump_metrics(s2_engine * se){
  printfEngine = se->e;
#define OUT(pfexp) s2sh_print pfexp
  if(App.verbosity){
    OUT(("\n"));
    OUT(("cwal-level allocation metrics:\n"));
    cwal_dump_allocation_metrics(se->e);
    OUT(("\n"));
    if(App.verbosity>2 && App.enableStringInterning){
      cwal_dump_interned_strings_table(se->e, 1, 32);
    }
  }
  if(se->metrics.tokenRequests){
    OUT(("s2-side metrics:\n"));
    OUT(("Peak cwal_scope level: %d with %d s2_scopes pushed/popped\n",
         se->metrics.maxScopeDepth,
         se->metrics.totalScopesPushed));
    OUT(("%d s2_scopes (sizeof %u) were alloc'd across "
         "%d (re)alloc(s) with a peak of %d bytes "
         "(counted among cwal_engine::memcap::currentMem).\n",
         (int)se->scopes.alloced,
         (unsigned int)sizeof(s2_scope),
         se->metrics.totalScopeAllocs,
         (int)(se->scopes.alloced * sizeof(s2_scope))));
    OUT(("Peak eval depth: %d\n",
         se->metrics.peakSubexpDepth));
    OUT(("Total s2_stokens (sizeof=%u) requested=%u, "
         "allocated=%u (=%u bytes), alive=%u, peakAlive=%u, "
         "currently in recycle bin=%d\n",
         (unsigned)sizeof(s2_stoken), se->metrics.tokenRequests,
         se->metrics.tokenAllocs, (unsigned)(se->metrics.tokenAllocs * (unsigned)sizeof(s2_stoken)),
         se->metrics.liveTokenCount, se->metrics.peakLiveTokenCount,
         se->recycler.stok.size));
    OUT(("Total calls to s2_next_token(): %u\n",
         se->metrics.nextTokenCalls));
    if(se->metrics.tokenAllocs < se->metrics.tokenRequests){
      OUT(("Saved %u bytes and %u allocs via stack token recycling :D.\n",
           (unsigned)((se->metrics.tokenRequests * (unsigned)sizeof(s2_stoken))
                      - (se->metrics.tokenAllocs * (unsigned)sizeof(s2_stoken))),
           se->metrics.tokenRequests - se->metrics.tokenAllocs
           ));
    }
  }
  OUT(("Scratch buffer (used by many String APIs): %u byte(s)\n",
       (unsigned)se->buffer.capacity));

  if(se->metrics.ukwdLookups){
    OUT(("se->metrics.ukwdLookups=%u, "
         "se->metrics.ukwdHits=%u\n",
         se->metrics.ukwdLookups,
         se->metrics.ukwdHits));
  }
  if(se->metrics.funcStateAllocs){
    OUT(("Allocated state for %u of %u script-side function(s) "
         "(sizeof %u), using %u bytes.\n",
         se->metrics.funcStateAllocs,
         se->metrics.funcStateRequests,
         s2_sizeof_script_func_state(),
         se->metrics.funcStateMemory));
  }
  if(se->funcStash){
    OUT(("Function script name hash: hash size=%d, entry count=%d. "
         "Re-used name count: %d\n",
         (int)cwal_hash_size(se->funcStash),
         (int)cwal_hash_entry_count(se->funcStash),
         (int)se->metrics.totalReusedFuncStash
         ));
    if(App.verbosity>2){
      OUT(("Function script name hash entries:\n"));
      cwal_hash_visit_keys(se->funcStash,
                           s2sh_metrics_dump_funcStash_keys, 0);
    }
  }
  if(se->stash){
    cwal_hash const * const h = cwal_value_get_hash(se->stash);
    OUT(("s2_engine::stash hash size=%d, entry count=%d\n",
         (int)cwal_hash_size(h), (int)cwal_hash_entry_count(h)));
  }
  if(se->metrics.assertionCount
     && (App.showMetrics || App.traceAssertions)
     ){
    OUT(("script-side 'assert' checks: %u\n",
         se->metrics.assertionCount));
  }
  OUT(("(end of s2 metrics)\n"));
  printfEngine = 0;
#undef OUT
}

static int s2sh_cb_dump_metrics(cwal_callback_args const * args, cwal_value ** rv){
  s2_engine * se = s2_engine_from_args(args);
  int const oldVerbosity = App.verbosity;
  int newVerbose = args->argc
    ? (int)cwal_value_get_integer(args->argv[0])
    : 0;
  App.verbosity = newVerbose;
  s2_dump_metrics(se);
  App.verbosity = oldVerbosity;
  *rv = cwal_value_undefined();
  return 0;
}

static int s2sh_report_engine_error( s2_engine * se ){
  int rc = 0;
  cwal_error * const err = &se->e->err;
  if(err->code || se->flags.interrupted){
    char const * msg = 0;
    rc = s2_engine_err_get(se, &msg, 0);
    if(err->line>0){
      fprintf(stderr,
              "s2_engine says error #%d (%s)"
              " @ script [%s], line %d, column %d: %s\n",
              rc, cwal_rc_cstr(rc),
              (char const *)err->script.mem,
              err->line, err->col, msg);
    }else{
      fprintf(stderr,
              "s2_engine says error #%d (%s)%s%s\n",
              rc, cwal_rc_cstr(rc),
              (msg && *msg) ? ": " : "",
              (msg && *msg) ? msg : "");
    }
  }
  return rc;
}

/**
   General-purpose result reporting function. rc should be the rc from
   the function we got *rv from. This function takes over
   responsibility of *rv from the caller (who should not take a
   reference to *rv). rv may be 0, as may *rv.

   After reporting the error, s2_engine_sweep() is run, which _might_
   clean up *rv (along with any other temporary values in the current
   scope).
*/
static int s2sh_report_result(s2_engine * se, int rc, cwal_value **rv ){
  char const * label = "result";
  cwal_value * rvTmp = 0;
  char showedMessage = 0;
  if(!rv) rv = &rvTmp;
  switch(rc){
    case 0: break;
    case CWAL_RC_RETURN:
      *rv = cwal_propagating_take(se->e);
      assert(*rv);
      break;
    case CWAL_RC_EXCEPTION:
      label = "EXCEPTION";
      *rv = cwal_exception_get(se->e);
      assert(*rv);
      cwal_value_ref(*rv);
      cwal_exception_set(se->e, 0);
      cwal_value_unhand(*rv);
      break;
    case CWAL_RC_EXIT:
      rc = 0;
      *rv = s2_propagating_take(se);
      break;
    case CWAL_RC_BREAK:
      label = "UNHANDLED BREAK";
      *rv = s2_propagating_take(se);
      break;
    case CWAL_RC_CONTINUE:
      label = "UNHANDLED CONTINUE";
      *rv = 0;
      break;
    case CWAL_RC_FATAL:
      label = "FATAL";
      *rv = s2_propagating_take(se);
      assert(*rv);
      assert(se->e->err.code);
      CWAL_SWITCH_FALL_THROUGH;
    default:
      ;
      if(s2sh_report_engine_error(se)){
        showedMessage = 1;
      }else{
        WARN(("Unusual result code (no error details): %d/%s\n",
              rc,cwal_rc_cstr2(rc)));
      }
      break;
  }
  if(*rv){
    assert((cwal_value_scope(*rv) || cwal_value_is_builtin(*rv))
           && "Seems like we've cleaned up too early.");
    if(rc || App.verbosity){
      if(rc && !showedMessage){
        fprintf(stderr,"rc=%d (%s)\n", rc, cwal_rc_cstr(rc));
      }
      s2_dump_value(*rv, label, 0, 0, 0);
    }
    /* We are uncertain of v's origin but want to clean it up if
       possible, so... */
#if 1
    /*
      Disable this and use -W to see more cleanup in action.  This
      ref/unref combo ensures that if it's a temporary, it gets
      cleaned up by this unref, but if it's not, then we effectively
      do nothing to it (because someone else is holding/containing
      it).
    */
    cwal_refunref(*rv);
#endif
    *rv = 0;
  }
  s2_engine_err_reset2(se);
  s2_engine_sweep(se);
  return rc;
}

static int s2_cb_s2sh_exit(cwal_callback_args const * args, cwal_value ** rv){
  if(args){/*avoid unused param warning*/}
  App.shellExitFlag = 1;
  *rv = cwal_value_undefined();
  return 0;
}

static int s2_install_shell_api( s2_engine * se, cwal_value * tgt,
                                 char const * name ){
  cwal_value * sub = 0;
  int rc = 0;
  if(!se || !tgt) return CWAL_RC_MISUSE;
  else if(!cwal_props_can(tgt)) return CWAL_RC_TYPE;

  if(name && *name){
    sub = cwal_new_object_value(se->e);
    if(!sub) return CWAL_RC_OOM;
    if( (rc = cwal_prop_set(tgt, name, cwal_strlen(name), sub)) ){
      cwal_value_unref(sub);
      return rc;
    }
  }else{
    sub = tgt;
  }

  {
    s2_func_def const funcs[] = {
    /* S2_FUNC2("experiment", s2_cb_container_experiment), */
    S2_FUNC2("tokenizeLine", s2_cb_tokenize_line),
    S2_FUNC2("readLine", s2_cb_s2sh_line_read),
    S2_FUNC2("historySave", s2_cb_s2sh_history_save),
    S2_FUNC2("historyLoad", s2_cb_s2sh_history_load),
    S2_FUNC2("historyAdd", s2_cb_s2sh_history_add),
    S2_FUNC2("exit", s2_cb_s2sh_exit),
    s2_func_def_empty_m
    };
    rc = s2_install_functions(se, sub, funcs, 0);
  }
#undef FUNC2
#undef SET
  return 0;
}

static int s2sh_run_file(s2_engine * se, char const * filename){
  cwal_value * xrv = 0;
  int rc = s2_eval_filename(se, 1, filename, -1, &xrv);
  return s2sh_report_result(se, rc, &xrv);
}

#if 0
cwal_value * s2_shell_global(){
  return App.s2Global;
}
#endif

/**
   Given a module name (via *name) and target object, this function
   creates any parent objects of that name, if needed. Parents are
   specified by using a underscore-delimited name. If the name
   contains a delimiter, each entry except that last one is treated as
   a parent, and they are created (if needed) recursively.

   On success:

   - *name is set to point to the tail part of the module name.
   e.g. the "foo" in "foo" and "bar" in "foo_bar".

   - *newTgt is set to new target container for the tail part of the
   module name (which this routine does not install - that's up to the
   caller).

   - 0 is returned.

   The only errors are allocation-related, so a non-0 return should be
   considered fatal.
*/
static int s2_shell_install_mod_parents( s2_engine * se,
                                         char const * separators,
                                         char const ** name,
                                         cwal_value * tgt,
                                         cwal_value **newTgt){
  int rc = 0;
  s2_path_toker pt = s2_path_toker_empty;
  char const * token = 0;
  int count = 0, i;
  cwal_size_t nT = 0, nName = cwal_strlen(*name);
  s2_path_toker_init(&pt, *name, (cwal_int_t)nName);
  pt.separators = separators;
  while(0==s2_path_toker_next(&pt, &token, &nT)){
    ++count;
  }
  assert(count>0);
  if(count>1){
    token = 0;
    nT = 0;
    s2_path_toker_init(&pt, *name, (cwal_int_t)nName);
    pt.separators = separators;
  }
  for(i = 0; i < count-1; ++i ){
    cwal_value * sub = 0;
    s2_path_toker_next(&pt, &token, &nT);
    sub = cwal_prop_get(tgt, token, nT);
    if(!sub){
      sub = cwal_new_object_value(se->e);
      if(!sub) return CWAL_RC_OOM;
      cwal_value_ref(sub);
      rc = cwal_prop_set(tgt, token, nT, sub);
      cwal_value_unref(sub);
      if(rc) return rc;
    }
    tgt = sub;
  }
  if(i==count-1){
    s2_path_toker_next(&pt, &token, &nT);
  }
  *name = token;
  *newTgt = tgt;
  return 0;
}

/**
   Internal helper for s2_setup_static_modules(). s2_module_init()'s
   the given module then sets its result (or wrapper object, for v1
   modules) to tgt[name]. Returns the result of the init call.

   If installName is not NULL and not the same as the given module
   name then it is assumed to be a dot-delimited list of properties
   (with 0 or more dots) as which the module should be installed
   in/under the given target. e.g. a name of "fooBarBaz" and
   installName of "foo.bar.baz" would install module fooBarBaz as
   tgt.foo.bar.baz. If the names are the same, or installName is NULL,
   the module is installed as tgt[name].

   As a compatibility crutch, if the installName and name differ, and
   installName installs sub-properties, this routine *also* installs
   the module as s2[name] for compatibilty with the numerous scripts
   which assuming a conventional module mapping of s2.moduleName.
*/
static int s2_shell_mod_init( s2_engine * se,
                              s2_loadable_module const * const mod,
                              char const * name,
                              char const * installName,
                              cwal_value * tgt){
  cwal_value * mrv = 0;
  int rc = 0;
  char const * originalName;
  char const * propName;
  char const * initLabel = "initialization";
#if 1 == S2SH_VERSION
  /* See comments below */
  cwal_value * const originalTgt = tgt;
#endif
  if(!name) name = mod->name;
  if(!installName) installName=name;
  originalName = name;
  propName = installName;
  if(strcmp(propName,name)){
    rc = s2_shell_install_mod_parents(se, ".", &propName, tgt, &tgt);
  }
  if(!rc) rc = s2_module_init(se, mod, &mrv);
  cwal_value_ref(mrv);
#if 1 == S2SH_VERSION
  top:
#endif
  if(!rc){
    rc = s2_set( se, tgt, propName, cwal_strlen(propName),
                 mrv ? mrv : cwal_value_undefined() );
  }else{
    assert(!mrv && "Module init must not return non-NULL on error.");
  }
  VERBOSE(2,("static module [%s%s%s] %s: %s\n",
             originalName,
             propName==installName ? "" : " ==> ",
             propName==installName ? "" : installName,
             initLabel,
             rc ? "FAILED" : "OK"));
#if 1 == S2SH_VERSION
  /**
     2020-01-31: kludge/crutch: we have many scripts which expect
     modules to be installed as s2[moduleName], but this code now
     supports installing modules as sub-properties, e.g. s2.regex.js
     instead of s2.regex_js. To keep the various scripts working, this
     check will install the module to its historical s2[moduleName]
     location if that was not where the first run-through of this
     function installed it.
  */
  if(!rc && originalTgt!=tgt){
    initLabel = "alias";
    installName = propName = name;
    tgt = originalTgt;
    goto top;
  }
#endif
  cwal_value_unref(mrv);
  return rc;
}

/**
   This holds a list of all module names which are being initialized
   via the "static module" process. We use this to give us a way to
   disable activation of built-in APIs which are being installed via
   their static module form.

   The names used here must be the canonical name of the module, not
   the "installation name". e.g. "regex_js" instead of "regex.js".
*/
static char const * const AppStaticModNames[] = {
#if defined(S2SH_MODULE_INITS)
#  define M(X) # X,
#  define M2(X,Y) " " # X,
S2SH_MODULE_INITS
#  undef M
#  undef M2
#endif
0 /* list sentinel */
};

/**
   A single-string form of AppModNames. It's exists only for
   showing the user in verbose mode.
*/
static char const * const AppStaticModNamesString = ""
#if defined(S2SH_MODULE_INITS)
#define M(X) " " # X
#define M2(X,Y) " " # Y
  S2SH_MODULE_INITS
#undef M
#undef M2
#endif
  ;

/**
   Returns true (non-0) if a static module with the name n was
   compiled in, else false (0).
*/
static int s2sh_has_static_mod( char const * n ){
  char const * const * p = AppStaticModNames;
  for( ; *p; ++p ){
    if(!strcmp(n,*p)) return 1;
  }
  return 0;
}
  
/**
   Initializes any static modules which this build knows about. Each
   one gets installed as tgt[X], where X is the module's name.
   Returns 0 on success. If App.initStaticModules is 0 then this
   is a no-op.
*/
static int s2_setup_static_modules( s2_engine * se, cwal_value * tgt ){
  int rc = 0;
  if(!App.initStaticModules) return 0;
  if(0){
    s2_shell_mod_init(0,0,0,0,tgt)
      /* We need a ref to this func in order for the func
         to be static, and there is otherwise no ref when building
         without statically-imported modules. */;      
  }
  /**
     Initializes the statically-linked module assocatiated with
     S2SH_MODULE_INIT_##MODNAME, installing it under the property
     named #INSTNAME.

     INSTNAME should either be the same as MODNAME or a
     "reformulation" which tells s2sh to install it under a different
     name, possibly somewhere other than the root of s2. e.g. passing
     (foo,bar.foo) would set up the installation of module foo as
     s2.bar.foo. INSTNAME must be a non-string token (this macro
     stringifies it).
  */
#define S2SH_MODULE_INIT2(MODNAME,INSTNAME)                             \
  {                                                                     \
    extern s2_loadable_module const * s2_module_ ## MODNAME;            \
    rc = s2_shell_mod_init(se, s2_module_ ## MODNAME, #MODNAME, #INSTNAME, tgt); \
    if(rc) goto end;                                                    \
  }
#define S2SH_MODULE_INIT(MODNAME)               \
  S2SH_MODULE_INIT2(MODNAME,MODNAME)

#if defined(S2SH_MODULE_INITS)
  /**
     Yet another approach to registering modules...

     Define S2SH_MODULE_INITS (note the trailing 'S') to a list
     in this form:

     M(module1) M(module2) ... M(moduleN)

     e.g.:

     cc -c shell.c ... '-DS2SH_MODULE_INITS=M(cgi) M(sqlite3)'

     Each M(X) will get translated to S2SH_MODULE_INIT(X), setting
     up registration of that module.

     Likewise, M2(X,Y) will be transformed the same way except that Y
     (which must not a quoted string (because we apparently can't(?)
     squeeze the required quotes through Make)) will be used for the
     property name, and a dot-delimited Y will cause the module to be
     installed in a sub-property of s2. e.g. M2(X,a.b.c) will install
     module X as s2.a.b.c, rather than s2.X.

     It is up to the user to link in the appropriate objects/libs for
     each module, as well as provide any linker flags or 3rd-party
     libraries those require.
  */
#  define M S2SH_MODULE_INIT
#  define M2 S2SH_MODULE_INIT2
  S2SH_MODULE_INITS
#  undef M
#  undef M2
#endif

#undef S2SH_MODULE_INIT
#undef S2SH_MODULE_INIT2
  
    if(rc) goto end /* avoid unused goto when no modules are built in */;
  end:
  if(rc){
    cwal_value * dummy = 0;
    s2sh_report_result(se, rc, &dummy)
      /* We do this to report any pending exception
         or se->err state. */;
  }
  return rc;
}

#if 0
static int s2_cb_dump_val(cwal_callback_args const * args, cwal_value ** rv){
  int i = 0;
  for( ; i < args->argc; ++i ){
    s2_dump_val(args->argv[i], "dump_val");
  }
  *rv = cwal_value_undefined();
  return 0;
}
#endif

static int s2sh_setup_s2_global( s2_engine * se ){
  int rc = 0;
  cwal_value * v = 0;
  cwal_engine * const e = se->e;
  cwal_value * s2 = 0;
#define VCHECK if(!v){rc = CWAL_RC_OOM; goto end;} (void)0
#define RC if(rc) goto end

  /*
    Reminder to self: we're not "leaking" anything on error here - it
    will be cleaned up by the scope when it pops during cleanup right
    after the error code is returned.
  */

  v = cwal_new_function_value( e, s2_cb_print, 0, 0, 0 );
  VCHECK;
  /*s2_dump_val(App.s2Global, "App.s2Global");*/
  cwal_value_ref(v);
#if 0
  if(2==S2SH_VERSION){
    rc = s2_define_ukwd(se, "if", 2, v);
    assert(CWAL_RC_ACCESS==rc);
    rc = s2_define_ukwd(se, "print", 5, v);
    RC;
    rc = s2_define_ukwd(se, "print", 5, v);
    assert(CWAL_RC_ALREADY_EXISTS==rc);
    rc = 0;
  }else
#endif
  {
    rc = cwal_var_decl(e, 0, "print", 5, v, 0);
    RC;
  }
  cwal_value_unref(v);

  /* Global s2 object... */
  s2 = v = cwal_new_object_value(e);
  VCHECK;
  cwal_value_ref(v);
  rc = cwal_var_decl(e, 0, "s2", 2, v, CWAL_VAR_F_CONST);
  if(!rc && 2==S2SH_VERSION){
    rc = s2_define_ukwd(se, "S2", 2, v);
  }
  cwal_value_unref(v);
  RC;
  App.s2Global = v;

#define SETV(K)                                     \
  VCHECK; cwal_value_ref(v);                        \
  rc = cwal_prop_set( s2, K, cwal_strlen(K), v );   \
  cwal_value_unref(v);                              \
  RC;

  v = s2_prototype_buffer(se);
  SETV("Buffer");
  v = s2_prototype_hash(se);
  SETV("Hash");

  v = s2_prototype_tuple(se);
  SETV("Tuple");
#undef SETV
    
  rc = s2_install_pf(se, s2);
  RC;

  if(AppStaticModNames[0]){
    VERBOSE(2,("Built-in static module list:%s\n",
               AppStaticModNamesString));
  }
  
  {
    /* Global functions must come before static module init because
       script-implemented modules often affirm/assert that a certain
       API is in place. */
    s2_func_def const funcs[] = {
    /*S2_FUNC2("newUnique", s2_cb_new_unique),*/
    /*S2_FUNC2("getResultCodeHash", s2_cb_rc_hash),*/
    /*S2_FUNC2("isCallable", s2_cb_is_callable),*/
    /*S2_FUNC2("isDerefable", s2_cb_is_derefable),*/
    S2_FUNC2("sealObject", s2_cb_seal_object),
    S2_FUNC2("loadModule", s2_cb_module_load),
    S2_FUNC2("minifyScript", s2_cb_minify_script),
#if 1==S2SH_VERSION
    /* s2.import is no longer needed but we very likely have scripts
       which still expect it. */
    S2_FUNC2("import", s2_cb_import_script),
#endif
    S2_FUNC2("glob", s2_cb_glob_matches_str),
    S2_FUNC2("getenv", s2_cb_getenv),
    /*S2_FUNC2("fork", s2_cb_fork),*/
    /*S2_FUNC2("dumpVal", s2sh_cb_dump_val),*/
    S2_FUNC2("dumpMetrics", s2sh_cb_dump_metrics),
    /*S2_FUNC2("compare", s2_cb_value_compare),*/
    S2_FUNC2("cwalBuildInfo", cwal_callback_f_build_info),
    s2_func_def_empty_m /* end-of-list sentinel */
    };
    if( (rc = s2_install_functions(se, s2,
                                   funcs, CWAL_VAR_F_CONST)) ) {
      goto end;
    }
  }

  /*
    Jump through some hoops to ensure that we don't install a built-in
    API which is getting statically linked in as a loadable module...

    For a module/API named N, if AppModNames contains N, we skip
    installation of that API because its module registration (which
    installs the same API) is pending. If AppModNames has no matching
    entry, we install the API with F(se, s2, N).
  */
#define MCHECK(N,F)                                             \
  if(!s2sh_has_static_mod(N)){                                  \
    if((rc = F(se, s2, N))) goto end;                           \
  }else{                                                        \
    VERBOSE(2,("Skipping install of [%s] API b/c "              \
               "there's a module with the same name.\n", N));   \
  }
  MCHECK("fs", s2_install_fs);
  MCHECK("io", s2_install_io);
  MCHECK("json",s2_install_json);
  MCHECK("ob", s2_install_ob_2);
  MCHECK("time",s2_install_time);
#undef MCHECK
#define MCHECK(N,F)                                                     \
  if(!s2sh_has_static_mod(N)){                                          \
    if((rc = s2_install_callback(se, s2, F, N, -1,                      \
                                 CWAL_VAR_F_CONST, 0, 0, 0))) goto end; \
  }else{                                                                \
    VERBOSE(2,("Skipping install of [%s] API b/c "                      \
               "there's a module with the same name.\n", N));           \
  }
  MCHECK("tmpl", s2_cb_tmpl_to_code);
#undef MCHECK

  if(App.enableInternalFu){
    rc = s2_install_callback(se, s2, s2_cb_internal_experiment,
                             "s2InternalExperiment", -1,
                             CWAL_VAR_F_CONST, 0, 0, 0);
    if(rc) goto end;
  }

  /* Some script-implemented modules rely on s2.ARGV for configuration,
     so it must be set up before modules are initialized. */
  v = cwal_new_array_value(e);
  cwal_value_ref(v);
  rc = cwal_parse_argv_flags(e, App.scriptArgc, App.scriptArgv, &v);
  if(rc){
    cwal_value_unref(v);
    goto end;
  }else{
    assert(v);
    rc = cwal_prop_set(s2, "ARGV", 4, v);
  }
  cwal_value_unref(v);
  v = 0;
  if(rc){
    goto end;
  }
  assert(!rc);

  if(1){
    rc = s2_setup_static_modules(se, s2);
    if(rc) goto end;
  }else{
      cwal_value * mod = cwal_new_object_value(se->e);
      if(!mod){
        rc = CWAL_RC_OOM;
        goto end;
      }
      cwal_value_ref(mod);
      rc = s2_set(se, s2, "mod", 3, mod);
      cwal_value_unref(mod);
      if(!rc) rc = s2_setup_static_modules(se, mod);
      if(rc) goto end;
  }

#if 0
  /*
    EXPERIMENTAL: i've always avoided making scope-owned variable
    storage directly visible to script code, but we're going to
    try this...

    Add global const __GLOBAL which refers directly to the variable
    storage of the top-most scope.

    If we like this, we'll change it to a keyword, which will make it
    many times faster to process.
  */
  v = cwal_scope_properties(cwal_scope_top(cwal_scope_current_get(e)))
    /* Cannot fail: we've already declared vars in this scope, so it's
       already allocated. */;
  assert(cwal_value_get_object(v));
  assert(cwal_value_refcount(v)>0 && "Because its owning scope holds a ref.");
  assert(0==cwal_value_prototype_get(e, v) && "Because cwal does not install prototypes for these!");
  cwal_value_prototype_set( v, s2_prototype_object(se) );
  if(cwal_value_is_hash(v)){
    s2_hash_dot_like_object(v, 1);
  }
  rc = cwal_var_decl(e, 0, "__GLOBAL", 8, v, CWAL_VAR_F_CONST)
    /* that effectively does: global.__GLOBAL = global */;
  RC;
#endif

#if 0
  { /* testing s2_enum_builder... */
    s2_enum_builder eb = s2_enum_builder_empty;
    cwal_value * v;
    char nameBuf[20] = {'e','n','t','r','y',0,0};
    cwal_int_t i;
    rc = s2_enum_builder_init(se, &eb, 17, "TestEnum");
    assert(!rc);
    RC;
    assert(cwal_value_is_vacuum_proof(eb.entries));
    for(i = 0; i < 3; ++i ){
      nameBuf[5] = '1' + i;
      v = cwal_new_integer(se->e, (i+1));
      VCHECK;
      cwal_value_ref(v);
      rc = s2_enum_builder_append(&eb, nameBuf, v);
      cwal_value_unref(v);
      v = 0;
    }
    rc = s2_enum_builder_seal(&eb, &v);
    assert(!rc);
    RC;
    assert(!eb.se);
    assert(!eb.entries);
    assert(v);
    assert(!cwal_value_is_vacuum_proof(v));
    assert(!cwal_value_refcount(v));
    cwal_value_ref(v);
    rc = cwal_var_decl(e, 0, "testEnum", 8, v, 0);
    cwal_value_unref(v);
    RC;
    MARKER(("testEnum (s2_enum_builder) installed! Try:\n"
            "foreach(testEnum=>k,v) print(k,v,v.value,testEnum[v])\n"));
  }
#endif
  
#undef FUNC2
#undef VCHECK
#undef RC
  end:
  return rc;
}

/**
   Callback for cliapp_repl().
*/
static int CliApp_repl_f_s2sh(char const * line, void * state){
  static cwal_hash_t prevHash = 0;
  cwal_size_t const nLine = cwal_strlen(line);
  if(nLine){
    s2_engine * const se = (s2_engine*)(state);
    cwal_value * v = 0;
    cwal_hash_t const lineHash = cwal_hash_bytes(line, nLine);
    int rc = 0;
    if(prevHash!=lineHash){
      /* Only add the line to the history if it seems to be different
         from the previous line. */
      cliapp_lineedit_add(line);
      prevHash = lineHash;
    }
    s2_set_interrupt_handlable( se );
    rc = s2_eval_cstr( se, 0, "shell input", line, (int)nLine, &v );
    s2_set_interrupt_handlable( 0 )
      /* keeps our ctrl-c handler from interrupting line reading or
         accidentially injecting an error state into se. */;
    s2sh_report_result(se, rc, &v);
    s2_engine_err_reset(se);
    if(App.shellExitFlag) return S2_RC_CLIENT_BEGIN;
  }
  return 0;
}

/**
   Enters an interactive-mode REPL loop if it can, otherwise it
   complains loudly about the lack of interactive editing and returns
   CWAL_RC_UNSUPPORTED.
*/
static int s2sh_interactive(s2_engine * se){
  int rc = 0;
  if(!cliApp.lineread.enabled){
      rc = s2_engine_err_set(se, CWAL_RC_UNSUPPORTED,
                             "Interactive mode not supported: "
                             "this shell was built without "
                             "line editing support.");
      s2sh_report_result(se, rc, 0);
      return rc;
  }

  cliapp_lineedit_load(NULL) /* ignore errors */;
  cwal_outputf(se->e, "s2 interactive shell. All commands "
               "are run in the current scope. Use your platform's EOF "
               "sequence on an empty line to exit. (Ctrl-D on Unix, "
               "Ctrl-Z(?) on Windows.) Ctrl-C might work, too.\n\n");
  rc = cliapp_repl( CliApp_repl_f_s2sh, &App.lnPrompt, 0, se );
  if(S2_RC_CLIENT_BEGIN==rc){
    /* Exit signal from the repl callback. */
    rc = 0;
  }
  cliapp_lineedit_save(NULL);
  s2_engine_sweep(se);
  cwal_output(se->e, "\n", 1);
  cwal_output_flush(se->e);
  return rc;
}


static int s2sh_main2( s2_engine * se ){
  int rc = 0;
  int i;
#if 0
  /* just testing post-init reconfig */
  {
    /* Configure the memory chunk recycler... */
    cwal_memchunk_config conf = cwal_memchunk_config_empty;
    conf.maxChunkCount = 0;
    conf.maxChunkSize = 1024 * 32;
    conf.maxTotalSize = 1024 * 256;
    conf.useForValues = 0;
    rc = cwal_engine_memchunk_config(se->e, &conf);
    assert(!rc);
  }
#endif

  if(!App.cleanroom){
    if(App.installShellApi>0
       || (App.interactive>0 && App.installShellApi<0)){
      assert(App.s2Global);
      if((rc = s2_install_shell_api(se, App.s2Global, "shell"))){
        return rc;
      }
    }
  }

  assert(App.scripts.nScripts <= S2SH_MAX_SCRIPTS);
  for( i = 0; i < App.scripts.nScripts; ++i ){
    char const * script = App.scripts.e[i].src;
    /*VERBOSE(2,("Running script #%d: %s\n", i+1, script));*/
    if(!*script) continue;
    if(App.scripts.e[i].isFile){
      rc = s2sh_run_file(se, script);
      cwal_output_flush(se->e);
      /* s2_engine_sweep(se); */
      if(rc) break;
    }else{
      cwal_value * rv = 0;
      rc = s2_eval_cstr( se, 0, "-e script",
                         script, cwal_strlen(script),
                         App.verbosity ? &rv : NULL );
      s2sh_report_result(se, rc, &rv);
      if(rc) break;
    }
  }
  /*MARKER(("Ran %d script(s)\n", i));*/
  if(!rc && App.interactive>0){
    rc = s2sh_interactive(se);
  }
  return rc;
}

/**
   If App.enableArg0Autoload and a file named $S2SH_INIT_SCRIPT or
   (failing that) the same as App.appName, minus any ".exe" or ".EXE"
   extension, but with an ".s2" extension, that script is loaded. If
   not found, 0 is returned. If loading or evaling the (existing) file
   fails, non-0 is returned and the error is reported.
*/
static int s2sh_autoload(s2_engine * se){
  int rc = 0;
  char const * scriptName = 0;
  if(!App.enableArg0Autoload || !App.appName || !*App.appName) return 0;
  else if(S2_DISABLE_FS_READ & s2_disable_get(se)){
    VERBOSE(1,("NOT loading init script because the --s2-disable=fs-read "
               "flag is active.\n"));
    return 0;
  }
  else if( !(scriptName = getenv("S2SH_INIT_SCRIPT")) ){
    /*
      See if $0.s2 exists...

      FIXME: this doesn't work reliably if $0 is relative, e.g.
      found via $PATH. We need to resolve its path to do this
      properly, but realpath(3) is platform-specific.
    */
    enum { BufSize = 1024 * 3 };
    static char fn[BufSize] = {0};
    cwal_size_t slen = 0;
    char const * exe = 0;
    char const * arg0 = App.appName;
    assert(arg0);
    exe = strstr(arg0, ".exe");
    if(!exe) exe = strstr(arg0, ".EXE");
    if(!exe) exe = strstr(arg0, ".Exe");
    slen = cwal_strlen(arg0) - (exe ? 4 : 0);
    rc = sprintf(fn, "%.*s.s2", (int)slen, arg0);
    assert(rc<BufSize-1);
    rc = 0;
    scriptName = fn;
  }
  if(s2_file_is_accessible(scriptName, 0)){
    if(App.cleanroom){
      VERBOSE(1,("Clean-room mode: NOT auto-loading script [%s].\n", scriptName));
    }else{
      cwal_value * rv = 0;
      VERBOSE(2,("Auto-loading script [%s].\n", scriptName));
      rc = s2_eval_filename(se, 1, scriptName, -1, &rv);
      if(rc){
        MESSAGE(("Error auto-loading init script [%s]: "
                 "error #%d (%s).\n", scriptName,
                 rc, cwal_rc_cstr(rc)));
        s2sh_report_result(se, rc, &rv);
      }
      else{
        cwal_refunref(rv);
      }
    }
  }
  return rc;
}

int s2sh_main(int argc, char const * const * argv){
  enum {
  /* If either of these is 0 then the corresponding engine gets
     allocated dynamically. In practice there's generally no reason to
     prefer dynamic allocation over stack allocation. */
  UseStackCwalEngine = 1 /* cwal_engine */,
  UseStackS2Engine = 1 /* s2_engine */
  };
  cwal_engine E = cwal_engine_empty;
  cwal_engine * e = UseStackCwalEngine ? &E : 0;
  cwal_engine_vtab vtab = cwal_engine_vtab_basic;
  s2_engine SE = s2_engine_empty;
  s2_engine * se = UseStackS2Engine ? &SE : 0;
  int rc = 0;

  if(argc || argv){/*possibly unused var*/}
  setlocale(LC_CTYPE,"") /* supposedly important for the JSON-parsing bits? */;

  vtab.tracer = cwal_engine_tracer_FILE;
  vtab.tracer.state = stdout;
  vtab.hook.on_init = s2sh_init_engine;
  vtab.interning.is_internable = cstr_is_internable;
  vtab.outputer.state.data = stdout;
  assert(cwal_finalizer_f_fclose == vtab.outputer.state.finalize);

  if(App.outFile){
    FILE * of = (0==strcmp("-",App.outFile))
      ? stdout
      : fopen(App.outFile,"wb");
    if(!of){
      rc = CWAL_RC_IO;
      MARKER(("Could not open output file [%s].\n", App.outFile));
      goto end;
    }
    else {
      vtab.outputer.state.data = of;
      vtab.outputer.state.finalize = cwal_finalizer_f_fclose;
      vtab.tracer.state = of;
      vtab.tracer.close = NULL
        /* Reminder: we won't want both outputer and tracer
           to close 'of'.
        */;
    }
  }

  vtab.memcap = App.memcap;

  rc = cwal_engine_init( &e, &vtab );
  if(rc){
    cwal_engine_destroy(e);
    goto end;
  }

  se = UseStackS2Engine ? &SE : s2_engine_alloc(e);
  assert(se);
  assert(UseStackS2Engine ? !se->allocStamp : (se->allocStamp == e));

  if((rc = s2_engine_init( se, e ))) goto end;
  if(App.cleanroom){
    VERBOSE(1,("Clean-room mode: NOT installing globals/prototypes.\n"));
  }else{
    if((rc = s2_install_core_prototypes(se))) goto end;
    else if( (rc = s2sh_setup_s2_global(se)) ) goto end;
  }
  if(App.zDisableFlags){
    rc = s2_disable_set_cstr(se, App.zDisableFlags, -1, 0);
    if(rc) goto end;
  }
  se->sweepInterval += App.addSweepInterval;
  se->vacuumInterval += App.addVacuumInterval;
  se->flags.traceTokenStack = App.traceStacks;
  se->flags.traceAssertions = App.traceAssertions;
  se->flags.traceSweeps = App.traceSweeps;
  s2_set_interrupt_handlable( se )
    /* after infrastructure, before the auto-loaded script, in case it
       contains an endless loop or some such and needs to be
       interrupted.
    */;
#if defined(S2_SHELL_EXTEND)
  if(!App.cleanroom){
    if( (rc = S2_SHELL_EXTEND_FUNC_NAME(se, App.s2Global, argc, argv)) ) goto end;
  }else{
    VERBOSE(1,("Clean-room mode: NOT running client-side shell extensions.\n"));
  }
#endif

  if(App.cleanroom){
    VERBOSE(1,("Clean-room mode: NOT auto-loading init script.\n"));
  }else{
    rc=s2sh_autoload(se);
  }
  s2_set_interrupt_handlable( 0 );
  if(rc) goto end;
       
  rc = s2sh_main2(se);
  App.s2Global = 0;

  end:
  s2_set_interrupt_handlable( 0 );
  if(App.showMetrics){
    s2_dump_metrics(se);
  }
#if 1
  s2sh_report_engine_error(se);
#endif
  assert(!se->st.vals.size);
  assert(!se->st.ops.size);
  s2_engine_finalize(se);
  return rc;
}


/**
   Pushes scr to App.scripts if there is space for it, else outputs an
   error message and returns non-0. isFile must be true if scr refers
   to a filename, else it is assumed to hold s2 script code.
*/
static int s2sh_push_script( char const * scr, int isFile ){
  const int n = App.scripts.nScripts;
  if(S2SH_MAX_SCRIPTS==n){
    MARKER(("S2SH_MAX_SCRIPTS (%d) exceeded: give fewer scripts or "
            "rebuild with a higher S2SH_MAX_SCRIPTS.\n",
            S2SH_MAX_SCRIPTS));
    return CWAL_RC_RANGE;
  }
  App.scripts.e[n].isFile = isFile;
  App.scripts.e[n].src = scr;
  VERBOSE(3,("Pushed %s #%d: %s\n", isFile ? "file" : "-e script",
             n+1, App.scripts.e[n].src));
  ++App.scripts.nScripts;
  return 0;
}

static void s2sh_help_header_once(){
  static int once = 0;
  if(once) return;
  once = 1;
  cliapp_print("s2 scripting engine shell #%d\n",
               S2SH_VERSION);
}

static void s2sh_show_version(int detailedMode){
  s2sh_help_header_once();
  cliapp_print("cwal/s2 version: %s\n",
               cwal_build_info()->versionString);
  cliapp_print("Project site: https://fossil.wanderinghorse.net/r/cwal\n");
  if(detailedMode){
    cwal_build_info_t const * const bi = cwal_build_info();
    cliapp_print("\nlibcwal compile-time info:\n\n");
#define STR(MEM) cliapp_print("\t%s = %s\n", #MEM, bi->MEM)
    /*STR(versionString);*/
    STR(cppFlags);
    STR(cFlags);
    STR(cxxFlags);
#undef STR

#define SZ(MEM) cliapp_print("\t%s = %"CWAL_SIZE_T_PFMT"\n", #MEM, (cwal_size_t)bi->MEM)
    SZ(size_t_bits);
    SZ(int_t_bits);
    SZ(maxStringLength);
#undef SZ

#define BUL(MEM) cliapp_print("\t%s = %s\n", #MEM, bi->MEM ? "true" : "false")
    BUL(isJsonParserEnabled);
    BUL(isDebug);
#undef BUL

    cliapp_print("\n");
  }
}

/**
   Parses and returns the value of an unsigned int CLI switch. Assigns
   *rc to 0 on success, non-0 on error.
*/
static cwal_size_t s2sh_parse_switch_unsigned(CliAppSwitch const * s, int * rc){
  cwal_int_t i = 0;
  if(!s2_cstr_parse_int(s->value, -1, &i)){
    WARN(("-cap-%s must have a positive integer value.\n", s->key));
    *rc = CWAL_RC_MISUSE;
    return 0;
  }else if(i<0){
    WARN(("-cap-%s must have a positive value (got %d).\n", s->key, (int)i));
    *rc = CWAL_RC_RANGE;
    return 0;
  }else{
    *rc = 0;
    return (cwal_size_t)i;
  }
}


/**
   The help handler for a single CliAppSwitch. It requires that all
   switches are passed to it in the order they are defined at the app
   level, as it applies non-inuitive semantics to certain entries,
   e.g. collapsing -short and --long-form switches into a single
   entry.
*/
static int s2sh_help_switch_visitor(CliAppSwitch const *s,
                                    void * /* ==>int*  */ verbosity){
  static CliAppSwitch const * skip = 0;
  CliAppSwitch const * next = s+1
    /* this is safe b/c the list ends with an empty sentinel entry */;
  int group = 0;
  static int prevContinue = 0;
  if(s->opaque & SW_UNDOCUMENTED){
    return 0;
  }else if(skip == s){
      skip = 0;
      return 0;
  }
  switch(s->opaque & SW_GROUPS){
    case SW_GROUP_0: break;
    case SW_GROUP_1: group = 1; break;
    case SW_GROUP_2: group = 2; break;
  }
  assert(s->dash>=-1 && s->dash<=2);
  assert(group>=0 && group<3);
  /*MARKER(("group %d (%d) %s\n", group, *((int const*)verbosity), s->key));*/
  if(group>*((int const*)verbosity)){
      return CWAL_RC_BREAK;
  }
  if(s->opaque & SW_GROUPS){
    /* Use s->brief as a label for this section then return. */
    if(s->brief && *s->brief){
      char const * splitter = "===============================";
      cliapp_print("\n%s\n%s\n%s\n", splitter, s->brief, splitter);
    }
    return 0;
  }
  if((s->opaque & SW_ID_MASK) && (next->opaque==s->opaque)){
    /* Collapse long/short options into one entry. We expect the long
       entry to be the first one. */
    /* FIXME? This only handles collapsing 2 options, but s2sh
       help supports -?/-help/--help. */
      skip = next;
      cliapp_print("\n  %s%s | ", cliapp_flag_prefix(skip->dash),
                   skip->key);
  }else if(!next->key){
      skip = 0;
  }
  /*MARKER(("switch entry: brief=%s, details=%s\n", s->brief, s->details));*/
  if(s->key && *s->key){
    cliapp_print("%s%s%s%s%s%s%s\n",
                 skip ? "" : "\n  ",
                 cliapp_flag_prefix(s->dash), s->key,
                 s->value ? " = " : "",
                 s->value ? s->value : "",
                 s->brief ? "\n      ": "",
                 s->brief ? s->brief : "");
    prevContinue = s->opaque;
  }else{
    switch(s->opaque){
      case SW_CONTINUE:
        assert(s->brief && *s->brief);
        if(prevContinue==SW_INFOLINE){
          cliapp_print("%s\n", s->brief);
        }else{
          cliapp_print("      %s\n", s->brief);
        }
        break;
      case SW_INFOLINE:
        assert(s->brief && *s->brief);
        cliapp_print("\n%s\n", s->brief);
        prevContinue = s->opaque;
        break;
      default:
        prevContinue = s->opaque;
        break;
    }
  }
  return 0;
}

static void s2sh_show_help(){
  int maxGroup = App.verbosity;
  char const * splitter = 1 ? "" :
    "------------------------------------------------------------\n";
  int n = 0;
  s2sh_show_version(0);
  cliapp_print("\nUsage: %s [options] [file | -f file]\n",
               App.appName);
  cliapp_print("%s",splitter);
  cliapp_switches_visit( s2sh_help_switch_visitor, &maxGroup );
  cliapp_print("\n%sNotes:\n",splitter); 
  cliapp_print("\n%d) Flags which require a value may be provided as (--flag=value) or "
        "(--flag value).\n", ++n);
  cliapp_print("\n%d) All flags after -- are ignored by the shell but "
               "made available to script code via the s2.ARGV object.\n", ++n);
  cliapp_print("Minor achtung: the script-side arguments parser uses --flag=VALUE, "
               "whereas this app's flags accept (--flag VALUE)!\n");

  if(maxGroup<2){
      cliapp_print("\n%d) Some less obscure options were not shown. "
                   "Use -v once or twice with -? for more options.\n", ++n);
  }
  puts("");
}

static int s2shGotHelpFlag = 0;
static int s2sh_arg_callback_common(int ndx, CliAppSwitch const * s,
                                    CliAppArg * a){
  int const sFlags = s ? s->opaque : 0;
  int const sID = sFlags & SW_ID_MASK;
  int rc = 0;
  if(0 && a){
    MARKER(("switch callback sFlags=%06x, sID=%d, SW_ESCRIPT=%d: #%d %s%s%s%s\n",
            sFlags, sID, SW_ESCRIPT,
            ndx, cliapp_flag_prefix(a->dash), a->key,
            a->value ? "=" : "",
            a->value ? a->value : ""));
  }
  if(!a/* end of args - there's nothing for use to do in this case */){
    return 0;
  }
  switch(sID){
    case SW_ASSERT_0: App.traceAssertions = 0; break;
    case SW_ASSERT_1: ++App.traceAssertions; break;
    case SW_AUTOLOAD_0: App.enableArg0Autoload = 0; break;
    case SW_AUTOLOAD_1: App.enableArg0Autoload = 1; break;
    case SW_CLAMPDOWN:
      WARN(("Clampdown mode has been removed."));
      return CWAL_RC_UNSUPPORTED;
    case SW_CLEANROOM: App.cleanroom = 1; break;
    case SW_DISABLE: App.zDisableFlags = a->value; break;
    case SW_ESCRIPT:
      assert(a->value && "Should have been captured by cliapp.");
      rc = s2sh_push_script( a->value, 0 );
      if(App.interactive<0) App.interactive=0;
      break;
    case SW_HELP: ++s2shGotHelpFlag; break;
    case SW_HIST_FILE_1: cliApp.lineread.historyFile = a->value; break;
    case SW_HIST_FILE_0: cliApp.lineread.historyFile = 0; break;
    case SW_INFILE:
      assert(a->value && "Should have been captured by cliapp.");
      rc = s2sh_push_script( a->value, 1 );
      if(!rc) App.inFile = a->value;
      break;
    case SW_INTERACTIVE: App.interactive = 1; break;
    case SW_INTERNAL_FU: ++App.enableInternalFu; break;
    case SW_MEM_FAST0: App.memcap.forceAllocSizeTracking = 0; break;
    case SW_MEM_FAST1: App.memcap.forceAllocSizeTracking = 1; break;
    case SW_MCAP_TOTAL_ALLOCS:
      App.memcap.maxTotalAllocCount =
        s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_MCAP_TOTAL_BYTES:
      App.memcap.maxTotalMem = s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_MCAP_CONC_ALLOCS:
      App.memcap.maxConcurrentAllocCount =
        s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_MCAP_CONC_BYTES:
      App.memcap.maxConcurrentMem = s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_MCAP_SINGLE_BYTES:
      App.memcap.maxSingleAllocSize =
        s2sh_parse_switch_unsigned(s, &rc);
      break;
    case SW_METRICS: App.showMetrics = 1; break;
    case SW_MOD_INIT_0: App.initStaticModules = 0; break;
    case SW_MOD_INIT_1: App.initStaticModules = 1; break;
    case SW_OUTFILE: App.outFile = a->value; break;
    case SW_RE_C0: App.maxChunkCount = 0; break;
    case SW_RE_C1: App.maxChunkCount = 30; break;
    case SW_RE_S0: App.enableStringInterning = 0; break;
    case SW_RE_S1: App.enableStringInterning = 1; break;
    case SW_RE_V0: App.enableValueRecycling = 0; break;
    case SW_RE_V1: App.enableValueRecycling = 1; break;
    case SW_SCOPE_H0: App.scopesUseHashes = 0; break;
    case SW_SCOPE_H1: App.scopesUseHashes = 1; break;
    case SW_SHELL_API_0: App.installShellApi = 0; break;
    case SW_SHELL_API_1: App.installShellApi = 1; break;
    case SW_TRACE_CWAL:
      App.cwalTraceFlags = CWAL_TRACE_SCOPE_MASK
        | CWAL_TRACE_VALUE_MASK
        | CWAL_TRACE_MEM_MASK
        | CWAL_TRACE_FYI_MASK
        | CWAL_TRACE_ERROR_MASK;
      break;
    case SW_TRACE_STACKS_0: App.traceStacks = 0; break;
    case SW_TRACE_STACKS_1: ++App.traceStacks; break;
    case SW_TRACE_SWEEP_0: App.traceSweeps = 0; break;
    case SW_TRACE_SWEEP_1: ++App.traceSweeps; break;
    case SW_VERBOSE: ++App.verbosity; break;
    case SW_SHOW_SIZEOFS:
    default: break;
  }
  return rc;
}

static int dump_args_visitor(CliAppArg const * p, int i, void * state){
  if(state){/*unused*/}
  assert(p->key);
  cliapp_print("argv[%d]  %s%s%s%s\n",i,
               cliapp_flag_prefix(p->dash), p->key,
               p->value ? "=" : "",
               p->value ? p->value : "");
  return 0;
}

static void s2sh_dump_args(){
  puts("CLI arguments:");
  cliapp_args_visit(dump_args_visitor, 0, 1)
    /* Over-engineering, just to test that cliapp code. */;
}

int main(int argc, char const * const * argv){
  int rc = 0;
  char const * arg = 0;
  int gotNoOp = 0
    /* incremented when we get a "no-op" flag. These are
       flags which perform some work without invoking the
       interpreter, and this flag lets us distinguish between
       not getting any options and getting some which did
       something useful (--help, --version, etc)*/;

  s2_static_init();
  App.appName = argv[0];
  cliApp.switches = s2sh_cliapp_switches();
  cliApp.argCallack = s2sh_arg_callback_common;
  assert(!App.shellExitFlag);
  memset(&App.scripts,0,sizeof(App.scripts));
  App.memcap.forceAllocSizeTracking = 0;
  
  if((arg = getenv("S2SH_HISTORY_FILE"))){
    App.editHistoryFile = arg;
  }
  cliApp.lineread.historyFile = App.editHistoryFile;

  rc = cliapp_process_argv(argc, argv, 0);
  if(rc) rc = CWAL_RC_MISUSE
           /* It's *possibly* not a CWAL_RC_ code, to we assume misuse. */;
  if(rc) goto end;
  App.scriptArgc = cliApp.doubleDash.argc;
  App.scriptArgv = cliApp.doubleDash.argv;

  if(0){
    CliAppArg const * p;
    if(0){
      if(!rc){
        s2sh_dump_args();
        while((p=cliapp_arg_nonflag())){
          MARKER(("Non-flag arg: %s\n",p->key));
        }
        cliapp_arg_nonflag_rewind();
      }
    }
  }

  if(s2shGotHelpFlag){
    s2sh_show_help();
    gotNoOp = 1;
  }else if(cliapp_arg_flag("V","version", 0)){
    s2sh_show_version(App.verbosity);
    gotNoOp = 1;
  }else if(!App.inFile){
      /* Treat the first non-flag arg as an -f flag if no -f was
         explicitely specified. */
    CliAppArg const * ca;
    assert(argc>1 ? cliApp.cursorNonflag>0 : 1);
    ca = cliapp_arg_nonflag();
    if(ca){
      rc = s2sh_push_script( ca->key, 1 );
      if(rc) goto end;
      else App.inFile = ca->key;
    }
  }
#if 1 == S2SH_VERSION
  if(cliapp_arg_flag("z",0,0)){
    gotNoOp = 1;
#define SO(T) MESSAGE(("sizeof("#T")=%d\n", (int)sizeof(T)))
    SO(s2_stoken);
    SO(s2_ptoken);
    SO(s2_ptoker);
    /* SO(s2_op); */
    SO(s2_scope);
    SO(cwal_scope);
    SO(s2_engine);
    SO(cwal_engine);
    SO(cwal_memchunk_overlay);
    SO(cwal_list);
#undef SO
    MESSAGE(("sizeof(s2_func_state)=%d\n",
             (int)s2_sizeof_script_func_state()));
  }
#endif /* -z flag */

  if(!gotNoOp && App.inFile && cliapp_arg_nonflag()){
    WARN(("Extraneous non-flag arguments provided.\n"));
    rc = CWAL_RC_MISUSE;
    goto end;
  }

  assert(!rc);
  if(!gotNoOp){
    if(!App.inFile && App.interactive<0
       && !App.scripts.nScripts){
#ifdef S2_OS_UNIX
      if(isatty(STDIN_FILENO)){
        App.interactive = 1;
      }else App.inFile = "-";
#else
      App.inFile = "-";
#endif
    }
    rc = s2sh_main(argc, argv);
  }

  end:
  {
    char const * errMsg = cliapp_err_get();
    if(errMsg){
      WARN(("%s\n", errMsg));
    }
  }
  if(App.verbosity>0 && rc){
    WARN(("rc=%d (%s)\n", rc, cwal_rc_cstr(rc)));
  }
  return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

Deleted bindings/s2/shell_extend.c.

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
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
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
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
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
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
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
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
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
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
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
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
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
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716




































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 
/* vim: set ts=2 et sw=2 tw=80: */
/**
   This file is meant to act as a client-side way to extend the default
   s2 shell app. It demonstrates examples of connecting C code to
   the s2 scripting environment.

   If shell.c is built with S2_SHELL_EXTEND defined, then it will assume
   this function will be linked in by the client:

   int s2_shell_extend(s2_engine * se, int argc, char const * const * argv);

   When built with the macro S2_SHELL_EXTEND, it calls that function right
   after it installs its own core functionality.
*/
#include <assert.h>
#if defined(S2_AMALGAMATION_BUILD)
#  include "s2_amalgamation.h"
#else
#  include "s2.h"
#endif
#include "fossil-scm/fossil.h"

#ifdef S2_OS_UNIX
# include <unistd.h> /* F_OK and friends */
#elif defined(S2_OS_WINDOWS)
# include <windows.h>
#endif

/* Only for debuggering... */
#include <stdio.h>
#define MARKER(pfexp)                                               \
  do{ printf("MARKER: %s:%d:%s():\t",__FILE__,__LINE__,__func__);   \
    printf pfexp;                                                   \
  } while(0)


/**
   Type IDs for type-safely mapping (void*) to cwal_native
   instances. Their types and values are irrelevant - we use only their
   addresses.
*/
static const int cwal_type_id_fsl_cx = 0;
static const int cwal_type_id_fsl_db = 0;
static const int cwal_type_id_fsl_stmt = 0;
#define FSL_TYPEID(X) (&cwal_type_id_##X)

#define VERBOSE_FINALIZERS 0

static void cwal_finalizer_f_fsl_db( cwal_engine * e, void * m ){
  if(m){
#if VERBOSE_FINALIZERS
    MARKER(("Finalizing fsl_db @%p\n", m));
#endif
    fsl_db_close( (fsl_db*)m );
  }
}

void cwal_finalizer_f_fsl_stmt( cwal_engine * e, void * m ){
  if(m){
#if VERBOSE_FINALIZERS
    MARKER(("Finalizing fsl_stmt @%p\n", m));
#endif
    fsl_stmt_finalize( (fsl_stmt*)m );
  }
}

static void cwal_finalizer_f_fsl_cx( cwal_engine * e, void * m ){
  if(m){
#if VERBOSE_FINALIZERS
    MARKER(("Finalizing fsl_cx @%p\n", m));
#endif
    fsl_cx_finalize( (fsl_cx*)m );
  }
}

#undef VERBOSE_FINALIZERS

static int cb_toss( cwal_callback_args const * args, int code,
                    char const * fmt, ... ){
  int rc;
  va_list vargs;
  s2_engine * se = s2_engine_from_args(args);
  cwal_native * nat = cwal_value_native_part(args->engine, args->self,
                                             FSL_TYPEID(fsl_cx) );
  cwal_value * natV = nat ? cwal_native_value(nat) : NULL;
  fsl_cx * f = natV ? cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL;
  assert(se);
  va_start(vargs,fmt);
  rc = cwal_exception_setfv(args->engine, code, fmt, vargs );
  if(f){
    /* Ensure that script-triggered errors do not unduly
       lie around, potentially propagating long after
       they are valid.
    */
    fsl_cx_err_reset(f);
  }
  va_end(vargs);
  return rc;
}

static int cb_toss_fsl( cwal_callback_args const * args,
                        fsl_cx * f ){
  int rc;
  assert(f && f->error.code);
  rc = (FSL_RC_OOM==f->error.code)
    ? CWAL_RC_OOM
    : cb_toss(args,
              f->error.code,
              "%.*s", (int)f->error.msg.used,
              (char const *)f->error.msg.mem );
  fsl_cx_err_reset(f);
  return rc;
}

static int cb_toss_db( cwal_callback_args const * args,
                       fsl_db * db ){
  int rc;
  assert(db && db->error.code);
  rc = (FSL_RC_OOM==db->error.code)
    ? CWAL_RC_OOM
    : cb_toss(args, db->error.code,
              "%s", (char const *)db->error.msg.mem );
  fsl_db_err_reset(db);
  return rc;
}

static cwal_value * fsl_cx_prototype( s2_engine * se );
static cwal_value * fsl_db_prototype( s2_engine * se );
static cwal_value * fsl_stmt_prototype( s2_engine * se );

/*
** Copies fb's state into cb.
*/
static int fsl_buffer_to_cwal_buffer( cwal_engine * e,
                                      fsl_buffer const * fb,
                                      cwal_buffer * cb ){
  cb->used = 0;
  return cwal_buffer_append(e, cb, fb->mem, (cwal_size_t)fb->used);
}

#if 0
/*
** Shallowly copies cb's state into fb. fb MUST NOT be modified
** via the fsl allocator while this is in place, because cwal's
** allocator is not guaranteed to be compatible (and won't be
** if certain memory capping options are enabled).
*/
static void cwal_buffer_to_fsl_buffer( cwal_buffer const * cb,
                                       fsl_buffer * fb ){
  fb->mem  = cb->mem;
  fb->capacity = cb->capacity;
  fb->used = cb->used;
  fb->cursor = 0;
}
#endif

/**
   Script usage:

   const sha1 = bufferInstance.sha1()
*/
static int cb_buffer_sha1_self( cwal_callback_args const * args,
                                cwal_value **rv ){
  cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
  char * sha;
  if(!buf){
    return cb_toss(args, CWAL_RC_TYPE,
                   "'this' is-not-a Buffer.");
  }
  sha = fsl_sha1sum_cstr( (char const*)buf->mem, (int)buf->used);
  if(!sha) return CWAL_RC_OOM;
  assert(sha[0] && sha[FSL_STRLEN_SHA1-1] && !sha[FSL_STRLEN_SHA1]);
  *rv = cwal_new_string_value(args->engine, sha, FSL_STRLEN_SHA1);
  /* Reminder: can't use zstring here b/c of potentially different
     allocators. */
  fsl_free(sha);
  if(*rv) return 0;
  else{
    return CWAL_RC_OOM;
  }
}
static int cb_buffer_sha3_self( cwal_callback_args const * args,
                                cwal_value **rv ){
  cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
  char * sha;
  if(!buf){
    return cb_toss(args, CWAL_RC_TYPE,
                   "'this' is-not-a Buffer.");
  }
  sha = fsl_sha3sum_cstr( (char const*)buf->mem, (int)buf->used);
  if(!sha) return CWAL_RC_OOM;
  assert(sha[0] && sha[FSL_STRLEN_K256-1] && !sha[FSL_STRLEN_K256]);
  *rv = cwal_new_string_value(args->engine, sha, FSL_STRLEN_K256);
  /* Reminder: can't use zstring here b/c of potentially different
     allocators. */
  fsl_free(sha);
  if(*rv) return 0;
  else{
    return CWAL_RC_OOM;
  }
}

/**
   Script usage:

   const md5 = bufferInstance.md5()
*/
static int cb_buffer_md5_self( cwal_callback_args const * args,
                               cwal_value **rv ){
  cwal_buffer * buf = cwal_value_buffer_part(args->engine, args->self);
  char * hash;
  if(!buf){
    return cb_toss(args, CWAL_RC_TYPE,
                   "'this' is-not-a Buffer.");
  }
  hash = fsl_md5sum_cstr( (char const*)buf->mem, (int)buf->used);
  if(!hash) return CWAL_RC_OOM;
  assert(hash[0] && hash[FSL_STRLEN_MD5-1] && !hash[FSL_STRLEN_MD5]);
  *rv = cwal_new_string_value(args->engine, hash, FSL_STRLEN_MD5);
  fsl_free(hash);
  if(*rv) return 0;
  else{
    return CWAL_RC_OOM;
  }
}

/*
** fsl_output_f() impl which forwards to cwal_output().
** state must be a (cwal_engine *). Reminder: must return
** a FSL_RC_xxx value, not CWAL_RC_xxx.
*/
static int fsl_output_f_cwal_output( void * state,
                                     void const * src,
                                     fsl_size_t n ){
  cwal_engine * e = (cwal_engine *)state;
  return cwal_output( e, src, (cwal_size_t)n )
    ? FSL_RC_IO
    : 0;
}

/*
** fsl_flush_f() impl which forwards to cwal_output_flush(). state
** must be a (cwal_engine *).
*/
static int fsl_flush_f_cwal_out( void * state ){
  cwal_engine * e = (cwal_engine *)state;
  return cwal_output_flush(e);
}

static int s2_fsl_openmode_to_flags(char const * openMode, int startFlags){
  int openFlags = startFlags;
  for( ; *openMode; ++openMode ){
    switch(*openMode){
      case 'r': openFlags |= FSL_OPEN_F_RO;
        break;
      case 'w': openFlags |= FSL_OPEN_F_RW;
        break;
      case 'c': openFlags |= FSL_OPEN_F_CREATE;
        break;
      case 'v': openFlags |= FSL_OPEN_F_SCHEMA_VALIDATE;
        break;
      case 'T': openFlags |= FSL_OPEN_F_TRACE_SQL;
        break;
      default:
        break;
    }
  }
  return openFlags;
}

/*
** Searches v and its prototype chain for a fsl_cx binding. If found,
** it is returned, else NULL is returned.
*/
static fsl_cx * cwal_value_fsl_cx_part( cwal_engine * e,
                                        cwal_value * v ){
  if(!v) return NULL;
  else {
    cwal_native * nv;
    fsl_cx * f;
    while(v){
      nv = cwal_value_get_native(v);
      f = nv
        ? (fsl_cx *)cwal_native_get( nv, FSL_TYPEID(fsl_cx) )
        : NULL;
      if(f) return f;
      else v = cwal_value_prototype_get(e, v);
    } while(v);
    return NULL;
  }
}


/*
** Creates a new cwal_value (cwal_native) wrapper for the given fsl_db
** instance. If addDtor is true then a finalizer is installed for the
** instance, else it is assumed to live native-side and gets no
** destructor installed (in which case we will eventually have a problem
** when such a db is destroyed outside of the script API, unless we
** rewrite these to use weak references instead, but that might require
** one more level of struct indirection). The role of the db
** is important so that we can get the proper filename. A role of
** FSL_DBROLE_NONE should be used for non-fossil-core db handles.
**
** On success, returns 0 and assigns *rv to the new Db value.
**
** If 0!=db->filename.used then the new value gets a 'name' property
** set to the contents of db->filename.
**
** Maintenance reminder: this routine must return cwal_rc_t codes, not
** fsl_rc_e codes.
**
** TODO? Wrap up cwal_weak_ref of db handle, instead of the db handle
** itself? We only need this if Fossil instances will have their DB
** instances manipulated from outside of script-space.
*/
static int fsl_db_new_native( s2_engine * se, fsl_cx * f,
                              fsl_db * db, char addDtor,
                              cwal_value **rv,
                              fsl_dbrole_e role){
  cwal_native * n;
  cwal_value * nv;
  int rc = 0;
  char const * fname = NULL;
  fsl_size_t nameLen = 0;
  cwal_value * tmpV = NULL;
  assert(se && db && rv);
  n = cwal_new_native(se->e, db,
                      addDtor ? cwal_finalizer_f_fsl_db : NULL,
                      FSL_TYPEID(fsl_db));
  if(!n) return CWAL_RC_OOM;
  nv = cwal_native_value(n);
  cwal_value_ref(nv);
  /* Set up  "filename" property. It's problematic because
     of libfossil's internal DB juggling :/.
  */
  if(f) fname = fsl_cx_db_file_for_role(f, role, &nameLen);
  if(!fname){
    fname = fsl_db_filename(db, &nameLen);
  }
  if(fname){
    tmpV = cwal_new_string_value(se->e, fname, (cwal_size_t)nameLen);
    cwal_value_ref(tmpV);
    rc = tmpV
      ? cwal_prop_set(nv, "filename", 8, tmpV)
      : CWAL_RC_OOM;
    cwal_value_unref(tmpV);
    tmpV = 0;
    if(rc){
      goto end;
    }
    fname = 0;
  }

  /* Set up  "name" property. */
  if(f) fname = fsl_cx_db_name_for_role(f, role, &nameLen);
  if(!fname){
    fname = fsl_db_name(db);
    nameLen = fname ? cwal_strlen(fname) : 0;
  }
  if(fname){
    tmpV =
      cwal_new_string_value(se->e, fname, (cwal_size_t)nameLen);
    cwal_value_ref(tmpV);
    rc = tmpV
      ? cwal_prop_set(nv, "name", 4, tmpV)
      : CWAL_RC_OOM;
    cwal_value_unref(tmpV);
    tmpV = NULL;
    if(rc){
      goto end;
    }
  }
  end:
  if(!rc){
    *rv = nv;
    cwal_value_unhand(nv);
    cwal_value_prototype_set( nv, fsl_db_prototype(se) );
  }else{
    /*
      Achtung: if addDtor then on error the dtor will be called
      here.
    */
    cwal_value_unref(nv);
  }
  return rc;
}


/* TODO: fix corner case: inheritence via multiple levels of Native
   types will break this. */
#define THIS_DB s2_engine * se = s2_engine_from_args(args);             \
  cwal_native * nat = cwal_value_native_part(args->engine, args->self, FSL_TYPEID(fsl_db)); \
  cwal_value * natV = nat ? cwal_native_value(nat) : NULL;              \
  fsl_db * db = natV ? (fsl_db*)cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL; \
  assert(se);          \
  if(!se){/*potentially unused var*/} \
  if(!db){ return cb_toss(args, FSL_RC_TYPE, \
                          "'this' is not (or is no longer) "            \
                          "a Db instance."); } (void)0

#define THIS_STMT s2_engine * se = s2_engine_from_args(args);           \
  cwal_native * nat = cwal_value_native_part(args->engine, args->self,FSL_TYPEID(fsl_stmt)); \
  cwal_value * natV = nat ? cwal_native_value(nat) : NULL;              \
  fsl_stmt * stmt = natV ? (fsl_stmt*)cwal_native_get( nat, FSL_TYPEID(fsl_stmt) ) : NULL; \
  if(!se){/*potentially unused var*/} \
  assert(se);          \
  if(!stmt){ return cb_toss(args, FSL_RC_TYPE, \
                            "'this' is not (or is no longer) "          \
                            "a Stmt instance."); } (void)0

#define THIS_F s2_engine * se = s2_engine_from_args(args); \
  cwal_native * nat = cwal_value_native_part(args->engine, args->self,FSL_TYPEID(fsl_cx)); \
  cwal_value * natV = nat ? cwal_native_value(nat) : NULL;                   \
  fsl_cx * f = natV ? (fsl_cx*)cwal_native_get( nat, FSL_TYPEID(fsl_cx) ) : NULL; \
  if(!se){/*potentially unused var*/} \
  assert(se); \
  if(!f){ return cb_toss(args, FSL_RC_TYPE, \
                         "'this' is not (or is no longer) "         \
                         "a Fossil Context instance."); } (void)0


static int cb_fsl_db_finalize( cwal_callback_args const * args,
                               cwal_value **rv ){
  THIS_DB;
  cwal_native_clear( nat, 1 );
  return 0;
}

static int cb_fsl_stmt_finalize( cwal_callback_args const * args,
                                 cwal_value **rv ){
  THIS_STMT;
  cwal_native_clear( nat, 1 );
  return 0;
}


int cb_fsl_db_ctor( cwal_callback_args const * args,
                    cwal_value **rv ){
  cwal_value * v = NULL;
  int rc;
  s2_engine * se = s2_engine_from_args(args);
  char const * fn;
  char const * openMode;
  cwal_size_t fnLen = 0;
  int openFlags = 0;
  fsl_db * dbP = 0;
  fsl_cx * f = cwal_value_fsl_cx_part(args->engine, args->self)
    /* It's okay if this is NULL. It will only be set when we
       aFossilContext.dbOpen() is called.
    */;
  assert(se);
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], &fnLen)
    : NULL;
  if(!fn){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting a string (db file name) argument.");
  }
  openMode = (args->argc>1)
    ? cwal_value_get_cstr(args->argv[1], NULL)
    : NULL;
  if(!openMode){
    openFlags = FSL_OPEN_F_RWC;
  }else{
    openFlags = s2_fsl_openmode_to_flags( openMode, openFlags );
  }
  dbP = fsl_db_malloc();
  if(!dbP) return CWAL_RC_OOM;
  dbP->f = f;
  rc = fsl_db_open( dbP, fn, openFlags );
  if(rc){
    if(dbP->error.msg.used){
      rc = cb_toss(args, FSL_RC_ERROR,
                   "Db open failed: code #%d: %s",
                   dbP->error.code,
                   (char const *)dbP->error.msg.mem);
    }else{
      rc = cb_toss(args, FSL_RC_ERROR,
                   "Db open failed "
                   "with code #%d (%s).", rc,
                   fsl_rc_cstr(rc));
    }
    fsl_db_close(dbP);
  }
  else{
    rc = fsl_db_new_native(se, f, dbP, 1, &v, FSL_DBROLE_NONE);
    if(rc){
      fsl_db_close(dbP);
      dbP = NULL;
      assert(!v);
    }
  }
  if(rc){
    assert(!v);
  }else{
    assert(v);
    *rv  = v;
  }
  return rc;
}


/**
   Returns a new cwal_array value containing the result column names
   of the given statement . Returns NULL on error, else an array value.
*/
static cwal_value * s2_fsl_stmt_col_names( cwal_engine * e,
                                           fsl_stmt * st ){
  cwal_value * aryV = NULL;
  cwal_array * ary = NULL;
  char const * colName = NULL;
  int i = 0;
  int rc = 0;
  cwal_value * newVal = NULL;
  assert(st);
  if( ! st->colCount ) return NULL;
  ary = cwal_new_array(e);
  if( ! ary ) return NULL;
  rc = cwal_array_reserve(ary, (cwal_size_t)st->colCount);
  if(rc) return NULL;
  aryV = cwal_array_value(ary);
  assert(ary);
  for( i = 0; (0==rc) && (i < st->colCount); ++i ){
    colName = fsl_stmt_col_name(st, i);
    if( ! colName ) rc = CWAL_RC_OOM;
    else{
      newVal = cwal_new_string_value(e, colName,
                                     cwal_strlen(colName));
      if( NULL == newVal ){
        rc = CWAL_RC_OOM;
      }
      else{
        rc = cwal_array_set( ary, i, newVal );
        if( rc ) cwal_value_unref( newVal );
      }
    }
  }
  if( 0 == rc ) return aryV;
  else{
    cwal_value_unref(aryV);
    return NULL;
  }
}

static int s2_fsl_setup_new_stmt(s2_engine * se,
                                 cwal_value * nv,
                                 fsl_stmt * st){
  int rc;
  cwal_value * v;
  cwal_engine * e = se->e;
#define STMT_INCLUDE_SQL 0
#if STMT_INCLUDE_SQL
  fsl_size_t sqlLen = 0;
  char const * sql;
#endif
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(K) VCHECK; rc = cwal_prop_set(nv, K, cwal_strlen(K), v); \
  if(rc) goto end;
#if STMT_INCLUDE_SQL
  /* Too costly, and so far never used. */
  sql = fsl_buffer_cstr2(&st->sql, &sqlLen);
  v = cwal_new_string_value(e, sql, (cwal_size_t)sqlLen)
    /* Reminder to self: we could get away with an X-string for 99.9%
       of use cases, but if someone holds a ref to that string
       after the statement is gone, blammo - cwal-level assertion
       at some point.
    */;
  SET("sql");
#endif
#undef STMT_INCLUDE_SQL
  v = cwal_new_integer(e, (cwal_int_t)st->colCount);
  SET("columnCount");
  v = cwal_new_integer(e, (cwal_int_t)st->paramCount);
  SET("parameterCount");
  if(st->colCount){
    v = s2_fsl_stmt_col_names(e, st);
    SET("columnNames");
  }
  cwal_value_prototype_set(nv, fsl_stmt_prototype(se));
#undef SET
#undef VCHECK
  assert(!rc);
  end:
  return rc;
}

static int cb_fsl_db_prepare( cwal_callback_args const * args,
                              cwal_value **rv ){
  fsl_stmt st = fsl_stmt_empty;
  int rc;
  char const * sql;
  cwal_size_t sqlLen = 0;
  cwal_value * nv = NULL;
  cwal_engine * e = args->engine;
  fsl_stmt * st2 = NULL;
  THIS_DB;
  sql = args->argc
    ? cwal_value_get_cstr(args->argv[0], &sqlLen)
    : NULL;
  if(!sql || !sqlLen){
    return cwal_exception_setf(e,
                               FSL_RC_MISUSE,
                               "Expecting a non-empty string "
                               "argument (SQL).");
  }
  assert(!st.stmt);
  rc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql);
  if(rc){
    rc = cb_toss_db(args, db);
    assert(!st.stmt);
  }else{
    st2 = fsl_stmt_malloc();
    nv = st2
      ? cwal_new_native_value(e,
                              st2, cwal_finalizer_f_fsl_stmt,
                              FSL_TYPEID(fsl_stmt))
      : NULL;
    if(!nv){
      if(st2) fsl_free(st2);
      fsl_stmt_finalize(&st);
      st2 = NULL;
      rc = CWAL_RC_OOM;
    }else{
      void const * kludge = st2->allocStamp;
      *st2 = st;
      st2->allocStamp = kludge;
      rc = s2_fsl_setup_new_stmt(se, nv, st2);
      if(!rc) *rv = nv;
    }
  }
  if(rc && nv){
    cwal_value_unref(nv);
  }        
  return rc;
}

static int cb_fsl_db_filename( cwal_callback_args const * args,
                               cwal_value **rv ){
  char const * fname = NULL;
  fsl_size_t nameLen = 0;
  THIS_DB;
  fname = fsl_db_filename(db, &nameLen);
  *rv = fname
    ? cwal_new_string_value(args->engine, fname, nameLen)
    : cwal_value_null();
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_db_name( cwal_callback_args const * args,
                               cwal_value **rv ){
  char const * fname = NULL;
  THIS_DB;
  fname = fsl_db_name(db);
  *rv = fname
    ? cwal_new_string_value(args->engine, fname, fsl_strlen(fname))
    : cwal_value_null();
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Extracts result column ndx from st and returns a "the closest
   approximation" of its type in cwal_value form by assigning it to
   *rv. Returns 0 on success. On errror *rv is not modified.
   Does not trigger a th1ish/cwal exception on error.
*/
static int s2_fsl_stmt_to_value( cwal_engine * e,
                                 fsl_stmt * st,
                                 int ndx,
                                 cwal_value ** rv ){
  int vtype = sqlite3_column_type(st->stmt, ndx);
  switch( vtype ){
    case SQLITE_NULL:
      *rv = cwal_value_null();
      return 0;
    case SQLITE_INTEGER:
      *rv = cwal_new_integer( e,
                              (cwal_int_t)fsl_stmt_g_int64(st,ndx) );
      break;
    case SQLITE_FLOAT:
      *rv = cwal_new_double( e,
                             (cwal_double_t)fsl_stmt_g_double(st, ndx) );
      break;
    case SQLITE_BLOB: {
      int rc;
      fsl_size_t slen = 0;
      void const * bl = 0;
      rc = fsl_stmt_get_blob(st, ndx, &bl, &slen);
      if(rc){
        /* FIXME: we need a fsl-to-cwal rc converter (many of them line up). */
        return CWAL_RC_ERROR;
      }else if(!bl){
        *rv = cwal_value_null();
      }else{
        cwal_buffer * buf = cwal_new_buffer(e, (cwal_size_t)(slen+1));
        if(!buf) return CWAL_RC_OOM;
        rc = cwal_buffer_append( e, buf, bl, (cwal_size_t)slen );
        if(rc){
          assert(!*rv);
          cwal_value_unref(cwal_buffer_value(buf));
        }else{
          *rv = cwal_buffer_value(buf);
        }
      }
      break;
    }
    case SQLITE_TEXT: {
      fsl_size_t slen = 0;
      char const * str = 0;
      str = fsl_stmt_g_text( st, ndx, &slen );
      *rv = cwal_new_string_value(e, slen ? str : NULL,
                                  (cwal_size_t) slen);
      break;
    }
    default:
      return cwal_exception_setf(e, CWAL_RC_TYPE,
                                 "Unknown db column type (%d).",
                                 vtype);
  }
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Extracts all columns from st into a new cwal_object value.
   colNames is expected to hold the same number of entries
   as st has columns, and in the same order. The entries in
   colNames are used as the object's field keys.

   Returns NULL on error.
*/
static cwal_value * s2_fsl_stmt_row_to_object2( cwal_engine * e,
                                                fsl_stmt * st,
                                                cwal_array const * colNames ){
  int colCount;
  cwal_value * objV;
  cwal_object * obj;
  cwal_string * colName;
  int i;
  int rc;
  if( ! st ) return NULL;
  colCount = st->colCount;
  if( !colCount || (colCount>(int)cwal_array_length_get(colNames)) ) {
    return NULL;
  }
  obj = cwal_new_object(e);
  if( ! obj ) return NULL;
  objV = cwal_object_value( obj );
  cwal_value_ref(objV);
  for( i = 0; i < colCount; ++i ){
    cwal_value * v = NULL;
    cwal_value * colNameV = cwal_array_get( colNames, i );
    colName = cwal_value_get_string( colNameV );
    if( ! colName ) goto error;
    rc = s2_fsl_stmt_to_value( e, st, i, &v );
    if( rc ) goto error;
    cwal_value_ref(v);
    rc = cwal_prop_set_v( objV, colNameV, v );
    cwal_value_unref(v);
    if( rc ){
      goto error;
    }
  }
  cwal_value_unhand(objV);
  return objV;
  error:
  cwal_value_unref( objV );
  return NULL;
}

/**
   Extracts all columns from st into a new cwal_object value,
   using st's column names as the keys.

   Returns NULL on error.
*/
static cwal_value * s2_fsl_stmt_row_to_object( cwal_engine * e,
                                               fsl_stmt * st ){
  cwal_value * objV;
  cwal_object * obj;
  char const * colName;
  int i;
  int rc;
  if( ! st || !st->colCount ) return NULL;
  obj = cwal_new_object(e);
  if( ! obj ) return NULL;
  objV = cwal_object_value(obj);
  cwal_value_ref(objV);
  for( i = 0; i < st->colCount; ++i ){
    cwal_value * v = NULL;
    colName = fsl_stmt_col_name(st, i);
    if( ! colName ) goto error;
    rc = s2_fsl_stmt_to_value( e, st, i, &v );
    if( rc ) goto error;
    cwal_value_ref(v);
    rc = cwal_prop_set( objV, colName, cwal_strlen(colName), v );
    cwal_value_unref(v);
    if( rc ){
      goto error;
    }
  }
  cwal_value_unhand(objV);
  return objV;
  error:
  cwal_value_unref( objV );
  return NULL;
}

/**
   Appends all result columns from st to ar.
*/
static int s2_fsl_stmt_row_to_array2( cwal_engine * e,
                                      fsl_stmt * st,
                                      cwal_array * ar)
{
  int i;
  int rc = 0;
  for( i = 0; i < st->colCount; ++i ){
    cwal_value * v = NULL;
    rc = s2_fsl_stmt_to_value( e, st, i, &v );
    if( rc ) goto end;
    cwal_value_ref(v);
    rc = cwal_array_append( ar, v );
    cwal_value_unref(v);
    if( rc ){
      goto end;
    }
  }
  end:
  return rc;
}

/**
   Converts all column values from st into a cwal_array
   value. Returns NULL on error, else an array value.
*/
static cwal_value * s2_fsl_stmt_row_to_array( cwal_engine * e,
                                              fsl_stmt * st )
{
  cwal_array * ar;
  int rc = 0;
  if( ! st || !st->colCount ) return NULL;
  ar = cwal_new_array(e);
  if( ! ar ) return NULL;
  rc = s2_fsl_stmt_row_to_array2( e, st, ar );
  if(rc){
    cwal_array_unref(ar);
    return NULL;
  }else{
    return cwal_array_value(ar);
  }
}

/**
   mode: 0==Object, >0==Array, <0==no row data (just boolean
   indicator)
*/
static int s2_fsl_stmt_step_impl( cwal_callback_args const * args,
                                  cwal_value **rv,
                                  int mode ){
  int rc;
  int scode;
  THIS_STMT;
  scode = fsl_stmt_step( stmt );
  switch(scode){
    case FSL_RC_STEP_DONE:
      rc = 0;
      *rv = cwal_value_undefined();
      break;
    case FSL_RC_STEP_ROW:{
      if(0==mode){
        /* Object mode */
        cwal_array const * colNames =
          cwal_value_get_array( cwal_prop_get(args->self,
                                              "columnNames", 11) );
        *rv = colNames
          ? s2_fsl_stmt_row_to_object2(args->engine,
                                       stmt, colNames)
          : s2_fsl_stmt_row_to_object(args->engine, stmt);
      }else if(mode>0){
        /* Array mode */
        *rv = s2_fsl_stmt_row_to_array(args->engine, stmt);
      }else{
        /* "Normal" mode */
        *rv = cwal_value_true();
      }
      rc = *rv ? 0 : CWAL_RC_OOM;
      break;
    }
    default:
      rc = cb_toss_db(args, stmt->db);
      break;
  }
  return rc;
}

static int cb_fsl_stmt_step_array( cwal_callback_args const * args, cwal_value **rv ){
  return s2_fsl_stmt_step_impl( args, rv, 1 );
}

static int cb_fsl_stmt_step_object( cwal_callback_args const * args, cwal_value **rv ){
  return s2_fsl_stmt_step_impl( args, rv, 0 );
}

static int cb_fsl_stmt_step( cwal_callback_args const * args, cwal_value **rv ){
  return s2_fsl_stmt_step_impl( args, rv, -1 );
}

static int cb_fsl_stmt_reset( cwal_callback_args const * args, cwal_value **rv ){
  char resetCounter = 0;
  int rc;
  THIS_STMT;
  if(1 < args->argc) resetCounter = cwal_value_get_bool(args->argv[1]);
  rc = fsl_stmt_reset2(stmt, resetCounter);
  if(!rc) *rv = args->self;
  return rc
    ? cb_toss_db( args, stmt->db )
    : 0;
}

static int cb_fsl_stmt_row_to_array( cwal_callback_args const * args,
                                     cwal_value **rv ){
  int rc;
  THIS_STMT;
  if(!stmt->rowCount) return cb_toss(args, FSL_RC_MISUSE,
                                     "Cannot fetch row data from an "
                                     "unstepped statement.");
  *rv = s2_fsl_stmt_row_to_array(args->engine, stmt);
  if(*rv) rc = 0;
  else rc = cb_toss(args, CWAL_RC_ERROR,
                    "Unknown error converting statement "
                    "row to Array.");
  return rc;
}

static int cb_fsl_stmt_row_to_object( cwal_callback_args const * args,
                                      cwal_value **rv ){
  int rc;
  THIS_STMT;
  if(!stmt->rowCount) return cb_toss(args, FSL_RC_MISUSE,
                                     "Cannot fetch row data from "
                                     "an unstepped statement.");
  *rv = s2_fsl_stmt_row_to_object(args->engine, stmt);
  if(*rv) rc = 0;
  else rc = cb_toss(args, CWAL_RC_ERROR,
                    "Unknown error converting statement row "
                    "to Object.");
  return rc;
}


/**
   Tries to bind v to the given parameter column (1-based) of
   nv->stmt.  Returns 0 on success, triggers a script-side exception
   on error.
*/
static int s2_fsl_stmt_bind( cwal_engine * e,
                             fsl_stmt * st,
                             int ndx,
                             cwal_value * v ){
  int rc;
  int const vtype = v ? cwal_value_type_id(v) : CWAL_TYPE_NULL;
  if(ndx<1) {
    return cwal_exception_setf(e, FSL_RC_RANGE,
                               "Bind index %d is invalid: indexes are 1-based.",
                               ndx);
  }
  else if(ndx > st->paramCount) {
    return cwal_exception_setf(e, FSL_RC_RANGE,
                               "Bind index %d is out of range. Range=(1..%d).",
                               ndx, st->paramCount);
  }
  /*MARKER(("Binding %s to column #%u\n", cwal_value_type_name(v), ndx));*/
  switch( vtype ){
    case CWAL_TYPE_NULL:
    case CWAL_TYPE_UNDEF:
      rc = fsl_stmt_bind_null(st, ndx);
      break;
    case CWAL_TYPE_BOOL:
      rc = fsl_stmt_bind_int32(st, ndx, cwal_value_get_bool(v));
      break;
    case CWAL_TYPE_INTEGER:
      /* We have no way of knowing which type (32/64-bit) to bind
         here, so we'll guess. We could check the range, i guess,
         but for sqlite it makes little or no difference, anyway.
      */
      rc = fsl_stmt_bind_int64(st, ndx,
                               (int64_t)cwal_value_get_integer(v));
      break;
    case CWAL_TYPE_DOUBLE:
      rc = fsl_stmt_bind_double(st, ndx, (double)cwal_value_get_double(v));
      break;
    case CWAL_TYPE_BUFFER:
    case CWAL_TYPE_STRING: {
      /* FIXME: bind using the fsl_stmt_bind_xxx() APIs!!!
         string/buffer binding is currently missing.
      */
      cwal_size_t slen = 0;
      char const * cstr = cwal_value_get_cstr(v, &slen);
      if(!cstr){
        /* Will only apply to empty buffers (Strings are never
           NULL). But it's also possible that a buffer with
           length 0 has a non-NULL memory buffer. So we cannot,
           without further type inspection, clearly
           differentiate between a NULL and empty BLOB
           here. This distinction would seem to be (?) 
           unimportant for this particular use case, so
           fixing/improving it can wait.
        */
        rc = fsl_stmt_bind_null(st, ndx);
      }
#if 1
      else if(CWAL_TYPE_BUFFER==vtype){
        rc = fsl_stmt_bind_blob(st, ndx, cstr, (int)slen, 1);
      }
#endif
      else{
        rc = fsl_stmt_bind_text(st, ndx, cstr, (int)slen, 1);
      }
      break;
    }
    default:
      return cwal_exception_setf(e, FSL_RC_TYPE,
                                 "Unhandled data type (%s) for binding "
                                 "column %d.",
                                 cwal_value_type_name(v), ndx);
  }
  if(rc){
    fsl_size_t msgLen = 0;
    char const * msg = NULL;
    int const dbRc = fsl_db_err_get(st->db, &msg, &msgLen);
    rc = cwal_exception_setf(e, FSL_RC_DB,
                             "Binding column %d failed with "
                             "sqlite code #%d: %.*s", ndx,
                             dbRc, (int)msgLen, msg);
  }
  return rc;
}

static int s2_fsl_stmt_bind_values_a( cwal_engine * e,
                                      fsl_stmt * st,
                                      cwal_array const * src){
  int const n = (int)cwal_array_length_get(src);
  int i = 0;
  int rc = 0;
  for( ; !rc && (i < n); ++i ){
    rc = s2_fsl_stmt_bind( e, st, i+1,
                           cwal_array_get(src,(cwal_size_t)i) );
  }
  return rc;
}

/**
   Internal helper for binding by name.
*/
typedef struct {
  fsl_stmt * st;
  cwal_engine * e;
} BindByNameState;

/**
   Property visitor helper for binding parameters by name.
*/
static int cwal_kvp_visitor_f_bind_by_name( cwal_kvp const * kvp, void * state ){
  cwal_size_t keyLen = 0;
  cwal_value const * v = cwal_kvp_key(kvp);
  char const * key = cwal_value_get_cstr(v, &keyLen);
  BindByNameState const * bbn = (BindByNameState const *)state;
  int ndx;
  if(!key){
    if(!cwal_value_is_integer(v)){
      return 0 /* non-string property. */;
    }else{
      ndx = (int)cwal_value_get_integer(v);
    }
  }else{
    ndx = fsl_stmt_param_index(bbn->st, key);
    if(ndx<=0){
      return cwal_exception_setf(bbn->e, CWAL_RC_RANGE, "Parameter name '%.*s' "
                                 "does not resolve to an index. (Maybe missing "
                                 "the leading ':' or '$' part of the param name?)",
                                 (int)keyLen, key);
    }
  }
  return s2_fsl_stmt_bind(bbn->e, bbn->st, ndx, cwal_kvp_value(kvp));
}

/**
   Internal helper for binding values to statements.

   bind may be either an Array of values to bind, a container of param
   names (incl. ':' or '$' prefix), or a single value, to bind at the
   given index. For arrays/containers, the given index is ignored. As
   a special case, if bind === cwal_value_undefined() then it is
   simply ignored.
*/
static int s2_fsl_stmt_bind_proxy( cwal_engine * e,
                                   fsl_stmt * st,
                                   int ndx,
                                   cwal_value * bind){
  int rc = 0;
  if(cwal_value_undefined() == bind) rc = 0;
  else if(cwal_value_is_array(bind)){
    rc = s2_fsl_stmt_bind_values_a(e, st,
                                   cwal_value_get_array(bind));
  }else if(cwal_props_can(bind)){
    BindByNameState bbn;
    bbn.e = e;
    bbn.st = st;
    rc = cwal_props_visit_kvp(bind, cwal_kvp_visitor_f_bind_by_name,
                              &bbn);
  }else{
    rc = s2_fsl_stmt_bind( e, st, ndx, bind);
  }
  return rc;
}

#if 0
static int s2_fsl_stmt_bind_from_args( cwal_engine * e,
                                       fsl_stmt *st,
                                       cwal_callback_args const * args,
                                       uint16_t startAtArg ){
  int rc = 0;
  cwal_value * bind = (args->argc < startAtArg)
    ? args->argv[startAtArg]
    : NULL;
  if(!bind) return 0;
  if(cwal_value_is_array(bind)){
    rc = s2_fsl_stmt_bind_values_a(e, st,
                                   cwal_value_get_array(bind));
  }else if(cwal_value_is_object(bind)){
    MARKER(("TODO: binding by name"));
    rc = CWAL_RC_ERROR;
  }else if(!cwal_value_is_undef(bind) && (args->argc > startAtArg)){
    int i = startAtArg;
    for( ; !rc && (i < (int)args->argc); ++i ){
      rc = s2_fsl_stmt_bind( e, st, i, args->argv[i]);
    }
  }
  return rc;
}
#endif

static int cb_fsl_stmt_bind( cwal_callback_args const * args,
                             cwal_value **rv ){
  cwal_int_t ndx = -999;
  int rc;
  THIS_STMT;
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting (integer Index [,Value=null]) or "
                   "(Array|Object|undefined) arguments.");
  }
  if(cwal_value_undefined() == args->argv[0]){
    /* Special case to simplify some script code */
    *rv = args->self;
    return 0;
  }else if(cwal_value_is_integer(args->argv[0])){
    ndx = cwal_value_get_integer(args->argv[0]);
    if(1>ndx){
      return cb_toss(args, FSL_RC_RANGE,
                     "SQL bind() indexes are 1-based.");
    }
  }
  rc = s2_fsl_stmt_bind_proxy(args->engine, stmt,
                              (int)(-999==ndx ? 1 : ndx),
                              (-999==ndx)
                              ? args->argv[0]
                              : ((args->argc>1)
                                 ? args->argv[1]
                                 : NULL));
  if(!rc) *rv = args->self;
  return rc;
}

static int cb_fsl_stmt_get( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_int_t ndx;
  THIS_STMT;
  if(!args->argc || !cwal_value_is_integer(args->argv[0])){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting Index arguments.");
  }
  else if(!stmt->colCount){
    return cb_toss(args, FSL_RC_MISUSE,
                   "This is not a fetch-style statement.");
  }
  ndx = cwal_value_get_integer(args->argv[0]);
  if((ndx<0) || (ndx>=stmt->colCount)){
    return cb_toss(args, CWAL_RC_RANGE,
                   "Column index %d is out of range. "
                   "Valid range is (%d..%d).", ndx,
                   0, stmt->colCount-1);
  }
  rc = s2_fsl_stmt_to_value( args->engine, stmt,
                             (uint16_t)ndx, rv );
  if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){
    rc = cb_toss( args, rc, "Get-by-index failed with code %d (%s).",
                  rc, cwal_rc_cstr(rc));
  }
  return rc;
}

static int cb_fsl_db_exec_impl( cwal_callback_args const * args,
                                cwal_value **rv,
                                char isMulti ){
  int rc = 0;
  int argIndex = 0;
  THIS_DB;
  do{ /* loop on the arguments, expecting SQL for each one... */
    cwal_size_t sqlLen = 0;
    char const * sql = (args->argc>argIndex)
      ? cwal_value_get_cstr(args->argv[argIndex], &sqlLen)
      : NULL;
    if(!sql || !sqlLen){
      rc = (argIndex>0)
        ? 0
        : cb_toss(args,
                  FSL_RC_MISUSE,
                  "Expecting a non-empty string/buffer "
                  "argument (SQL).");
      break;
    }
    /* MARKER(("SQL:<<<%.*s>>>\n", (int)sqlLen, sql)); */
    rc = isMulti
      ? fsl_db_exec_multi( db, "%.*s", (int)sqlLen, sql )
      : fsl_db_exec( db, "%.*s", (int)sqlLen, sql )
      ;
    if(rc) rc = cb_toss_db( args, db );
  }while(!rc && (++argIndex < args->argc));
  if(!rc) *rv = args->self;
  return rc;
}

static int cb_fsl_db_exec_multi( cwal_callback_args const * args,
                                 cwal_value **rv ){
  return cb_fsl_db_exec_impl( args, rv, 1 );
}

static int cb_fsl_db_exec( cwal_callback_args const * args,
                           cwal_value **rv ){
  return cb_fsl_db_exec_impl( args, rv, 0 );
}

static int cb_fsl_db_last_insert_id( cwal_callback_args const * args,
                                     cwal_value **rv ){
  THIS_DB;
  *rv = cwal_new_integer(args->engine,
                         (cwal_int_t)fsl_db_last_insert_id(db));
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   If !cb, this is a no-op, else if cb is-a Function, it is call()ed,
   if it is a String/Buffer, it is eval'd, else an exception is
   thrown.  self is the 'this' for the call(). Result is placed in
   *rv.  Returns 0 on success.
   */
static int s2_fsl_stmt_each_call_proxy( s2_engine * se, fsl_stmt * st,
                                        cwal_value * cb, cwal_value * self,
                                        cwal_value **rv ){
  char const * cstr;
  cwal_size_t strLen = 0;
  char const useNewScope = 1
    /*
      We need a new scope to avoid that evaluation of a string callback
      vacuums up self.
    */;
  if(!cb) return 0;
  else if((cstr = cwal_value_get_cstr(cb,&strLen))){
    /** TODO: tokenize this only the first time. */
    s2_ptoker pr = s2_ptoker_empty;
    cwal_scope s2Scope = cwal_scope_empty;
    int rc;
    /**
       Bug Reminder: in stack traces and assertion traces the
       script name/location info is wrong in this case (empty and
       relative to cstr, respectively) because s2 doesn't have
       a way to map this string back to a source location at this
       point.
    */
    if(useNewScope){
      rc = cwal_scope_push2(se->e, &s2Scope);
      if(rc) return rc;
    }
    s2_ptoker_init(&pr, cstr, (int)strLen);
    pr.name = "Db.each() callback";
    rc = s2_eval_ptoker(se, &pr, 0, rv);
    if(CWAL_RC_RETURN==rc){
      rc = 0;
      *rv = cwal_propagating_take(se->e);
    }
    if(useNewScope){
      assert(se->e->current == &s2Scope);
      cwal_scope_pop2(se->e, rc ? 0 : *rv);
    }
    return rc;
  }else if(cwal_value_is_function(cb)){
    cwal_function * f = cwal_value_get_function(cb);
    if(useNewScope){
      return cwal_function_call(f, self, rv, 0, NULL );
    }else{
      cwal_scope * sc = cwal_scope_current_get(se->e);
      return cwal_function_call_in_scope(sc, f, self, rv,
                                         0, NULL );
    }
  }else {
    return cwal_exception_setf(se->e, FSL_RC_MISUSE,
                               "Don't now how to handle callback "
                               "of type '%s'.",
                               cwal_value_type_name(cb));
  }
}

/**
   Script usage:

   db.each({
   sql: "SQL CODE", // required
   bind: X, // parameter value or array of values to bind.
            // Passing the undefined value is the same as
            // not passing any value.
   mode: 0, // 0==rows as objects, else as arrays (default)
   callback: string | function // called for each row
   })

   Only the 'sql' property is required, and 'bind' must be set
   if the SQL contains any binding placeholders.

   In the scope of the callback, 'this' will resolve to the current
   row data, either in object or array form (depending on the 'mode'
   property). If the callback throws, that exception is propagated.
   If it returns a literal false (as opposed to another falsy value)
   then iteration stops without an error.

   In addition, the following scope-level variables are set:

   - rowNumber: 1-based number of the row (the iteration count).

   - columnNames: array of column names for the result set.
  
   Example callbacks:

   <<<EOF
     print(rowNumber, columnNames, this);
     print(this.0 + this.1); // array-mode column access
   EOF

   proc(){
     print(rowNumber, columnNames, this);
     print(this.colA + this.colB); // object-mode column access
   }

   Using the string form should be ever so slightly more efficient.
*/
static int cb_fsl_db_each( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0;
  cwal_value const * sql;
  char const * csql;
  cwal_size_t sqlLen = 0;
  fsl_stmt st = fsl_stmt_empty;
  int scode;
  cwal_value * vMode;
  int mode;
  cwal_value * props;
  cwal_value * bind;
  cwal_value * callback;
  cwal_value * cbSelf = NULL;
  cwal_array * cbArray = NULL;
  cwal_value * colNamesV = 0;
  cwal_array * colNames = 0;
  cwal_int_t rowNum;
  cwal_engine * e = args->engine;
  THIS_DB;
  if(!args->argc || !cwal_value_is_object(args->argv[0])){
    return cwal_exception_setf(e, FSL_RC_MISUSE,
                               "Expecting Object parameter.");
  }
  props = args->argv[0];
  sql = cwal_prop_get(props, "sql", 3 );
  csql = sql ? cwal_value_get_cstr(sql, &sqlLen) : NULL;
  if(!csql){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Missing 'sql' string/buffer property.");
  }
  vMode = cwal_prop_get( props, "mode", 4 );
  mode = vMode ? cwal_value_get_integer(vMode) : 1;
  bind = cwal_prop_get( props, "bind", 4 );
  callback = cwal_prop_get( props, "callback", 8 );
  if(callback){
    if(!cwal_value_is_function(callback)
       && !cwal_value_get_cstr(callback, 0)){
      callback = NULL;
    }
  }
  rc = fsl_db_prepare(db, &st, "%.*s", (int)sqlLen, csql);
  if(rc){
    return cb_toss_db(args, db);
  }
  if(bind && !cwal_value_is_undef(bind)){
    rc = s2_fsl_stmt_bind_proxy( e, &st, 1, bind );
    if(rc) goto end;
  }
  if(st.colCount){
    colNamesV = s2_fsl_stmt_col_names(e,&st);
    if(!colNamesV){
      rc = CWAL_RC_OOM;
      goto end;
    }
    cwal_value_ref(colNamesV);
    rc = s2_var_set( se, 0, "columnNames", 11, colNamesV );
    cwal_value_unref(colNamesV);
    if(rc){
      colNamesV = NULL;
      goto end;
    }
    colNames = cwal_value_get_array(colNamesV);
    assert(cwal_array_length_get(colNames) == (cwal_size_t)st.colCount);
  }
  rc = s2_var_set( se, 0, "columnCount", 11,
                   cwal_new_integer(e, (cwal_int_t)st.colCount) );
  if(rc) goto end;
    
  /*
    Step through each row and call the given callback function/string...
  */
  for( rowNum = 1;
       FSL_RC_STEP_ROW == (scode = fsl_stmt_step( &st ));
       ++rowNum){
    if(callback && !cbSelf){
      /** Init callback info if needed */
      cbArray = (0==mode)
        ? NULL
        : cwal_new_array(e);
      cbSelf = cbArray
        ? cwal_array_value(cbArray)
        : cwal_new_object_value(e);
      if(!cbSelf){
        rc = CWAL_RC_OOM;
        break;
      }else{
        cwal_value_ref(cbSelf);
        rc = s2_var_set(se, 0, "this", 4, cbSelf);
        cwal_value_unref(cbSelf);
        if(rc){
          goto end;
        }
      }
    }
    if(cbSelf){
      /**
         Set up this.XXX to each column's value, where
         XXX is either the column index (for array mode)
         or column name (for object mode).
      */
      cwal_value * frv = 0;
      int i = 0;
      for( ; !rc && (i < st.colCount); ++i ){
        cwal_value * cv;
        cv = NULL;
        rc = s2_fsl_stmt_to_value(e, &st, i, &cv);
        if(rc && (CWAL_RC_EXCEPTION!=rc) && (CWAL_RC_OOM!=rc)){
          rc = cwal_exception_setf(e, rc,
                                   "Conversion from db column to "
                                   "value failed with code "
                                   "%d (%s).",
                                   rc, fsl_rc_cstr(rc));
        }
        cwal_value_ref(cv);
        if(cbArray) rc = cwal_array_set( cbArray, i, cv);
        else{
          cwal_value * key;
          assert(colNames);
          key = cwal_array_get(colNames, i);
          assert(key);
          rc = cwal_prop_set_v( cbSelf, key, cv);
        }
        cwal_value_unref(cv);
      }
      if(rc) goto end;
      rc = s2_var_set( se, 0, "rowNumber", 9,
                       cwal_new_integer(e, rowNum) );
      if(rc) goto end /* leave potentially leaked new integer for the
                         scope to clean up */;
      rc = s2_fsl_stmt_each_call_proxy(se, &st, callback, cbSelf, &frv);
      if(rc) goto end;
      else if(frv == cwal_value_false()/*yes, a ptr comparison*/){
        /* If the function returns literal false, stop
           looping without an error. */
        goto end;
      }
      cbSelf = 0 /* Causes the next iteration to create a new
                  * array/object per row, overwriting "this". */;
    }
  }
  if(FSL_RC_STEP_ERROR==scode){
    rc = cwal_exception_setf(e, FSL_RC_DB,
                             "Stepping cursor failed.");
  }
  end:
  if(st.stmt){
    fsl_stmt_finalize( &st );
  }
  if(!rc) *rv = args->self;
  return rc;
}

/**
   Value DB.selectValue(string SQL [, bind = undefined [,defaultResult=undefined]])
*/
static int cb_fsl_db_select_value( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0, dbrc = 0;
  char const * sql;
  cwal_size_t sqlLen = 0;
  fsl_stmt st = fsl_stmt_empty;
  cwal_value * bind = 0;
  cwal_value * dflt = 0;
  THIS_DB;
  sql = args->argc ? cwal_value_get_cstr(args->argv[0], &sqlLen) : 0;
  if(!sql || !sqlLen){
    return cwal_exception_setf(args->engine, FSL_RC_MISUSE,
                               "Expecting SQL string/buffer parameter.");
  }
  bind = args->argc>1 ? args->argv[1] : 0;

  dbrc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql );
  if(dbrc){
    rc = cb_toss_db(args, db);
    assert(rc);
    goto end;
  }

  if(bind){
    if(2==args->argc && !st.paramCount){
      dflt = bind;
    }else{
      rc = s2_fsl_stmt_bind_proxy(args->engine, &st, 1, bind);
    }
    bind = 0;
  }

  if( !rc && (FSL_RC_STEP_ROW == (dbrc=fsl_stmt_step(&st)))){
    cwal_value * xrv = 0;
    rc = s2_fsl_stmt_to_value(args->engine, &st, 0, &xrv);
    if(!rc){
      *rv = xrv ? xrv : cwal_value_undefined();
    }
  }else if(FSL_RC_STEP_DONE == dbrc){
    *rv = dflt ? dflt : cwal_value_undefined();
  }else if(FSL_RC_STEP_ERROR == dbrc){
    assert(st.db->error.code);
    rc = cb_toss_db(args, st.db);
  }
  end:
  fsl_stmt_finalize(&st);
  return rc;  
}


/**
   Array Db.selectValues(string|buffer SQL [, bind = undefined])
*/
static int cb_fsl_db_select_values( cwal_callback_args const * args, cwal_value **rv ){
  int rc = 0, dbrc = 0;
  char const * sql;
  cwal_size_t sqlLen = 0;
  fsl_stmt st = fsl_stmt_empty;
  cwal_value * bind = 0;
  cwal_array * ar = 0;
  cwal_value * arV = 0;
  THIS_DB;
  sql = args->argc ? cwal_value_get_cstr(args->argv[0], &sqlLen) : 0;
  if(!sql || !sqlLen){
    return cwal_exception_setf(args->engine, FSL_RC_MISUSE,
                               "Expecting SQL string/buffer parameter.");
  }
  bind = args->argc>1 ? args->argv[1] : 0;

  dbrc = fsl_db_prepare( db, &st, "%.*s", (int)sqlLen, sql );
  if(dbrc){
    rc = cb_toss_db(args, db);
    assert(rc);
    goto end;
  }

  ar = cwal_new_array(args->engine);
  if(!ar){
    rc = CWAL_RC_OOM;
    goto end;
  }
  arV = cwal_array_value(ar);
  cwal_value_ref(arV);

  if(bind){
    rc = s2_fsl_stmt_bind_proxy(args->engine, &st, 1, bind);
    bind = 0;
  }

  while( !rc && (FSL_RC_STEP_ROW == (dbrc=fsl_stmt_step(&st)))){
    cwal_value * col = 0;
    rc = s2_fsl_stmt_to_value(args->engine, &st, 0, &col);
    if(!rc){
      cwal_value_ref(col);
      rc = cwal_array_append(ar, col);
      cwal_value_unref(col);
    }
  }

  end:
  fsl_stmt_finalize(&st);
  if(rc){
    cwal_value_unref(arV);
  }else if(arV){
    *rv = arV;
    cwal_value_unhand(arV);
  }
  return rc;  
}

static int cb_fsl_db_trans_begin( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  THIS_DB;
  rc = fsl_db_transaction_begin(db);
  if(rc){
    rc = cb_toss_db(args, db);
  }
  if(!rc) *rv = args->self;
  return rc;
}

static int cb_fsl_db_trans_end( cwal_callback_args const * args, cwal_value **rv, int mode ){
  int rc;
  THIS_DB;
  if(db->beginCount<=0){
    return cb_toss( args, CWAL_RC_RANGE, "No transaction is active.");
  }
  if(mode < 0){
    rc = fsl_db_rollback_force(db);
  }else{
    rc = fsl_db_transaction_end(db, mode ? 1 : 0);
  }
  if(rc){
    if(db->error.code){
      rc = cb_toss_db(args, db);
    }else{
      rc = cb_toss( args, CWAL_RC_ERROR,
                    "fsl error code during %s: %d (%s)",
                    (mode<0
                     ? "forced rollback"
                     : (mode>0
                        ? "rollback"
                        : "db commit" /* as opposed to repo checkin/commit */)
                     ),
                    rc, fsl_rc_cstr(rc) );
    }
  }else{
    *rv = args->self;
  }
  return rc;
}

static int cb_fsl_db_trans_commit( cwal_callback_args const * args, cwal_value **rv ){
  return cb_fsl_db_trans_end( args, rv, 0 );
}

static int cb_fsl_db_trans_rollback( cwal_callback_args const * args, cwal_value **rv ){
  return cb_fsl_db_trans_end( args, rv,
                              (args->argc
                               && cwal_value_get_bool(args->argv[0]))
                              ? -1 /* force immediate rollback */
                              : 1 );
}

static int cb_fsl_db_trans_state( cwal_callback_args const * args, cwal_value **rv ){
  int bc;
  THIS_DB;
  bc = db->beginCount > 0 ? db->beginCount : 0;
  *rv = cwal_new_integer(args->engine,
                         (cwal_int_t)(db->doRollback ? -bc : bc));
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_db_trans( cwal_callback_args const * args, cwal_value **rv ){
  int rc;
  cwal_function * func;
  THIS_DB;
  func = args->argc ? cwal_value_function_part(args->engine, args->argv[0]) : 0;
  if(!func){
    return cb_toss(args, CWAL_RC_MISUSE, "Expecting a Function argument.");
  }
  rc = fsl_db_transaction_begin( db );
  if(rc){
    return db->error.code
      ? cb_toss_db(args, db)
      : cb_toss(args, CWAL_RC_ERROR,
                "Error #%d (%s) while starting transaction.",
                rc, fsl_rc_cstr(rc));
  }
  rc = cwal_function_call( func, args->self, 0, 0, 0 );
  if(rc){
    fsl_db_transaction_end( db, 1 );
#if 0
    /* Just proving to myself that this rollback is still called in
       the face of an s2-level exit()/fatal()/assert(). It does. We
       can in fact preempt a FATAL result here, but doing so is a bad
       idea.
    */
    if(CWAL_RC_FATAL==rc){
      cwal_exception_set(args->engine, cwal_propagating_take(args->engine));
      rc = CWAL_RC_EXCEPTION;
    }
#endif
  }else{
    rc = fsl_db_transaction_end( db, 0 );
    if(rc){
      rc = db->error.code
        ? cb_toss_db(args, db)
        : cb_toss(args, CWAL_RC_ERROR,
                  "Error #%d (%s) while committing transaction.",
                  rc, fsl_rc_cstr(rc));
    }else{
      *rv = args->self;
    }
  }
  return rc;
}


/**
 ** If cx has a property named "db", it is returned, else if
 ** createIfNotExists is true then a new native fsl_db Object named
 ** "db" is inserted into cx and returned. Returns NULL on allocation
 ** error or if no such property exists and createIfNotExists is
 ** false.
 */
static cwal_value * fsl_cx_db_prop( fsl_cx * f,
                                    cwal_value * fv,
                                    s2_engine * se,
                                    char createIfNotExists){
  cwal_value * rv;
  fsl_db * db = fsl_cx_db(f);
  /* assert(db); */
  rv = db ? cwal_prop_get(fv, "db", 2) : NULL;
  if(db && !rv && createIfNotExists){
    int rc = fsl_db_new_native(se, f, db, 0, &rv, FSL_DBROLE_MAIN);
    if(rc) return NULL;
    cwal_value_ref(rv);
    if(rv){
      rc = cwal_prop_set(fv, "db", 2, rv);
      cwal_value_unref(rv);
      if(rc) rv = NULL;
    }
  }
  return rv;
}


/**
 ** Internal helper which adds vSelf->db->repo/checkout/config
 ** handles. Returns 0 on success. MUST return CWAL_RC_xxx codes, NOT
 ** FSL_RC_xxx codes.
 */
static int fsl_cx_add_db_handles(fsl_cx * f, cwal_value * vSelf,
                                 s2_engine * se){
  cwal_value * dbV = fsl_cx_db_prop(f, vSelf, se, 1);
  cwal_value * d = NULL;
  fsl_db * db;
  int rc;
  cwal_size_t nameLen;
  if(!dbV) return FSL_RC_OOM;
#define DBH(NAME, GETTER, ROLE)                 \
  nameLen = cwal_strlen(NAME); \
  d = cwal_prop_get(dbV, NAME, nameLen); \
  if(!d && (db = GETTER(f))){ \
    rc = fsl_db_new_native(se, f, db, 0, &d, ROLE); \
    if(rc) return rc; \
    cwal_value_ref(d); \
    assert(d); \
    rc = cwal_prop_set(dbV, NAME, nameLen, d); \
    cwal_value_unref(d); \
    if(rc) return rc; \
  }
  /*DBH("main", fsl_cx_db, FSL_DBROLE_MAIN);*/
  DBH("repo", fsl_cx_db_repo, FSL_DBROLE_REPO);
  DBH("checkout", fsl_cx_db_ckout, FSL_DBROLE_CKOUT);
  DBH("config", fsl_cx_db_config, FSL_DBROLE_CONFIG);
#undef DBH
  return 0;    
}

static int cb_fsl_config_open( cwal_callback_args const * args,
                               cwal_value **rv ){
  char const * dbName;
  int rc;
  THIS_F;
  dbName = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(args->argc && (!dbName || !*dbName)){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting a non-empty string argument.");
  }
  rc = fsl_config_open( f, (dbName && *dbName) ? dbName : 0 );
  if(rc){
    rc = cb_toss_fsl(args, f);
  }else{
    rc = fsl_cx_add_db_handles(f, args->self, se);
    if(!rc) *rv = args->self;
  }
  return rc;
}

#if 0
/* this gets trickier than i'd like because of the script-side
   db handles.
*/
static int cb_fsl_config_close( cwal_callback_args const * args,
                                cwal_value **rv ){
  THIS_F;
  fsl_config_close(f);
  *rv = args->self;
  return 0;
}
#endif

static int cb_fsl_repo_open( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * dbName;
  int rc;
  THIS_F;
  dbName = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!dbName || !*dbName){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting a non-empty string argument.");
  }
  rc = fsl_repo_open( f, dbName );
  if(rc){
    rc = cb_toss_fsl(args, f);
  }else{
    rc = fsl_cx_add_db_handles(f, args->self, se);
    if(!rc) *rv = args->self;
  }
  return rc;
}

static int cb_fsl_ckout_open_dir( cwal_callback_args const * args,
                                  cwal_value **rv ){
  char const * dbName = 0;
  cwal_size_t nameLen = 0;
  int rc;
  int checkParentDirs;
  THIS_F;
  dbName = args->argc
    ? cwal_value_get_cstr(args->argv[0], &nameLen)
    : 0;
  checkParentDirs = args->argc>1
    ? (cwal_value_get_bool(args->argv[1]) ? 1 : 0)
    : 1;
  rc = fsl_ckout_open_dir( f, dbName, checkParentDirs );
  if(rc){
    rc = cb_toss_fsl(args, f);
  }else{
    rc = fsl_cx_add_db_handles(f, args->self, se);
    if(!rc) *rv = args->self;
  }
  return rc;
}

static int cb_fsl_login_cookie_name( cwal_callback_args const * args,
                                     cwal_value ** rv){
  int rc;
  char * name;
  THIS_F;
  name = fsl_repo_login_cookie_name(f);
  if(name){
    assert( name[22] );
    assert( !name[23] );
    *rv = cwal_string_value(cwal_new_stringf(args->engine, "%.23s", name))
      /* z-string is only legal if libfossil and s2 use the same
         allocator, so we'll be pedantically correct here and
         copy it. String interning might make this a no-op.
      */;
    rc = *rv ? 0 : CWAL_RC_OOM;
    fsl_free(name);
  }else{
    *rv = cwal_value_undefined();
    rc = 0;
    /*cb_toss(args, CWAL_RC_ERROR,
      "Unexpected FossilContext state: "
      "no login cookie name.");*/
  }
  return rc;
}

/*
** Constructor function for fsl_cx instances.
**
** Script-side usage:
**
** var f = ThisFunc(object{...})
**
**
** The parameter object is optional. The only supported option at the
** moment is the traceSql boolean, which enables or disabled SQL
** tracing output.
**
** On success it returns (via *rv) a new cwal_native which is bound to
** a new fsl_cx instance. The fsl_cx will be initialized such that
** fsl_output() and friends will be redirected through cwal_output(),
** to allow fsl_output()-generated output to take advantage of
** th1ish's output buffering and whatnot.
*/
static int cb_fsl_cx_ctor( cwal_callback_args const * args,
                           cwal_value **rv ){
  fsl_cx * f = NULL;
  cwal_value * v;
  int rc;
  fsl_cx_init_opt init = fsl_cx_init_opt_empty;
  s2_engine * se = s2_engine_from_args(args);
  assert(se);

  init.output.out = fsl_output_f_cwal_output;
  init.output.flush = fsl_flush_f_cwal_out;
  init.output.state.state = args->engine;
  if(args->argc && cwal_props_can(args->argv[0])){
    init.config.traceSql =
      cwal_value_get_bool(cwal_prop_get(args->argv[0],
                                        "traceSql", 8));
  }
  rc = fsl_cx_init( &f, &init );
  if(rc){
    fsl_cx_finalize( f );
    return cb_toss(args, FSL_RC_ERROR,
                   "Fossil context initialization failed "
                   "with code #%d (%s).", rc,
                   fsl_rc_cstr(rc));
  }
  v = cwal_new_native_value(args->engine, f,
                            cwal_finalizer_f_fsl_cx,
                            FSL_TYPEID(fsl_cx));
  if(!v){
    fsl_cx_finalize( f );
    return CWAL_RC_OOM;
  }
  cwal_value_prototype_set( v, fsl_cx_prototype(se) );
  fsl_cx_db_prop( f, v, se, 1 ) /* initialize this->db */;
  *rv  = v;
  return 0;
}

/**
   If parent contains a Db property with the given name, that property
   is disconnected from its native and removed from parent, but its Db
   finalizer is not called (ownership lies elsewhere for the fsl_db
   parts of properties). The db's Fossil context pointer (if any) is
   also set to 0.

*/
static void cb_fsl_clear_handles(cwal_value * parent,
                                 char const * dbName){
  cwal_engine * e = cwal_value_engine(parent);
  cwal_size_t const dbNameLen = cwal_strlen(dbName);
  cwal_value * v = e ? cwal_prop_get(parent, dbName, dbNameLen) : NULL;
  assert(e);
  if(v){
    cwal_native * nat = cwal_value_native_part(e, v, FSL_TYPEID(fsl_db));
    fsl_db * db = nat ? cwal_native_get( nat, FSL_TYPEID(fsl_db) ) : NULL;
    assert(nat ? !!db : 1);
    if(db){
      db->f = 0 /* avoid holding a stale pointer. */;
      cwal_native_clear(nat, 0);
      cwal_prop_unset(parent, dbName, dbNameLen);
    }
  }
}

static int cb_fsl_cx_close( cwal_callback_args const * args,
                         cwal_value **rv ){
  cwal_value * dbNs;
  THIS_F;
  /* It's important that we clear the Value/Native bindings
     to all of the context's databases because clients can do this:

     var db = f.db.repo;
     f.close();'
     db.something(...)

     Which, if we're not careful here, can lead to them having
     not only a stale fsl_db handle, but one which points back
     to a stale fsl_cx pointer.

     Thank you, valgrind.
  */
  dbNs = fsl_cx_db_prop(f, natV, se, 0);
  if(dbNs){
    cb_fsl_clear_handles(dbNs, "repo");
    cb_fsl_clear_handles(dbNs, "checkout");
    cb_fsl_clear_handles(dbNs, "config");
    cb_fsl_clear_handles(natV, "db");
  }
  if(fsl_cx_db_config(f)){
    fsl_config_close(f);
  }
  if(fsl_cx_db_ckout(f)){
    fsl_ckout_close(f);
    /* also closes its repo db */
  }else if(fsl_cx_db_repo(f)){
    fsl_repo_close(f);
  }
  *rv = args->self;
  return 0;
}


static int cb_fsl_cx_finalize( cwal_callback_args const * args,
                              cwal_value **rv ){
  THIS_F;
  if(fsl_cx_db_prop(f, args->self, se, 0)){
    cb_fsl_cx_close(args, rv);
  }
  cwal_native_clear( nat, 1 );
  return 0;
}

static int cb_fsl_cx_user_name( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * uname;
  THIS_F;
  if(! (uname = fsl_cx_user_get(f)) ){
    uname = fsl_guess_user_name();
  }
  *rv = (uname && *uname)
    ? cwal_new_string_value(args->engine, uname, cwal_strlen(uname))
    : cwal_value_undefined();
  return *rv ? 0 : CWAL_RC_OOM;
}



static int cb_fsl_cx_resolve_sym( cwal_callback_args const * args,
                                  cwal_value **rv,
                                  char toUuid ){
  char * uuid = NULL;
  char const * sym;
  int rc;
  cwal_int_t argRid = 0;
  fsl_id_t rid = 0;
  THIS_F;
  sym = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if((!sym || !*sym) && args->argc){
    argRid = cwal_value_get_integer(args->argv[0]);
  }
  if(argRid<=0 && (!sym || !*sym)){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting a positive integer or non-empty "
                   "string argument.");
  }
  rc = toUuid
    ? fsl_sym_to_uuid( f, sym, FSL_SATYPE_ANY, &uuid, &rid )
    : fsl_sym_to_rid( f, sym, FSL_SATYPE_ANY, &rid );
  if(rc){
    /* Undecided: throw or return undefined if no entry found? */
#if 1
    if(FSL_RC_NOT_FOUND==rc){
      *rv = cwal_value_undefined();
      rc = 0;
    }else{
      rc = cb_toss_fsl(args, f);
    }
#else
    rc = cb_toss_fsl(args, f);
#endif
  }else{
    if(toUuid){
      assert(uuid);
      *rv = cwal_new_string_value(args->engine, uuid, cwal_strlen(uuid));
      fsl_free(uuid);
      if(!*rv){
        rc = CWAL_RC_OOM;
      }
    }else{
      assert(!uuid);
      assert(rid>0);
      *rv = cwal_new_integer(args->engine, (cwal_int_t)rid);
      if(!*rv) rc = CWAL_RC_OOM;
    }
  }
  return rc;
}


static int cb_fsl_cx_sym2uuid( cwal_callback_args const * args,
                                   cwal_value **rv ){
  return cb_fsl_cx_resolve_sym(args, rv, 1);
}

static int cb_fsl_cx_sym2rid( cwal_callback_args const * args,
                              cwal_value **rv ){
  return cb_fsl_cx_resolve_sym(args, rv, 0);
}

static int cb_fsl_cx_content_get( cwal_callback_args const * args,
                                  cwal_value **rv ){
  int rc;
  fsl_id_t rid = 0;
  char const * sym = NULL;
  fsl_buffer fbuf = fsl_buffer_empty;
  THIS_F;
  if(!args->argc) goto usage;
  else{
    cwal_value * arg = args->argv[0];
    if(cwal_value_is_integer(arg)){
      rid = (fsl_id_t)cwal_value_get_integer(arg);
      if(rid<=0) goto usage;
    }else{
      sym = cwal_value_get_cstr(arg, NULL);
      if(!sym || !*sym) goto usage;
    }
  }
  rc = (rid>0)
    ? fsl_content_get(f, rid, &fbuf)
    : fsl_content_get_sym(f, sym, &fbuf);
  if(rc){
    assert(f->error.code);
    rc = cb_toss_fsl(args, f);
  }else{
    cwal_buffer * cbuf = cwal_new_buffer(args->engine, 0);
    if(!cbuf) rc = CWAL_RC_OOM;
    else{
      cwal_value * cbufV = cwal_buffer_value(cbuf);
      cwal_value_ref(cbufV);
      rc = fsl_buffer_to_cwal_buffer(args->engine, &fbuf, cbuf);
      if(rc){
        cwal_value_unref(cbufV);
      }else{
        cwal_value_unhand(cbufV);
        *rv = cbufV;
      }
    }
  }
  fsl_buffer_clear(&fbuf);
  return rc;
  usage:
  return cb_toss(args, FSL_RC_MISUSE,
                 "Expecting one symbolic name (string) or "
                 "RID (positive integer) argument.");
}

typedef struct card_visitor_F_to_object {
  cwal_engine * e;
  cwal_array * dest;
} card_visitor_F_to_object;

/**
   fsl_card_F_visitor_f() impl which converts fc to a cwal Object
   representation and appends it to
   ((card_visitor_F_to_object*)state)->dest.
*/
static int fsl_card_F_visitor_fc_to_object(fsl_card_F const * fc,
                                           void * state){
  card_visitor_F_to_object * vs = (card_visitor_F_to_object*)state;
  int rc;
  cwal_value * ov = cwal_new_object_value(vs->e);
  char const * typeLabel = NULL;
  if(!ov) return CWAL_RC_OOM;
  cwal_value_ref(ov);
  rc = cwal_array_append(vs->dest, ov);
  cwal_value_unref(ov);
  if(rc){
    return rc;
  }
#define vset(K,KL,V) cwal_prop_set(ov, K, KL, V)
  vset("name", 4, cwal_new_string_value(vs->e, fc->name,
                                        cwal_strlen(fc->name)));
  if(fc->priorName){
    vset("priorName", 9,
         cwal_new_string_value(vs->e, fc->priorName,
                               cwal_strlen(fc->priorName)));
  }
  if(fc->uuid){
    vset("uuid", 4, cwal_new_string_value(vs->e, fc->uuid,
                                          cwal_strlen(fc->uuid)));
  }
  switch(fc->perm){
    case FSL_FILE_PERM_EXE: typeLabel = "x"; break;
    case FSL_FILE_PERM_LINK: typeLabel = "l"; break;
    default: typeLabel = "f";
  }
  vset("perm", 4, cwal_new_string_value(vs->e, typeLabel, 1)
       /* good case for cwal's string interning! */);
#undef vset
  return rc;
}

/**
   Converts a fsl_deck to an Object representation, assigning *rv
   to the new object (new, with no live references).

   Must return a CWAL_RC value, not FSL_RC!
*/
static int s2_deck_to_object( cwal_engine * e, fsl_deck * mf,
                              char includeBaselineObj,
                              cwal_value ** rv ){
  int rc = 0;
  cwal_value * ov = NULL;
  cwal_value * card = NULL;
  cwal_array * ar = NULL;
  cwal_value * av = NULL;
  cwal_size_t i;
  assert(e);
  assert(mf);
  assert(mf->uuid);
  assert(mf->rid);
  rc = fsl_deck_F_rewind(mf);
  if(rc) goto end;
  ov = cwal_new_object_value(e);
  if(!ov) return CWAL_RC_OOM;
  cwal_value_ref(ov);
#define vset2(OBJ,K,V) cwal_prop_set(OBJ, K, cwal_strlen(K), V)
#define dset(K,V) vset2(ov,K,V)
#define cset(K,V) vset2(card,K,V)
#define vornull(VP,V) ((VP) ? (V) : cwal_value_null())
#define strval2(CSTR,LEN) vornull((CSTR), cwal_new_string_value(e, (char const *)(CSTR), (cwal_size_t)(LEN)))
#define strval(CSTR) strval2((CSTR), cwal_strlen(CSTR))
  dset("type", cwal_new_integer(e, mf->type));
  dset("rid", cwal_new_integer(e, (cwal_int_t)mf->rid));
  dset("uuid", strval2(mf->uuid, cwal_strlen(mf->uuid)));
  
  if(mf->A.src){
    card = cwal_new_object_value(e);
    dset("A", card);
    cset("name", strval(mf->A.name));
    cset("tgt", strval(mf->A.tgt));
    cset("uuid", strval2(mf->A.src, cwal_strlen(mf->A.src)));
  }
  if(mf->B.uuid){
    card = cwal_new_object_value(e);
    dset("B", card);
    cset("uuid", strval2(mf->B.uuid,cwal_strlen(mf->B.uuid)));
    if(includeBaselineObj){
      rc = fsl_deck_F_rewind(mf) /* load baseline if needed */;
      if(!rc){
        cwal_value * v = NULL;
        assert(mf->B.baseline);
        rc = s2_deck_to_object( e, mf->B.baseline, 0, &v );
        if(v){
          cset("baseline", v);
        }
        else{
          assert(rc);
          goto end;
        }
      }
    }
  }
  if(mf->C){
    dset("C", strval(mf->C));
  }
  if(mf->D > 0){
    dset("D", cwal_new_double(e, mf->D));
  }
  if(mf->E.julian > 0){
    card = cwal_new_object_value(e);
    dset("E", card);
    cset("julian", cwal_new_double(e, mf->E.julian));
    cset("uuid", strval2(mf->E.uuid, cwal_strlen(mf->E.uuid)));
  }
  if(mf->F.used || mf->B.uuid){
    card_visitor_F_to_object vstate;
    cwal_size_t const reserveSize = mf->F.used
      + (mf->B.baseline ? mf->B.baseline->F.used : 0);
    assert(fsl_card_is_legal(mf->type, 'F'));
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("F", card);
    cwal_array_reserve(ar, reserveSize);
    vstate.e = e;
    vstate.dest = ar; 
    rc = fsl_deck_F_foreach(mf, fsl_card_F_visitor_fc_to_object,
                            &vstate);
    if(rc) goto end;
  }/* end of F-card */
  if(mf->G){
    dset("G", strval2(mf->G, cwal_strlen(mf->G)));
  }
  if(mf->H){
    dset("H", strval(mf->H));
  }
  if(mf->I){
    dset("I", strval2(mf->I, cwal_strlen(mf->I)));
  }
#define append(V) cwal_array_append(ar, (V))
  if(mf->J.used){
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("J", card);
    append(strval2("TODO",4));
    for(i = 0; i < mf->J.used; ++i ){
      /* TODO */
    }
  }
  if(mf->K){
    dset("K", strval2(mf->K, cwal_strlen(mf->K)));
  }
  if(mf->L){
    dset("L", strval(mf->L));
  }
  
  if(mf->M.used){
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("M", card);
    for(i = 0; i < mf->M.used; ++i ){
      fsl_uuid_cstr uuid = (fsl_uuid_cstr)mf->M.list[i];
      assert(uuid);
      append(strval2(uuid,cwal_strlen(uuid)));
    }
  }

  if(mf->P.used){
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("P", card);
    for(i = 0; i < mf->P.used; ++i ){
      fsl_uuid_cstr uuid = (fsl_uuid_cstr)mf->P.list[i];
      assert(uuid);
      append(strval2(uuid,cwal_strlen(uuid)));
    }
  }

  if(mf->Q.used){
    ar = cwal_new_array(e);
    card = av = cwal_array_value(ar);
    dset("Q", card);
    append(strval2("TODO",4));
    for(i = 0; i < mf->P.used; ++i ){
      fsl_card_Q const * qc = (fsl_card_Q const *)mf->Q.list[i];
      assert(qc);
      /* TODO */
    }
  }
  
  if(mf->U){
      dset("U", strval(mf->U));
  }

  if(mf->W.used){
    dset("W", vornull(mf->W.used, strval2(mf->W.mem,mf->W.used)));
  }else if(0 < fsl_card_is_legal(mf->type, 'W') /* required card */){
    /* corner-case: empty wiki page */
    dset("W", strval2("",0));
  }

#undef append
#undef strval
#undef strval2
#undef vornull
#undef cset
#undef dset
#undef vset2
  end:
  if(!rc && rv){
    assert(ov);
    cwal_value_unhand(ov);
    *rv = ov;
  }
  else if(ov){
    cwal_value_unref(ov);
  }
  return rc;
}

/**
   Script usage:

   const deck = Fossil.loadManifest(
       version [, includeBaselineObject]

   The deck object's struct matches fsl_deck pretty
   closely.
*/
static int cb_fsl_cx_deck_load( cwal_callback_args const * args,
                                cwal_value **rv ){
  int rc;
  fsl_deck mf = fsl_deck_empty;
  fsl_id_t rid = 0;
  char const * sym = NULL;
  fsl_satype_e mfType = FSL_SATYPE_ANY;
  char includeBaselineObj;
  THIS_F;
  if(!args->argc) goto usage;
  else if(cwal_value_is_number(args->argv[0])){
    rid = (fsl_id_t)cwal_value_get_integer(args->argv[0]);
    if(rid<=0) goto usage;
  }else{
    sym = cwal_value_get_cstr(args->argv[0], NULL);
    if(!sym || !*sym) goto usage;
  }
  includeBaselineObj = (args->argc>1)
    ? cwal_value_get_bool(args->argv[1])
    : 0;
  /* TODO: script mapping for fsl_satype_e values for 3nd arg.
   */
  rc = sym
    ? fsl_deck_load_sym(f, &mf, sym, mfType)
    : fsl_deck_load_rid(f, &mf, rid, mfType)
    ;
  if(rc){
    if(f->error.code) rc = cb_toss_fsl(args, f);
    else{
      rc = sym
        ? cb_toss(args, rc, "Loading manifest '%s' failed with code %d/%s",
                  sym, rc, fsl_rc_cstr(rc))
        : cb_toss(args, rc, "Loading manifest RID %d failed with code %d/%s",
                  (int)rid, rc, fsl_rc_cstr(rc));
    }
    goto end;
  }

  rc = s2_deck_to_object(args->engine, &mf,
                         includeBaselineObj, rv);
  end:
  fsl_deck_finalize(&mf);
  return rc;
  usage:
  return cb_toss(args, FSL_RC_MISUSE,
                 "Expecting one symbolic name (string) or "
                 "RID (positive integer) argument.");
}

/**
   Script usage:

   var x = thisFunc(fromVersion, toVersion [, Object options])

   Options:

   integer contextLines

   integer sbsWidth

   bool html

   bool text

   bool inline

   bool invert

   Result is a Buffer containing the generated diff.
*/
static int cb_fsl_cx_adiff( cwal_callback_args const * args,
                            cwal_value **rv ){
  char const * sym;
  fsl_id_t rid1, rid2;
  cwal_value * arg;
  int diffFlags = 0;
  int rc;
  short contextLines = 3;
  short sbsWidth = 0;
  fsl_buffer c1 = fsl_buffer_empty;
  fsl_buffer c2 = fsl_buffer_empty;
  fsl_buffer delta = fsl_buffer_empty;
  THIS_F;
  if(args->argc<2){
    misuse:
    return cb_toss(args, FSL_RC_MISUSE, "Expecting two artifact ID arguments.");
  }
  /* The v1 arg... */
  arg = args->argv[0];
  if(cwal_value_is_integer(arg)){
    rid1 = (fsl_id_t)cwal_value_get_integer(arg);
  }else if(!(sym = cwal_value_get_cstr(arg,NULL))){
    goto misuse;
  }else{
    rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid1);
    if(rc) return cb_toss_fsl(args, f);
  }

  /* The v2 arg... */
  arg = args->argv[1];
  if(cwal_value_is_integer(arg)){
    rid2 = (fsl_id_t)cwal_value_get_integer(arg);
  }else if(!(sym = cwal_value_get_cstr(arg,NULL))){
    goto misuse;
  }else{
    rc = fsl_sym_to_rid(f, sym, FSL_SATYPE_ANY, &rid2);
    if(rc) return cb_toss_fsl(args, f);
  }

  assert(rid1>0);
  assert(rid2>0);

  rc = fsl_content_get(f, rid1, &c1);
  if(!rc) rc = fsl_content_get(f, rid2, &c2);
  if(rc){
    rc = cb_toss_fsl(args, f);
    goto end;
  }
  diffFlags = FSL_DIFF_LINENO | FSL_DIFF_SIDEBYSIDE;
  if((args->argc>2) && cwal_props_can((arg = args->argv[2]))){
    do{
      /* Collect diff flags from object */
      cwal_value * v;
      v = cwal_prop_get(arg, "contextLines", 12);
      if(v) contextLines = (short)cwal_value_get_integer(v);
      v = cwal_prop_get(arg, "html", 4);
      if(v && cwal_value_get_bool(v)){
        diffFlags |= FSL_DIFF_HTML;
      }
      v = cwal_prop_get(arg, "inline", 6);
      if(v && cwal_value_get_bool(v)){
        diffFlags &= ~FSL_DIFF_SIDEBYSIDE;
        sbsWidth = 0;
      }
      v = cwal_prop_get(arg, "invert", 6);
      if(v && cwal_value_get_bool(v)){
        diffFlags |= FSL_DIFF_INVERT;
      }
      v = cwal_prop_get(arg, "sbsWidth", 8);
      if(v){
        sbsWidth = (short)cwal_value_get_integer(v);
      }
      v = cwal_prop_get(arg, "text", 4);
      if(v && cwal_value_get_bool(v)){
        diffFlags &= ~FSL_DIFF_HTML;
      }
    } while(0);
  }

  rc = fsl_diff_text(&c1, &c2, fsl_output_f_buffer, &delta,
                     contextLines, sbsWidth, diffFlags);
  if(rc){
    if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
    else rc = cb_toss(args, rc, "Error %s while creating diff.",
                      fsl_rc_cstr(rc));
  }else{
    cwal_value * v = cwal_new_buffer_value(args->engine, 0);
    cwal_buffer * bv = v ? cwal_value_buffer_part(args->engine, v) : NULL;
    if(!v){
      rc = CWAL_RC_OOM;
      goto end;
    }else{
      cwal_value_ref(v);
      assert(!bv->mem);
      rc = fsl_buffer_to_cwal_buffer(args->engine, &delta, bv);
      if(rc){
        cwal_value_unref(v);
      }else{
        assert(bv->used == delta.used);
        cwal_value_unhand(v);
        *rv = v;
      }
    }
  }
  end:
  fsl_buffer_clear(&c1);
  fsl_buffer_clear(&c2);
  fsl_buffer_clear(&delta);
  return rc;
}


cwal_value * fsl_stmt_prototype( s2_engine * se ){
  int rc = 0;
  cwal_value * proto;
  cwal_value * v;
  char const * pKey = "Fossil.Stmt";
  proto = s2_prototype_stashed(se, pKey);
  if(proto) return proto;
  proto = cwal_new_object_value(se->e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, pKey, proto );
  if(rc) goto end;

#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                           \
  VCHECK;                                                   \
  cwal_value_ref(v);                                        \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );  \
  cwal_value_unref(v);                                      \
  if(rc) {goto end;} (void)0

  v = cwal_new_string_value(se->e, "Stmt", 4);
  SET("__typename");

  {
    s2_func_def const funcs[] = {
      S2_FUNC2("stepObject", cb_fsl_stmt_step_object),
      S2_FUNC2("stepArray", cb_fsl_stmt_step_array),
      S2_FUNC2("step", cb_fsl_stmt_step),
      S2_FUNC2("rowToObject", cb_fsl_stmt_row_to_object),
      S2_FUNC2("rowToArray", cb_fsl_stmt_row_to_array),
      S2_FUNC2("reset", cb_fsl_stmt_reset),
      S2_FUNC2("get", cb_fsl_stmt_get),
      S2_FUNC2("finalize", cb_fsl_stmt_finalize),
      S2_FUNC2("bind", cb_fsl_stmt_bind),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
  }
  {
    /* Stmt.each() impl. */
    char const * src =
      "proc(_){"
        "affirm typeinfo(iscallable _) && typeinfo(iscallable _.call); "
        "while(this.step()) false === _.call(this) && break;"
        "return this"
      "}";
      int const srcLen = (int)cwal_strlen(src);
      rc = s2_set_from_script(se, src, srcLen, proto, "each", 4);
  }

#undef SET
#undef VCHECK
  end:
  return rc ? NULL : proto;
}

cwal_value * fsl_db_prototype( s2_engine * se ){
  int rc = 0;
  cwal_value * proto;
  cwal_value * v;
  char const * pKey = "Fossil.Db";
  cwal_engine * e;
  proto = s2_prototype_stashed(se, pKey);
  if(proto) return proto;
  e = s2_engine_engine(se);
  /* cwal_value * fv; */
  assert(se && e);
  proto = cwal_new_object_value(e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, pKey, proto );
  if(rc) goto end;
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                           \
  VCHECK;                                                   \
  cwal_value_ref(v);                                        \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );  \
  cwal_value_unref(v);                                      \
  if(rc) {goto end; } (void)0
  
  v = cwal_new_string_value(e, "Db", 2);
  SET("__typename");

  {
    s2_func_def const funcs[] = {
      S2_FUNC2("transactionState", cb_fsl_db_trans_state),
      S2_FUNC2("transaction", cb_fsl_db_trans),
      S2_FUNC2("selectValues", cb_fsl_db_select_values),
      S2_FUNC2("selectValue", cb_fsl_db_select_value),
      S2_FUNC2("rollback", cb_fsl_db_trans_rollback),
      S2_FUNC2("prepare", cb_fsl_db_prepare),
      S2_FUNC2("open", cb_fsl_db_ctor),
      S2_FUNC2("lastInsertId", cb_fsl_db_last_insert_id),
      S2_FUNC2("getName", cb_fsl_db_name),
      S2_FUNC2("getFilename", cb_fsl_db_filename),
      S2_FUNC2("execMulti", cb_fsl_db_exec_multi),
      S2_FUNC2("exec", cb_fsl_db_exec),
      S2_FUNC2("each", cb_fsl_db_each),
      S2_FUNC2("commit", cb_fsl_db_trans_commit),
      S2_FUNC2("close", cb_fsl_db_finalize),
      S2_FUNC2("begin", cb_fsl_db_trans_begin),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
    if(rc) goto end;
  }
  v = cwal_prop_get(proto, "open", 4);
  assert(v && "We just installed this!");
  rc = s2_ctor_method_set(se, proto, cwal_value_get_function(v));
  if(rc) goto end;

#if 0
  /* TODO: port in stepTuple() from the main sqlite3 s2 mod
     and then add these... */     
  {
    /* selectRow(sql,bind,asTuple) impl. */
    char const * src =
      "proc(s,b,t){"/*sql, bind, asTuple*/
        "return this.prepare(s).bind(b)"
        "[t?'stepTuple':'stepObject']()"
        /* return will indirectly finalize() the anonymous stmt. */
      "}";
    rc = s2_set_from_script(se, src, (int)cwal_strlen(src),
                            proto, "selectRow", 9);
    if(rc) goto end;
    /* selectRows(sql,bind,asTuple) impl: */
    src = "proc(s,b,t){"/*sql, bind, asTuple*/
        "const S = this.prepare(s).bind(b), m = t?'stepTuple':'stepObject', rc=[];"
        "var v; while(v=S[m]())rc[]=v; S.finalize();"
        "return rc;"
      "}";
    rc = s2_set_from_script(se, src, (int)cwal_strlen(src),
                            proto, "selectRows", 10);
  }
#endif

  v = fsl_stmt_prototype(se);
  VCHECK;
  if( (rc = cwal_prop_set_with_flags( proto, "Stmt", 4, v,
                                      CWAL_VAR_F_CONST)) ){
    goto end;
  }

#undef SET
#undef VCHECK
  end:
  return rc ? NULL : proto;
}

cwal_value * fsl_cx_prototype( s2_engine * se ){
  int rc = 0;
  cwal_value * proto;
  cwal_value * v;
  char const * pKey = "Fossil.Context";
  cwal_engine * e;
  proto = s2_prototype_stashed(se, pKey);
  if(proto) return proto;
  e = s2_engine_engine(se);
  assert(se && e);
  proto = cwal_new_object_value(e);
  if(!proto){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, pKey, proto );
  if(rc) goto end;
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                           \
  VCHECK;                                                   \
  cwal_value_ref(v);                                        \
  rc = cwal_prop_set( proto, NAME, cwal_strlen(NAME), v );  \
  cwal_value_unref(v);                                      \
  if(rc) {goto end; } (void)0
  
  v = cwal_new_string_value(e, "Context", 7);
  SET("__typename");
  {
    s2_func_def const funcs[] = {
      S2_FUNC2("symToUuid", cb_fsl_cx_sym2uuid),
      S2_FUNC2("symToRid", cb_fsl_cx_sym2rid),
      S2_FUNC2("openRepo", cb_fsl_repo_open),
      S2_FUNC2("openDb", cb_fsl_db_ctor),
      S2_FUNC2("openConfig", cb_fsl_config_open),
      S2_FUNC2("openCheckout", cb_fsl_ckout_open_dir),
      S2_FUNC2("loginCookieName", cb_fsl_login_cookie_name),
      S2_FUNC2("loadManifest",cb_fsl_cx_deck_load),
      S2_FUNC2("loadBlob", cb_fsl_cx_content_get),
      S2_FUNC2("getUserName", cb_fsl_cx_user_name),
      S2_FUNC2("finalize", cb_fsl_cx_finalize),
      /* S2_FUNC2("closeConfig", cb_fsl_config_close), */
      S2_FUNC2("close", cb_fsl_cx_close),
      S2_FUNC2("artifactDiff", cb_fsl_cx_adiff),
      s2_func_def_empty_m
    };
    rc = s2_install_functions(se, proto, funcs, 0);
    if(rc) goto end;
  }
  rc = s2_ctor_callback_set(se, proto, cb_fsl_cx_ctor);

#undef SET
#undef VCHECK
  end:
  return rc ? NULL : proto;
}


/**
   Script usage:


   const m = globMatches("*.*", "some.string")

   Or:

   const m = "*.*".globMatches(someString)

   (The second form is not installed by default!)

 */
static int cb_strglob( cwal_callback_args const * args,
                       cwal_value **rv ){
  int rc = 0;
  char const * glob =
    (args->argc>0) ? cwal_value_get_cstr(args->argv[0], NULL) : NULL;
  char const * str =
    (args->argc>1) ? cwal_value_get_cstr(args->argv[1], NULL) : NULL;
  char const * sSelf = cwal_value_get_cstr(args->self, NULL);
  if(sSelf){
    str = glob;
    glob = sSelf;
  }
  if(!glob){
    rc = cb_toss(args, FSL_RC_MISUSE,
                 "Expecting (glob,string) arguments "
                 "OR stringInstance.thisFunc(otherString) "
                 "usage.");
  }
  else if(!str){
    *rv = cwal_value_false();
  }else{
    *rv = fsl_str_glob(glob, str)
      ? cwal_value_true() : cwal_value_false();
  }
  return rc;
}

static int cb_is_uuid( cwal_callback_args const * args,
                       cwal_value **rv ){
  cwal_size_t slen = 0;
  char const * str =
    (args->argc>0) ? cwal_value_get_cstr(args->argv[0], &slen) : NULL;
  *rv = (str && fsl_is_uuid(str))
    ? cwal_value_true()
    : cwal_value_false();
  return 0;
}

static fsl_timer_state RunTimer = fsl_timer_state_empty_m;

/**
   Retuns the number of microseconds of _CPU_ time (not wall clock
   time) used since s2_shell_extend() was run.
*/
static int cb_run_timer_fetch( cwal_callback_args const * args,
                               cwal_value **rv ){
  uint64_t t = fsl_timer_fetch(&RunTimer);
  *rv = cwal_new_integer(args->engine, (cwal_int_t)t);
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_fsl_delta_create( cwal_callback_args const * args,
                                cwal_value **rv ){
  char const * s1 = NULL;
  char const * s2 = NULL;
  cwal_size_t len1, len2;
  fsl_size_t outLen = 0;
  cwal_buffer * cb;
  cwal_value * cbV = 0;
  int rc;
  if(args->argc>1){
    s1 = cwal_value_get_cstr(args->argv[0], &len1);
    s2 = cwal_value_get_cstr(args->argv[1], &len2);
  }
  if(!s1 || !s2){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting two non-empty string/buffer "
                   "arguments.");
  }
  cb = cwal_new_buffer(args->engine, len2+61);
  if(!cb) return CWAL_RC_OOM;
  cbV = cwal_buffer_value(cb);
  cwal_value_ref(cbV);
  assert(cb->capacity > (len2+60));
  rc = fsl_delta_create( (unsigned char const *)s1, (fsl_size_t)len1,
                         (unsigned char const *)s2, (fsl_size_t)len2,
                         cb->mem, &outLen);
#if 0
  if(!rc){
    /* Resize the buffer to fit. */
    rc = cwal_buffer_resize(args->engine, cb, (cwal_size_t)outLen);
  }
#endif
  if(rc) cwal_value_unref(cbV);
  else{
    cwal_value_unhand(cbV);
    *rv = cbV;
  }
  return rc;
}

static int cb_fsl_delta_applied_len( cwal_callback_args const * args,
                                     cwal_value **rv ){
  char const * src;
  cwal_size_t srcLen;
  fsl_size_t appliedLen = 0;
  int rc;
  src = (args->argc>0)
    ? cwal_value_get_cstr(args->argv[0], &srcLen)
    : NULL;
  if(!src){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one delta string "
                   "argument.");
  }
  rc = fsl_delta_applied_size((unsigned char const *)src,
                              (fsl_size_t)srcLen, &appliedLen);
  if(rc){
    return cb_toss(args, rc,
                   "Input does not appear to be a "
                   "delta. Error #%d (%s).",
                   rc, fsl_rc_cstr(rc));
  }else{
    *rv = cwal_new_integer(args->engine,
                           (cwal_int_t)appliedLen);
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

static int cb_fsl_delta_apply( cwal_callback_args const * args,
                               cwal_value **rv ){
  char const * s1 = NULL;
  char const * s2 = NULL;
  char const * src;
  char const * delta;
  cwal_size_t len1, len2, srcLen, dLen;
  fsl_size_t appliedLen = 0;
  cwal_buffer * cb;
  cwal_value * cbV;
  int rc;
  if(args->argc>1){
    s1 = cwal_value_get_cstr(args->argv[0], &len1);
    s2 = cwal_value_get_cstr(args->argv[1], &len2);
  }
  if(!s1 || !s2){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting two non-empty string/buffer "
                   "arguments.");
  }
  rc = fsl_delta_applied_size((unsigned char const *)s2,
                              (fsl_size_t)len2, &appliedLen);
  if(!rc){
    src = s1;
    srcLen = len1;
    delta = s2;
    dLen = len2;
  }else{ /* Check if the user perhaps swapped the args. */
    rc = fsl_delta_applied_size((unsigned char const *)s1,
                                (fsl_size_t)len1, &appliedLen);
    if(rc){
      return cb_toss(args, FSL_RC_MISUSE,
                     "Expecting a delta string/buffer as one "
                     "of the first two arguments.");
    }
    src = s2;
    srcLen = len2;
    delta = s1;
    dLen = len1;
  }
  cb = cwal_new_buffer(args->engine, appliedLen+1);
  if(!cb) return CWAL_RC_OOM;
  cbV = cwal_buffer_value(cb);
  cwal_value_ref(cbV);
  assert(cb->capacity > appliedLen);
  rc = fsl_delta_apply( (unsigned char const *)src, (fsl_size_t)srcLen,
                        (unsigned char const *)delta, (fsl_size_t)dLen,
                        cb->mem);
  if(!rc){
    assert(0==cb->mem[appliedLen]);
#if 0
    if(cb->capacity > (cb->used * 4 / 3)){
      rc = cwal_buffer_resize(args->engine, cb, (cwal_size_t)appliedLen);
    }
#endif
  }else{
    rc = cb_toss(args, rc,
                 "Application of delta failed with "
                 "code #%d (%s).", rc,
                 fsl_rc_cstr(rc));
  }
  if(rc) cwal_value_unref(cbV);
  else {
    cwal_value_unhand(cbV);
    *rv = cbV;
  }
  return rc;
}

static int fsl_add_delta_funcs( cwal_engine * e, cwal_value * ns ){
  cwal_value * func;
  cwal_value * v;
  int rc;
  func = cwal_new_object_value(e);
  if(!func) return CWAL_RC_OOM;
  cwal_value_ref(func);
  rc = cwal_prop_set(ns, "delta", 5, func);
  cwal_value_unref(func);
  if(rc){
    return rc;
  }
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                              \
  VCHECK;                                                         \
  cwal_value_ref(v);                                               \
  rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v );          \
  cwal_value_unref(v);                                             \
  if(rc){goto end;}(void)0
#define FUNC(NAME,FP)                    \
  v = cwal_new_function_value( e, FP, 0, 0, 0 );  \
  SET(NAME)
  FUNC("appliedLength", cb_fsl_delta_applied_len);
  FUNC("apply", cb_fsl_delta_apply);
  FUNC("create", cb_fsl_delta_create);
#undef SET
#undef FUNC
#undef VCHECK
  end:
  return rc;
}

/**
   Script usage:

   string canonicalName(string filename [, bool keepSlash = false])
   string canonicalName(string rootDirName, string filename[, bool keepSlash = false])
*/
static int cb_fs_file_canonical( cwal_callback_args const * args,
                                 cwal_value **rv ){
  int rc;
  char const * fn = 0;
  char const * root = 0;
  fsl_buffer buf = fsl_buffer_empty;
  char slash = 0;
  if(1==args->argc){
    fn = cwal_value_get_cstr(args->argv[0], 0);
  }else if(args->argc>1){
    if(cwal_value_is_bool(args->argv[1])){
      fn = cwal_value_get_cstr(args->argv[0], 0);
      slash = cwal_value_get_bool(args->argv[1]) ? 1 : 0;
    }else{
      root = cwal_value_get_cstr(args->argv[0], 0);
      fn = cwal_value_get_cstr(args->argv[1], 0);
      if(args->argc>2) slash = cwal_value_get_bool(args->argv[2]);
    }
  }
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting a non-empty string "
                         "argument.");
  rc = fsl_file_canonical_name2( root, fn, &buf, slash );
  if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
  else if(!rc){
    *rv = cwal_new_string_value(args->engine,
                                buf.used ? (char const *)buf.mem : 0,
                                (cwal_size_t)buf.used);
    if(!*rv){
      rc = CWAL_RC_OOM;
    }
  }
  fsl_buffer_clear(&buf);
  return rc;
}

/**
   Script binding to fsl_file_dirpart():

   var x = thisFunc(path, leaveSlash=true)
 */
static int cb_fs_file_dirpart( cwal_callback_args const * args,
                               cwal_value **rv ){
  int rc;
  char const * fn;
  fsl_buffer buf = fsl_buffer_empty;
  cwal_size_t fnLen = 0;
  char leaveSlash;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], &fnLen)
    : NULL;
  if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                                 "Expecting non-empty string "
                                 "argument.");
  leaveSlash = args->argc>1
    ? cwal_value_get_bool(args->argv[1])
    : 1;

  rc = fsl_file_dirpart(fn, (fsl_size_t)fnLen, &buf, leaveSlash);
  if(FSL_RC_OOM==rc) rc = CWAL_RC_OOM;
  else if(!rc){
    *rv = cwal_new_string_value(args->engine,
                                buf.used ? (char const *)buf.mem : 0,
                                buf.used);
    if(!*rv){
      rc = CWAL_RC_OOM;
    }
  }
  fsl_buffer_clear(&buf);
  return rc;
}

/**
   Script usage:

   const size = Fossil.file.size(filename)
*/
static int cb_fs_file_size( cwal_callback_args const * args,
                            cwal_value **rv ){
  char const * fn;
  fsl_size_t sz;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  sz = fsl_file_size(fn);
  if((fsl_size_t)-1 == sz){
    return cb_toss(args, FSL_RC_IO,
                   "Could not stat file: %s", fn);
  }
  *rv = (sz > (int64_t)CWAL_INT_T_MAX)
    ? cwal_new_double(args->engine, (cwal_double_t)sz)
    : cwal_new_integer(args->engine, (cwal_int_t)sz)
    ;
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Script usage:

   const tm = Fossil.file.unlink(filename)
*/
static int cb_fs_file_unlink( cwal_callback_args const * args,
                              cwal_value **rv ){
  char const * fn;
  int rc;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                                 "Expecting non-empty string "
                                 "argument.");
  rc = fsl_file_unlink( fn );
  if(!rc) *rv = args->self;
  return rc
    ? cb_toss(args, rc, "Got %s error while "
              "trying to remove file: %s",
              fsl_rc_cstr(rc), fn)
    : 0;
}

static int cb_fs_file_chdir( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * fn;
  int rc;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn || !*fn) return cb_toss(args, FSL_RC_MISUSE,
                                 "Expecting non-empty string "
                                 "argument.");
  rc = fsl_chdir( fn );
  if(!rc) *rv = args->self;
  return rc
    ? cb_toss(args, rc, "Got %s error while "
              "trying to remove file: %s",
              fsl_rc_cstr(rc))
    : 0;
}

static int cb_fs_file_cwd( cwal_callback_args const * args,
                           cwal_value **rv ){
  int rc;
  enum { BufSize = 512 * 5 };
  fsl_size_t nLen = 0;
  char const slash = args->argc ? cwal_value_get_bool(args->argv[0]) : 0;
  char buf[BufSize] = {0};
  rc = fsl_getcwd(buf, BufSize, &nLen);
  if(rc) return cb_toss(args, rc, "Got error %d (%s) while "
                        "trying to getcwd()",
                        rc, fsl_rc_cstr(rc));
  if(slash && nLen<(fsl_size_t)BufSize){
    buf[nLen++] = '/';
  }
  *rv = cwal_new_string_value(args->engine, buf, (cwal_size_t)nLen);
  return *rv ? 0 : CWAL_RC_OOM;
}


/**
   Script usage:

   const tm = Fossil.file.mtime(filename)
*/
static int cb_fs_file_mtime( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * fn;
  fsl_time_t sz;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  sz = fsl_file_mtime(fn);
  if(-1 == sz){
    return cb_toss(args, FSL_RC_IO,
                   "Could not stat() file: %s", fn);
  }
  *rv = cwal_new_integer(args->engine, (cwal_int_t)sz);
  return *rv ? 0 : CWAL_RC_OOM;
}

/**
   Script usage:

   const bool = Fossil.file.isFile(filename)
*/
static int cb_fs_file_isfile( cwal_callback_args const * args,
                              cwal_value **rv ){
  char const * fn;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  *rv = fsl_is_file(fn)
    ? cwal_value_true()
    : cwal_value_false();
  return 0;
}

/**
   Script usage:

   const bool = Fossil.file.isDir(filename)
*/
static int cb_fs_file_isdir( cwal_callback_args const * args,
                             cwal_value **rv ){
  char const * fn;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  *rv = (fsl_dir_check(fn) > 0)
    ? cwal_value_true()
    : cwal_value_false();
  return 0;
}


/**
   Script usage:

   bool Fossil.file.access(filename [, bool mustBeADir=false)
*/
static int cb_fs_file_access( cwal_callback_args const * args,
                              cwal_value **rv ){
  char const * fn;
  char checkWrite;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  checkWrite = (args->argc>1)
    ? cwal_value_get_bool(args->argv[1])
    : 0;

  *rv = fsl_file_access( fn, checkWrite ? W_OK : F_OK )
    /*0==OK*/
    ? cwal_value_false()
    : cwal_value_true();
  return 0;
}


/**
   Streams a file's contents directly to the script engine's output
   channel, without (unless the file is small) reading the whole file
   into a buffer first.

   Script usage:

   Fossil.file.passthrough(filename)
*/
static int cb_fs_file_passthrough( cwal_callback_args const * args,
                                   cwal_value **rv ){
  char const * fn;
  cwal_size_t len;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], &len)
    : NULL;
  if(!fn){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting non-empty string "
                   "argument.");
  }else if(0!=fsl_file_access(fn, 0)){
    return cb_toss(args, FSL_RC_NOT_FOUND,
                   "Cannot find file: %s", fn);
  }else{
    FILE * fi = fsl_fopen(fn, "r");
    int rc;
    if(!fi){
      rc = cb_toss(args, FSL_RC_IO,
                   "Could not open file for reading: %s",
                   fn);
    }else{
      rc = fsl_stream( fsl_input_f_FILE, fi,
                       fsl_output_f_cwal_output,
                       args->engine );
      fsl_fclose(fi);
    }
    if(!rc) *rv = args->self;
    return rc ? CWAL_RC_IO : 0;
  }
}

/**
   Script binding to fsl_stat(). Returns undefined if
   stat fails, else an object:
   
   {
   name: args->argv[0],
   mtime: unix epoch,
   ctime: unix epoch,
   size: in bytes,
   type: 'file', 'dir', 'link', or 'unknown'
   }

   TODO? Canonicalize the result name?
*/
static int cb_fs_file_stat( cwal_callback_args const * args,
                            cwal_value **rv ){
  char const * fn;
  fn = args->argc
    ? cwal_value_get_cstr(args->argv[0], NULL)
    : NULL;
  if(!fn) return cb_toss(args, FSL_RC_MISUSE,
                         "Expecting non-empty string "
                         "argument.");
  else{
    fsl_fstat fst = fsl_fstat_empty;
    int rc;
    cwal_engine * e = args->engine;
    rc = fsl_stat( fn, &fst, 1 );
    if(rc){
      *rv = cwal_value_undefined();
      rc = 0;
    }else{
      char const * typeName;
      cwal_value * obj = cwal_new_object_value(e);
      switch(fst.type){
        case FSL_FSTAT_TYPE_DIR: typeName = "dir"; break;
        case FSL_FSTAT_TYPE_FILE: typeName = "file"; break;
        case FSL_FSTAT_TYPE_LINK: typeName = "link"; break;
        case FSL_FSTAT_TYPE_UNKNOWN:
        default:
          typeName = "unknown";
          break;
      }
      cwal_prop_set(obj, "type", 4, cwal_new_string_value(e, typeName,
                                                          cwal_strlen(typeName)));
      cwal_prop_set(obj, "name", 4, args->argv[0]);
      cwal_prop_set(obj, "size", 4, cwal_new_integer(e, (cwal_int_t)fst.size));
      cwal_prop_set(obj, "mtime", 5, cwal_new_integer(e, (cwal_int_t)fst.mtime));
      cwal_prop_set(obj, "ctime", 5, cwal_new_integer(e, (cwal_int_t)fst.ctime));
      *rv = obj;
      /* See how little code it is if we ignore malloc errors? */
    }
    return rc;
  }
}

static int fsl_add_file_funcs( cwal_engine * e, cwal_value * ns ){
  cwal_value * func;
  cwal_value * v;
  int rc;
  func = cwal_new_object_value(e);
  if(!func) return CWAL_RC_OOM;
  cwal_value_ref(func);
  rc = cwal_prop_set(ns, "file", 4, func);
  cwal_value_unref(func);
  if(rc){
    return rc;
  }
    
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                              \
  VCHECK;                                                      \
  cwal_value_ref(v);                                           \
  rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v );      \
  cwal_value_unref(v);                                         \
  if(rc) {goto end;}(void)0
#define FUNC(NAME,FP)                        \
  v = cwal_new_function_value( e, FP, 0, 0, 0 );    \
  SET(NAME)

  FUNC("canonicalName", cb_fs_file_canonical);
  FUNC("chdir", cb_fs_file_chdir);
  FUNC("currentDir", cb_fs_file_cwd);
  FUNC("dirPart", cb_fs_file_dirpart);
  FUNC("isAccessible", cb_fs_file_access);
  FUNC("isDir", cb_fs_file_isdir);
  FUNC("isFile", cb_fs_file_isfile);
  FUNC("mtime", cb_fs_file_mtime);
  FUNC("passthrough", cb_fs_file_passthrough);
  FUNC("size", cb_fs_file_size);
  FUNC("stat", cb_fs_file_stat);
  FUNC("unlink", cb_fs_file_unlink);
    
#undef SET
#undef FUNC
#undef VCHECK
    end:

    return rc;
}

static int cb_time_now( cwal_callback_args const * args,
                        cwal_value **rv ){
  char asJulian = 0;
  time_t now;
  asJulian = args->argc && cwal_value_get_bool(args->argv[0]);
  time(&now);
  *rv = asJulian
    ? cwal_new_double(args->engine,
                      fsl_unix_to_julian((fsl_time_t)now))
    : cwal_new_integer(args->engine,
                       (cwal_int_t)now)
    ;
  return *rv ? 0 : CWAL_RC_OOM;
}


static int cb_unix_to_julian( cwal_callback_args const * args,
                              cwal_value **rv ){
  cwal_int_t tm;
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one integer argument.");
  }
  tm = cwal_value_get_integer(args->argv[0]);
  *rv = cwal_new_double(args->engine,
                        fsl_unix_to_julian( (fsl_time_t)tm ));
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_julian_to_unix( cwal_callback_args const * args,
                              cwal_value **rv ){
  cwal_double_t tm;
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one double argument.");
  }
  tm = cwal_value_get_double(args->argv[0]);
  *rv = cwal_new_integer(args->engine,
                         fsl_julian_to_unix( (double)tm ));
  return *rv ? 0 : CWAL_RC_OOM;
}

static int cb_julian_to_iso8601( cwal_callback_args const * args,
                                 cwal_value **rv ){
  cwal_double_t tm;
  char includeMs;
  char buf[24];
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one double argument.");
  }
  tm = cwal_value_get_double(args->argv[0]);
  includeMs = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0;
  if(fsl_julian_to_iso8601(tm, buf, includeMs)){
    *rv = cwal_new_string_value(args->engine, buf, includeMs ? 23 : 19);
    return *rv ? 0 : CWAL_RC_OOM;
  }else{
    return cb_toss(args, FSL_RC_MISUSE,
                   "Invalid Julian date value.");
  }
}

static int cb_julian_to_human( cwal_callback_args const * args,
                               cwal_value **rv ){
  cwal_double_t tm;
  char includeMs;
  char buf[24];
  if(!args->argc){
    return cb_toss(args, FSL_RC_MISUSE,
                   "Expecting one double argument.");
  }
  includeMs = (args->argc>1) ? cwal_value_get_bool(args->argv[1]) : 0;
  tm = cwal_value_get_double(args->argv[0]);
  if(fsl_julian_to_iso8601(tm, buf, includeMs)){
    assert('T' == buf[10]);
    buf[10] = ' ';
    *rv = cwal_new_string_value(args->engine, buf, includeMs ? 23 : 19);
    return *rv ? 0 : CWAL_RC_OOM;
  }else{
    return cb_toss(args, FSL_RC_MISUSE,
                   "Invalid Julian data value.");
  }
}

static int fsl_add_time_funcs( cwal_engine * e, cwal_value * ns ){
  cwal_value * func;
  cwal_value * v;
  int rc;
  func = cwal_new_object_value(e);
  if(!func) return CWAL_RC_OOM;
  cwal_value_ref(func);
  rc = cwal_prop_set(ns, "time", 4, func);
  cwal_value_unref(func);
  if(rc){
    return rc;
  }
    
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                              \
  VCHECK;                                                         \
  cwal_value_ref(v);                                           \
  rc = cwal_prop_set( func, NAME, cwal_strlen(NAME), v );   \
  cwal_value_unref(v);                                      \
  if(rc) {goto end;} (void)0
#define FUNC(NAME,FP)                   \
  v = cwal_new_function_value( e, FP, 0, 0, 0 );    \
  SET(NAME)

  FUNC("julianToHuman", cb_julian_to_human);
  FUNC("julianToISO8601", cb_julian_to_iso8601);
  FUNC("julianToUnix", cb_julian_to_unix);
  FUNC("now", cb_time_now);
  FUNC("unixToJulian", cb_unix_to_julian);
  FUNC("cpuTime", cb_run_timer_fetch);

#undef SET
#undef FUNC
#undef VCHECK
  end:
  return rc;
}

static int cb_fsl_rc_cstr( cwal_callback_args const * args,
                           cwal_value **rv ){
  if(!args->argc){
    return cb_toss(args,CWAL_RC_MISUSE,
                   "Expecting a single integer argument (FSL_RC_xxx value).");
  }else{
    cwal_int_t const i = cwal_value_get_integer(args->argv[0]);
    char const * msg = fsl_rc_cstr((int)i);
    *rv = msg
      ? cwal_new_xstring_value(args->engine, msg, cwal_strlen(msg))
      : cwal_value_undefined();
    return *rv ? 0 : CWAL_RC_OOM;
  }
}

static cwal_value * s2_fsl_ns( s2_engine * se ){
  int rc = 0;
  cwal_value * ns;
  cwal_value * v;
  char const * pKey = "Fossil";
  cwal_engine * e;
  ns = s2_prototype_stashed(se, pKey);
  if(ns) return ns;
  e = s2_engine_engine(se);
  assert(se && e);
  ns = cwal_new_object_value(e);
  if(!ns){
    rc = CWAL_RC_OOM;
    goto end;
  }
  rc = s2_prototype_stash( se, pKey, ns );
  if(rc) goto end;
#define VCHECK if(!v){ rc = CWAL_RC_OOM; goto end; } (void)0
#define SET(NAME)                                       \
  VCHECK;                                               \
  cwal_value_ref(v);                                    \
  rc = cwal_prop_set( ns, NAME, cwal_strlen(NAME), v ); \
  cwal_value_unref(v);                                  \
  if(rc) {goto end; } (void)0
#define FUNC(NAME,FP)                           \
  v = cwal_new_function_value(e, FP, 0, 0, 0 ); \
  SET(NAME)
  
  v = cwal_new_string_value(e, "Fossil", 6);
  SET("__typename");

  FUNC("globMatches", cb_strglob);
  FUNC("isUuid", cb_is_uuid);
  FUNC("rcString", cb_fsl_rc_cstr);

  v = fsl_db_prototype(se);
  VCHECK;
  if( (rc = cwal_prop_set_with_flags( ns, "Db", 2, v,
                                      CWAL_VAR_F_CONST)) ){
    goto end;
  }

  v = fsl_cx_prototype(se);
  VCHECK;
  if( (rc = cwal_prop_set_with_flags( ns, "Context", 7, v,
                                      CWAL_VAR_F_CONST)) ){
    goto end;
  }

  if( (rc = fsl_add_delta_funcs(e, ns)) ) goto end;
  if( (rc = fsl_add_file_funcs(e, ns)) ) goto end;
  if( (rc = fsl_add_time_funcs(e, ns)) ) goto end;

#undef SET
#undef FUNC
#undef VCHECK
  end:
  return rc ? NULL : ns;
}

/**
    We copy fsl_lib_configurable.allocator as a base allocator.
*/
static fsl_allocator fslAllocOrig;

/**
    Proxies fslAllocOrig() and abort()s on OOM conditions.
 */
static void * fsl_realloc_f_failing(void * state, void * mem, fsl_size_t n){
  void * rv = fslAllocOrig.f(fslAllocOrig.state, mem, n);
  if(n && !rv){
    fprintf(stderr,"\nOUT OF MEMORY\n");
    fflush(stderr);
    s2_fatal(CWAL_RC_OOM,"Out of memory.");
  }
  return rv;
}

/**
    Replacement for fsl_lib_configurable.allocator which abort()s on OOM.
    Why? Because fossil(1) has shown how much that can simplify error
    checking in an allocates-often API.
 */
static const fsl_allocator fcli_allocator = {
fsl_realloc_f_failing,
NULL/*state*/
};


/**
   Gets called by s2sh's init phase to install our bindings.
*/
int s2_shell_extend(s2_engine * se, int argc, char const * const * argv){
  static char once = 0;
  int rc = 0;
  cwal_value * v /* temp value */;
  cwal_engine * e = s2_engine_engine(se)
    /* cwal_engine is used by the core language-agnostic script engine
       (cwal). s2_engine is a higher-level abstraction which uses
       cwal_engine to (A) provide a Value type system and (B) manage
       the lifetimes of memory allocated on behalf of the scripting
       language (s2). s2 has to take some part in managing the
       lifetimes, but it basically just sets everything up how cwal
       expects it to be, and lets cwal do the hard parts wrt memory
       management.
    */
    ;
  if(once){
    assert(!"libfossil/s2 bindings initialized more than once!");
    s2_fatal(CWAL_RC_MISUSE,
             "libfossil/s2 bindings initialized more than once!");
  }
  fslAllocOrig = fsl_lib_configurable.allocator;
  fsl_lib_configurable.allocator = fcli_allocator
    /* This MUST be done BEFORE the fsl API allocates
       ANY memory!
    */;

  if(0){
    /* force hash-based scope property storage for future scopes */
    int32_t featureFlags = cwal_engine_feature_flags(e, -1);
    featureFlags |= CWAL_FEATURE_SCOPE_STORAGE_HASH;
    cwal_engine_feature_flags(e, featureFlags);
  }

#define VCHECK if(!v && (rc = CWAL_RC_OOM)) goto end

  /* MARKER(("Initializing Fossil namespace...\n")); */

  fsl_timer_start(&RunTimer);

  v = s2_fsl_ns(se);
  VCHECK;
#if 1
  if( (rc = s2_define_ukwd(se, "Fossil", 6, v)) ) goto end;
#else
  cwal_scope * dest = cwal_scope_current_get(e)
    /* Where we want to install our functionality to. Note that we're
       not limited to installing in one specific place, but in practice
       that is typical, and the module loader's interface uses that
       approach. */;
  if( (rc = cwal_scope_chain_set_with_flags( dest, 0, "Fossil", 6,
                                             v, CWAL_VAR_F_CONST)) ){
    goto end;
  }
#endif

  {
    /* Extend the built-in Buffer prototype */
    cwal_value * bufProto = s2_prototype_buffer(se);
    assert(bufProto);
#define BFUNC(NAME,FP) cwal_prop_set( bufProto, NAME, cwal_strlen(NAME), \
                                      cwal_new_function_value(se->e, FP, 0, 0, 0))
    BFUNC("md5", cb_buffer_md5_self);
    BFUNC("sha1", cb_buffer_sha1_self);
    BFUNC("sha3", cb_buffer_sha3_self);
  }

#undef VCHECK
  end:
  return rc;    
}

#undef MARKER
#undef THIS_DB
#undef THIS_STMT
#undef THIS_F

Deleted bindings/s2/timeline.s2.

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






































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
#!/usr/bin/env f-s2sh
/**
   A very basic timeline application implemented in s2.

   Optional flags, passed after '--' on the CLI:

   -f|--files lists files changed by each commit.

   -R|--repo-db=DBNAME specifies a repository db (default=current checkout).
*/
assert Fossil.require;
Fossil.require(
['fsl/db/repoOrCheckout', // opens -R|--repo-db REPOFILE db or the current checkout
 'fsl/timeline/basic', // array of recent event table entries
 'cliargs' // flags passed after '--' are "script flags", and this module assists in using them
],
proc(db,tl,cli){
    const showFiles = cli.takeFlag('f', cli.takeFlag('files'));
    if(cli.hasFlags()){
        throw "Unexpected flags(s): "+{}.toJSONString.call(cli.flags);
    }else if(cli.hasNonFlags()){
        throw "Unexpected non-flag(s): "+cli.nonFlags.toJSONString();
    }
    const j2h = Fossil.time.julianToHuman;
    const showFilesChangedBy = proc(uuid){
        affirm 'string' === typename uuid;
        var gotOne = 0;
        db.each({
            sql:<<<EOSQL
            SELECT bf.uuid, filename.name fname, mlink.pid pid, mlink.fid fid, bf.size size
            FROM mlink, filename LEFT JOIN blob bf -- FILE blob
            ON bf.rid=mlink.fid LEFT JOIN blob bm -- MANIFEST/checkin blob
            ON bm.rid=mlink.mid
            WHERE bm.uuid = ?1
            AND filename.fnid=mlink.fnid AND bf.rid=mlink.fid
            AND bm.rid=mlink.mid
            ORDER BY filename.name EOSQL,
            bind: uuid,
            mode: 0,
            callback:proc(){
                if(1===rowNumber){
                    print("\t%1$-12s %2$-10s %3$10s Name".applyFormat(
                        "File", "UUID", "Size"
                    ));
                    gotOne=true;
                }
                print("\t%1$-10s   %2$.10s %3$10d %4$s".applyFormat(
                    (!this.fid ? "removed" : (!this.pid ? "added" : "modified")),
                    this.uuid|||"",
                    this.size|||0,
                    this.fname
                ));
            }});
        if(gotOne) print("") /*spacing */;
    };

    const lineFmt = "%1$-8s %2$.10s @ %3$s by %4$s\n\t%5$s";
    const typeMap = { // maps event.type labels to strings
        g: 'tag', w: 'wiki', ci: 'checkin',
        e: 'event', t: 'ticket'
    };
    tl.eachIndex(proc(v){
        print(lineFmt.applyFormat(typeMap[v.type]|||'???',
                                  v.uuid,
                                  j2h(v.mtime),
                                  v.user,
                                  v.comment));
        showFiles && showFilesChangedBy(v.uuid);
    });
});

Deleted bindings/s2/unit/000-000-0empty.s2.

1

-
/* valgrind this to find the base s2 script overhead costs. */

Deleted bindings/s2/unit/000-000-abc.s2.

1
2
3
4
5
6
7
8
9









-
-
-
-
-
-
-
-
-
assert true;  // if it were always this easy
assert !false;
assert 1;
assert !0;
assert !undefined;
assert !null;
assert !'';
assert !(false && {another, error, skipped});
assert   22 === ('☺', __COLUMN) /* must count non-ASCII chars properly */;

Deleted bindings/s2/unit/000-005-bitshift.s2.

1
2
3
4
5
6
7
8
9
10
11
12












-
-
-
-
-
-
-
-
-
-
-
-
assert 2 === 1<<1;
assert 64 === 256>>2;
assert 4 === 1<<1<<1;

assert 4 === 1<<1<<1 ||| 0;
assert 4 === 0 || 1<<1<<1 ||| 1;

assert 1 === 0b1;
assert 4 === 0b100;
assert 0xff === 0b11111111;
assert 0b0110 === (0b_11_10 & 0b_01_11);
assert 0b100 === 0b1 << 2;

Deleted bindings/s2/unit/000-010-compare.s2.

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









































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
assert true === true;
assert true == 1;
assert true !== 1;
assert true != 0;
assert !false;
assert !(false == 1);;
assert false != '0';
assert false == +'0';
assert false == '';
assert !!!'';
assert <<<X fooX === 'foo';

assert 42 === (1
       ? 2
         ? 4 + 38
         : -1
       : 0 /* '===' has higher prec than '?' */);
assert 42 === (true ? 1 ? 4 + 38 : -1 : 0);

assert 0 < 1;
assert 1 > 0;
assert !(0 > 1);
assert !(1 < 0);
assert 0 < 1 && 1 > 0;

assert !(3>7);
assert 1 ? 3<2+2 : 3>7;

// stack size error:  (1 ? 'hi' : (again && yet !wrong) )
// (is invalid syntax, but the error message could be better)
// hmmm:
assert 'hi' === (false ? nope : (1 ? 'hi' : (again && yet + !wrong) ));

assert 1 === 3.2 % 2.1 /* modulo is always integer */;

assert 1 < 1.1 /* this wasn't always the case in s2 */;
assert 1.1 > 1 /* and yet this was */;

/* Arguable (but consistent) result type behaviours: */
assert 18 === 8*2.3 /* integer result of double multiplication */;
assert 18.0 < 8.0*2.3 /* double result of double multiplication */;
assert 8.0*2.3 > 18 /* make sure this works both ways. */;
assert 18 < 8.0*2.3 /*b/c of the integer-to-double mode comparison at the cwal level*/;
assert !(8*2.3 > 18) /* integer math on the LHS of > op */;
assert 2 === 1/0.4 /* integer result of double division */;
assert 0 === 1/2;
assert 0 === 1/2.0;

assert 2 === 1 ? 2 ? 3 : 4 : 5;
assert 5 === 0 ? 1&&2 ? 3 : 4 : 5; 
assert 5 === 0 ? 1||2 ? 3 : 4 : 5; 
assert 4 === 1 ? 0 ? 3 : 4 : 5;
assert 3 === 0&&1 ? 2 : 3;
assert 2 === 1||0 ? 2 : 3;
assert 2 === 0||1 ? 2 : 3;
assert 3 === 0||0 ? 2 : 3;
assert 3 === false||null ? 2 : 3;

var y = "";
assert 1 === (y ||| 1);
assert "" === (0 ||| y);
var x;
assert 1 === (x ?: 1);
x = false;
assert false === (x ?: 1);
assert 0 === (0 ?: 1);
x = undefined;
assert 1 === (x ?: 1);

assert false === (false ?: throw 1) /* short-circuits the RHS */;
assert 1 === catch {undefined ?: throw 1}.message /* doesn't short-circuit the RHS */;


Deleted bindings/s2/unit/001-010-typename.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20




















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
assert 'string' === typeinfo(name '');
assert 'integer' === typeinfo(name 3 * 8);
assert 'double' === typeinfo(name 3.1);
assert 'function' === typeinfo(name print);
assert 'string' === typeinfo(name typeinfo(name 3));
assert 'undefined' === typeinfo(name undefined);
assert 'undefined' === typeinfo(name $omeUndefined$ymbol);
assert 'object' === typeinfo(name s2.('prototype'));
assert 'object' === typeinfo(name s2.'prototype');
assert 'object' === typeinfo(name s2.prototype);
assert 'object' === typeinfo(name s2['pro'+'totype']);
assert 'object' === typeinfo(name s2[('p'+('r'+('o')))+('totype')]);

assert 'integer' === typeinfo(name 8*2.3);
assert 18 === 8*2.3 /* LHS type determines result type,
                       but double on the RHS is honored
                       for multiplication/division purposes
                       and the result gets truncated to an int.
                    */;
assert 'double' === typeinfo(name 8.0*2.3);

Deleted bindings/s2/unit/001-011-nameof.s2.

1
2
3



-
-
-
var x;
assert nameof x === 'x';
assert 'x' === nameof x;

Deleted bindings/s2/unit/002-000-eval.s2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
assert undefined === eval -> {};
assert undefined === eval -> '';

assert 3 === eval {1 * 3};
assert 3 === eval -> {1 * 3};
assert '3 + 1' === eval "3 + 1";

assert 4 === eval -> "3 + 1";
assert eval {2+2} === eval -> "3 + 1";

assert '3  * 2+1; 0' === eval => {  3  * 2+1; 0  };
assert '3  * 2+1' === eval =>   3  * 2+1  ;
assert 7 === eval-> eval =>3*2+1;
assert 2 === eval -> eval { 1; 2 };
assert undefined === eval -> eval {};
assert undefined === eval -> eval { 1; 2;; };

Deleted bindings/s2/unit/002-050-catch.s2.

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











































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
const oldStackTrace = pragma(exception-stacktrace true);
assert undefined === catch {1+3};
var ex;
//ex = catch -> ",";
ex = catch -> {","};
//ex = catch{,}
//return ex;

assert typeinfo(isexception ex);
assert typeinfo(isstring ex.script);
assert typeinfo(isinteger ex.line);
assert typeinfo(isinteger ex.column);
assert typeinfo(isinteger ex.code);
//print(typename ex,':',ex);

assert 'CWAL_RC_TYPE' === catch{var x; x.foo}.codeString();
assert 'CWAL_RC_NOT_FOUND' === catch{unknownVar}.codeString();

/*
  dupe vars and var names which collide with a keyword currently
  throw, rather than being triggered like syntax errors. The reason
  is so that they are catchable. That is arguable behaviour but the
  current unit test system can't (without additional sub-scripts)
  'catch' a syntax error to test these...
*/
assert 'CWAL_RC_ALREADY_EXISTS' === catch{ var dupe, dupe }.codeString();
assert 'CWAL_RC_ALREADY_EXISTS' === catch{ var for /*<== any keyword*/ }.codeString();

assert 'CWAL_RC_TYPE' === catch{s2.import('.')/*cannot eval a directory*/}
    .codeString();

scope {
    var ex = exception("hi");
    assert 'CWAL_RC_EXCEPTION' === ex.codeString();
    assert 'hi' === ex.message;

    ex = exception(1,2);
    assert 1 === ex.code;
    assert 2 === ex.message;

    ex = proc(){
        return exception(1,2);
    }();
    assert 1 === ex.code;
    assert 2 === ex.message;
    assert typeinfo(isarray ex.stackTrace);

    const obj = {
        prototype: exception(1,2)
    };
    assert obj inherits ex.prototype;
    assert 1 === obj.code;
    assert 2 === obj.message;
    assert typeinfo(isobject obj);

    const e2 = catch{throw obj};
    assert e2 === obj && "obj is-a exception, so it is thrown as-is.";
}

assert undefined === (1 ? catch 0 : 0)
/* must not syntax error (fixed 20160205). Formerly the implicit scope
   pushed by 'catch' (resp. the 'eval' family of keywords) was not
   respecting the current ternary level (it was clearing it, on
   purpose, because that's what we want in most cases), causing a
   syntax error in such constructs. It now retains the ternary level
   if the 'eval' operand is not a block expression. */;

assert 0 === (1 ? catch {0:} : 0).message.indexOf("Unexpected ':'")
/* But _this_ use of ':', without a '?' in the same (explicit) scope,
   is not permitted. */;

ex = catch [1,2,3].get(-1);
assert typeinfo(isexception ex);
assert 'CWAL_RC_RANGE' === ex.codeString();

scope {
    const ex = catch proc() { proc() { proc() {throw 1}() }() }();
    assert typeinfo(isexception ex);
    const st = ex.stackTrace;
    assert st.length() >= 3 /*(may vary in amalgamated tests)*/;
    assert 47 === ex.column;
    assert 55 === st.0.column;
    assert 59 === st.1.column;
    assert 63 === st.2.column;
}

var line;
ex = catch{
    (line=__LINE+1), var x = {a:1 /*intentionally missing comma*/
     b:1}/* ACHTUNG: 'b' must be at column 5 or adjust the assertion below */
};
//print(__FLC,line, ex);
assert ex;
assert ex.column ===  5
  /* at one point it was reporting the line/column of the opening '{'
     for the object literal containing the syntax error. */;
assert ex.line === line;

/*
  On 20171130 it was accidentally discovered that tokens with token
  type IDs in the range (1..127) were, in effect, being treated as
  string literals by the eval engine. They now trigger a syntax error.
*/
assert 'CWAL_SCR_SYNTAX' === catch{/*<== a literal \r*/}.codeString();
assert 'CWAL_SCR_SYNTAX' === catch{``}.codeString();

pragma(exception-stacktrace oldStackTrace);

Deleted bindings/s2/unit/003-000-string-ops.s2.

1
2
3
4
5
6
7
8
9









-
-
-
-
-
-
-
-
-
assert 'ab' === 'a'+"b";
assert 'a123' === 'a' + 123;
assert 123 === 123 + 'a';
assert '123' === '1' + 2 + 3;
assert 123 === +'1' + 122;
assert 'def' === 'd'+<<<X e X+"f";
assert 1 === 'a'.#;
assert '☺☺'.# === 2;
assert catch {'a'.# = 1 /*cannot assign*/};

Deleted bindings/s2/unit/005-000-var-decl.s2.

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







































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
assert 11 === var d, z, a=3, b = a,
  c = 1,
  e,
  g = 1+7+3;
assert 3 === const f = 3;

assert 3===a;
assert a===b;
assert a!=c;
assert 1===c;
assert undefined===d;
assert d===e;
assert 3 === f;

//const fail

// must syntax err:
//var w, y,, x
 // must throw (var already defined):
//var a

assert 8 === scope {var a = 7; assert 10 === a + b; a} + 1;

assert scope{11/*NOT a built-in value*/} === scope scope {
  scope {
    assert 3 === a;
      var u = 1+2+3-1+6;
  };
};

assert 'undefined' === typeinfo(name u);

scope {
    var a2 = 'aaa', b2 = 'aaa', c2 = 'aaa',
        d2 = ('aaa','aaa','aaa','aaa','aaa');
    // just checking the interning stats
    assert typeinfo(isstring a2);
    assert typeinfo(isstring b2);
    assert typeinfo(isstring c2);
    assert undefined === unset a2, b2;
    assert 'undefined' === typeinfo(name a2);
    assert 'undefined' === typeinfo(name b2);
    assert typeinfo(isstring c2);
  ;;
};

var zz = scope {
  var z = "the end";
  //var z; // must throw (z already defined)
  //"abc";
};

//assert 'the end' === zz;
//zz;

scope {
    /**
       2020-02-18: the := operator assigns as const in var decls and
       function parameter default values.
    */
    var a = 1, b := 2, c = 3;
    assert c===(a = c) /* non-const */;
    assert 'CWAL_RC_CONST_VIOLATION' === catch{b = a}.codeString();
    assert b === (c = b) /* non-const */;
    const d := 1 /* same as (const d = 1), but := is permitted for
                    consistency with var. */;
    assert 'CWAL_RC_CONST_VIOLATION' === catch{d = b}.codeString();
    var ex = catch { var a := , b };
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
    assert ex.message.indexOf(':=') > 0;
}

Deleted bindings/s2/unit/010-000-assign.s2.

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













































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
print.test = print;
var top = 3;
scope{
  assert true /* Basic identifier assignments... */;
  var a = 3, b;
  b = a;
  assert b === a;
  //assert 4 !== c = 4 // must throw for 'c';

  var c;
  c = a = b = a - 2;
  assert 1 === c;
  assert 1 === a;
  assert 1 === b;
  top = 7;
};

assert top === 7;

scope {
  assert true /* Basic property assignments... */;
  var other = print.prototype;
  print.x = 7;
  other.test = 'test';
  assert 7 === print.x;
  print.test[other.test]['y'] = -1;
  assert -1 === print.y;
  print.y = var a = 1;
  assert 1 === print.y;
  assert 1 === a;
  print.y = a = -1;
  assert -1 === print.y;
  assert -1 === a;
};

scope {
  assert true /* Making sure 'this' is respected properly... */;
  var a, other = print.prototype;
  print.x = 0;
  //other.('z') = print.x = a = -1;
  other['z'] = print.x = a = -1;
  assert other.z === -1;
  assert print.x === -1;
  assert a === -1;
  print.x = 1;
  assert other.z === -1;
  assert print.x === 1;
  assert a === -1;
};

0 && scope {
  
  const c= -1;
  c = 1 /* Must fail (assign to const) */;
};

scope {
  var a, other = print.prototype; other.z = print.x = a = -1;
  assert -1 === a;
  assert other.z === a;
  assert print.x === a;
  assert other === print.prototype;
  assert print inherits other;
}

scope {
  var a = 1, b;
  assert (a -= 3) === -2;
  assert 3 === (b = a+=5);
  assert 6 === (a*=1+1);
  assert 1 === (b /= 2);
  assert 3 === (a>>=1);
  assert 2 === (b<<=1);
  a = 2; a*=3+2;
  assert 10===a;
  assert (a/=3*4%5) === 5;
}

Deleted bindings/s2/unit/010-010-properties.s2.

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










































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

scope {
  var o = {}; 
  assert 'object' === typename o;
  o.x = o;
  o.x['x'].'z' = 1;
  assert 1 === o.x.'x'['x'].z;
  assert 1 === o.x['x'].('z');
  assert 1 === o.x['x'].('x').('z');
  assert 1 === o.x['x'].('x')['x'].('z');
  o.x = 1;
  o.x = o.x + 2;
  assert 3 === o.x;
  assert 6 === o.x * 2;
  assert 4 === 1 + o.('x');
  assert 2 === o['x'] * 3 % 7;
  var z = 'x';
  assert 3 === o[z];
  assert 9 === o.(z) * o[z];
}

scope {
  var o = {a:1};
  assert 2 === (o.a+=1);
  assert -2 === (o.a*=3*3-10);
  o.a = 2; 
  assert 10===(o.a*=3+2);
  assert 10 === o.a;
  assert 5 === (o['a'] /= 2);
  assert 20 === (o['a'] <<= 2);
  assert 20 === o.a;
}

scope {
  var o = {a:1}, o2 = {prototype:o};
  assert o2 inherits o;
  assert 1 === o2.a;
  o2.a = -1;
  assert 1 === o.a;
  assert -1 === o2.a;
  unset o2.a;
  assert 1 === o2.a;
  unset o2.a;
  assert 1 === o2.a /* o.a not cleared by unset */;
  o2.unset('a');
  assert 1 === o2.a /* o.a not cleared by o2.unset() */;
  o.clearProperties();
  assert undefined === o2.a;

  o.x = 1, o2.x = 1;
  assert 1 === o.x && 1 === o2.x;
  unset o.x, o2.x;
  assert undefined === o.x /* ensure unset handles commas properly */;
  assert undefined === o2.x /* ensure unset handles commas properly */;
}

scope {
    // Test 20190706 fix for bool lookup/property key bug...
    var o = {};
    assert undefined === o[true] /* must not resolve to a truthy-keyed
                                    property (e.g. a member method) */;
    o[true] = 3; // bool-typed property key
    assert undefined === o['x'] /*must not match a boolean true property key */;
    assert 3 === o[true] /* must match existing bool-type key */;
    assert undefined === o.true /* note that o.true is a STRING key, not bool */;
    assert undefined === o[false] /* just checking */;
    o[false] = 1;
    assert 1 === o[false] /* must match literal bool prop key */;
    assert undefined === o.false /* note that o.false is a STRING key, not bool */;
    assert undefined === o[0] /* must not match property key [false] */;
    o[0] = 2;
    unset o[false];
    assert undefined === o[false] /* must not match falsy property key [0] */;
}

Deleted bindings/s2/unit/010-050-incrdecr.s2.

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

































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

if(1) {
  var a = 1;
  assert 1 === a++;
  assert 2 === a;
  assert 3 === ++a;
  assert 'CWAL_SCR_SYNTAX' === catch{++a = a}.codeString() /*invalid target for assignment*/;
  assert 3 === a /* b/c the exception is thrown before ++a happened */;
  assert 'CWAL_RC_NOT_FOUND' === catch{++unknownSymbol}.codeString() /*unknown symbol*/;
  assert 3 === (a = a++);
  assert 3 === a;
  assert 3 === a--;
  assert 2 === a;
  assert 1 === --a;
  assert 6 === --a + 2 * 3;
  assert 0 === a;
}

if(1) {
  var o = {a:1};
  var a = 1;
  assert 1 === o.a++;
  assert 2 === o.a;
  assert 3 === ++o.a;
  assert 'CWAL_SCR_SYNTAX' === catch{++o.a = o.a}.codeString() /*invalid target for assignment*/;
  assert 3 === o.a /* b/c the exception is thrown before ++o.a happened */;
  assert 3 === (o.a = o.a++);
  assert 3 === o.a;
  assert 3 === o.a--;
  assert 2 === o.a;
  assert 1 === --o['a'];
  assert 6 === --o.a + 2 * 3;
  assert 0 === o.a;
}

if(1){
    // Make sure incr/decr do not demote doubles to integers. (They used to.)
    var a = 0.0;
    assert 1.0 === ++a;
    assert 0.0 === --a;

    assert 0.0 === a++;
    assert 1.0 === a++;
    assert 2.0 === a;

    var o = {a:0.0};
    assert 1.0 === ++o.a;
    assert 0.0 === --o.a;
    assert 0.0 === o.a++;
    assert 1.0 === o.a++;
    assert 2.0 === o.a;

    // Make sure booleans are coerced to integers...
    a = false;
    assert 1 === ++a;
    a = true;
    assert true === a++ /* Interesting... but is it wrong? */;
    assert 2 === a;

    assert 1 === +true;
    assert 0 === +false;
    assert -1 === -true;
    assert 0 === -false;

}

Deleted bindings/s2/unit/015-000-interceptors.s2.

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





















































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
if(!s2.propertyInterceptor){
    /**
       property interceptor support will almost certainly never be
       enabled - the chances of unwanted side effects, especially
       within cwal's limited key/value property model are simply too
       high. Also, the performance hit it applies to the library is
       not something i'm willing to accept for a sugar-only feature
       like this one. It might be more feasible if cwal had a
       higher-level properties API, instead of simply key/value pairs,
       but that's too heavy-weight for cwal's design goals.
    */
    // print("s2.propertyInterceptor() not enabled.");
    /* No 'return' - it messes up one of the unit tests. */
}else{
    affirm typeinfo(isfunction const mkint = s2.propertyInterceptor);

    scope {
        const o = {
            a: 1
        };
        assert mkint === mkint(o, 'b', proc(){
            if(argv.length()){
                // setter...
                interceptee.a = argv.0;
                //throw "testing";
                return /* return val is ignored for setters! */;
            }
            // getter...
            return interceptee.a;
        });
        assert 1 === o.b;
        o.b = 12;
        //throw {o};
        assert 12 === o.a;
        ++o.a;
        ++o.b; /* well that wasn't supposed to work. */
        o.b++; /* well that wasn't supposed to work. */
        assert 15 === o.b;
        assert o.a === o.b;

        const x = {prototype:o, foo: 7};
        assert o.a === x.b;
        x.b *= 2;
        assert 30 === o.a;
        assert 30 === x.b;
        o.a *= 2;
        assert 60 === o.a;
    }

    scope {
        const pro = {foo: 7};
        const x = {prototype: pro};

        mkint(pro, 'y', proc(){
            affirm pro === interceptee;
            affirm x === this;
            //return this.y;
            //throw {x: typename this.y};
            // this.y is NOT supposed to be resolving
            // as a Function here, but should recurse.
            return this.y(@argv);
        });
        x.y2 = mkint(proc(){
            affirm this === interceptee;
            affirm x === this;
            return this.y;
        });
        mkint(x, 'r', proc(){
            affirm this === interceptee;
            affirm x === this;
            return this.r /* why is this access not triggering the
                             interceptor again? It's most certainly
                             not by design. */;
        });
        mkint(x, 'zz', proc(){
            //break; // error info here is not useful.
            affirm this === interceptee;
            affirm x === this;
            if(argv.length()){
                this.foo = argv.0;
                return;
            }
            return this.foo;
        });
        x.zzz = mkint(proc(){
            //break; // error info here is not useful.
            affirm this === interceptee;
            affirm x === this;
            if(argv.length()){
                this.zz = argv.0;
                return;
            }
            return this.zz;
        });
        assert 7 === x.zzz;
        var ex;
        //why not triggering the interceptor here!?!?!?
        //ex = catch{x.y}.codeString();
        //assert typeinfo(isexception ex);
        //assert 'CWAL_RC_CYCLES_DETECTED' === ex.codeString();
        ex = catch{x.y2};
        assert typeinfo(isexception ex);
        assert 'CWAL_RC_CYCLES_DETECTED' === ex.codeString();
        assert typeinfo(isfunction x.r)
        /* This assertion is unexpected/wrong: i'm expecting an exception. */;
        unset ex;
        var i = 0;
        foreach(x=>k,v) print(++i, __FLC, k, typeinfo(name v));
        assert !i /* foreach skips over interceptors */;

        x.zzz = 3;
        assert 3 === x.zz;
        assert 3 === x.foo;

        foreach(x=>k,v) print(++i, __FLC, k, typeinfo(name v));
        assert 1===i /* .foo prop */;

    }

    scope {
        const ar = [1,2,3];
        ar.L = mkint(proc(){
            const x = 0 ? this : interceptee;
            affirm typeinfo(isarray interceptee);
            if(argv.length()){
                // setter...
                //print(__FLC,"setting",x,".length(",argv.0,")");
                x.length(argv.0);
                //throw "testing";
                return/* return val is ignored for setters! */;
                
            }
            // getter...
            //print(__FLC,"getting",x,".length()");
            return x.length();
        });
        assert 3 === ar.L;
        ar.L = 5;
        assert 5 === ar.length();
        assert undefined === foreach(ar=>k,v){
            k === 'L' && break k;
        } /* foreach() (currently) explicitly skips over interceptors */;
        var obj = {prototype:ar, x:1};
        foreach(obj=>k,v){
            assert k !== 'L';
        };

        var newLen = 2;
        obj.L = newLen;
        assert obj.L === ar.length();
        assert 2 === ar.L;
        assert ar.L === obj.length();
        assert ar.L === obj.L;
        obj.L *= newLen;
        newLen *= newLen;
        assert newLen === ar.L;
        assert newLen === ar.length();
        assert 2 === ar.1;
        assert undefined === ar[ar.L-1];

    }

    scope {
        const str = "abcdef";
        str.prototype.L = mkint(proc(){
            affirm "".prototype === interceptee;
            affirm typeinfo(isstring this);
            if(argv.length()){
                // setter...
                throw "Strings are immutable - cannot set their length.";
                
            }
            // getter...
            return this.length();
        });
        assert 6 === str.L;
        assert catch str.L = 1;
        assert str[5] === str[str.L-1];
    }

}

Deleted bindings/s2/unit/020-000-strings.s2.

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








































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* String method tests... */

scope{
    assert 'ab' === 'a'.concat('b');
    assert 'ABC' === 'a'.concat('b','c').toUpper();
    assert 42 === '0*'.byteAt(1);
    assert undefined === '0'.byteAt(3);
    assert '*' === '1*1'.charAt(1);
    assert '☺' === '1☺1'.charAt(1);
    assert '*' === '1*1'[1];
    assert '☺' === '1☺'[1];
    assert '☺' === '☺1'[0];
    assert undefined === '☺1'[2];

    assert '.'.isAscii();
    assert !'1☺1'.isAscii();
    assert '...'[0].isAscii();
    assert '1☺1'[0].isAscii();
    assert !'1☺1'[1].isAscii();

    var x = "hi! there! this string cannot not get interned (not simple/short enough)";
    var y = "".concat(x);
    assert x === y
    /* String.concat() optimization: "".concat(oneString) returns
       oneString. Unfortunately, from the script level, we can't
       _really_ be sure that we have the same string instance. Except
       possibly via this little cheat... */;
    assert typeinfo(refcount x) === typeinfo(refcount y);
    var z = [x,x,x]; // just grab a few refs to x
    assert typeinfo(refcount x) === typeinfo(refcount z.0);
    assert typeinfo(refcount x) === typeinfo(refcount y);
    y = "".concat("",y) /* bypasses optimization */;
    assert typeinfo(refcount x) === typeinfo(refcount z.0);
    assert typeinfo(refcount x) > typeinfo(refcount y);
    // WEIRD... the refcount on y is 1 higher on the first hit:
    // print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
    // it's almost like an argv isn't letting its ref go (but gets
    // swept up by the next call), but i just checked and argv is
    // ref/unref'd properly.  Hmmm. Aha: it's the pending result value
    // in the stack (s2_eval_ptoker(), actually) at that point! Correct behaviour -
    // it gets cleaned up by s2_eval_ptoker() after the next expression (the first
    // call, were the refcount is +1 higher than we might (otherwise) expect).
    //print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
    //print(__FLC,typeinfo(refcount x),typeinfo(refcount y),typeinfo(refcount z));
}

scope {
    assert 'Z☺Z' === 'z☺z'.toUpper();
    assert 'z☺z' === 'Z☺Z'.toLower();

    var str = '↻Æ©';
    assert 5 === "©123©".length();
    assert 5 === "©123©".#;
    assert 7 === "©123©".lengthBytes();
    assert 3 === str.length();
    assert 3 === str.#;
    assert 7 === str.lengthBytes();
    assert '↻' === str.charAt(0);
    assert 'Æ' === str.charAt(1);
    assert '©' === str.charAt(2);
    assert '©' === str[2];
    assert undefined === str.charAt(3);
    assert undefined === str[3];
    assert 'Æ©' === str.substr(1);
    assert '↻Æ' === str.substr(0,2);

    
    str = 'こんにちは' /*no idea what this says (or not) - copied it from the net.
                        Google says it means "Hi there"*/;
    assert 15 === str.lengthBytes();
    assert 5 === str.length();
    assert 'こ' === str.charAt(0);
    assert 'ん' === str.charAt(1);
    assert 'に' === str.charAt(2);
    assert 'ち' === str.charAt(3);
    assert 'は' === str.charAt(4);
    assert 'んにち' === str.substr(1,3);
    assert 'にちは' === str.substr(2,-1);

    assert '' === str.substr(0,0);

    assert 'CWAL_RC_RANGE' === catch {"abc"[-1]}.codeString()
    /* negative (from-the-end) indexes are a potential TODO, currently disallowed. */;

    assert 'c' === 'abc'.charAt(2).charAt(0).charAt(0).charAt(0);
    assert 'c' === 'abc'[2][0][0]
    /* SOMETIMES a cwal-level assertion: lifetime issue. Triggering it
       depends on recycling settings, engine state, and... lemme
       guess... right: string interning again >:(. Somewhere we're
       missing a reference we need. This was "resolved" in
       s2_process_top() by adding its newly-pushed result value to the
       eval-holder list (if such a list is active).
    */;
    assert 'c' === 'abc'[2][0][0][0][0][0][0][0][0][0];
    
};


scope {
  /* The subtleties of split()... */
  const split = proc(s,sep){
    sep || (sep = ':');
    var ar = s.split(sep);
    var j = ar.join(sep);
    assert j === s;
    return ar;
  };

  scope {
    var str = "//aaa//b//c//xy//";
    var sep = '//';
    var ar = split(str,sep);
    assert ar.join(sep) === str;
    assert "" == ar.0;
    assert "" === ar.5;
    assert 6 === ar.length();
    assert 'aaa' === ar.1;
    assert 'xy' === ar.4;
  }

  scope {
    var str = "aaa/b/c/xy/";
    var sep = '/';
    var ar = split(str, sep);
    assert ar.join(sep) === str;
    assert 5 === ar.length();
    assert 'aaa' === ar.0;
    assert "" === ar.4;
 }

  scope {
    var str = "aaa©b©c©";
    var sep = '©';
    var ar = split(str, sep);
    assert ar.join(sep) === str;
    assert 4 === ar.length();
    assert 'aaa' === ar.0;
    assert "" === ar.3;
  }

  scope{
    var str = ":::";
    var sep = ':';
    var ar = split(str, ':');
    assert ar.join(sep) === str;
    assert 4 === ar.length();
    assert !ar.0;
    assert !ar.3;
  }

  scope {
    var str = ":";
    var sep = str;
    var ar = split(str, sep);
    assert ar.join(sep) === str;
    assert 2 === ar.length();
    assert !ar.0;
    assert !ar.1;
  }

  scope {
    var ar = split(":a:b:",':');
    assert 4 === ar.length();
    assert !ar.0;
    assert 'a' === ar.1;
    assert 'b' === ar.2;
    assert !ar.3;
  }

  scope {
    var ar = split("abc", '|');
    assert 1 === ar.length();
    assert 'abc' === ar.0;
  }

  scope {
    var ar = split("", '.');
    assert 1 === ar.length();
    assert "" === ar.0;
  }

  scope {
    var ar = split("a:::b", ':');
    assert 4 === ar.length();
    assert "a" === ar.0;
    assert !ar.1;
    assert !ar.2;
    assert "b" === ar.3;
  }

  scope {
      // The 'limit' semantics changed on 20160213 to match what JS does.
      // Note that we still don't accept split() with no args, like JS does.
      var ar = "a:b:c".split(':',2);
      assert 2 === ar.length();
      assert 'b' === ar.1;

      ar = 'a:b:c'.split('/');
      assert 1 === ar.length();
      assert 'a:b:c' === ar.0;
  }

  scope {
      /* "Empty split". String interning saves us scads of memory here
         when the input string is large... */
      var ar = "abc".split('');
      assert typeinfo(isarray ar);
      assert 3 === ar.length();
      assert 'c' === ar.2;

      ar = "abc".split('',2);
      assert 2 === ar.length();
      assert 'b' === ar.1;
  }

} /* end split() */

scope {
  //assert "3.00.1" === "3.00.1" /* just making sure */;
  //print("3.00.1", "3.0"+0.1);
  assert "3.00.1" === "3.0"+0.1; // string on the left
  assert 3.7 === 0.7 + "3"; // string on the right
  assert 3.74 === 0.7 + "3" + "0.04";
  assert 3.1 === 3.1 + "abc"; // "abc"==0
  assert 5 === +"5";
  assert -5 === -"5";
  assert -5 === +"-5";
  assert 1.2 === -"-1.2"; // but beware of precision changes on such conversions!
}

scope {
  const sp = "".prototype;
  sp.firstChar = proc(asInteger=false){
    return this.charAt(0, asInteger);
  };
  assert "a" === "abc".firstChar();
  //unset "".prototype.firstChar; // hmmm - unset cannot deal with this.
  unset sp.firstChar;
}

scope {
    var a = "a";
    a += "b";
    assert 'ab' === a;
}

scope {
    /* String.replace() tests... */
    proc x(haystack,needle,replace/*,limitAndOrExpect*/){
        const expect = argv.4 ||| argv.3,
           limit = argv.4 ? argv.3 : 0;
        const v = haystack.replace(needle, replace, limit);
        (expect === v)
            && (assert 1 /*String.replace() check passed*/)
            && return x;
        throw "Mismatch: expecting ["+expect+"] but got: ["+v.toString()+"]";
    }
    ("abc", 'b', 'B', 'aBc')
    ('abcabc', 'b', 'BB', 1, 'aBBcabc')
    ('ab©ab©ab©', 'b', 'BB', 2, 'aBB©aBB©ab©')
    ('(C)ab(C)', '(C)', '©', '©ab©')
    ('abc', 'x', 'y', 'abc')
    ('abc', 'abc', '', '')
    ('a\r\nc\r\n', '\r\n', '\n', 'a\nc\n')
    ;;
}

scope {
    assert 2 === 'abcd'.indexOf('c');
    assert 0 > 'abcd'.indexOf('e');
    assert 2 === 'ab©'.indexOf('©');
    assert 3 === 'ab©c'.indexOf('c');
    assert 3 === 'ab©cはd'.indexOf('cはd');

    // 20190713 bugfix: the indexOf() of same-length strings
    // was just plain broken due to an "optimization" which
    // backfired.
    assert 'a'.indexOf('b') < 0;
    assert 'cab'.indexOf('cab') === 0;
}

scope {

    /* 20191220: an invalid \Uxxxxxxxx value now triggers a syntax
       error rather than being immediately fatal with no error
       location info. Because it's a syntax error, and not an exception,
       it can only be catch'd if it comes from inside/through a block.
       i.e. (catch '\U00E0A080') will not convert it to an exception, but
       the following will... */       
    const ex = catch {
        '\U00E0A080'
    };
    assert ex;
    assert ex.message.indexOf('Uxx')>0;
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
}

Deleted bindings/s2/unit/020-050-buffer.s2.

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
















































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

const Buffer = s2.Buffer.new;

scope {

  var b = Buffer();
  assert b inherits s2.Buffer;
  assert !b.capacity();
  assert !b.length();
  b.length(10);
  assert 10 === b.length();
  assert b.# === b.length();
  assert b.capacity() >= b.length();
}


scope {
    const sz = 20, b = Buffer(sz);
    assert !b.#;
    assert b.isEmpty();
    assert b.capacity() >= sz;
    b.append('a');
    assert !b.isEmpty();
    assert 1 === b.#;
    b.appendf('%1$d', 1);
    assert 2 === b.#;

    b.fill(42);
    assert '**' === b.toString() /* ensure that fill() does not change the length() */;

    b.reset();
    assert 0 === b.#;
    b.fill('!');
    assert undefined === b.byteAt(0) /* ensure that fill() does not change the length() */;

    b.length(sz).fill( '$' );
    assert 36 === b.byteAt( 0 );
    assert 36 === b.byteAt( sz-1 );
    assert undefined === b.byteAt( sz );

    b.fill( '!', 1, 18 );
    assert 36 === b.byteAt( 0 );
    assert 36 === b.byteAt( 19 );
    assert 33 === b.byteAt( 1 );
    assert 33 === b.byteAt( 18 );
    assert '$!' === b.toString( 0, 2 );
}

scope {
    const fmt = proc callee(fmt){
        return callee.buffer.reset( 0 ).appendf.apply( callee.buffer, argv ).toString();
    };
    fmt.buffer = Buffer(100);
    const check = proc(cmp){
        argv.shift();
        const s = fmt.apply(fmt,argv);
        (s===cmp) || throw ("Mismatch: <<<"+cmp+">>> !== <<<" +s+">>>");
        assert 1 /* count the above comparison as an assertion.
                    We use throw only to get more info out of it
                    if it fails*/;
    };
    const av = [ 42, "string formatter", "abcde",
                 19.17, true, false, -13.19];

    assert 'CWAL_RC_RANGE' === catch{fmt("%8$+f", 0)/*not enough args*/}.codeString();

    check( '%', '%%' );
    check( '%x%y', '%%x%%y' );

    check( 'hi, world', "%1$s", 'hi, world' );
    check('hi, world (           hi, world)',
          "%1$s (%1$20s)", 'hi, world');
    check('integer 42 042 02A 002a',
          "%1$y %1$d %1$03d %1$03X %1$04x", av.0);
    check('(        42) (42        )',
          "(%1$10d) (%1$-10d)", av.0);
    check('(       +42) (+42       )',
          "(%1$+10d) (%1$+-10d)", av.0);

    check( '00042', "%1$05d", av.0 );
    check( '42.00000', "%1$0.5f", av.0 );
    check('string formatter', "%2$s", 0, av.1);
    check('string', "%2$.6s", 0, av.1);
    check('string formatter and string formatter',
          "%2$s and %2$s", 0, av.1);
    check( '(abcde     ) (     abcde)',
           "(%3$-10s) (%3$10s)", 0, 0, av.2 );
    check( '(     abcde) (abcde     )',
           "(%3$10s) (%3$-10s)", 0, 0, av.2 );
    check( '(       abc) (abc       )',
           "(%3$10.3s) (%3$-10.3s)", 0, 0, av.2 );
    check( '(abc       ) (       abc)',
           "(%3$-10.3s) (%3$10.3s)", 0, 0, av.2 );
    check( '(abcd) (abcd    )',
           "(%3$.4s) (%3$-8.4s)", 0, 0, av.2 );
    check( '19.17 19.2 19.17 19.170',
           "%4$f %4$.1f %4$.2f %4$.3f", 0, 0, 0, av.3 );
    check( '+19.17 (19.170    ) (    19.170) (+00019.170)',
           "%4$+0f (%4$-10.3f) (%4$10.3f) (%4$+010.3f)", 0, 0, 0, av.3 );
    check( '1 true false null undefined',
           "%5$d %5$b %6$b %5$N %5$U", 0, 0, 0, 0, av.4, av.5 );
    check( '0.0', "%6$+f", 0, 0, 0, 0, 0, av.5 );
    check( '-13.19', "%7$+f", 0, 0, 0, 0, 0, 0, av.6 );

    check( '68692C20776F726C64',
           "%1$B", "hi, world" );
    check( '68692C20',
           "%1$.4B", "hi, world" );
    check( '                     68692C20776F726C64',
           "%1$30B", "hi, world" );

    check( '-1.0', '%1$.1f', -1.02 );
    check( '+1.0', '%1$+.1f', 1.02 );

    check( '-1', '%1$d', (-1) );
    check( '-1', '%1$+d', (-1) );
    check( '+1', '%1$+d', 1 );

    check( 'h', '%1$c', 'hi' );
    check( '     hhhhh', '%1$10.5c', 'hi' );
    check( 'hhhhh     ', '%1$-10.5c', 'hi' );
    check( '  ©', '%1$3c', '©' );
    check( '©©©', '%1$0.3c', '©' );
    check( '©©©', '%1$.3c', '©' );
    check( '©©©  ', '%1$-5.3c', '©' );
    check( '  ©©©', '%1$5.3c', '©' );
    check( 'A', '%1$c', 65 );
    check( 'AAAA', '%1$.4c', 65 );
    check( '10', '%1$o', 8 );
    check( '0010', '%1$04o', 8 );
    check( '  10', '%1$4o', 8 );
    assert '1 2 3' === "%1$d %2$d %3$d".applyFormat( 1,2,3 );
    check( '(NULL)', '%1$q', null );
    check( "h''i", '%1$q', "h'i" );
    check( "'h''i'", '%1$Q', "h'i" );
    check( 'NULL', '%1$Q', null );

    // check 0-precision characters...
    check( '', '%1$.0c', '*' );
    check( '', '%1$0.0c', '*' );
    check( '  ', '%1$2.0c', '*' ) /* arguable, but currently true */;

    // check urlencoding/decoding
    check( 'a%20b%26c', '%1$r', 'a b&c' );
    check( 'a b&c', '%1$R', 'a%20b%26c' );
    check( 'a%2zb', '%1$R', 'a%2zb' );

    assert catch {'%1$.1r'.applyFormat()}.message.indexOf('precision')>0;
    assert catch {'%1$1r'.applyFormat()}.message.indexOf('width')>0;
    assert catch {'%1$.1R'.applyFormat()}.message.indexOf('precision')>0;
    assert catch {'%1$1R'.applyFormat()}.message.indexOf('width')>0;
}

scope{
    var b = Buffer(100);
    var x = 0, y = 0;
    b << <<<EOF
    for(var i = 0; i < 5; ++i) ++x, --y; 
    1
    EOF;
    var rc = b.evalContents("foo");
    assert 1 === rc;
    assert 5 === x;
    assert -x === y;

    b.reset() << <<<EOF
    0;
    1;
    return {a:'a',b:'bbbb'};
    EOF;
  
    var name = 'bar';
    rc = b.evalContents(name);
    assert rc.a === 'a';
    assert rc.b === 'bbbb';

    b.reset() << <<<EOF
    0;
    1;
    throw 3;
    return {a:'a',b:'bbbb'};
    EOF;
  
    var ex = catch{
        rc = 1
        ? b.evalContents(name)
            : eval ->b;
        throw "NOT REACHED";
    };
    assert ex;
    //print(__FLC,'ex =',ex) /* a cycle (the exception obj!) in the stack trace!?!?!? Corruption??? */;
    // something here is triggering an assertion in cleanup!
    // Worked around: comments are in evalContents() impl for further consideration later.
    assert name === ex.script;
    assert 3 === ex.message;
    assert 3 === ex.line;
    assert 4 === ex.column;

    assert 3 === "a+b".evalContents({a:1,b:2});
    assert 'xyz' === "__FILE".evalContents('xyz');
    assert 'xyz9' === "__FILE+a".evalContents({a:9},'xyz');
    assert 'xyz8' === "__FILE+a".evalContents('xyz',{a:8});
    assert 'test.x' === catch {"A_+B_".evalContents('test.x',{a:1,b:2})}.script;
}

scope {
    var b = Buffer() << "h©, ©orld";
    assert '©, ©' === b.substr(1,4);
    assert 'h©, ©orld' === b.substr(0,-1);
    assert '©orld' === b.substr(4);
    assert ', ©o' === b.substr(2,4);

    b.reset();
    assert '' === b.substr(1);
}

scope {
    var b = Buffer() << 1.00;
    assert "1.0" === b.toString() /* bugfix check: leave final trailing 0 after the dot. */;


    /* Testing Buffer.slice()... */
    b.reset() << "012345";
    proc x(expect,offset=0,count=-1){
        const v = b.slice(offset, count).toString();
        (expect === v) && (assert "slice() check passed") && return x;
        throw "Mismatch: expecting ["+expect+"] but got: ["+v.toString()+"]";
    }
    ("012345")
    ("012345",0)
    ("012345",0,-1)
    ("12345",1,-1)
    ("12345",1,50)
    ("5", 5, 1)
    ("5", 5, 10)
    ("", 5, 0)
    ("1234", 1, 4)
    ;

    /* Buffer.replace() tests... */
    b.reset() << "012345";
    const check = proc callee(needle,replace/*,limitAndOrExpect*/){
        const expect = argv.3 ||| argv.2,
           limit = argv.3 ? argv.2 : 0;
        const v = b.replace(needle, replace, limit).toString();
        (expect === v) && (assert 1 /*Buffer.replace() check passed*/) && return callee;
        throw "Mismatch: expecting ["+expect+"] but got: ["+v+"]";
    }
    ('1', '9', '092345')
    (57, 42, '0*2345')
    ('345', '**', 42, '0*2**')
    ('*', 'x', 1, '0x2**')
    ('0x2', '', '**')
    (42, 0, '\0\0')
    ;
    assert !b.isEmpty() /* technically speaking */;
    assert 2 === b.#;
    assert 0 === b.byteAt(1);
    var v = b.toString();
    assert 2 === v.#;
    assert '\0\0' === v;

    b.reset() << "012345";
    check('1', '©', '0©2345')
         ('345', '©©', '0©2©©')
    ;

    b.reset() << "a*b*c";
    assert b.replace(42,33).toString() === "a!b!c";
    b.reset() << "a*b*c";
    assert b.replace(42,33,1).toString() === "a!b*c";

    assert catch {b.replace("","x")}.codeString() === 'CWAL_RC_RANGE' /* needle must be >0 bytes */;
}

scope {
    /*
      Ensure that evalContents() behaves safely when the being-eval'd
      buffer's contents are modified during evaluation. i.e. the
      modifications are only temporary and get discarded when
      evalContents() is done.
    */
    const contents = "x = 2; b<<'abc'; assert 'abc'===b.takeString()";
    var x, b = Buffer() << contents;
    b.evalContents(__FLC);
    assert 2 === x;
    assert contents === b.takeString();
}


if(const BB = s2.Buffer.compression ? s2.Buffer : 0){
    assert 0 === catch {BB.isCompressed()}.message.indexOf("'this'");
    assert 0 === catch {BB.isCompressed(1)}.message.indexOf("Argument");
    assert 0 === catch {BB.uncompressedSize()}.message.indexOf("'this'");
    assert 0 === catch {BB.uncompressedSize(1)}.message.indexOf("Argument");
    const b = BB.readFile(__FILE);
    const len = b.#;
    assert len > 5000;
    assert !b.isCompressed();
    assert !b.isCompressed(b);
    assert 0 === catch {b.isCompressed(1)}.message.indexOf("Argument");
    assert !BB.isCompressed(b);
    assert undefined === b.uncompressedSize();
    assert undefined === BB.uncompressedSize(b);
    assert b === b.uncompress() /* must be a no-op */;
    assert b.# === len;

    assert b === b.compress();
    assert b.isCompressed();
    assert b.isCompressed(b);
    assert BB.isCompressed(b);
    const zlen = b.#;
    assert zlen < len;
    assert zlen > len/10;
    assert b === BB.compress(b) /* must be a no-op */;
    assert b.# === zlen;
    assert len === b.uncompressedSize();
    assert len === BB.uncompressedSize(b);

    assert b === BB.uncompress(b);
    assert !b.isCompressed();
    assert !b.isCompressed(b);
    assert b.# === len;
    assert undefined === b.uncompressedSize();
    assert undefined === BB.uncompressedSize(b);

}else{
    /* For the benefit of the -A flag (assert tracing). */
    var b = Buffer();
    assert !b.isCompressed();
    assert 'CWAL_RC_UNSUPPORTED' === catch {b.compress()}.codeString();
    assert 'CWAL_RC_UNSUPPORTED' === catch {b.uncompress()}.codeString();
    assert undefined === b.uncompressedSize();
    /* note that b.uncompressedSize() _would_ work if it was compressed,
       but that's hard to demonstrate here w/o compression support. */
}

Deleted bindings/s2/unit/030-000-arrays.s2.

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




















































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
var a = [1,2,3], b = [];
assert 'array' === typename a;
assert 'array' === typename b;
assert 2 === a[1];

// unset array.index broken by other fixes:
assert undefined === unset a[1];
//a[1] = undefined;
assert undefined === a[1];

assert undefined === b[2];
a[3] = [4,5,[6,7,8]];
assert 'array' === typename a[3];
assert 'array' === typename a[3][2];
assert 8 === a[3][2][2];
assert 8 === a.3[2].2; // tokenizer sees a.3.2.2 as: a.(3.2).2
//assert 8 === a.3.2.2; // tokenizer sees 3.2 as a double
assert 8 === a.3[2][2];
a[] = 5*2-3;
assert 7 === a.4;
((a))[
        // note that space/comments don't count here
        ] = // nor on the RHS of a binary op
(2*5-2);
assert 8 === a.5;
assert 8 === a[4+1];
assert 9 === ++a[5];
assert 9 === a[5]++;
assert 10 === a[5];

a[3][2][] = 9;
assert 9 === a[3][2][3];

assert 'CWAL_SCR_SYNTAX'===catch{print[]/* must throw*/}.codeString();

// Must cause syntax errors:
assert catch{[1,2,3,,]}.message.indexOf('comma')>0;
assert catch{[1,2,,3]}.message.indexOf('comma')>0;
assert catch{[1,]}.message.indexOf('comma')>0;
assert catch{[,1]}.message.indexOf('comma')>0;
assert catch{[,]}.message.indexOf('comma')>0;
// Bugfix 20171111: disallow trailing semicolon in array value expressions:
assert catch{[1,2;]}.message.indexOf('semicolon')>0;
assert catch{[1;,2]}.message.indexOf('semicolon')>0;

0 && scope {
    var a = [];
    assert 1 === (a[]=1);
    assert 2 === a.length();
};

a /* propagate it out for lifetime checks */;

Deleted bindings/s2/unit/040-000-objects.s2.

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



















































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* Basic object literal tests. The prototype-supplied methods
   are tested in a separate script.
*/
const o = {
    a: 1, b: 2,
    c
:
3,
    4:'four',
    5: 5,
    array: [2,4,6],
    "removed": true
};

o.self = o;
o[((('d')))] = 42;
assert o === o.self;
assert 'array' === typename o.array;
assert 1 === o.a;
assert 2 === o['b'];
assert 3 === o.self['self'].self.c;
assert 'four' === o.4;
assert 5 === o[3+2];
//assert 5 === o.(3+2);
assert o.removed;
assert undefined === unset o.self.removed, o.removed /*does not fail on missing properties*/;
assert 'undefined' === typename o.removed;

//unset o.self /* cannot JSON-output cyclical objects */;
scope {
    const proto = { a:0 }, o = { prototype:proto, a:1 };
    assert o inherits proto;
    assert 1 === o.a;
    unset o.a;
    assert 0 === o.a;
    unset o.a;
    assert 0 === o.a;

    o.a = 1;
    ++o.a;
    assert 2 === o.a;
    o.a += -1;
    assert 1 === o.a;

    o.prototype = null /* 'null' or 'undefined' assignment removes a prototype... */;
    assert undefined === o.prototype /* ... which looks like this afterwards */;
    assert !(o inherits proto);
    assert o !inherits proto;
    assert 'CWAL_RC_TYPE' === catch{o.prototype = 1}.codeString();
}

scope {
    const g = {x:0};
    assert 1 === ++g.x;
    assert ++g.x < 3 /* must not error b/c of missing "this" via dot op */;
    assert g.x++ === 2 /* must not error b/c of missing "this" via dot op */;

    // Former BUG:
    g.x++ ? g.x-- : --g.x /* Must not error with: Unexpected LHS (X++ operation) for X?Y:Z operator. */;
    g /* trailing expr. needed to trigger the bug. */;
    ;;
}

assert catch{{a:1, b:2, }}.codeString() === 'CWAL_SCR_SYNTAX';
assert catch{{a:1,,  b:2, }}.codeString() === 'CWAL_SCR_SYNTAX';
assert catch{{,a:1,  b:2, }}.codeString() === 'CWAL_SCR_SYNTAX';
assert catch{{,}}.codeString() === 'CWAL_SCR_SYNTAX';
// Bugfix 20171111: disallow trailing semicolon in value-part expressions:
assert catch{{a:1;}}.message.indexOf('semicolon')>0;
assert catch{{a:1;,b:0}}.message.indexOf('semicolon')>0;

scope {
    /**
       Added 20171201: JS-like shortcut syntax:

       {a, b, c} ==> {a:a, b:b, c:c}
    */
    const a = 1, b = "hi", c = [1,2,3];
    const o = {a, x:4, b, c, y: 5};
    assert 1 === o.a;
    assert "hi" === o.b;
    assert c === o.c;
    assert 4 === o.x;
    assert 5 === o.y;
    assert catch{{x$x$x}}.codeString() === 'CWAL_RC_NOT_FOUND' /* unknown identifier */;
    assert catch{{"x"}}.codeString() === 'CWAL_SCR_SYNTAX' /* non-identifier */;
}

scope {
    /*
      Added 20191117: use an expression as an object literal key:

      const x = 'c';
      const o = {a:1, b: 2, [x]: 3}
    */ 
    const x = 'c';
    const o = {prototype: null, a:1, b: 2, [x]: 3};
    assert 3 === o[x];
    assert 3 === o.c;
    assert 1 === {
        prototype:null,
        [scope{
            for(;;) break "hi, "+"world";
        }]: 1
    }["hi, world"];

    assert 1 === {
        ["abc äbc"[4][0][0][0][0][0]]: 1
    }.ä;
    
    assert 0 === catch {{[x, break]: 1}}
        .message.indexOf("Unhandled 'break' in object literal.");
    assert 0 === catch {{[x, return]: 1}}
        .message.indexOf("Unhandled 'return' in object literal.");
    assert 0 === catch {{[x, continue]: 1}}
        .message.indexOf("Unhandled 'continue' in object literal.");
    ;;
}

assert 999 === scope {
    /*
      2016-02-03: testing fix:

      https://fossil.wanderinghorse.net/r/cwal/info/5041ab1deee33194

      If it's broken, this will crash if built in debug mode,
      triggering a cwal-level assertion, possibly a different one
      depending on the type of the scope result value's type.
    */
    {a: 999}.a
    /* 2020-02-20: The code that was testing has since disappeared: it
       seems that the related feature was no longer needed once cwal
       added scope push/pop APIs and cwal_scope_pop2(), both of which
       allowed that behaviour to be relegated to cwal. */
};

scope {
    /** 2020-02-06: dot-length operator (lhs.#) now works on non-list
        containers, resolving to the number of properties. */
    assert 2 === import.# /* path+doPathSearch properties */;
    assert 3 === {a:1, b:1, c:1}.#;
}

scope {
    /** 2020-02-18: {x:=y} sets x as a const property. */
    const p = {x:1};
    const o = {a:1, b:=2, c:3, prototype:=undefined};
    assert 2 === o.b;
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o.b = 1}.codeString();
    assert p === (o.prototype = p)
      /* consting the prototype has no effect b/c we don't have a way
         to flag/enforce that constraint at the C level. */;
    assert 0 === o.clearProperties().#
           /* also removes const properties! Bug or feature? */;
    assert 1 === (o.b=1 /* constness was lost via clearProperties() */);
}

scope {
    /** 2020-02-18: x.y:=z sets x as a const property. */
    const o= {a:1};
    assert 2 === (o.a=2);
    assert 3 === (o.a:=3);
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o.a = 1}.codeString();
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o['a'] = 1}.codeString();
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o.a := 1}.codeString();

    assert 'CWAL_SCR_SYNTAX' === catch {o := 1}.codeString()
      /* := op only works for property assignment. */;
    assert 1 === o.#;
    assert o === o.clearProperties();
    assert 0 === o.#;
}

scope {
    /** 2020-02-20: inline expansion of object properties into an
        object literal, similar to JS's {...otherObj}, which it calls
        "spread" syntax. We use the @ prefix because already use that
        for @array expansion, which is semantically very similar to
        what we use it for here.
    */
    var o = {a: 1, b: 2, c:3};
    var o2 = {@o};
    assert o.# === o2.#;
    foreach(o=>k,v) assert o2[k]==v;

    o2 = {@o, c:4};
    assert o.# === o2.#;
    assert 4 === o2.c;

    o2 = {a:4, @o, d:5};
    assert o.#+1 === o2.#;
    assert o.a === o2.a;
    assert 5 === o2.d;

    o2 = {d:5, @o};
    assert o.#+1 === o2.#;
    assert 5 === o2.d;

    o2 = {@{a:1, b:2}};
    assert 2 === o2.#;
    assert 1 === o2.a;
    assert 2 === o2.b;

    o2 = {@proc(){return {a:1,b:2}}(), c:3};
    assert 3 === o2.#;
    assert 1 === o2.a;
    assert 2 === o2.b;
    assert 3 === o2.c;
    
    var ex = catch{ {a:1, @, b:1} };
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
    assert ex.message.indexOf('empty expression') > 0;

    ex = catch{ {@3} };
    assert 'CWAL_RC_TYPE' === ex.codeString();
    assert ex.message.indexOf('container') > 0;

    assert 'CWAL_RC_CONST_VIOLATION' === catch o2 = {a:=1, @o}.codeString();
    o2 = {@o, a:=3};
    assert 3 === o2.a;
    assert 'CWAL_RC_CONST_VIOLATION' === catch {o2.a =1}.codeString();
    
    o = {
        // Special case: constructor in an object literal is
        // assigned as hidden/const, and hidden properties
        // are not iterated over but do count towards the
        // container.# operator...
        __new:proc(){}
    };
    assert 1 === o.#;
    o2 = {@o};
    assert 0 === o2.#;

    assert 'CWAL_RC_TYPE' === catch{
        /*
          The "problem" with enums is that they can use either object
          or hash storage internally, so we would need to
          differentiate between the two in the @ expansion.  We don't
          currently do that, but maybe someday will.

          ^^^^ that's outdated. enums now always use hashes.
        */
        {@enum {a,b,c}}
    }.codeString();
}

scope {
    /* 2021-06-24: ensure that vars declared in property access [] or ()
       are not leaked into the current scope. e.g.

       var o = {}; o[var x = 'hi'] = 1; assert 'hi'===x;
    */
    var o = {};
    o[var xx = 'a'];
    assert !typeinfo(isdeclared xx);
    o.(var xx = 'b');
    assert !typeinfo(isdeclared xx);
    /* But we want standalone (...) to run in the current scope. */
    1 && (var xx = 'y');
    assert 'y' === xx;
}

scope {
    /* 2021-07-09: Ensure that cwal_prop_key_can() prohibits certain
       property key types... */
    const o = {};
    assert 'CWAL_RC_TYPE' === catch{
        o[new s2.Buffer()] = 1;
    }.codeString();
    assert 'CWAL_RC_TYPE' === catch{
        o[[#]] = 1;
    }.codeString();
}

o /* propagate top-most o out for lifetime checks */;

Deleted bindings/s2/unit/050-000-func-call.s2.

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

































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

scope {
    // make sure we can chain calls...
    const P = proc f(){return f};
    P.x = P;
    assert P === P('from P()');
    assert P === P.x('from P.x()');
    assert P === P.x['x']('from P.x["x"]()');

    // Demonstrate skip-mode's effect on function calls:
    false && P.x.y.z(foo);
    true || P.x.y.z(foo);

    assert P === (P.x)('from (P.x)()');

    var o = {
        p: P
    };
    assert P === o.p('from o.p()');

    if(s2.isCallable){
        assert s2.isCallable(s2.isCallable, false);
        assert s2.isCallable(s2.isCallable, true);
        assert s2.isCallable(s2.isCallable);
        o = {
            prototype: proc(){
                ++this.value;
                this.args = argv;
                return argv;
            },
            value: 0
        };
        assert 0 === o.value;
        assert undefined === o.args;
        assert o(1,2,3) === o.args;
        assert 1 === o.value;
        assert 3 === o.args.2;
        assert s2.isCallable(o) /* searches up through prototypes */;
        assert !s2.isCallable(o,false) /* does not search prototypes */;
        assert s2.isCallable(o,true) /* searches up through prototypes */;
    }
}

scope { // exploring "callable" objects...
    var obj = {
        foo: proc(){
            assert this === obj;
        }
    }.eachProperty(proc(k,v){
        //print(__FLC,'eachprop',k);
        v.importSymbols(nameof this);
    });

    // let's change up "this" a bit...
    var f = obj.foo;
    obj.foo();
    f();
    obj.bar = proc(name){
        //print(name,this);
        return this === eval -> name;
    };
    assert obj.bar(nameof obj);
    var x =obj.bar;
    assert x(nameof x);
}

scope {
    /* Prior to 20181015, completely empty functions were optimized
       away at call()-time. This led to side-effects in their
       parameter/argument lists not happening, e.g.: proc(){}(assert 0) did not
       fail. Make sure that's no longer the case...

       We now optimize away the call() part but still process the
       argument list, if any, so that side-effects can trigger.
    */
    var f = proc(){}, x = 3, y;
    f(y=x);
    assert x === y /* side-effects in argument list trigger */;
    assert (catch proc(){}(affirm 0)).message.indexOf('Affirmation') >= 0 /* affirmation must trigger */;

    /* Let's test some more complicated cases involving default
       parameter values which use call()-time imported symbols... */
    y = 0;
    f = proc callee(a=(y=Z), b=(z=callee.foo)){} using{Z:2};
    f.foo = 7;
    assert 0 === y;
    var z;
    f();
    assert 2 === y /* side effect of default param value using imported symbol */;
    assert 7 === z /* side effect of default param value using imported symbol */;
    y = 0;
    f.foo = 8;
    f(1);
    assert 0 === y /* skipped side effect of default param value */;
    assert 8 === z /* side effect of default param value using imported symbol */;
    z = 0;
    f(1,2);
    assert 0 === z /* skipped side effect of default param value */;

    z = 0;
    f = proc(a=(z=this.foo)){};
    f.foo = 7;
    f();
    assert 7 === z /* 'this' was set up before default parameter values were processed */;
}

scope {
    // Check for handling of errant continue/break...
    // Prior to 20181104 continue/break in call param lists
    // were handled incorrectly.

    const ar = [], f=proc(){};
    foreach(@[1,2,3]=>v) f(ar[] = v%2 ? v : continue);
    assert 2 === ar.#;
    assert 1===ar.0;
    assert 3===ar.1;

    ar.length(0);
    foreach(@[1,2,3]=>v) f(ar[] = v%2 ||| break);
    assert 1 === ar.#;
    assert 1===ar.0;

    var ex = catch foreach(@ar=>v) proc(){continue}();
    assert typeinfo(isexception ex);
    assert 0===ex.message.indexOf("Unhandled 'continue'");

    ex = catch foreach(@ar=>v) proc(){break}();
    assert typeinfo(isexception ex);
    assert 0===ex.message.indexOf("Unhandled 'break'");
}

scope {
    /**
       20190820: confirm fix of @-expansion bug when a newline preceeds
       the @expr in a function call. */
    const f = proc(){}, args = [1,2,3];
    f(1,2,3,
      @args);
    // ^^^ previously, that would fail with "'@' is not allowed here
}

scope {
    /**
       2020-02-18: var keyword now supports the := op to assign as
       const, but it is explicitly disabled for function parameters
       (which are handled by the same C-level code
       (s2_keyword_f_var_impl())) because of inconsistencies in
       parameters with and without default values. Namely, we can
       easily make the argument to proc(a:=2){...} const whether or
       not an argument is passed to the function call but we have no
       syntax to saying that it sould be const unless it has a default
       value. e.g. we could not make that same parameter const in
       proc(a){...}.
    */

    var f = proc(a:=1){} /* params are not parsed yet, so won't fail
                            here. */;
    var ex = catch f();
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
    assert ex.message.indexOf(':=') > 0;
}

Deleted bindings/s2/unit/060-000-numbers.s2.

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


































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
scope {
    /* Some sanity checks... */
    assert 0xffff === 0xf_f__f___f;
    assert 0b101 === 5;
    assert 0b101 === 0b_1__0___1;
    assert 0x13 === 0b_0001_0011;
    assert 123 === 1_2__3;
    assert 501 === 0o7_6__5;
}

scope {
  //print(0.INT_MIN, 0.INT_MAX);
  assert 0.INT_MIN < 0;
  assert 0.INT_MAX > 0;
  
  assert 0.INT_MIN-1 === 0.INT_MAX /* This (signed underflow/overflow) "should" be is platform-dependent: */;
  assert 0.INT_MAX+1 === 0.INT_MIN /* This (signed underflow/overflow) "should" be is platform-dependent: */;

  var i = 0;
  assert 'integer' === typename i;
  assert 0 === i.compare(0);
  assert i.compare(1) < 0;
  assert i.compare(-1) > 0;
  assert 0.0 === i.toDouble();
  assert 0 !== i.toDouble();
  assert "0" === i.toJSONString();
  assert "0" === i.toString();
  assert "1" === 1.toString();
  i = 42;
  assert '*' === i.toChar();
}

scope {
  var d = 0.0;
  assert 'double' === typename d;
  assert 0 === d.compare(0);
  assert d.compare(1) < 0;
  assert d.compare(-1) > 0;
  assert 0 === d.toInt();
  assert 0.0 !== d.toInt();
  assert "0.0" === d.toJSONString();
  assert "0.0" === d.toString();
  assert "1.0" === 1.0.toString();
  assert 1 === 0.5.ceil();
  assert 0 === (-0.5).ceil(); // parens needed: dot has higher precedence than unary -.
  //assert 0 === 0.ceil(); // INTEGER 0 doesn't have ceil()
  assert 0 === 0.0.ceil();
  assert -1 === (-1.0).ceil();
  assert -1 === (-1.01).ceil();
  assert 0 === (-0.999).ceil();
  assert -2 === (-2.2).ceil();
  assert 3 === 2.2.ceil();
  assert 2 === 2.0.ceil();

  assert 1 === 1.5.floor();
  assert 1 === 1.0.floor();
  assert 0 === 0.0.floor();
  assert 0 === 0.5.floor();
  assert -1 === (-0.5).floor();
  assert -2 === (-1.5).floor();
}

scope {
  1.0.prototype.twice = proc(){ return this * 2 };
  assert 6.2 === 3.1.twice();
  assert 8.0 === (2.0*2).twice();
  //unset 0.0.prototype.twice; // unset doesn't like this, so...
  var d = 0.0;
  unset d.prototype.twice, d;
}

scope { // number.parseInt/Double/Number()
    const pi = 0.parseInt, pf = 0.parseDouble, pn = 0.parseNumber;
    assert 1 === pi(1);
    assert 1 === pi('1');
    assert undefined === pn('1_');
    assert undefined === pi('1_');
    assert 1 === pi('1.0');
    assert -1 === pi('-1');
    assert -1 === pi('-1.2');
    assert undefined === pi('1-1');
    assert 1.0 === pf(1);
    assert 1.0 === pf('1');
    assert 1.0 === pf(true);
    assert 0.0 === pf(false);
    assert 0.0 === pf('-0');
    assert 1 === pn(1);
    assert 1 === pn('1');
    assert 1.0 === pn(1.0);
    assert 1.0 === pn('1.0');
    assert 0 === pn('-0');
    assert 0.0 === pn('  +   0.0');
    assert 56 === pn('0o70');
    assert 56 === pn('0o7_0');
    assert 56 === pn('0o_7__0');
    assert undefined === pn('0o1_')/*trailing non-digit*/;
    assert undefined === pn('0o18')/*trailing non-[octal-]digit*/;
    assert -1 === pn('-0o1');
    assert -1 === pn('-0x0001');
    assert undefined === pn('-0x0001.')/*trailing non-digit*/;
    assert undefined === pn(pn);
    assert pf('1') === pn('1.0');

    assert 'double' === typename pn("1.3") /* parseNumber() keeps the numeric type */;
    assert 'integer' === typename pn("1") /* parseNumber() keeps the numeric type */;
    assert 'integer' === typename pi("1.3") /* parseInt() reduces to an integer */;
}

scope { // number.nthPrime()
    assert 2 === 0.nthPrime(1);
    assert 7919 === 0.nthPrime(1000);
    assert 'CWAL_RC_RANGE' === catch {0.nthPrime(0)}.codeString();
    assert 'CWAL_RC_RANGE' === catch {0.nthPrime(1001)}.codeString();
    assert 'CWAL_RC_MISUSE' === catch {0.nthPrime()}.codeString();
}

scope { // toString()
    assert '1' === 1.toString();
    assert '1.0' === 1.0.toString();
    assert '1' === 1.0.toString('d');
    assert '000c' === 12.toString('04x');
    assert '000C' === 12.toString('04X');
    assert '0e' === 0b11_10.toString('02x');
    assert 'FBF' === 0x_f_B_f.toString('X');
    assert '765' === 0o7_6_5.toString('o');
    assert '765' === 0b111_110_101.toString('o');
}

scope {
    assert 0x_f_0 === 2_4__0;
    /* Interesting: we can't catch these errors because they trigger
       in the tokenizer while slurping the {...} blocks.  That means
       that catch cannot really know that it "could" safely convert
       these fatal syntax errors to non-fatal exceptions. */
    //assert 0 === catch {1_}.message.indexOf('Malformed');
    //assert catch {1_2.3}.message.indexOf('not legal') > 0;
    /* So... to test these we'll wrap them in strings and eval them in
       the 2nd-pass phase, as 2nd-pass eval knows that it can convert
       fatal syntax errors to non-fatal exceptions. Note that using
       eval=>{...} to capture them as strings cannot work here for the
       same timing reason. */
    assert 0 === catch -> {'1_'}.message.indexOf('Malformed numeric literal');
    assert 0 === catch -> {'1.2_'}.message.indexOf('Malformed numeric literal');
    assert 0 === catch -> {'0b1_'}.message.indexOf('Malformed binary');
    assert 0 === catch -> {'0o1_'}.message.indexOf('Malformed octal');
    assert 0 === catch -> {'0x1_'}.message.indexOf('Malformed hex');
    assert catch -> {'1_2.3'}.message.indexOf('not legal in floating-point') > 0;
}

if(0.INT_MAX > 0xFFFF_FFFF /* 64 bit */) {
    const largeI = 0xffff_ffff_ffff,
          largeD = largeI.toDouble();
    assert 0.parseNumber(largeD.toString()) === largeD;
    assert 0.parseNumber(largeI.toString()) === largeI;

    const largeD2 = 10.356 + 0xffffffffffff,
          d2Str = largeD2.toString();
    /* was "2.814749767107e14" prior to 20181127, but now
       it's "281474976710665.4" (or thereabouts). */
    assert d2Str.indexOf('e')<0 /* no scientific notation */;
    assert 0.parseNumber(d2Str) === largeD2;
}

Deleted bindings/s2/unit/070-000-enum.s2.

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






























































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


const countEach = proc callee(k,v){
    if(1===++callee.count){
        assert 'unique' === typeinfo(name v);
        assert 'string' === typeinfo(name k);
        assert this inherits enumProto;
    }
};

const enumProto = (enum {a}).prototype;

scope {
    assert 'enum' === typeinfo(name enum{a,b,c});
    assert 'CWAL_SCR_SYNTAX' === catch{enum {a,b,c, /* extra trailing comma */}}.codeString();
    assert 'CWAL_SCR_SYNTAX' === catch{enum {a,,c /* extra comma */}}.codeString();
    assert 'CWAL_SCR_SYNTAX' === catch{enum {a:,,c /* missing value after ':' */}}.codeString()
}

scope {
    const E = enum MyEnum {
        // This small enum will use an Object for storage.
        A, B, C
    };

    assert 'MyEnum' === typeinfo(name E);
    assert E inherits enumProto;
    assert 3 === E.#;
    assert E.A;
    assert undefined === E.A.prototype
        /* prototype pseudo-property is allowed but (currently) evals
	   to undefined */;
    assert E.hasEnumEntry(E.A);
    assert E.hasEnumEntry('A');
    assert !E.hasEnumEntry('a');
    assert 'C' === E[E.('C')];
    assert catch {E.x}.message.indexOf('Unknown')>=0;
    assert catch {E.A = 1}.message.indexOf('disallowed')>=0;
    var ar = E.getEnumKeys();
    assert 'array' === typeinfo(name ar);
    assert 3 === ar.length();
    assert ar.indexOf('C') >= 0;
    assert ar.indexOf('D') < 0;

    E.eachEnumEntry(countEach);
    assert 3 === countEach.count;
}

scope {
    const Big = enum {
        ☺,b,c,d,e,
        f,g,h,i,j,
        k,l,m,n,o,
        p,q,r,s,t,
        u,v,
        こんにちは
    };
    const bigLen = Big.#;
    assert 23 === bigLen;
    assert 'enum' === typeinfo(name Big);
    assert Big inherits enumProto;
    assert 'c' === Big[Big.c];
    assert '☺' === Big[Big.☺];
    assert 'こんにちは' === Big[Big.こんにちは];
    assert Big.こんにちは === Big.'こんにちは';
    assert Big[<<<X こんX + 'にちは'] === Big->'こんにちは';
    assert catch {Big.z}.message.indexOf('Unknown')>=0;
    assert catch {Big.b = 1}.message.indexOf('disallowed')>=0;

    var ar = Big.getEnumKeys();
    assert 'array' === typeinfo(name ar);
    assert bigLen === ar.length();
    assert ar.indexOf('☺') >= 0;
    assert ar.indexOf('B') < 0;

    countEach.count = 0;
    Big.eachEnumEntry(countEach);
    assert bigLen === countEach.count;
}

scope {
    var x = enum {a,b,c,d,e,f,g,h,i,j,k};
    assert 'unique' === typeinfo(name x.a);
    /**
       20191210: we may disallow enum prototype reassignment in the
       future, so don't rely on this feature. It's not a question of
       whether it makes sense to allow it, but whether or not it's a
       tolerable inconsistency vis-a-vis the disallowing of setting
       non-prototype properties. For relevant commentary, search for
       comments in s2.c near references to
       CWAL_CONTAINER_DISALLOW_PROP_SET. "The problem" is that if we
       allow it here, we always have to allow it on other "sealed"
       objects, such as the one which the s2out keyword resolves to.
    */
    x.prototype = {
        prototype: x.prototype,
        z: 3
    };
    assert 'enum' === typeinfo(name x);
    assert x.a;
    assert 3===x.z;
    assert 'CWAL_RC_DISALLOW_PROP_SET'
        === catch{x.z = 1}.codeString();
    // But...
    x.prototype.z = 4;
    assert 4===x.z /* modified in (mutable) prototype */;
    var ar = x.getEnumKeys();
    assert 'array' === typename ar;
    assert x.# === ar.length();
    assert 'CWAL_RC_NOT_FOUND'
      === catch{x['x']}.codeString();

}

scope {
   
    var x = enum {'a',b,c,d,e,f,g,h,i,j,k};
    assert 'unique' === typename x.a;
    assert 'CWAL_RC_DISALLOW_PROP_SET' === (catch x.z = 1).codeString();
    assert catch {x.z}.message.indexOf('Unknown')>=0;
    x.prototype = {
        prototype: x.prototype,
        z: 3
    };
    assert undefined === catch {x.z};
    assert 'enum' === typename x;
    assert x.a;
    assert 3===x.z;
    ++x.prototype.z;
    assert 4 === x.prototype.z;

    assert 'a' === x.a.value;
    assert 'a' === x::a;

    var e = enum { E, A: 'Aa', "B": 'abc', C: 13, OBJ: {x: 1, y:0}, D};
    assert 'enum' === typeinfo(name e);
    assert 'Aa' === e.A.value;
    assert 'abc' === e.B.value;
    assert 13 === e.C.value;
    assert 0 === e.OBJ.value.y;
    assert 1 === e.OBJ['value'].x;
    assert 1 === e.OBJ.('va'+'lue').x;
    assert 'CWAL_RC_TYPE' === catch (e.OBJ.invalid).codeString();
    assert e[e.A] === 'A';
    assert 'D' === e.D.value;
    assert 2 === e.A.value.length();
}

scope {
    const e = enum {
        a: {x:1, y:undefined},
        b: proc(){return 1},
        c,
        d
    };
    assert 4 === e.#;
    assert 1 === e.a.value.x;
    assert 1 === e::a.x;
    assert 1 === e::'a'.x;
    assert 1 === e.b.value();
    assert 1 === e::b();
    assert undefined === e.a.value.y++;
    assert e.a.value.x === e.a.value.y;
    assert 2 === ++e::a.y;
    assert 2 === e::a.y;
    assert 2 === e::(scope{while(true) break 'a'}).y
        /* reminder: parens needed b/c dot/dotdot treat all identifiers
           on the RHS equally, and does not expand keywords as keywords. */;
    assert e.c;
    assert 'c' === e::c;
    assert 'CWAL_RC_DISALLOW_PROP_SET' === (catch e.a = 1).codeString();
    assert 'CWAL_RC_NOT_FOUND' === catch{e.x}.codeString();
    assert 'CWAL_RC_NOT_FOUND' === catch {e::x}.codeString();
}

scope {
    scope {
        var e = enum {a:{x:1},b:{x:2}};
        e.a.value.b = e.b;
        e.b.value.a = e.a;
    }
    assert 1 /* must not crash during prior scope's cleanup (former
                cwal bug in cleanup handling of Unique-type values).*/;
    /* Must also properly upscope everything... */
    assert 200 === scope {
        var e = enum {a:{x:100},b:{x:200}};
        e::a.b = e.b;
        e::b.a = e.a; 
        assert e.a.value.b === e::a.b;
        assert e::a.b === e.b;
        e.a.value.b/*===e.b*/
    }.value.a/*===e.a*/.value.b.value.x;
    assert scope {
        enum {a:{x:100}}::a
    }.x === 100;
    assert enum {a:{x:-100}}::a.x === -100;
}

scope {
    const o2e = proc(obj, name) {
        affirm typeinfo(iscontainer obj);
        var b = new s2.Buffer(100).append("enum ", name ||| '', '{'),
            first = true;
        foreach(obj=>k){
            first ? first = false : b.append(", ");
            b.append(k , ": obj.",k);
        }
        return b.append("}").evalContents(__FLC);
    };
    var obj = {a:1, b:{x:2}};
    var e = o2e(obj, 'blah');
    assert 'blah' === typeinfo(name e);
    assert 2 === e.#;
    //print(__FLC, e);
    foreach(obj=>k, v) {
        assert e::(k) === v;
        assert e->(k).value === v;
        assert e.(k).value === v;
        assert v === ('e.'+k+'.value').evalContents(__FLC);
        assert v === ('e::'+k).evalContents(__FLC);
    }
    
    assert enum{a:enum{x:30}}::a::x + enum{b:30}::b === 60 /* lifetime(s) check */;
}

scope {
    const e = enum {
      f1: proc(){assert 'enum' === typeinfo(name E)},
      f2: proc(){assert 'unique' === typeinfo(name E.f1)}
    };
    const imports = {E:e};
    foreach(e=>k,v) v.value.importSymbols(imports);
    e::f1();
    e::f2();
    e::f1.z = e.f1
        /* will propagate e.f1 out of the scope. Formerly this crashed
           during scope cleanup due to a mis-assertion in the Unique-type's
           finalizer. Let's see what happens to it... */;
}

scope {
    /* 20191210: it's now possible (though probably not wise) to set
       the prototype property in an enum's body. Before this, that
       handling was not well-defined. */
    const x = {__typename: "X"};
    assert catch {enum {prototype:x}}.message.indexOf("at least one") > 0;
    var e = enum {prototype:x, a};
    assert 'X' === typeinfo(name e);
    assert e inherits x;
    assert typeinfo(isenum e);
    e = enum eee {prototype:x, b, c};
    assert 'eee' === typeinfo(name e);
    assert e inherits x;
    assert typeinfo(isenum e);

    e = enum hhh {prototype:x, b, c, d, e, f, g, h, i, j, k};
    assert 'hhh' === typeinfo(name e);
    assert e inherits x;
    assert e.prototype === x;
    assert typeinfo(isenum e);

    e = enum { prototype: null /* remove prototype */, a };
    assert catch {e.prototype}.message.indexOf('Unknown')===0;
    /**
       Interestingly, this formulation doesn't work:

       assert catch e.prototype.message.indexOf('Unknown')===0;

       Unexpected consecutive non-operators:
         #3006 (KeywordCatch) ==> #1011 (Identifier)

       At the ".message" part. Because... ???

       Hmmm.
    */

    ;;; /* <=== for vacuum testing */
}

scope {
    /* As of 2020-02-21, enums are always hashes, but the the
       restriction against using the hash search operator persists
       because it would otherwise behave exactly as the -> operator.
       typeinfo(ishash) still evals to false for enums.
    */
    const e = enum{a};
    assert typeinfo(isunique e.a);
    assert 'a' === e::a;
    assert e.a === e->'a';
    assert catch {e#'a'}.codeString()==='CWAL_RC_TYPE' /* hash op is not allowed on enums */;
    assert e::a === e.'a'.value;
    assert typeinfo(is-enum e);
    assert !typeinfo(is-hash e);
}

scope {
    /* Examples from the user docs... */
    const e = enum OptionalTypeName {
        a, // its own name (a string) as its value
        b: 2, // any value type is fine
        f: proc(){return this}
    };
    assert 3 === e.#;
    assert 'a' === e.a.value;
    assert 'a' === e::a; // equivalent
    assert 2 === e.b.value;
    assert 2 === e::b; // equivalent unless the entry.value is a Function call:
    assert e.f === e.f.value(); // e.f is bound as 'this'
    assert e::f === e::f(); // no 'this', so e.f.value is its own 'this'
    assert 'b' === e[e.b]; // get the string-form name of an entry
    assert e.a === e['a']; // note that e['a'] is functionally identical to e.a.
    assert 'OptionalTypeName' === typeinfo(name e);
    assert undefined === e->'c'; //-> op does not throw for unknown properties
    assert catch {e#'c'}.codeString()==='CWAL_RC_TYPE'; // hash op not allowed
    assert catch {e::c}.codeString()==='CWAL_RC_NOT_FOUND'; // unknown property
    assert catch {e.c}.codeString()==='CWAL_RC_NOT_FOUND'; // unknown property
    assert catch {e.a = 1}.codeString()==='CWAL_RC_DISALLOW_PROP_SET';
}

Deleted bindings/s2/unit/099-000-typeinfo.s2.

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






































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/*
  typeinfo keyword tests...
*/

assert typeinfo(isbool true);
assert typeinfo(is-bool false);
assert !typeinfo(isbool 1);

assert typeinfo(isinteger 3);
assert !typeinfo(is-integer 3.3);
assert typeinfo(isdouble 3.3);
assert !typeinfo(is-double 3);
assert typeinfo(hasprototype 3);

scope {
    const b = s2.Buffer.new();
    assert typeinfo(isbuffer b);
    assert typeinfo(hasbuffer b);
    assert !typeinfo(is-buffer {prototype:b});
    assert typeinfo(has-buffer {prototype:b});
    assert !typeinfo(isbuffer s2.Buffer);
    assert !typeinfo(hasbuffer s2.Buffer);
    assert typeinfo(cannew s2.Buffer);
    assert typeinfo(can-new s2.Buffer);
    assert !typeinfo(isnewing s2.Buffer);
    assert !typeinfo(is-newing s2.Buffer);
    /* is-newing positive test is in the 'new' unit tests */
    assert !typeinfo(cannew 0);
    assert !typeinfo(can-new 0);
}

assert !typeinfo(isdeclared hasNative);
assert !typeinfo(islocal hasFunction);


const obj = {},
      ar = [],
      h = {#},
      hasHash = {prototype:h},
      hasArray = {prototype:ar},
      f = proc(){},
      hasFunction = {prototype:f},
      e = enum {a:undefined,b},
      ex = exception(0,0),
      hasEx = {prototype: ex},
      pf = new s2.PathFinder(),
      hasNative = {prototype:pf}
;

assert catch {
    obj = {a:1}
    /* 20160226: BUG: when hashes are used as scope storage, const
       violations are not caught! Not sure why - a cursory examination
       seems to imply that all const checks are in place.

       Can't reproduce it in the interactive shell!?!?

       Was caused by the automatic resizing of hashes losing their
       flags.
    */;
};

assert typeinfo(is-declared hasFunction);
assert typeinfo(is-local hasFunction);
scope{
    var x;
    assert !typeinfo(islocal hasFunction);
    assert typeinfo(islocal x);
}

assert typeinfo(hasprototype obj);
assert !typeinfo(has-prototype {prototype:null});

assert typeinfo(hasobject obj);
assert typeinfo(isobject obj);
assert typeinfo(has-object ar);
assert !typeinfo(is-object ar);
assert typeinfo(iscontainer ar);

assert typeinfo(hasobject 0);
assert !typeinfo(isobject 0);
assert !typeinfo(hasobject null);

assert typeinfo(isderefable obj);
assert typeinfo(is-derefable ar);
assert typeinfo(isderefable f);
assert typeinfo(iscontainer f);
assert typeinfo(isderefable 0);
assert !typeinfo(is-container 0);

assert !typeinfo(isderefable null);
assert !typeinfo(iscontainer null);
assert !typeinfo(isderefable undefined);
assert !typeinfo(isderefable true);
assert typeinfo(isderefable e.a);
assert !typeinfo(isderefable e.a.value);

assert typeinfo(isarray ar);
assert typeinfo(hasarray ar);
assert typeinfo(islist ar);
assert !typeinfo(is-array obj);
assert !typeinfo(has-array obj);
assert !typeinfo(isarray hasArray);
assert typeinfo(hasarray hasArray);
assert !typeinfo(islist hasArray);

assert typeinfo(iscallable f);
assert typeinfo(is-callable hasFunction);
assert typeinfo(isfunction f);
assert !typeinfo(is-function hasFunction);

assert typeinfo(isenum e);
assert typeinfo(is-enum e);
assert !typeinfo(isenum obj);
assert typeinfo(isunique e.a);
assert typeinfo(is-unique e.a);

assert !typeinfo(hashash obj);
assert !typeinfo(ishash obj);
assert !typeinfo(is-hash e)
 /* 2020-02-21: though enums are technically hashes, we
    report them here as non-hashes because not all hash
    operations work on enums and reporting them as such
    might lead script code to inadvertently send an enum
    down a hash-only code path. */;
assert !typeinfo(has-hash e);

assert typeinfo(has-hash h);
assert typeinfo(is-hash h);
assert typeinfo(hashash hasHash);
assert !typeinfo(ishash hasHash);

assert typeinfo(isexception ex);
assert typeinfo(hasexception hasEx);
assert !typeinfo(is-exception hasEx);
assert typeinfo(has-exception ex);


assert typeinfo(isinteger 1);
assert !typeinfo(is-integer '1');
assert typeinfo(isdouble 1.0);
assert !typeinfo(is-double 1);

assert typeinfo(isnumber 1);
assert !typeinfo(is-number true);
assert typeinfo(isnumeric true);
assert typeinfo(is-numeric '3');
assert !typeinfo(isnumeric '3x');
assert typeinfo(isnumeric '-3.3');
assert typeinfo(isnumeric '+0o7');
assert !typeinfo(isnumeric '0o8');

assert typeinfo(isnative pf);
assert typeinfo(hasnative pf);
assert !typeinfo(is-native obj);
assert !typeinfo(isnative hasNative);
assert typeinfo(has-native hasNative);
assert !typeinfo(hasnative obj);

assert typeinfo(isstring '');
assert !typeinfo(is-string 1);
assert typeinfo(isstring <<<X X);
assert typeinfo(isstring for(;;) break '1');

assert 'array' === typeinfo(name ar);

obj.x = 1;
assert typeinfo(mayiterateprops obj);
obj.eachProperty(proc(){
    assert typeinfo(mayiterateprops this);
    assert typeinfo(mayiterate this);
    assert !typeinfo(mayiteratelist this);
    assert typeinfo(isiteratingprops this);
    assert !typeinfo(isiteratinglist this);
    assert typeinfo(isiterating this);
    assert 'CWAL_RC_IS_VISITING' === catch {this.x=1}.codeString();
});
foreach(obj=>k){
    assert typeinfo(may-iterate-props obj);
    assert typeinfo(may-iterate obj);
    assert !typeinfo(may-iterate-list obj);
    assert typeinfo(is-iterating-props obj);
    assert !typeinfo(is-iterating-list obj);
    assert typeinfo(is-iterating obj);
}
assert !typeinfo(isiterating obj);
assert !typeinfo(isiteratingprops obj);
assert !typeinfo(isiteratinglist obj);

scope {
    var t0 = [#/* empty tuple is a built-in constant */],
        t2 = [#1,2];
    assert typeinfo(istuple t0);
    assert typeinfo(is-tuple t2);
    assert typeinfo(islist t0);
    assert typeinfo(islist t2);
}

Deleted bindings/s2/unit/100-000-object-methods.s2.

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






































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

var o = {a:1, 2:'two', __typename:'Fred'};
scope {
  assert 'Fred' === typename o;
  assert o.hasOwnProperty('a');
  assert !o.hasOwnProperty('hasOwnProperty');
  assert o.prototype.hasOwnProperty('hasOwnProperty');
  assert o.hasOwnProperty(2);
  assert pragma(build-opt CWAL_OBASE_ISA_HASH)
        ? !o.hasOwnProperty('2') : o.hasOwnProperty('2');
  unset o.2;
  assert !o.hasOwnProperty(2);
  assert 0 != o.compare({});
  assert 0 === o.compare(print,print);
  assert 0 === o.compare(o);
  assert 0 === o.compare(o,o);
  assert 0 === o.compare(1,1);
  assert o.compare(1,-1) > 0;
  assert o.compare(-1,1) < 0;
}

scope {
  var k = o.propertyKeys();
  assert 'array' === typename k;
  assert 'string' === typename k.0;
  //print('o.propertyKeys() ==',k);
}


scope {
  o.clearProperties();
  assert 'object' === typename o;
  assert undefined === o.a;
}

scope {
  assert !o.hasOwnProperty('x');
  o.x = 1;
  assert 1 === o.x;
  assert o.hasOwnProperty('x');
  o.unset('x');
  assert undefined === o.x;
  assert !o.hasOwnProperty('x');
}

scope {
  assert o.mayIterate();
}

scope {
    o.a = 3, o.b = 7, o.c = 42.24, o.d = [1,2,3], o.1 = -1;
    const json = o.toJSONString(' ');
    const o2 = eval -> json;
    assert typename o === typename o2;
    assert o2.d[1] === o.d[1];
    if(!pragma(build-opt CWAL_OBASE_ISA_HASH)){
        /*only works if !CWAL_OBASE_ISA_HASH, else order is undefined*/
        assert o2.toJSONString(1) === json;
        const abc = o.toJSONString('abc');
        assert abc.indexOf('abc')>0;
        assert abc.toLower() === o2.toJSONString('ABC').toLower();
    }
}

scope {
  o.x = -1;
  assert -1 === o.get('x');
  o.set('x',1);
  assert 1 === o.get('x');
  o.set({Z: 'zzz'});
  assert 'zzz' === o.Z;
  assert true === o.unset('x','Z');
  assert false === o.unset('x','Z');
  assert undefined === o.Z;
  //o.compare(); // check that exception contains call-point line/col info
}

scope {
  o.s = o;
  var counter = 0;
  o.p = proc f(){
    counter = counter + 1;
    return f;
  };
  o.('p')(__FLC)(__FLC);
  assert 2===counter;
  counter = 0;
  o.s.p(__FLC)(__FLC)(__FLC);
  assert 3===counter;
  counter = 0;
  o['s'].('p')(__FLC);
  o['s'].s.('p')(__FLC);
  o['s'].s['p'](__FLC);
  assert 3===counter;
  o;
}

scope {
  var counter = 0, lastKey, lastVal;

  const f = proc(k,v){
    assert this.mayIterate()/*as of 20191212*/;
    counter = counter + 1;
    //print(k,v);
    lastKey = k;
    lastVal = v;
  };
  assert o.mayIterate();
  o.eachProperty(f);
  assert o.mayIterate();
  assert counter === o.propertyKeys().length();
  assert undefined !== lastKey;
  assert undefined !== lastVal;
  assert lastKey !== lastVal;
}

scope {
    /* Ensure that an object which inherits Function is call()able
       and that 'this' resolution works right. */
    var x = 0;
    var f = proc callee(){
        assert this inherits callee;
        assert this !== callee /* 'this' is one step down the prototype chain */;
        /* 'this' for non-property call() === the function resp. the call()able */;
        ++x;
        ++this.z;
    };
    var y = { prototype: f, z: 1 };
    assert y inherits f;
    y();
    assert 1 === x;
    assert 2 === y.z;
}

scope {
    var o = {
    };
    assert o.isEmpty();
    assert 0 === o.propertyCount();
    o.a = 1, o.b = 2;
    assert !o.isEmpty();
    assert 2 === o.propertyCount();

    var y = {};
    var x = o.copyPropertiesTo(y, {});
    assert x !== o;
    assert x !== y;
    assert 2 === x.propertyCount();
    foreach(x=>k,v){
        assert v === o[k];
        assert v === y[k];
    };

    assert 'CWAL_RC_TYPE' === catch{x.copyPropertiesTo(1)}.codeString();
    assert 'CWAL_RC_MISUSE' === catch{x.copyPropertiesTo(/*no args*/)}.codeString();
    assert 'CWAL_RC_TYPE' === catch{x.copyPropertiesTo.call(0/*not a container*/, 1)}.codeString();
}

scope {
    const o = {};
    assert o === o.withThis(proc(){});
    assert o === o.withThis(proc(){return});
    assert 1 === o.withThis(proc(){return 1});
}

o; // propagate this out to make sure it survives the propagation process w/o being cleaned up.

Deleted bindings/s2/unit/100-050-overloading.s2.

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










































































































































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/* Demonstrate operator overloading... */  

if(1){
  assert "3.00.1" === "3.0"+0.1 /* bug chasing */;

  var pCount = 0, mCount = 0, o = {
    a: 0,
    // Binary X+Y
    'operator+': proc(r){
      assert 1 === argv.length();
      ++pCount;
      return this.a + r;
    },
    // X+=Y
    'operator+=': proc(r){
      //print('operator+=',argv);
      assert 1 === argv.length();
      ++pCount;
      this.a += r;
      return this;
    },
    // X-Y
    'operator-': proc(r){
      //print('operator-',argv);
      assert 1 === argv.length();
      ++mCount;
      return this.a - r;
    },
    // X-=Y
    'operator-=': proc(r){
      //print('operator+=',argv);
      assert 1 === argv.length();
      ++mCount;
      this.a -= r;
      return this;
    },
    // X++
    'operator++': proc(){
      //assert this===argv.0 /* historical! */;
      assert !argv.#;
      ++pCount;
      this.a++;
      return this;
    },
    // ++X
    '++operator': proc(){
      assert !argv.0;
      ++pCount;
      ++this.a;
      return this;
    },
    // X--
    'operator--': proc(){
      //assert this===argv.0 /* historical! */;
      assert !argv.#;
      ++mCount;
      this.a--;
      return this;
    },
    // --X
    '--operator': proc(){
      assert !argv.0;
      ++mCount;
      --this.a;
      return this;
    },
    // -X
    '-operator': proc(){
      //print('-operator', argv);
      return -this.a;
    },
    // X==Y
    'operator==': proc(r){
      // very minimal impl which does not detect
      // an RHS of the same class as this.
      return ((typeinfo(name this)) === (typeinfo(name r)))
             ? this.a === r.a
             : this.a == r;
    },
    // X!=Y
    'operator!=': proc(r){
      return !(this==r);
    },
    // X<Y
    'operator<': proc(r){
      return ((typeinfo(name this)) === (typeinfo(name r)))
             ? this.a < r.a
             : this.a < r;
    },
    // X>Y
    'operator>': proc(r){
      return ((typeinfo(name this)) === (typeinfo(name r)))
             ? this.a > r.a
             : this.a > r;
    }
  };
  //print("Properties:", o.propertyKeys());
  assert(!o.prototype.'operator+');
  assert ++o === o;
  assert 1 === o.a;
  assert 1 === pCount;
  assert o++ === o;
  assert 2 === o.a;
  assert 2 === pCount;
  assert --o === o;
  assert 1 === o.a;
  assert 1 === mCount;
  assert o-- === o;
  assert 0 === o.a;
  assert 2 === mCount;
  //assert 2 === (o += 2);

  //print('o =',o);
  assert o === (o += 2);
  //print('o =',o);
  assert 4 === o + 2;
  assert 2 === o.a;
  assert 1 === o - 1;
  //assert 'CWAL_RC_NOT_FOUND' === catch{-o/*no such operator*/}.codeString();
  assert 'CWAL_RC_NOT_FOUND' === catch{+o/*no such operator*/}.codeString();
  //print("Properties:", o.propertyKeys());  
  assert 2 === o.a;
  o -= 1;
  assert 1 === o.a;
  assert -1 === -o;
  assert 1 === o.a;


  assert !(o < 1);
  assert o == 1;
  assert o != 2;
  assert o < 2;
  assert o > 0;

  assert 2 === ++o.a;
  assert 2 === o.a++;
  assert 3 === o.a;
  assert o == 3;
  assert !(3 == 0) /* b/c only LHS is checked for overloads */;

  assert catch{o * 1 /* no such op */};
  assert catch{o / 1 /* no such op */};
  assert catch{o % 1 /* no such op */};
  assert catch{o << 1 /* no such op */};
  assert catch{o >> 1 /* no such op */};

}

scope {
  const sproto = "".prototype;
  assert catch{"" * 3 /* no such op (yet) */};
  sproto.'operator*' = proc f(rhs){
    (rhs>0) || throw "Expecting positive RHS for STRING*N op.";
    f.buf || (f.buf = buffer(100));
    f.buf.reset();
    for(var i = 0; i < rhs; ++i ){
      f.buf.append(this);
    }
    return f.buf.toString();
  } using {buffer: s2.Buffer.new};
  assert '***' === "*" * 3;
  assert catch{'*' * -1 /* negative index */};
  assert "3.00.1" === "3.0"+0.1 /* checking for a misplaced bug */;
  unset sproto.'operator*';
}

var three = 3;
scope {
  var ar = [];
  ar.'operator+=' = proc(r){
    this.push(r);
    return this;
  };
  ar.'operator<<' = proc(r){
      return this.push(r);
  };
  ar += 1;
  ar << 2;
  assert '12' === ar.join('');

  var out;
  ar.'operator>>' = proc(r){
    //var code = r+" = this.pop()";
    //print('code =',code);
    //return eval -> code;
    return eval -> r+"=this.pop()";
  };
  1 ? (ar >> nameof out) // yet another (obscure) use for nameof
    : ar.pop();
  assert 2 === out;
  assert 1 === ar.length();
  assert 1 === ar.0;
  //print(out,ar);
  //print(__FLC, refcount three);
  ar[3] = three;
  //print(__FLC, refcount three);
  ;1;1;1;
  //print(__FLC, refcount three);
  ar.clear();
  //print(__FLC, refcount three);

  assert catch{ar & 1/* no such operator (yet) */};
  ar.'operator&' = proc(r){
    return this;
  };
  assert ar === ar & 1;


  var o = {a: [1,2]};
  o.a.'operator+=' = ar.'operator+=';
  o.a += 3;
  assert '123' === o.a.join('');

}
//print(__FLC, refcount three);
assert 3 === three;

scope {
  // Overloading math ops on functions...
  var x, setX = proc(){return x = argv.0};
  setX.'operator+' = proc(arg){return this(arg)};
  setX+1;
  assert 1 === x;
  assert 0 === setX + 0;
  assert 1 === setX + -1 + 1 * 2;
}

scope {
  // Overload-only -> op...
  var x, setX = proc(){return x = argv.0};
  setX.'operator->' = proc(arg){return this(arg)};
  setX->1;
  assert 1 === x;
  assert 0 === setX -> 0;
  assert -1 === setX ->( -1 * 1 ) /* -> has . precedence */;

  unset setX;

  // Overload-only =~ ("contains") and !~ ("does not contain") ops...
  var o = {
    a: [1,2,3],
    'operator=~':proc(arg){
      return this.a.indexOf(arg)>=0;
    }
  };
  assert o =~ 1;
  assert o =~ 3;
  assert !(o =~ -1);

  assert catch{o !~ 1 /* no such operator yet */};
  o.'operator!~' =proc(arg){
    return this.a.indexOf(arg)<0;
  };

  assert o !~ -1;
  assert !(o !~ 1);
  assert o =~ 1 && o !~ -1; // has comparison precedence
}

scope {
  // C++-style streams...
  var b = new s2.Buffer(20);
  // Remember that buffers are not containers:
  //b.prototype.'operator<<' = proc(self,arg){ // built-in overload
  //  return this.append(arg);
  //};
  b << "a" << "bc" << "def";
  assert "abcdef" === b.toString();
  b.reset();
  b << 1 << 2+3; 
  assert "15" === b.toString();
  //unset b.prototype.'operator<<';

  // Alternate implementation:
  var o = {
    buf: b.reset(),
    'operator<<': proc(arg){
       this.buf.append(arg);
       return this;
    }
  };
  o << "a" << "bc" << "def";
  assert "abcdef" === b.toString();
  b.reset();
  o << 1 << 2+3; 
  assert "15" === b.toString();
  //unset b.prototype.'operator<<';
}

scope {
    var x = 0;
    var obj = {
        'operator<<': proc(arg){
            x += arg;
            return this;
        }
    };
    proc(a,b,c){
        obj << a << b << c;
        assert 6 === x /* testing an argv propagation fix */;
        proc(d,e,f){
            obj << d << e << f /* testing an argv propagation fix */;
            assert 21 === x;
        }(4,5,6);
    }(1,2,3);
}

scope {
    var h = new s2.Hash(13);
    h.insert(1, "hi");
    assert 'hi' === h # 1;
    assert undefined === h # 0;
    var x = 0;
    assert 'hi' === h # (++x);

    assert 0 === catch{h#1 = 'error'}.message.indexOf('Invalid LHS')
    /* # is not valid in an assignment op */;

    var h2 = new s2.Hash(13);
    h2.insert(1,3);
    h2.insert('a', 0);
    h.insert(2, h2);
    assert 3 === h # 2 # 1;
    assert h # 2 # 1 === 3;
    assert h # 2 # 0 === undefined;
    assert h # 2 # 'a' === 0;

    h2.insert('f', proc f(){assert f===this; return 1});
    // # does not set 'this' like the dot op does
    assert 1 === (h # 2 # 'f')();
    // If it did, (h2#f)() would have h2 has 'this', which is
    // borderline disturbing. It also means this call syntax
    // won't work:
    // assert 1 === h # 2 # 'f'();

    var obj = { h: h, x:0 };
    assert catch{obj#1}.message.indexOf('Hash')>0
        /* LHS of # must be-a Hash. Overloading of # was removed,
           as it (A) will complicate extending # to support assignment
           (insert()) and (B) it essentially negates the reason
           for having the # operator: an access method for hashes which
           makes them competative with Objects (using Hash.search()
           has function call overhead which obj[prop] does not, making
           objects faster for most cases).
        */;
    assert 'hi' === obj.h # 1;
    assert undefined === obj.h # 0;
    assert obj.h # 2 # 'a' === 0;
    assert 3 === obj.h # 2 # (0+1);
    assert 0 === catch{obj.h#1 = 'error'}.message.indexOf('Invalid LHS')
    /* # is not valid in an assignment op */;;
}

scope {
    var o = {
        x: 3,
        'operator::': proc(key){
            return this.hasOwnProperty(key);
        }
    };
    assert o::x;
    assert !o::y;
}

0 && scope {
// experimental, doesn't yet work how i would like
// Certain parts of this require enabling/disabling
// specific ifdefs in s2_eval.c and/or s2_ops.c

  var o = {
    __typename: 'Bob',
    'operator->': proc(arg){
      return this.sub[arg];
    },
    sub:{
      __typename: 'Sub',
      x: -1,
      f: proc(){
        print(__FLC,'this =',this);
        assert 'Bob' === typename this;
        return 1;
      }
    }
  };
  print(o->'f');
  assert 'function' === typename o->'f';
  assert 1 === o->f(); // works, yet...
  assert 1 === o->'f'(); // works
  o->x = 1; // assigns in o (or errors, depending on how we assign engine->dotOpLhs)
  o->x++; // as well.
  // i understand why, but a fix seems rather intrusive. i'd rather have it not behave
  // like the dot, i think.
  print(__FLC,'o =',o);
};

Deleted bindings/s2/unit/200-000-ifelse.s2.

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









































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


/* note that the semicolons here are needed for the assert, not the if/else blocks */
assert if(1){};
assert !if(0){throw __FLC};
assert !if(0){throw __FLC}else{};
assert if(0){throw __FLC} else if(1){} else{throw __FLC};

assert if(0){throw __FLC}
       else if(true)<<<_FI /*trued the second time*/;; _FI
       else {throw __FCL};
assert true /* Just checking tail consumption */;
assert !if(0){throw __FLC}
       else if(1-2+1){throw __FLC}
       else {};

var a;
if(0){throw __FLC}
else if(0){throw __FLC}
else{a=4*2-1} /* note no semicolon needed here */
assert 7 === a;
assert 7 === a /* make sure previous one wasn't skipped */;

a = 0;
//pragma(trace-token-stack 1);
if(0) throw __FLC; else if(1) a = 1; else throw __FLC;
//pragma(trace-token-stack 0);
assert 1 === a;

a = -1;
if(1) a = 0; else if(1) {throw __FLC} else {throw __FLC}
assert 0 === a;

a = 0;
if(0) throw __FLC; else if(0) {throw __FLC} else a = 1 ? 2 : 3;
assert 2 === a;

a = 0;
if(0) throw __FLC; else if(0) {throw __FLC} else {a = 0 ? 2 : 3}
assert 3 === a;

assert var ex = catch{
    if(0) 1; else if(0) else a = 1 ? 2 : 3;
    // --------------^^^^
};
assert 'CWAL_SCR_SYNTAX' === ex.codeString();
assert 0 < ex.message.indexOf('consecutive non-operators');

a = 0;
assert true === if(1) 1; else throw __FLC;
assert false === if(0) throw __FLC; else if(0) throw __FLC;
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert false === if(0) throw __FLC; else if(0) {0};
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert true === if(0) throw __FLC; else if(1) {0};
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert true === if(0) 1; else if(1) 0;
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert true === if(1) 1; else if(1) {throw __FLC};
a = 1;
assert 1 === a /* make sure final if() DTRT */;

a = 0;
assert true === if(1) 1; else if(1) throw __FLC;
a = 1;
assert 1 === a /* make sure final if() DTRT */;

// checking newline-as-EOX handling...
if(1){}
var b;
assert typeinfo(islocal b);

if(0){} else{}
var b2;
assert typeinfo(islocal b2);

if(0){} else if(0){}
var b3;
assert typeinfo(islocal b3);

if(0){}
var c;
assert typeinfo(islocal c);
/*
  When using heredocs as if/else bodies, a semicolon/newline
  between the heredoc's closing token and the "else" part is
  optional. Typing it seems to feel more intuitive, but internally
  heredocs are normally treated like script blocks, and a semicolon
  after a script block has a different meaning here:

  if(1){...}; else{...} // syntax error

  NEVERMIND: i don't like this semicolon inconsistency. But now it's
  doucmented :/
*/
var x;
if(1)<<<X x=1 X
else<<<Y Y // semicolon and EOL are equivalent at the END {} of LHS if/else
assert 1===x;

x = 0;
if(0)<<<X X
else<<<Y x=1 Y; // semicolon and EOL are equivalent at the END of the construct
assert 1===x;

if(0){// see comments above
    x = 0;
    if(1)<<<X x=1 X; // semicolon is a syntax error here for consistency with {blocks}.
    else<<<Y Y
    assert 1===x;
}

assert !catch{0 ? if(1) 1; else{2} : 1} /*no syntax error*/;
assert !catch{0 ? if(0) 1; else{2} : 1} /*no syntax error*/;
assert !catch{1 ? if(1) 1; else{2} : 1} /*no syntax error*/;
assert !catch{1 ? if(0) 1; else{2} : 1} /*no syntax error*/;
assert catch{1 ? if(0) 1; else 2 : 1}
/*
  Not exactly a bug: "unexpected ':'". The problem is one of scope:
  the if/else parts run in a new scope and it's there that the else
  encounters ':', but the ternary op state does not span scopes (we
  explicitely save/reset/restore it when pushing/popping
  scopes). Terminating the 'else' body with a semicolon OR using a {}
  body instead of non-{} body resolves it.
*/;

Deleted bindings/s2/unit/200-100-while.s2.

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

































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

var i = 0, max = 1000;
while(i < max){i = i + 1}
assert max===i;

assert 'hi' === while( true ){ break "hi" };

assert undefined === while(false){throw __FLC};

assert 1 === while(true){ scope {catch {break 1} };; };

assert 'CWAL_SCR_SYNTAX' === catch{
       while(/*empty expr is not OK*/){/*empty body is OK*/}
}.codeString();

i = 0;
while(!i){
  var x =0;
  i = while(true){
    if((x=x+1)>3){break x}
    //(x=x+1)>3 && break x
  }; /* semicolon needed b/c of assignment expr */
  true // at EOF, semicolon is optional
}
assert 4===i;

i = 0;
while(!i){
  var x =0;
  i = while(true){
    if((x=x+1)>3){break x}
    //(x=x+1)>3 && break x
  } /* semicolon _normally_ needed b/c of assignment expr,
       but special-casing of keywords ending in blocks
       implicitly treat this EOL as an EOX */
}
assert 4 === i;

i = 0;
assert 4 === while(true){
  ((i = i + 1)<4) && continue;
  break i;
};

i = while(false){};
assert undefined === i;

i = while(true){break};
assert undefined === i;

i = 0;
while(i<3)++i;
assert 3 === i;
assert 3 === i /* make sure the previous line wasn't snarfed by the loop */;

i = 0;
while(i++<3);
assert 4 === i;
assert 4 === i /* make sure the previous line wasn't snarfed by the loop */;

i = 0;
assert !catch{0 ? while(i<2) ++i : 1} /* no syntax error */;
assert !i;
assert !catch{1 ? while(i<2) ++i : 1} /* no syntax error */;
assert 2 === i;

Deleted bindings/s2/unit/200-200-for.s2.

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
















































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

for(var i = 1, x = -1;
        i >= -1;
        i -= 1, x+=1){
  //print('for(): i =',i,'x =',x);
}
assert 'undefined' === typename i;

var i = 0, x = for(;;){
  (i = i + 1)>3 && break i
};
assert 4 === i;
assert x === i;

x = for(i = 0;;){
  scope {
    catch{ (i = i + 1)>4 && break i }
  }
};
assert 5 === i;
assert x === i;

assert -1 === (1-2 ||| for(;;){/* if this infinite loops then skip mode is broken*/});
assert -1 === eval 1-2 ||| for(;;){/* if this infinite loops then skip mode is broken*/};
assert false === (0 && for(;;){/* if this infinite loops then skip mode is broken*/});
assert false === eval 0 && for(;;){/* if this infinite loops then skip mode is broken*/};


for(i = 0, x = 0;
    i < 3;
   ) ++i, x += 2;
assert 3 === i;
assert 3 === i /* make sure the previous line wasn't snarfed by the loop */;
assert 6 === x;

for(i = 0, x = 0; i < 3; ++i
) x += 2;
assert 3 === i;
assert 3 === i /* make sure the previous line wasn't snarfed by the loop */;
assert 6 === x;

for(i = 0, x = 0; i < 3; ++i    );
assert 3 === i;
assert 3 === i /* make sure the previous line wasn't snarfed by the loop */;
assert 0 === x;

assert !catch{0 ? for(x = 0; x<1; ++x) 1 : 1} /* no syntax error */;
assert !catch{1 ? for(x = 0; x<1; ++x) 1 : 1} /* no syntax error */;

Deleted bindings/s2/unit/200-300-dowhile.s2.

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










































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

var i;
i = 0;
do{++i}while(i<5);
assert 5 === i;
i = 0;
do ++i; while(i<5);
assert 5 === i;

assert 'CWAL_SCR_SYNTAX' === catch{
    do ++i while(true) /* missing semicolon after ++i */
}.codeString();

i = 0;
do{
    ++i
}
while(!i);
assert 1 === i;

assert -1 === do{
    break -1;
}while(true);

assert undefined === do ; while(false)
  /* if a for/while loop body may be empty, why not a do loop, too? */
;


i = 0;
do ++i>2 && break; while(true);
assert 3 === i;

i = 0;
do i++>2 && break; while(true);
assert 4 === i /* yes, four */;

i = 0;
assert !catch{0 ? do ++i; while(i<2) : 1} /* no syntax error */;
assert !i;
assert !catch{1 ? do ++i; while(i<2) : 1} /* no syntax error */;
assert 2 === i;

Deleted bindings/s2/unit/300-000-functions.s2.

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













































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
var x = function X(a=1,b=0){
  //print("argv =",argv);
  assert 'array' === typename argv;
  assert 'function' === typename X;
  //print('"this" type =',typename this);
  return a + b;
};
assert 'function' === typename x;
assert 1 === x(1);
assert 3 === x(1,2);
//return;
assert 3 === x(1,2,3);

var o = { f: x };
assert -1 === o.f(0,-1,1);
unset o;

var y = 1 || proc(){skipped}(args,skipped,too);

// just to watch the dtor in debug output...
// print('unsetting func'); unset x; print('after unset');

unset x, y;
// Fixed :-D
//print('script location info from inside funcs will be wrong for a while:',
//      catch proc(){throw    __FLC}()
//);
assert __LINE+1 === catch {
  proc(){throw 0}()
}.line;

var recursive = proc r(a=1){
  return a>3 ? a : r(a+1);
};

assert 4 === recursive();

assert 4 === proc rec(a=1){
  return a>3 ? a : rec(a+1);
}();
assert 'undefined' === typename rec;

var x = function myfunc(a=myfunc.defaults.0, b=myfunc.defaults.1){
  return a + b;
};
x.defaults = [1,-1];
assert 0 === x();
assert 1 === x(2);

var o = {
  name: 'fred',
  f:function(){return this.name}
};
assert 'fred' === o.f();

scope {
    var x = 3, y = 4;
    var f = function(c){
        return a + b + c;
    }.importSymbols({a: x, b: y});
    assert 8 === f(1);
    f.a = 1;
    assert 8 === f(1) /* that's a different 'a' */;
    var counter = 0;
    f.eachProperty(proc(){
        counter = counter + 1;
        //print(argv);
    });
    assert 1 === counter
    /* making sure that the importSymbols sym is hidden (does not get
       iterated over). */;
    f.clearProperties();
    assert 9 === f(2) /* f.clearProperties() does not nuke imported
                         symbols, as of 20160206. */;
    f.importSymbols(false,{b: 3})
    /* initial bool arg specifies whether or not to clear
       existing imported properties before the import.
       Added 20171227.
    */;
    assert 10 === f(4);
}

scope {
  var o = {a:1};
  var f = function(a){
    return this.a + a;
  };
  assert 2 === f.apply(o,[1]);
  assert 2 === f.call(o,1);

}

scope {
  // Make sure that skip mode is honored
  // for default parameters which are passed
  // in by the caller...
  var f = proc(a, c=(throw 'default param threw'), b=1){
    assert 1===b;
    assert 0===c;
    assert -1===a;
  };
  f(-1,0);
  assert 0 === catch{f(1) /* param c=throw... gets processed */}
         .message.indexOf('default param');

  // make sure sourceCode() is working.
    assert f.sourceCode().indexOf("default param threw")>8;
    assert proc(){/*comment*/}.sourceCode() === "proc(){}";
    assert proc(/*comment*/){/*comment*/}.sourceCode() === "proc(){}";
}

scope {
    /* The "using" pseudo-keyword... */
    var x = 30, f = proc() using(x) {return x};
    unset x;
    assert 30 === f();
    var f2 = proc() using({y:20, F:f}) {return F() * y};
    assert 600 === f2();
    f.importSymbols({x: 40});
    unset f;
    assert 800 === f2();
    unset f2;

    /* Alternate placement at the end of the function, primarily
       because it makes migration from importSymbols() a simple
       search/replace op: */
    var x = 30, y = 20, f = proc() {return x*y} using(x, y);
    unset x, y;
    assert 600 === f();
    var f2 = proc() {return F() * y} using({y:10, F:f});
    assert 6000 === f2();
    f.importSymbols({x: 40, y: 1});
    unset f;
    assert 400 === f2();
    unset f2;

    /* Another alternate syntax which takes a single object literal.
       It does not accept an object expression or a hash literal.
    */
    assert 60 === proc() using {A: 30} { return A * argv.0 }(2);
    assert -60 === proc() { return B * argv.0 } using {B: -30}(2);

    assert 'CWAL_RC_TYPE' === catch {
        proc() using {#a:1} {}
    }.codeString() /* hash literal is not allowed */;

    assert catch {
        proc() using (true) {}
    }.message.indexOf("eyword") > 0 /* keyword as identifier not allowed */;

    assert catch {
        proc() using () {}
    }.message.indexOf("mpty") > 0 /* empty expression not allowed */;

    /* Contrast empty () with empty {object}, which is allowed, though
       not of much use... */
    assert 0 === proc() using {} {return 0}();

    assert catch {
        var x;
        proc() using (x ?) {}
    }.message.indexOf("nexpected") > 0 /* illegal token */;

    assert catch {
        var x;
        proc() using (x, 1) {}
    }.message.indexOf("'integer'") > 0 /* illegal expr. type */;

    assert catch {
        proc() using x{}
    }.message.indexOf("Expecting (...)") === 0 /*using requires () or {}*/;

    assert catch {
        proc() {} using x
    }.message.indexOf("Expecting (...)") === 0 /*using requires () or {}*/;


    // Demonstrate how using() deals with identifiers vs non-identifier/non-literal
    // expressions:
    var a = 0;;
    assert catch {
        proc() using(a.prototype, b) {}
    }.message.indexOf("Unexpected token '.'") === 0
    /* fails b/c a leading identifier triggers import of that identifier
       and does not eval a sub-expression. */;
    assert proc(){} using((a.prototype)) /* works b/c it doesn't start with an identifier */;
    assert proc(){} using(0.prototype) /* works b/c it doesn't start with an identifier */;
    unset a;
    var obj = {a:1, b:2};
    var f1 = proc(){return obj.a+obj.b} using(obj);
    var f2 = proc(){return a+b} using((obj));
    unset obj;
    assert 3 === f1() /* because using(obj) resolves obj as an identifier. */;
    assert 3 === f2() /* because using((obj)) triggers a sub-expression parse and
                         imports the resulting properties. */;
}

scope {
    // testing a parsing fix for prefix ++/-- ...
    var f = proc(){return o} using{o:{x:1}};
    assert 1 === f().x;
    assert 1 === f().x++;
    assert 3 === ++f().x;
    assert 2 === --(f()).x;
    //assert 3 === ++(f().x) /* doesn't work b/c (x) is resolved in a subexpression */;
}

var u = scope {
    /* 20191209: using() keyword */
    assert undefined === proc(){return using()}();
    var f = proc(){return using()} using{a: 1};
    var u = f();
    assert typeinfo(isobject u);
    assert !u.prototype;
    assert 1 === u.a;
    u.b = 2;
    assert 2 === f().b;
    assert undefined ===  using(proc(){});
    assert 'CWAL_SCR_SYNTAX' === catch {using()}.codeString();
    assert 'CWAL_RC_TYPE' === catch using(f.apply).codeString();
    f = proc(){return using()} using{}
    /* as fate would have it, using{} is legal, and creates an empty
       imports object, but using() is not. The latter was an
       intentional design decision but the former just a happy
       accident. */;
    u = f();
    assert typeinfo(isobject u);
    u.b = 1;
    assert 1 === f().b;

    /* imports must survive propagation out of a temporary value... */
    u = using(proc()using{a:999}{});
    ;;;;;;;; /* pedanticness: make sure sweepup triggers (though the temp function is already gone by now) */
    assert typeinfo(isobject u);
    assert 999 === u.a;

    /**
       20191211: using (without parens) is legal in function bodies as a shorthand
       for using() (it's also more efficient).
    */
    assert catch {
        using;
    }.message.indexOf("Expecting (") === 0;
    f = proc F()using{a:1}{
        assert typeinfo(islocal a);
        assert using(F) === using();
        proc(){assert undefined===using /* not inherited from outer function */}();
        return scope {using.a /*from containing function*/}
    };
    assert 1 === f();

    /**
       20191211: adding a dot after the "using" clause in a function
       definition specifies that the imported symbols (whether via
       this "using" decl or a future importSymbols() call) are NOT to
       be declared as local variables at call()-time. Instead, they
       may be accessed via the call-local "using" keyword.
    */
    f = proc F() using.{XXX: 1} {
        assert !typeinfo(islocal XXX);
        assert typeinfo(islocal F);
        assert 1 === using.XXX;
        assert 1 === using()['XXX'];
        assert using.XXX === using(F).XXX;
        return using['XXX'];
    };
    assert 1 === f();

    assert 1 === proc(){
        assert !typeinfo(islocal a);
        return using.a;
    } using.{a:1}();
    /* 20191218 bugfix test:
       1) Order of {body}using.{...} broke parser.
       2) S2_FUNCSTATE_F_NO_USING_DECL flag was not being set
       on the function when that order applied. 
    */
    ;

    /* Confirm lifetime of imported symbols for functions defined
       inside using(<here>)... */
    assert "!wakawaka!" === using(proc(){}using{a:"!wakawaka!"}).a;
    assert "/foo/bar/baz" === scope using(proc()using{a:"/foo/bar/baz"}{}).a;
    var uo = using(proc()using{a:"/foo/bar/baz", b: 1000}{});
    assert "/foo/bar/baz" === uo.a;
    assert 1000 === uo.b;
    unset uo;
    assert using(proc(){}using{a:"!wakawaka!"}).a
        === proc(){return using()}using{b:"!wakawaka!"}().b;
    
    /* imports can be added after the fact with importSymbols()... */
    f = proc(){return using()};
    assert undefined === f();
    f.importSymbols({a: 1000});
    assert 1000 === f().a;
    /* imports must survive propagation out of this scope... */
    f() /* scope result */;
};
assert typeinfo(isobject u);
assert 1000 === u.a;

Deleted bindings/s2/unit/400-000-new.s2.

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






























































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
proc(){
    assert !typeinfo(isnewing);
}();
assert !typeinfo(cannew 1);
assert !typeinfo(cannew {});
assert typeinfo(cannew s2.Hash);
var v = new s2.Hash(13);
assert v inherits s2.Hash;
assert 13 === v.hashSize();
//s2.dumpVal(v);

assert typeinfo(cannew s2.Buffer);
v = new s2.Buffer(10);
assert v inherits s2.Buffer;
assert v.capacity() >= 10;
//s2.dumpVal(v);

assert typeinfo(cannew s2.PathFinder);
v = new s2['PathFinder']() {
    this.x = 1;
};
assert v inherits s2.PathFinder;
assert undefined === v.search('PathFinder');
assert 'string' === typeinfo(name v.search(__FILE));
assert 1 === v.x;
//s2.dumpVal(v);

const Array = {
    prototype: [].prototype,
    blah: 2,
    __new: proc(){
        this.x = 1;
        assert typeinfo(isnewing);
        assert typeinfo(is-newing this);
        assert argv && !typeinfo(isnewing argv);
        if(1) assert typeinfo(isnewing) /* works as of 20171206 */;
        assert typeinfo(isnewing) /* But now we're back in our 'new' scope. */;
        proc(){assert !typeinfo(is-newing)/* different "this" */}();
        var rc = argv.slice();
        rc.prototype = this;
        return rc;
    }
};
assert typeinfo(cannew Array);
v = new Array(-1,0,1);
assert v inherits Array;
assert 'array' === typeinfo(name v);
assert [].prototype === Array.prototype;
assert -1 === v.0;
assert 1 === v.2;
assert 2 === v.blah;
assert v.blah === (v[] = v.blah); // reminder to self: not a precedence bug - JS also requires extra parens here.
assert 3 === ++v.blah;
assert 2 === v.prototype.blah;
assert 2 === v.prototype.prototype.blah;
assert 3 === v.blah;
assert 4 === v.length();
v[4] = -1;
assert 5 === v.length();
assert -1 === v.4;
//s2.dumpVal(v);
//v.eachIndex(proc(v){print(typename v, v)});

const Object = {
    prototype: {}.prototype,
    __new: proc(){
        assert typeinfo(isnewing);
        return;// {prototype: this}; // same effect but requires +1 temp Object allocation
    }
};
assert typeinfo(cannew Object);
v = new Object();
assert v inherits Object;
assert 'function' === typename v.eachProperty;
assert v.isEmpty();

const String = {
    prototype: "".prototype,
    __new: proc(){
        // hmmm. Can't do POD types as PODs this way
        // without other s2-level infrastructure.
        this.value = argv.join('');
        assert typeinfo(isnewing);
    }
};
assert typeinfo(cannew String);
v = new String("a","b","c");
assert v inherits String;
assert v !== String;
assert 'abc' === v.value;
assert undefined === v.prototype.value;

// An alternate approach...
const String2 = {
    __new: proc(){
        assert typeinfo(isnewing);
        return argv.join('');
    }
};
assert typeinfo(cannew String2);
v = new String2('a','b','c');
assert v === 'abc';

const Boolean = {
    __new: proc(v=(affirm 0 && "Boolean ctor requires an argument")){
        assert typeinfo(isnewing);
        return !!v;
    }
};
assert typeinfo(cannew Boolean);
assert true === new Boolean(true);
assert false === new Boolean(false);
assert catch {new Boolean()}.message.indexOf('ctor') > 0;
assert catch new Boolean().message.indexOf('ctor') > 0; // also works

const MyClass = {
    prototype: s2.Hash,
    __new: proc(){
        assert typeinfo(isnewing) /* in 'new' scope */;
        if(1){
            assert typeinfo(isnewing);
            const x = this;
            proc(){
                assert !typeinfo(isnewing)/*not the same 'this' as the ctor call*/;
                assert typeinfo(isnewing x)/*optionally accepts an expression*/;
            }();
        }
        var rc = new s2.Hash();
        rc.prototype = this;
        foreach(@argv=>i,v) rc.insert('k'+i, v);
        proc(){
            assert !typeinfo(isnewing) /* not in 'new' scope */;
            new String() /* typeinfo(isnewing) check must pass */;
            assert !typeinfo(isnewing) /* not in 'new' scope */;
        }();
        new Boolean(1) {
            assert !typeinfo(isnewing) /* not in the post-ctor block */;
        };
        assert typeinfo(isnewing) /* back in 'new' scope */;
        return rc;
    }
};
assert typeinfo(cannew MyClass);
v = new MyClass(1,2,3);
assert v inherits MyClass;
assert MyClass inherits s2.Hash;
assert v inherits s2.Hash;
// v's prototype is a hash, but v is not, so new properties
// set via its dot/[] ops don't land in the hash:
//v.eachEntry(proc(k,v){print(__FLC,' ',k,'=',v)});
assert 2 === v#'k1';
assert 'function' === typeinfo(name v.__new);
assert 3 === v.search('k2');
assert 3 === v # 'k2';


/* Exception subclasses are a bit trickier... */
const E = {
    prototype: exception(0).clearProperties(),
    __typename: 'Exceptional',
    __new: proc(codeOrMsg,
                msgOrCode){
        assert typeinfo(isnewing);
        (argv.length()>1
         ? exception(codeOrMsg,msgOrCode)
         : exception(0,codeOrMsg)
        ).copyPropertiesTo(this);
        //e.copyPropertiesTo(this)
        /* collect stack trace and friends */;
        this.hi = 1;
    }
};
assert !E.stackStrace;
assert !E.script;
var e = catch throw new E('yo');
assert e;
assert e.prototype === E;
assert e inherits E;
assert !E.stackStrace /* testing a bufix in s2_throw_value() */;
assert typeinfo(hasexception e);
assert !typeinfo(isexception e);


/* Make sure short-circuiting skips all it's supposed to... */
assert !typeinfo(isdeclared Nope);
assert -1 === 0 ? new Nope() : -1;
assert -1 === 0 ? new Nope() { nope nope nope } : -1;
assert -1 === (1 ? new MyClass() { this.x = -1 } : new Nope() { nope nope nope }).x;

// reminder to self: braces are needed around these catch blocks because
// otherwise catch does not convert syntax errors to exceptions.
assert 'CWAL_SCR_SYNTAX' === catch { new MyClass() {return 3} }.codeString();
assert catch { new MyClass() { continue } }.message.indexOf('Unhandled') >= 0;
assert catch { new MyClass() { break } }.message.indexOf('Unhandled') >= 0;

assert 3 === new MyClass(), 3 /* new must stop after the call() part... */;
assert 3 === new MyClass(){}, 3 /* ... or the post-init part */;

if(0){
    // Functions as ctors is currently disabled
    /**
       This is more JavaScripty but also leads to each instance being
       able to be call()'d, and that call landing in the constructor!
       Unlike in JS, this ctor _is_ the prototype for new instances,
       so add shared methods directly as properties of this ctor.
       
       i don't have a workaround for the call()able problem which also
       leaves the resulting objects as (v inherits Func). :/
    */
    const Func = proc(a,b){
        this.a = a;
        this.b = b;
    };
    assert typeinfo(cannew Func);
    var f = new Func(1,2);
    assert f inherits Func;
    assert f.a === 1;
    assert f.b === 2;
    assert 'object' === typeinfo(name f);
    assert !typeinfo(isfunction f);
    assert typeinfo(iscallable f) /* unfortunate */;
}

Deleted bindings/s2/unit/500-000-array.s2.

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






















































































































































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
var ar = [1,2,3];

scope {
  assert 3 === ar.#;
  assert 3 === ar.#;
  assert ar.# === 3;
  assert '3' === ar.#.toString();
  assert 2 === ar.indexOf(3);
  assert ar === ar.length(2) /* setter returns the array */;
  assert 2 === ar.#;
  assert 2 === ar[1];
  assert undefined === ar[2];

  ar.prop = 1;
  ar.clear();
  assert 1===ar.prop;
  assert !ar.#;
  ar.clear(true);
  assert undefined===ar.prop;
}

scope {
  assert ar.isEmpty();
  assert 3 === ar.push(1,2,3);
  assert !ar.isEmpty();
  assert 3 === ar.#;
  var x = 0;
  var eachCallback = proc(v,i){
    //print('in func x=',x,i);
    x = i;
      //print(x, argv, v, i);
  } ;
  ar.eachIndex( eachCallback );
  assert 2 === x;
  eachCallback(0,1);
  assert 1 === x;
}

scope {
  assert 3 === ar.#;
  assert 3 === ar.2;
  assert 3 === ar.pop();
  assert 2 === ar.#;
  assert 1 === ar.shift();
  assert 1 === ar.#;
  assert 2 === ar.0;
  assert 0 === ar.indexOf(2);
  ar.reverse();
  assert 2 === ar.0;
}

scope {
    // Array.indexOf() type-strict comparison flag...
    var a = [0,'1',2];
    assert a.indexOf(1,false) === 1 /* not type-strict */;
    assert a.indexOf(1,true) < 0 /* type-strict */;
    assert a.indexOf('1',false) === 1 /* not type-strict */;
    assert a.indexOf('1',true) === 1 /* type-strict */;
    assert a.indexOf(1) < 0 /* default == type-strict */;
}

scope {
  assert 1 === ar.#;
  assert 2 === ar.0;
  ar.unshift(1,-1);
  assert 3 === ar.#;
  assert 2 === ar.2;
  assert -1 === ar.1;
  assert 1 === ar[0];
  assert '1,-1,2' === ar.join(',');
  assert 3 === ar.setIndex(1,3);
  assert '1,2,3' === ar.sort().join(',');
  assert '3,2,1' === ar.sort(function(l,r){
    // reverse-order (l,h)...
    return l==r ? 0 : l < r ? 1 : -1;
    cannot happen;
    // The long way:
    l==r && return 0;
    l < r && return 1;
    return -1;
  }).join(',');
  assert '123' === ar.reverse().join('');
  assert ar === ar.unshift(-1);
  assert '-1123' === ar.join('');
}

scope {
  const a1 = [1,2,3];
  assert catch{a1.slice(0,-1)}.codeString() === 'CWAL_RC_RANGE';
  assert catch{a1.slice(-1,0)}.codeString() === 'CWAL_RC_RANGE';
  // slice() during sort is madness...
  assert catch a1.sort(proc(){a1.slice()}).codeString() === 'CWAL_RC_LOCKED';
    
  var a2 = a1.slice(0,1);
  assert 'array' === typename a2;
  assert 1 === a2.#;
  assert 1 === a2.0;

  const a3 = a2.slice(3);
  assert 0 === a3.#;
}

scope {
  const a1 = [1,2,3];
  var a2 = a1.slice(0, 1);
  //print('a2=',a2);
  assert 1=== a2.#;

  a2 = a1.slice(1,1);
  assert 1=== a2.#;
  assert 2 === a2.0;

  a2 = a1.slice(1, 6);
  assert 2=== a2.#;
  assert 3 === a2.1;
}

scope {
    const a = [-2,-1,0,1];
    assert -2 === a.shift();
    assert -1 === a.0;
    assert 0 === a.shift(2);
    assert 1 === a.#;
    assert 1 === a.0;
}

scope {
    const ar = [0,proc f(){assert f===this; return this},1];
    assert ar.1 === ar.1() /* 'this' is not the array */;
}

scope { // using arrays as prototypes...
    var x = {0: 0, // interesting: whether or not this property
             // gets set as an object prop or array index depends
             // on whether it gets set before or after this object
             // extends the array class...
             prototype:[1,2]
            };
    x.0 = -1 /* sets array index */;
    assert -1 === x.0 /* array index */;
    assert -1 === x.prototype.0 /* array index */;
    if(!pragma(build-opt CWAL_OBASE_ISA_HASH)){
        assert 0 === x.'0' /* string type bypasses array index check,
                              so this is an object property access.*/;
        x.'0' = 1 /* also object property, not array index */;
        assert -1 === x.0 /* string key '0' did not overwrite this */;
    }
    x[] = 3;
    assert 3 === x.length();
    assert 3 === x.2;

    /*
      Minor descrepancy vis-a-vis objects: objects never (via
      assignment) add new properties to their prototypes, but instead
      shadow any prototype prop with the same name. Arrays index
      access does not behave that way: it modifies the array part of
      the value directly (the prototype, in the above example).
    */
}

scope {
    // Check for propagation of non-exception errors from array.sort()
    // callbacks...
    const ex = catch [1,2].sort(proc(){
        ,/*intentional syntax error*/
        assert cannot happen;
    });
    assert 'CWAL_SCR_SYNTAX' === ex.codeString();
}

scope {
    // Ensure that attempts to iterate over an array from a sort()
    // callback, or sort during iteration, are rejected...
    const ar = [1,2,3];
    var ex = catch foreach(@ar=>v) ar.sort();
    assert 'CWAL_RC_IS_VISITING_LIST' === ex.codeString();
    ex = catch ar.sort(proc(){foreach(@ar=>v){1}});
    assert 'CWAL_RC_LOCKED' === ex.codeString();

    // But multiple iteration must (as of 20191211) work...
    var i = 0;
    foreach(@ar=>v) foreach(@ar=>v) ++i;
    assert i === ar.# * ar.#;

    // Assigning during traversal is OK:
    foreach(@ar=>i,v) ar[i] = 2*v;
    assert 6 === ar.2;

    // reverse() during traversal is allowed only because it's
    // just a special case of assignment:
    foreach(@ar=>v) ar.reverse();
    assert 6 === ar.0;
    assert 2 === ar.2;
    /**
       One could rightfullyargue that sorting is also just a special
       case of get/set, but its implementation is much more involved,
       so traversing during sort, or vice versa, are currently
       disallowed.  That Way Lies Madness.
    */
}

scope {
    // Ensure that sort() never calls its callback for an empty- or
    // length-1 array...
    [].sort(proc(){cannot happen});
    [1].sort(proc(){cannot happen});
}

scope { // removeIndex()
    var x = [1,2,3,4];
    assert true === x.removeIndex(1);
    assert 3 === x.#;
    assert 1 === x[0];
    assert 3 === x[1];
    assert 4 === x[2];
    assert undefined === x[3];
}

scope { // filter()
    const isOdd = proc(v){ return v % 2 };
    var a = [1,2,3];
    var b = a.filter(isOdd);
    var c = a.filter(isOdd, true);
    assert 2 === b.#;
    assert 1===b.0 && 3===b.1;
    assert 1 === c.#;
    assert 2 === c.0;
    unset a, b, c;

    /**
       20181109: Array.filter() now accepts a Tuple 'this'. As of
       20190811, it returns a Tuple if called that way (it previously
       returned an array).
    */
    const t = [#1,2,3,4];
    assert typeinfo(istuple t);
    var ta = t.filter(isOdd);
    assert typeinfo(istuple ta);
    assert 2 === ta.#;
    assert 1 === ta.0;
    assert 3 === ta.1;
    assert [#1,3] === ta;

    ta = t.filter(isOdd,true);
    assert typeinfo(istuple ta);
    assert [#2,4] === ta;
    
    assert [#] === [#].filter(isOdd);

}

scope { // operator+=
    var a = [] /* can't be const because of += assignment :/ */;
    assert a === (a+=3);
    assert 1 === a.#;
    assert 3 === a.0;
    assert a === (a += 'b');
    assert 2 === a.#;
    assert 'b' === a.1;
    const o = {a:[]};
    assert o.a === (o.a+=1);
    assert (o.a += 2) === o.a;
    assert 2===o.a.1;
}

scope {
    /* 20191211 changes to how iteration is reported... */
    var a = [1,2,3];
    a.x = -1;
    assert !typeinfo(isiteratingprops a);
    assert !typeinfo(isiterating a);
    foreach(a=>k){
        assert typeinfo(mayiteratelist a);
        assert !typeinfo(isiteratinglist a);
        assert typeinfo(isiteratingprops a);
        assert typeinfo(isiterating a);
        foreach(@a=>v){
            assert typeinfo(isiteratinglist a);
            assert typeinfo(isiteratingprops a);
            assert typeinfo(mayiteratelist a);
            assert typeinfo(isiterating a);
        }
        assert !typeinfo(isiteratinglist a);
        assert typeinfo(isiterating a);
        assert typeinfo(isiteratingprops a);
    }
    assert !typeinfo(isiteratingprops a);
    assert !typeinfo(isiterating a);

    a.sort(proc(l,r){
        assert !typeinfo(mayiteratelist a);
        return -l.compare(r);
    });
    assert 3 === a.0;
}

scope {
    /*
      Array comparison func must internally use floating point
      comparisons.

      Changed 20191220, prompted by a bug repor against the muJS JS
      engine: https://github.com/ccxvii/mujs/issues/122
    */
    const l = [{a: 0.5}, {a: -0.1}, {a: 0.7}];
    l.sort(proc(x,y) {return x.a - y.a});
    assert 0.7===l.2.a;
    assert 0.5===l.1.a;
    assert -0.1===l.0.a;
}

Deleted bindings/s2/unit/600-000-hash.s2.

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




















































































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
const Hash = s2.Hash.new;

scope {
  var h = Hash();
  assert h inherits s2.Hash;
  var tableSize = h.hashSize();
  assert 'integer' === typename tableSize;
  assert tableSize > 0;
  unset tableSize;
  assert 0 === h.entryCount();

  h.insert(1,-1.0);
  assert 1 === h.entryCount();
  assert -1.0 === h.search(1);
  h.insert(0,1.0);
  var c = 0;
  h.eachEntry(function(k,v){
    assert 'integer' === typename k;
    assert 'double' === typename v;
    c = c + 1;
  });
  assert h.entryCount() === c;

  assert h.containsEntry(1);
  assert !h.containsEntry(2);

  var keys = h.entryKeys(), vals = h.entryValues();
  assert 'array' === typename keys;
  assert 'array' === typename vals;
  assert vals.length() == c;
  assert keys.length() == c;
  unset keys, vals;

  h.remove(-1);
  assert 2 === h.entryCount();
  h.insert(1, 0.0);
  assert 2 === h.entryCount();
  h.remove(1);
  assert 1 === h.entryCount();
  h.clearEntries();
  assert 0 === h.entryCount();
}

scope {
    var h1 = s2.Hash.new(11), h2 = s2.Hash.new(23);
    h1.insert(1, 2);
    h1.insert(2, 3);
    h1.eachEntry(h2, h2.insert);
    assert 2 === h2.entryCount();
    h1.clearEntries();
    assert !h1.entryCount();
    h2.eachEntry(h1);
    assert 2 === h1.entryCount();

    var o = {};
    h1.eachEntry(o, o.set);
    assert 3 === o.2;
    assert 2 === o.1;

    h2.clearEntries();
    h1.eachEntry(h2);
    assert 2 === h2.entryCount();
    assert 3 === h2.search(2);
    assert 2 === h2.search(1);

    h1.insert('f', proc f(){
        assert f === this;
        return this;
    });

    assert h1#'f' === (h1#'f')();
    assert h1#'f' === h1#'f'();
}

scope {
    var h = s2.Hash.new(11);
    assert !h.hasEntries();
    assert 11 === h.hashSize();
    h.insert(1, 2);
    assert h.hasEntries();
    h.insert(3, 4);
    assert 2 === h.entryCount();
    h.resize(3);
    assert 3 === h.hashSize();
    assert 4 === h # 3;
    assert 2 === h # 1;
    assert 2 === h.entryCount();
    assert h.hasEntries();
    assert 'CWAL_RC_RANGE' === catch{h.resize(-1)}.codeString();
    assert 'CWAL_RC_MISUSE' === catch{h.resize()}.codeString();
}

if(s2.getResultCodeHash){
    const rch = s2.getResultCodeHash();
    assert rch.entryCount() > 90 /* === 102 as of this writing */;
    assert rch === s2.getResultCodeHash() /* result is cached */;
    assert 'integer' === typename rch # 'CWAL_RC_OOM';
    assert 'string' === typename rch # 0 /* the only code with a well-defined value! */;
}

scope{
    var src, h;
    const reset = proc(){
        src = {a:1, b:-1, c:0};
        h = s2.Hash.new(5);
    };
    reset();
    h.insert('a', 'aaaa');
    h.takeProperties(src,1);
    assert 1 === h # 'a';
    assert undefined === src.a;
    assert undefined === src.b;
    assert undefined === src.c;
    assert(src.isEmpty());

    reset();
    h.insert('a', 'aaaa');
    h.takeProperties(src,-1);
    assert 'aaaa' === h # 'a';
    assert 1 === src.a;
    assert undefined === src.b;
    assert undefined === src.c;
    assert(!src.isEmpty());

    reset();
    h.insert('a', 'aaaa');
    const ex = catch h.takeProperties(src,0);
    assert ex;
    assert 'CWAL_RC_ALREADY_EXISTS' === ex.codeString();
    assert 1 === src.a;
    /* src.b and src.c might have been moved already:
       internal order is undefined and mutable
       at runtime. */
    assert !src.isEmpty();
}

scope { // 2021-07-24: X.takeProperties(X) (dest==src)
    const h = {#a:1, b:2};
    assert 2 === h.#;
    h.a = 3;
    assert 3 === h.a;
    assert 1 === h#'a';
    assert 1 === h.propertyCount();
    h.takeProperties(h);
    assert 0 === h.propertyCount();
    assert 3 === h#'a';
    assert 2 === h.#;
    h.a = 4;
    assert 'CWAL_RC_ALREADY_EXISTS'
        === catch{h.takeProperties(h,0)}.codeString();
    assert 2 === h.#;
    h.takeProperties(h,-1);
    assert 4 === h.a;
    assert 3 === h#'a';
    assert 2 === h.#;
}

assert 999 === scope {
    /*
      testing fix:

      https://fossil.wanderinghorse.net/r/cwal/info/5041ab1deee33194

      If it's broken, this will crash if built in debug mode,
      triggering a cwal-level assertion, possibly a different one
      depending on the type of the scope's result value.
    */
    {#a: 999}#'a'
};

scope {
    /** 2020-02-06: dot-length operator (lhs.#) now, on hashes,
        resolves to the number of hash entries.

        2021-07-24: that now seems like a bug.
    */
    assert 3 === {# a:1, b:1, c:1}.#;
}
scope {
    /* 2020-02-18: {#x:=y} assigns x as a const hash entry */
    const h = {# a:1, b:=2};
    assert 1 === h#'a';
    assert 2 === h#'b';
    assert 3 === h.insert('a',3);
    assert 'CWAL_RC_CONST_VIOLATION' === catch h.insert('b',1).codeString();
    assert 2 === h.#;
    assert 2 === foreach(#h=>k,v) 'b'===k && break v;
}

scope {
    /** 2020-02-20: inline expansion of object properties into an
        object literal, similar to JS's {...otherObj}. This is
        currently explicitly disallowed for hash literals because of
        potential semantic ambiguities in handling of hash vs. object
        properties.
    */
    const ex = catch{ {#@{a:1}} };
    assert 'CWAL_RC_TYPE' === ex.codeString();
    assert ex.message.indexOf('hash literal') > 0;
}

scope {
    /* 2021-07-09: Ensure that cwal_prop_key_can() prohibits certain
       property key types... */
    const h = {#};
    assert 'CWAL_RC_TYPE' === catch{
        h.insert(new s2.Buffer(),1);
    }.codeString();
    assert 'CWAL_RC_TYPE' === catch{
        h.insert([#], 1);
    }.codeString();
}

Deleted bindings/s2/unit/650-000-tuple.s2.

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











































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

scope {
    // Bugfix 20171111: disallow trailing semicolon in tuple value expressions:
    assert catch{[#1,2;]}.message.indexOf('semicolon')>0;
    assert catch{[#1;,2]}.message.indexOf('semicolon')>0;
}

scope {
    const T = s2.Tuple;
    var t0 = new T(0);
    assert 'tuple' === typename t0;
    assert typeinfo(istuple t0);
    assert !typeinfo(istuple T);

    assert t0 === new T(0) /* same instance! */;
    assert 0 === t0.length();
    assert !t0.#;
    var t1 = new T(1){
        this.0 = 'zero';
    };
    assert 1 == t1.length();
    assert 1 === t1.#;
    assert 'zero' === t1.0;
    assert t1 != t0;
    assert t1 === new T(1){ this.0 = 'zero' };
    assert t1 ==/*overloaded*/ new T(1){ this.0 = 'zero' };
    assert 'CWAL_RC_TYPE' === catch{t1==0}.codeString();
    assert 'CWAL_RC_RANGE' === catch{t1.3}.codeString();
    var t2 = new T(2){this.0 = -1; this.1 = -2};

    /* the comparison ops all disallow a non-tuple RHS */;
    assert t1 < t2;
    assert t2 > t1;
    assert t2 != t1;
    assert t2 !== t1;
    assert "[-1, -2]" === t2.toJSONString(0);
    assert "[-1, -2]" === t2.toString();
    assert 'CWAL_RC_TYPE' == catch {t1<1}.codeString();
    assert 'CWAL_RC_RANGE' == catch {new T(1<<16)}.codeString()
    /* length currently limited to 16 bits (64k) */
    ;
    assert catch {t1>1}.message.indexOf('annot compare')>0;

    var i = 0;
    foreach(t2 => v){assert v<0; ++i};
    assert t2.#===i;
    i = 0;
    assert typeinfo(mayiterate t2);
    assert !typeinfo(mayiterateprops t2);
    assert typeinfo(mayiteratelist t2);
    foreach(t2 => ndx,v){
        assert typeinfo(mayiteratelist t2) /* allowed as of 20191211 */; 
        assert ndx>=0;
        assert v<0;
        ++i;
    };
    assert typeinfo(mayiterate t2);
    assert !typeinfo(mayiterateprops t2);
    assert typeinfo(mayiteratelist t2);
    assert t2.length()===i;
    t2.1 = t1;
    t1.0 = t2;

    assert typeinfo(mayiteratelist [#]);
    
    var t3 = new T([1,2,3]);
    assert 3 === t3.length();
    assert 3 === t3[2];

    t3 = new T(t2);
    assert 2 === t3.length();
    foreach(t3=>i,v) assert t2[i] === v;

    assert 0 === catch {new T(0,1)}.message.indexOf("Expecting");

    // Tuple literals...
    var tL = [#1, 2, [#3, 4, 5]];
    assert typeinfo(istuple tL);
    assert typeinfo(istuple tL.2);
    assert !typeinfo(istuple tL.1);
    assert 5 === tL.2[2];
    assert '1.2.[3, 4, 5]' === tL.join('.') /* curious but true. */;

    // Tuples are treated like arrays for @rray expansion:
    var a = [@[#1,2,3]];
    assert 3 === a.length();
    assert 3 === a.2;

    assert catch {new T(1,2)}.line > 0 /* make sure 'new' decorates C-thrown exceptions */;


    {x:t1}.x; // let it propagate out for lifetime checking.
    // ^^^ reminder to self: t1[0] has a circular refererence, so t1
    // will have a refcount>0 after it leaves this scope. It will be
    // up to vacuuming to clean it up.
}

scope {
    /* More comparisons... */
    const t1 = [#1,2,3],
          t2 = [#3,2,1];
    assert t1 < t2;
    assert t2 > t1;
    assert t2 === [#3.0,2.0,1.0] /* === strictness applies only to the tuples, not their contents. */;
    assert t2 < [#3,2,1.01];
    assert t1 > [#1];
}

Deleted bindings/s2/unit/700-000-foreach.s2.

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



























































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

scope {

    var i = 0;
    foreach({a:1,b:2} => k){
        ++i;
        assert 'a'===k || 'b'===k;
    }
    assert 2 === i;
    var obj = {a:1,b:2};
    i = 0;
    foreach(obj => k,v){
        assert obj[k] === v;
        if(2===++i){
            assert 'CWAL_RC_IS_VISITING'===catch{
                obj[k] = 1 /* assignment fails while iterating
                              (limitation of cwal's properties
                              model).*/
            }.codeString();
        }
    }
    assert 2 === i;

    i = var j = 0;
    foreach(@[1,2,3] => ndx,v) i+=v, j+=ndx;
    assert 6 === i;
    assert 3 === j;
    unset j;

    i = 0;
    foreach(#{#a:1, b:2} => k,v){
        assert 'a'===k || 'b'===k;
        assert 'a'===k ? 1===v : 2===v;
        ++i;
    }
    assert 2 === i;

    i = 0;
    foreach({}.prototype => k,v) ++i;
    assert i > 10;

    assert undefined === foreach(@[]=>k) assert 0;
    assert undefined === foreach({}=>k) assert 0;
    assert undefined === foreach({#}=>k) assert 0;

    // Slightly special behaviour for enums, for consistency
    // in handling them regardless of their property storage type...
    // We "just happen to know" that enums switch from objects to hashes
    // if they get "big enough" (counting their reverse mappings)...
    // 2020-02-21: enums are now always hashes, to eliminate special-case
    // handling such as this.

    var myEnum = enum {a,b,c,d,e};
    i = 0;
    foreach(myEnum=>k){
        assert typeinfo(isstring k);
        assert typeinfo(isunique myEnum[k]);
        ++i;
    }
    assert 5 === i;
    assert 5 === myEnum.#;
    i = 0;
    foreach(myEnum=>k,v){
        assert typeinfo(isstring k);
        assert typeinfo(isunique v);
        assert myEnum[k] === v;
        assert myEnum[v] === k;
        ++i;
    }
    assert i === 5;
    unset myEnum;

    assert 3 === foreach(@[1,2,3] => v){
        v<3 ? continue : break v;
        throw "impossible";
    };
    assert 2 === foreach(@[1,2,3]=>v) v%2 || break v;

    /* Empty arrays and tuples must be a no-op: */
    i = 0;
    foreach(@[]=>i) assert false /*loop body is never eval'd*/;
    assert 0===i;
    foreach([#]=>i) assert false /*loop body is never eval'd*/;
    assert 0===i;

}

scope {
    /* foreach() in a ternary must not generate a syntax error... */
    assert 1 === 0 ? foreach(@[0]=>v) v : 1;
    assert 0 === 1 ? foreach(@[0]=>v) break v : 1;
}

scope {
    var a = [1], i =0;
    a[4] = 1;
    foreach( @a => v ) ++i;
    assert 5 === i /* array visitation now includes C-level NULL entries (as the undefined value). */;
}

scope { // foreach(string=>...)
    var n = 0;
    foreach('© © '=>i,v){
        assert v === i%2 ? ' ' : '©';
        ++n;
    }
    assert 4 === n;

    n = 0;
    var x = 'にちは';
    assert x[2] === foreach(x=>i,v){
        ++n;
        0===i && assert 'に'===v;
        1===i && assert 'ち'===v;
        2===i && break v;
        assert x[i] === v;
    };
    unset x;
    assert 3 === n;

    /* We have one slightly different code path (opimization) for ASCII strings: */
    n = 0;
    foreach('abcd'=>i,v){
        0===i && assert 'a' === v;
        1===i && assert 'b' === v;
        2===i && assert 'c' === v;
        3===i && assert 'd' === v;
        ++n;
    }
    assert 4 === n;

    /* For strings, if no key is provided we iterate over the values.
       Contrast with objects, where we iterate over keys in that case. */
    n = 0;
    foreach('にちは'=>v){
        assert 'に' === v || 'ち' === v || 'は' === v;
        ++n;
    }
<