├── test
├── empty.bas
├── input.bas
├── factorial.bas
├── multidim.bas
├── refs.bas
├── env.bas
├── yield.bas
├── gc.bas
├── hello.bas
├── inf.bas
├── ramload.bas
├── sort.bas
├── select.bas
├── call.bas
├── split.txt
├── io.bas
├── dict2.bas
├── beer.bas
├── array2.txt
├── dict.bas
├── globs.bas
├── colfuns.bas
├── setvar.bas
├── ramsave.bas
├── truth.bas
├── array.bas
├── lib.bas
├── circuit.bas
├── arith.bas
└── syntax.bas
├── msvc
├── .gitignore
├── WTS.sln
└── WTS.vcxproj
├── include
├── getopt.h
├── wts.h
└── seqio.h
├── .gitignore
├── extra
├── split.awk
├── Makefile.splits
└── d.awk
├── makefile.vbcc
├── MAKEFILE.WAT
├── MAKEFILE.TCC
├── Makefile.gnu
├── src
├── getopt.c
├── io.c
└── main.c
└── README.md
/test/empty.bas:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/test/input.bas:
--------------------------------------------------------------------------------
1 | x = input(":> ");
2 | print("X is '", x, "'");
3 |
--------------------------------------------------------------------------------
/msvc/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | Debug
3 | Release
4 | x64
5 | *.filters
6 | *.user
--------------------------------------------------------------------------------
/test/factorial.bas:
--------------------------------------------------------------------------------
1 | def fac(X)
2 | begin
3 | if x <= 1 then return 1;
4 | return X * fac(X-1)
5 | end;
6 |
7 | print fac(6);
--------------------------------------------------------------------------------
/test/multidim.bas:
--------------------------------------------------------------------------------
1 | A = [[1,2],[3,4]];
2 |
3 | Print(A[1][1]);
4 |
5 | Print A[2][2];
6 | A[2][2] = "Hello";
7 |
8 | Print A[2][2];
--------------------------------------------------------------------------------
/test/refs.bas:
--------------------------------------------------------------------------------
1 | foo$ = 1234;
2 | var = @foo$;
3 | vset(var, 15);
4 | print var & " is " & vget(var);
5 |
6 | # print "a:\ab:\bv:\vf:\fe:\e";
--------------------------------------------------------------------------------
/test/env.bas:
--------------------------------------------------------------------------------
1 | if len(args) > 0 then
2 | SETENV("Name", args[1])
3 | else
4 | SETENV("Name", "Stranger");
5 |
6 | SYSTEM("echo Hello World, $Name!");
7 |
--------------------------------------------------------------------------------
/test/yield.bas:
--------------------------------------------------------------------------------
1 | # ./wts -R test/yield.bas
2 |
3 | FOR I = 2 TO 20 STEP 2
4 | begin
5 | PRINT "I is", I;
6 | Y = YIELD();
7 | PRINT "Y is", Y
8 | end;
--------------------------------------------------------------------------------
/include/getopt.h:
--------------------------------------------------------------------------------
1 | extern int opterr;
2 | extern int optind;
3 | extern int optopt;
4 | extern char *optarg;
5 |
6 | int getopt(int argc, char **argv, char *opts);
7 |
--------------------------------------------------------------------------------
/test/gc.bas:
--------------------------------------------------------------------------------
1 | a = "frotz";
2 | b = "bar";
3 | a = "fo";
4 | a = a & "o";
5 | c = 12;
6 | print(a, b, "fred");
7 | gc("xyzzy", a);
8 | print("=============");
9 | b = "BARR";
10 | gc(b);
11 | print(a, " ", b);
--------------------------------------------------------------------------------
/test/hello.bas:
--------------------------------------------------------------------------------
1 | print("hello world");
2 | print "hello world";
3 | print;
4 | print "hello", "world";
5 | print "hello" & " world";
6 | print 'hello world';
7 | x = "hello world";
8 | print x;
9 | #error "test error"
10 |
--------------------------------------------------------------------------------
/test/inf.bas:
--------------------------------------------------------------------------------
1 | # This is just to test whether the runtime.max_cycles mechanism works.
2 | # Run it like so, and the interpreter should yield after 1000 instructions:
3 | # ./wts -v -Y 1000 test/inf.bas
4 |
5 | WHILE 1 DO
6 | PRINT "working";
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.o
2 |
3 | wts
4 | *.exe
5 | *.out
6 |
7 | wts.html
8 | README.html
9 |
10 | splits
11 |
12 | *~
13 | *.old
14 | *.bak
15 | *.tmp
16 | out.*
17 |
18 | *.OBJ
19 | *.MAP
20 | *.TC
21 |
22 | *.BIN
23 |
24 | .gdbinit
25 | .vscode
--------------------------------------------------------------------------------
/test/ramload.bas:
--------------------------------------------------------------------------------
1 | # See `ramsave.bas` for more information.
2 |
3 | PRINT "A:";
4 | FOR I IN A PRINT I, "=>", A[I];
5 |
6 | PRINT "B:";
7 | FOR I IN B PRINT I, "=>", B[I];
8 |
9 | PRINT "C:", C;
10 | PRINT "D:", D;
11 | PRINT "E:", E;
12 |
13 | PRINT "DONE";
--------------------------------------------------------------------------------
/test/sort.bas:
--------------------------------------------------------------------------------
1 |
2 | a = ["foo", "bar", "baz",
3 | "quux", "zulu",
4 | "lima", "gamma", "kappa", "echo",
5 | "alpha", "bravo", "zulu",
6 | "golf", "fred", "alice", "bob"];
7 |
8 | b = [13,53, 21, 65, 12, 32];
9 |
10 | sort(a);
11 | for i in a
12 | print a[i];
13 |
14 | sort(b);
15 | for i in b
16 | print b[i];
17 |
--------------------------------------------------------------------------------
/test/select.bas:
--------------------------------------------------------------------------------
1 | DO
2 | SELECT INPUT()
3 | CASE 0, "exit":
4 | BREAK
5 | CASE 1, "a", "A":
6 | PRINT "Option 1 chosen"
7 | CASE 2, 'b', "B":
8 | PRINT "Option 2 chosen"
9 | CASE 3, 'c', "C":
10 | PRINT "Option 3 chosen"
11 | CASE 'n': # Do nothing
12 | DEFAULT:
13 | PRINT "Default option"
14 | END
15 | LOOP;
--------------------------------------------------------------------------------
/extra/split.awk:
--------------------------------------------------------------------------------
1 | /\/\*<<{
2 | redir = $2; next
3 | }
4 |
5 | /\/\*#include/ {
6 | print "#include " $2 > redir;
7 | next
8 | }
9 |
10 | /\/\*#extern/ {
11 | gsub(/.*#extern|\*\/.*/,"");
12 | print "extern " $0 > redir;
13 | next
14 | }
15 |
16 | /#define.*static/ {next}
17 |
18 | redir {
19 | gsub(/STATIC/,"",$0);
20 | print $0 > redir
21 | }
--------------------------------------------------------------------------------
/test/call.bas:
--------------------------------------------------------------------------------
1 | # ./wts -Cfoo -Cbar -Cfoo test/call.bas
2 |
3 | def foo(a, b, c)
4 | begin
5 | print "foo: initially:", a, ",", b, "and", c;
6 | # bar(a*2,b*2,(c+1)*2);
7 | call(@bar, a*2,b*2,(c+1)*2);
8 | print "foo: afterwards:", a, ",", b, "and", c
9 | end;
10 |
11 | def bar(a, b, c) print "bar: ", a, ",", b, "and", c;
12 |
13 | call(@foo, 1,2,3);
14 |
15 | print "nothing to see here";
16 |
--------------------------------------------------------------------------------
/test/split.txt:
--------------------------------------------------------------------------------
1 | s = "foozle bar baz fred";
2 | A = split(s);
3 |
4 | B = split("foozle;bar;;baz;fred;", ";");
5 |
6 | C = join(A, ':');
7 |
8 | x = [];
9 |
10 | D = {"XXX":"YYY", "bar": 5 + 5 * 5, 123 : 456,
11 | "child": {"a key":"A value", "b key" : "B value"}, 1:2, 2:3, 3:4,
12 | "array": [10,20,30,40,50]};
13 |
14 | k = KEYS(D);
15 | q = KEYS(A);
16 |
17 | for i in D print(i);
18 |
19 | print("done");
--------------------------------------------------------------------------------
/test/io.bas:
--------------------------------------------------------------------------------
1 | filename = 'test.out';
2 |
3 | ofile = OPEN(filename, 'output');
4 | print "Opened output file as", ofile;
5 |
6 | WRITE ofile, "Orc", 9, 12, "rusty sword";
7 | WRITE ofile, "Cave Troll", 12, 14, "club";
8 |
9 | CLOSE ofile;
10 |
11 | ifile = OPEN(filename, 'input');
12 | print "Opened input file as", ifile;
13 |
14 | for i = 1 to 2
15 | begin
16 | READ ifile, @name, @strength, @gold, @weapon;
17 | print "Got creature:", name, strength, weapon, gold
18 | end;
19 |
20 | CLOSE ifile;
21 |
22 | print "fin.";
23 |
--------------------------------------------------------------------------------
/test/dict2.bas:
--------------------------------------------------------------------------------
1 | # Run with the `-u` command line option to see
2 | # that all the values are correct
3 |
4 | A = {"XXX":"YYY", "bar": 5 + 5 * 5, 123 : 456,
5 | "child": {"a key":"A value", "b key" : "B value"}, 1:2, 2:3, 3:4,
6 | "array": [10,20,30,40,50]};
7 |
8 | A["foo"] = "bar";
9 |
10 | print A["foo"], A["XXX"], A[123];
11 |
12 | print A["child"]["a key"];
13 | print A["array"][4];
14 |
15 | A["child"]["c key"] = "C value";
16 |
17 | D = A["child"];
18 | D["d key"] = "D value";
19 | D["e key"] = {};
20 |
21 | C = A["array"];
22 |
23 |
--------------------------------------------------------------------------------
/test/beer.bas:
--------------------------------------------------------------------------------
1 | # Script to excercise the garbage collector.
2 | # https://en.wikipedia.org/wiki/99_Bottles_of_Beer
3 |
4 | a = "some string";
5 |
6 | b = 99;
7 |
8 | do
9 | print b & " bottles of beer on the wall. " & b & " bottles of beer. Take one down, pass it around, " & (b-1) & " bottles of beer on the wall";
10 | #print a
11 | b = b - 1
12 | loop until b = 1;
13 |
14 | print "one bottle of beer on the wall. One bottle of beer. Take one down, pass it around, no more bottles of beer on the wall";
15 |
16 | print "No more bottles of beer on the wall, no more bottles of beer.";
17 | print "We've taken them down and passed them around; now we're drunk and passed out! ";
18 |
--------------------------------------------------------------------------------
/test/array2.txt:
--------------------------------------------------------------------------------
1 | A = [1, 2, 3];
2 |
3 | B = PUSH(A, 0);
4 | C = APPEND(B, 4);
5 |
6 | D = C;
7 | WHILE D DO
8 | BEGIN
9 | PRINT HEAD(D);
10 | D = TAIL(D)
11 | END;
12 |
13 | RECURSE(C);
14 |
15 | PRINT "done";
16 |
17 | def RECURSE(A)
18 | BEGIN
19 | IF NOT A THEN RETURN;
20 | PRINT HEAD(A);
21 | RECURSE(TAIL(A))
22 | END;
23 |
24 | # Test the truthiness of empty arrays and dicts
25 | IF [1] THEN print "Yay (1)";
26 | IF NOT [1] THEN print "Nay (1)";
27 | IF [] THEN print "Nay (2)";
28 | IF NOT [] THEN print "Yay (2)";
29 | IF {"A":1} THEN print "Yay (3)";
30 | IF NOT {"A":1} THEN print "Nay (3)";
31 | IF {} THEN print "Nay (4)";
32 | IF NOT {} THEN print "Yay (4)";
--------------------------------------------------------------------------------
/test/dict.bas:
--------------------------------------------------------------------------------
1 | x = "foo"&"bar";
2 | x = "";
3 |
4 | n = "na" & "me";
5 |
6 | A = {};
7 |
8 | A[n] = "dummy";
9 | A["xxx"] = n;
10 | A[200] = 1234;
11 | A[n] = "mickey";
12 | A["surname"] = "mouse";
13 | A["foo"] = "bar";
14 |
15 | n = "name";
16 | #GC
17 |
18 | print A["xxx"], A[n], A["surname"], A[200];
19 |
20 | print "iterating:";
21 |
22 | for key in A
23 | print key & " => " & A[key];
24 |
25 | B = {};
26 | for i = 1 to 20
27 | B["key"&i] = "value " & i;
28 |
29 | print "iterating, part 2:";
30 | for key in B
31 | print key & " => " & B[key];
32 |
33 | PRINT "B is ", B;
34 |
35 | PRINT "Looping with CONTINUE and BREAK:";
36 | A = { "one":1, "two":2, "three":3, "four":4, "five":5, "six":6 };
37 | FOR K IN A
38 | begin
39 | if k = "five" then continue;
40 | PRINT K, A[K];
41 | if k = "six" then break
42 | end;
43 |
44 | print "fin.";
--------------------------------------------------------------------------------
/test/globs.bas:
--------------------------------------------------------------------------------
1 | # This script is meant to be run with the -s option
2 | # ./wts -s abc=123 -s q=Quux -s p=Fred -u test/globs.bas
3 | # `ABC` is not used in the script, but it should still be in the dump.
4 | # (It is a basic test for when I started storing globals in a dict)
5 |
6 | # Garbage for the GC:
7 | X = "1";
8 | X = "2";
9 |
10 | X = "Foo";
11 | y = "Bar";
12 | Z = {"x":X, "y":y, "p": p, "q" : Q};
13 | W = [1,2,X,Y,p,q];
14 | Print x, y, z["q"], x, q;
15 | PRINT something(5);
16 | PRINT foo(Z);
17 |
18 | # Some additional globals to force the
19 | # globals hash table to rehash
20 | A1 = "123";
21 | A2 = "456";
22 | A3 = "789";
23 | A4 = "ABC";
24 | A5 = "DEF";
25 |
26 | def something(a)
27 | begin
28 | # `a` is local, so you shouldn't see it in the dump. So is `b`
29 | local b = 2;
30 | return (a + 1) * b
31 | end;
32 |
33 | def foo(A)
34 | begin
35 | local xx = "FOO";
36 | A["a"] = xx;
37 | return A["a"]
38 | end;
--------------------------------------------------------------------------------
/test/colfuns.bas:
--------------------------------------------------------------------------------
1 | PRINT "Array:";
2 | A = [23, 32, 12, 63, 7, 58, 14, 87, 2, 42, 36, 95, 15];
3 | FOR I IN A PRINT I, "=>", A[I];
4 | print "Find 87: ", FIND(A, 87);
5 | print "Find 23: ", FIND(A, 23);
6 | print "Find 64: ", FIND(A, 64);
7 |
8 | B = COPY(A);
9 |
10 | PRINT "Sorting:";
11 | SORT A;
12 | FOR I IN A PRINT I, "=>", A[I];
13 |
14 | PRINT "Reverse:";
15 | REVERSE A;
16 | FOR I IN A PRINT I, "=>", A[I];
17 |
18 | PRINT "Shuffle:";
19 | SHUFFLE A;
20 | FOR I IN A PRINT I, "=>", A[I];
21 |
22 | PRINT "Copy:";
23 | FOR I IN B PRINT I, "=>", B[I];
24 |
25 | PRINT "Dict:";
26 | D = {"foo":3, "baz":2, "quux":1, "bar":4};
27 | C = COPY(D);
28 | C['xxx'] = 6;
29 | C['yyy'] = 8;
30 | C['zzz'] = 9;
31 | FOR I IN C PRINT I, "=>", C[I];
32 | print "Find 1: ", FIND(C, 1);
33 | print "Find 3: ", FIND(C, 3);
34 | print "Find 5: ", FIND(C, 5);
35 | print "Find 6: ", FIND(C, 6);
36 |
37 | PRINT "Original:";
38 | FOR I IN D PRINT I, "=>", D[I];
39 |
--------------------------------------------------------------------------------
/test/setvar.bas:
--------------------------------------------------------------------------------
1 | # Test program for the `-s` and `-r` command line options which in turn test
2 | # the `w_set_variable_string()` and `w_get_variable_string()` API functions.
3 |
4 | # Run like so:
5 | # $ ./wts -s foo=bar -s bar=1234 -s fred -v test/setvar.bas
6 | # or
7 | # $ ./wts -r foo -r bar -r baz test/setvar.bas
8 |
9 | print "foo = ", foo;
10 | print "bar = ", bar;
11 | print "baz = ", baz;
12 | print "fred = ", fred;
13 |
14 | vara = 1306;
15 | varb = '';
16 |
17 | # `@vara` is syntactic sugar for saying `"VARA"`
18 | # Using it with the `VSET` and `VGET` functions
19 | # gives you something akin to pointer symantics.
20 |
21 | variable = @vara;
22 | print 'VGET: ', variable, ' = ', vget(variable);
23 | variable = @varb;
24 | print 'VGET: ', variable, ' = ', vget(variable);
25 |
26 | VSET(@vara, 'a string');
27 | VSET(@varb, 503);
28 |
29 | print 'VSET: vara: ', vara;
30 | print 'VSET: varb: ', varb;
31 |
32 | foo = "This is foo";
33 | bar = 12001;
34 |
--------------------------------------------------------------------------------
/test/ramsave.bas:
--------------------------------------------------------------------------------
1 | # This script along with `ramload.bas` tests the ability to save and
2 | # load the VM's RAM to and from a file.
3 | #
4 | # Run the interpreter like so to initialise a couple of variables
5 | # and save those variables to a file called `ram.bin`:
6 | #
7 | # ./wts -M ram.bin -u test/ramsave.bas save
8 | #
9 | # Then run the interpreter again like this to load all the variables
10 | # from the `ram.bin` file:
11 | #
12 | # ./wts -m ram.bin -u test/ramload.bas
13 | #
14 | # If it's broken, check whether W_PRESERVE_RAM is set to 1
15 |
16 | PRINT "Initialising variables";
17 | A = ["Hello", "World", "Hello " & "World", 101, 201, 301, 401, 501, [11, 12, 13, 14], 701];
18 | B = {"foo": 10, "bar": "Some String", "baz": 30, "fred": {"quux": 1000, "xyzzy": 2000}};
19 | C = "Hello World";
20 | D = 1234;
21 | E = C;
22 |
23 | PRINT "A:";
24 | FOR I IN A PRINT I, "=>", A[I];
25 |
26 | PRINT "B:";
27 | FOR I IN B PRINT I, "=>", B[I];
28 |
29 | PRINT "C:", C;
30 | PRINT "D:", D;
31 | PRINT "E:", E;
32 |
33 | PRINT "DONE";
--------------------------------------------------------------------------------
/makefile.vbcc:
--------------------------------------------------------------------------------
1 | # GNU Make Makefile for cross-compiling for Amiga using VBCC
2 | CC=vc +kick13
3 |
4 | thisfile = $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
5 |
6 | #.SILENT:
7 |
8 | CFLAGS=-Iinclude
9 | LDFLAGS=
10 |
11 | # Add your source files here:
12 | SOURCES=src/wts.c src/main.c src/getopt.c src/io.c
13 | OBJECTS=$(SOURCES:.c=.o)
14 |
15 | EXECUTABLE=wts
16 |
17 | ifeq ($(BUILD),debug)
18 | # Debug
19 | CFLAGS +=-O0 -g -stack-check
20 | LDFLAGS +=-g -stack-check
21 | else
22 | # Release mode
23 | CFLAGS +=-O2 -DNDEBUG
24 | LDFLAGS += -s
25 | endif
26 |
27 | all: $(EXECUTABLE)
28 |
29 | debug:
30 | $(MAKE) -f $(thisfile) BUILD=debug --no-print-directory
31 |
32 | $(EXECUTABLE): $(OBJECTS)
33 | $(CC) $(LDFLAGS) $(OBJECTS) -o $@
34 |
35 | .c.o:
36 | $(CC) $(CFLAGS) -c $< -o $@
37 |
38 | src/wts.o: src/wts.c include/wts.h
39 | src/main.o: src/main.c include/getopt.h include/wts.h
40 | src/getopt.o: src/getopt.c
41 | src/io.o: src/io.c include/seqio.h
42 |
43 | .PHONY : clean
44 |
45 | clean:
46 | -rm -f src/*.o $(EXECUTABLE) *.asm
47 |
--------------------------------------------------------------------------------
/MAKEFILE.WAT:
--------------------------------------------------------------------------------
1 | # Watcom Makefile
2 | # To invoke, use the command line `wmake /f MAKEFILE.WAT`
3 |
4 | CC = wcc386
5 | LD = wlink
6 |
7 | # I don't know how to specify a stack size with Watcom,
8 | # so use W_REENTRANT=0 to avoid stack overflow errors
9 | cflags= /i=include /dW_REENTRANT=0
10 | lflags=
11 |
12 | source = src\wts.c src\main.c src\getopt.c src\io.c
13 | objects = wts.obj main.obj getopt.obj io.obj
14 |
15 | #!if $d(DEBUG)
16 | #cflags= $(cflags) -N -y -v
17 | #lflags= $(lflags) /s
18 | #!else
19 | #cflags= $(cflags) -DNDEBUG
20 | #lflags= $(lflags)
21 | #!endif
22 |
23 | wts.exe : $(objects)
24 | $(LD) file wts.obj,main.obj,getopt.obj,io.obj
25 |
26 | .c.obj:
27 | $(CC) $(cflags) $<
28 |
29 | wts.obj: src\wts.c include\wts.h
30 | $(CC) $(cflags) src\wts.c
31 | main.obj: src\main.c include\wts.h
32 | $(CC) $(cflags) src\main.c
33 | io.obj: src\io.c include\seqio.h
34 | $(CC) $(cflags) src\io.c
35 | getopt.obj: src\getopt.c
36 | $(CC) $(cflags) src\getopt.c
37 |
38 | clean: .symbolic
39 | -del *.obj
40 | -del *.exe
41 |
--------------------------------------------------------------------------------
/test/truth.bas:
--------------------------------------------------------------------------------
1 | # Run like so: ./wts -s Q=Quux truth.bas
2 | Q = Q OR "default";
3 | PRINT "Q is", Q;
4 |
5 | If 0 then
6 | print "0 truthy"
7 | else
8 | print "0 falsy";
9 |
10 | If 1 then
11 | print "1 truthy"
12 | else
13 | print "1 falsy";
14 |
15 | If '' then
16 | print "'' truthy"
17 | else
18 | print "'' falsy";
19 |
20 | If '1' then
21 | print "'1' truthy"
22 | else
23 | print "'1' falsy";
24 |
25 | If '0' then
26 | print "'0' truthy"
27 | else
28 | print "'0' falsy";
29 |
30 | If 'foo' then
31 | print "'foo' truthy"
32 | else
33 | print "'foo' falsy";
34 |
35 | If [] then
36 | print "[] truthy"
37 | else
38 | print "[] falsy";
39 |
40 | If [1] then
41 | print "[1] truthy"
42 | else
43 | print "[1] falsy";
44 |
45 | If {} then
46 | print "{} truthy"
47 | else
48 | print "{} falsy";
49 |
50 | If {'x':'y'} then
51 | print "{'x':'y'} truthy"
52 | else
53 | print "{'x':'y'} falsy";
54 |
--------------------------------------------------------------------------------
/test/array.bas:
--------------------------------------------------------------------------------
1 | A = dim(10);
2 |
3 | x = "a" & " string";
4 |
5 | # Arrays are indexed from 1
6 | A[1] = "Hel" & "lo";
7 | A[5] = 20;
8 | A[4] = x;
9 | A[6] = 30;
10 | A[7] = "hello";
11 |
12 | print A[1];
13 | print A[5];
14 | print A[6];
15 | print A[7];
16 |
17 | A = dim(10);
18 | A[1] = "Good" & "bye";
19 | A[4] = x;
20 | print "A[1] is", A[1];
21 | print "A[4] is", A[4];
22 | print "A[7] is", A[7];
23 |
24 | B = A;
25 |
26 | gc;
27 |
28 | print "A[1] is", A[1];
29 | print "A[4] is", A[4];
30 | print "A[7] is", A[7];
31 |
32 | A = [10, 20, 30];
33 |
34 | print A[1], A[2], A[3];
35 |
36 | # Array elements containing arrays:
37 | A[2] = ["x",
38 | "y", "z"];
39 |
40 | # Array elements containing dicts:
41 | A[3] = {"foo":"bar", "bar" : "baz"};
42 |
43 | print A[2][2];
44 | print A[3]["foo"];
45 |
46 | print "Looping through an array:";
47 |
48 | A = [23, 32, 12, 63, 58, 14, 87, 42, 36, 95];
49 | FOR I IN A PRINT A[I];
50 |
51 | print "Testing REDIM()";
52 |
53 | A = [3, 5, 6, 7, 1, 5];
54 | B = REDIM(A, 10);
55 | C = REDIM(A, 3);
56 |
57 | PRINT "A:";
58 | FOR I IN A PRINT A[I];
59 | PRINT "B:";
60 | FOR I IN B PRINT B[I];
61 | PRINT "C:";
62 | FOR I IN C PRINT C[I];
63 |
64 | print "fin.";
--------------------------------------------------------------------------------
/test/lib.bas:
--------------------------------------------------------------------------------
1 | print min(8, 3, 6, -5, 7, -4, 2, 8, 13, 3, 4, 7);
2 | print max(8, 3, 6, -5, 7, -4, 2, 8, 13, 3, 4, 7);
3 |
4 | print asc('A'), " ", chr(65);
5 |
6 | X = "Hello World abc XYZ";
7 | print len(X);
8 | print ucase(X);
9 | print lcase(X);
10 |
11 | print '* LEFT ...: "',left(X, 5), '"';
12 | print '* RIGHT ..: "', right(X, 3), '"';
13 | print '* MID ....: "', mid(X, 7, 5), '"';
14 |
15 | print '* LEFT ...: "',left(X, 25), '"';
16 | print '* RIGHT ..: "', right(X, 25), '"';
17 | print '* MID ....: "', mid(X, 1, 0), '"';
18 |
19 | print '* TRIM ...: "', trim(" \t\n to be trimmed\t "), '"';
20 |
21 | print instr(X, "Wor"), ' ', instr(X, "War");
22 |
23 | print iif(1, "right", "wrong");
24 | print iif(0, "wrong", 1234);
25 |
26 |
27 | print '* ABS ....: ', abs(-10), abs(0), abs(10);
28 |
29 | print '* RND ....: ', rnd();
30 | print '* RND ....: ', rnd(4);
31 | print '* RND ....: ', rnd(5,10);
32 |
33 | print '* MATCH ..: ', MATCH('foooooo', 'f*o');
34 | print '* MATCH ..: ', MATCH('foooooo', 'f*a');
35 | print '* MATCH ..: ', MATCH('fooooob', 'f*oo?');
36 |
37 | print 'TYPEOF(0) ........:', TYPEOF(0);
38 | print 'TYPEOF("foo") ....:', TYPEOF("foo");
39 | print 'TYPEOF([1]) ......:', TYPEOF([1]);
40 | print 'TYPEOF({1:"x"}) ..:', TYPEOF({1:"x"});
41 |
42 | print "fin";
43 |
44 | #print undefed('x')
45 | #printf 'x'
--------------------------------------------------------------------------------
/test/circuit.bas:
--------------------------------------------------------------------------------
1 | # Small test program to test the short-circuiting behaviour
2 |
3 | def true()
4 | begin
5 | print "true";
6 | return 1
7 | end;
8 |
9 | def false()
10 | begin
11 | print "false";
12 | return 0
13 | end;
14 |
15 | print "true() and false():";
16 | print "=", true() and false();
17 |
18 | print "false() and true():";
19 | print "=", false() and true();
20 |
21 | print "true() or false():";
22 | print "=", true() or false();
23 |
24 | print "false() or true():";
25 | print "=", false() or true();
26 |
27 | print "false() or false() or false():";
28 | print "=", false() or false() or false();
29 |
30 | print "false() or true() or true():";
31 | print "=", false() or true() or true();
32 |
33 | print "true() or false() or false():";
34 | print "=", true() or false() or false();
35 |
36 | print "false() or true() and true():";
37 | print "=", false() or true() and true();
38 |
39 | print "(true() or false()) and true():";
40 | print "=", (true() or false()) and true();
41 |
42 | # Using 'OR' for default values:
43 | Print "Input something, or leave blank for default";
44 | X = INPUT() or "default";
45 | Print "X is", X;
46 |
47 | # Without short-circuiting, you'll get an error in the `print` call:
48 | def error_on_0(x)
49 | begin
50 | if not x then error("Zero");
51 | return "no error"
52 | end;
53 |
54 | Y = 0;
55 | print Y and error_on_0(Y);
--------------------------------------------------------------------------------
/MAKEFILE.TCC:
--------------------------------------------------------------------------------
1 | # Turbo C (v2.01) Makefile
2 | # To invoke, use the command line `make -fMAKEFILE.TCC`
3 |
4 | # Set the `tc_path` variable correctly first.
5 | # There are a lot of things in here that don't match what I read
6 | # in the manual, but I suspect that the issue is in how DOSBox
7 | # handles environment variables...
8 |
9 | # Make sure this path is correct:
10 | tc_path=c:\tc
11 |
12 | # Use the medium model
13 | mdl=m
14 |
15 | # Remove this when ready:
16 | #DEBUG=1
17 |
18 | cflags= -m$(mdl)
19 | lflags= /d
20 |
21 | source = src\wts.c src\main.c src\getopt.c src\io.c
22 | objects = wts.obj main.obj getopt.obj io.obj
23 |
24 | !if $d(DEBUG)
25 | cflags= $(cflags) -N -y -v -Iinclude
26 | lflags= $(lflags) /s
27 | !else
28 | cflags= $(cflags) -DNDEBUG -Iinclude
29 | lflags= $(lflags) /x
30 | !endif
31 |
32 | wts.exe : $(objects)
33 | tlink $(lflags) $(tc_path)\lib\c0$(mdl) $(objects), $*, , $(tc_path)\lib\c$(mdl)
34 |
35 | debug:
36 | make -DDEBUG=1
37 |
38 | #.c.obj:
39 | # tcc $(cflags) -c $<
40 |
41 | wts.obj: src\wts.c include\wts.h
42 | tcc $(cflags) -c src\wts.c
43 | main.obj: src\main.c include\wts.h
44 | tcc $(cflags) -c src\main.c
45 | io.obj: src\io.c include\seqio.h
46 | tcc $(cflags) -c src\io.c
47 | getopt.obj: src\getopt.c
48 | tcc $(cflags) -c src\getopt.c
49 |
50 | clean:
51 | -del *.obj
52 | -del *.exe
53 | -del *.map
54 |
--------------------------------------------------------------------------------
/test/arith.bas:
--------------------------------------------------------------------------------
1 | print("-(2+5) = ", -(2+5) );
2 | print("1 + 3 = ", 1 + 3);
3 | print("1 - 3 = ", 1 - 3);
4 | print("(2+2) * 3 = ", (2+2) * 3);
5 | print("17 / 3 = ", 17 / 3);
6 | print("17 mod 3 = ", 17 mod 3);
7 | print("17 & 3 = ", 17 & 3);
8 |
9 | # this is a comment
10 |
11 | print("0 and 0 = ", 0 and 0);
12 | print("0 and 1 = ", 0 and 1);
13 | print("1 and 1 = ", 1 and 1);
14 | print("0 or 0 = ", 0 or 0);
15 | print("0 or 1 = ", 0 or 1);
16 | print("not 0 = ", not 0);
17 |
18 | print("1 = 1 : ", 1=1);
19 | print("1 = 2 : ", 1=2);
20 | print("1 <> 1: ", 1 <> 1);
21 | print("1 <> 2: ", 1 <> 2);
22 | print("1 < 1: ", 1 < 1);
23 | print("1 < 2: ", 1 < 2);
24 | print("1 <= 1: ", 1 <= 1);
25 | print("1 <= 2: ", 1 <= 2);
26 | print("1 > 1: ", 1 > 1);
27 | print("1 > 2: ", 1 > 2);
28 | print("2 > 1: ", 2 > 1);
29 | print("1 >= 1: ", 1 >= 1);
30 | print("1 >= 2: ", 1 >= 2);
31 |
32 | print("'a' = 'a' : ", "a" = "a");
33 | print("'a' < 'a' : ", "a" < "a");
34 | print("'a' <= 'a' : ", "a" <= "a");
35 | print("'a' = 'b' : ", "a" = "b");
36 | print("'a' < 'b' : ", "a" < "b");
37 | print("'a' > 'b' : ", "a" > "b");
38 | print("'a' <= 'b' : ", "a" <= "b");
39 | print("'c' = 'b' : ", "c" = "b");
40 | print("'c' < 'b' : ", "c" < "b");
41 | print("'c' <= 'b' : ", "c" <= "b");
42 | print("'c' = 'b' : ", "c" = "b");
43 | print("'c' > 'b' : ", "c" > "b");
44 | print("'c' >= 'b' : ", "c" >= "b");
45 |
46 | print("Concatenate: ", 1234 & 5678);
47 |
48 | if 1 < 0 then print("yes") else print("no");
49 | if 1 > 0 then print("yes") else print("no");
50 |
--------------------------------------------------------------------------------
/msvc/WTS.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31424.327
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WTS", "WTS.vcxproj", "{11D94BA6-22C4-48DC-941A-F60DD43D64B4}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Debug|x86 = Debug|x86
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {11D94BA6-22C4-48DC-941A-F60DD43D64B4}.Debug|x64.ActiveCfg = Debug|x64
17 | {11D94BA6-22C4-48DC-941A-F60DD43D64B4}.Debug|x64.Build.0 = Debug|x64
18 | {11D94BA6-22C4-48DC-941A-F60DD43D64B4}.Debug|x86.ActiveCfg = Debug|Win32
19 | {11D94BA6-22C4-48DC-941A-F60DD43D64B4}.Debug|x86.Build.0 = Debug|Win32
20 | {11D94BA6-22C4-48DC-941A-F60DD43D64B4}.Release|x64.ActiveCfg = Release|x64
21 | {11D94BA6-22C4-48DC-941A-F60DD43D64B4}.Release|x64.Build.0 = Release|x64
22 | {11D94BA6-22C4-48DC-941A-F60DD43D64B4}.Release|x86.ActiveCfg = Release|Win32
23 | {11D94BA6-22C4-48DC-941A-F60DD43D64B4}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {93DD18E5-F2F9-49C2-A29A-A06A2A604DF3}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/extra/Makefile.splits:
--------------------------------------------------------------------------------
1 | CC ?= gcc
2 |
3 | EXECUTABLE ?= wts
4 |
5 | thisfile = $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
6 |
7 | CFLAGS=-Wall -Wextra -pedantic -I ../include
8 | #CFLAGS += -std=c89
9 | LDFLAGS=
10 |
11 | # Add your source files here:
12 | SOURCES= $(wildcard *.c)
13 | OBJECTS=$(SOURCES:%.c=%.o)
14 | SOURCE += ../src/main.c ../src/io.c
15 | OBJECTS += main.o io.o
16 |
17 | ifeq ($(OS),Windows_NT)
18 | EXECUTABLE:=$(EXECUTABLE).exe
19 | endif
20 |
21 | ifeq ($(BUILD),debug)
22 | # Debug
23 | CFLAGS += -O0 -g3
24 | LDFLAGS +=
25 | else
26 | # Release mode
27 | CFLAGS +=-O2 -DNDEBUG
28 | LDFLAGS += -s
29 | endif
30 |
31 | all: $(EXECUTABLE)
32 |
33 | debug:
34 | @make -f $(thisfile) $(MAKEFLAGS) BUILD=debug --no-print-directory
35 |
36 | $(EXECUTABLE): $(OBJECTS)
37 | $(CC) $(LDFLAGS) $(OBJECTS) -o $@
38 |
39 | %.o: src/%.c
40 | $(CC) $(CFLAGS) -c $< -o $@
41 | main.o: ../src/main.c
42 | $(CC) $(CFLAGS) -c $< -o $@
43 | io.o: ../src/io.c
44 | $(CC) $(CFLAGS) -c $< -o $@
45 |
46 | bytecode.o: bytecode.c ../include/wts.h internal.h
47 | compiler.o: compiler.c ../include/wts.h internal.h
48 | globals.o: globals.c ../include/wts.h internal.h
49 | library.o: library.c ../include/wts.h internal.h
50 | other.o: other.c ../include/wts.h internal.h
51 | runtime.o: runtime.c ../include/wts.h internal.h
52 | main.o: ../src/main.c ../include/wts.h
53 | io.o: ../src/io.c ../include/wts.h ../include/seqio.h
54 |
55 | .PHONY : clean deps fixdos
56 |
57 | deps:
58 | $(CC) -MM -I ../include $(SOURCES)
59 |
60 | # The DOS compiler has a lot of trouble with
61 | # \n line endings, so this target fixes those
62 | fixdos:
63 | unix2dos src/*.c include/*.h
64 |
65 | clean:
66 | -rm -f *.o $(EXECUTABLE)
67 |
--------------------------------------------------------------------------------
/Makefile.gnu:
--------------------------------------------------------------------------------
1 | CC ?= gcc
2 | AWK ?= awk
3 |
4 | EXECUTABLE ?= wts
5 |
6 | CFLAGS=-Wall -Wextra -pedantic -I include
7 | #CFLAGS += -std=c89
8 | LDFLAGS=
9 |
10 | # Add your source files here:
11 | SOURCES=src/wts.c src/main.c src/io.c
12 | OBJECTS=$(SOURCES:src/%.c=%.o)
13 |
14 | .SILENT:
15 |
16 | ifeq ($(OS),Windows_NT)
17 | EXECUTABLE:=$(EXECUTABLE).exe
18 | endif
19 |
20 | ifeq ($(BUILD),debug)
21 | # Debug
22 | CFLAGS += -O0 -g3
23 | LDFLAGS +=
24 | else
25 | # Release mode
26 | CFLAGS +=-O2 -DNDEBUG
27 | LDFLAGS += -s
28 | endif
29 |
30 | all: $(EXECUTABLE) docs ## Builds the executable and docs
31 |
32 | debug: ## Builds a debug version
33 | $(MAKE) BUILD=debug --no-print-directory
34 |
35 | $(EXECUTABLE): $(OBJECTS)
36 | echo $@
37 | $(CC) $(LDFLAGS) $(OBJECTS) -o $@
38 |
39 | %.o: src/%.c
40 | echo $@
41 | $(CC) $(CFLAGS) -c $< -o $@
42 |
43 | # run `make deps` to update this list
44 | wts.o: src/wts.c include/wts.h
45 | main.o: src/main.c include/wts.h
46 | io.o: src/io.c include/wts.h include/seqio.h
47 |
48 | deps: ## Generates a list of dependencies for the Makefile
49 | $(CC) -MM -I include $(SOURCES)
50 |
51 | docs: wts.html README.html ## Generates documentation
52 |
53 | wts.html : extra/d.awk include/wts.h src/wts.c src/main.c src/io.c
54 | echo $@
55 | $(AWK) -vTitle="WTS Manual" -f extra/d.awk $(filter-out extra/d.awk, $^) > $@
56 |
57 | README.html: README.md extra/d.awk
58 | echo $@
59 | $(AWK) -f extra/d.awk -vClean=1 $< > $@
60 |
61 | # Eh, you don't actually need the filter-out above ^^^
62 |
63 | .PHONY : clean deps fixdos split help
64 |
65 | # The DOS compiler has a lot of trouble with
66 | # \n line endings, so this target fixes those
67 | fixdos: ## Replaces \n in the source with \r\n for MS-DOS
68 | unix2dos src/*.c include/*.h
69 |
70 | split: src/wts.c extra/split.awk | splits ## Splits src/wts.c into separate files
71 | awk -f extra/split.awk src/wts.c
72 | cp extra/Makefile.splits splits/Makefile
73 |
74 | splits:
75 | mkdir -p $@
76 |
77 | clean:
78 | -rm -f *.o $(EXECUTABLE)
79 | -rm -f wts.html
80 | -rm -rf splits
81 |
82 | help: ## Displays this help message
83 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-10s\033[0m %s\n", $$1, $$2}'
84 |
--------------------------------------------------------------------------------
/src/getopt.c:
--------------------------------------------------------------------------------
1 | /*
2 | Modified from the public domain getopt() posted to comp.sources.misc:
3 | https://groups.google.com/g/comp.sources.misc/c/egycYBQHHWY/m/9mXy43Ojax8J
4 |
5 | There's an even older version here:
6 | https://groups.google.com/g/mod.std.unix/c/HQlsXpuokHE/m/PSJxR3CzMccJ
7 |
8 | I just cleaned it up and ANSIfied it.
9 | */
10 |
11 | /*
12 | * Here's something you've all been waiting for: the AT&T public domain
13 | * source for getopt(3). It is the code which was given out at the 1985
14 | * UNIFORUM conference in Dallas. I obtained it by electronic mail
15 | * directly from AT&T. The people there assure me that it is indeed
16 | * in the public domain.
17 | *
18 | * There is no manual page. That is because the one they gave out at
19 | * UNIFORUM was slightly different from the current System V Release 2
20 | * manual page. The difference apparently involved a note about the
21 | * famous rules 5 and 6, recommending using white space between an option
22 | * and its first argument, and not grouping options that have arguments.
23 | * Getopt itself is currently lenient about both of these things White
24 | * space is allowed, but not mandatory, and the last option in a group can
25 | * have an argument. That particular version of the man page evidently
26 | * has no official existence, and my source at AT&T did not send a copy.
27 | * The current SVR2 man page reflects the actual behavor of this getopt.
28 | * However, I am not about to post a copy of anything licensed by AT&T.
29 | */
30 |
31 | #include
32 | #include
33 |
34 | #define ERR(s, c) if(opterr){ fprintf(stderr, "%s%s%c\n", argv[0], s, c); }
35 |
36 | int opterr = 1;
37 | int optind = 1;
38 | int optopt;
39 | char *optarg;
40 |
41 | int getopt(int argc, char **argv, char *opts) {
42 | static int sp = 1;
43 | register int c;
44 | register char *cp;
45 |
46 | if(sp == 1) {
47 | if(optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
48 | return -1;
49 | else if(strcmp(argv[optind], "--") == 0) {
50 | optind++;
51 | return -1;
52 | }
53 | }
54 | optopt = c = argv[optind][sp];
55 | if(c == ':' || (cp=strchr(opts, c)) == 0) {
56 | ERR(": illegal option -- ", c);
57 | if(argv[optind][++sp] == '\0') {
58 | optind++;
59 | sp = 1;
60 | }
61 | return('?');
62 | }
63 | if(*++cp == ':') {
64 | if(argv[optind][sp+1] != '\0')
65 | optarg = &argv[optind++][sp+1];
66 | else if(++optind >= argc) {
67 | ERR(": option requires an argument -- ", c);
68 | sp = 1;
69 | return('?');
70 | } else
71 | optarg = argv[optind++];
72 | sp = 1;
73 | } else {
74 | if(argv[optind][++sp] == '\0') {
75 | sp = 1;
76 | optind++;
77 | }
78 | optarg = NULL;
79 | }
80 | return(c);
81 | }
82 |
--------------------------------------------------------------------------------
/test/syntax.bas:
--------------------------------------------------------------------------------
1 | # This is a comment
2 |
3 | # Calling a built-in function
4 | PRINT("Hello World");
5 |
6 | # The parenthesis are optional if you call a function
7 | # at the statement level, so this is equivalent:
8 | PRINT "Hello World";
9 |
10 | # Just be careful: The parenthesis are required at
11 | # the expression level.
12 |
13 | PRINT "`IF` statements:";
14 |
15 | A = input("Input a number:");
16 |
17 | # Regular form of the IF statement:
18 | If A = 1 THEN
19 | BEGIN
20 | PRINT("ONE");
21 | PRINT("SEE ME")
22 | END
23 | ELSE IF A = 2 THEN
24 | PRINT "TWO"
25 | ELSE IF A = 3 THEN
26 | PRINT "THREE"
27 | ELSE
28 | PRINT "Something else..";
29 |
30 | IF A = 4 THEN PRINT "A is four";
31 |
32 | IF A = 5 THEN PRINT "It is five" ELSE PRINT "it is not five";
33 |
34 | PRINT "`WHILE..WEND` loop:";
35 | A = 1;
36 | WHILE A < 5 DO BEGIN
37 | PRINT("A is", A);
38 | A = A + 1
39 | END;
40 |
41 | PRINT "`DO..LOOP...` loop:";
42 | A = 0;
43 | DO
44 | PRINT "A is ", A;
45 | A = A+1
46 | LOOP WHILE A < 5;
47 |
48 | DO
49 | A = A-1;
50 | PRINT "A is ", A
51 | LOOP UNTIL A = 0;
52 |
53 | PRINT "`BREAK` and `CONTINUE` statements:";
54 | A = 0;
55 | WHILE A <= 200 DO
56 | BEGIN
57 | A = A + 1;
58 | if A = 5 then break;
59 | if A = 2 then continue;
60 | PRINT "A is now", A;
61 |
62 | B = 0;
63 | DO
64 | B = B+1;
65 | IF B = 2 THEN continue;
66 | IF B = A THEN BREAK;
67 | PRINT "B is now", B
68 | LOOP UNTIL B > 100
69 | END;
70 |
71 | PRINT "`FOR..TO` loop:";
72 | FOR I = 2 TO 10 STEP 2
73 | PRINT "I is", I;
74 |
75 | PRINT "`FOR..DOWNTO` loop:";
76 | FOR I = 20 DOWNTO 8 STEP -4
77 | PRINT "I is", I;
78 |
79 | PRINT "Simple form:";
80 | FOR I = 1 TO 5 PRINT "I is", I;
81 |
82 | # Arrays are indexed from 1
83 | PRINT "`FOR..IN array` loop:";
84 |
85 | A = [23, 32, 12, 63, 58, 14, 87, 42, 36, 95];
86 |
87 | FOR I IN A
88 | BEGIN
89 | IF A[I] = 12 then continue;
90 | PRINT I, A[I];
91 | IF A[I] = 58 then break
92 | END;
93 |
94 | PRINT "Simple form:";
95 | FOR I IN A PRINT A[I];
96 |
97 | PRINT "`FOR..IN dict` loop:";
98 |
99 | A = { "one":1, "two":2, "three":3, "four":4, "five":5, "six":6 };
100 |
101 | FOR K IN A
102 | BEGIN
103 | #if k = "six" then continue;
104 | PRINT K, A[K]
105 | #if k = "five" then break
106 | END;
107 |
108 | PRINT "Simple form:";
109 | FOR K IN A PRINT K, A[K];
110 |
111 | # FIXME: The 1,2,3 options don't seem to work on Amiga anymore.
112 | PRINT "`SELECT` statement (type 'exit' to quit):";
113 | DO
114 | SELECT INPUT()
115 | CASE 1, 'a', "A":
116 | begin
117 | PRINT "Option 1";
118 | PRINT "Another statement"
119 | end
120 | CASE 2, "b", "B":
121 | PRINT "Option 2"
122 | CASE 3, "c", "C":
123 | PRINT "Option 3"
124 | CASE "foo":
125 | PRINT "bar"
126 | CASE "bar":
127 | PRINT "foo"
128 | CASE "exit":
129 | BREAK
130 | CASE "nothing": # You don't actually need a body
131 | DEFAULT:
132 | PRINT "Default option"
133 | END
134 | LOOP;
135 |
136 | # Declaring a function:
137 | def fac(X)
138 | begin
139 | if x <= 1 then return 1;
140 | return X * fac(X-1)
141 | end;
142 |
143 | # shorter version with one statement:
144 | def sqr(X) return X*X;
145 |
146 | # Calling a function:
147 | print "fac(6) = ", fac(6);
148 | print "sqr(12) = ", sqr(12);
149 |
150 | PRINT "fin";
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WTS - What The Script!?
2 |
3 | A very simple scripting language for simple/old computers.
4 |
5 | # Compiling
6 |
7 | The core of the interpreter is in `wts.c` with the API defined in `wts.h`.
8 |
9 | `main.c` is a program that embeds the interpreter so that it can be run
10 | from the command-line. It also demonstrates the features of the API
11 | and serves as a test program for all the features.
12 |
13 | The program is written to be portable, so it should be sufficient to
14 | copy these files into your project and compile them with your C compiler
15 | of choice.
16 |
17 | `Makefile.gnu` is a makefile for GNU Make that can build with either GCC
18 | or clang.
19 |
20 | Makefiles are provided for some other specific platforms, as described
21 | below.
22 |
23 | ## MS-DOS
24 |
25 | `MAKEFILE.TCC` for Borland Turbo C (v2.01).
26 |
27 | At the top of the makefile, set the `tc_path` variable to where Turbo
28 | C is installed. You can then use the command `make -fMAKEFILE.TCC`
29 | to compile it (_note there's no space after the `-f`_).
30 |
31 | `MAKEFILE.WAT` for Watcom C++ (9.0).
32 |
33 | Run it like so: `wmake /f MAKEFILE.WAT`
34 |
35 | I've only used both under [DOSBox][].
36 |
37 | > [!WARNING]
38 | > If you have trouble compiling under DOS, it might be the case that
39 | > the line endings have been converted to carriage returns rather than
40 | > carriage return-linefeed pairs when the code was committed to git.
41 | >
42 | > `Makefile.gnu` has a `fixdos` target to convert carriage returns to
43 | > fix the line endings in the source files using the `unix2dos` program.
44 | >
45 | > Run `make -fMakefile.gnu fixdos` to fix it.
46 |
47 | [DOSBox]: http://www.dosbox.com
48 |
49 | ## Amiga
50 |
51 | `makefile.vbcc` is for cross-compiling with [VBCC][]. This video
52 | explains how to set up a VBCC environment:
53 |
54 | At some point it did compile natively in a Amiga 500 environment with Aztec C,
55 | based on instructions in [this video](https://youtu.be/UwKPB9Kz9rE), but that
56 | is now deprecated.
57 |
58 | > [!WARNING]
59 | > The `make -f makefile.vbcc debug` build doesn't work anymore. When I try to
60 | > run it in an emulated A500 there's a _stack overflow_ error. I suspect the
61 | > debug build makes the runtime's stack just too big.
62 |
63 | [VBCC]: http://sun.hasenbraten.de/vbcc/
64 |
65 | # Documentation
66 |
67 | The default makefile has a `docs` target. Run `make docs` to generate
68 | `docs.md.html`.
69 |
70 | The makefile uses Awk to extract comments formatted like `/** comment */`
71 | from the source code, and then converts it to HTML.
72 |
73 | # See Also
74 |
75 | * [Sneklang](https://sneklang.org/) - _Python-inspired language for embedded systems_ which
76 | is a more ambitious project than WTS.
77 | * [C Portability Lessons from Weird Machines][weird-machines] talks
78 | about some of the issues I encountered with the 68000 when I tried to
79 | port it to Amiga.
80 | * Bob Nystrom's [lisp2-gc](https://github.com/munificent/lisp2-gc)
81 | taught me how a mark-compact garbage collector works.
82 | * See also his [Jasic](https://github.com/munificent/jasic) BASIC
83 | interpreter in Java that partially inspired this one.
84 | * He also taught me about Pratt parsing. See below.
85 | * - A small BASIC interpreter
86 | for embedded devices, based on the original [TinyBASIC][] from
87 | DrDobbs' Journal.
88 | * Some other small BASIC interpreters
89 | *
90 | * -
91 | The comments say it's from "C Power User's Guide" by Herbert Schildt, 1988.
92 | I found the book on [the Internet Archive](https://archive.org/details/cpowerusersguide00schi_0/mode/2up).
93 | The sourcecode is also [here](https://github.com/generalram/cpowerusers/blob/master/CHAP7.C)
94 | * Some resources on Pratt parsers:
95 | * [Pratt Parsers: Expression Parsing Made Easy ](https://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/) by Munificent
96 | * He also talks about it in [chapter 17 of Crafting Interpreters](https://craftinginterpreters.com/compiling-expressions.html#a-pratt-parser)
97 | * - has a lot of pseudo code and uses
98 | the same terminology that Pratt used in his original paper.
99 | * - very thorough explanation.
100 | Would upvote if I was signed in
101 | * [Demystifying Pratt Parsers](https://martin.janiczek.cz/2023/07/03/demystifying-pratt-parsers.html)
102 | Example code in Elm, but nice diagrams
103 | * [Simple but Powerful Pratt Parsing](https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html),
104 | in Rust, but I wont hold that against it.
105 | * [On Recursive Descent and Pratt Parsing](https://chidiwilliams.com/posts/on-recursive-descent-and-pratt-parsing)
106 | also has a concise implementation
107 | * [Top-Down operator precedence (Pratt) parsing](https://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing)
108 | on Eli Bendersky's blog
109 | * The Fredrik Lundh article he cites is now here:
110 | [Simple Top-Down Parsing in Python](https://11l-lang.org/archive/simple-top-down-parsing/)
111 |
112 | [weird-machines]: https://begriffs.com/posts/2018-11-15-c-portability.html
113 | [TinyBASIC]: https://en.wikipedia.org/wiki/Tiny_BASIC
114 |
--------------------------------------------------------------------------------
/src/io.c:
--------------------------------------------------------------------------------
1 | #ifdef _MSC_VER
2 | # define _CRT_SECURE_NO_WARNINGS
3 | #endif
4 | #include
5 |
6 | /**
7 | * ## Sequential Text File Functions
8 | *
9 | * Functions for working with Sequential Text Files.
10 | *
11 | * These files contain lines, each line containing a series of fields separated
12 | * by commas. The fields themselves can be numeric or strings delimited by double
13 | * quotes.
14 | *
15 | * This is an example of such a file:
16 | *
17 | * ```
18 | * "Orc",9,12,"rusty sword"
19 | * "Cave Troll",12,14,"club"
20 | * ```
21 | *
22 | * These functions are available if the optional **io.c** is included and
23 | * `register_io_funs()` is called after `w_init_stdlib()`.
24 | */
25 |
26 | #include "wts.h"
27 |
28 | #define SEQIO_NO_FLOAT 1
29 | #define SEQIO_IMPL
30 | #include "seqio.h"
31 |
32 | #define NUM_FILES 4
33 |
34 | static SeqIO files[NUM_FILES];
35 |
36 | /**
37 | * `OPEN(filename, mode)`
38 | * : Opens a file named `filename` in the specified mode.
39 | *
40 | * `mode` must be either `'input'` for reading from the file,
41 | * or `'output'` for writing to the file.
42 | *
43 | * It returns a _filenumber_ that is used with the `CLOSE`,
44 | * `READ` and `WRITE` functions in this module.
45 | */
46 | static int io_open(W_runtime *rt, int argc, W_value *argv) {
47 | const char *filename, *mode;
48 | int fileno;
49 | if(argc < 2 || !w_is_string(&argv[0]) || !w_is_string(&argv[1]))
50 | return w_error("%u: OPEN() expects a filename and a mode ['input'|'output'] as arguments", w_current_line(rt));
51 | filename = w_as_string(rt, &argv[0]);
52 | mode = w_as_string(rt, &argv[1]);
53 |
54 | for(fileno = 0; fileno < NUM_FILES; fileno++)
55 | if(!files[fileno].data)
56 | break;
57 | if(fileno >= NUM_FILES)
58 | return w_error("%u: too many OPEN()ed files", w_current_line(rt));
59 |
60 | if(!strcmp(mode, "input")) {
61 | if(!seq_infile(&files[fileno], filename))
62 | return w_error("%u: error opening input file%s: %s\n", w_current_line(rt), filename, seq_error(&files[fileno]));
63 | } else if(!strcmp(mode, "output")) {
64 | if(!seq_outfile(&files[fileno], filename))
65 | return w_error("%u: error opening input file %s: %s\n", w_current_line(rt), filename, seq_error(&files[fileno]));
66 | } else
67 | return w_error("%u: mode must be 'input' or 'output' in OPEN()", w_current_line(rt));
68 |
69 | return w_return_number(rt, fileno);
70 | }
71 |
72 | /**
73 | * `CLOSE(filenumber)`
74 | * : Closes a file.
75 | *
76 | * `filenumber` is the value returned by the `OPEN()` call that opened the file.
77 | */
78 | static int io_close(W_runtime *rt, int argc, W_value *argv) {
79 | int n;
80 | if(argc < 1)
81 | return w_error("%u: CLOSE() expects a file number as argument", w_current_line(rt));
82 | n = w_as_number(rt, &argv[0]);
83 | if(n < 0 || n >= NUM_FILES)
84 | return w_error("%u: Invalid file number in call to CLOSE()", w_current_line(rt));
85 | seq_close(&files[n]);
86 | return w_return_number(rt, n);
87 | }
88 |
89 | /**
90 | * `WRITE(filenumber, val1, val2, ...)`
91 | * : Writes a series of values `val1`, `val2`, etc to a sequential file.
92 | *
93 | * `filenumber` is the value returned by the `OPEN()` call that opened the file.
94 | */
95 | static int io_write(W_runtime *rt, int argc, W_value *argv) {
96 | int i, n;
97 |
98 | if(argc < 1)
99 | return w_error("%u: WRITE() expects a file number as first argument", w_current_line(rt));
100 | n = w_as_number(rt, &argv[0]);
101 | if(n < 0 || n >= NUM_FILES)
102 | return w_error("%u: Invalid file number in call to WRITE()", w_current_line(rt));
103 |
104 | for(i = 1; i < argc; i++) {
105 | if(w_is_number(&argv[i])) {
106 | seq_write_int(&files[n], w_as_number(rt, &argv[i]));
107 | } else if(w_is_string(&argv[i])) {
108 | seq_write(&files[n], w_as_string(rt, &argv[i]));
109 | }
110 | if(seq_error(&files[n]))
111 | return w_error("%u: Error in WRITE(): %s", w_current_line(rt), seq_error(&files[n]));
112 | }
113 | seq_endl(&files[n]);
114 | return w_return_number(rt, argc - 1);
115 | }
116 |
117 | /**
118 | * `READ(filenumber, @var1, @var2, ...)`
119 | * : Reads a series of values from a sequential file into the variables `var1`, `var2`, etc.
120 | *
121 | * `filenumber` is the value returned by the `OPEN()` call that opened the file.
122 | */
123 | static int io_read(W_runtime *rt, int argc, W_value *argv) {
124 | int i, n;
125 |
126 | if(argc < 1)
127 | return w_error("%u: READ() expects a file number as first argument", w_current_line(rt));
128 | n = w_as_number(rt, &argv[0]);
129 | if(n < 0 || n >= NUM_FILES)
130 | return w_error("%u: Invalid file number in call to READ()", w_current_line(rt));
131 |
132 | for(i = 1; i < argc; i++) {
133 | if(w_is_string(&argv[i])) {
134 | const char *value = seq_read(&files[n]);
135 | w_set_variable_string(rt, w_as_string(rt, &argv[i]), value);
136 | } else {
137 | return w_error("%u: READ() expects a varname in position %d", w_current_line(rt), i + 1);
138 | }
139 | if(seq_error(&files[n]))
140 | return w_error("%u: Error in READ(): %s", w_current_line(rt), seq_error(&files[n]));
141 | }
142 | return w_return_number(rt, argc - 1);
143 | }
144 |
145 | int register_io_funs( void ) {
146 | int i;
147 | for(i = 0; i < NUM_FILES; i++) {
148 | files[i].data = NULL;
149 | }
150 | if(!w_add_function("OPEN", io_open)) return 0;
151 | if(!w_add_function("CLOSE", io_close)) return 0;
152 | if(!w_add_function("WRITE", io_write)) return 0;
153 | if(!w_add_function("READ", io_read)) return 0;
154 | return 1;
155 | }
156 |
157 |
--------------------------------------------------------------------------------
/msvc/WTS.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | 16.0
34 | Win32Proj
35 | {11d94ba6-22c4-48dc-941a-f60dd43d64b4}
36 | WTS
37 | 10.0
38 |
39 |
40 |
41 | Application
42 | true
43 | v142
44 | Unicode
45 |
46 |
47 | Application
48 | false
49 | v142
50 | true
51 | Unicode
52 |
53 |
54 | Application
55 | true
56 | v142
57 | Unicode
58 |
59 |
60 | Application
61 | false
62 | v142
63 | true
64 | Unicode
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | true
86 |
87 |
88 | false
89 |
90 |
91 | true
92 |
93 |
94 | false
95 |
96 |
97 |
98 | Level3
99 | true
100 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
101 | true
102 |
103 |
104 | Console
105 | true
106 |
107 |
108 |
109 |
110 | Level3
111 | true
112 | true
113 | true
114 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
115 | true
116 |
117 |
118 | Console
119 | true
120 | true
121 | true
122 |
123 |
124 |
125 |
126 | Level3
127 | true
128 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
129 | true
130 | ..\include;%(AdditionalIncludeDirectories)
131 |
132 |
133 | Console
134 | true
135 |
136 |
137 |
138 |
139 | Level3
140 | true
141 | true
142 | true
143 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
144 | true
145 |
146 |
147 | Console
148 | true
149 | true
150 | true
151 |
152 |
153 |
154 |
155 |
156 |
--------------------------------------------------------------------------------
/src/main.c:
--------------------------------------------------------------------------------
1 | #define _CRT_SECURE_NO_WARNINGS
2 | #include
3 | #include
4 | #include
5 | #include
6 |
7 | #if defined(__GNUC__)
8 | # include
9 | #else
10 | # include "getopt.h"
11 | #endif
12 |
13 | #ifndef SETENV
14 | #define SETENV 0
15 | #endif
16 |
17 | #include "wts.h"
18 |
19 | /* defined in io.c */
20 | int register_io_funs(void);
21 |
22 | /**
23 | * ## Additional Functions
24 | *
25 | * These additional functions are available in the standalone
26 | * WTS interpreter.
27 | *
28 | * `SYSTEM(cmd)`
29 | * : Runs the shell command `cmd`.
30 | *
31 | * It uses the `system()` function in the C standard library
32 | * so its exact behaviour is dependent on the particular
33 | * operating system.
34 | *
35 | * `GETENV(var)`
36 | * : Retrieves the value of the environment variable `var`.
37 | *
38 | * `SETENV(var, val)`
39 | * : Sets the value of the environment variable `var` to `val`
40 | * (on platforms that support it).
41 | *
42 | */
43 | /* (they're declared at the bottom of this file) */
44 | static int bif_system(W_runtime *rt, int argc, W_value *argv);
45 | static int bif_getenv(W_runtime *rt, int argc, W_value *argv);
46 | static int bif_setenv(W_runtime *rt, int argc, W_value *argv);
47 |
48 | static void usage(const char *name, FILE *f, int more) {
49 | /* Why, yes, I did have fun with figlets. */
50 |
51 | if(!more) {
52 | fprintf(f, " __ ____________ \n");
53 | fprintf(f, " \\ \\ / /_ _/ ___| \n");
54 | fprintf(f, " \\ \\ /\\ / / | | \\___ \\ \n");
55 | fprintf(f, " \\ V V / | | ___) | \n");
56 | fprintf(f, " \\_/\\_/ |_| |____/ \n\n");
57 | }
58 |
59 | fprintf(f, " What The Script!?\n\n");
60 | fprintf(f, "Usage: %s [options] infile\n", name);
61 |
62 | if(more) {
63 | fprintf(f, "Where [options] can be:\n");
64 | fprintf(f, " -b bfile : read Bytecode from `bfile` rather than compiling\n");
65 | fprintf(f, " -c cfile : write Compiled bytecode to `cfile`\n");
66 | fprintf(f, " -D : omit Debug info from bytecode\n");
67 | fprintf(f, " -i : show Information about the bytecode\n");
68 | fprintf(f, " -E : don't Execute the bytecode\n");
69 | fprintf(f, " -C func : Call function `func` after the script executed\n");
70 | fprintf(f, " -s var=val : Sets variable `var` to value `val` before executing\n");
71 | fprintf(f, " -r var : Retrieves variable `var` after executing\n");
72 | fprintf(f, " -u : dUmp variables' contents after executing\n");
73 | fprintf(f, " -v : Verbose output\n");
74 | fprintf(f, " -I : dump development Information about the interpreter\n");
75 | fprintf(f, " -R : Resumes if the script ends in a yielded state\n");
76 | fprintf(f, " -Y n : Yields the interpreter after `n` cycles\n");
77 | fprintf(f, " -S seed : sets the Seed of srand()\n");
78 | fprintf(f, " -M file : Save the VM's RAM to a file after executing\n");
79 | fprintf(f, " -m file : Restore the VM's RAM from a file before executing\n");
80 | fprintf(f, " -t : Displays some technical information\n");
81 | fprintf(f, " -h : displays this Help message\n");
82 | } else {
83 | fprintf(f, "Type:\n\t%s -h\nfor more information\n", name);
84 | }
85 | }
86 |
87 | static struct W_bytecode bytecodes;
88 | static struct W_runtime runtime;
89 |
90 | #if defined(__VBCC__) && !defined(NDEBUG)
91 | /* VBCC helped track down my problems with some
92 | functions using up too much stack space */
93 | extern size_t __stack_usage;
94 | #endif
95 |
96 | #if 0
97 | /* Test to confirm whether `W_rand_fun` works */
98 | #define XKCD
99 | static int rand_xkcd() {
100 | return 4;
101 | }
102 | #endif
103 |
104 | int main(int argc, char *argv[]) {
105 | FILE *file;
106 |
107 | int c, execute = 1, info = 0, variables = 0, verbose = 0, resume = 0, cycles = 0;
108 | char *bfile = NULL, *cfile = NULL;
109 | char *save_ram_file = NULL, *load_ram_file = NULL;
110 |
111 | #define MAX_CALLS 8
112 | const char *calls[MAX_CALLS];
113 | int ncalls = 0, i;
114 |
115 | #define MAX_CHK_VARS 8
116 | struct {char *var; enum {SET, GET} op; } chkvars[MAX_CHK_VARS];
117 | int nchkvars = 0;
118 | unsigned int seed = (unsigned int)time(NULL);
119 |
120 | while ((c = getopt(argc, argv, "b:c:DEiuvIC:s:r:hRY:S:M:m:t")) != -1) {
121 | switch (c) {
122 | case 'b': bfile = optarg; break;
123 | case 'c': cfile = optarg; break;
124 | case 'D': w_debug_info = 0; break;
125 | case 'E': execute = 0; break;
126 | case 'i': info = 1; break;
127 | case 'C':
128 | if(ncalls == MAX_CALLS) {
129 | w_error("too many calls (max: %d)", MAX_CALLS);
130 | return 1;
131 | }
132 | calls[ncalls++] = optarg;
133 | break;
134 | case 's':
135 | case 'r':
136 | if(nchkvars == MAX_CHK_VARS) {
137 | w_error("too many variables to set/retrieve (max: %d)", MAX_CHK_VARS);
138 | return 1;
139 | }
140 | chkvars[nchkvars].var = optarg;
141 | chkvars[nchkvars].op = c == 's' ? SET : GET;
142 | nchkvars++;
143 | break;
144 | case 'u': variables = 1; break;
145 | case 'v': verbose++; break;
146 | case 'R': resume = 1; break;
147 | case 'Y': cycles = atoi(optarg); break;
148 | case 'S': seed = strtoul(optarg, NULL, 0); break;
149 | case 'I': w_details(); return 0;
150 | case 'M': save_ram_file = optarg; break;
151 | case 'm': load_ram_file = optarg; break;
152 | case 't':
153 | printf("sizeof(w_op_t) = %u\n", (unsigned int)sizeof(w_op_t));
154 | printf("sizeof(w_int) = %u\n", (unsigned int)sizeof(w_int));
155 | printf("sizeof(w_uint) = %u\n", (unsigned int)sizeof(w_uint));
156 | printf("sizeof(w_ptr) = %u\n", (unsigned int)sizeof(w_ptr));
157 | printf("WTS_UINT_MAX = %X\n", WTS_UINT_MAX);
158 | return 0;
159 | case 'h': usage(argv[0], stdout, 1); return 0;
160 | case '?':
161 | default: usage(argv[0], stderr, 0); return 1;
162 | }
163 | }
164 |
165 | if (verbose)
166 | printf("seed: %u\n", seed);
167 |
168 | srand(seed);
169 | w_init_stdlib();
170 | if(!register_io_funs()
171 | || !w_add_function("SYSTEM", bif_system)
172 | || !w_add_function("GETENV", bif_getenv)
173 | || !w_add_function("SETENV", bif_setenv)) {
174 | w_error("Unable to register built-in functions");
175 | return 1;
176 | }
177 |
178 | #ifdef XKCD
179 | W_rand_fun = rand_xkcd;
180 | #endif
181 |
182 | if(!bfile) {
183 | if(optind < argc) {
184 | const char *filename = argv[optind++];
185 | file = fopen(filename, "r");
186 | if(!file) {
187 | w_error("unable to open %s", filename);
188 | return 1;
189 | }
190 | if(!w_compile_file(&bytecodes, file)) {
191 | w_error("unable to compile");
192 | fclose(file);
193 | return 1;
194 | }
195 | fclose(file);
196 | if(verbose) printf("* Script %s compiled\n", filename);
197 | } else {
198 | usage(argv[0], stderr, 0);
199 | return 1;
200 | }
201 | } else {
202 | if(!w_load_bytecode(&bytecodes, bfile)) {
203 | w_error("unable to load bytecode from %s", bfile);
204 | return 1;
205 | }
206 | if(verbose) printf("* bytecode loaded from %s\n", bfile);
207 | }
208 |
209 | if(info)
210 | w_show_info(&bytecodes);
211 |
212 | if(cfile) {
213 | if(!w_save_bytecode(&bytecodes, cfile)) {
214 | w_error("unable to write bytecode to %s", cfile);
215 | return 1;
216 | }
217 | if(verbose) printf("* bytecode written to %s\n", cfile);
218 | }
219 |
220 | if(!w_init_runtime(&runtime, &bytecodes)) {
221 | w_error("couldn't create runtime :(");
222 | return 1;
223 | }
224 |
225 | if(load_ram_file) {
226 | FILE *f = fopen(load_ram_file, "rb");
227 | long size;
228 | unsigned char *readbuf;
229 | if(!f) {
230 | w_error("unable to open %s for input", load_ram_file);
231 | return 1;
232 | }
233 | fseek(f, 0, SEEK_END);
234 | size = ftell(f);
235 | rewind(f);
236 | readbuf = malloc(size);
237 | if(fread(readbuf, size, 1, f) != 1) {
238 | w_error("unable to load RAM from %s", load_ram_file);
239 | return 1;
240 | }
241 | w_set_ram(&runtime, readbuf, (w_ptr)size);
242 | free(readbuf);
243 | fclose(f);
244 | if(verbose) printf("RAM loaded from %s\n", load_ram_file);
245 | }
246 |
247 | w_dim(&runtime, argc - optind);
248 | for(i = 0; optind < argc; optind++, i++) {
249 | if(verbose) printf("args[%d] = %s\n", i, argv[optind]);
250 | if(!w_push_string(&runtime, argv[optind])
251 | || !w_array_set(&runtime, i)) {
252 | w_error("unable to set args[%d] = %s", i, argv[optind]);
253 | }
254 | }
255 | w_set_variable(&runtime, "args");
256 |
257 | #if 0
258 | w_dict(&runtime, 8);
259 | w_push_string(&runtime, "A Value");
260 | w_dict_set(&runtime, "first");
261 | w_push_string(&runtime, "Another Value");
262 | w_dict_set(&runtime, "second");
263 | {
264 | W_value *val = w_dict_get(&runtime, "second");
265 | printf("@@@ second: `%s`\n", w_as_string(&runtime, val));
266 | w_pop(&runtime);
267 | }
268 | w_push_string(&runtime, "Yet Another Value");
269 | w_dict_set(&runtime, "third");
270 | w_set_variable(&runtime, "dict_demo");
271 | #endif
272 |
273 | for(i = 0; i < nchkvars; i++) {
274 | int result;
275 | char *var, *val;
276 | if(chkvars[i].op != SET) continue;
277 | var = chkvars[i].var;
278 | val = strchr(var, '=');
279 | if(val) {
280 | *val = '\0';
281 | val++;
282 | } else
283 | val = "";
284 | if(verbose) printf("Set `%s` to `%s`\n", var, val);
285 | result = w_set_variable_string(&runtime, var, val);
286 | if(!result) {
287 | w_error("Unable to set variable `%s` to value `%s`", var, val);
288 | return 1;
289 | }
290 | }
291 |
292 | if(verbose) printf("===========\n");
293 | if(execute) {
294 |
295 | if(cycles) {
296 | runtime.max_cycles = cycles;
297 | }
298 |
299 | if(!w_execute(&runtime)) {
300 | w_error("%u: executing failed :(", w_current_line(&runtime));
301 | return 1;
302 | }
303 |
304 | if(resume) {
305 | int rcount = 0;
306 | while(w_yielded(&runtime)) {
307 | rcount++;
308 | if(verbose)
309 | printf("Resume %d:\n", rcount);
310 |
311 | /* You can make the YIELD() function return anything */
312 | w_return_number(&runtime, rcount);
313 |
314 | if(!w_resume(&runtime)) {
315 | w_error("%u: resume failed :(", w_current_line(&runtime));
316 | return 1;
317 | }
318 | }
319 | } else if(w_yielded(&runtime) && verbose) {
320 | printf("script ended in a yielded state\n");
321 | }
322 | }
323 |
324 | for(i = 0; i < ncalls; i++) {
325 | int res;
326 | if(verbose) printf("gosub %s:\n", calls[i]);
327 | /* Call the subroutine with two integers and a string parameter
328 | (for demonstration purposes) */
329 | res = w_call(&runtime, calls[i], "iis", i, 42, "a string");
330 | if(res < 0) {
331 | w_error("no subroutine named `%s`", calls[i]);
332 | } else if(!res) {
333 | w_error("%u: error in subroutine `%s`", w_current_line(&runtime), calls[i]);
334 | return 1;
335 | }
336 | if(verbose) {
337 | W_value *ret = w_get_return(&runtime);
338 | printf("%s returned: %s\n", calls[i], w_as_string_cvrt(&runtime, ret));
339 | }
340 | }
341 |
342 | if(verbose) printf(" ~ fin ~\n");
343 |
344 | if(variables) {
345 | /* gc_strings(&runtime); */
346 | printf("-------\nvariables:\n");
347 | w_show_variables(&runtime);
348 | }
349 |
350 | for(i = 0; i < nchkvars; i++) {
351 | char *var;
352 | const char *val;
353 | if(chkvars[i].op != GET) continue;
354 | var = chkvars[i].var;
355 | val = w_get_variable_string(&runtime, var);
356 | printf("%s = '%s'\n", var, val);
357 | }
358 |
359 | if(save_ram_file) {
360 | FILE *f = fopen(save_ram_file, "wb");
361 | w_ptr size;
362 | const unsigned char *ram;
363 | if(!f) {
364 | w_error("unable to open %s for output", save_ram_file);
365 | return 1;
366 | }
367 | ram = w_get_ram(&runtime, &size);
368 | fwrite(ram, size, 1, f);
369 | fclose(f);
370 | if(verbose) printf("RAM saved to %s\n", save_ram_file);
371 | }
372 |
373 | #if defined(__VBCC__) && !defined(NDEBUG)
374 | printf("stack used: %lu\n",(unsigned long)__stack_usage);
375 | #endif
376 |
377 | return 0;
378 | }
379 |
380 | static int bif_system(W_runtime *rt, int argc, W_value *argv) {
381 | const char *str;
382 | if(argc == 0)
383 | return w_return_number(rt, system(NULL));
384 | if(argc != 1 || !w_is_string(&argv[0]))
385 | return w_error("%u: SYSTEM() expects a string argument", w_current_line(rt));
386 | str = w_as_string(rt, &argv[0]);
387 | return w_return_number(rt, system(str));
388 | }
389 |
390 | static int bif_getenv(W_runtime *rt, int argc, W_value *argv) {
391 | const char *env;
392 | if(argc != 1 || !w_is_string(&argv[0]))
393 | return w_error("%u: GETENV() expects a string argument", w_current_line(rt));
394 | env = w_as_string(rt, &argv[0]);
395 | env = getenv(env);
396 | if(!env)
397 | return w_return_string(rt, "");
398 | return w_return_string(rt, env);
399 | }
400 |
401 | static int bif_setenv(W_runtime *rt, int argc, W_value *argv) {
402 | #if SETENV
403 | const char *env, *val;
404 | if(argc != 2 || !w_is_string(&argv[0]))
405 | return w_error("%u: SETENV() expects two arguments", w_current_line(rt));
406 | env = w_as_string(rt, &argv[0]);
407 | val = w_as_string_cvrt(rt, &argv[1]);
408 | return setenv(env, val, 1) == 0;
409 | #else
410 | /* TODO: Some compilers have a `putenv()` that can be used... */
411 | (void)argc, (void)argv;
412 | return w_error("%u: SETENV() not supported", w_current_line(rt));
413 | #endif
414 | }
415 |
--------------------------------------------------------------------------------
/include/wts.h:
--------------------------------------------------------------------------------
1 | #ifndef WTS_H
2 | #define WTS_H
3 |
4 | /**
5 | * WTS - What the script!?
6 | * =======================
7 | *
8 | * ![toc]
9 | *
10 | */
11 |
12 | #ifdef __cplusplus
13 | extern "C" {
14 | #endif
15 |
16 | /**
17 | * C API
18 | * =====
19 | *
20 | * Definitions
21 | * -----------
22 | *
23 | * ### Constants
24 | *
25 | * These constants can be set when compiling the module to control some aspects of the
26 | * interpreter.
27 | *
28 | * * `W_MAX_CODE` - Number of bytes reserved for instructions in the bytecode.
29 | * * `W_MAX_CONST` - Number of bytes reserved for constants in the bytecode,
30 | * like string literals, GOTO labels, variable names and line numbers.
31 | * * `W_MAX_ROM` - Number of bytes required for ROM: `W_MAX_CODE` + `W_MAX_CONST`
32 | * * `W_MAX_RAM` - Bytes of RAM available to the interpreter. The stack and variables
33 | * are stored in a buffer of this size.
34 | * * `W_STR_SIZE` - Maximum size of strings that the interpreter can handle,
35 | * as well as the size of the buffers that will hold strings during
36 | * parsing.
37 | */
38 |
39 | #define W_MAX_CODE 4096
40 | #define W_MAX_CONST 4096
41 | #define W_MAX_ROM (W_MAX_CODE + W_MAX_CONST)
42 | #define W_MAX_RAM 16384
43 | #define W_STR_SIZE 256
44 |
45 | /**
46 | * ### Typedefs
47 | *
48 | * `w_op_t`
49 | * : Type for the _opcode_ part for each bytecode instruction.
50 | * It should be at least two bytes to keep the instructions word aligned.
51 | * For instructions with small operands, the high byte can contain the operand.
52 | * `w_int`
53 | * : Signed integer
54 | * `w_uint`
55 | * : Unsigned integer type
56 | * `w_ptr`
57 | * : _Pointer_ type - an unsigned integer index into either the VM's ROM or RAM
58 | *
59 | * `typedef enum {WV_NUM, WV_DSTR, WV_CSTR, WV_ARR, WV_DICT, WV_CONS, WV_PTR} W_type`
60 | * : Types of variables/constants stored in the interpreter.
61 | *
62 | * `WV_NUM` is for numeric types.
63 | *
64 | * `WV_DSTR` is for dynamic strings that are allocated in the interpreter's RAM and
65 | * is managed by the garbage collector. `WV_CSTR` is for constant strings in the
66 | * bytecode that are not garbage collected.
67 | *
68 | * `WV_ARR` is used for array values.
69 | *
70 | * `WV_DICT` is used for dictionary objects values.
71 | *
72 | * `WV_CONS` is used internally to store lists.
73 | *
74 | * `WV_PTR` is a pointer type that is only used internally (It is actually an
75 | * index into either the interpreter's RAM or ROM, depending on context).
76 | */
77 |
78 | #ifndef WTS_32BIT
79 | # define WTS_32BIT 0
80 | #endif
81 |
82 | typedef unsigned short w_op_t;
83 |
84 | #if WTS_32BIT
85 | typedef signed int w_int;
86 | typedef unsigned int w_uint;
87 | typedef unsigned int w_ptr;
88 | typedef signed short w_word;
89 | #else
90 | typedef signed short w_int;
91 | typedef unsigned short w_uint;
92 | typedef unsigned short w_ptr;
93 | #endif
94 | #define WTS_UINT_MAX ((w_uint)(~0))
95 |
96 | typedef enum {WV_NUM, WV_DSTR, WV_CSTR, WV_ARR, WV_DICT, WV_CONS, WV_PTR} W_type;
97 |
98 | /**
99 | * ### Structures
100 | *
101 | * #### `typedef struct W_bytecode W_bytecode`
102 | *
103 | * Stores the byte code that has been compiled.
104 | */
105 | typedef struct W_bytecode {
106 | unsigned char rom[W_MAX_ROM];
107 | w_uint romsize;
108 | } W_bytecode;
109 |
110 | /**
111 | * #### `typedef struct W_value W_value`
112 | *
113 | * Variables and values stored in the VM
114 | */
115 | typedef struct W_value {
116 | /* W_type type -> too many bytes on modern systems, so use w_uint instead. */
117 | w_uint type;
118 | union {
119 | w_int n;
120 | w_ptr p;
121 | w_ptr sp;
122 | w_ptr ap;
123 | } v;
124 | } W_value;
125 |
126 | /**
127 | * #### `enum W_rt_state`
128 | *
129 | * State the interpreter could be in:
130 | *
131 | * * `W_STATE_OK` - interpreter is ready
132 | * * `W_STATE_HALTED` - interpreter reached the end of a script
133 | * * `W_STATE_YIELDED` - interpreter is in a _yielded_ state.
134 | * * `W_STATE_PREEMPT` - interpreter was yielded because the script
135 | * had too many instructions to execute. See the `max_cycles`
136 | * member of `W_runtime`
137 | */
138 | enum W_rt_state {
139 | W_STATE_OK = 0,
140 | W_STATE_HALTED,
141 | W_STATE_YIELDED,
142 | W_STATE_PREEMPT
143 | };
144 |
145 | /**
146 | * #### `typedef struct W_runtime W_runtime`
147 | *
148 | * Runtime data structure of the virtual machine.
149 | *
150 | * It has these members:
151 | */
152 | typedef struct W_runtime {
153 |
154 | /** * `W_bytecode *bc` - the current bytecode being executed */
155 | W_bytecode *bc;
156 |
157 | /** * `unsigned char ram[W_MAX_RAM]` - the RAM of the virtual machine */
158 | unsigned char ram[W_MAX_RAM];
159 |
160 | unsigned char *code;
161 | char *meta;
162 |
163 | w_uint ncode;
164 | w_ptr bump, heap;
165 |
166 | W_value *variables;
167 | W_value *stack;
168 |
169 | W_value *globals;
170 |
171 | /** * `w_ptr sp, fp, bp, pc` - Stack pointer, frame pointer, base pointer and program counter registers of the VM */
172 | w_ptr sp, fp, bp, pc;
173 |
174 | /** * `enum W_rt_state state` - the state of the interpreter. See above. */
175 | enum W_rt_state state;
176 |
177 | /** * `w_uint max_cycles` - maximum number of cycles the interpreter should run */
178 | w_uint max_cycles;
179 |
180 | } W_runtime;
181 |
182 | /**
183 | * Global variables
184 | * ----------------
185 | *
186 | * * `extern int w_debug_info;`
187 | *
188 | * If non-zero (default) the compiler will add line number information.
189 | *
190 | * Line number information is required by the `w_current_line()` function
191 | * (described in the Handling errors subsection below) to provide accurate error
192 | * messages, but it increases the size of the bytecode slightly.
193 | */
194 | extern int w_debug_info;
195 |
196 | /**
197 | * * `extern int (*W_rand_fun)()`
198 | *
199 | * The function that will be used to generate random numbers for the
200 | * `RND()` built-in function. The default will just use `rand()` from
201 | * the C library.
202 | */
203 | extern int (*W_rand_fun)();
204 |
205 | /**
206 | * API functions
207 | * -------------
208 | *
209 | * ### Output functions
210 | *
211 | * * `typedef void (*W_output_fun)(const char *)`
212 | * * `extern W_output_fun w_print_fun`
213 | * * `extern W_output_fun w_error_fun`
214 | * * `int w_printf(const char *fmt, ...)`
215 | *
216 | * `w_print_fun` is a pointer to a function that will be used for
217 | * printing text. `w_error_fun` is a pointer to a function that
218 | * will be used for error reporting.
219 | *
220 | * WTS provides default implementations for `w_print_fun` and
221 | * `w_error_fun` that just writes the text to `stdout` and `stderr`
222 | * respectively.
223 | *
224 | * `w_printf` writes `printf`-style formatted text through
225 | * `w_print_fun`.
226 | *
227 | * `w_error_fun` is used by the the `w_error()` function described
228 | * in the Handling errors subsection
229 | */
230 | typedef void (*W_output_fun)(const char *);
231 | extern W_output_fun w_print_fun;
232 | extern W_output_fun w_error_fun;
233 |
234 | int w_printf(const char *fmt, ...);
235 |
236 | /**
237 | * ### Compiling scripts to bytecode
238 | *
239 | * * `int w_compile_string(W_bytecode *bc, const char *str)`
240 | * * `int w_compile_file(W_bytecode *bc, FILE *file)`
241 | * * `int w_compile_custom(W_bytecode *bc, int (*get_char)(void *), void *data)`
242 | * * `void w_show_info(W_bytecode *bc)`
243 | * * `void w_details()`
244 | *
245 | * `w_compile_string`and `w_compile_file` compiles a script from a string `str`
246 | * or a file `file`, respectively, and stores the compiled bytecode in `bc`.
247 | *
248 | * `w_compile_custom` allows you to specify a custom function `get_char` to load
249 | * the script from an arbitrary source. This `get_char` function will be called
250 | * with the `data` parameter by the parser every time it needs a character from
251 | * the script. The `get_char` function should return 0 when it reaches the end of
252 | * the script.
253 | *
254 | * `w_compile_string`and `w_compile_file` actually uses `w_compile_custom` internally
255 | * so you can look at their implementations to see how to use `w_compile_custom`.
256 | *
257 | * They return 1 on success and 0 on failure, in which case the error message
258 | * would be written through `w_error()`.
259 | *
260 | * `w_show_info` writes information about compiled bytecode through `w_print_fun`.
261 | *
262 | * `w_details` writes information about the interpreter through `w_print_fun`. It is
263 | * mostly internal information about how the interpreter was compiled for development
264 | * purposes.
265 | */
266 | int w_compile_string(W_bytecode *bc, const char *str);
267 | #ifdef EOF /* is #included? */
268 | int w_compile_file(W_bytecode *bc, FILE *file);
269 | #endif
270 | int w_compile_custom(W_bytecode *bc, int (*get_char)(void *), void *data);
271 |
272 | void w_show_info(W_bytecode *bc);
273 |
274 | void w_details();
275 |
276 | /**
277 | * ### Saving and loading bytecode
278 | *
279 | * * `int w_save_bytecode(const W_bytecode *bc, const char *filename)`
280 | * * `int w_load_bytecode(W_bytecode *bc, const char *filename)`
281 | *
282 | * `w_save_bytecode` saves a bytecode object `bc` to a file named `filename`.
283 | *
284 | * `w_load_bytecode` loads bytecode from a file named `filename` into `bc`.
285 | *
286 | * Both returns 1 on success and 0 on failure, in which case an error message
287 | * would be written through `w_error()`.
288 | */
289 | int w_save_bytecode(const W_bytecode *bc, const char *filename);
290 | int w_load_bytecode(W_bytecode *bc, const char *filename);
291 |
292 | /**
293 | * ### Executing a script
294 | * These functions are used to execute the bytecode:
295 | *
296 | * * `int w_init_runtime(W_runtime *rt, W_bytecode *bc)`
297 | * * `int w_execute(W_runtime *rt)`
298 | * * `int w_yield(W_runtime *rt)`
299 | * * `int w_yielded(W_runtime *rt)`
300 | * * `int w_resume(W_runtime *rt)`
301 | * * `int w_show_variables(W_runtime *rt)`
302 | * * `const char *w_get_ram(W_runtime *rt, w_ptr *size)`
303 | * * `void w_set_ram(W_runtime *rt, const char *ram, w_ptr size)`
304 | *
305 | * A `W_runtime` object is required to store the variables and stack and
306 | * of the virtual machine. It is initialised through `w_init_runtime`, which
307 | * also takes the bytecode `bc`
308 | *
309 | * `w_execute` then runs the bytecode. It returns 1 if the script ran to completion,
310 | * or 0 if an error occured.
311 | *
312 | * It returns -1 if the subroutine couldn't be found, 0 if an error occurred,
313 | * or 1 on success.
314 | *
315 | * `w_show_variables` will print the names and values of all the variables in
316 | * the runtime through the print function `w_print_fun`.
317 | *
318 | * `w_get_ram` retrieves a pointer to the VM's ram. The `size` parameter will contain
319 | * the size of the memory. `w_set_ram` fills the VM's memory with the bytes pointed to
320 | * by the `ram` parameter. These functions can be used to save and restore the
321 | * state of the VM
322 | *
323 | * Care must be taken because it doesn't concern itself with the state of the other
324 | * VM registers.
325 | */
326 | int w_init_runtime(W_runtime *rt, W_bytecode *bc);
327 |
328 | int w_execute(W_runtime *rt);
329 |
330 | int w_yield(W_runtime *rt);
331 |
332 | int w_yielded(W_runtime *rt);
333 |
334 | int w_resume(W_runtime *rt);
335 |
336 | int w_show_variables(W_runtime *rt);
337 |
338 | const unsigned char *w_get_ram(W_runtime *rt, w_ptr *size);
339 |
340 | void w_set_ram(W_runtime *rt, const unsigned char *ram, w_ptr size);
341 |
342 | /**
343 | * ### Calling functions in a script
344 | *
345 | * * `typedef struct W_caller W_caller`
346 | * * `int w_start_call(W_runtime *rt, const char *func, W_caller *caller)`
347 | * * `W_value *w_push(W_runtime *rt)`
348 | * * `void w_call_restore(W_runtime *rt, W_caller *caller)`
349 | * * `int w_do_call(W_runtime *rt, W_caller *caller)`
350 | * * `int w_call(W_runtime *rt, const char *func, const char *argspec, ...)`
351 | * * `int w_calla(W_runtime *rt, const char *func, int argc, W_value *argv)`
352 | *
353 | * `w_start_call()` gets the interpreter ready to execute a call. It searches
354 | * the bytecode loaded into `rt` for a function named `func` and saves the
355 | * registers into the structure pointed to by `caller`.
356 | *
357 | * The next step is to push all the parameters for the func onto the stack.
358 | * `w_push()` will push a parameter with value 0 onto the stack. You can change
359 | * this value to the actual value of the argument.
360 | *
361 | * When the parameters have been pushed, use `w_do_call()` to execute the func.
362 | *
363 | * `w_call_restore()` restores the registers. It is called by `w_do_call()`
364 | * automatically, so doesn't need to be called under normal circumstances. It is
365 | * provided to abort the call if an error condition occurs while pushing the
366 | * arguments onto the stack.
367 | *
368 | * `w_call()` and `w_calla()` are convenience functions that wrap around these
369 | * functions to simplify calling functions.
370 | *
371 | */
372 |
373 | typedef struct W_caller {
374 | w_ptr dest, is_bif;
375 | w_ptr savsp, savbp, savfp, savpc;
376 | } W_caller;
377 |
378 | int w_start_call(W_runtime *rt, const char *func, W_caller *caller);
379 |
380 | W_value *w_push(W_runtime *rt);
381 |
382 | /* TODO: Document these... */
383 | W_value *w_push_value(W_runtime *rt, W_value *val);
384 |
385 | W_value *w_push_string(W_runtime *rt, const char *str);
386 |
387 | W_value *w_push_number(W_runtime *rt, w_int num);
388 |
389 | W_value *w_pop(W_runtime *rt);
390 |
391 | /* w_dim() and w_dict() actually belong with w_push() */
392 |
393 | void w_call_restore(W_runtime *rt, W_caller *caller);
394 |
395 | int w_do_call(W_runtime *rt, W_caller *caller);
396 |
397 | int w_call(W_runtime *rt, const char *func, const char *argspec, ...);
398 |
399 | int w_calla(W_runtime *rt, const char *func, int argc, W_value *argv);
400 |
401 | /**
402 | * ### Manipulating variables in the interpreter
403 | *
404 | * * `int w_value_cmp(W_runtime *rt, const W_value *p, const W_value *q)`
405 | *
406 | * `w_value_cmp` compares two `W_value` objects in the interpreter. It returns 0 if the two
407 | * values are the same, a positive integer if `a` comes _after_ `b` and a negative integer
408 | * if `a` comes _before_ `b`
409 | *
410 | * #### Getting variables from the interpreter
411 | *
412 | * These functions searches the runtime `rt` for a global variable named `name`:
413 | *
414 | * * `W_value *w_get_variable(W_runtime *rt, const char *name)`
415 | * * `const char *w_get_variable_string(W_runtime *rt, const char *name, char buffer[], unsigned int bufsize)`
416 | * * w_int w_get_variable_number(W_runtime *rt, const char *name)`
417 | *
418 | * `w_get_variable()` finds the global variable, pushes it onto the stack and returns a
419 | *
420 | * `w_get_variable_string` uses `w_get_variable()` to find a variable, then pops it off the
421 | * stack as a string value.
422 | *
423 | * The result is copied to `buffer`. The size of `buffer` is passed through `bufsize`.
424 | * It must have at least `W_STR_SIZE+1` bytes to ensure it will be able to hold any
425 | * string in the interpreter.
426 | *
427 | * `w_get_variable_number()`uses `w_get_variable()` to find a variable, then pops it off the
428 | * stack as a numeric value.
429 | */
430 | int w_value_cmp(W_runtime *rt, W_value *p, W_value *q);
431 |
432 | W_value *w_get_variable(W_runtime *rt, const char *name);
433 | const char *w_get_variable_string(W_runtime *rt, const char *name);
434 | w_int w_get_variable_number(W_runtime *rt, const char *name);
435 |
436 | /**
437 | * #### Setting variables in the interprter
438 | *
439 | * These functions are used to set the value of a variable named `name` in a runtime:
440 | *
441 | * * `int w_set_variable(W_runtime *rt, const char *name)`
442 | * * `int w_set_variable_stringl(W_runtime *rt, const char *name, const char *str, unsigned int len)`
443 | * * `int w_set_variable_string(W_runtime *rt, const char *name, const char *str)`
444 | * * `int w_set_variable_number(W_runtime *rt, const char *name, w_int num)`
445 | *
446 | * `w_set_variable()` pops a value off the top of the stack and
447 | * assigns its value to the global
448 | *
449 | * The other versions pushes a value on the stack and then calls
450 | * `w_set_variable()`.
451 | *
452 | * (It needs to use the stack like this so that the garbage collector
453 | * can track the objects during the operation)
454 | *
455 | * They return 1 on success, -1 if the variable is not available in the runtime,
456 | * and 0 on error.
457 | */
458 | int w_set_variable(W_runtime *rt, const char *name);
459 | int w_set_variable_stringl(W_runtime *rt, const char *name, const char *str, unsigned int len);
460 | int w_set_variable_string(W_runtime *rt, const char *name, const char *str);
461 | int w_set_variable_number(W_runtime *rt, const char *name, w_int num);
462 |
463 | /**
464 | * ### Adding functions to the interpreter
465 | *
466 | * These functions can be used to add _built-in functions_ to the interpreter that
467 | * can be called from scripts:
468 | *
469 | * * `int w_add_function(const char *name, W_function fun)`
470 | * * `void w_init_stdlib()`
471 | *
472 | * `w_add_function` registers a function pointed to by `fun` under the name `name`
473 | * in the list of functions that the interpreter can call.
474 | *
475 | * The function `fun` must match the following prototype:
476 | *
477 | * ```
478 | * typedef int (*W_function)(W_runtime *rt, int argc, W_value *argv)
479 | * ```
480 | *
481 | * where `rt` will be the runtime from which the function is called,
482 | * `argc` is the number of arguments passed, and `argv` is an array of all
483 | * the arguments passed to the function.
484 | *
485 | * The function should return 1 on success, or 0 on failure, in which case
486 | * the interpreter will halt.
487 | *
488 | * `w_init_stdlib` registers the functions of WTS' standard library.
489 | * See section Standard functions for the functions' details.
490 | */
491 | typedef int (*W_function)(W_runtime *rt, int argc, W_value *argv);
492 |
493 | int w_add_function(const char *name, W_function fun);
494 |
495 | void w_init_stdlib(void);
496 |
497 | /**
498 | * #### Getting function arguments
499 | *
500 | * * `const char *w_as_string_cvrt(W_runtime *rt, const W_value *val)`
501 | * * `const char *w_as_string(W_runtime *rt, W_value *val)`
502 | * * `w_int w_as_number(W_runtime *rt, W_value *val)`
503 | *
504 | * `w_as_string_cvrt` retrieves a value as a null terminated C-string. If the value is a number
505 | * it will convert the value to a string if it is a number.
506 | *
507 | * `w_as_string` will return the string value of a variable if it is a string type. If
508 | * the value is not a string, it will return "".
509 | *
510 | * > [!WARNING]
511 | * > These functions return a pointer to memory inside the interpreter's RAM. If the
512 | * > pointer isn't used immediately it is better to make a copy of the string, because
513 | * > the garbage collector might scramble it if it moves object around in memory (functions
514 | * > like `w_return_string` or `w_return_alloc` might trigger the garbage collector).
515 | *
516 | * `w_as_number` retrieves a value as a `w_int` number.
517 | */
518 | const char *w_as_string_cvrt(W_runtime *rt, W_value *val);
519 | const char *w_as_string(W_runtime *rt, const W_value *val);
520 | w_int w_as_number(W_runtime *rt, const W_value *val);
521 |
522 | /**
523 | * #### Checking types
524 | *
525 | * * `int w_is_string(W_value *val)`
526 | * * `int w_is_number(W_value *val)`
527 | * * `int w_is_array(W_value *val)`
528 | * * `int w_is_dict(W_value *val)`
529 | * * `int w_is_true(W_runtime *rt, const W_value *val)`
530 | *
531 | * `w_is_number` returns 1 if the value `val` is a string (`WV_DSTR` or `WV_CSTR`),
532 | * `w_is_number` returns 1 if `val` is a number (`WV_NUM`), `w_is_array` returns
533 | * 1 if it is an array (`WV_ARR`) and `w_is_dict` returns 1 if it is a dictionary.
534 | *
535 | * All of them return 0 if the value is not the specific type.
536 | *
537 | * `w_is_true` returns true if its `val` argument is _truthy_ according to
538 | * WTS' rules: the number 0, the empty string `""`, an empty array `[]` or
539 | * an empty dict `{}` are false; Any other values are true.
540 | */
541 | int w_is_string(W_value *val);
542 | int w_is_number(W_value *val);
543 | int w_is_array(W_value *val);
544 | int w_is_dict(W_value *val);
545 |
546 | int w_is_true(W_runtime *rt, const W_value *val);
547 |
548 | /**
549 | * #### Array API
550 | *
551 | * * `W_value *w_dim(W_runtime *rt, unsigned int len)`
552 | * * `w_uint w_array_len(W_runtime *rt, W_value *arrayv)`
553 | * * `W_value *w_array_get(W_runtime *rt, W_value *arrayv, w_uint elem)`
554 | * * `int w_array_set(W_runtime *rt, w_uint elem)`
555 | */
556 | W_value *w_dim(W_runtime *rt, unsigned int len);
557 | w_uint w_array_len(W_runtime *rt, W_value *arrayv);
558 | W_value *w_array_get(W_runtime *rt, W_value *arrayv, w_uint elem);
559 | int w_array_set(W_runtime *rt, w_uint elem);
560 |
561 |
562 | /**
563 | * #### Dicts API
564 | *
565 | * * `W_value *w_dict(W_runtime *rt, unsigned int icapacity)`
566 | * * `W_value *w_dict_get(W_runtime *rt, const char *key)`
567 | * * `int w_dict_set(W_runtime *rt, const char *key)`
568 | */
569 | W_value *w_dict(W_runtime *rt, unsigned int icapacity);
570 | W_value *w_dict_get(W_runtime *rt, const char *key);
571 | int w_dict_set(W_runtime *rt, const char *key);
572 |
573 | /**
574 | * #### Setting the return value
575 | *
576 | * The return value of functions is stored in a variable at
577 | * position 0. The functions in this section can be used to
578 | * manipulate that variable.
579 | *
580 | * * `int w_return_val(W_runtime *rt, const W_value *val)`
581 | * * `int w_return_number(W_runtime *rt, w_int num)`
582 | * * `int w_return_stringl(W_runtime *rt, const char *str, unsigned int len)`
583 | * * `int w_return_string(W_runtime *rt, const char *str)`
584 | * * `char *w_return_alloc(W_runtime *rt, unsigned int len)`
585 | * * `W_value *w_get_return(W_runtime *rt)`
586 | *
587 | * `w_return_val` sets the return value of a function to a `W_value`.
588 | * `w_return_number` sets it to a number. `w_return_stringl` and
589 | * `w_return_string` sets it to a string value - the first form is for
590 | * when the length is known, the second will compute the length.
591 | *
592 | * `w_return_alloc` allocates `len` bytes for a string return
593 | * value, but doesn't fill it. It returns `NULL` on error.
594 | *
595 | * `w_get_return` returns a pointer to where the return value is stored
596 | * in the runtime
597 | */
598 | int w_return_val(W_runtime *rt, const W_value *val);
599 | int w_return_number(W_runtime *rt, w_int num);
600 | int w_return_stringl(W_runtime *rt, const char *str, unsigned int len);
601 | int w_return_string(W_runtime *rt, const char *str);
602 | char *w_return_alloc(W_runtime *rt, unsigned int len);
603 |
604 | W_value *w_get_return(W_runtime *rt);
605 |
606 | /**
607 | * #### Handling errors
608 | *
609 | * Built-in functions can use these functions to handle errors:
610 | *
611 | * * `int w_error(const char *fmt, ...)`
612 | * * `w_uint w_current_line(W_runtime *rt)`
613 | *
614 | * `w_error` writes an error message through `w_error_fun`. It always returns 0.
615 | *
616 | * `w_current_line` finds the number of the last line executed in the runtime `rt`
617 | * from the line number information in the bytecode. It will return 0 if the bytecode
618 | * was compiled without debug information (see `w_debug_info` above).
619 | *
620 | * An error in a built-in function is typically handled by this pattern:
621 | * ```
622 | * if(error_occurred)
623 | * return w_error("%u: some error message", w_current_line(rt));
624 | * ```
625 | */
626 | int w_error(const char *fmt, ...);
627 | w_uint w_current_line(W_runtime *rt);
628 |
629 | #ifdef __cplusplus
630 | } /* extern "C" */
631 | #endif
632 |
633 | #endif
634 |
--------------------------------------------------------------------------------
/include/seqio.h:
--------------------------------------------------------------------------------
1 | /**
2 | * # Sequential IO
3 | *
4 | * Functions for working with Visual Basic-style Sequential Text Files.
5 | *
6 | * To use this library, define `SEQIO_IMPL` before including **seqio.h** in _one_
7 | * of your C files (other C files include **seqio.h** normally), like so:
8 | *
9 | * ```
10 | * #define SEQIO_IMPL
11 | * #include "seqio.h"
12 | * ```
13 | *
14 | * The only reason for this code's existence is because I was bored and it
15 | * scratched a certain nostalgia itch: In my youth I used these types of files
16 | * many times for custom file formats that could be created by a tool written
17 | * in one language (Visual Basic 6) and then consumed by a program written in
18 | * another (in my case, a game written in Visual C++), while having the benefit
19 | * of being human readable and easy to parse with my limited experience at the
20 | * time.
21 | *
22 | * This was before I knew what XML was (maybe not such a bad thing) and before
23 | * JSON became a thing.
24 | *
25 | * You might want to look at [this VB6 guide][vb-files] for more information
26 | * about how these files were used in practice.
27 | *
28 | * There is a test program at the bottom of this file. You can compile it like so:
29 | *
30 | * ```sh
31 | * gcc -DSEQIO_TEST -Wall -std=c89 -xc -g -O0 seqio.h
32 | * ```
33 | *
34 | * I wanted some form of Visual Basic compatibility. Unfortunately there does
35 | * not seem to be an _official_ specification; The best I could find seems to be
36 | * the documentation on VBA's [WRITE][vba-write] and [INPUT][vba-input] statements.
37 | *
38 | * This implementation takes some inspiration from [RFC 4180][rfc4180]
39 | * for escaping quotes and dealing with newlines in values (though that
40 | * RFC is meant for something else).
41 | *
42 | * [vb-files]: https://www.informit.com/articles/article.aspx?p=20992&seqNum=4#
43 | * [vba-write]: https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/writestatement
44 | * [vba-input]: https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/inputstatement
45 | * [rfc4180]: https://www.ietf.org/rfc/rfc4180.txt
46 | */
47 | #ifndef SEQIO_H
48 | #define SEQIO_H
49 |
50 | #ifdef __cplusplus
51 | extern "C" {
52 | #endif
53 |
54 | /* Length of the internal buffer where tokens are kept as they're read */
55 | #ifndef SEQIO_MAXLEN
56 | # define SEQIO_MAXLEN 256
57 | #endif
58 |
59 | /* I can't find any reference of the BASICs using comments in sequential
60 | * text files, but I thought it'd be a useful feature nonetheless.
61 | */
62 | #if !defined(SEQIO_NOCOMMENT) && !defined(SEQIO_COMMENT_CHAR)
63 | # define SEQIO_COMMENT_CHAR ';'
64 | #endif
65 |
66 | /* Should quotes in string values be escaped?
67 | * The BASICs don't do it, but IMHO it is quite a
68 | * shortcoming, so I allow for it.
69 | */
70 | #ifndef SEQIO_ESCAPE_QUOTES
71 | # define SEQIO_ESCAPE_QUOTES 1
72 | #endif
73 |
74 | #ifndef SEQIO_NO_FLOAT
75 | /* I actually tried this on Turbo C, which
76 | does not enable floating point by default. */
77 | # define SEQIO_NO_FLOAT 0
78 | #endif
79 |
80 | /* Which `printf()` format string to use for floating point numbers? */
81 | #ifndef SEQIO_FLOAT_FORMAT
82 | # define SEQIO_FLOAT_FORMAT "%g"
83 | #endif
84 |
85 | /* VB6 would store dates like so: `#1998-01-01#`
86 | * There is also `#NULL#`, `#TRUE#` and `#FALSE#`.
87 | * Defining `SEQIO_HASH_QUOTES` treats the
88 | * `#` symbols the same way as quotes
89 | */
90 | #ifndef SEQIO_HASH_QUOTES
91 | # define SEQIO_HASH_QUOTES 1
92 | #endif
93 |
94 | /* I tried to keep the code C89, but snprintf() is just too useful */
95 | #ifndef SEQIO_HAS_SNPRINTF
96 | # define SEQIO_HAS_SNPRINTF 0
97 | #endif
98 |
99 | /**
100 | * ## Definitions
101 | *
102 | * ### Function types
103 | *
104 | * * `typedef int (*seq_getchar_fun)(void *)`
105 | * * `typedef int (*seq_putchar_fun)(int, void *)`
106 | * * `typedef int (*seq_close_fun)(void *)`
107 | * * `typedef int (*seq_eof_fun)(void *)`
108 | */
109 | typedef int (*seq_getchar_fun)(void *);
110 | typedef int (*seq_putchar_fun)(int, void *);
111 | typedef int (*seq_close_fun)(void *);
112 | typedef int (*seq_eof_fun)(void *);
113 |
114 | /**
115 | * ### `typedef struct SeqIO`
116 | *
117 | * It has these members
118 | *
119 | * * `seq_getchar_fun getc`
120 | * * `seq_putchar_fun putc`
121 | * * `seq_close_fun close`
122 | * * `seq_eof_fun eof`
123 | * * `int error, ateof`
124 | * * `unsigned int pos`
125 | * * `char buffer[SEQIO_MAXLEN]`
126 | */
127 | typedef struct {
128 | void *data;
129 | seq_getchar_fun getc;
130 | seq_putchar_fun putc;
131 | seq_close_fun close;
132 | seq_eof_fun eof;
133 | int error, ateof;
134 | unsigned int pos;
135 | char buffer[SEQIO_MAXLEN];
136 | int c, unget;
137 | } SeqIO;
138 |
139 | /**
140 | * ## Open/Close Functions
141 | *
142 | * These functions opens and close streams.
143 | */
144 |
145 | /**
146 | * ### `int seq_istream(SeqIO *a, void *data, seq_getchar_fun get_fn)`
147 | */
148 | int seq_istream(SeqIO *S, void *data, seq_getchar_fun get_fn);
149 |
150 | /**
151 | * ### `int seq_ostream(SeqIO *S, void *data, seq_putchar_fun put_fn)`
152 | */
153 | int seq_ostream(SeqIO *S, void *data, seq_putchar_fun put_fn);
154 |
155 | #ifdef EOF
156 | /**
157 | * ### `int seq_infilep(SeqIO *S, FILE *f)`
158 | */
159 | int seq_infilep(SeqIO *S, FILE *f);
160 |
161 | /**
162 | * ### `int seq_outfilep(SeqIO *S, FILE *f)`
163 | */
164 | int seq_outfilep(SeqIO *S, FILE *f);
165 | #endif
166 |
167 | /**
168 | * ### `int seq_infile(SeqIO *S, const char *name)`
169 | */
170 | int seq_infile(SeqIO *S, const char *name);
171 |
172 | /**
173 | * ### `int seq_outfile(SeqIO *S, const char *name)`
174 | */
175 | int seq_outfile(SeqIO *S, const char *name);
176 |
177 | /**
178 | * ### `void seq_close(SeqIO *S)`
179 | *
180 | * Closes the stream `S` by calling the `S->close` function
181 | * (if any) and then setting `S->data` to `NULL`
182 | */
183 | void seq_close(SeqIO *S);
184 |
185 | /**
186 | * ### `int seq_eof(SeqIO *S)`
187 | *
188 | * Checks whether the end of an (input) stream has been reached by
189 | * first calling the `S->eof` function (if any) and then checking
190 | * the `S->ateof` flag.
191 | */
192 | int seq_eof(SeqIO *S);
193 |
194 | /**
195 | * ### `const char *seq_error(SeqIO *S)`
196 | *
197 | * Checks the `S->error` flag after reading or writing
198 | * operations to see if an error occured.
199 | *
200 | * If an error occured it returns the error message (which
201 | * is stored internally in `S->buffer`).
202 | */
203 | const char *seq_error(SeqIO *S);
204 |
205 | /**
206 | * ## Input Functions
207 | */
208 |
209 | /**
210 | * ### `const char *seq_read(SeqIO *S)`
211 | */
212 | const char *seq_read(SeqIO *S);
213 |
214 | /**
215 | * ### `int seq_read_int(SeqIO *S)`
216 | */
217 | int seq_read_int(SeqIO *S);
218 |
219 | #if !SEQIO_NO_FLOAT
220 | /**
221 | * ### `double seq_read_float(SeqIO *S)`
222 | */
223 | double seq_read_float(SeqIO *S);
224 | #endif
225 |
226 | /**
227 | * ### `int seq_read_rec(SeqIO *S, const char *fmt, ...)`
228 | *
229 | * Reads a record from the file.
230 | *
231 | * A record is just a sequence of values in this context.
232 | * The `fmt` parameter determines how the values are organised:
233 | *
234 | * - `%s` for string values. The corresponding arg should be a pointer to a `char[]` that can
235 | * hold at least `SEQIO_MAXLEN` bytes.
236 | * - Alternatively `%*s` can be used to specify the size of the buffer in the args.
237 | * - `%d` or `%d` for decimal integer values. The corresponding arg should be an `int*`
238 | * - `%f` or `%g` for floating point values. The corresponding arg should be a `double*`
239 | * - `%b` for boolean values. The corresponding arg should be an `int*`
240 | */
241 | int seq_read_rec(SeqIO *S, const char *fmt, ...);
242 |
243 | /**
244 | * ## Ouput Functions
245 | */
246 |
247 | /**
248 | * ### `void seq_write(SeqIO *S, const char *str)`
249 | */
250 | void seq_write(SeqIO *S, const char *str);
251 |
252 | /**
253 | * ### `void seq_write_int(SeqIO *S, int value)`
254 | */
255 | void seq_write_int(SeqIO *S, int value);
256 |
257 | #if !SEQIO_NO_FLOAT
258 | /**
259 | * ### `void seq_write_float(SeqIO *S, double value)`
260 | */
261 | void seq_write_float(SeqIO *S, double value);
262 | #endif
263 |
264 | /**
265 | * ### `void seq_write_special(SeqIO *S, const char *value)`
266 | */
267 | void seq_write_special(SeqIO *S, const char *value);
268 |
269 | /**
270 | * ### `void seq_write_bool(SeqIO *S, int value)`
271 | */
272 | void seq_write_bool(SeqIO *S, int value);
273 |
274 | /**
275 | * ### `void seq_endl(SeqIO *S)`
276 | */
277 | void seq_endl(SeqIO *S);
278 |
279 | /**
280 | * ### `int seq_write_rec(SeqIO *S, const char *fmt, ...)`
281 | */
282 | int seq_write_rec(SeqIO *S, const char *fmt, ...);
283 |
284 | /**
285 | * ### `void seq_comment(SeqIO *S, const char *str)`
286 | */
287 | void seq_comment(SeqIO *S, const char *str);
288 |
289 | #if defined(SEQIO_IMPL) || defined(SEQIO_TEST)
290 |
291 | #ifdef _MSC_VER
292 | # define _CRT_SECURE_NO_WARNINGS
293 | #endif
294 |
295 | #include
296 | #include
297 | #include
298 | #include
299 | #include
300 | #include
301 | #include
302 |
303 | static int _s_set_error(SeqIO *S, const char *msg) {
304 | size_t len = strlen(msg);
305 | if(len >= SEQIO_MAXLEN)
306 | len = SEQIO_MAXLEN - 1;
307 | memcpy(S->buffer, msg, len);
308 | S->buffer[len] = '\0';
309 | S->pos = (unsigned int)len;
310 | S->error = 1;
311 | return 0;
312 | }
313 |
314 | int seq_istream(SeqIO *S, void *data, seq_getchar_fun get_fn) {
315 | S->data = data;
316 | S->getc = get_fn;
317 | S->putc = NULL;
318 | S->close = NULL;
319 | S->eof = NULL;
320 | S->error = 0;
321 | S->ateof = 0;
322 | S->pos = 0;
323 | S->c = '\0';
324 | S->unget = 0;
325 | return 1;
326 | }
327 |
328 | #ifdef feof
329 | /* feof() is a macro */
330 | static int _s_file_eof(void *fp) {
331 | FILE *f = fp;
332 | return feof(f);
333 | }
334 | #define FEOF _s_file_eof
335 | #else
336 | /* feof() is a function */
337 | #define FEOF feof
338 | #endif
339 |
340 | int seq_infilep(SeqIO *S, FILE *f) {
341 | if(!seq_istream(S, f, (seq_getchar_fun)fgetc))
342 | return 0;
343 | S->eof = (seq_eof_fun)FEOF;
344 | return 1;
345 | }
346 |
347 | int seq_infile(SeqIO *S, const char *name) {
348 | int result;
349 | FILE *f = fopen(name, "r");
350 | if(!f)
351 | return _s_set_error(S, strerror(errno));
352 | result = seq_infilep(S, f);
353 | S->close = (seq_close_fun)fclose;
354 | return result;
355 | }
356 |
357 | int seq_ostream(SeqIO *S, void *data, seq_putchar_fun put_fn) {
358 | S->data = data;
359 | S->getc = NULL;
360 | S->putc = put_fn;
361 | S->close = NULL;
362 | S->eof = NULL;
363 | S->error = 0;
364 | S->ateof = 0;
365 | S->pos = 0;
366 | S->c = '\0';
367 | S->unget = 0;
368 | return 1;
369 | }
370 |
371 | int seq_outfilep(SeqIO *S, FILE *f) {
372 | if(!seq_ostream(S, f, (seq_putchar_fun)fputc))
373 | return 0;
374 | S->eof = (seq_eof_fun)FEOF;
375 | return 1;
376 | }
377 |
378 | int seq_outfile(SeqIO *S, const char *name) {
379 | int result;
380 | FILE *f = fopen(name, "wb");
381 | if(!f)
382 | return _s_set_error(S, strerror(errno));
383 | result = seq_outfilep(S, f);
384 | S->close = (seq_close_fun)fclose;
385 | return result;
386 | }
387 |
388 | void seq_close(SeqIO *S) {
389 | if(S->close)
390 | (S->close)(S->data);
391 | S->data = NULL;
392 | S->ateof = 1;
393 | }
394 |
395 | int seq_eof(SeqIO *S) {
396 | if(S->eof)
397 | return (S->eof)(S->data);
398 | return S->ateof;
399 | }
400 |
401 | static int _s_getc(SeqIO *S) {
402 | return (S->getc)(S->data);
403 | }
404 |
405 | static int _s_putc(SeqIO *S, int c) {
406 | return (S->putc)(c, S->data);
407 | }
408 |
409 | static int _s_read(SeqIO *S) {
410 | int q = 0;
411 | enum {pre, word, quote, post, error, comment} state = pre;
412 |
413 | if(S->error) return 0;
414 | S->buffer[0] = '\0';
415 | S->pos = 0;
416 |
417 | if(!S->getc)
418 | return _s_set_error(S, "not an input stream");
419 |
420 | if(S->ateof)
421 | return _s_set_error(S, "end of stream");
422 |
423 | for(;;) {
424 | if(S->unget) {
425 | S->unget = 0;
426 | } else {
427 | S->c = _s_getc(S);
428 | }
429 | if(S->c == EOF || S->c == '\0') {
430 | if(state == quote)
431 | _s_set_error(S, "unterminated string");
432 | S->ateof = 1;
433 | break;
434 | }
435 |
436 | if(S->pos >= SEQIO_MAXLEN) {
437 | _s_set_error(S, "token too long");
438 | state = error;
439 | }
440 |
441 | switch(state) {
442 | case error:
443 | if(S->c == '\n' || S->c == ',')
444 | goto end;
445 | break;
446 | case comment:
447 | if(S->c == '\n')
448 | state = pre;
449 | break;
450 | case pre:
451 | if(S->c == '\n' || S->c == ',')
452 | goto end;
453 | #ifdef SEQIO_COMMENT_CHAR
454 | else if(S->c == SEQIO_COMMENT_CHAR)
455 | state = comment;
456 | #endif
457 | else if(isspace(S->c))
458 | continue;
459 | else if(S->c == '"') {
460 | q = '"';
461 | state = quote;
462 | #if SEQIO_HASH_QUOTES
463 | } else if(S->c == '#') {
464 | q = '#';
465 | state = quote;
466 | #endif
467 | } else {
468 | state = word;
469 | S->unget = 1;
470 | }
471 | break;
472 | case word:
473 | if(S->c == '\n' || S->c == ',')
474 | goto end;
475 | #if SEQIO_COMMENT_CHAR
476 | else if(S->c == SEQIO_COMMENT_CHAR) {
477 | S->unget = 1;
478 | goto end;
479 | }
480 | #endif
481 | else if(isspace(S->c)) {
482 | state = post;
483 | } else {
484 | S->buffer[S->pos++] = S->c;
485 | }
486 | break;
487 | case quote:
488 | if(S->c == q) {
489 | #if SEQIO_ESCAPE_QUOTES
490 | S->c = _s_getc(S);
491 | if(S->c == q) {
492 | S->buffer[S->pos++] = q;
493 | } else {
494 | S->unget = 1;
495 | state = post;
496 | }
497 | #else
498 | state = post;
499 | #endif
500 | } else {
501 | S->buffer[S->pos++] = S->c;
502 | }
503 | break;
504 | case post:
505 | if(S->c == '\n' || S->c == ',')
506 | goto end;
507 | #ifdef SEQIO_COMMENT_CHAR
508 | else if(S->c == SEQIO_COMMENT_CHAR) {
509 | S->unget = 1;
510 | goto end;
511 | }
512 | #endif
513 | else if(isspace(S->c))
514 | continue;
515 | else {
516 | _s_set_error(S, "expected ',' or EOL");
517 | state = error;
518 | }
519 | break;
520 | }
521 | }
522 |
523 | end:
524 | S->buffer[S->pos] = '\0';
525 | return S->pos;
526 | }
527 |
528 | const char *seq_read(SeqIO *S) {
529 | _s_read(S);
530 | if(S->error)
531 | return "";
532 | return S->buffer;
533 | }
534 |
535 | int seq_read_int(SeqIO *S) {
536 | _s_read(S);
537 | if(S->error)
538 | return 0;
539 | return strtol(S->buffer, NULL, 0);
540 | }
541 |
542 | #if !SEQIO_NO_FLOAT
543 | double seq_read_float(SeqIO *S) {
544 | _s_read(S);
545 | if(S->error)
546 | return 0.0;
547 | return atof(S->buffer);
548 | }
549 | #endif
550 |
551 | int seq_read_bool(SeqIO *S) {
552 | _s_read(S);
553 | if(S->error)
554 | return 0;
555 | return !strcmp(S->buffer,"TRUE");
556 | }
557 |
558 | int seq_read_rec(SeqIO *S, const char *fmt, ...) {
559 | va_list arg;
560 | int s_len = SEQIO_MAXLEN - 1, n = 0;
561 |
562 | if(S->error) return 0;
563 | if(!S->getc)
564 | return _s_set_error(S, "not an input stream");
565 |
566 | va_start(arg, fmt);
567 | while(*fmt) {
568 | switch(*fmt++) {
569 | case ' ':
570 | case '\n':
571 | case '\t': continue;
572 | case '%': {
573 | if(isdigit(*fmt)) {
574 | char *e;
575 | s_len = strtol(fmt, &e, 10);
576 | fmt = e;
577 | } else if(*fmt == '*') {
578 | s_len = va_arg(arg, int);
579 | fmt++;
580 | }
581 | if(!strchr("sdigfb", *fmt)) {
582 | _s_set_error(S, "bad % format");
583 | }
584 | } break;
585 | case 's': {
586 | /* Note about safety: It assumes the buffer is big enough to hold the
587 | token. To ensure that it is, it must be at least SEQIO_MAXLEN bytes,
588 | or you can use the %*s or %[len]s form */
589 | const char *s = seq_read(S);
590 | char *dest = va_arg(arg, char *);
591 | if(S->error) return 0;
592 | strncpy(dest, s, s_len - 1);
593 | dest[s_len-1] = '\0';
594 | s_len = SEQIO_MAXLEN - 1; /* reset s_len */
595 | n++;
596 | } break;
597 | case 'd':
598 | case 'i': { /* '%d' and '%i' are equivalent */
599 | int i = seq_read_int(S);
600 | if(S->error) return 0;
601 | *va_arg(arg, int *) = i;
602 | n++;
603 | } break;
604 | #if !SEQIO_NO_FLOAT
605 | case 'g':
606 | case 'f': {
607 | double f = seq_read_float(S);
608 | if(S->error) return 0;
609 | *va_arg(arg, double *) = f;
610 | n++;
611 | } break;
612 | #endif
613 | case 'b': {
614 | int i = seq_read_bool(S);
615 | if(S->error) return 0;
616 | *va_arg(arg, int *) = i;
617 | n++;
618 | } break;
619 | default:
620 | _s_set_error(S, "bad format");
621 | return 0;
622 | }
623 | }
624 | va_end(arg);
625 | return n;
626 | }
627 |
628 | static void _s_write(SeqIO *S, const char *str) {
629 | if(S->error) return;
630 | if(!S->putc) {
631 | _s_set_error(S, "not an output stream");
632 | return;
633 | }
634 | if(S->pos++)
635 | _s_putc(S, ',');
636 | while(*str)
637 | _s_putc(S, *str++);
638 | S->pos++;
639 | }
640 |
641 | void seq_write(SeqIO *S, const char *str) {
642 | if(S->error) return;
643 | if(!S->putc) {
644 | _s_set_error(S, "not an output stream");
645 | return;
646 | }
647 | if(S->pos++)
648 | _s_putc(S, ',');
649 | _s_putc(S, '"');
650 | while(*str) {
651 | int c = *str++;
652 | #if SEQIO_ESCAPE_QUOTES
653 | if(c == '"')
654 | _s_putc(S, '"');
655 | #endif
656 | _s_putc(S, c);
657 | }
658 | _s_putc(S, '"');
659 | }
660 |
661 | void seq_write_int(SeqIO *S, int value) {
662 | if(S->error) return;
663 | sprintf(S->buffer, "%d", value);
664 | _s_write(S, S->buffer);
665 | }
666 |
667 | #if !SEQIO_NO_FLOAT
668 | void seq_write_float(SeqIO *S, double value) {
669 | if(S->error) return;
670 | sprintf(S->buffer, SEQIO_FLOAT_FORMAT, value);
671 | _s_write(S, S->buffer);
672 | }
673 | #endif
674 |
675 | void seq_write_special(SeqIO *S, const char *value) {
676 | #if SEQIO_HASH_QUOTES
677 | # define RAW_FMT "#%s#"
678 | #else
679 | # define RAW_FMT "\"%s\""
680 | #endif
681 | if(S->error) return;
682 | #if !SEQIO_HAS_SNPRINTF
683 | if(strlen(value) >= sizeof S->buffer - 3) {
684 | _s_set_error(S, "value is too long");
685 | return;
686 | }
687 | sprintf(S->buffer, RAW_FMT, value);
688 | #else
689 | snprintf(S->buffer, sizeof S->buffer, RAW_FMT, value);
690 | #endif
691 | _s_write(S, S->buffer);
692 | #undef RAW_FMT
693 | }
694 |
695 | void seq_write_bool(SeqIO *S, int value) {
696 | if(S->error) return;
697 | #if SEQIO_HASH_QUOTES
698 | sprintf(S->buffer, "#%s#", value ? "TRUE" : "FALSE");
699 | #else
700 | sprintf(S->buffer, "%s", value ? "TRUE" : "FALSE");
701 | #endif
702 | _s_write(S, S->buffer);
703 | }
704 |
705 | int seq_write_rec(SeqIO *S, const char *fmt, ...) {
706 | va_list arg;
707 | int n = 0;
708 |
709 | if(S->error) return 0;
710 |
711 | if(!S->putc)
712 | return _s_set_error(S, "not an output stream");
713 |
714 | va_start(arg, fmt);
715 | while(*fmt) {
716 | if(S->error) return 0;
717 | switch(*fmt++) {
718 | case '\n': seq_endl(S); continue;
719 | case ' ':
720 | case '\t': continue;
721 | case '%':
722 | /* % is optional; length specifiers are discarded, unlike seq_read_rec() */
723 | if(*fmt == '*') {
724 | fmt++;
725 | va_arg(arg, int);
726 | } else
727 | while(isdigit(*fmt)) fmt++;
728 | continue;
729 | case 's':
730 | seq_write(S, va_arg(arg, const char *));
731 | n++;
732 | continue;
733 | case 'd':
734 | case 'i': /* '%d' and '%i' are equivalent */
735 | seq_write_int(S, va_arg(arg, int));
736 | n++;
737 | continue;
738 | #if !SEQIO_NO_FLOAT
739 | case 'g':
740 | case 'f':
741 | seq_write_float(S, va_arg(arg, double));
742 | n++;
743 | continue;
744 | #endif
745 | case 'b':
746 | seq_write_bool(S, va_arg(arg, int));
747 | n++;
748 | continue;
749 | default:
750 | return _s_set_error(S, "bad format");
751 | }
752 | }
753 | va_end(arg);
754 | _s_putc(S, '\n');
755 | S->pos = 0;
756 | return n;
757 | }
758 |
759 | void seq_comment(SeqIO *S, const char * str) {
760 | if(!S->putc) {
761 | _s_set_error(S, "not an output stream");
762 | return;
763 | }
764 | if(S->pos) {
765 | _s_putc(S, '\n');
766 | S->pos = 0;
767 | }
768 | _s_putc(S, SEQIO_COMMENT_CHAR);
769 | _s_putc(S, ' ');
770 | while(*str) {
771 | _s_putc(S, *str++);
772 | }
773 | _s_putc(S, '\n');
774 | S->pos = 0;
775 | }
776 |
777 | void seq_endl(SeqIO *S) {
778 | if(!S->putc) {
779 | _s_set_error(S, "not an output stream");
780 | return;
781 | }
782 | _s_putc(S, '\n');
783 | S->pos = 0;
784 | }
785 |
786 | const char *seq_error(SeqIO *S) {
787 | if(!S->error)
788 | return NULL;
789 | return S->buffer;
790 | }
791 |
792 | /* ## TEST PROGRAM ############################################################## */
793 |
794 | # ifdef SEQIO_TEST
795 | int main(int argc, char *argv[]) {
796 | const char *filename = NULL;
797 | char *strings[] = {"foo", "bar", "baz", "fred", "quux"};
798 | int i, count = sizeof strings / sizeof strings[0];
799 |
800 | SeqIO stream;
801 |
802 | if(argc > 1)
803 | filename = argv[1];
804 |
805 | if(filename) {
806 | if(!seq_infile(&stream, filename)) {
807 | fprintf(stderr, "error opening input file: %s\n", seq_error(&stream));
808 | return 1;
809 | }
810 |
811 | while(!seq_eof(&stream)) {
812 | const char *s = seq_read(&stream);
813 | if(seq_error(&stream)) {
814 | fprintf(stderr, "error: %s\n", seq_error(&stream));
815 | break;
816 | }
817 | printf("token: '%s'\n", s);
818 | }
819 |
820 | seq_close(&stream);
821 | return 0;
822 | }
823 |
824 | if(!seq_outfile(&stream, "seqio.out")) {
825 | fprintf(stderr, "error opening output file: %s\n", seq_error(&stream));
826 | return 1;
827 | }
828 |
829 | seq_comment(&stream, "Number of records:");
830 | seq_write_int(&stream, count);
831 | seq_comment(&stream, "Examples using seq_write_rec:");
832 | for(i = 0; i < count; i++) {
833 | seq_write_rec(&stream, "%i %s %f %s %b", i, strings[i], (float)(i*i)/3.0, strings[(i+1) % count], i & 0x01);
834 | if(seq_error(&stream)) {
835 | fprintf(stderr, "error: %s\n", seq_error(&stream));
836 | break;
837 | }
838 | }
839 |
840 | seq_comment(&stream, "Examples using the other functions:");
841 | for(i = 0; i < count; i++) {
842 | seq_write_int(&stream, i+count);
843 | seq_write(&stream, strings[(i+2) % count]);
844 | seq_write_float(&stream, (i*i + 7)/3.0);
845 | seq_write(&stream, strings[(i+4) % count]);
846 | seq_endl(&stream);
847 | if(seq_error(&stream)) {
848 | fprintf(stderr, "error: %s\n", seq_error(&stream));
849 | break;
850 | }
851 | }
852 |
853 | /* If you don't use SEQIO_ESCAPE_QUOTES you're in for a world
854 | of hurt if you try to read this file */
855 | seq_write(&stream, "A string with embedded \"quote\" values" );
856 |
857 | seq_endl(&stream);
858 | seq_write_bool(&stream, 1);
859 | seq_write_bool(&stream, 0);
860 | seq_write_special(&stream, "NULL");
861 |
862 | seq_close(&stream);
863 |
864 | #if 1
865 | /* Demo of how to use seq_read_rec */
866 | if(!seq_infile(&stream, "seqio.out")) {
867 | fprintf(stderr, "error opening input file: %s\n", seq_error(&stream));
868 | return 1;
869 | }
870 | count = seq_read_int(&stream);
871 | printf("%d records:\n", count);
872 | for(i = 0; i < count; i++) {
873 | char a[SEQIO_MAXLEN], b[SEQIO_MAXLEN];
874 | int x, odd;
875 | double y;
876 | /*seq_read_rec(&stream, "i%*sfs", &x, 3, a, &y, b);*/
877 | /*seq_read_rec(&stream, "i%3sfs", &x, a, &y, b);*/
878 | seq_read_rec(&stream, "%i %s %f %s %b", &x, a, &y, b, &odd);
879 | if(seq_error(&stream)) {
880 | fprintf(stderr, "error: %s\n", seq_error(&stream));
881 | break;
882 | }
883 | printf("** x=%d; y=%g a='%s'; b='%s'; odd=%d\n", x, y, a, b, odd);
884 | fflush(stdout);
885 | }
886 | seq_close(&stream);
887 | #endif
888 |
889 | return 0;
890 | }
891 | # endif /* SEQIO_TEST */
892 |
893 | #endif /* SEQIO_IMPL */
894 |
895 | #ifdef __cplusplus
896 | } /* extern "C" */
897 | #endif
898 |
899 | #endif /* SEQIO_H */
900 |
--------------------------------------------------------------------------------
/extra/d.awk:
--------------------------------------------------------------------------------
1 | #! /usr/bin/awk -f
2 |
3 | ##
4 | # d.awk
5 | # =====
6 | #
7 | # Converts Markdown in C/C++-style code comments to HTML. \
8 | #
9 | #
10 | # The comments must have the `/** */` pattern. Every line in the comment
11 | # must start with a *. Like so:
12 | #
13 | # ```c
14 | # /**
15 | # * Markdown here...
16 | # */
17 | # ```
18 | #
19 | # Alternatively, three slashes can also be used: `/// Markdown here`
20 | #
21 | # ## Configuration Options
22 | #
23 | # You can set these in the BEGIN block below, or pass them to the script through the
24 | # `-v` command-line option:
25 | #
26 | # - `-vTitle="My Document Title"` to set the ` ` in the ` ` section of the HTML
27 | # - `-vStyleSheet=style.css` to use a separate CSS file as style sheet.
28 | # - `-vCss=n` with n either 0 to disable CSS or 1 to enable; Default = 1
29 | # - `-vTopLinks=1` to have links to the top of the doc next to headers.
30 | # - `-vMaxWidth=1080px` specifies the Maximum width of the HTML output.
31 | # - `-vHighlight=0` disable syntax highlighting with [highlight.js][].
32 | # - `-vMermaid=0` disable [Mermaid][] diagrams.
33 | # - `-vMathjax=0` disable [MathJax][] mathematical rendering.
34 | # - `-vHideToCLevel=n` specifies the level of the ToC that should be collapsed by default.
35 | # - `-vLang=n` specifies the value of the `lang` attribute of the tag; Default = "en"
36 | # - `-vTables=0` disables support for [GitHub-style tables][github-tables]
37 | # - `-vclassic_underscore=1` words_with_underscores behave like old markdown where the
38 | # underscores in the word counts as emphasis. The default behaviour is to have
39 | # `words_like_this` not contain any emphasis.
40 | # - `-vNumberHeadings=1` to enable or disable section numbers in front of headings; Default = 1
41 | # - `-vNumberH1s=1`: if `NumberHeadings` is enabled, `
";
478 | else if(match(st, /^[[:space:]]*>/))
479 | Buf = Buf "\n" scrub(trim(substr(st, RSTART+RLENGTH)));
480 | else if(match(st, /^[[:space:]]*$/)) {
481 | res = end_blockquote(Buf);
482 | pop();
483 | res = res filter(st);
484 | } else
485 | Buf = Buf st;
486 | } else if(Mode == "table") {
487 | if(match(st, /.*\|(.*\|)+/)) {
488 | process_table_row(st);
489 | } else {
490 | res = end_table();
491 | pop();
492 | res = res filter(st);
493 | }
494 | } else if(Mode == "pre") {
495 | if(!Preterm && match(st, /^(( )| *\t)/) || Preterm && !match(st, /^[[:space:]]*```+/))
496 | Buf = Buf ((Buf)?"\n":"") substr(st, RSTART+RLENGTH);
497 | else {
498 | gsub(/\t/," ",Buf);
499 | res = end_pre(Buf);
500 | pop();
501 | if(Preterm) sub(/^[[:space:]]*```+[[:alnum:]]*/,"",st);
502 | res = res filter(st);
503 | }
504 | } else if(Mode == "ul" || Mode == "ol") {
505 | if(ListLevel == 0 || match(st, /^[[:space:]]*$/) && (RLENGTH <= indent[1])) {
506 | while(ListLevel > 1)
507 | Buf = Buf "\n" Open[ListLevel--] ">";
508 | res = tag(Mode, "\n" Buf "\n");
509 | pop();
510 | } else {
511 | if(match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)/)) {
512 | tmp = substr(st, RLENGTH+1);
513 | match(st, /^[[:space:]]*/);
514 | if(RLENGTH > indent[ListLevel]) {
515 | indent[++ListLevel] = RLENGTH;
516 | if(match(st, /^[[:space:]]*[*+-]/))
517 | Open[ListLevel] = "ul";
518 | else
519 | Open[ListLevel] = "ol";
520 | Buf = Buf "\n<" Open[ListLevel] ">";
521 | } else while(RLENGTH < indent[ListLevel])
522 | Buf = Buf "\n" Open[ListLevel--] ">";
523 | if(match(tmp,/^[[:space:]]*\[[xX[:space:]]\]/)) {
524 | st = substr(tmp,RLENGTH+1);
525 | tmp = tolower(substr(tmp,RSTART,RLENGTH));
526 | Buf = Buf "
" scrub(st);
527 | } else
528 | Buf = Buf "" scrub(tmp);
529 | } else if(match(st, /^[[:space:]]*$/)){
530 | Buf = Buf " \n";
531 | } else {
532 | sub(/^[[:space:]]+/,"",st);
533 | Buf = Buf "\n" scrub(st);
534 | }
535 | }
536 | } else if(Mode == "dl") {
537 | if(Dl_state == 1) {
538 | if(match(st, /^[[:space:]]*:/)) {
539 | Buf = Buf "\n" st;
540 | } else if(match(st, /^[[:space:]]*$/)) {
541 | Buf = Buf "\n";
542 | Dl_state = 3;
543 | } else if(match(st, /^( |\t)[[:space:]]+[^[:space:]]/)) {
544 | Buf = Buf "\n" st;
545 | } else {
546 | Buf = Buf "\n" st;
547 | Dl_state = 2;
548 | }
549 | } else if(Dl_state == 2) {
550 | if(match(st, /^[[:space:]]*:/)) {
551 | Buf = Buf "\n" st;
552 | Dl_state = 1;
553 | } else {
554 | res = end_dl(Buf);
555 | pop();
556 | res = res filter(st);
557 | }
558 | } else if(Dl_state == 3) {
559 | if(match(st, /^( |\t)[[:space:]]+[^[:space:]]/)) {
560 | Buf = Buf "\n" st;
561 | Dl_state = 1;
562 | } else if(!match(st, /^[[:space:]]*$/)) {
563 | Dl_line = Dl_line "\n" st;
564 | Dl_state = 4;
565 | } else {
566 | Buf = Buf "\n";
567 | }
568 | } else if(Dl_state == 4) {
569 | if(match(st, /^[[:space:]]*:/)) {
570 | Buf = Buf "\n" Dl_line "\n" st;
571 | Dl_line = "";
572 | Dl_state = 1;
573 | } else {
574 | res = end_dl(Buf);
575 | pop();
576 | res = res filterM(Dl_line "\n" st);
577 | }
578 | }
579 | }
580 | Prev = st;
581 | return res;
582 | }
583 | function scrub(st, mp, ms, me, r, p, tg, a, tok) {
584 | gsub(//,"",st);
585 | sub(/ $/," \n",st);
586 | gsub(/( |[[:space:]]+\\)\n/," \n",st);
587 | gsub(/( |[[:space:]]+\\)$/," \n",st);
588 | while(match(st, /(__?|\*\*?|~~|`+|\$+|\\\(|[&><\\])/)) {
589 | a = substr(st, 1, RSTART-1);
590 | mp = substr(st, RSTART, RLENGTH);
591 | ms = substr(st, RSTART-1,1);
592 | me = substr(st, RSTART+RLENGTH, 1);
593 | p = RSTART+RLENGTH;
594 |
595 | if(!classic_underscore && match(mp,/_+/)) {
596 | if(match(ms,/[[:alnum:]]/) && match(me,/[[:alnum:]]/)) {
597 | tg = substr(st, 1, index(st, mp));
598 | r = r tg;
599 | st = substr(st, index(st, mp) + 1);
600 | continue;
601 | }
602 | }
603 | st = substr(st, p);
604 | r = r a;
605 | ms = "";
606 |
607 | if(mp == "\\") {
608 | if(match(st, /^!?\[/)) {
609 | r = r "\\" substr(st, RSTART, RLENGTH);
610 | st = substr(st, 2);
611 | } else if(match(st, /^(\*\*|__|~~|`+)/)) {
612 | r = r substr(st, 1, RLENGTH);
613 | st = substr(st, RLENGTH+1);
614 | } else {
615 | r = r substr(st, 1, 1);
616 | st = substr(st, 2);
617 | }
618 | continue;
619 | } else if(mp == "_" || mp == "*") {
620 | if(match(me,/[[:space:]]/)) {
621 | r = r mp;
622 | continue;
623 | }
624 | p = index(st, mp);
625 | while(p && match(substr(st, p-1, 1),/[\\[:space:]]/)) {
626 | ms = ms substr(st, 1, p-1) mp;
627 | st = substr(st, p + length(mp));
628 | p = index(st, mp);
629 | }
630 | if(!p) {
631 | r = r mp ms;
632 | continue;
633 | }
634 | ms = ms substr(st,1,p-1);
635 | r = r itag("em", scrub(ms));
636 | st = substr(st,p+length(mp));
637 | } else if(mp == "__" || mp == "**") {
638 | if(match(me,/[[:space:]]/)) {
639 | r = r mp;
640 | continue;
641 | }
642 | p = index(st, mp);
643 | while(p && match(substr(st, p-1, 1),/[\\[:space:]]/)) {
644 | ms = ms substr(st, 1, p-1) mp;
645 | st = substr(st, p + length(mp));
646 | p = index(st, mp);
647 | }
648 | if(!p) {
649 | r = r mp ms;
650 | continue;
651 | }
652 | ms = ms substr(st,1,p-1);
653 | r = r itag("strong", scrub(ms));
654 | st = substr(st,p+length(mp));
655 | } else if(mp == "~~") {
656 | p = index(st, mp);
657 | if(!p) {
658 | r = r mp;
659 | continue;
660 | }
661 | while(p && substr(st, p-1, 1) == "\\") {
662 | ms = ms substr(st, 1, p-1) mp;
663 | st = substr(st, p + length(mp));
664 | p = index(st, mp);
665 | }
666 | ms = ms substr(st,1,p-1);
667 | r = r itag("del", scrub(ms));
668 | st = substr(st,p+length(mp));
669 | } else if(match(mp, /`+/)) {
670 | p = index(st, mp);
671 | if(!p) {
672 | r = r mp;
673 | continue;
674 | }
675 | ms = substr(st,1,p-1);
676 | r = r itag("code", escape(ms));
677 | st = substr(st,p+length(mp));
678 | } else if(Mathjax && match(mp, /\$+/)) {
679 | tok = substr(mp, RSTART, RLENGTH);
680 | p = index(st, mp);
681 | if(!p) {
682 | r = r mp;
683 | continue;
684 | }
685 | ms = substr(st,1,p-1);
686 | r = r tok escape(ms) tok;
687 | st = substr(st,p+length(mp));
688 | HasMathjax = 1;
689 | } else if(Mathjax && mp=="\\(") {
690 | p = index(st, "\\)");
691 | if(!p) {
692 | r = r mp;
693 | continue;
694 | }
695 | ms = substr(st,1,p-1);
696 | r = r "\\(" escape(ms) "\\)";
697 | st = substr(st,p+length(mp));
698 | HasMathjax = 1;
699 | } else if(mp == ">") {
700 | r = r ">";
701 | } else if(mp == "<") {
702 |
703 | p = index(st, ">");
704 | if(!p) {
705 | r = r "<";
706 | continue;
707 | }
708 | tg = substr(st, 1, p - 1);
709 | if(match(tg,/^[[:alpha:]]+[[:space:]]/)) {
710 | a = trim(substr(tg,RSTART+RLENGTH-1));
711 | tg = substr(tg,1,RLENGTH-1);
712 | } else
713 | a = "";
714 |
715 | if(match(tolower(tg), HTML_tags)) {
716 | if(!match(tg, /\//)) {
717 | if(match(a, /class="/)) {
718 | sub(/class="/, "class=\"dawk-ex ", a);
719 | } else {
720 | if(a)
721 | a = a " class=\"dawk-ex\""
722 | else
723 | a = "class=\"dawk-ex\""
724 | }
725 | r = r "<" tg " " a ">";
726 | } else
727 | r = r "<" tg ">";
728 | } else if(match(tg, "^[[:alpha:]]+://[[:graph:]]+$")) {
729 | if(!a) a = tg;
730 | r = r "" a " ";
731 | } else if(match(tg, "^[[:graph:]]+@[[:graph:]]+$")) {
732 | if(!a) a = tg;
733 | r = r "" obfuscate(a) " ";
734 | } else {
735 | r = r "<";
736 | continue;
737 | }
738 |
739 | st = substr(st, p + 1);
740 | } else if(mp == "&") {
741 | if(match(st, /^[#[:alnum:]]+;/)) {
742 | r = r "&" substr(st, 1, RLENGTH);
743 | st = substr(st, RLENGTH+1);
744 | } else {
745 | r = r "&";
746 | }
747 | }
748 | }
749 | return r st;
750 | }
751 |
752 | function push(newmode) {Stack[StackTop++] = Mode; Mode = newmode;}
753 | function pop() {Mode = Stack[--StackTop];Buf = ""; return Mode;}
754 | function heading(level, st, res, href, u, text) {
755 | if(level > 6) level = 6;
756 | st = trim(st);
757 | href = tolower(st);
758 | href = strip_tags(href);
759 | gsub(/[^-_ [:alnum:]]+/, "", href);
760 | gsub(/[[:space:]]/, "-", href);
761 | if(TitleUrls[href]) {
762 | for(u = 1; TitleUrls[href "-" u]; u++);
763 | href = href "-" u;
764 | }
765 | TitleUrls[href] = "#" href;
766 |
767 | text = "" (TopLinks?" ↑ Top ":"");
768 |
769 | res = tag("h" level, text, "id=\"" href "\"");
770 | for(;ToCLevel < level; ToCLevel++) {
771 | ToC_ID++;
772 | if(ToCLevel < HideToCLevel) {
773 | ToC = ToC "▼ ";
774 | ToC = ToC "";
775 | } else {
776 | ToC = ToC "► ";
777 | ToC = ToC "";
778 | }
779 | }
780 | for(;ToCLevel > level; ToCLevel--)
781 | ToC = ToC " ";
782 | ToC = ToC "" st " \n";
783 | ToCLevel = level;
784 | return res;
785 | }
786 | function process_table_row(st ,cols, i) {
787 | if(match(st, /^[[:space:]]*\|/))
788 | st = substr(st, RSTART+RLENGTH);
789 | if(match(st, /\|[[:space:]]*$/))
790 | st = substr(st, 1, RSTART - 1);
791 | st = trim(st);
792 |
793 | if(match(st, /^([[:space:]:|]|---+)*$/)) {
794 | IsHeaders[Row-1] = 1;
795 | cols = split(st, A, /[[:space:]]*\|[[:space:]]*/)
796 | for(i = 1; i <= cols; i++) {
797 | if(match(A[i], /^:-*:$/))
798 | Align[i] = "center";
799 | else if(match(A[i], /^-*:$/))
800 | Align[i] = "right";
801 | else if(match(A[i], /^:-*$/))
802 | Align[i] = "left";
803 | }
804 | return;
805 | }
806 |
807 | cols = split(st, A, /[[:space:]]*\|[[:space:]]*/);
808 | for(i = 1; i <= cols; i++) {
809 | Table[Row, i] = A[i];
810 | }
811 | NCols[Row] = cols;
812 | if(cols > MaxCols)
813 | MaxCols = cols;
814 | IsHeaders[Row] = 0;
815 | Row++;
816 | }
817 | function end_table( r,c,t,a,s) {
818 | for(r = 1; r < Row; r++) {
819 | t = IsHeaders[r] ? "th" : "td"
820 | s = s ""
821 | for(c = 1; c <= NCols[r]; c++) {
822 | a = Align[c];
823 | if(a)
824 | s = s "<" t " align=\"" a "\">" scrub(Table[r,c]) "" t ">"
825 | else
826 | s = s "<" t ">" scrub(Table[r,c]) "" t ">"
827 | }
828 | s = s " \n"
829 | }
830 | return tag("table", s, "class=\"da\"");
831 | }
832 | function end_pre(buffer, res, plang, mmaid) {
833 | if(length(trim(buffer)) > 0) {
834 | plang = ""; mmaid=0;
835 | if(match(Preterm, /^[[:space:]]*```+/)) {
836 | plang = trim(substr(Preterm, RSTART+RLENGTH));
837 | if(plang) {
838 | if(plang == "mermaid") {
839 | mmaid = 1;
840 | HasMermaid = 1;
841 | } else if(Highlight) {
842 | HasHighlight = 1;
843 | if(plang == "auto")
844 | plang = "class=\"highlight\"";
845 | else {
846 | if(language_supported(plang)) {
847 | plang = "class=\"highlight language-" plang "\"";
848 | } else {
849 | plang = "class=\"nohighlight\"";
850 | }
851 | }
852 | } else {
853 | plang = "class=\"nohighlight\"";
854 | }
855 | }
856 | }
857 | if(mmaid && Mermaid)
858 | res = tag("div", buffer, "class=\"mermaid\"");
859 | else {
860 | if(!plang) plang = "class=\"nohighlight\"";
861 | res = tag("pre", tag("code", escape(buffer), plang));
862 | if(Css)
863 | res = tag("div", tag("div", tag("span", "Copied","class=\"code-message hidden\"") tag("span", svg("copy") ,"class=\"code-button\""), "class=\"code-toolbar no-print\"") res, "class=\"code-block\"");
864 | }
865 | }
866 | return res;
867 | }
868 | function end_blockquote(buffer, tmp) {
869 | if(match(buffer, /^[[:space:]]*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]/)) {
870 | tmp = tolower(trim(substr(buffer, RSTART, RLENGTH)));
871 | buffer = substr(buffer, RSTART+RLENGTH);
872 | gsub(/[^[:alpha:]]/,"",tmp);
873 | return tag("blockquote", tag("p", svg(tmp, icon_color(tmp)) " " toupper(substr(tmp,0,1)) substr(tmp,2) , "class=\"alert-head\"") tag("p", trim(buffer)), "class=\"alert alert-" tmp "\"");
874 | }
875 | return tag("blockquote", tag("p", trim(buffer)));
876 | }
877 | function end_dl(buffer, n,i,dd,res) {
878 | gsub(/\n\n+/, "\n\n", buffer);
879 | n = split(trim(buffer), Dl_Rows, /\n/);
880 | for(i = 1; i <= n; i++) {
881 | if(match(Dl_Rows[i], /^[[:space:]]:[[:space:]]/)) {
882 | if(dd) res = res tag("dd", tag("p", scrub(dd)));
883 | sub(/^[[:space:]]:[[:space:]]/,"", Dl_Rows[i]);
884 | dd = Dl_Rows[i];
885 | } else if(match(Dl_Rows[i], /^( |\t)[[:space:]]+[^[:space:]]/)) {
886 | if(dd) {
887 | dd = dd "\n" trim(Dl_Rows[i]);
888 | } else {
889 | res = res tag("dt", scrub(Dl_Rows[i]));
890 | }
891 | } else if(match(Dl_Rows[i], /^$/)) {
892 | if(dd) dd = dd "";
893 | } else {
894 | if(dd) {
895 | res = res tag("dd", tag("p", scrub(dd)));
896 | dd = "";
897 | }
898 | res = res tag("dt", scrub(Dl_Rows[i]));
899 | }
900 | }
901 | if(dd) res = res tag("dd", tag("p", scrub(dd)));
902 | return tag("dl", res);
903 | }
904 | function make_toc(st, r,p,dis,t,n,tocBody) {
905 | if(!ToC) return st;
906 | for(;ToCLevel > 1;ToCLevel--)
907 | ToC = ToC "
";
908 |
909 | tocBody = "\n";
910 |
911 | p = match(st, /!\[toc[-+]?\]/);
912 | while(p) {
913 | if(substr(st,RSTART-1,1) == "\\") {
914 | r = r substr(st,1,RSTART-2) substr(st,RSTART,RLENGTH);
915 | st = substr(st,RSTART+RLENGTH);
916 | p = match(st, /!\[toc[-+]?\]/);
917 | continue;
918 | }
919 |
920 | ++n;
921 | dis = index(substr(st,RSTART,RLENGTH),"+");
922 | t = "\nContents \n" \
923 | tocBody " ";
924 | t = t "\n" tocBody "
"
925 | r = r substr(st,1,RSTART-1);
926 | r = r t;
927 | st = substr(st,RSTART+RLENGTH);
928 | p = match(st, /!\[toc[-+]?\]/);
929 | }
930 | return r st;
931 | }
932 | function fix_links(st, lt,ld,lr,url,img,res,rx,pos,pre) {
933 | do {
934 | pre = match(st, /<(pre|code)>/); # Don't substitute in or blocks
935 | pos = match(st, /\[[^\]]+\]/);
936 | if(!pos)break;
937 | if(pre && pre < pos) {
938 | match(st, /<\/(pre|code)>/);
939 | res = res substr(st,1,RSTART+RLENGTH);
940 | st = substr(st, RSTART+RLENGTH+1);
941 | continue;
942 | }
943 | img=substr(st,RSTART-1,1)=="!";
944 | if(substr(st, RSTART-(img?2:1),1)=="\\") {
945 | res = res substr(st,1,RSTART-(img?3:2));
946 | if(img && substr(st,RSTART,RLENGTH)=="[toc]")res=res "\\";
947 | res = res substr(st,RSTART-(img?1:0),RLENGTH+(img?1:0));
948 | st = substr(st, RSTART + RLENGTH);
949 | continue;
950 | }
951 | res = res substr(st, 1, RSTART-(img?2:1));
952 | rx = substr(st, RSTART, RLENGTH);
953 | st = substr(st, RSTART+RLENGTH);
954 | if(match(st, /^[[:space:]]*\([^)]+\)/)) {
955 | lt = substr(rx, 2, length(rx) - 2);
956 | match(st, /\([^)]+\)/);
957 | url = substr(st, RSTART+1, RLENGTH-2);
958 | st = substr(st, RSTART+RLENGTH);
959 | ld = "";
960 | if(match(url,/[[:space:]]+["']/)) {
961 | ld = url;
962 | url = substr(url, 1, RSTART - 1);
963 | match(ld,/["']/);
964 | delim = substr(ld, RSTART, 1);
965 | if(match(ld,delim ".*" delim))
966 | ld = substr(ld, RSTART+1, RLENGTH-2);
967 | } else ld = "";
968 | if(img)
969 | res = res " ";
970 | else
971 | res = res "" lt " ";
972 | } else if(match(st, /^[[:space:]]*\[[^\]]*\]/)) {
973 | lt = substr(rx, 2, length(rx) - 2);
974 | match(st, /\[[^\]]*\]/);
975 | lr = trim(tolower(substr(st, RSTART+1, RLENGTH-2)));
976 | if(!lr) {
977 | lr = tolower(trim(lt));
978 | if(LinkDescs[lr]) lt = LinkDescs[lr];
979 | }
980 | st = substr(st, RSTART+RLENGTH);
981 | url = LinkUrls[lr];
982 | ld = LinkDescs[lr];
983 | if(img)
984 | res = res " ";
985 | else if(url)
986 | res = res "" lt " ";
987 | else
988 | res = res "[" lt "][" lr "]";
989 | } else
990 | res = res (img?"!":"") rx;
991 | } while(pos > 0);
992 | return res st;
993 | }
994 | function fix_footnotes(st, r,p,n,i,d,fn,fc) {
995 | p = match(st, /\[\^[^\]]+\]/);
996 | while(p) {
997 | if(substr(st,RSTART-2,1) == "\\") {
998 | r = r substr(st,1,RSTART-3) substr(st,RSTART,RLENGTH);
999 | st = substr(st,RSTART+RLENGTH);
1000 | p = match(st, /\[\^[^\]]+\]/);
1001 | continue;
1002 | }
1003 | r = r substr(st,1,RSTART-1);
1004 | d = substr(st,RSTART+2,RLENGTH-3);
1005 | n = tolower(d);
1006 | st = substr(st,RSTART+RLENGTH);
1007 | if(Footnote[tolower(n)]) {
1008 | if(!fn[n]) fn[n] = ++fc;
1009 | d = Footnote[n];
1010 | } else {
1011 | Footnote[n] = scrub(d);
1012 | if(!fn[n]) fn[n] = ++fc;
1013 | }
1014 | footname[fc] = n;
1015 | d = strip_tags(d);
1016 | if(length(d) > 20) d = substr(d,1,20) "…";
1017 | r = r " ";
1018 | p = match(st, /\[\^[^\]]+\]/);
1019 | }
1020 | for(i=1;i<=fc;i++)
1021 | footnotes = footnotes "\n";
1024 | return r st;
1025 | }
1026 | function fix_abbrs(str, st,k,r,p) {
1027 | for(k in Abbrs) {
1028 | r = "";
1029 | st = str;
1030 | t = escape(Abbrs[toupper(k)]);
1031 | gsub(/&/,"\\&", t);
1032 | p = match(st,"[^[:alnum:]]" k "[^[:alnum:]]");
1033 | while(p) {
1034 | r = r substr(st, 1, RSTART);
1035 | r = r "" k " ";
1036 | st = substr(st, RSTART+RLENGTH-1);
1037 | p = match(st,"[^[:alnum:]]" k "[^[:alnum:]]");
1038 | }
1039 | str = r st;
1040 | }
1041 | return str;
1042 | }
1043 | function tag(t, body, attr) {
1044 | if(attr)
1045 | attr = " " trim(attr);
1046 | # https://www.w3.org/TR/html5/grouping-content.html#the-p-element
1047 | if(t == "p" && (match(body, /<\/?(div|table|blockquote|dl|ol|ul|h[[:digit:]]|hr|pre)[>[:space:]]/))|| (match(body,/!\[toc\]/) && substr(body, RSTART-1,1) != "\\"))
1048 | return "<" t attr ">" body "\n";
1049 | else
1050 | return "<" t attr ">" body "" t ">\n";
1051 | }
1052 | function itag(t, body) {
1053 | return "<" t ">" body "" t ">";
1054 | }
1055 | function language_supported(lang) {
1056 | for(l in LangsCommon) {
1057 | if(LangsCommon[l] == lang) return 1;
1058 | }
1059 | for(l in LangsExtra) {
1060 | if(LangsExtra[l] == lang) {
1061 | AdditionalLangs[lang]++;
1062 | return 1;
1063 | }
1064 | }
1065 | return 0;
1066 | }
1067 | function obfuscate(e, r,i,t,o) {
1068 | for(i = 1; i <= length(e); i++) {
1069 | t = substr(e,i,1);
1070 | r = int(rand() * 100);
1071 | if(r > 50)
1072 | o = o sprintf("%02X;", _ord[t]);
1073 | else if(r > 10)
1074 | o = o sprintf("%d;", _ord[t]);
1075 | else
1076 | o = o t;
1077 | }
1078 | return o;
1079 | }
1080 | function make_css( css,ss,hr,bg1,bg2,bg3,bg4,ff,fs,i,lt,dt,pt) {
1081 |
1082 | css["body"] = "color:var(--color);background:var(--background);font-family:%font-family%;font-size:%font-size%;line-height:1.5em;" \
1083 | "padding:1em 2em;width:80%;max-width:%maxwidth%;margin:0 auto;min-height:100%;float:none;";
1084 | css["h1"] = "border-bottom:1px solid var(--heading);padding:0.3em 0.1em;";
1085 | css["h1 a"] = "color:var(--heading);";
1086 | css["h2"] = "color:var(--heading);border-bottom:1px solid var(--heading);padding:0.2em 0.1em;";
1087 | css["h2 a"] = "color:var(--heading);";
1088 | css["h3"] = "color:var(--heading);border-bottom:1px solid var(--heading);padding:0.1em 0.1em;";
1089 | css["h3 a"] = "color:var(--heading);";
1090 | css["h4,h5,h6"] = "padding:0.1em 0.1em;";
1091 | css["h4 a,h5 a,h6 a"] = "color:var(--heading);";
1092 | css["h1,h2,h3,h4,h5,h6"] = "font-weight:bolder;line-height:1.25em;";
1093 | css["h4"] = "border-bottom:1px solid var(--heading)";
1094 | css["p"] = "margin:0.5em 0.1em;"
1095 | css["hr"] = "background:var(--color);height:1px;border:0;"
1096 | css["a"] = "color:var(--alt-color);";
1097 | css["a.top"] = "font-size:x-small;text-decoration:initial;float:right;";
1098 | css["a.header svg"] = "opacity:0;";
1099 | css["a.header:hover svg"] = "opacity:1;";
1100 | css["a.header"] = "text-decoration: none;";
1101 | css["a.dark-toggle"] = "float:right; cursor: pointer; font-size: small; padding: 0.3em 0.5em 0.5em 0.5em; font-family: monospace; border-radius: 3px;";
1102 | css["a.dark-toggle:hover"] = "background:var(--alt-background);";
1103 | css[".toc-button"] = "color:var(--alt-color);cursor:pointer;font-size:small;padding: 0.3em 0.5em 0.5em 0.5em;font-family:monospace;border-radius:3px;";
1104 | css["a.toc-button:hover"] = "background:var(--alt-background);";
1105 | css["a.footnote"] = "font-size:smaller;text-decoration:initial;";
1106 | css["a.footnote-back"] = "text-decoration:initial;font-size:x-small;font-style:italic;";
1107 | css["strong,b"] = "color:var(--color)";
1108 | css["code"] = "color:var(--alt-color);font-weight:bold;";
1109 | css["blockquote"] = "margin: 0.25em 0.5em;color:var(--alt-color);border-left:0.2em solid var(--alt-color);padding:0.25em 0.5em;overflow-x:auto;";
1110 | css["pre:has(code.nohighlight)"] = "color:var(--alt-color);background:var(--alt-background);line-height:1.3em;margin:0.25em 0.5em;padding:1em;overflow-x:auto;";
1111 | css["table.dawk-ex"] = "border-collapse:collapse;margin:0.5em;";
1112 | css["th.dawk-ex,td.dawk-ex"] = "padding:0.5em 0.75em;border:1px solid var(--heading);";
1113 | css["th.dawk-ex"] = "color:var(--heading);border:1px solid var(--heading);border-bottom:2px solid var(--heading);";
1114 | css["tr.dawk-ex:nth-child(odd)"] = "background-color:var(--alt-background);";
1115 | css["table.da"] = "border-collapse:collapse;margin:0.5em;";
1116 | css["table.da th,td"] = "padding:0.5em 0.75em;border:1px solid var(--heading);";
1117 | css["table.da th"] = "color:var(--heading);border:1px solid var(--heading);border-bottom:2px solid var(--heading);";
1118 | css["table.da tr:nth-child(odd)"] = "background-color:var(--alt-background);";
1119 | css["div.dawk-ex"] = "padding:0.5em;";
1120 | css["caption.dawk-ex"] = "padding:0.5em;font-style:italic;";
1121 | css["dl"] = "margin:0.5em;";
1122 | css["dt"] = "font-weight:bold;";
1123 | css["dd"] = "padding:0.2em;";
1124 | css["mark.dawk-ex"] = "color:var(--alt-background);background-color:var(--heading);";
1125 | css["del.dawk-ex,s.dawk-ex"] = "color:var(--heading);";
1126 | css["div#table-of-contents"] = "padding:0;font-size:smaller;";
1127 | css["abbr"] = "cursor:help;";
1128 | css["ol.footnotes"] = "font-size:small;color:var(--alt-color)";
1129 | css[".fade"] = "color:var(--alt-background);";
1130 | css[".highlight"] = "color:var(--alt-color);background-color:var(--alt-background);";
1131 | css["summary"] = "cursor:pointer;";
1132 | css["ul.toc"] = "list-style-type:none;";
1133 | css["details.credits"] = "opacity: 0.7;font-size: xx-small; border-top: 1px solid var(--heading);margin-top: 4em;";
1134 | css["details.credits summary"] = "font-style: italic;";
1135 |
1136 | css["p.alert-head"] = "font-weight: bolder;";
1137 | css["p.alert-head svg"] = "margin-right: 0.5em;";
1138 | css["blockquote.alert"] = "background: var(--alt-background);";
1139 | css["blockquote.alert-note"] = "border-left:0.3em solid " icon_color("note") ";";
1140 | css["blockquote.alert-note .alert-head"] = "color: " icon_color("note") ";";
1141 | css["blockquote.alert-tip"] = "border-left:0.3em solid " icon_color("tip") ";";
1142 | css["blockquote.alert-tip .alert-head"] = "color: " icon_color("tip") ";";
1143 | css["blockquote.alert-important"] = "border-left:0.3em solid " icon_color("important") ";";
1144 | css["blockquote.alert-important .alert-head"] = "color: " icon_color("important") ";";
1145 | css["blockquote.alert-warning"] = "border-left:0.3em solid " icon_color("warning") ";";
1146 | css["blockquote.alert-warning .alert-head"] = "color: " icon_color("warning") ";";
1147 | css["blockquote.alert-caution"] = "border-left:0.3em solid " icon_color("caution") ";";
1148 | css["blockquote.alert-caution .alert-head"] = "color: " icon_color("caution") ";";
1149 |
1150 | css["div.code-block .code-button"] = "border:2px solid rgb(from var(--alt-color) r g b / 20%);background: var(--background);" \
1151 | "width:16px;height:16px;" \
1152 | "cursor:pointer;padding:4px;border-radius:2px;opacity:0;" \
1153 | "transition-property: opacity; transition-duration: .25s;";
1154 | css["div.code-block:hover .code-button"] = "opacity:1 !important;";
1155 | css["div.code-block"] = "position: sticky;";
1156 | css["div.code-toolbar"] = "display: flex;justify-content: flex-end;width: 100%;position: absolute;right: 12px; top: 4px;";
1157 | css[".hidden"] = "opacity:0; transition-property: opacity; transition-duration: .5s;";
1158 | css["span.code-message"] = "font-size: smaller; padding: 0.2em 0.5em;";
1159 |
1160 | # This is a trick to prevent page-breaks immediately after headers
1161 | # https://stackoverflow.com/a/53742871/115589
1162 | css["blockquote,code,pre,table"] = "break-inside: avoid;break-before: auto;"
1163 | css["section"] = "break-inside: avoid;break-before: auto;"
1164 | css["h1,h2,h3,h4"] = "break-inside: avoid;";
1165 | css["h1::after,h2::after,h3::after,h4::after"] = "content: \"\";display: block;height: 200px;margin-bottom: -200px;";
1166 |
1167 | if(NumberHeadings) {
1168 | if(NumberH1s) {
1169 | css["body"] = css["body"] "counter-reset: h1 toc1;";
1170 | css["h1"] = css["h1"] "counter-reset: h2 h3 h4;";
1171 | css["h2"] = css["h2"] "counter-reset: h3 h4;";
1172 | css["h3"] = css["h3"] "counter-reset: h4;";
1173 | css["h1::before"] = "content: counter(h1) \" \"; counter-increment: h1; margin-right: 10px;";
1174 | css["h2::before"] = "content: counter(h1) \".\"counter(h2) \" \";counter-increment: h2; margin-right: 10px;";
1175 | css["h3::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;";
1176 | css["h4::before"] = "content: counter(h1) \".\"counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;";
1177 |
1178 | css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;";
1179 | css["li.toc-2"] = "counter-reset: toc3 toc4;";
1180 | css["li.toc-3"] = "counter-reset: toc4;";
1181 | css["a.toc-1::before"] = "content: counter(h1) \" \";counter-increment: toc1;";
1182 | css["a.toc-2::before"] = "content: counter(h1) \".\" counter(toc2) \" \";counter-increment: toc2;";
1183 | css["a.toc-3::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \" \";counter-increment: toc3;";
1184 | css["a.toc-4::before"] = "content: counter(h1) \".\" counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \" \";counter-increment: toc4;";
1185 |
1186 | } else {
1187 | css["h1"] = css["h1"] "counter-reset: h2 h3 h4;";
1188 | css["h2"] = css["h2"] "counter-reset: h3 h4;";
1189 | css["h3"] = css["h3"] "counter-reset: h4;";
1190 | css["h2::before"] = "content: counter(h2) \" \";counter-increment: h2; margin-right: 10px;";
1191 | css["h3::before"] = "content: counter(h2) \".\"counter(h3) \" \";counter-increment: h3; margin-right: 10px;";
1192 | css["h4::before"] = "content: counter(h2) \".\"counter(h3)\".\"counter(h4) \" \";counter-increment: h4; margin-right: 10px;";
1193 |
1194 | css["li.toc-1"] = "counter-reset: toc2 toc3 toc4;";
1195 | css["li.toc-2"] = "counter-reset: toc3 toc4;";
1196 | css["li.toc-3"] = "counter-reset: toc4;";
1197 | css["a.toc-2::before"] = "content: counter(toc2) \" \";counter-increment: toc2;";
1198 | css["a.toc-3::before"] = "content: counter(toc2) \".\" counter(toc3) \" \";counter-increment: toc3;";
1199 | css["a.toc-4::before"] = "content: counter(toc2) \".\" counter(toc3) \".\" counter(toc4) \" \";counter-increment: toc4;";
1200 | }
1201 | }
1202 |
1203 | if(Highlight && HasHighlight) {
1204 | css["pre code.hljs"] = "display:block;overflow-x: auto;padding: 1em;margin: 0.25em 0.5em -1.5em;";
1205 | css["code.hljs"] = "padding: 3px 5px; line-height:1.3em";
1206 | css[".hljs"] = "color: var(--alt-color);background: var(--alt-background)";
1207 | css[".hljs-comment,.hljs-quote"] = "color: var(--com);font-style: italic";
1208 | css[".hljs-doctag,.hljs-keyword,.hljs-formula"] = "color:var(--kwd);";
1209 | css[".hljs-section,.hljs-name,.hljs-selector-tag,.hljs-deletion,.hljs-subst"] = "color: var(--nam);";
1210 | css[".hljs-literal"] = "color: var(--lit);";
1211 | css[".hljs-string,.hljs-regexp,.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string"] = "color: var(--str);";
1212 | css[".hljs-attr,.hljs-variable,.hljs-template-variable,.hljs-type,.hljs-selector-class,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-number"] = "color: var(--var);";
1213 | css[".hljs-symbol,.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-title"] = "color: var(--sym);";
1214 | css[".hljs-built_in,.hljs-title.class_,.hljs-class .hljs-title"] = "color: var(--typ);";
1215 | css[".hljs-emphasis"] = "font-style: italic";
1216 | css[".hljs-strong"] = "font-weight: bold";
1217 | css[".hljs-link"] = "text-decoration: underline";
1218 | }
1219 |
1220 | # Font Family:
1221 | ff = "sans-serif";
1222 | fs = "11pt";
1223 |
1224 | for(i = 0; i<=255; i++)_hex[sprintf("%02X",i)]=i;
1225 |
1226 | # Light theme colors:
1227 | lt = "--color: #263053; --alt-color: #383A42; --heading: #394970; --background: #FDFDFD; --alt-background: #FAFAFA;";
1228 | # Dark theme colors:
1229 | dt = "--color: #BABFDB; --alt-color: #ABB2BF; --heading: #919DC5; --background: #141825; --alt-background: #282C34;";
1230 |
1231 | if(Highlight && HasHighlight) {
1232 | # Colors for the highlight.js syntax highlighting.
1233 | # They come from the Atom One Dark and Light themes by Daniel Gamage
1234 | # from the original highlight.js distribution:
1235 | # https://github.com/highlightjs/highlight.js/blob/main/src/styles/atom-one-dark.css
1236 | # https://github.com/highlightjs/highlight.js/blob/main/src/styles/atom-one-light.css
1237 | lt = lt "\n --com:#a0a1a7;--kwd:#a626a4;--nam:#e45649;--lit:#0184bb;--str:#50a14f;--var:#986801;--sym:#4078f2;--typ:#c18401;";
1238 | dt = dt "\n --com:#5c6370;--kwd:#c678dd;--nam:#e06c75;--lit:#56b6c2;--str:#98c379;--var:#d19a66;--sym:#61aeee;--typ:#e6c07b;";
1239 | }
1240 |
1241 | # Print theme: Same as light theme...
1242 | pt = lt;
1243 | # ...but make sure the background is white
1244 | sub(/--background:[[:space:]]*#?[[:alnum:]]+/, "--background: white", pt);
1245 |
1246 | ss = "@media screen {\n" \
1247 | " body { " lt " }\n" \
1248 | " body.dark-theme { " dt " }\n" \
1249 | " @media (prefers-color-scheme: dark) {\n" \
1250 | " body { " dt " }\n" \
1251 | " body.light-theme { " lt " }\n" \
1252 | " }\n" \
1253 | "}\n" \
1254 | "@media print {\n" \
1255 | " body { " pt " }\n" \
1256 | " pre code.highlight.hljs {overflow-x:hidden;}\n" \
1257 | " pre code {font-size:smaller;}\n" \
1258 | "}\n" \
1259 | ".print-only {display:none}\n" \
1260 | "@media print {\n" \
1261 | " .no-print {display: none !important;}\n" \
1262 | " .print-only {display:initial;}\n" \
1263 | " pre {overflow-x: clip !important;}\n" \
1264 | " a {text-decoration: none;}\n" \
1265 | "}";
1266 |
1267 | for(k in css)
1268 | ss = ss "\n" k "{" css[k] "}";
1269 | gsub(/%maxwidth%/,MaxWidth,ss);
1270 | gsub(/%font-family%/,ff,ss);
1271 | gsub(/%font-size%/,fs,ss);
1272 | gsub(/%hr%/,hr,ss);
1273 |
1274 | return ss;
1275 | }
1276 | function icon_color(which) {
1277 | if(which == "note") return "#3d88f1";
1278 | if(which == "tip") return "#029802";
1279 | if(which == "important") return "#a30fa3";
1280 | if(which == "warning") return "#ffb328";
1281 | if(which == "caution") return "#fa1c1c";
1282 | return "black";
1283 | }
1284 | function svg(which, color, size, path, body) {
1285 | # TODO: Get better at Inkscape
1286 | if(which == "moon")
1287 | path = "M 10.04 0.26 A 11.64 11.64 0 0 1 10.79 4.36 A 11.64 11.63625 0 0 1 4.01 14.94 A 8 8 0 0 0 8 16 A 8 8 0 0 0 16 8 A 8 8 0 0 0 10.04 0.26 z";
1288 | else if(which == "link")
1289 | path = "m 3.34,4.63 1.31,2.66 0,0 0.61,1.24 0.01,0 1.23,2.5 L 9.52,9.58 8.91,8.34 7.17,9.18 6.82,8.47 6.55,7.92 5.94,6.68 5.59,5.96 5.24,5.26 11.74,2.13 13.67,6.05 11.25,7.21 11.86,8.45 15.53,6.69 12.39,0.29 Z M 0.47,9.31 3.61,15.71 12.63,11.37 11.67,9.43 11.32,8.71 10.71,7.47 10.43,6.92 9.48,4.97 6.48,6.42 7.09,7.66 8.84,6.82 9.19,7.52 9.46,8.08 10.07,9.32 10.42,10.03 10.76,10.73 4.26,13.87 2.33,9.95 4.75,8.79 4.14,7.55 Z";
1290 | else if(which == "note")
1291 | path = "M 8 0 A 8 8 0 0 0 0 8 A 8 8 0 0 0 8 16 A 8 8 0 0 0 16 8 A 8 8 0 0 0 8 0 z M 8 1.52 C 11.60 1.52 14.48 4.40 14.48 8 C 14.48 11.60 11.60 14.48 8 14.48 C 4.40 14.48 1.52 11.60 1.52 8 C 1.52 4.40 4.40 1.52 8 1.52 z M 7.01 3.22 L 7.01 4.87 L 8.99 4.87 L 8.99 3.22 L 7.01 3.22 z M 6.28 5.51 L 6.28 7.15 L 7.01 7.15 L 7.01 11.45 L 6.28 11.45 L 6.28 13.09 L 7.01 13.09 L 8.99 13.09 L 9.70 13.09 L 9.70 11.45 L 8.99 11.45 L 8.99 5.51 L 8.97 5.51 L 6.28 5.51 z";
1292 | else if(which == "tip")
1293 | path = "M 8 0.06 C 4.8 0.06 2.29 2.12 2.29 6.04 C 2.29 8 4.02 8.96 5.07 10.24 C 5.34 10.58 5.56 11.13 5.77 11.75 L 6.98 11.75 C 6.71 10.93 6.38 10.04 5.96 9.52 C 5.51 8.98 4.84 8.37 4.45 7.97 C 3.79 7.3 3.43 6.81 3.43 6.04 C 3.43 4.32 3.95 3.17 4.74 2.4 C 5.53 1.63 6.64 1.21 8 1.21 C 9.36 1.21 10.53 1.63 11.36 2.41 C 12.19 3.19 12.73 4.33 12.73 6.04 C 12.73 6.75 12.33 7.25 11.59 7.94 C 11.16 8.34 10.43 8.95 9.96 9.52 C 9.7 9.85 9.49 10.27 9.34 10.63 C 9.22 10.95 9.09 11.34 8.96 11.75 L 10.16 11.75 C 10.36 11.13 10.58 10.58 10.85 10.24 C 11.9 8.96 13.88 8 13.88 6.04 C 13.88 2.12 11.2 0.06 8 0.06 z M 5.96 12.35 L 5.96 13.55 L 10.13 13.55 L 10.13 12.35 L 5.96 12.35 z M 6.56 14.21 L 6.56 15.41 L 9.54 15.41 L 9.54 14.21 L 6.56 14.21 z";
1294 | else if(which == "important")
1295 | path = "M 0 0 L 0 12.42 L 8.16 12.42 L 8.14 16 L 13.17 12.42 L 16 12.42 L 16 0 L 0 0 z M 1.52 1.52 L 14.48 1.52 L 14.48 10.9 L 12.69 10.9 L 9.68 13.04 L 9.7 10.9 L 1.52 10.9 L 1.52 1.52 z M 6.6 2.64 L 6.6 7.13 L 8.87 7.13 L 8.87 2.64 L 6.6 2.64 z M 6.6 8.11 L 6.6 10.01 L 8.87 10.01 L 8.87 8.11 L 6.6 8.11 z";
1296 | else if(which == "warning")
1297 | path = "M 8 0 L 0 15.969 L 15.97 16 L 8 -0.02 z M 8 3.26 L 13.53 14.36 L 2.43 14.34 L 8.01 3.26 z M 7.23 6.13 L 7.23 10.6 L 8.77 10.6 L 8.77 6.13 L 7.23 6.13 z M 7.23 11.69 L 7.23 13.38 L 8.77 13.38 L 8.77 11.69 L 7.23 11.69 z";
1298 | else if(which == "caution")
1299 | path = "M 4.7 0 L 0.01 4.68 L 0 11.3 L 4.68 16 L 11.3 16 L 16 11.32 L 16 4.7 L 11.32 0.01 L 4.7 0 z M 5.33 1.52 L 10.7 1.53 L 14.48 5.33 L 14.47 10.7 L 10.67 14.48 L 5.31 14.47 L 1.52 10.67 L 1.53 5.31 L 5.33 1.52 z M 6.82 2.75 L 6.82 9.58 L 9.18 9.58 L 9.18 2.75 L 6.82 2.75 z M 6.82 10.55 L 6.82 13.14 L 9.18 13.14 L 9.18 10.55 L 6.82 10.55 z";
1300 | else if(which == "copy")
1301 | path = "M 5.8 0.9 L 5.8 3.6 L 7 3.6 L 7 2 L 14 2 L 14 10 L 11.6 10 L 11.6 11 L 15.1 11 L 15.1 0.9 L 5.8 0.9 z M 1.2 4.8 L 1.2 14.9 L 10.5 14.9 L 10.5 4.8 L 1.2 4.8 z M 2.3 5.9 L 9.3 5.9 L 9.3 13.7 L 2.3 13.7 L 2.3 5.9 z";
1302 | else
1303 | path = "";
1304 |
1305 | if(!UsedSymbols[which]) {
1306 | UsedSymbols[which] = 1;
1307 | body = " ";
1308 | } else {
1309 | body = " ";
1310 | }
1311 |
1312 | if(!color) color = "var(--color)";
1313 | if(!size) size = "16";
1314 |
1315 | return "" body " "
1316 | }
1317 |
--------------------------------------------------------------------------------