├── 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 | /\/\*<< 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 `<head/>` 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 <html> 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, `<H1>` headings are not numbered by 42 | # default (because the `<H1>` would typically contain the document title). Use this to 43 | # number `<H1>`s as well. 44 | # - `-vClean=1` to treat the input file as a plain Markdown file. 45 | # You could use `./d.awk -vClean=1 README.md > README.html` to generate HTML from 46 | # your README, for example. 47 | # 48 | # I've tested it with Gawk, Nawk and Mawk. 49 | # Gawk and Nawk worked without issues, but don't use the `-W compat` 50 | # or `-W traditional` settings with Gawk. 51 | # Mawk v1.3.4 worked correctly but v1.3.3 choked on it. 52 | # 53 | # [highlight.js]: https://highlightjs.org/ 54 | # [Mermaid]: https://github.com/mermaid-js/mermaid 55 | # [MathJax]: https://www.mathjax.org/ 56 | # [github-tables]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#tables 57 | # [github-mermaid]: https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/ 58 | # [github-math]: https://github.blog/changelog/2022-05-19-render-mathematical-expressions-in-markdown/ 59 | # 60 | # ## Extensions 61 | # 62 | # - Insert a Table of Contents by using `\![toc]`. 63 | # The Table of Contents is collapsed by default: 64 | # - Use `\\![toc+]` to insert a ToC that is expanded by default; 65 | # - Use `\\![toc-]` for a collapsed ToC. 66 | # - Github-style ```` ``` ```` code blocks supported. 67 | # - Github-style `~~strikethrough~~` supported. 68 | # - [Github-style tables][github-tables] are supported. 69 | # - [GitHub-style Mermaid diagrams][github-mermaid] 70 | # - [GitHub-style mathematical expressions][github-math]: $\sqrt{3x-1}+(1+x)^2$ 71 | # - GitHub-style task lists `- [x]` are supported for documenting bugs and todo lists in code. 72 | # - The `id` attribute of anchor tags `<a>` are treated as in GitHub: 73 | # The tag's id should be the title, in lower case stripped of non-alphanumeric characters 74 | # (except hyphens and spaces) and then with all spaces replaced with hyphens. 75 | # then add -1, -2, -3 until it's unique 76 | # See [here](https://gist.github.com/asabaylus/3071099) (especially the comment by TomOnTime) 77 | # and [here](https://gist.github.com/rachelhyman/b1f109155c9dafffe618) 78 | # - A couple of ideas from MultiMarkdown: 79 | # - `\\[^footnotes]` are supported. 80 | # - `*[abbr]:` Abbreviations are supported. 81 | # - Space followed by \\ at the end of a line also forces a line break. 82 | # - Default behaviour is to have words_like_this not contain emphasis. 83 | # 84 | # Limitations: 85 | # 86 | # - You can't nest `<blockquote>`s, and they can't contain nested lists 87 | # or `pre` blocks. You can work around this by using HTML directly. 88 | # - It takes some liberties with how inline (particularly block-level) HTML is processed and not 89 | # all HTML tags supported. HTML forms and `<script/>` tags are out. 90 | # - Paragraphs in lists differ a bit from other markdowns. Use indented blank lines to get 91 | # to insert `<br>` tags to emulate paragraphs. Blank lines stop the list by inserting the 92 | # `</ul>` or `</ol>` tags. 93 | # 94 | # ## References 95 | # 96 | # - <https://tools.ietf.org/html/rfc7764> 97 | # - <http://daringfireball.net/projects/markdown/syntax> 98 | # - <https://guides.github.com/features/mastering-markdown/> 99 | # - <http://fletcher.github.io/MultiMarkdown-4/syntax> 100 | # - <http://spec.commonmark.org> 101 | # 102 | # ## License 103 | # 104 | # (c) 2016-2025 Werner Stoop 105 | # Copying and distribution of this file, with or without modification, 106 | # are permitted in any medium without royalty provided the copyright 107 | # notice and this notice are preserved. This file is offered as-is, 108 | # without any warranty. 109 | # 110 | 111 | BEGIN { 112 | 113 | # Configuration options 114 | if(Css== "") Css = 1; 115 | 116 | if(Highlight== "") Highlight = 1; 117 | if(Mermaid== "") Mermaid = 1; 118 | if(MermaidTheme== "") MermaidTheme = "neutral"; 119 | if(Mathjax=="") Mathjax = 1; 120 | 121 | if(HideToCLevel== "") HideToCLevel = 3; 122 | if(Lang == "") Lang = "en"; 123 | if(Tables == "") Tables = 1; 124 | #TopLinks = 1; 125 | #classic_underscore = 1; 126 | if(MaxWidth=="") MaxWidth="1080px"; 127 | if(NumberHeadings=="") NumberHeadings = 1; 128 | if(NumberH1s=="") NumberH1s = 0; 129 | 130 | # Definition lists are still experimental, so if they cause problems you can 131 | # disable them here, and use <dl> <dt> and <dd> tags instead 132 | if(DefLists=="") DefLists = 1; 133 | 134 | Mode = "p"; 135 | ToC = ""; ToCLevel = 1; 136 | for(i = 0; i < 128; i++) 137 | _ord[sprintf("%c", i)] = i; 138 | srand(); 139 | 140 | # Allowed HTML tags: 141 | HTML_tags = "^/?(a|abbr|b|blockquote|br|caption|cite|code|col|colgroup|column|dd|del|details|div|dl|dt|em|figcaption|figure|h[[:digit:]]+|hr|i|img|ins|li|mark|ol|p|pre|q|s|samp|small|span|strong|sub|summary|sup|table|tbody|td|tfoot|th|thead|tr|u|ul|var)$"; 142 | 143 | # Languages supported by the default highlight.js distribution: 144 | # (They're the languages in the 'Common' section of this page: https://highlightjs.org/download) 145 | split("bash c cpp csharp css diff go graphql ini java javascript json kotlin less lua makefile " \ 146 | "markdown objectivec perl php-template php plaintext python-repl python r ruby rust scss " \ 147 | "shell sql swift typescript vbnet wasm xml yaml", LangsCommon); 148 | 149 | # Languages supported by highlight.js for which additional files are needed: 150 | split("1c abnf accesslog actionscript ada angelscript apache applescript arcade arduino armasm " \ 151 | "asciidoc aspectj autohotkey autoit avrasm awk axapta basic bnf brainfuck cal capnproto " \ 152 | "ceylon clean clojure clojure-repl cmake coffeescript coq cos crmsh crystal csp d dart " \ 153 | "delphi django dns dockerfile dos dsconfig dts dust ebnf elixir elm erb erlang erlang-repl " \ 154 | "excel fix flix fortran fsharp gams gauss gcode gherkin glsl gml golo gradle groovy haml " \ 155 | "handlebars haskell haxe hsp http hy inform7 irpf90 isbl jboss-cli julia julia-repl lasso " \ 156 | "latex ldif leaf lisp livecodeserver livescript llvm lsl mathematica matlab maxima mel " \ 157 | "mercury mipsasm mizar mojolicious monkey moonscript n1ql nestedtext nginx nim nix " \ 158 | "node-repl nsis ocaml openscad oxygene parser3 pf pgsql pony powershell processing profile " \ 159 | "prolog properties protobuf puppet purebasic q qml reasonml rib roboconf routeros rsl " \ 160 | "ruleslanguage sas scala scheme scilab smali smalltalk sml sqf stan stata step21 stylus " \ 161 | "subunit taggerscript tap tcl thrift tp twig vala vbscript vbscript-html verilog vhdl vim " \ 162 | "wren x86asm xl xquery zephir", LangsExtra); 163 | } 164 | 165 | !Clean && !Multi && /\/\*\*/ { 166 | sub(/^.*\/\*/,""); 167 | Multi = 1; 168 | } 169 | Multi { 170 | if(match($0, /\*\//)) { 171 | gsub(/\*\/.*$/,""); 172 | Multi = 0; 173 | } 174 | gsub(/\r/, "", $0); 175 | 176 | gsub(/^[[:space:]]+/,"",$0); 177 | if(substr($0,1,1)=="*") { 178 | Out = Out filter(substr($0,2)); 179 | } else if(!Multi) { 180 | # Pretend there's a blank line at the end of a comment... 181 | Out = Out filter(""); 182 | } 183 | } 184 | 185 | # These are the rules for `///` single-line comments: 186 | Single && $0 !~ /\/\/\// { 187 | Out = Out filter(""); 188 | Single = 0; 189 | } 190 | !Clean && !Single && !Multi && /\/\/\// { 191 | Single = 1; 192 | Mode = "p"; 193 | } 194 | Single && /\/\/\// { 195 | st = substr($0, match($0, /\/\/\//) + 3); 196 | Out = Out filter(st); 197 | } 198 | 199 | Clean { 200 | Out = Out filter($0); 201 | } 202 | 203 | !Title { Title = FILENAME; } 204 | 205 | END { 206 | if(Title == "-") Title = "Documentation"; 207 | if(Mode == "ul" || Mode == "ol") { 208 | while(ListLevel > 1) 209 | Buf = Buf "\n</" Open[ListLevel--] ">"; 210 | Out = Out tag(Mode, Buf "\n"); 211 | } else if(Mode == "pre") { 212 | Out = Out end_pre(Buf); 213 | } else if(Mode == "table") { 214 | Out = Out end_table(); 215 | } else if(Mode == "blockquote") { 216 | Out = Out end_blockquote(Buf); 217 | } else if(Mode == "dl") { 218 | Out = Out end_dl(Buf); 219 | pop(); 220 | if(Dl_line) Out = Out filter(Dl_line); 221 | } else { 222 | Buf = trim(scrub(Buf)); 223 | if(Buf) 224 | Out = Out tag(Mode, Buf); 225 | } 226 | 227 | print "<!DOCTYPE html>\n<html lang=\"" Lang "\"><head>" 228 | print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">"; 229 | print "<title>" Title ""; 230 | if(StyleSheet) 231 | print ""; 232 | else if(Css) 233 | print ""; 234 | if(ToC && match(Out, /!\[toc[-+]?\]/)) { 235 | print ""; 251 | } 252 | print ""; 253 | 254 | if(Css) 255 | print "\n" svg("moon", "", 12) "\n Toggle Dark Mode\n"; 256 | 257 | if(Out) { 258 | Out = fix_footnotes(Out); 259 | Out = fix_links(Out); 260 | Out = fix_abbrs(Out); 261 | Out = make_toc(Out); 262 | 263 | print trim(Out); 264 | if(footnotes) { 265 | footnotes = fix_links(footnotes); 266 | print "
    \n" footnotes "
"; 267 | } 268 | } 269 | 270 | print ""; 293 | 294 | if(Highlight && HasHighlight) { 295 | tp++; 296 | print ""; 297 | for(lang in AdditionalLangs) { 298 | print ""; 299 | } 300 | print ""; 301 | } 302 | if(Mermaid && HasMermaid) { 303 | tp++; 304 | print ""; 305 | print ""; 306 | } 307 | if(Mathjax && HasMathjax) { 308 | tp++; 309 | print ""; 310 | print ""; 311 | } 312 | 313 | print "
"; 314 | print "d.awk"; 315 | print "

Documentation generated with d.awk

"; 316 | if(tp) { 317 | print "

Third party libraries:

"; 318 | print ""; 319 | print ""; 320 | if(Highlight && HasHighlight) { 321 | print ""; 322 | print ""; 323 | print ""; 324 | print ""; 325 | print ""; 326 | } 327 | if(Mermaid && HasMermaid) { 328 | print ""; 329 | print ""; 330 | print ""; 331 | print ""; 332 | print ""; 333 | } 334 | if(Mathjax && HasMathjax) { 335 | print ""; 336 | print ""; 337 | print ""; 338 | print ""; 339 | print ""; 340 | } 341 | print "
LibraryAuthorLicense
highlight.jsIvan Sagalaev and contributorsBSD 3-Clause
MermaidKnut Sveidqvist and contributorsMIT License
MathJaxDavide P. Cervone and contributorsApache 2.0
"; 342 | } 343 | print "
"; 344 | 345 | print "" 346 | } 347 | 348 | function escape(st) { 349 | gsub(/&/, "\\&", st); 350 | gsub(//, "\\>", st); 352 | return st; 353 | } 354 | function strip_tags(st) { 355 | gsub(/<\/?[^>]+>/,"",st); 356 | return st; 357 | } 358 | function trim(st) { 359 | sub(/^[[:space:]]+/, "", st); 360 | sub(/[[:space:]]+$/, "", st); 361 | return st; 362 | } 363 | function filterM(st, n,i,res) { 364 | n = split(st, Lines, /\n/); 365 | for(i = 1; i <= n; i++) { 366 | res = res filter(Lines[i]); 367 | } 368 | return res; 369 | } 370 | function filter(st, res,tmp, linkdesc, url, delim, edelim, name, def, plang, mmaid, cols, i) { 371 | if(Mode == "p") { 372 | if(match(st, /^[[:space:]]*\[[-._[:alnum:][:space:]]+\]:/)) { 373 | linkdesc = ""; LastLink = 0; 374 | match(st,/\[.*\]/); 375 | LinkRef = tolower(substr(st, RSTART+1, RLENGTH-2)); 376 | st = substr(st, RSTART+RLENGTH+2); 377 | match(st, /[^[:space:]]+/); 378 | url = substr(st, RSTART, RLENGTH); 379 | st = substr(st, RSTART+RLENGTH+1); 380 | if(match(url, /^<.*>/)) 381 | url = substr(url, RSTART+1, RLENGTH-2); 382 | if(match(st, /["'(]/)) { 383 | delim = substr(st, RSTART, 1); 384 | edelim = (delim == "(") ? ")" : delim; 385 | if(match(st, delim ".*" edelim)) 386 | linkdesc = substr(st, RSTART+1, RLENGTH-2); 387 | } 388 | LinkUrls[LinkRef] = escape(url); 389 | if(!linkdesc) LastLink = 1; 390 | LinkDescs[LinkRef] = escape(linkdesc); 391 | return; 392 | } else if(LastLink && match(st, /^[[:space:]]*["'(]/)) { 393 | match(st, /["'(]/); 394 | delim = substr(st, RSTART, 1); 395 | edelim = (delim == "(") ? ")" : delim; 396 | st = substr(st, RSTART); 397 | if(match(st, delim ".*" edelim)) 398 | LinkDescs[LinkRef] = escape(substr(st,RSTART+1,RLENGTH-2)); 399 | LastLink = 0; 400 | return; 401 | } else if(match(st, /^[[:space:]]*\[\^[-._[:alnum:][:space:]]+\]:[[:space:]]*/)) { 402 | match(st, /\[\^[[:alnum:]]+\]:/); 403 | name = substr(st, RSTART+2,RLENGTH-4); 404 | def = substr(st, RSTART+RLENGTH+1); 405 | Footnote[tolower(name)] = scrub(def); 406 | return; 407 | } else if(match(st, /^[[:space:]]*\*\[[[:alnum:]]+\]:[[:space:]]*/)) { 408 | match(st, /\[[[:alnum:]]+\]/); 409 | name = substr(st, RSTART+1,RLENGTH-2); 410 | def = substr(st, RSTART+RLENGTH+2); 411 | Abbrs[toupper(name)] = def; 412 | return; 413 | } else if(match(st, /^(( )| *\t)/) || match(st, /^[[:space:]]*```+[[:alnum:]]*/)) { 414 | Preterm = trim(substr(st, RSTART,RLENGTH)); 415 | st = substr(st, RSTART+RLENGTH); 416 | if(Buf) res = tag("p", scrub(Buf)); 417 | Buf = st; 418 | push("pre"); 419 | } else if(!trim(Prev) && match(st, /^[[:space:]]*[*-][[:space:]]*[*-][[:space:]]*[*-][-*[:space:]]*$/)) { 420 | if(Buf) res = tag("p", scrub(Buf)); 421 | Buf = ""; 422 | res = res "
\n"; 423 | } else if(match(st, /^[[:space:]]*===+[[:space:]]*$/)) { 424 | Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1)); 425 | if(Buf) res= tag("p", scrub(Buf)); 426 | if(Prev) res = res heading(1, scrub(Prev)); 427 | Buf = ""; 428 | } else if(match(st, /^[[:space:]]*---+[[:space:]]*$/)) { 429 | Buf = trim(substr(Buf, 1, length(Buf) - length(Prev) - 1)); 430 | if(Buf) res = tag("p", scrub(Buf)); 431 | if(Prev) res = res heading(2, scrub(Prev)); 432 | Buf = ""; 433 | } else if(match(st, /^[[:space:]]*#+/)) { 434 | sub(/#+[[:space:]]*$/, "", st); 435 | match(st, /#+/); 436 | ListLevel = RLENGTH; 437 | tmp = substr(st, RSTART+RLENGTH); 438 | if(Buf) res = tag("p", scrub(Buf)); 439 | res = res heading(ListLevel, scrub(trim(tmp))); 440 | Buf = ""; 441 | } else if(match(st, /^[[:space:]]*>/)) { 442 | if(Buf) res = tag("p", scrub(Buf)); 443 | Buf = scrub(trim(substr(st, RSTART+RLENGTH))); 444 | push("blockquote"); 445 | } else if(Tables && match(st, /.*\|(.*\|)+/)) { 446 | if(Buf) res = tag("p", scrub(Buf)); 447 | Row = 1; 448 | for(i = 1; i <= MaxCols; i++) 449 | Align[i] = ""; 450 | process_table_row(st); 451 | push("table"); 452 | } else if(match(st, /^[[:space:]]*([*+-]|[[:digit:]]+\.)[[:space:][]/)) { 453 | if(Buf) res = tag("p", scrub(Buf)); 454 | Buf=""; 455 | match(st, /^[[:space:]]*/); 456 | ListLevel = 1; 457 | indent[ListLevel] = RLENGTH; 458 | Open[ListLevel]=match(st, /^[[:space:]]*[*+-][[:space:]]*/)?"ul":"ol"; 459 | push(Open[ListLevel]); 460 | res = res filter(st); 461 | } else if(DefLists && match(st, /^[[:space:]]*:/)) { 462 | res = ""; 463 | Buf = Buf st "\n"; 464 | Dl_state=1; 465 | Dl_line = ""; 466 | push("dl"); 467 | } else if(match(st, /^[[:space:]]*$/)) { 468 | if(trim(Buf)) { 469 | res = tag("p", scrub(trim(Buf))); 470 | Buf = ""; 471 | } 472 | } else 473 | Buf = Buf st "\n"; 474 | LastLink = 0; 475 | } else if(Mode == "blockquote") { 476 | if(match(st, /^[[:space:]]*>[[:space:]]*$/)) 477 | Buf = Buf "\n

"; 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"; 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"; 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 = "" st " " svg("link") "" (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]) "" 825 | else 826 | s = s "<" t ">" scrub(Table[r,c]) "" 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 = "
      " ToC "
    \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 "[" fn[n] "]";
    1018 |         p = match(st, /\[\^[^\]]+\]/);
    1019 |     }
    1020 |     for(i=1;i<=fc;i++)
    1021 |         footnotes = footnotes "
  • " Footnote[footname[i]] \ 1022 | "  ↶ Back
  • \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 "\n"; 1051 | } 1052 | function itag(t, body) { 1053 | return "<" t ">" body ""; 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("&#x%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 | --------------------------------------------------------------------------------