├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── build ├── build.bat ├── dist ├── cmd.d.ts ├── cmd.js ├── cmd.ts ├── driver.js ├── mac │ └── sink-beta3 ├── posix │ └── sink-beta3 ├── repl.html ├── require.js ├── sink.d.ts ├── sink.js ├── sink.ts ├── sink_shell.d.ts ├── sink_shell.js ├── sink_shell.ts └── windows │ └── sink-beta3.exe ├── docs ├── command-line.md ├── crash-course.md ├── embedding.md ├── hash.md ├── isnative.md ├── lib.md ├── order.md ├── pickle-selfref.md ├── pickle.md ├── rand.md ├── range.md ├── shell.md ├── stacktrace.md ├── tech-info.md └── why.md ├── editor-configs └── codemirror.net │ ├── README.md │ └── codemirror-sink.js ├── lib └── require.js ├── package-lock.json ├── package.json ├── perf ├── callcnt.sink └── cat.sink ├── src ├── cmd.c ├── cmd.js ├── cmd.ts ├── driver.js ├── repl.html ├── sink.c ├── sink.h ├── sink.js ├── sink.ts ├── sink_shell.c ├── sink_shell.h ├── sink_shell.js ├── sink_shell.ts └── tsconfig.json ├── tests ├── 0.sanity │ ├── pass.txt │ └── sanity.sink ├── 1.namespaces │ ├── namespaces.sink │ └── pass.txt ├── 10.lex-memleak │ ├── lex-memleak.sink │ └── pass.txt ├── 11.version │ ├── pass.txt │ └── version.sink ├── 12.setat │ ├── pass.txt │ └── setat.sink ├── 13.alias │ ├── alias.sink │ └── pass.txt ├── 14.loop │ ├── loop.sink │ └── pass.txt ├── 15.include │ ├── include.sink │ ├── pass.txt │ ├── test1.sink │ ├── test2 │ │ └── index.sink │ └── test3.sink ├── 16.def-fail │ └── def-fail.sink ├── 17.str-interp │ ├── pass.txt │ └── str-interp.sink ├── 18.open-eof-inc │ ├── open-eof-inc.sink │ └── test.sink ├── 19.early-return │ ├── early-return.sink │ └── pass.txt ├── 2.say-as-cmd │ ├── pass.txt │ └── say-as-cmd.sink ├── 20.vararg-return │ ├── pass.txt │ └── vararg-return.sink ├── 21.expr-keyend │ ├── expr-keyend.sink │ └── pass.txt ├── 22.splicing │ ├── pass.txt │ └── splicing.sink ├── 23.str-escape │ ├── pass.txt │ └── str-escape.sink ├── 24.large-slices │ ├── large-slices.sink │ └── pass.txt ├── 25.assign-self │ ├── assign-self.sink │ └── pass.txt ├── 26.tail-call │ ├── pass.txt │ └── tail-call.sink ├── 27.range │ ├── pass.txt │ └── range.sink ├── 28.str-split │ ├── pass.txt │ └── str-split.sink ├── 29.no-newline │ ├── no-newline.sink │ └── pass.txt ├── 3.math │ ├── math.sink │ └── pass.txt ├── 30.list-sort │ ├── list-sort.sink │ └── pass.txt ├── 31.range-bug │ ├── pass.txt │ └── range-bug.sink ├── 32.num-ints │ ├── num-ints.sink │ └── pass.txt ├── 33.str-replace │ ├── pass.txt │ └── str-replace.sink ├── 34.str-beginsends │ ├── pass.txt │ └── str-beginsends.sink ├── 35.str-pad │ ├── pass.txt │ └── str-pad.sink ├── 36.str-find │ ├── pass.txt │ └── str-find.sink ├── 37.str-case │ ├── pass.txt │ └── str-case.sink ├── 38.str-trimrevrep │ ├── pass.txt │ └── str-trimrevrep.sink ├── 39.str-hash │ ├── pass.txt │ └── str-hash.sink ├── 4.list-ops │ ├── list-ops.sink │ └── pass.txt ├── 40.utf8 │ ├── pass.txt │ └── utf8.sink ├── 41.struct │ ├── pass.txt │ └── struct.sink ├── 42.circular │ ├── circular.sink │ └── pass.txt ├── 43.include-err │ ├── include-err.sink │ └── test.sink ├── 44.tonum │ ├── pass.txt │ └── tonum.sink ├── 45.include-def │ ├── include-def.sink │ ├── pass.txt │ └── test.sink ├── 46.num-hex │ ├── num-hex.sink │ └── pass.txt ├── 47.valid-json │ ├── pass.txt │ └── valid-json.sink ├── 48.valid-bin │ ├── pass.txt │ └── valid-bin.sink ├── 49.pickle-json │ ├── pass.txt │ └── pickle-json.sink ├── 5.multi-push │ ├── multi-push.sink │ └── pass.txt ├── 50.pickle-bin │ ├── pass.txt │ └── pickle-bin.sink ├── 51.pickle-copy │ ├── pass.txt │ └── pickle-copy.sink ├── 52.reassign │ ├── pass.txt │ └── reassign.sink ├── 53.pickle-ref │ ├── pass.txt │ └── pickle-ref.sink ├── 54.pickle-val │ ├── pass.txt │ └── pickle-val.sink ├── 55.int-sizes │ ├── int-sizes.sink │ └── pass.txt ├── 56.all-args │ ├── all-args.sink │ └── pass.txt ├── 57.num-nans │ ├── num-nans.sink │ └── pass.txt ├── 58.label-bug │ ├── label-bug.sink │ └── pass.txt ├── 59.large-cat │ ├── large-cat.sink │ └── pass.txt ├── 6.lvalues │ ├── lvalues.sink │ └── pass.txt ├── 60.enums │ ├── enums.sink │ └── pass.txt ├── 61.range-override │ ├── pass.txt │ └── range-override.sink ├── 62.for-novars │ ├── for-novars.sink │ └── pass.txt ├── 63.bitops │ ├── bitops.sink │ └── pass.txt ├── 64.include-plus │ ├── include-plus.sink │ ├── pass.txt │ └── test.sink ├── 65.embed │ ├── data1.txt │ ├── embed.sink │ ├── pass.txt │ └── test │ │ ├── data2.txt │ │ └── index.sink ├── 66.static-cat │ ├── pass.txt │ └── static-cat.sink ├── 67.stacktrace │ ├── pass.txt │ └── stacktrace.sink ├── 68.lookup-bug │ ├── lookup-bug.sink │ └── pass.txt ├── 69.gc │ ├── gc.sink │ └── pass.txt ├── 7.varargs │ ├── pass.txt │ └── varargs.sink ├── 70.big-str-split │ ├── big-str-split.sink │ └── pass.txt ├── 71.var-reinit │ ├── pass.txt │ └── var-reinit.sink ├── 72.no-def │ └── no-def.sink ├── 73.no-label │ └── no-label.sink ├── 74.d-error │ ├── d-error.sink │ └── testinc-d.sink ├── 75.rand-range │ ├── pass.txt │ └── rand-range.sink ├── 76.isnative │ ├── isnative.sink │ └── pass.txt ├── 8.open-eof │ └── open-eof.sink ├── 9.rand │ ├── pass.txt │ └── rand.sink └── run.sink └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # see: http://editorconfig.org 2 | # 3 | # (c) Copyright 2017, Sean Connelly (@voidqk), http://syntheti.cc 4 | # MIT License 5 | 6 | root = true 7 | 8 | [*] 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | charset = utf-8 13 | indent_style = tab 14 | indent_size = 4 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tgt/ 2 | node_modules/ 3 | 4 | # random crap that tends to appear over time 5 | Thumbs.db 6 | .DS_Store 7 | .DS_Store? 8 | *.bak 9 | *~ 10 | *# 11 | *.orig 12 | desktop.ini 13 | *.swp 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD Zero Clause License 2 | 3 | Copyright (c) 2024 by Sean Connelly (https://sean.fun) 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | sink 2 | ==== 3 | 4 | Sink is a minimal programming language for embedding small scripts in larger programs, with 5 | TypeScript and C99 implementations. It is specifically designed to be small, simple, and easily 6 | embeddable, with support for a REPL as used in debug consoles. 7 | 8 | **Note: sink is currently in [beta](https://en.wikipedia.org/wiki/Software_release_life_cycle#Beta)** 9 | 10 | ### Sink the Language 11 | 12 | * [Crash Course](https://github.com/velipso/sink/blob/master/docs/crash-course.md) (Tutorial) 13 | ← Read This 14 | * [Online Demo](https://sean.fun/m/sink/dist/repl.html) 15 | * [Standard Library](https://github.com/velipso/sink/blob/master/docs/lib.md) 16 | 17 | ### Sink for Embedding 18 | 19 | * [Guide to Embedding](https://github.com/velipso/sink/blob/master/docs/embedding.md) 20 | 21 | ### Sink Binaries (BETA 3) 22 | 23 | * [Mac OSX](https://github.com/velipso/sink/raw/master/dist/mac/sink-beta3) 24 | * [Linux](https://github.com/velipso/sink/raw/master/dist/posix/sink-beta3) 25 | * [Windows](https://github.com/velipso/sink/raw/master/dist/windows/sink-beta3.exe) 26 | 27 | ### Details 28 | 29 | * [Why Another Language](https://github.com/velipso/sink/blob/master/docs/why.md) 30 | * [Technical Information](https://github.com/velipso/sink/blob/master/docs/tech-info.md) 31 | -------------------------------------------------------------------------------- /build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | C_OPTS="-fwrapv -Werror" 6 | 7 | pushd "$(dirname "$0")" > /dev/null 8 | SCRIPT_DIR="$(pwd)" 9 | popd > /dev/null 10 | SRC_DIR=$SCRIPT_DIR/src 11 | TGT_DIR=$SCRIPT_DIR/tgt 12 | DIST_DIR=$TGT_DIR/dist 13 | LIB_DIR=$SCRIPT_DIR/lib 14 | DIST_DIR=$SCRIPT_DIR/dist 15 | 16 | BUILD_TS=0 17 | BUILD_C=0 18 | BUILD_DEBUG=0 19 | BUILD_MEM=0 20 | 21 | case "$1" in 22 | "all") 23 | BUILD_TS=1 24 | BUILD_C=1 25 | if [ "$2" = "debug" ]; then 26 | BUILD_DEBUG=1 27 | fi 28 | ;; 29 | "ts") 30 | BUILD_TS=1 31 | if [ "$2" = "debug" ]; then 32 | BUILD_DEBUG=1 33 | fi 34 | ;; 35 | "c") 36 | BUILD_C=1 37 | if [ "$2" = "debug" ]; then 38 | BUILD_DEBUG=1 39 | fi 40 | ;; 41 | "debug") 42 | BUILD_TS=1 43 | BUILD_C=1 44 | BUILD_DEBUG=1 45 | ;; 46 | "mem") 47 | BUILD_C=1 48 | BUILD_MEM=1 49 | ;; 50 | "clean") 51 | echo Cleaning... 52 | rm -rf "$TGT_DIR" 53 | echo Done 54 | exit 0 55 | ;; 56 | "dist") 57 | echo Publishing builds to ./dist 58 | find dist -type f -maxdepth 1 -delete 59 | mkdir -p "$DIST_DIR"/windows 60 | mkdir -p "$DIST_DIR"/mac 61 | mkdir -p "$DIST_DIR"/posix 62 | cp "$LIB_DIR/require.js" "$DIST_DIR" 63 | cp "$SRC_DIR/cmd.ts" "$DIST_DIR" 64 | cp "$SRC_DIR/repl.html" "$DIST_DIR" 65 | cp "$SRC_DIR/sink.ts" "$DIST_DIR" 66 | cp "$SRC_DIR/sink_shell.ts" "$DIST_DIR" 67 | cp "$TGT_DIR/cmd.d.ts" "$DIST_DIR" 68 | cp "$TGT_DIR/cmd.js" "$DIST_DIR" 69 | cp "$TGT_DIR/driver.js" "$DIST_DIR" 70 | cp "$TGT_DIR/sink.d.ts" "$DIST_DIR" 71 | cp "$TGT_DIR/sink.js" "$DIST_DIR" 72 | cp "$TGT_DIR/sink_shell.d.ts" "$DIST_DIR" 73 | cp "$TGT_DIR/sink_shell.js" "$DIST_DIR" 74 | echo 'You must copy the correct executables yourself, from tgt/ to dist/' 75 | ;; 76 | *) 77 | echo './build [debug]' 78 | echo '' 79 | echo 'Commands:' 80 | echo ' all Build everything' 81 | echo ' ts Build the TypeScript sources (node + browser)' 82 | echo ' c Build the C source' 83 | echo ' mem Build the C source with memory leak detection' 84 | echo ' clean Delete the tgt directory' 85 | echo ' dist Publish builds to ./dist' 86 | echo '' 87 | echo 'If "debug" is specified, then builds will be debug versions' 88 | exit 0 89 | ;; 90 | esac 91 | 92 | EXIT=0 93 | 94 | # 95 | # TypeScript build 96 | # 97 | if [ "$BUILD_TS" = "1" ]; then 98 | echo Building TypeScript... 99 | mkdir -p $TGT_DIR 100 | pushd "$SRC_DIR" > /dev/null 101 | set +e 102 | npm run tsc 103 | status=$? 104 | set -e 105 | popd > /dev/null 106 | if [ "$status" != "0" ]; then 107 | echo 'TypeScript errors' 108 | exit 1 109 | fi 110 | cp $SRC_DIR/driver.js $TGT_DIR/driver.js 111 | chmod +x $TGT_DIR/driver.js 112 | fi 113 | 114 | # 115 | # C build 116 | # 117 | if [ "$BUILD_C" = "1" ]; then 118 | echo Building C... 119 | mkdir -p $TGT_DIR 120 | if which clang > /dev/null; then 121 | if [ "$BUILD_MEM" = "1" ]; then 122 | C_OPTS="$C_OPTS -DSINK_MEMTEST" 123 | echo Memory Test 124 | elif [ "$BUILD_DEBUG" = "1" ]; then 125 | C_OPTS="$C_OPTS -g -DSINK_DEBUG" 126 | echo Debug Build 127 | else 128 | C_OPTS="$C_OPTS -O2" 129 | fi 130 | clang $C_OPTS \ 131 | -lm \ 132 | -o $TGT_DIR/sink \ 133 | $SRC_DIR/sink.c \ 134 | $SRC_DIR/sink_shell.c \ 135 | $SRC_DIR/cmd.c 136 | else 137 | echo '' 138 | echo 'ERROR:' 139 | echo 'Missing "clang" which is required for building.' 140 | echo '' 141 | echo 'Skipping C build.' 142 | echo '' 143 | echo 'You can install "clang" by visiting:' 144 | echo ' http://llvm.org/' 145 | echo '' 146 | EXIT=1 147 | fi 148 | fi 149 | 150 | if [ "$EXIT" = "1" ]; then 151 | exit 1 152 | fi 153 | echo Done 154 | -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | REM windows build 2 | REM (I hate batch files) 3 | 4 | clang -o tgt\sink.exe -fwrapv -O2 src\cmd.c src\sink_shell.c src\sink.c 5 | 6 | cd src 7 | tsc 8 | cd .. 9 | -------------------------------------------------------------------------------- /dist/cmd.d.ts: -------------------------------------------------------------------------------- 1 | export declare function main(): Promise; 2 | -------------------------------------------------------------------------------- /dist/cmd.ts: -------------------------------------------------------------------------------- 1 | // 2 | // sink - Minimal programming language for embedding small scripts in larger programs 3 | // by Sean Connelly (@velipso), https://sean.fun 4 | // Project Home: https://github.com/velipso/sink 5 | // SPDX-License-Identifier: 0BSD 6 | // 7 | 8 | import sink = require('./sink.js'); 9 | import sink_shell = require('./sink_shell.js'); 10 | import fs = require('fs'); 11 | import path = require('path'); 12 | import readline = require('readline'); 13 | 14 | async function io_say(ctx: sink.ctx, str: sink.str, iouser: any): Promise { 15 | console.log(str); 16 | return sink.NIL; 17 | } 18 | 19 | async function io_warn(ctx: sink.ctx, str: sink.str, iouser: any): Promise { 20 | console.error(str); 21 | return sink.NIL; 22 | } 23 | 24 | async function io_ask(ctx: sink.ctx, str: sink.str, iouser: any): Promise { 25 | var rl = readline.createInterface({ 26 | input: process.stdin, 27 | output: process.stdout 28 | }); 29 | return new Promise(function(resolve, reject){ 30 | rl.question(str, function(ans){ 31 | rl.close(); 32 | resolve(ans); 33 | }); 34 | }); 35 | } 36 | 37 | let io: sink.io_st = { 38 | f_say: io_say, 39 | f_warn: io_warn, 40 | f_ask: io_ask 41 | }; 42 | 43 | async function nodeStat(file: string): Promise { 44 | return new Promise(function(resolve, reject){ 45 | fs.stat(file, function(err, st: fs.Stats){ 46 | if (err){ 47 | if (err.code == 'ENOENT') 48 | resolve(null); 49 | else 50 | reject(err); 51 | } 52 | else 53 | resolve(st); 54 | }); 55 | }); 56 | } 57 | 58 | async function fstype(scr: sink.scr, file: string): Promise { 59 | let st = await nodeStat(file); 60 | if (st !== null){ 61 | if (st.isFile()) 62 | return sink.fstype.FILE; 63 | else if (st.isDirectory()) 64 | return sink.fstype.DIR; 65 | } 66 | return sink.fstype.NONE; 67 | } 68 | 69 | async function nodeRead(file: string): Promise { 70 | return new Promise(function(resolve, reject){ 71 | fs.readFile(file, 'binary', function(err, data){ 72 | if (err){ 73 | console.error(err); 74 | resolve(null); 75 | } 76 | else 77 | resolve(data); 78 | }); 79 | }); 80 | } 81 | 82 | async function fsread(scr: sink.scr, file: string): Promise { 83 | let data = await nodeRead(file); 84 | if (data === null) 85 | return false; // `false` indicates the file couldn't be read 86 | await sink.scr_write(scr, data); 87 | return true; // `true` indicates the file was read 88 | } 89 | 90 | let inc: sink.inc_st = { 91 | f_fstype: fstype, 92 | f_fsread: fsread 93 | }; 94 | 95 | function newctx(scr: sink.scr, argv: string[]): sink.ctx { 96 | let ctx = sink.ctx_new(scr, io); 97 | sink_shell.ctx(ctx, argv); 98 | return ctx; 99 | } 100 | 101 | function printscrerr(scr: sink.scr): void { 102 | console.error(sink.scr_geterr(scr)); 103 | } 104 | 105 | function printctxerr(ctx: sink.ctx): void { 106 | let err = sink.ctx_geterr(ctx); 107 | if (err === null) // context can error without an error message if script contains `abort` 108 | return; // without any parameters 109 | console.error(err); 110 | } 111 | 112 | async function readPrompt(p: string): Promise { 113 | var rl = readline.createInterface({ 114 | input: process.stdin, 115 | output: process.stdout 116 | }); 117 | return new Promise(function(resolve){ 118 | rl.question(p, function(ans){ 119 | rl.close(); 120 | resolve(ans); 121 | }); 122 | }); 123 | } 124 | 125 | async function main_repl(scr: sink.scr, argv: string[]): Promise { 126 | let ctx = newctx(scr, argv); 127 | let line = 1; 128 | while (true){ 129 | let levels = sink.scr_level(scr); 130 | var p = ': '; 131 | if (levels > 0) 132 | p = (new Array(levels + 1)).join('..') + '. '; 133 | if (line < 10) 134 | p = ' ' + line + p; 135 | else 136 | p = line + p; 137 | let ans = await readPrompt(p); 138 | line++; 139 | let buf = ans + '\n'; 140 | if (!await sink.scr_write(scr, buf)) 141 | printscrerr(scr); 142 | if (sink.scr_level(scr) <= 0){ 143 | switch (await sink.ctx_run(ctx)){ 144 | case sink.run.PASS: 145 | return true; 146 | case sink.run.FAIL: 147 | printctxerr(ctx); 148 | break; 149 | case sink.run.ASYNC: 150 | console.error('REPL returned async (impossible)'); 151 | return false; 152 | case sink.run.TIMEOUT: 153 | console.error('REPL returned timeout (impossible)'); 154 | return false; 155 | case sink.run.REPLMORE: 156 | // do nothing 157 | break; 158 | } 159 | } 160 | } 161 | } 162 | 163 | async function main_run(scr: sink.scr, file: string, argv: string[]): Promise { 164 | if (!await sink.scr_loadfile(scr, file)){ 165 | printscrerr(scr); 166 | return false; 167 | } 168 | let ctx = newctx(scr, argv); 169 | let res = await sink.ctx_run(ctx); 170 | if (res == sink.run.FAIL) 171 | printctxerr(ctx); 172 | return res == sink.run.PASS; 173 | } 174 | 175 | async function main_eval(scr: sink.scr, ev: string, argv: string[]): Promise { 176 | if (!await sink.scr_write(scr, ev)){ 177 | printscrerr(scr); 178 | return false; 179 | } 180 | let ctx = newctx(scr, argv); 181 | let res = await sink.ctx_run(ctx); 182 | if (res == sink.run.FAIL) 183 | printctxerr(ctx); 184 | return res == sink.run.PASS; 185 | } 186 | 187 | function perform_dump(scr: sink.scr, debug: boolean): void { 188 | // process.stdout.write isn't guaranteed to have synchronous writes (!!) 189 | let dump_data = ''; 190 | function dump(data: string): void { 191 | dump_data += data; 192 | } 193 | sink.scr_dump(scr, debug, null, dump); 194 | process.stdout.write(dump_data, 'binary'); 195 | } 196 | 197 | async function main_compile_file(scr: sink.scr, file: string, debug: boolean): Promise { 198 | if (!await sink.scr_loadfile(scr, file)){ 199 | printscrerr(scr); 200 | return false; 201 | } 202 | perform_dump(scr, debug); 203 | return true; 204 | } 205 | 206 | async function main_compile_eval(scr: sink.scr, ev: string, debug: boolean): Promise { 207 | if (!await sink.scr_write(scr, ev)){ 208 | printscrerr(scr); 209 | return false; 210 | } 211 | perform_dump(scr, debug); 212 | return true; 213 | } 214 | 215 | function print_version(): void { 216 | console.log( 217 | 'Sink v1.0\n' + 218 | 'by Sean Connelly (@velipso), 0BSD License\n' + 219 | 'https://github.com/velipso/sink https://sean.fun' 220 | ); 221 | } 222 | 223 | function print_help(): void { 224 | print_version(); 225 | console.log( 226 | '\nUsage:\n' + 227 | ' sink [options] [ -e \'\' | ] [arguments]\n' + 228 | '\n' + 229 | 'With no arguments, sink will enter interactive mode (REPL).\n' + 230 | '\n' + 231 | 'Option Description\n' + 232 | ' -v Display version information and exit\n' + 233 | ' -h, --help Display help information and exit\n' + 234 | ' -I Add to the include search path\n' + 235 | ' -c Compile input and output bytecode to stdout\n' + 236 | ' -d If compiling, output bytecode with debug info\n' + 237 | ' -D If compiling, add declarations when including \n' + 238 | '\n' + 239 | ' The -D option is useful for providing declarations so that compilation can\n' + 240 | ' succeed for other host environments.\n' + 241 | '\n' + 242 | ' For example, a host might provide declarations for native commands via:\n' + 243 | '\n' + 244 | ' include \'shapes\'\n' + 245 | '\n' + 246 | ' The host could provide a declaration file, which can be used during\n' + 247 | ' compilation using a `-D shapes shapes_decl.sink` option. This means when the\n' + 248 | ' script executes `include \'shapes\'`, the compiler will load `shapes_decl.sink`.\n' + 249 | ' Even though the compiler doesn\'t know how to execute the host commands, it can\n' + 250 | ' still compile the file for use in the host environment.'); 251 | } 252 | 253 | export async function main(): Promise { 254 | let argv = process.argv.splice(1); 255 | let argc = argv.length; 256 | let compile = false; 257 | let compile_debug = false; 258 | let input_type = 'repl'; 259 | let input_content = ''; 260 | 261 | // first pass, just figure out what we're doing and validate arguments 262 | let i = 1; 263 | for ( ; i < argc; i++){ 264 | let a = argv[i]; 265 | if (a === '-v'){ 266 | if (i + 1 < argc){ 267 | print_help(); 268 | return false; 269 | } 270 | print_version(); 271 | return true; 272 | } 273 | else if (a === '-h' || a === '--help'){ 274 | print_help(); 275 | return i + 1 < argc ? false : true; 276 | } 277 | else if (a === '-I'){ 278 | if (i + 1 >= argc){ 279 | print_help(); 280 | return false; 281 | } 282 | i++; // skip include path 283 | } 284 | else if (a === '-c') 285 | compile = true; 286 | else if (a === '-d') 287 | compile_debug = true; 288 | else if (a === '-D'){ 289 | if (i + 2 >= argc){ 290 | print_help(); 291 | return false; 292 | } 293 | i += 2; // skip declaration key/file 294 | } 295 | else if (a === '-e'){ 296 | if (i + 1 >= argc){ 297 | print_help(); 298 | return false; 299 | } 300 | input_content = argv[i + 1]; 301 | i += 2; // skip over script 302 | input_type = 'eval'; 303 | break; 304 | } 305 | else if (a === '--'){ 306 | i++; 307 | break; 308 | } 309 | else{ 310 | if (a.charAt(0) === '-'){ 311 | // some unknown option 312 | print_help(); 313 | return false; 314 | } 315 | input_content = a; 316 | i++; // skip over file 317 | input_type = 'file'; 318 | break; 319 | } 320 | } 321 | 322 | if (compile && input_type == 'repl'){ 323 | print_help(); 324 | return false; 325 | } 326 | 327 | // grab sink arguments 328 | let s_argv = argv.slice(i); 329 | 330 | // create the script with the current working directory 331 | let cwd = process.cwd(); 332 | let scr = sink.scr_new(inc, cwd, path.sep === '/', input_type === 'repl'); 333 | 334 | // add the appropriate paths 335 | sink.scr_addpath(scr, '.'); 336 | /* 337 | TODO: this 338 | const char *sp = getenv("SINK_PATH"); 339 | if (sp == NULL){ 340 | // if no environment variable, then add a default path of the current directory 341 | sink_scr_addpath(scr, "."); 342 | } 343 | else{ 344 | fprintf(stderr, "TODO: process SINK_PATH\n"); 345 | abort(); 346 | } 347 | */ 348 | 349 | // add any libraries 350 | sink_shell.scr(scr); 351 | 352 | // load include paths and declaration key/files 353 | for (i = 1; argv[i] !== input_content && i < argv.length; i++){ 354 | let a = argv[i]; 355 | if (a === '-I'){ 356 | sink.scr_addpath(scr, argv[i + 1]); 357 | i++; 358 | } 359 | else if (a === '-D'){ 360 | sink.scr_incfile(scr, argv[i + 1], argv[i + 2]); 361 | i += 2; 362 | } 363 | } 364 | 365 | if (input_type === 'file'){ 366 | if (compile) 367 | return main_compile_file(scr, input_content, compile_debug); 368 | return main_run(scr, input_content, s_argv); 369 | } 370 | else if (input_type === 'repl') 371 | return main_repl(scr, s_argv); 372 | else if (input_type === 'eval'){ 373 | if (compile) 374 | return main_compile_eval(scr, input_content, compile_debug); 375 | return main_eval(scr, input_content, s_argv); 376 | } 377 | // shouldn't happen 378 | throw new Error('Bad input type'); 379 | } 380 | -------------------------------------------------------------------------------- /dist/driver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // 3 | // sink - Minimal programming language for embedding small scripts in larger programs 4 | // by Sean Connelly (@velipso), https://sean.fun 5 | // Project Home: https://github.com/velipso/sink 6 | // SPDX-License-Identifier: 0BSD 7 | // 8 | 9 | require('./cmd.js').main().then( 10 | function(res){ process.exit(res ? 0 : 1); }, 11 | function(err){ console.error(err); process.exit(1); } 12 | ); 13 | -------------------------------------------------------------------------------- /dist/mac/sink-beta3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velipso/sink/b7d86bf4f0e4246bdf90fc0f39bca9a9d61486f2/dist/mac/sink-beta3 -------------------------------------------------------------------------------- /dist/posix/sink-beta3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velipso/sink/b7d86bf4f0e4246bdf90fc0f39bca9a9d61486f2/dist/posix/sink-beta3 -------------------------------------------------------------------------------- /dist/repl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sink REPL 5 | 6 | 15 | 16 | 17 | 18 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /dist/sink.d.ts: -------------------------------------------------------------------------------- 1 | export declare enum type { 2 | NIL = 0, 3 | NUM = 1, 4 | STR = 2, 5 | LIST = 3 6 | } 7 | export type str = string; 8 | export type strnil = string | null; 9 | export type valtrue = number | str | list; 10 | export type val = null | valtrue; 11 | export type user = number; 12 | export declare class list extends Array { 13 | usertype: user; 14 | user: any; 15 | constructor(...args: val[]); 16 | } 17 | export type u64 = [number, number]; 18 | export type ctx = any; 19 | export type scr = any; 20 | export declare enum fstype { 21 | NONE = 0, 22 | FILE = 1, 23 | DIR = 2 24 | } 25 | export declare enum gc_level { 26 | NONE = 0, 27 | DEFAULT = 1, 28 | LOWMEM = 2 29 | } 30 | export declare enum run { 31 | PASS = 0, 32 | FAIL = 1, 33 | ASYNC = 2, 34 | TIMEOUT = 3, 35 | REPLMORE = 4 36 | } 37 | export declare enum status { 38 | READY = 0, 39 | WAITING = 1, 40 | PASSED = 2, 41 | FAILED = 3 42 | } 43 | export type fstype_f = (scr: scr, file: string, incuser: any) => Promise; 44 | export type fsread_f = (scr: scr, file: string, incuser: any) => Promise; 45 | export type io_f = (ctx: ctx, str: str, iouser: any) => Promise; 46 | export type native_f = (ctx: ctx, args: val[], natuser: any) => Promise; 47 | export type dump_f = (data: string, dumpuser: any) => void; 48 | export interface io_st { 49 | f_say?: io_f; 50 | f_warn?: io_f; 51 | f_ask?: io_f; 52 | user?: any; 53 | } 54 | export interface inc_st { 55 | f_fstype: fstype_f; 56 | f_fsread: fsread_f; 57 | user?: any; 58 | } 59 | export declare const NIL: null; 60 | export declare function bool(f: boolean): val; 61 | export declare function istrue(v: val): v is valtrue; 62 | export declare function isfalse(v: val): v is null; 63 | export declare function isnil(v: val): v is null; 64 | export declare function isstr(v: val): v is str; 65 | export declare function islist(v: val): v is list; 66 | export declare function isnum(v: val): v is number; 67 | export declare function sink_typeof(v: val): type; 68 | export declare function num_nan(): val; 69 | export declare function num_inf(): val; 70 | export declare function num_isnan(v: val): boolean; 71 | export declare function num_isfinite(v: val): boolean; 72 | export declare function num_e(): number; 73 | export declare function num_pi(): number; 74 | export declare function num_tau(): number; 75 | export declare function user_new(ctx: ctx, usertype: user, user: any): val; 76 | export declare let seedauto_src: () => number; 77 | export declare function scr_setuser(scr: scr, user: any): void; 78 | export declare function scr_getuser(scr: scr): any; 79 | export declare function rand_seedauto(ctx: ctx): void; 80 | export declare function rand_seed(ctx: ctx, n: number): void; 81 | export declare function rand_int(ctx: ctx): number; 82 | export declare function rand_num(ctx: ctx): number; 83 | export declare function rand_range(ctx: ctx, start: number, stop: number, step: number): val; 84 | export declare function rand_getstate(ctx: ctx): list; 85 | export declare function rand_setstate(ctx: ctx, a: val): void; 86 | export declare function rand_pick(ctx: ctx, a: val): val; 87 | export declare function rand_shuffle(ctx: ctx, a: val): void; 88 | export declare function str_new(ctx: ctx, vals: val[]): val; 89 | export declare function str_split(ctx: ctx, a: val, b: val): val; 90 | export declare function str_replace(ctx: ctx, a: val, b: val, c: val): val; 91 | export declare function str_find(ctx: ctx, a: val, b: val, c: val): val; 92 | export declare function str_rfind(ctx: ctx, a: val, b: val, c: val): val; 93 | export declare function str_begins(ctx: ctx, a: val, b: val): boolean; 94 | export declare function str_ends(ctx: ctx, a: val, b: val): boolean; 95 | export declare function str_pad(ctx: ctx, a: val, b: number): val; 96 | export declare function str_lower(ctx: ctx, a: val): val; 97 | export declare function str_upper(ctx: ctx, a: val): val; 98 | export declare function str_trim(ctx: ctx, a: val): val; 99 | export declare function str_rev(ctx: ctx, a: val): val; 100 | export declare function str_rep(ctx: ctx, a: val, rep: number): val; 101 | export declare function str_list(ctx: ctx, a: val): val; 102 | export declare function str_byte(ctx: ctx, a: val, b: number): val; 103 | export declare function str_hash(ctx: ctx, a: val, seed: number): val; 104 | export declare function utf8_valid(ctx: ctx, a: val): boolean; 105 | export declare function utf8_list(ctx: ctx, a: val): val; 106 | export declare function utf8_str(ctx: ctx, a: val): val; 107 | export declare function struct_size(ctx: ctx, a: val): val; 108 | export declare function struct_str(ctx: ctx, a: val, b: val): val; 109 | export declare function struct_list(ctx: ctx, a: val, b: val): val; 110 | export declare function struct_isLE(): boolean; 111 | export declare function size(ctx: ctx, a: val): number; 112 | export declare function tonum(ctx: ctx, a: val): val; 113 | export declare function say(ctx: ctx, vals: val[]): Promise; 114 | export declare function warn(ctx: ctx, vals: val[]): Promise; 115 | export declare function ask(ctx: ctx, vals: val[]): Promise; 116 | export declare function stacktrace(ctx: ctx): val; 117 | export declare function str_cat(ctx: ctx, vals: val[]): val; 118 | export declare function str_slice(ctx: ctx, a: val, b: val, c: val): val; 119 | export declare function str_splice(ctx: ctx, a: val, b: val, c: val, d: val): val; 120 | export declare function list_new(ctx: ctx, a: val, b: val): val; 121 | export declare function list_slice(ctx: ctx, a: val, b: val, c: val): val; 122 | export declare function list_splice(ctx: ctx, a: val, b: val, c: val, d: val): void; 123 | export declare function list_shift(ctx: ctx, a: val): val; 124 | export declare function list_pop(ctx: ctx, a: val): val; 125 | export declare function list_push(ctx: ctx, a: val, b: val): val; 126 | export declare function list_unshift(ctx: ctx, a: val, b: val): val; 127 | export declare function list_append(ctx: ctx, a: val, b: val): val; 128 | export declare function list_prepend(ctx: ctx, a: val, b: val): val; 129 | export declare function list_find(ctx: ctx, a: val, b: val, c: val): val; 130 | export declare function list_rfind(ctx: ctx, a: val, b: val, c: val): val; 131 | export declare function list_join(ctx: ctx, a: val, b: val): val; 132 | export declare function list_rev(ctx: ctx, a: val): val; 133 | export declare function list_str(ctx: ctx, a: val): val; 134 | export declare function list_sort(ctx: ctx, a: val): void; 135 | export declare function list_rsort(ctx: ctx, a: val): void; 136 | export declare function order(ctx: ctx, a: val, b: val): number; 137 | export declare function range(ctx: ctx, start: number, stop: number, step: number): val; 138 | export declare function pickle_json(ctx: ctx, a: val): val; 139 | export declare function pickle_binstr(a: val): string; 140 | export declare function pickle_bin(ctx: ctx, a: val): val; 141 | export declare function pickle_valstr(s: str): val | false; 142 | export declare function pickle_val(ctx: ctx, a: val): val; 143 | export declare function pickle_valid(ctx: ctx, a: val): number; 144 | export declare function pickle_sibling(ctx: ctx, a: val): boolean; 145 | export declare function pickle_circular(ctx: ctx, a: val): boolean; 146 | export declare function pickle_copy(ctx: ctx, a: val): val; 147 | export declare function scr_new(inc: inc_st, curdir: strnil, posix: boolean, repl: boolean): scr; 148 | export declare function scr_addpath(scr: scr, path: string): void; 149 | export declare function scr_incbody(scr: scr, name: string, body: string): void; 150 | export declare function scr_incfile(scr: scr, name: string, file: string): void; 151 | export declare function scr_loadfile(scr: scr, file: string): Promise; 152 | export declare function scr_getfile(scr: scr): strnil; 153 | export declare function scr_getcwd(scr: scr): strnil; 154 | export declare function scr_write(scr: scr, bytes: string): Promise; 155 | export declare function scr_geterr(scr: scr): strnil; 156 | export declare function scr_level(scr: scr): number; 157 | export declare function scr_dump(scr: scr, debug: boolean, user: any, f_dump: dump_f): void; 158 | export declare function ctx_new(scr: scr, io: io_st): ctx; 159 | export declare function ctx_getstatus(ctx: ctx): status; 160 | export declare function ctx_native(ctx: ctx, name: string, natuser: any, f_native: native_f): void; 161 | export declare function ctx_nativehash(ctx: ctx, hash: u64, natuser: any, f_native: native_f): void; 162 | export declare function ctx_setuser(ctx: ctx, user: any): void; 163 | export declare function ctx_getuser(ctx: ctx): any; 164 | export declare function ctx_addusertype(ctx: ctx, hint: string): user; 165 | export declare function ctx_getuserhint(ctx: ctx, usertype: user): string; 166 | export declare function ctx_settimeout(ctx: ctx, timeout: number): void; 167 | export declare function ctx_gettimeout(ctx: ctx): number; 168 | export declare function ctx_consumeticks(ctx: ctx, amount: number): void; 169 | export declare function ctx_forcetimeout(ctx: ctx): void; 170 | export declare function ctx_run(ctx: ctx): Promise; 171 | export declare function ctx_geterr(ctx: ctx): strnil; 172 | export declare function arg_bool(args: val[], index: number): boolean; 173 | export declare function arg_num(ctx: ctx, args: val[], index: number): number; 174 | export declare function arg_str(ctx: ctx, args: val[], index: number): string; 175 | export declare function arg_list(ctx: ctx, args: val[], index: number): list; 176 | export declare function arg_user(ctx: ctx, args: val[], index: number, usertype: user): any; 177 | export declare function tostr(v: val): str; 178 | export declare function exit(ctx: ctx, vals: val[]): Promise; 179 | export declare function abort(ctx: ctx, vals: val[]): void; 180 | export declare function abortstr(ctx: ctx, str: string): Promise; 181 | export declare function isnative(ctx: ctx, name: string): boolean; 182 | export declare function isnativehash(ctx: ctx, hash: u64): boolean; 183 | export declare function num_neg(ctx: ctx, a: val): val; 184 | export declare function num_add(ctx: ctx, a: val, b: val): val; 185 | export declare function num_sub(ctx: ctx, a: val, b: val): val; 186 | export declare function num_mul(ctx: ctx, a: val, b: val): val; 187 | export declare function num_div(ctx: ctx, a: val, b: val): val; 188 | export declare function num_mod(ctx: ctx, a: val, b: val): val; 189 | export declare function num_pow(ctx: ctx, a: val, b: val): val; 190 | export declare function num_abs(ctx: ctx, a: val): val; 191 | export declare function num_sign(ctx: ctx, a: val): val; 192 | export declare function num_max(ctx: ctx, vals: val[]): val; 193 | export declare function num_min(ctx: ctx, vals: val[]): val; 194 | export declare function num_clamp(ctx: ctx, a: val, b: val, c: val): val; 195 | export declare function num_floor(ctx: ctx, a: val): val; 196 | export declare function num_ceil(ctx: ctx, a: val): val; 197 | export declare function num_round(ctx: ctx, a: val): val; 198 | export declare function num_trunc(ctx: ctx, a: val): val; 199 | export declare function num_sin(ctx: ctx, a: val): val; 200 | export declare function num_cos(ctx: ctx, a: val): val; 201 | export declare function num_tan(ctx: ctx, a: val): val; 202 | export declare function num_asin(ctx: ctx, a: val): val; 203 | export declare function num_acos(ctx: ctx, a: val): val; 204 | export declare function num_atan(ctx: ctx, a: val): val; 205 | export declare function num_atan2(ctx: ctx, a: val, b: val): val; 206 | export declare function num_log(ctx: ctx, a: val): val; 207 | export declare function num_log2(ctx: ctx, a: val): val; 208 | export declare function num_log10(ctx: ctx, a: val): val; 209 | export declare function num_exp(ctx: ctx, a: val): val; 210 | export declare function num_lerp(ctx: ctx, a: val, b: val, t: val): val; 211 | export declare function num_hex(ctx: ctx, a: val, b: val): val; 212 | export declare function num_oct(ctx: ctx, a: val, b: val): val; 213 | export declare function num_bin(ctx: ctx, a: val, b: val): val; 214 | export declare function int_new(ctx: ctx, a: val): val; 215 | export declare function int_not(ctx: ctx, a: val): val; 216 | export declare function int_and(ctx: ctx, vals: val[]): val; 217 | export declare function int_or(ctx: ctx, vals: val[]): val; 218 | export declare function int_xor(ctx: ctx, vals: val[]): val; 219 | export declare function int_shl(ctx: ctx, a: val, b: val): val; 220 | export declare function int_shr(ctx: ctx, a: val, b: val): val; 221 | export declare function int_sar(ctx: ctx, a: val, b: val): val; 222 | export declare function int_add(ctx: ctx, a: val, b: val): val; 223 | export declare function int_sub(ctx: ctx, a: val, b: val): val; 224 | export declare function int_mul(ctx: ctx, a: val, b: val): val; 225 | export declare function int_div(ctx: ctx, a: val, b: val): val; 226 | export declare function int_mod(ctx: ctx, a: val, b: val): val; 227 | export declare function int_clz(ctx: ctx, a: val): val; 228 | export declare function int_pop(ctx: ctx, a: val): val; 229 | export declare function int_bswap(ctx: ctx, a: val): val; 230 | export declare function str_hashplain(bytes: string, seed: number): [number, number, number, number]; 231 | export declare function list_setuser(ctx: ctx, ls: val, usertype: user, user: any): void; 232 | export declare function list_hasuser(ctx: ctx, ls: val, usertype: user): boolean; 233 | export declare function list_getuser(ctx: ctx, ls: val): any; 234 | export declare function list_cat(ctx: ctx, vals: val[]): val; 235 | export declare function list_joinplain(vals: list | val[], sep: string): val; 236 | export declare function gc_getlevel(ctx: ctx): gc_level; 237 | export declare function gc_setlevel(ctx: ctx, level: gc_level): void; 238 | -------------------------------------------------------------------------------- /dist/sink_shell.d.ts: -------------------------------------------------------------------------------- 1 | import sink = require('./sink.js'); 2 | export declare function scr(scr: sink.scr): void; 3 | export declare function ctx(ctx: sink.ctx, args: string[]): void; 4 | -------------------------------------------------------------------------------- /dist/sink_shell.js: -------------------------------------------------------------------------------- 1 | // 2 | // sink - Minimal programming language for embedding small scripts in larger programs 3 | // by Sean Connelly (@velipso), https://sean.fun 4 | // Project Home: https://github.com/velipso/sink 5 | // SPDX-License-Identifier: 0BSD 6 | // 7 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 8 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 9 | return new (P || (P = Promise))(function (resolve, reject) { 10 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 11 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 12 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 13 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 14 | }); 15 | }; 16 | var __generator = (this && this.__generator) || function (thisArg, body) { 17 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 18 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 19 | function verb(n) { return function (v) { return step([n, v]); }; } 20 | function step(op) { 21 | if (f) throw new TypeError("Generator is already executing."); 22 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 23 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 24 | if (y = 0, t) op = [op[0] & 2, t.value]; 25 | switch (op[0]) { 26 | case 0: case 1: t = op; break; 27 | case 4: _.label++; return { value: op[1], done: false }; 28 | case 5: _.label++; y = op[1]; op = [0]; continue; 29 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 30 | default: 31 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 32 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 33 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 34 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 35 | if (t[2]) _.ops.pop(); 36 | _.trys.pop(); continue; 37 | } 38 | op = body.call(thisArg, _); 39 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 40 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 41 | } 42 | }; 43 | (function (factory) { 44 | if (typeof module === "object" && typeof module.exports === "object") { 45 | var v = factory(require, exports); 46 | if (v !== undefined) module.exports = v; 47 | } 48 | else if (typeof define === "function" && define.amd) { 49 | define(["require", "exports", "./sink.js"], factory); 50 | } 51 | })(function (require, exports) { 52 | "use strict"; 53 | Object.defineProperty(exports, "__esModule", { value: true }); 54 | exports.scr = scr; 55 | exports.ctx = ctx; 56 | var sink = require("./sink.js"); 57 | var VERSION_MAJ = 1; 58 | var VERSION_MIN = 0; 59 | var VERSION_PAT = 0; 60 | var isBrowser = typeof window === 'object'; 61 | function L_version(ctx, args) { 62 | return __awaiter(this, void 0, void 0, function () { 63 | var reqmaj, reqmin, reqpat; 64 | return __generator(this, function (_a) { 65 | reqmaj = 0, reqmin = 0, reqpat = 0; 66 | if (args.length >= 1) { 67 | if (!sink.isnum(args[0])) 68 | return [2 /*return*/, sink.abortstr(ctx, 'Expecting number')]; 69 | reqmaj = args[0] | 0; 70 | } 71 | if (args.length >= 2) { 72 | if (!sink.isnum(args[1])) 73 | return [2 /*return*/, sink.abortstr(ctx, 'Expecting number')]; 74 | reqmin = args[1] | 0; 75 | } 76 | if (args.length >= 3) { 77 | if (!sink.isnum(args[2])) 78 | return [2 /*return*/, sink.abortstr(ctx, 'Expecting number')]; 79 | reqpat = args[2] | 0; 80 | } 81 | while (true) { 82 | if (reqmaj > VERSION_MAJ) 83 | break; 84 | else if (reqmaj == VERSION_MAJ) { 85 | if (reqmin > VERSION_MIN) 86 | break; 87 | else if (reqmin == VERSION_MIN) { 88 | if (reqpat > VERSION_PAT) 89 | break; 90 | } 91 | } 92 | return [2 /*return*/, new sink.list(VERSION_MAJ, VERSION_MIN, VERSION_PAT)]; 93 | } 94 | return [2 /*return*/, sink.abortstr(ctx, 'Script requires version ' + reqmaj + '.' + reqmin + '.' + reqpat + ', but sink is ' + 95 | 'version ' + VERSION_MAJ + '.' + VERSION_MIN + '.' + VERSION_PAT)]; 96 | }); 97 | }); 98 | } 99 | function L_args(ctx, args, pargs) { 100 | return __awaiter(this, void 0, void 0, function () { 101 | var v, i; 102 | return __generator(this, function (_a) { 103 | v = new sink.list(); 104 | for (i = 0; i < pargs.length; i++) 105 | v.push(pargs[i]); 106 | return [2 /*return*/, v]; 107 | }); 108 | }); 109 | } 110 | function L_dir_work(ctx, args) { 111 | return __awaiter(this, void 0, void 0, function () { 112 | return __generator(this, function (_a) { 113 | return [2 /*return*/, isBrowser ? 114 | window.location.href 115 | .replace(/^.*:/, '') // remove protocol 116 | .replace(/\?.*$/, '') // remove query params 117 | .replace(/\/[^\/]*$/, '') : // remove trailing file and slash 118 | process.cwd()]; 119 | }); 120 | }); 121 | } 122 | function scr(scr) { 123 | sink.scr_incbody(scr, 'shell', "declare version 'sink.shell.version' ;" + 124 | "declare args 'sink.shell.args' ;" + 125 | "declare dir.work 'sink.shell.dir.work';"); 126 | } 127 | function ctx(ctx, args) { 128 | sink.ctx_native(ctx, 'sink.shell.version', null, L_version); 129 | sink.ctx_native(ctx, 'sink.shell.args', args, L_args); 130 | sink.ctx_native(ctx, 'sink.shell.dir.work', null, L_dir_work); 131 | } 132 | }); 133 | -------------------------------------------------------------------------------- /dist/sink_shell.ts: -------------------------------------------------------------------------------- 1 | // 2 | // sink - Minimal programming language for embedding small scripts in larger programs 3 | // by Sean Connelly (@velipso), https://sean.fun 4 | // Project Home: https://github.com/velipso/sink 5 | // SPDX-License-Identifier: 0BSD 6 | // 7 | 8 | import sink = require('./sink.js'); 9 | 10 | const VERSION_MAJ = 1; 11 | const VERSION_MIN = 0; 12 | const VERSION_PAT = 0; 13 | 14 | var isBrowser = typeof window === 'object'; 15 | 16 | async function L_version(ctx: sink.ctx, args: sink.val[]): Promise { 17 | let reqmaj = 0, reqmin = 0, reqpat = 0; 18 | if (args.length >= 1){ 19 | if (!sink.isnum(args[0])) 20 | return sink.abortstr(ctx, 'Expecting number'); 21 | reqmaj = (args[0] as number) | 0; 22 | } 23 | if (args.length >= 2){ 24 | if (!sink.isnum(args[1])) 25 | return sink.abortstr(ctx, 'Expecting number'); 26 | reqmin = (args[1] as number) | 0; 27 | } 28 | if (args.length >= 3){ 29 | if (!sink.isnum(args[2])) 30 | return sink.abortstr(ctx, 'Expecting number'); 31 | reqpat = (args[2] as number) | 0; 32 | } 33 | while (true){ 34 | if (reqmaj > VERSION_MAJ) 35 | break; 36 | else if (reqmaj == VERSION_MAJ){ 37 | if (reqmin > VERSION_MIN) 38 | break; 39 | else if (reqmin == VERSION_MIN){ 40 | if (reqpat > VERSION_PAT) 41 | break; 42 | } 43 | } 44 | return new sink.list(VERSION_MAJ, VERSION_MIN, VERSION_PAT); 45 | } 46 | return sink.abortstr(ctx, 47 | 'Script requires version ' + reqmaj + '.' + reqmin + '.' + reqpat + ', but sink is ' + 48 | 'version ' + VERSION_MAJ + '.' + VERSION_MIN + '.' + VERSION_PAT); 49 | } 50 | 51 | async function L_args(ctx: sink.ctx, args: sink.val[], pargs: string[]): Promise { 52 | let v = new sink.list(); 53 | for (let i = 0; i < pargs.length; i++) 54 | v.push(pargs[i]); 55 | return v; 56 | } 57 | 58 | async function L_dir_work(ctx: sink.ctx, args: sink.val[]): Promise { 59 | return isBrowser ? 60 | window.location.href 61 | .replace(/^.*:/, '') // remove protocol 62 | .replace(/\?.*$/, '') // remove query params 63 | .replace(/\/[^\/]*$/, '') : // remove trailing file and slash 64 | process.cwd(); 65 | } 66 | 67 | export function scr(scr: sink.scr): void { 68 | sink.scr_incbody(scr, 'shell', 69 | "declare version 'sink.shell.version' ;" + 70 | "declare args 'sink.shell.args' ;" + 71 | "declare dir.work 'sink.shell.dir.work';"); 72 | } 73 | 74 | export function ctx(ctx: sink.ctx, args: string[]): void { 75 | sink.ctx_native(ctx, 'sink.shell.version', null, L_version); 76 | sink.ctx_native(ctx, 'sink.shell.args', args, L_args); 77 | sink.ctx_native(ctx, 'sink.shell.dir.work', null, L_dir_work); 78 | } 79 | -------------------------------------------------------------------------------- /dist/windows/sink-beta3.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velipso/sink/b7d86bf4f0e4246bdf90fc0f39bca9a9d61486f2/dist/windows/sink-beta3.exe -------------------------------------------------------------------------------- /docs/command-line.md: -------------------------------------------------------------------------------- 1 | 2 | Command-Line Options 3 | ==================== 4 | 5 | Usage: 6 | 7 | ``` 8 | sink [options] [ -e '' | ] [arguments] 9 | ``` 10 | 11 | With no arguments, sink will enter interactive mode (REPL). 12 | 13 | | Option | Description | 14 | |-------------------|-----------------------------------------------------------------------------| 15 | | `-v` | Display version information and exit | 16 | | `-h`, `--help` | Display help information and exit | 17 | | `-I ` | Add `` to the include search path | 18 | | `-c` | Compile input and output bytecode to stdout | 19 | | `-d` | If compiling, output bytecode with debug/stacktrace information | 20 | | `-D ` | If compiling, add `` declarations when including `` | 21 | 22 | The `-D` option is useful for providing declarations so that compilation can succeed for other host 23 | environments. 24 | 25 | For example, a host might provide declarations for native commands via `include 'shapes'`. The host 26 | could provide a declaration file, which can be used during compilation using a 27 | `-D shapes shapes_decl.sink` option. This means when the script executes `include 'shapes'`, the 28 | compiler will load `shapes_decl.sink`. Even though the compiler doesn't know how to execute the 29 | host commands, it can still compile the file for use in the host environment. 30 | -------------------------------------------------------------------------------- /docs/hash.md: -------------------------------------------------------------------------------- 1 | 2 | String Hash Function 3 | ==================== 4 | 5 | The `str.hash` command is defined as [Murmur3_x64_128](https://github.com/aappleby/smhasher), and 6 | returns the same results on all platforms, given the same input. Murmur3 is known as a fast and 7 | high quality non-cryptographic hash function. 8 | 9 | The `str.hash` command will return a list of four numbers, each 32-bit unsigned integers. 10 | 11 | The seed parameter is optional, and defaults to 0. Changing the seed is useful for generating 12 | different hashes for the same input. 13 | 14 | ```c 15 | // MurmurHash3 was written by Austin Appleby, and is placed in the public 16 | // domain. The author hereby disclaims copyright to this source code. 17 | // https://github.com/aappleby/smhasher 18 | 19 | static inline uint64_t rotl64(uint64_t x, int8_t r){ 20 | return (x << r) | (x >> (64 - r)); 21 | } 22 | 23 | static inline uint64_t fmix64(uint64_t k){ 24 | k ^= k >> 33; 25 | k *= UINT64_C(0xFF51AFD7ED558CCD); 26 | k ^= k >> 33; 27 | k *= UINT64_C(0xC4CEB9FE1A85EC53); 28 | k ^= k >> 33; 29 | return k; 30 | } 31 | 32 | void str_hash(const uint8_t *str, uint64_t len, uint32_t seed, uint32_t *out){ 33 | uint64_t nblocks = len >> 4; 34 | 35 | uint64_t h1 = seed; 36 | uint64_t h2 = seed; 37 | 38 | uint64_t c1 = UINT64_C(0x87C37B91114253D5); 39 | uint64_t c2 = UINT64_C(0x4CF5AD432745937F); 40 | 41 | for (uint64_t i = 0; i < nblocks; i++){ 42 | uint64_t ki = i * 16; 43 | uint64_t k1 = 44 | ((uint64_t)str[ki + 0] ) | 45 | ((uint64_t)str[ki + 1] << 8) | 46 | ((uint64_t)str[ki + 2] << 16) | 47 | ((uint64_t)str[ki + 3] << 24) | 48 | ((uint64_t)str[ki + 4] << 32) | 49 | ((uint64_t)str[ki + 5] << 40) | 50 | ((uint64_t)str[ki + 6] << 48) | 51 | ((uint64_t)str[ki + 7] << 56); 52 | uint64_t k2 = 53 | ((uint64_t)str[ki + 8] ) | 54 | ((uint64_t)str[ki + 9] << 8) | 55 | ((uint64_t)str[ki + 10] << 16) | 56 | ((uint64_t)str[ki + 11] << 24) | 57 | ((uint64_t)str[ki + 12] << 32) | 58 | ((uint64_t)str[ki + 13] << 40) | 59 | ((uint64_t)str[ki + 14] << 48) | 60 | ((uint64_t)str[ki + 15] << 56); 61 | 62 | k1 *= c1; 63 | k1 = rotl64(k1, 31); 64 | k1 *= c2; 65 | h1 ^= k1; 66 | 67 | h1 = rotl64(h1, 27); 68 | h1 += h2; 69 | h1 = h1 * 5 + 0x52DCE729; 70 | 71 | k2 *= c2; 72 | k2 = rotl64(k2, 33); 73 | k2 *= c1; 74 | h2 ^= k2; 75 | 76 | h2 = rotl64(h2, 31); 77 | h2 += h1; 78 | h2 = h2 * 5 + 0x38495AB5; 79 | } 80 | 81 | const uint8_t *tail = &str[nblocks << 4]; 82 | 83 | uint64_t k1 = 0; 84 | uint64_t k2 = 0; 85 | 86 | switch(len & 15) { 87 | case 15: k2 ^= (uint64_t)(tail[14]) << 48; 88 | case 14: k2 ^= (uint64_t)(tail[13]) << 40; 89 | case 13: k2 ^= (uint64_t)(tail[12]) << 32; 90 | case 12: k2 ^= (uint64_t)(tail[11]) << 24; 91 | case 11: k2 ^= (uint64_t)(tail[10]) << 16; 92 | case 10: k2 ^= (uint64_t)(tail[ 9]) << 8; 93 | case 9: k2 ^= (uint64_t)(tail[ 8]) << 0; 94 | 95 | k2 *= c2; 96 | k2 = rotl64(k2, 33); 97 | k2 *= c1; 98 | h2 ^= k2; 99 | 100 | case 8: k1 ^= (uint64_t)(tail[ 7]) << 56; 101 | case 7: k1 ^= (uint64_t)(tail[ 6]) << 48; 102 | case 6: k1 ^= (uint64_t)(tail[ 5]) << 40; 103 | case 5: k1 ^= (uint64_t)(tail[ 4]) << 32; 104 | case 4: k1 ^= (uint64_t)(tail[ 3]) << 24; 105 | case 3: k1 ^= (uint64_t)(tail[ 2]) << 16; 106 | case 2: k1 ^= (uint64_t)(tail[ 1]) << 8; 107 | case 1: k1 ^= (uint64_t)(tail[ 0]) << 0; 108 | 109 | k1 *= c1; 110 | k1 = rotl64(k1, 31); 111 | k1 *= c2; 112 | h1 ^= k1; 113 | } 114 | 115 | h1 ^= len; 116 | h2 ^= len; 117 | 118 | h1 += h2; 119 | h2 += h1; 120 | 121 | h1 = fmix64(h1); 122 | h2 = fmix64(h2); 123 | 124 | h1 += h2; 125 | h2 += h1; 126 | 127 | out[0] = h1 & 0xFFFFFFFF; 128 | out[1] = h1 >> 32; 129 | out[2] = h2 & 0xFFFFFFFF; 130 | out[3] = h2 >> 32; 131 | } 132 | ``` 133 | 134 | ``` 135 | str.hash 'hello, world', 123 # => {3439238593, 804096095, 2029097957, 3684287146} 136 | str.hash 'demon produce aisle' # => {2133076460, 2322631415, 1728380306, 2686374473} 137 | ``` 138 | -------------------------------------------------------------------------------- /docs/isnative.md: -------------------------------------------------------------------------------- 1 | 2 | isnative 3 | ======== 4 | 5 | You can use `isnative` to test for command implementation in the host environment. 6 | 7 | For example: 8 | 9 | ``` 10 | declare circle 'company.product.shape.circle' 11 | 12 | if isnative circle 13 | # safe to use circle 14 | var c = circle 0, 0, 50 15 | #... 16 | else 17 | # can't use circle, maybe use something else 18 | end 19 | ``` 20 | 21 | This might be useful for detecting if certain features are available without aborting the script. 22 | -------------------------------------------------------------------------------- /docs/order.md: -------------------------------------------------------------------------------- 1 | 2 | Order 3 | ===== 4 | 5 | The `order` command performs a deep comparison of two values. This is used for sorting, but can 6 | also be used for equality. 7 | 8 | Sorting precedence: 9 | 10 | 1. Nil 11 | 2. Numbers (NaN, negative infinity to positive infinity) 12 | 3. Strings (byte-by-byte comparison; if equal, shorter strings first) 13 | 4. Lists (item by item comparison; if equal, shorter lists first) 14 | 15 | ``` 16 | order nil, 1 # => -1 17 | order 55, 5 # => 1 18 | order 'a', 'ab' # => -1 19 | order {}, '' # => 1 20 | order {1}, {1} # => 0 21 | order num.nan, num.nan # => 0 22 | 23 | list.sort {3, 2, nil, 4} # => {nil, 2, 3, 4} 24 | list.rsort {3, 2, nil, 4} # => {4, 3, 2, nil} 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/pickle-selfref.md: -------------------------------------------------------------------------------- 1 | 2 | Pickle Self-Refrencing 3 | ====================== 4 | 5 | Pickling lists is complicated because they can contain the same list multiple times, or even have 6 | recursive lists: 7 | 8 | ``` 9 | var a = {'hello'} 10 | var b = {a, a} 11 | say b # {{'hello'}, {'hello'}} 12 | 13 | var c = {'world'} 14 | list.push c, c 15 | say c # {{'world'}, {circular}} 16 | ``` 17 | 18 | List `a` is flat and doesn't contain duplicate references. 19 | 20 | List `b` contains a sibling reference. Sibling references are when there exists multiple 21 | non-circular references to the same object. 22 | 23 | Sibling references can be serialized using JSON, but upon deserializing, the fact that 24 | `b[0] == b[1]` will be lost: 25 | 26 | ``` 27 | var b2 = b | pickle.json | pickle.val 28 | say b[0] == b[1] # 1 (true) 29 | say b2[0] == b2[1] # nil (false) 30 | ``` 31 | 32 | List `c` contains a circular reference -- this is when a list contains itself somewhere inside of 33 | it. Circular references cannot be serialized using JSON and will cause the sink script to abort in 34 | failure. 35 | 36 | The `pickle.sibling` and `pickle.circular` commands can test whether an object has sibling or 37 | circular references. To avoid this problem completely, it's recommended to use the binary format 38 | (via `pickle.bin`) in lieu of the JSON format, since it correctly handles restoring references 39 | (both sibling and circular). 40 | -------------------------------------------------------------------------------- /docs/pickle.md: -------------------------------------------------------------------------------- 1 | 2 | Pickle Binary Format 3 | ==================== 4 | 5 | The pickle binary format is specified explicitly and can be used for interchange with programs that 6 | support the format. 7 | 8 | Variable length integers (V-Int) are used multiple times in the format. They can either represent a 9 | value from 0 to 127 using one byte (with the most significant bit cleared), or 0 to 10 | 231 - 1 using four bytes (**big**-endian encoded, with the most significant bit set on 11 | the first byte). This is the basis for the hard limits of the format. 12 | 13 | | Value(s) | Description | 14 | |---------------------------------------------|-------------------------------------------------| 15 | | `0xxxxxxx` | 7-bit V-Int for values 0 to 127 | 16 | | `1xxxxxxx` `xxxxxxxx` `xxxxxxxx` `xxxxxxxx` | 31-bit V-Int for values 0 to 231 - 1 | 17 | 18 | The format has the following basic sequence: 19 | 20 | | Value | Description | 21 | |--------|--------------------------------------------------------------------------| 22 | | `0x01` | A single non-printable byte at the start to distinguish from JSON format | 23 | | V-Int | Number of strings in the string table | 24 | | | (For each string...) | 25 | | V-Int | Number of bytes in the string | 26 | | Bytes | Raw bytes of the string | 27 | | | (...end string table) | 28 | | S-Val | The pickled sink value, described below | 29 | 30 | A sink value (S-Val) can be one of the four basic sink types (`nil`, number, string, list). 31 | 32 | ### `0xF0` Positive 8-bit Number (0 to 255) 33 | 34 | | Value | Description | 35 | |--------|---------------------------------------------------| 36 | | `0xF0` | A single byte to indicate a positive 8-bit number | 37 | | Number | The number (1 byte) | 38 | 39 | ### `0xF1` Negative 8-bit Number (-256 to -1) 40 | 41 | | Value | Description | 42 | |--------|---------------------------------------------------| 43 | | `0xF1` | A single byte to indicate a negative 8-bit number | 44 | | Number | The number + 256 (1 byte) | 45 | 46 | ### `0xF2` Positive 16-bit Number (0 to 65535) 47 | 48 | | Value | Description | 49 | |--------|----------------------------------------------------| 50 | | `0xF2` | A single byte to indicate a positive 16-bit number | 51 | | Number | The number in little-endian (2 bytes) | 52 | 53 | ### `0xF3` Negative 16-bit Number (-65536 to -1) 54 | 55 | | Value | Description | 56 | |--------|----------------------------------------------------| 57 | | `0xF3` | A single byte to indicate a negative 16-bit number | 58 | | Number | The number + 65536 in little-endian (2 bytes) | 59 | 60 | ### `0xF4` Positive 32-bit Number (0 to 4294967295) 61 | 62 | | Value | Description | 63 | |--------|----------------------------------------------------| 64 | | `0xF4` | A single byte to indicate a positive 32-bit number | 65 | | Number | The number in little-endian (4 bytes) | 66 | 67 | ### `0xF5` Negative 32-bit Number (-4294967296 to -1) 68 | 69 | | Value | Description | 70 | |--------|----------------------------------------------------| 71 | | `0xF5` | A single byte to indicate a negative 32-bit number | 72 | | Number | The number + 4294967296 in little-endian (4 bytes) | 73 | 74 | ### `0xF6` 64-bit Floating-point Number 75 | 76 | | Value | Description | 77 | |---------|----------------------------------------------------| 78 | | `0xF6` | A single byte to indicate a number | 79 | | Number | The raw 64-bit `double` in little-endian (8 bytes) | 80 | 81 | ### `0xF7` Nil 82 | 83 | | Value | Description | 84 | |--------|---------------------------------| 85 | | `0xF7` | A single byte to indicate `nil` | 86 | 87 | ### `0xF8` String 88 | 89 | | Value | Description | 90 | |---------|----------------------------------------| 91 | | `0xF8` | A single byte to indicate a string | 92 | | V-Int | String table index | 93 | 94 | ### `0xF9` New List 95 | 96 | Lists are the only compound type. Each new list is assigned an internal index, starting with 0 and 97 | increasing by 1, in case it must be referenced later. 98 | 99 | | Value | Description | 100 | |--------|--------------------------------------------------------------------| 101 | | `0xF9` | A single byte to begin new list and assign it an internal index | 102 | | V-Int | Number of values in the list | 103 | | S-Val* | An S-Val for each of the values in the list | 104 | 105 | ### `0xFA` Referenced List 106 | 107 | Sibling or circular references are handled by referencing a previously provided new list, using its 108 | internal index. 109 | 110 | | Value | Description | 111 | |--------|------------------------------------------------------| 112 | | `0xFA` | A single byte to indicate a previously provided list | 113 | | V-Int | The internal index of the list | 114 | 115 | ## Examples 116 | 117 | Encoding `nil`: 118 | 119 | ``` 120 | 0x01 Header 121 | 0x00 String table size 122 | 0xF7 Nil 123 | ``` 124 | 125 | Encoding a list of strings `{'a', {0}, 'abcd', 'a'}`: 126 | 127 | ``` 128 | 0x01 Header 129 | 0x02 String table size 130 | 0x01 String[0] length 131 | 0x61 'a' 132 | 0x04 String[1] length 133 | 0x61 'a' 134 | 0x62 'b' 135 | 0x63 'c' 136 | 0x64 'd' 137 | 0xF9 New list (index 0) 138 | 0x04 List size (index 0) 139 | 0xF8 String 140 | 0x00 String[0] 141 | 0xF9 New list (index 1) 142 | 0x01 List size (index 1) 143 | 0xF0 Positive 8-bit number 144 | 0x00 Zero 145 | 0xF8 String 146 | 0x01 String[1] 147 | 0xF8 String 148 | 0x00 String[0] 149 | ``` 150 | 151 | Encoding of circular list `var a = {{-250}}; list.push a, a`: 152 | 153 | ``` 154 | 0x01 Header 155 | 0x00 String table size 156 | 0xF9 New list (index 0) 157 | 0x02 List size (index 0) 158 | 0xF9 New list (index 1) 159 | 0x01 List size (index 1) 160 | 0xF1 Negative 8-bit number 161 | 0x06 -250 (0x06 = -250 + 256) 162 | 0xFA Reference list 163 | 0x00 Index 0 164 | ``` 165 | -------------------------------------------------------------------------------- /docs/rand.md: -------------------------------------------------------------------------------- 1 | 2 | Random Number Generator 3 | ======================= 4 | 5 | ```c 6 | // RNG has 64-bit state 7 | static uint32_t seed, i; 8 | 9 | void rand_seed(uint32_t s){ 10 | seed = s; 11 | i = 0; 12 | } 13 | 14 | uint32_t rand_int(){ 15 | uint32_t m = 0x5bd1e995; 16 | uint32_t k = i++ * m; 17 | seed = (k ^ (k >> 24) ^ (seed * m)) * m; 18 | return seed ^ (seed >> 13); 19 | } 20 | 21 | double rand_num(){ 22 | uint64_t M1 = rand_int(); 23 | uint64_t M2 = rand_int(); 24 | uint64_t M = (M1 << 20) | (M2 >> 12); // 52 bit random number 25 | union { uint64_t i; double d; } u = { 26 | .i = UINT64_C(0x3FF) << 52 | M 27 | }; 28 | return u.d - 1.0; 29 | } 30 | 31 | double rand_range(double start, double stop, double step){ 32 | double count = ceil((stop - start) / step); 33 | return start + floor(rand_num() * count) * step; 34 | } 35 | 36 | void rand_getstate(uint32_t *state){ 37 | state[0] = seed; 38 | state[1] = i; 39 | } 40 | 41 | void rand_setstate(const uint32_t *state){ 42 | seed = state[0]; 43 | i = state[1]; 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /docs/range.md: -------------------------------------------------------------------------------- 1 | 2 | Range 3 | ===== 4 | 5 | ``` 6 | range 5 # {0, 1, 2, 3, 4} 7 | range -1 # {} 8 | range 3.4 # {0, 1, 2, 3} 9 | 10 | range 2, 5 # {2, 3, 4} 11 | range -1, 5 # {-1, 0, 1, 2, 3, 4} 12 | range 1.5, 3 # {1.5, 2.5} 13 | 14 | range 0, 10, 3 # {0, 3, 6, 9} 15 | range -1, -2, -0.25 # {-1, -1.25, -1.5, -1.75} 16 | range 0, 0.5, 0.1 # {0, 0.1, 0.2, 0.3, 0.4} 17 | ``` 18 | 19 | Note that special optimizations are performed when using `range` in a for loop, as long as 1-3 20 | arguments are specified: 21 | 22 | ``` 23 | # temporary list not actually created because `range` is called with 1-3 24 | # arguments: 25 | var start = 10, stop = 10000, step = 0.001 26 | for var v, i: range stop * 3 # does *not* create a list of 30000 items! 27 | say v, i 28 | end 29 | 30 | for var v, i: range start + 5, stop / 2 31 | say v, i 32 | end 33 | 34 | for var v, i: range start, stop, step * 3 35 | say v, i 36 | end 37 | 38 | # temporary list is created because the expression is too complicated: 39 | for var v, i: 5 * range 5 40 | say v, i 41 | end 42 | 43 | for var v, i: (range 5) + (range 6) 44 | say v, i 45 | end 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/shell.md: -------------------------------------------------------------------------------- 1 | 2 | Shell Library Extension 3 | ======================= 4 | 5 | `include 'shell'` 6 | 7 | The shell library is an extension available to sink scripts running inside the command-line tool's 8 | environment. 9 | 10 | | Function | Description | 11 | |----------------|--------------------------------------------------------------------------------| 12 | | [`version`](#version) | Verifies a minimum version of the sink executable; returns the current version | 13 | | [`args`](#args)| Returns the arguments passed into the script from the command line | 14 | | [`run`](#run) | Executes a child process and waits for it to finish; returns output and status | 15 | | [`which`](#which) | Search the `PATH` environment variable for the location of an executable | 16 | 17 | ``` 18 | run, which 19 | file: read, canread, copy, write, canwrite, exists, delete, temp, head, tail, permissions?, touch?, script?, sink? 20 | dir: create, delete, change, working, list 21 | path: join, basename, stat 22 | symlink: create, delete, resolve 23 | date 24 | sleep 25 | env: get, set 26 | args 27 | process: pid, list, kill 28 | user: whoami, id, who 29 | host: hostname, uname 30 | net: ping, listen, wget/curl 31 | glob, find 32 | ``` 33 | 34 | version 35 | ------- 36 | 37 | ``` 38 | version [major], [minor], [patch] 39 | ``` 40 | 41 | If parameters are specified, it will verify that the host is at least the version specified. If 42 | the host doesn't meet the requirement, it will abort the script with an error. 43 | 44 | Returns the current version in the format of `{ major, minor, patch }`. 45 | 46 | args 47 | ---- 48 | 49 | ``` 50 | args 51 | ``` 52 | 53 | Returns a list of strings of the arguments listed *after* the script name. 54 | 55 | Note: the zeroth element *is not* the location of sink or the location of the script. These 56 | locations are stored in [`file.sink`](#file.sink) and [`file.script`](#file.script) respectively. 57 | 58 | run 59 | --- 60 | 61 | ``` 62 | run file, [args], [env], [stdin], [capture] 63 | ``` 64 | 65 | `file` is the executable to run. If the file is simply a command name, the `PATH` environment 66 | variable will be searched to locate the executable. If the executable isn't found, then the script 67 | will abort in failure. 68 | 69 | `[args]`, if provided, is a list of strings used as the command-line arguments. 70 | 71 | `[env]`, if provided, is a list of environment variables, in the format of: `{{ 'ENV1', 'VALUE1'}, 72 | {'ENV2', 'VALUE2'}, ... }` (default: copy current environment). 73 | 74 | `[stdin]`, if provided, is a string that will be fed as standard input. 75 | 76 | `[capture]`, if provided, is a flag that determines if stdout and stderr are captured (default: 77 | false). 78 | 79 | If capture mode is disabled, then the return value is the exit status of the process. 80 | 81 | If capture mode is enabled, then the return value is a list: `{ , , }`. 82 | 83 | Where `` is the exit status of the process, and `` and `` are the captured 84 | output (strings). 85 | 86 | which 87 | ----- 88 | 89 | ``` 90 | which file 91 | ``` 92 | 93 | `file` is the command name to search in the `PATH` environment variable. 94 | 95 | Returns a string of the aboslute path if an executable was found, or `nil` if nothing was found. 96 | -------------------------------------------------------------------------------- /docs/stacktrace.md: -------------------------------------------------------------------------------- 1 | 2 | Stacktrace 3 | ========== 4 | 5 | Note that `stacktrace` always returns a list of strings, but the list could be empty, or the format 6 | of each string could change. The `stacktrace` command should only be used for logging, and its 7 | contents should not be relied upon or parsed. 8 | 9 | If a sink script is compiled without debug information, `stacktrace` will simply return an empty 10 | list `{}`. 11 | -------------------------------------------------------------------------------- /docs/tech-info.md: -------------------------------------------------------------------------------- 1 | Technical Information 2 | ===================== 3 | 4 | Sink development started in March of 2016. 5 | 6 | Language design: 7 | 8 | * Imperative/procedural 9 | * Dynamically typed 10 | * Garbage collected 11 | * Lexical scoping 12 | * Static binding with robust namespacing 13 | * A mixture between shell scripts, Lua, and a bit of Lisp 14 | 15 | Both implementations: 16 | 17 | * Virtual machine with a big `switch` statement 18 | * Register based VM with 256 registers per stack frame 19 | * Lexer, parser, code generator, and VM are stackless and can be paused at any moment 20 | * Tail call optimization supported 21 | * REPL supported 22 | * All I/O is governed by the host 23 | * Designed for asynchronous use, with ability to pause execution at any time 24 | * Almost identical [embedding API](https://github.com/velipso/sink/blob/master/docs/embedding.md) between implementations 25 | 26 | C99 implementation: 27 | 28 | * Values are stored via [NaN-boxing](https://sean.fun/a/nan-boxing) 29 | * Simple stop-the-world mark sweep garbage collector 30 | * Easy to build, just two files: `sink.h` and `sink.c` 31 | * Asynchronous commands via simplified Promise-like objects `sink_wait` 32 | 33 | TypeScript implementation: 34 | 35 | * Values are stored as JavaScript values `null`, numbers, strings, and arrays 36 | * Relies on the native JavaScript garbage collector 37 | * Asynchronous commands and I/O via JavaScript Promises 38 | -------------------------------------------------------------------------------- /docs/why.md: -------------------------------------------------------------------------------- 1 | 2 | Why Another Language 3 | ==================== 4 | 5 | Why bother creating sink? 6 | 7 | (Warning: Rant ahead) 8 | 9 | ### It's Fun! 10 | 11 | Creating a language is *fun*. Once you know the different techniques that go into making a 12 | language (lexer, parser, code generator, virtual machine, garbage collector), it's pretty damn 13 | fun to create new ones. 14 | 15 | ### Competition is Slim for Embeddable Languages 16 | 17 | Most languages are created to solve every problem in the world, which causes the language to become 18 | a beast in one form or another. 19 | 20 | Languages designed for embedding into host programs are a little more slim: 21 | 22 | 1. [Lua](http://lua.org) - Overall excellent and one I've used quite a lot, but a few annoyances 23 | * Starts counting from one 24 | * Everything is a hash table 25 | * Variables can be used without declaring them 26 | * No `continue` statement 27 | * API is stack based 28 | * Syntax is sometimes a bit goofy for my taste 29 | * They keep adding more crap... I wish they would just stop 30 | 2. [Squirrel](http://squirrel-lang.org/) - Popular, haven't really used it 31 | * Object-oriented 32 | * Similar hash table to Lua 33 | * Very C++ like syntax 34 | 3. [Tcl](http://wiki.tcl.tk/) - Older popular embedding language 35 | * Syntax is a mess 36 | * Semantics are a mess 37 | 38 | Surely someone could make a lot of complaints about sink too. No language is perfect. 39 | 40 | But sink was created to scratch my particular itch. 41 | 42 | ### Need TypeScript + C99 Implementation 43 | 44 | Embedding languages typically think of JavaScript as an afterthought, perhaps accomplished via 45 | Emscripten or a third-party port. 46 | 47 | Having a JavaScript implementation, via TypeScript, is really nice because I can build web 48 | applications that use the same language, with the same standard library. That means I can create 49 | cross-platform tools for creating resources for my games. 50 | 51 | ### Compile-Time Symbol Table 52 | 53 | The idea that a simple variable lookup results in a hash table search is a bit sickening to me. 54 | 55 | Sink's symbols (variable names, command names, namespaces) only exist at compile-time. All name 56 | resolution happens at compile-time. 57 | 58 | Even native commands are stored as 64-bit hashes, so all symbols can be stripped out of the final 59 | bytecode. 60 | 61 | ### No Feature Creep 62 | 63 | The most annoying thing about other languages is that *they keep changing*. 64 | 65 | Programming languages are foundational technology. Adding a new feature *creates a new language*. 66 | I really wish people would stop changing languages so much. 67 | 68 | Once sink hits 1.0, I won't be changing it, with the exception of bug fixes and security issues. If 69 | I feel like creating a new language, I will do just that -- and leave sink alone. 70 | 71 | ### Most Features Aren't Worth It 72 | 73 | Languages usually wear their features like a badge of honor. One feature that I wear proudly is 74 | that *sink doesn't have many features*. 75 | 76 | Classes? Nope. Closures? Nope. Exceptions? Nope. Coroutines? Nope. 77 | 78 | Every feature has a real *cost*: 79 | 80 | * Developing and maintaining each feature 81 | * Documenting, teaching, and learning each feature 82 | * Deciding which feature to use when scripting 83 | * Reading someone else's code who uses a feature that you don't typically use 84 | 85 | Features are not badges of honor, they are *concessions* -- we should be apologizing for each 86 | feature because they represent a failure to solve a problem with something that already exists. 87 | 88 | I've tried very hard to make sink as small as I possibly could, while still having the bare-bone 89 | features necessary to accomplish real work. 90 | -------------------------------------------------------------------------------- /editor-configs/codemirror.net/README.md: -------------------------------------------------------------------------------- 1 | Syntax Highlighter for CodeMirror 2 | ================================= 3 | 4 | Sink syntax highlighter for the [CodeMirror](http://codemirror.net) editor. 5 | 6 | Install Instructions 7 | ==================== 8 | 9 | After loading the CodeMirror code, include the `codemirror-sink.js` file somehow on the page, for 10 | example: 11 | 12 | ```html 13 | 14 | 15 | ``` 16 | 17 | This will install the `'sink'` mode and the `'text/x-sink'` MIME. 18 | 19 | To create a sink editor on a page, something like this will work: 20 | 21 | ```javascript 22 | var myCodeMirror = CodeMirror(document.body, { 23 | value: 'say "hello, world"\n', 24 | mode: 'sink' // <- the important part 25 | }); 26 | ``` 27 | -------------------------------------------------------------------------------- /editor-configs/codemirror.net/codemirror-sink.js: -------------------------------------------------------------------------------- 1 | // 2 | // sink - Minimal programming language for embedding small scripts in larger programs 3 | // by Sean Connelly (@velipso), https://sean.fun 4 | // Project Home: https://github.com/velipso/sink 5 | // SPDX-License-Identifier: 0BSD 6 | // 7 | 8 | (function(mod){ 9 | if (typeof exports == 'object' && typeof module == 'object') // CommonJS 10 | mod(require('../../lib/codemirror')); 11 | else if (typeof define == 'function' && define.amd) // AMD 12 | define(['../../lib/codemirror'], mod); 13 | else // Plain browser env 14 | mod(CodeMirror); 15 | })(function(CodeMirror){ 16 | 17 | CodeMirror.defineMode('sink', function(config, parserConfig) { 18 | function has(obj, key){ 19 | return Object.prototype.hasOwnProperty.call(obj, key); 20 | } 21 | var specials = [ 22 | '+', '-', '%', '*', '/', '^', '~', '+=', '-=', '%=', '*=', '/=', '^=', '~=', '<', '>', '!', '=', 23 | '||', '&&', '<=', '>=', '!=', '==', '||=', '&&=', '(', '[', '{', ',', '|', '&', ')', ']', '}', 24 | ':', '.', '...' 25 | ]; 26 | 27 | var libs = { 28 | 'nil': true, 'say': true, 'warn': true, 'ask': true, 'exit': true, 'abort': true, 29 | 'isnum': true, 'isstr': true, 'islist': true, 'isnative': true, 'range': true, 'order': true, 30 | 'pick': true, 'embed': true, 'stacktrace': true, 31 | 'num': [ 32 | 'abs', 'sign', 'max', 'min', 'clamp', 'floor', 'ceil', 'round', 'trunc', 'nan', 'inf', 33 | 'isnan', 'isfinite', 'e', 'pi', 'tau', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 34 | 'atan2', 'log', 'log2', 'log10', 'exp', 'lerp', 'hex', 'oct', 'bin' 35 | ], 36 | 'int': [ 37 | 'new', 'not', 'and', 'or', 'xor', 'shl', 'shr', 'sar', 'add', 'sub', 'mul', 'div', 38 | 'mod', 'clz', 'pop', 'bswap' 39 | ], 40 | 'rand': [ 41 | 'seed', 'seedauto', 'int', 'num', 'getstate', 'setstate', 'pick', 'shuffle' 42 | ], 43 | 'str': [ 44 | 'new', 'split', 'replace', 'begins', 'ends', 'pad', 'find', 'rfind', 'lower', 'upper', 45 | 'trim', 'rev', 'rep', 'list', 'byte', 'hash' 46 | ], 47 | 'utf8': [ 48 | 'valid', 'list', 'str' 49 | ], 50 | 'struct': [ 51 | 'size', 'str', 'list', 'isLE', 'U8', 'U16', 'UL16', 'UB16', 'U32', 'UL32', 'UB32', 'S8', 52 | 'S16', 'SL16', 'SB16', 'S32', 'SL32', 'SB32', 'F32', 'FL32', 'FB32', 'F64', 'FL64', 'FB64' 53 | ], 54 | 'list': [ 55 | 'new', 'shift', 'pop', 'push', 'unshift', 'append', 'prepend', 'find', 'rfind', 'join', 56 | 'rev', 'str', 'sort', 'rsort' 57 | ], 58 | 'pickle': [ 59 | 'json', 'bin', 'val', 'valid', 'sibling', 'circular', 'copy' 60 | ], 61 | 'gc': [ 62 | 'getlevel', 'setlevel', 'run', 'NONE', 'DEFAULT', 'LOWMEM' 63 | ] 64 | }; 65 | 66 | var keywords = [ 67 | 'break', 'continue', 'declare', 'def', 'do', 'else', 'elseif', 'end', 'enum', 'for', 'goto', 68 | 'if', 'include', 'namespace', 'return', 'using', 'var', 'while' 69 | ]; 70 | 71 | function ident(id){ 72 | if (keywords.indexOf(id) >= 0) 73 | return 'keyword'; 74 | return 'variable'; 75 | } 76 | 77 | function isSpace(c){ 78 | return c === ' ' || c === '\n' || c === '\r' || c === '\t'; 79 | } 80 | 81 | function isAlpha(c){ 82 | return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); 83 | } 84 | 85 | function isNum(c){ 86 | return c >= '0' && c <= '9'; 87 | } 88 | 89 | function isIdentStart(c){ 90 | return isAlpha(c) || c === '_'; 91 | } 92 | 93 | function isIdentBody(c){ 94 | return isIdentStart(c) || isNum(c); 95 | } 96 | 97 | return { 98 | startState: function(basecol) { 99 | return { 100 | state: 'start', 101 | braces: [0], 102 | lib: '' 103 | }; 104 | }, 105 | token: function(stream, st){ 106 | var id = ''; 107 | while (!stream.eol()){ 108 | var ch = stream.next(); 109 | switch (st.state){ 110 | case 'start': 111 | if (isSpace(ch)){ 112 | stream.eatSpace(); 113 | return null; 114 | } 115 | else if (ch == '#'){ 116 | stream.skipToEnd(); 117 | return 'comment'; 118 | } 119 | else if (ch == '/' && stream.eat('*')) 120 | st.state = 'blockcomment'; 121 | else if (isNum(ch) || ((ch == '-' || ch == '+') && isNum(stream.peek()))){ 122 | var pk = stream.peek(); 123 | if (ch == '0' && (pk == 'x' || pk == 'c' || pk == 'b')) 124 | stream.next(); 125 | if (isNum(pk)) 126 | st.state = 'number'; 127 | else 128 | return 'number' 129 | } 130 | else if (isIdentStart(ch)){ 131 | if (isIdentBody(stream.peek())){ 132 | id = ch; 133 | st.state = 'ident'; 134 | } 135 | else 136 | return ident(ch); 137 | } 138 | else if (specials.indexOf(ch) >= 0){ 139 | if (ch == '{') 140 | st.braces[0]++; 141 | else if (ch == '}'){ 142 | if (st.braces[0] > 0) 143 | st.braces[0]--; 144 | else if (st.braces.length > 1){ 145 | st.braces.shift(); 146 | st.state = 'strinterp'; 147 | break; 148 | } 149 | } 150 | while (specials.indexOf(ch + stream.peek()) >= 0) 151 | ch += stream.next(); 152 | return 'builtin'; 153 | } 154 | else if (ch == '\''){ 155 | while (!stream.eol()){ 156 | ch = stream.next(); 157 | if (ch == '\''){ 158 | if (stream.peek() == '\'') 159 | stream.next(); 160 | else 161 | return 'string'; 162 | } 163 | } 164 | return 'error'; 165 | } 166 | else if (ch == '"') 167 | st.state = 'strinterp'; 168 | break; 169 | 170 | case 'blockcomment': 171 | while (true){ 172 | if (ch == '*' && stream.eat('/')){ 173 | st.state = 'start'; 174 | return 'comment'; 175 | } 176 | if (stream.eol()) 177 | break; 178 | ch = stream.next(); 179 | } 180 | return 'comment'; 181 | 182 | case 'number': 183 | while (true){ 184 | if (stream.eol()) 185 | break; 186 | if (!isIdentBody(stream.peek())) 187 | break; 188 | stream.next(); 189 | } 190 | st.state = 'start'; 191 | return 'number'; 192 | 193 | case 'ident': 194 | while (true){ 195 | id += ch; 196 | if (stream.eol()) 197 | break; 198 | if (!isIdentBody(stream.peek())) 199 | break; 200 | ch = stream.next(); 201 | } 202 | if (has(libs, id)){ 203 | if (libs[id] === true){ 204 | st.state = 'start'; 205 | return 'builtin'; 206 | } 207 | else if (stream.peek() == '.'){ 208 | st.lib = id; 209 | id = ''; 210 | st.state = 'lib'; 211 | return 'builtin'; 212 | } 213 | } 214 | st.state = 'start'; 215 | return ident(id); 216 | 217 | case 'strinterp': 218 | while (true){ 219 | if (ch == '\\') 220 | stream.next(); 221 | else if (ch == '"'){ 222 | st.state = 'start'; 223 | return 'string'; 224 | } 225 | else if (ch == '$' && stream.peek() == '{'){ 226 | stream.next(); 227 | st.braces.unshift(0); 228 | st.state = 'start'; 229 | return 'string'; 230 | } 231 | if (stream.eol()) 232 | break; 233 | ch = stream.next(); 234 | } 235 | return 'error'; 236 | 237 | case 'lib': 238 | while (true){ 239 | id += ch == '.' ? '' : ch; 240 | if (stream.eol()) 241 | break; 242 | if (!isIdentBody(stream.peek())) 243 | break; 244 | ch = stream.next(); 245 | } 246 | st.state = 'start'; 247 | if (libs[st.lib].indexOf(id) >= 0) 248 | return 'builtin'; 249 | return 'variable'; 250 | } 251 | } 252 | switch (st.state){ 253 | case 'start': 254 | return null; 255 | case 'blockcomment': 256 | return 'comment'; 257 | case 'number': 258 | st.state = 'start'; 259 | return 'number'; 260 | case 'ident': 261 | st.state = 'start'; 262 | return ident(id); 263 | case 'strinterp': 264 | st.state = 'start'; 265 | return 'error'; 266 | case 'lib': 267 | st.state = 'start'; 268 | return 'error'; 269 | } 270 | return 'error'; 271 | } 272 | }; 273 | }); 274 | 275 | CodeMirror.defineMIME('text/x-sink', 'sink'); 276 | 277 | }); 278 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sink", 3 | "version": "0.1.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "sink", 9 | "version": "0.1.0", 10 | "license": "MIT", 11 | "bin": { 12 | "sink": "dist/sink/driver.js" 13 | }, 14 | "devDependencies": { 15 | "@types/node": "^20.14.8", 16 | "typescript": "^5.5.2" 17 | } 18 | }, 19 | "node_modules/@types/node": { 20 | "version": "20.14.8", 21 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz", 22 | "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==", 23 | "dev": true, 24 | "dependencies": { 25 | "undici-types": "~5.26.4" 26 | } 27 | }, 28 | "node_modules/typescript": { 29 | "version": "5.5.2", 30 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", 31 | "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", 32 | "dev": true, 33 | "bin": { 34 | "tsc": "bin/tsc", 35 | "tsserver": "bin/tsserver" 36 | }, 37 | "engines": { 38 | "node": ">=14.17" 39 | } 40 | }, 41 | "node_modules/undici-types": { 42 | "version": "5.26.5", 43 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 44 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 45 | "dev": true 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sink", 3 | "version": "0.1.0", 4 | "description": "Minimalistic programming language for embedding small scripts in larger programs", 5 | "main": "./dist/sink/sink.js", 6 | "bin": { 7 | "sink": "./dist/sink/driver.js" 8 | }, 9 | "scripts": { 10 | "tsc": "tsc" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git@github.com:velipso/sink.git" 15 | }, 16 | "keywords": [ 17 | "programming language", 18 | "scripting" 19 | ], 20 | "author": { 21 | "name": "Sean", 22 | "url": "https://sean.fun" 23 | }, 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/velipso/sink/issues" 27 | }, 28 | "homepage": "https://github.com/velipso/sink", 29 | "devDependencies": { 30 | "@types/node": "^20.14.8", 31 | "typescript": "^5.5.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /perf/callcnt.sink: -------------------------------------------------------------------------------- 1 | 2 | def add a, b 3 | return a + b 4 | end 5 | 6 | var tot = 0 7 | for var i: range 1000000 8 | tot += add rand.int, rand.int 9 | if i % 1000 == 0 10 | say tot 11 | end 12 | end 13 | say 'end', tot 14 | -------------------------------------------------------------------------------- /perf/cat.sink: -------------------------------------------------------------------------------- 1 | 2 | var res 3 | for var i: range 10000 4 | res ~= 5 | "i+0: ${i + 0}, i+1: ${i + 1}, i+2: ${i + 2}, i+3: ${i + 3}" ~ 6 | "i+4: ${i + 4}, i+5: ${i + 5}, i+6: ${i + 6}, i+7: ${i + 7}" ~ 7 | "i+8: ${i + 8}, i+9: ${i + 9}, i+8: ${i + 8}, i+7: ${i + 7}" 8 | end 9 | say res 10 | -------------------------------------------------------------------------------- /src/cmd.c: -------------------------------------------------------------------------------- 1 | // 2 | // sink - Minimal programming language for embedding small scripts in larger programs 3 | // by Sean Connelly (@velipso), https://sean.fun 4 | // Project Home: https://github.com/velipso/sink 5 | // SPDX-License-Identifier: 0BSD 6 | // 7 | 8 | #include "sink.h" 9 | #include "sink_shell.h" 10 | #include 11 | #include 12 | 13 | #if defined(SINK_WIN) 14 | # include // _getcwd 15 | # define getcwd _getcwd 16 | # include // _setmode, _fileno 17 | # include // _O_BINARY 18 | #else 19 | # include // getcwd 20 | #endif 21 | 22 | #if defined(SINK_WIN) 23 | static inline FILE *fopen_i(const char *file, const char *mode){ 24 | // remove annoying warnings about using "deprecated" (ugh) fopen 25 | FILE *fp; 26 | errno_t err = fopen_s(&fp, file, mode); 27 | if (err != 0){ 28 | if (fp) 29 | fclose(fp); 30 | return NULL; 31 | } 32 | return fp; 33 | } 34 | #else 35 | # define fopen_i(a, b) fopen(a, b) 36 | #endif 37 | 38 | #if defined(SINK_WIN) 39 | static inline const char *getenv_i(const char *name){ 40 | char *buf; 41 | _dupenv_s(&buf, NULL, name); 42 | return buf; 43 | } 44 | 45 | static inline void freeenv_i(const char *ptr){ 46 | free((char *)ptr); 47 | } 48 | #else 49 | # define getenv_i(s) getenv(s) 50 | # define freeenv_i(s) 51 | #endif 52 | 53 | static sink_wait io_say(sink_ctx ctx, sink_str str, void *iouser){ 54 | printf("%.*s\n", str.size, str.bytes); 55 | return NULL; 56 | } 57 | 58 | static sink_wait io_warn(sink_ctx ctx, sink_str str, void *iouser){ 59 | fprintf(stderr, "%.*s\n", str.size, str.bytes); 60 | return NULL; 61 | } 62 | 63 | static sink_wait io_ask(sink_ctx ctx, sink_str str, void *iouser){ 64 | printf("%.*s", str.size, str.bytes); 65 | char buf[1000]; 66 | if (fgets(buf, sizeof(buf), stdin) == NULL) 67 | return NULL; 68 | int sz = strlen(buf); 69 | if (sz <= 0) 70 | return sink_done(ctx, sink_str_newcstr(ctx, "")); 71 | if (buf[sz - 1] == '\n') 72 | buf[--sz] = 0; // TODO: do I need to check for \r as well..? test on windows 73 | return sink_done(ctx, sink_str_newblob(ctx, sz, (const uint8_t *)buf)); 74 | } 75 | 76 | static sink_io_st io = (sink_io_st){ 77 | .f_say = io_say, 78 | .f_warn = io_warn, 79 | .f_ask = io_ask, 80 | .user = NULL 81 | }; 82 | 83 | static volatile bool done = false; 84 | 85 | #if defined(SINK_POSIX) || defined(SINK_MAC) 86 | 87 | #include 88 | 89 | static void catchdone(int dummy){ 90 | fclose(stdin); 91 | done = true; 92 | } 93 | 94 | static inline void catchint(){ 95 | signal(SIGINT, catchdone); 96 | signal(SIGSTOP, catchdone); 97 | } 98 | 99 | #else 100 | 101 | static inline void catchint(){ 102 | // do nothing, I guess 103 | } 104 | 105 | #endif 106 | 107 | #if defined(SINK_POSIX) || defined(SINK_MAC) 108 | # include 109 | #else 110 | # include 111 | #endif 112 | 113 | #include 114 | 115 | #if !defined(S_ISDIR) 116 | # define S_ISDIR(st_mode) (st_mode & S_IFDIR) 117 | #endif 118 | 119 | static bool isdir(const char *dir){ 120 | struct stat buf; 121 | if (stat(dir, &buf) != 0) 122 | return 0; 123 | return S_ISDIR(buf.st_mode); 124 | } 125 | 126 | static bool isfile(const char *file){ 127 | FILE *fp = fopen_i(file, "rb"); 128 | if (fp == NULL) 129 | return false; 130 | fclose(fp); 131 | return true; 132 | } 133 | 134 | static bool fsread(sink_scr scr, const char *file, void *user){ 135 | FILE *fp = fopen_i(file, "rb"); 136 | if (fp == NULL) 137 | return false; // `false` indicates that the file couldn't be read 138 | char buf[5000]; 139 | while (!feof(fp)){ 140 | size_t sz = fread(buf, 1, sizeof(buf), fp); 141 | if (!sink_scr_write(scr, sz, (const uint8_t *)buf)) 142 | break; 143 | } 144 | fclose(fp); 145 | return true; // `true` indicates that the file was read 146 | } 147 | 148 | static sink_fstype fstype(sink_scr scr, const char *file, void *user){ 149 | if (isdir(file)) 150 | return SINK_FSTYPE_DIR; 151 | else if (isfile(file)) 152 | return SINK_FSTYPE_FILE; 153 | return SINK_FSTYPE_NONE; 154 | } 155 | 156 | static sink_inc_st inc = { 157 | .f_fstype = fstype, 158 | .f_fsread = fsread, 159 | .user = NULL 160 | }; 161 | 162 | static inline sink_ctx newctx(sink_scr scr, int argc, char **argv, const char *sink_exe, 163 | const char *script){ 164 | // create the context with the standard I/O 165 | sink_ctx ctx = sink_ctx_new(scr, io); 166 | 167 | // add any libraries 168 | sink_shell_ctx(ctx, argc, argv, sink_exe, script); 169 | 170 | return ctx; 171 | } 172 | 173 | static inline void printline(int line, int level){ 174 | printf("%2d", line); 175 | if (level <= 0) 176 | printf(": "); 177 | else{ 178 | printf("."); 179 | for (int i = 0; i < level; i++) 180 | printf(".."); 181 | printf(" "); 182 | } 183 | } 184 | 185 | static inline void printscrerr(sink_scr scr){ 186 | // scripts always have an error message when they fail 187 | fprintf(stderr, "%s\n", sink_scr_geterr(scr)); 188 | } 189 | 190 | static inline void printctxerr(sink_ctx ctx){ 191 | const char *err = sink_ctx_geterr(ctx); 192 | if (err == NULL) // context can error without an error message if script contains `abort` 193 | return; // without any parameters 194 | fprintf(stderr, "%s\n", err); 195 | } 196 | 197 | typedef struct { 198 | sink_scr scr; 199 | int line; 200 | int size; 201 | int count; 202 | char *buf; 203 | int res; 204 | } replinfo_st, *replinfo; 205 | 206 | static void main_repl_nextline(sink_ctx ctx, sink_val statusv, replinfo ri){ 207 | if (done) 208 | return; 209 | 210 | sink_run status = (sink_run)sink_castnum(statusv); 211 | switch (status){ 212 | case SINK_RUN_PASS: 213 | done = true; 214 | ri->res = 0; 215 | return; 216 | case SINK_RUN_FAIL: 217 | printctxerr(ctx); 218 | break; 219 | case SINK_RUN_ASYNC: 220 | fprintf(stderr, "REPL returned async (impossible)\n"); 221 | done = true; 222 | return; 223 | case SINK_RUN_TIMEOUT: 224 | fprintf(stderr, "REPL returned timeout (impossible)\n"); 225 | done = true; 226 | return; 227 | case SINK_RUN_REPLMORE: 228 | // do nothing 229 | break; 230 | } 231 | 232 | printline(ri->line++, sink_scr_level(ri->scr)); 233 | ri->size = 0; 234 | while (!done){ 235 | int ch = getchar(); 236 | if (ch == EOF){ 237 | ch = '\n'; 238 | done = true; 239 | } 240 | if (ri->size >= ri->count - 1){ // make sure there is always room for two chars 241 | ri->count += 200; 242 | ri->buf = realloc(ri->buf, sizeof(char) * ri->count); 243 | if (ri->buf == NULL){ 244 | fprintf(stderr, "Out of memory!\n"); 245 | ri->res = 1; 246 | done = true; 247 | return; 248 | } 249 | } 250 | ri->buf[ri->size++] = ch; 251 | if (ch == '\n'){ 252 | if (!sink_scr_write(ri->scr, ri->size, (uint8_t *)ri->buf)) 253 | printscrerr(ri->scr); 254 | if (sink_scr_level(ri->scr) <= 0){ 255 | if (done){ // if done, run one last time 256 | sink_then( 257 | sink_ctx_run(ctx), 258 | (sink_then_st){ 259 | .f_then = (sink_then_f)main_repl_nextline, 260 | .f_cancel = NULL, 261 | .user = ri 262 | } 263 | ); 264 | } 265 | // the level is <= 0, so we need to run the context... this is done by returning 266 | // from this function, which will eventually return up to `main_repl`, where the 267 | // while-loop will execute `sink_ctx_run` 268 | return; 269 | } 270 | else{ 271 | if (done) 272 | return; 273 | printline(ri->line++, sink_scr_level(ri->scr)); 274 | ri->size = 0; 275 | } 276 | } 277 | } 278 | } 279 | 280 | static int main_repl(sink_scr scr, int argc, char **argv, const char *sink_exe){ 281 | catchint(); 282 | sink_ctx ctx = newctx(scr, argc, argv, sink_exe, NULL); 283 | 284 | replinfo_st ri = (replinfo_st){ 285 | .scr = scr, 286 | .line = 1, 287 | .size = 0, 288 | .count = 0, 289 | .buf = NULL, 290 | .res = 0 291 | }; 292 | 293 | while (!done){ 294 | sink_then( 295 | sink_ctx_run(ctx), 296 | (sink_then_st){ 297 | .f_then = (sink_then_f)main_repl_nextline, 298 | .f_cancel = NULL, 299 | .user = &ri 300 | } 301 | ); 302 | } 303 | 304 | free(ri.buf); 305 | sink_ctx_free(ctx); 306 | sink_scr_free(scr); 307 | return ri.res; 308 | } 309 | 310 | static void run_setresult(sink_ctx ctx, sink_val result, sink_run *output){ 311 | *output = result.f; 312 | } 313 | 314 | static int main_run(sink_scr scr, const char *file, int argc, char **argv, const char *sink_exe){ 315 | if (!sink_scr_loadfile(scr, file)){ 316 | printscrerr(scr); 317 | sink_scr_free(scr); 318 | return 1; 319 | } 320 | sink_ctx ctx = newctx(scr, argc, argv, sink_exe, sink_scr_getfile(scr)); 321 | sink_run res = SINK_RUN_FAIL; 322 | sink_then( 323 | sink_ctx_run(ctx), 324 | (sink_then_st){ 325 | .f_then = (sink_then_f)run_setresult, 326 | .f_cancel = NULL, 327 | .user = &res 328 | } 329 | ); 330 | if (res == SINK_RUN_FAIL) 331 | printctxerr(ctx); 332 | sink_ctx_free(ctx); 333 | sink_scr_free(scr); 334 | return res == SINK_RUN_PASS ? 0 : 1; 335 | } 336 | 337 | static int main_eval(sink_scr scr, const char *eval, int argc, char **argv, const char *sink_exe){ 338 | if (!sink_scr_write(scr, strlen(eval), (const uint8_t *)eval)){ 339 | printscrerr(scr); 340 | sink_scr_free(scr); 341 | return 1; 342 | } 343 | sink_ctx ctx = newctx(scr, argc, argv, sink_exe, NULL); 344 | sink_run res = SINK_RUN_FAIL; 345 | sink_then( 346 | sink_ctx_run(ctx), 347 | (sink_then_st){ 348 | .f_then = (sink_then_f)run_setresult, 349 | .f_cancel = NULL, 350 | .user = &res 351 | } 352 | ); 353 | if (res == SINK_RUN_FAIL) 354 | printctxerr(ctx); 355 | sink_ctx_free(ctx); 356 | sink_scr_free(scr); 357 | return res == SINK_RUN_PASS ? 0 : 1; 358 | } 359 | 360 | static int main_compile_file(sink_scr scr, const char *file, bool debug){ 361 | if (!sink_scr_loadfile(scr, file)){ 362 | printscrerr(scr); 363 | sink_scr_free(scr); 364 | return 1; 365 | } 366 | #if defined(SINK_WIN) 367 | _setmode(_fileno(stdout), _O_BINARY); 368 | #endif 369 | sink_scr_dump(scr, debug, (void *)stdout, (sink_dump_f)fwrite); 370 | sink_scr_free(scr); 371 | return 0; 372 | } 373 | 374 | static int main_compile_eval(sink_scr scr, const char *eval, bool debug){ 375 | if (!sink_scr_write(scr, strlen(eval), (const uint8_t *)eval)){ 376 | printscrerr(scr); 377 | sink_scr_free(scr); 378 | return 1; 379 | } 380 | #if defined(SINK_WIN) 381 | _setmode(_fileno(stdout), _O_BINARY); 382 | #endif 383 | sink_scr_dump(scr, debug, (void *)stdout, (sink_dump_f)fwrite); 384 | sink_scr_free(scr); 385 | return 0; 386 | } 387 | 388 | static void print_version(){ 389 | printf( 390 | "Sink v1.0\n" 391 | "by Sean Connelly (@velipso), 0BSD License\n" 392 | "https://github.com/velipso/sink https://sean.fun\n" 393 | ); 394 | } 395 | 396 | static void print_help(){ 397 | print_version(); 398 | printf( 399 | "\nUsage:\n" 400 | " sink [options] [ -e '' | ] [arguments]\n" 401 | "\n" 402 | "With no arguments, sink will enter interactive mode (REPL).\n" 403 | "\n" 404 | "Option Description\n" 405 | " -v Display version information and exit\n" 406 | " -h, --help Display help information and exit\n" 407 | " -I Add to the include search path\n" 408 | " -c Compile input and output bytecode to stdout\n" 409 | " -d If compiling, output bytecode with debug info\n" 410 | " -D If compiling, add declarations when including \n" 411 | "\n" 412 | " The -D option is useful for providing declarations so that compilation can\n" 413 | " succeed for other host environments.\n" 414 | "\n" 415 | " For example, a host might provide declarations for native commands via:\n" 416 | "\n" 417 | " include 'shapes'\n" 418 | "\n" 419 | " The host could provide a declaration file, which can be used during\n" 420 | " compilation using a `-D shapes shapes_decl.sink` option. This means when the\n" 421 | " script executes `include 'shapes'`, the compiler will load `shapes_decl.sink`.\n" 422 | " Even though the compiler doesn't know how to execute the host commands, it can\n" 423 | " still compile the file for use in the host environment.\n"); 424 | } 425 | 426 | int main(int argc, char **argv){ 427 | bool compile = false; 428 | bool compile_debug = false; 429 | enum { 430 | INPUT_REPL, 431 | INPUT_FILE, 432 | INPUT_EVAL 433 | } input_type = INPUT_REPL; 434 | char *input_content = NULL; 435 | 436 | // first pass, just figure out what we're doing and validate arguments 437 | int i; 438 | for (i = 1; i < argc; i++){ 439 | if (strcmp(argv[i], "-v") == 0){ 440 | if (i + 1 < argc){ 441 | print_help(); 442 | return 1; 443 | } 444 | print_version(); 445 | return 0; 446 | } 447 | else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0){ 448 | print_help(); 449 | return i + 1 < argc ? 1 : 0; 450 | } 451 | else if (strcmp(argv[i], "-I") == 0){ 452 | if (i + 1 >= argc){ 453 | print_help(); 454 | return 1; 455 | } 456 | i++; // skip include path 457 | } 458 | else if (strcmp(argv[i], "-c") == 0) 459 | compile = true; 460 | else if (strcmp(argv[i], "-d") == 0) 461 | compile_debug = true; 462 | else if (strcmp(argv[i], "-D") == 0){ 463 | if (i + 2 >= argc){ 464 | print_help(); 465 | return 1; 466 | } 467 | i += 2; // skip declaration key/file 468 | } 469 | else if (strcmp(argv[i], "-e") == 0){ 470 | if (i + 1 >= argc){ 471 | print_help(); 472 | return 1; 473 | } 474 | input_content = argv[i + 1]; 475 | i += 2; // skip over script 476 | input_type = INPUT_EVAL; 477 | break; 478 | } 479 | else if (strcmp(argv[i], "--") == 0){ 480 | i++; 481 | break; 482 | } 483 | else{ 484 | if (argv[i][0] == '-'){ 485 | // some unknown option 486 | print_help(); 487 | return 1; 488 | } 489 | input_content = argv[i]; 490 | i++; // skip over file 491 | input_type = INPUT_FILE; 492 | break; 493 | } 494 | } 495 | 496 | if (compile && input_type == INPUT_REPL){ 497 | print_help(); 498 | return 1; 499 | } 500 | 501 | // grab sink arguments 502 | int s_argc = argc - i; 503 | char **s_argv = &argv[i]; 504 | 505 | // create the script with the current working directory 506 | char *cwd = getcwd(NULL, 0); 507 | #if defined(SINK_WIN) 508 | bool posix = false; 509 | #else 510 | bool posix = true; 511 | #endif 512 | sink_scr scr = sink_scr_new(inc, cwd, posix, input_type == INPUT_REPL); 513 | free(cwd); 514 | 515 | // add the appropriate paths 516 | const char *sp = getenv_i("SINK_PATH"); 517 | if (sp == NULL){ 518 | // if no environment variable, then add a default path of the current directory 519 | sink_scr_addpath(scr, "."); 520 | } 521 | else{ 522 | fprintf(stderr, "TODO: process SINK_PATH\n"); 523 | abort(); 524 | freeenv_i(sp); 525 | } 526 | 527 | // add any libraries 528 | sink_shell_scr(scr); 529 | 530 | // load include paths and declaration key/files 531 | for (i = 1; argv[i] != input_content; i++){ 532 | if (strcmp(argv[i], "-I") == 0){ 533 | sink_scr_addpath(scr, argv[i + 1]); 534 | i++; 535 | } 536 | else if (strcmp(argv[i], "-D") == 0){ 537 | sink_scr_incfile(scr, argv[i + 1], argv[i + 2]); 538 | i += 2; 539 | } 540 | } 541 | 542 | const char *sink_exe = argv[0]; 543 | 544 | switch (input_type){ 545 | case INPUT_FILE: 546 | if (compile) 547 | return main_compile_file(scr, input_content, compile_debug); 548 | return main_run(scr, input_content, s_argc, s_argv, sink_exe); 549 | 550 | case INPUT_REPL: 551 | return main_repl(scr, s_argc, s_argv, sink_exe); 552 | 553 | case INPUT_EVAL: 554 | if (compile) 555 | return main_compile_eval(scr, input_content, compile_debug); 556 | return main_eval(scr, input_content, s_argc, s_argv, sink_exe); 557 | } 558 | // shouldn't happen 559 | return 1; 560 | } 561 | -------------------------------------------------------------------------------- /src/cmd.ts: -------------------------------------------------------------------------------- 1 | // 2 | // sink - Minimal programming language for embedding small scripts in larger programs 3 | // by Sean Connelly (@velipso), https://sean.fun 4 | // Project Home: https://github.com/velipso/sink 5 | // SPDX-License-Identifier: 0BSD 6 | // 7 | 8 | import sink = require('./sink.js'); 9 | import sink_shell = require('./sink_shell.js'); 10 | import fs = require('fs'); 11 | import path = require('path'); 12 | import readline = require('readline'); 13 | 14 | async function io_say(ctx: sink.ctx, str: sink.str, iouser: any): Promise { 15 | console.log(str); 16 | return sink.NIL; 17 | } 18 | 19 | async function io_warn(ctx: sink.ctx, str: sink.str, iouser: any): Promise { 20 | console.error(str); 21 | return sink.NIL; 22 | } 23 | 24 | async function io_ask(ctx: sink.ctx, str: sink.str, iouser: any): Promise { 25 | var rl = readline.createInterface({ 26 | input: process.stdin, 27 | output: process.stdout 28 | }); 29 | return new Promise(function(resolve, reject){ 30 | rl.question(str, function(ans){ 31 | rl.close(); 32 | resolve(ans); 33 | }); 34 | }); 35 | } 36 | 37 | let io: sink.io_st = { 38 | f_say: io_say, 39 | f_warn: io_warn, 40 | f_ask: io_ask 41 | }; 42 | 43 | async function nodeStat(file: string): Promise { 44 | return new Promise(function(resolve, reject){ 45 | fs.stat(file, function(err, st: fs.Stats){ 46 | if (err){ 47 | if (err.code == 'ENOENT') 48 | resolve(null); 49 | else 50 | reject(err); 51 | } 52 | else 53 | resolve(st); 54 | }); 55 | }); 56 | } 57 | 58 | async function fstype(scr: sink.scr, file: string): Promise { 59 | let st = await nodeStat(file); 60 | if (st !== null){ 61 | if (st.isFile()) 62 | return sink.fstype.FILE; 63 | else if (st.isDirectory()) 64 | return sink.fstype.DIR; 65 | } 66 | return sink.fstype.NONE; 67 | } 68 | 69 | async function nodeRead(file: string): Promise { 70 | return new Promise(function(resolve, reject){ 71 | fs.readFile(file, 'binary', function(err, data){ 72 | if (err){ 73 | console.error(err); 74 | resolve(null); 75 | } 76 | else 77 | resolve(data); 78 | }); 79 | }); 80 | } 81 | 82 | async function fsread(scr: sink.scr, file: string): Promise { 83 | let data = await nodeRead(file); 84 | if (data === null) 85 | return false; // `false` indicates the file couldn't be read 86 | await sink.scr_write(scr, data); 87 | return true; // `true` indicates the file was read 88 | } 89 | 90 | let inc: sink.inc_st = { 91 | f_fstype: fstype, 92 | f_fsread: fsread 93 | }; 94 | 95 | function newctx(scr: sink.scr, argv: string[]): sink.ctx { 96 | let ctx = sink.ctx_new(scr, io); 97 | sink_shell.ctx(ctx, argv); 98 | return ctx; 99 | } 100 | 101 | function printscrerr(scr: sink.scr): void { 102 | console.error(sink.scr_geterr(scr)); 103 | } 104 | 105 | function printctxerr(ctx: sink.ctx): void { 106 | let err = sink.ctx_geterr(ctx); 107 | if (err === null) // context can error without an error message if script contains `abort` 108 | return; // without any parameters 109 | console.error(err); 110 | } 111 | 112 | async function readPrompt(p: string): Promise { 113 | var rl = readline.createInterface({ 114 | input: process.stdin, 115 | output: process.stdout 116 | }); 117 | return new Promise(function(resolve){ 118 | rl.question(p, function(ans){ 119 | rl.close(); 120 | resolve(ans); 121 | }); 122 | }); 123 | } 124 | 125 | async function main_repl(scr: sink.scr, argv: string[]): Promise { 126 | let ctx = newctx(scr, argv); 127 | let line = 1; 128 | while (true){ 129 | let levels = sink.scr_level(scr); 130 | var p = ': '; 131 | if (levels > 0) 132 | p = (new Array(levels + 1)).join('..') + '. '; 133 | if (line < 10) 134 | p = ' ' + line + p; 135 | else 136 | p = line + p; 137 | let ans = await readPrompt(p); 138 | line++; 139 | let buf = ans + '\n'; 140 | if (!await sink.scr_write(scr, buf)) 141 | printscrerr(scr); 142 | if (sink.scr_level(scr) <= 0){ 143 | switch (await sink.ctx_run(ctx)){ 144 | case sink.run.PASS: 145 | return true; 146 | case sink.run.FAIL: 147 | printctxerr(ctx); 148 | break; 149 | case sink.run.ASYNC: 150 | console.error('REPL returned async (impossible)'); 151 | return false; 152 | case sink.run.TIMEOUT: 153 | console.error('REPL returned timeout (impossible)'); 154 | return false; 155 | case sink.run.REPLMORE: 156 | // do nothing 157 | break; 158 | } 159 | } 160 | } 161 | } 162 | 163 | async function main_run(scr: sink.scr, file: string, argv: string[]): Promise { 164 | if (!await sink.scr_loadfile(scr, file)){ 165 | printscrerr(scr); 166 | return false; 167 | } 168 | let ctx = newctx(scr, argv); 169 | let res = await sink.ctx_run(ctx); 170 | if (res == sink.run.FAIL) 171 | printctxerr(ctx); 172 | return res == sink.run.PASS; 173 | } 174 | 175 | async function main_eval(scr: sink.scr, ev: string, argv: string[]): Promise { 176 | if (!await sink.scr_write(scr, ev)){ 177 | printscrerr(scr); 178 | return false; 179 | } 180 | let ctx = newctx(scr, argv); 181 | let res = await sink.ctx_run(ctx); 182 | if (res == sink.run.FAIL) 183 | printctxerr(ctx); 184 | return res == sink.run.PASS; 185 | } 186 | 187 | function perform_dump(scr: sink.scr, debug: boolean): void { 188 | // process.stdout.write isn't guaranteed to have synchronous writes (!!) 189 | let dump_data = ''; 190 | function dump(data: string): void { 191 | dump_data += data; 192 | } 193 | sink.scr_dump(scr, debug, null, dump); 194 | process.stdout.write(dump_data, 'binary'); 195 | } 196 | 197 | async function main_compile_file(scr: sink.scr, file: string, debug: boolean): Promise { 198 | if (!await sink.scr_loadfile(scr, file)){ 199 | printscrerr(scr); 200 | return false; 201 | } 202 | perform_dump(scr, debug); 203 | return true; 204 | } 205 | 206 | async function main_compile_eval(scr: sink.scr, ev: string, debug: boolean): Promise { 207 | if (!await sink.scr_write(scr, ev)){ 208 | printscrerr(scr); 209 | return false; 210 | } 211 | perform_dump(scr, debug); 212 | return true; 213 | } 214 | 215 | function print_version(): void { 216 | console.log( 217 | 'Sink v1.0\n' + 218 | 'by Sean Connelly (@velipso), 0BSD License\n' + 219 | 'https://github.com/velipso/sink https://sean.fun' 220 | ); 221 | } 222 | 223 | function print_help(): void { 224 | print_version(); 225 | console.log( 226 | '\nUsage:\n' + 227 | ' sink [options] [ -e \'\' | ] [arguments]\n' + 228 | '\n' + 229 | 'With no arguments, sink will enter interactive mode (REPL).\n' + 230 | '\n' + 231 | 'Option Description\n' + 232 | ' -v Display version information and exit\n' + 233 | ' -h, --help Display help information and exit\n' + 234 | ' -I Add to the include search path\n' + 235 | ' -c Compile input and output bytecode to stdout\n' + 236 | ' -d If compiling, output bytecode with debug info\n' + 237 | ' -D If compiling, add declarations when including \n' + 238 | '\n' + 239 | ' The -D option is useful for providing declarations so that compilation can\n' + 240 | ' succeed for other host environments.\n' + 241 | '\n' + 242 | ' For example, a host might provide declarations for native commands via:\n' + 243 | '\n' + 244 | ' include \'shapes\'\n' + 245 | '\n' + 246 | ' The host could provide a declaration file, which can be used during\n' + 247 | ' compilation using a `-D shapes shapes_decl.sink` option. This means when the\n' + 248 | ' script executes `include \'shapes\'`, the compiler will load `shapes_decl.sink`.\n' + 249 | ' Even though the compiler doesn\'t know how to execute the host commands, it can\n' + 250 | ' still compile the file for use in the host environment.'); 251 | } 252 | 253 | export async function main(): Promise { 254 | let argv = process.argv.splice(1); 255 | let argc = argv.length; 256 | let compile = false; 257 | let compile_debug = false; 258 | let input_type = 'repl'; 259 | let input_content = ''; 260 | 261 | // first pass, just figure out what we're doing and validate arguments 262 | let i = 1; 263 | for ( ; i < argc; i++){ 264 | let a = argv[i]; 265 | if (a === '-v'){ 266 | if (i + 1 < argc){ 267 | print_help(); 268 | return false; 269 | } 270 | print_version(); 271 | return true; 272 | } 273 | else if (a === '-h' || a === '--help'){ 274 | print_help(); 275 | return i + 1 < argc ? false : true; 276 | } 277 | else if (a === '-I'){ 278 | if (i + 1 >= argc){ 279 | print_help(); 280 | return false; 281 | } 282 | i++; // skip include path 283 | } 284 | else if (a === '-c') 285 | compile = true; 286 | else if (a === '-d') 287 | compile_debug = true; 288 | else if (a === '-D'){ 289 | if (i + 2 >= argc){ 290 | print_help(); 291 | return false; 292 | } 293 | i += 2; // skip declaration key/file 294 | } 295 | else if (a === '-e'){ 296 | if (i + 1 >= argc){ 297 | print_help(); 298 | return false; 299 | } 300 | input_content = argv[i + 1]; 301 | i += 2; // skip over script 302 | input_type = 'eval'; 303 | break; 304 | } 305 | else if (a === '--'){ 306 | i++; 307 | break; 308 | } 309 | else{ 310 | if (a.charAt(0) === '-'){ 311 | // some unknown option 312 | print_help(); 313 | return false; 314 | } 315 | input_content = a; 316 | i++; // skip over file 317 | input_type = 'file'; 318 | break; 319 | } 320 | } 321 | 322 | if (compile && input_type == 'repl'){ 323 | print_help(); 324 | return false; 325 | } 326 | 327 | // grab sink arguments 328 | let s_argv = argv.slice(i); 329 | 330 | // create the script with the current working directory 331 | let cwd = process.cwd(); 332 | let scr = sink.scr_new(inc, cwd, path.sep === '/', input_type === 'repl'); 333 | 334 | // add the appropriate paths 335 | sink.scr_addpath(scr, '.'); 336 | /* 337 | TODO: this 338 | const char *sp = getenv("SINK_PATH"); 339 | if (sp == NULL){ 340 | // if no environment variable, then add a default path of the current directory 341 | sink_scr_addpath(scr, "."); 342 | } 343 | else{ 344 | fprintf(stderr, "TODO: process SINK_PATH\n"); 345 | abort(); 346 | } 347 | */ 348 | 349 | // add any libraries 350 | sink_shell.scr(scr); 351 | 352 | // load include paths and declaration key/files 353 | for (i = 1; argv[i] !== input_content && i < argv.length; i++){ 354 | let a = argv[i]; 355 | if (a === '-I'){ 356 | sink.scr_addpath(scr, argv[i + 1]); 357 | i++; 358 | } 359 | else if (a === '-D'){ 360 | sink.scr_incfile(scr, argv[i + 1], argv[i + 2]); 361 | i += 2; 362 | } 363 | } 364 | 365 | if (input_type === 'file'){ 366 | if (compile) 367 | return main_compile_file(scr, input_content, compile_debug); 368 | return main_run(scr, input_content, s_argv); 369 | } 370 | else if (input_type === 'repl') 371 | return main_repl(scr, s_argv); 372 | else if (input_type === 'eval'){ 373 | if (compile) 374 | return main_compile_eval(scr, input_content, compile_debug); 375 | return main_eval(scr, input_content, s_argv); 376 | } 377 | // shouldn't happen 378 | throw new Error('Bad input type'); 379 | } 380 | -------------------------------------------------------------------------------- /src/driver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // 3 | // sink - Minimal programming language for embedding small scripts in larger programs 4 | // by Sean Connelly (@velipso), https://sean.fun 5 | // Project Home: https://github.com/velipso/sink 6 | // SPDX-License-Identifier: 0BSD 7 | // 8 | 9 | require('./cmd.js').main().then( 10 | function(res){ process.exit(res ? 0 : 1); }, 11 | function(err){ console.error(err); process.exit(1); } 12 | ); 13 | -------------------------------------------------------------------------------- /src/repl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sink REPL 5 | 6 | 15 | 16 | 17 | 18 | 210 | 211 | 212 | -------------------------------------------------------------------------------- /src/sink_shell.h: -------------------------------------------------------------------------------- 1 | // 2 | // sink - Minimal programming language for embedding small scripts in larger programs 3 | // by Sean Connelly (@velipso), https://sean.fun 4 | // Project Home: https://github.com/velipso/sink 5 | // SPDX-License-Identifier: 0BSD 6 | // 7 | 8 | #ifndef SINK_SHELL__H 9 | #define SINK_SHELL__H 10 | 11 | #include "sink.h" 12 | 13 | void sink_shell_scr(sink_scr scr); 14 | void sink_shell_ctx(sink_ctx ctx, int size, char **args, const char *sink_exe, const char *script); 15 | 16 | #endif // SINK_SHELL__H 17 | -------------------------------------------------------------------------------- /src/sink_shell.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // 3 | // sink - Minimal programming language for embedding small scripts in larger programs 4 | // by Sean Connelly (@velipso), https://sean.fun 5 | // Project Home: https://github.com/velipso/sink 6 | // SPDX-License-Identifier: 0BSD 7 | // 8 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 9 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 10 | return new (P || (P = Promise))(function (resolve, reject) { 11 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 12 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 13 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 14 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 15 | }); 16 | }; 17 | var __generator = (this && this.__generator) || function (thisArg, body) { 18 | var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; 19 | return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; 20 | function verb(n) { return function (v) { return step([n, v]); }; } 21 | function step(op) { 22 | if (f) throw new TypeError("Generator is already executing."); 23 | while (g && (g = 0, op[0] && (_ = 0)), _) try { 24 | if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; 25 | if (y = 0, t) op = [op[0] & 2, t.value]; 26 | switch (op[0]) { 27 | case 0: case 1: t = op; break; 28 | case 4: _.label++; return { value: op[1], done: false }; 29 | case 5: _.label++; y = op[1]; op = [0]; continue; 30 | case 7: op = _.ops.pop(); _.trys.pop(); continue; 31 | default: 32 | if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } 33 | if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } 34 | if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } 35 | if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } 36 | if (t[2]) _.ops.pop(); 37 | _.trys.pop(); continue; 38 | } 39 | op = body.call(thisArg, _); 40 | } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } 41 | if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; 42 | } 43 | }; 44 | Object.defineProperty(exports, "__esModule", { value: true }); 45 | exports.scr = scr; 46 | exports.ctx = ctx; 47 | var sink = require("./sink.js"); 48 | var VERSION_MAJ = 1; 49 | var VERSION_MIN = 0; 50 | var VERSION_PAT = 0; 51 | var isBrowser = typeof window === 'object'; 52 | function L_version(ctx, args) { 53 | return __awaiter(this, void 0, void 0, function () { 54 | var reqmaj, reqmin, reqpat; 55 | return __generator(this, function (_a) { 56 | reqmaj = 0, reqmin = 0, reqpat = 0; 57 | if (args.length >= 1) { 58 | if (!sink.isnum(args[0])) 59 | return [2 /*return*/, sink.abortstr(ctx, 'Expecting number')]; 60 | reqmaj = args[0] | 0; 61 | } 62 | if (args.length >= 2) { 63 | if (!sink.isnum(args[1])) 64 | return [2 /*return*/, sink.abortstr(ctx, 'Expecting number')]; 65 | reqmin = args[1] | 0; 66 | } 67 | if (args.length >= 3) { 68 | if (!sink.isnum(args[2])) 69 | return [2 /*return*/, sink.abortstr(ctx, 'Expecting number')]; 70 | reqpat = args[2] | 0; 71 | } 72 | while (true) { 73 | if (reqmaj > VERSION_MAJ) 74 | break; 75 | else if (reqmaj == VERSION_MAJ) { 76 | if (reqmin > VERSION_MIN) 77 | break; 78 | else if (reqmin == VERSION_MIN) { 79 | if (reqpat > VERSION_PAT) 80 | break; 81 | } 82 | } 83 | return [2 /*return*/, new sink.list(VERSION_MAJ, VERSION_MIN, VERSION_PAT)]; 84 | } 85 | return [2 /*return*/, sink.abortstr(ctx, 'Script requires version ' + reqmaj + '.' + reqmin + '.' + reqpat + ', but sink is ' + 86 | 'version ' + VERSION_MAJ + '.' + VERSION_MIN + '.' + VERSION_PAT)]; 87 | }); 88 | }); 89 | } 90 | function L_args(ctx, args, pargs) { 91 | return __awaiter(this, void 0, void 0, function () { 92 | var v, i; 93 | return __generator(this, function (_a) { 94 | v = new sink.list(); 95 | for (i = 0; i < pargs.length; i++) 96 | v.push(pargs[i]); 97 | return [2 /*return*/, v]; 98 | }); 99 | }); 100 | } 101 | function L_dir_work(ctx, args) { 102 | return __awaiter(this, void 0, void 0, function () { 103 | return __generator(this, function (_a) { 104 | return [2 /*return*/, isBrowser ? 105 | window.location.href 106 | .replace(/^.*:/, '') // remove protocol 107 | .replace(/\?.*$/, '') // remove query params 108 | .replace(/\/[^\/]*$/, '') : // remove trailing file and slash 109 | process.cwd()]; 110 | }); 111 | }); 112 | } 113 | function scr(scr) { 114 | sink.scr_incbody(scr, 'shell', "declare version 'sink.shell.version' ;" + 115 | "declare args 'sink.shell.args' ;" + 116 | "declare dir.work 'sink.shell.dir.work';"); 117 | } 118 | function ctx(ctx, args) { 119 | sink.ctx_native(ctx, 'sink.shell.version', null, L_version); 120 | sink.ctx_native(ctx, 'sink.shell.args', args, L_args); 121 | sink.ctx_native(ctx, 'sink.shell.dir.work', null, L_dir_work); 122 | } 123 | -------------------------------------------------------------------------------- /src/sink_shell.ts: -------------------------------------------------------------------------------- 1 | // 2 | // sink - Minimal programming language for embedding small scripts in larger programs 3 | // by Sean Connelly (@velipso), https://sean.fun 4 | // Project Home: https://github.com/velipso/sink 5 | // SPDX-License-Identifier: 0BSD 6 | // 7 | 8 | import sink = require('./sink.js'); 9 | 10 | const VERSION_MAJ = 1; 11 | const VERSION_MIN = 0; 12 | const VERSION_PAT = 0; 13 | 14 | var isBrowser = typeof window === 'object'; 15 | 16 | async function L_version(ctx: sink.ctx, args: sink.val[]): Promise { 17 | let reqmaj = 0, reqmin = 0, reqpat = 0; 18 | if (args.length >= 1){ 19 | if (!sink.isnum(args[0])) 20 | return sink.abortstr(ctx, 'Expecting number'); 21 | reqmaj = (args[0] as number) | 0; 22 | } 23 | if (args.length >= 2){ 24 | if (!sink.isnum(args[1])) 25 | return sink.abortstr(ctx, 'Expecting number'); 26 | reqmin = (args[1] as number) | 0; 27 | } 28 | if (args.length >= 3){ 29 | if (!sink.isnum(args[2])) 30 | return sink.abortstr(ctx, 'Expecting number'); 31 | reqpat = (args[2] as number) | 0; 32 | } 33 | while (true){ 34 | if (reqmaj > VERSION_MAJ) 35 | break; 36 | else if (reqmaj == VERSION_MAJ){ 37 | if (reqmin > VERSION_MIN) 38 | break; 39 | else if (reqmin == VERSION_MIN){ 40 | if (reqpat > VERSION_PAT) 41 | break; 42 | } 43 | } 44 | return new sink.list(VERSION_MAJ, VERSION_MIN, VERSION_PAT); 45 | } 46 | return sink.abortstr(ctx, 47 | 'Script requires version ' + reqmaj + '.' + reqmin + '.' + reqpat + ', but sink is ' + 48 | 'version ' + VERSION_MAJ + '.' + VERSION_MIN + '.' + VERSION_PAT); 49 | } 50 | 51 | async function L_args(ctx: sink.ctx, args: sink.val[], pargs: string[]): Promise { 52 | let v = new sink.list(); 53 | for (let i = 0; i < pargs.length; i++) 54 | v.push(pargs[i]); 55 | return v; 56 | } 57 | 58 | async function L_dir_work(ctx: sink.ctx, args: sink.val[]): Promise { 59 | return isBrowser ? 60 | window.location.href 61 | .replace(/^.*:/, '') // remove protocol 62 | .replace(/\?.*$/, '') // remove query params 63 | .replace(/\/[^\/]*$/, '') : // remove trailing file and slash 64 | process.cwd(); 65 | } 66 | 67 | export function scr(scr: sink.scr): void { 68 | sink.scr_incbody(scr, 'shell', 69 | "declare version 'sink.shell.version' ;" + 70 | "declare args 'sink.shell.args' ;" + 71 | "declare dir.work 'sink.shell.dir.work';"); 72 | } 73 | 74 | export function ctx(ctx: sink.ctx, args: string[]): void { 75 | sink.ctx_native(ctx, 'sink.shell.version', null, L_version); 76 | sink.ctx_native(ctx, 'sink.shell.args', args, L_args); 77 | sink.ctx_native(ctx, 'sink.shell.dir.work', null, L_dir_work); 78 | } 79 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ 5 | "module": "umd", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation: */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 12 | // "outFile": "../tgt/sink.js", /* Concatenate and emit output to single file. */ 13 | "outDir": "../tgt", /* Redirect output structure to the directory. */ 14 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 15 | "removeComments": true, /* Do not emit comments to output. */ 16 | // "noEmit": true, /* Do not emit outputs. */ 17 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 18 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 19 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 20 | 21 | /* Strict Type-Checking Options */ 22 | "strict": true, /* Enable all strict type-checking options. */ 23 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 24 | "strictNullChecks": true, /* Enable strict null checks. */ 25 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 26 | "alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */ 27 | 28 | /* Additional Checks */ 29 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 30 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 31 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 32 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 33 | 34 | /* Module Resolution Options */ 35 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 36 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 37 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 38 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 39 | // "typeRoots": [], /* List of folders to include type definitions from. */ 40 | // "types": [], /* Type declaration files to be included in compilation. */ 41 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 42 | 43 | /* Source Map Options */ 44 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 45 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 46 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 47 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 48 | 49 | /* Experimental Options */ 50 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 51 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tests/0.sanity/pass.txt: -------------------------------------------------------------------------------- 1 | 123 2 | 82938 3 | 7023512703 4 | hello, world 5 | hello, world 6 | x: 123, y: 423, z: hello 7 | add 5000, 1234 8 | 6234 9 | pass 10 | pass 11 | pass 12 | goto1 13 | goto3 14 | 0: 1 15 | 1: 2 16 | 2: 3 17 | 3: 4 18 | 4: 5 19 | 5: 6 20 | 1 21 | 3 22 | 2 23 | 1 24 | 0 25 | 5 26 | 4 27 | 3 28 | 2 29 | 1 30 | 0 31 | x is num 32 | x is nil 33 | hello world 34 | 5 35 | 4 36 | add 2, 3 37 | add 3, 4 38 | {5, 2, {3, 4, 6}, nil, nil, 7} 39 | true 40 | false 41 | false 42 | true 43 | pass 44 | false 45 | true 46 | pass 47 | true 48 | add 1, 2 49 | 3 50 | false 51 | add 4, 5 52 | 9 53 | 1 54 | 2 55 | 5 56 | {2, 3} 57 | {1, 2, 3, 4} 58 | {1, 6, 7, 8, 9, 4} 59 | {1, 2, 3} 60 | {1, 4, 3} 61 | add 3, 4 62 | x[0] = 1 x[1] = 7 63 | 0 a 64 | 1 b 65 | 2 c 66 | 3628800 67 | 97 68 | add -10, -5 69 | -15 70 | nil 71 | 18 72 | 3 73 | 3 74 | 5 75 | 0.1 76 | -0.1 77 | 3 78 | 3 4 79 | {1, 'two\three''four', 5} 80 | -------------------------------------------------------------------------------- /tests/0.sanity/sanity.sink: -------------------------------------------------------------------------------- 1 | # 2 | # comment 3 | # 4 | 5 | declare add 6 | 7 | say 123 8 | say 70235 + 12703 9 | say 70235 ~ 12703 10 | say "hello, world" 11 | say "hello, " ~ "world" 12 | 13 | var x = 1, y = 2, z = "hello" 14 | x = 123 15 | y = x + 300 16 | say "x: $x, y: $y, z: $z" 17 | 18 | say add 5000, 1234 19 | 20 | def add a, b 21 | say "add $a, $b" 22 | return a + b 23 | end 24 | 25 | x = 1 26 | 27 | if x < 0 28 | say 'fail' 29 | elseif x < 1 30 | say 'fail' 31 | else 32 | say 'pass' 33 | end 34 | 35 | if x > 0 36 | say 'pass' 37 | elseif x > 0 38 | say 'fail' 39 | elseif x > 1 40 | say 'fail' 41 | else 42 | say 'fail' 43 | end 44 | 45 | if x < 0 46 | say 'fail' 47 | elseif x == 1 48 | say 'pass' 49 | else 50 | say 'fail' 51 | end 52 | 53 | say 'goto1' 54 | goto test 55 | say 'goto2' 56 | test: 57 | say 'goto3' 58 | 59 | x = {1, 2, 3, 4} 60 | 61 | list.push x, 5 62 | 63 | for var e, i: x ~ {6, 7} 64 | say "$i: $e" 65 | if e == 6 66 | break 67 | end 68 | end 69 | 70 | say x[0] 71 | 72 | x = 3 73 | for 74 | say x 75 | if x == 0 76 | break 77 | end 78 | x -= 1 79 | end 80 | 81 | x = 5 82 | do 83 | say x 84 | while x > 0 85 | x -= 1 86 | end 87 | 88 | if isnum x 89 | say 'x is num' 90 | end 91 | 92 | x = nil 93 | if x == nil 94 | say 'x is nil' 95 | end 96 | 97 | say 'hello', 'world' 98 | 99 | def get_x 100 | return x 101 | end 102 | 103 | x = {1, 2, {3, 4}} 104 | x[0] = 5 105 | say x[0] 106 | list.push (get_x)[2], 6 107 | say x[2][1] 108 | x[add 2, 3] = add 3, 4 109 | 110 | say x 111 | 112 | def false 113 | say 'false' 114 | return nil 115 | end 116 | 117 | def true 118 | say 'true' 119 | return 1 120 | end 121 | 122 | if (true) && (false) 123 | say 'fail' 124 | end 125 | 126 | if (false) && (true) 127 | say 'fail' 128 | end 129 | 130 | if (true) || (false) 131 | say 'pass' 132 | end 133 | 134 | if (false) || (true) 135 | say 'pass' 136 | end 137 | 138 | say pick (true ), (add 1, 2), (add 2, 3) 139 | say pick (false), (add 3, 4), (add 4, 5) 140 | 141 | say 1 142 | do 143 | say 2 144 | goto test2 145 | say 3 146 | end 147 | say 4 148 | test2: 149 | say 5 150 | 151 | x = {1, 2, 3, 4} 152 | say x[1:2] # {2, 3} 153 | say x # {1, 2, 3, 4} 154 | x[1:2] = {6, 7, 8, 9} 155 | say x # {1, 6, 7, 8, 9, 4} 156 | 157 | x = {1, 2, 3} 158 | y = x[:] 159 | y[1] = 4 160 | say x # {1, 2, 3} 161 | say y # {1, 4, 3} 162 | 163 | x = {1, nil, 2} 164 | x[0] ||= add 1, 2 165 | x[1] ||= add 3, 4 166 | say "x[0] = ", x[0], "x[1] = ", x[1] 167 | 168 | var a = {'a', 'b', 'c'} 169 | for var s, i: a 170 | say i, s 171 | end 172 | 173 | def fac a 174 | if a <= 0 175 | return 1 176 | end 177 | return a * fac a - 1 178 | end 179 | say fac 10 # 3628800 180 | 181 | 182 | def one a, b, c 183 | def two a, b, d 184 | def three a, b, e 185 | return a + b + c + d + e 186 | end 187 | return three b, c, 3 188 | end 189 | return two b, c, 4 190 | end 191 | say one 10, 20, 30 # 97 192 | 193 | say add -10, -5 194 | 195 | var d 196 | say d 197 | 198 | do 199 | var x = 1, y = 2 200 | 201 | def foo a 202 | if a < 3 203 | return foo a + 1 204 | end 205 | def bar b 206 | if y < 3 207 | y += 1 208 | return foo y * 2 209 | end 210 | return a + b + y 211 | end 212 | return bar a + 3 213 | end 214 | 215 | say foo x # 18 216 | say y 217 | end 218 | 219 | do 220 | var a = {1, 2, 3} 221 | say &a 222 | a = 'hello' 223 | say &a 224 | end 225 | 226 | say 10^-1 227 | say -10^-1 228 | 229 | 1, 2, 3 | say 230 | 1, 2, 3 | say 4 231 | 232 | say {1, 'two\three''four', 5} 233 | -------------------------------------------------------------------------------- /tests/1.namespaces/namespaces.sink: -------------------------------------------------------------------------------- 1 | 2 | def add x 3 | say 'bad' 4 | end 5 | 6 | namespace foo 7 | def add a, b 8 | say "adding $a $b" 9 | return a + b 10 | end 11 | end 12 | 13 | say (foo.add 4, 5) 14 | 15 | def obj.test 16 | say 'inside obj.test' 17 | end 18 | 19 | namespace obj2 20 | using obj 21 | test 22 | end 23 | 24 | namespace obj3 25 | using obj 26 | end 27 | 28 | obj3.test 29 | 30 | # allow using before namespace exists 31 | using qux 32 | def qux.test 33 | say 'q' 34 | end 35 | test 36 | -------------------------------------------------------------------------------- /tests/1.namespaces/pass.txt: -------------------------------------------------------------------------------- 1 | adding 4 5 2 | 9 3 | inside obj.test 4 | inside obj.test 5 | q 6 | -------------------------------------------------------------------------------- /tests/10.lex-memleak/lex-memleak.sink: -------------------------------------------------------------------------------- 1 | 2 | def foo n 3 | say n 4 | def bar n2 5 | say n2 6 | if n2 == 4 7 | exit 8 | end 9 | foo n2 + 1 10 | end 11 | bar n + 1 12 | end 13 | 14 | foo 1 15 | -------------------------------------------------------------------------------- /tests/10.lex-memleak/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 2 3 | 3 4 | 4 5 | -------------------------------------------------------------------------------- /tests/11.version/pass.txt: -------------------------------------------------------------------------------- 1 | pass 2 | pass 3 | -------------------------------------------------------------------------------- /tests/11.version/version.sink: -------------------------------------------------------------------------------- 1 | 2 | include 'shell' 3 | 4 | if islist version 5 | say 'pass' 6 | else 7 | say 'fail' 8 | end 9 | 10 | namespace a.b.c.d 11 | include 'shell' 12 | end 13 | 14 | if islist a.b.c.d.version 15 | say 'pass' 16 | else 17 | say 'fail' 18 | end 19 | -------------------------------------------------------------------------------- /tests/12.setat/pass.txt: -------------------------------------------------------------------------------- 1 | {0} 1 2 | {0, 1} 2 3 | {0, 1, nil, nil, 4} 5 4 | {0, 1, nil, 3, 8} 5 5 | -------------------------------------------------------------------------------- /tests/12.setat/setat.sink: -------------------------------------------------------------------------------- 1 | 2 | var x = {} 3 | x[0] = 0 4 | say x, &x 5 | x[1] = 1 6 | say x, &x 7 | x[4] = 4 8 | say x, &x 9 | x[-2] = 3 10 | x[-1] = 8 11 | say x, &x 12 | -------------------------------------------------------------------------------- /tests/13.alias/alias.sink: -------------------------------------------------------------------------------- 1 | var x = {} 2 | def getx 3 | return x 4 | end 5 | 6 | list.push getx, 123 7 | say x 8 | say getx[0] 9 | getx[0] = 456 10 | say x 11 | -------------------------------------------------------------------------------- /tests/13.alias/pass.txt: -------------------------------------------------------------------------------- 1 | {123} 2 | 123 3 | {456} 4 | -------------------------------------------------------------------------------- /tests/14.loop/loop.sink: -------------------------------------------------------------------------------- 1 | 2 | var i = 0 3 | for 4 | say i 5 | i += 1 6 | if i > 4 7 | break 8 | end 9 | end 10 | say 'done' 11 | -------------------------------------------------------------------------------- /tests/14.loop/pass.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 2 4 | 3 5 | 4 6 | done 7 | -------------------------------------------------------------------------------- /tests/15.include/include.sink: -------------------------------------------------------------------------------- 1 | 2 | say 'start' 3 | 4 | include './test1.sink' 5 | 6 | test1 1 7 | 8 | include './test2' 9 | 10 | test2 2 11 | 12 | include './test3' 13 | 14 | test3 3 15 | 16 | include T1 './test1.sink' 17 | 18 | T1.test1 4 19 | 20 | include T2 './test2' 21 | 22 | T2.test2 5 23 | 24 | include T3 './test3' 25 | 26 | T3.test3 6 27 | 28 | say 'end' 29 | -------------------------------------------------------------------------------- /tests/15.include/pass.txt: -------------------------------------------------------------------------------- 1 | start 2 | inside test1.sink 3 | test1: 1 4 | inside test2/index.sink 5 | test2: 2 6 | inside test3.sink 7 | test3: 3 8 | inside test1.sink 9 | test1: 4 10 | inside test2/index.sink 11 | test2: 5 12 | inside test3.sink 13 | test3: 6 14 | end 15 | -------------------------------------------------------------------------------- /tests/15.include/test1.sink: -------------------------------------------------------------------------------- 1 | 2 | say 'inside test1.sink' 3 | 4 | def test1 a 5 | say "test1: $a" 6 | end 7 | -------------------------------------------------------------------------------- /tests/15.include/test2/index.sink: -------------------------------------------------------------------------------- 1 | 2 | say 'inside test2/index.sink' 3 | 4 | def test2 a 5 | say "test2: $a" 6 | end 7 | -------------------------------------------------------------------------------- /tests/15.include/test3.sink: -------------------------------------------------------------------------------- 1 | 2 | say 'inside test3.sink' 3 | 4 | def test3 a 5 | say "test3: $a" 6 | end 7 | -------------------------------------------------------------------------------- /tests/16.def-fail/def-fail.sink: -------------------------------------------------------------------------------- 1 | 2 | namespace baz 3 | var x 4 | def qux 5 | end 6 | end 7 | 8 | namespace bar 9 | def foo 10 | end 11 | 12 | def foo 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /tests/17.str-interp/pass.txt: -------------------------------------------------------------------------------- 1 | x: 5 2 | y: {5, 6, 7} 3 | y[1] + y[2]: 13 4 | {1}[0]: 1 5 | {"hi ${{1}[0]}"}[0]: hi 1 6 | -------------------------------------------------------------------------------- /tests/17.str-interp/str-interp.sink: -------------------------------------------------------------------------------- 1 | 2 | var x = 5, y = {5, 6, 7} 3 | say "x: $x" 4 | say "y: $y" 5 | say "y[1] + y[2]: ${y[1] + y[2]}" 6 | say "{1}[0]: ${{1}[0]}" 7 | say "{\"hi \${{1}[0]}\"}[0]: ${{"hi ${{1}[0]}"}[0]}" 8 | -------------------------------------------------------------------------------- /tests/18.open-eof-inc/open-eof-inc.sink: -------------------------------------------------------------------------------- 1 | 2 | include 'test.sink' 3 | 4 | test { 5 | -------------------------------------------------------------------------------- /tests/18.open-eof-inc/test.sink: -------------------------------------------------------------------------------- 1 | 2 | def test 3 | say 'inside test' 4 | end 5 | -------------------------------------------------------------------------------- /tests/19.early-return/early-return.sink: -------------------------------------------------------------------------------- 1 | say 1 2 | return 3 | say 2 4 | -------------------------------------------------------------------------------- /tests/19.early-return/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/2.say-as-cmd/pass.txt: -------------------------------------------------------------------------------- 1 | 2 | say returns "nil" 3 | -------------------------------------------------------------------------------- /tests/2.say-as-cmd/say-as-cmd.sink: -------------------------------------------------------------------------------- 1 | say 'say returns "' ~ say ~ '"' 2 | -------------------------------------------------------------------------------- /tests/20.vararg-return/pass.txt: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /tests/20.vararg-return/vararg-return.sink: -------------------------------------------------------------------------------- 1 | 2 | def test ...c 3 | return c[1] 4 | end 5 | 6 | say test 1, 2, 3 7 | -------------------------------------------------------------------------------- /tests/21.expr-keyend/expr-keyend.sink: -------------------------------------------------------------------------------- 1 | 2 | var i = 0 3 | do 4 | say i 5 | i += 1 6 | while i < 3 end 7 | 8 | def test msg 9 | say msg 10 | return {1, 2} 11 | end 12 | 13 | for var v: test 'hi' end 14 | 15 | if i < 1 end 16 | if i < 1 else end 17 | if i < 1 elseif i < 2 else end 18 | if i < 1 elseif i < 2 elseif i < 3 end 19 | -------------------------------------------------------------------------------- /tests/21.expr-keyend/pass.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 2 4 | hi 5 | -------------------------------------------------------------------------------- /tests/22.splicing/pass.txt: -------------------------------------------------------------------------------- 1 | aGHIJdef 2 | GH 3 | {1, 22, 33, 4, 5} 4 | {22, 33} 5 | {1, 'aGHIJdef', 2} 6 | GH 7 | {1, 2, 3} 8 | {4, 6, 8, 7, 8, 9} 9 | {6, 8} 10 | {1} 11 | {1} 12 | {1} 13 | {2} 14 | {1} 15 | {1} 16 | {2} 17 | {3} 18 | {1} 19 | {2, 6, 3, 4} 20 | {6, 3} 21 | {1, {2, 6, 3, 4}, 5} 22 | {6, 3} 23 | heo 24 | {'heo'} 25 | {1, {1, 7, 9, 4}, 2} 26 | {1, 'heLLo', 2} 27 | 5 28 | {'LL'} 29 | heLLo 30 | -------------------------------------------------------------------------------- /tests/22.splicing/splicing.sink: -------------------------------------------------------------------------------- 1 | 2 | var x, y, z 3 | 4 | x = 'abcdef' 5 | y = x[1:2] = 'GHIJ' 6 | say x # aGHIJdef 7 | say y # GH 8 | 9 | x = {1, 2, 3, 4, 5} 10 | y = x[1:2] += {20, 30, 40} 11 | say x # {1, 22, 33, 4, 5} 12 | say y # {22, 33} 13 | 14 | x = {1, 'abcdef', 2} 15 | y = x[1][1:2] = 'GHIJ' 16 | say x # {1, 'aGHIJdef', 2} 17 | say y # GH 18 | 19 | x = {1, 2, 3} 20 | y = {4, 5, 6, 7, 8, 9} 21 | z = y[1:2] += x 22 | say x # {1, 2, 3} 23 | say y # {4, 6, 8, 7, 8, 9} 24 | say z # {6, 8} 25 | 26 | x = {1} 27 | y = {2} 28 | z = y[0:1] = x 29 | say x # {1} 30 | say y # {1} 31 | say z # {1} 32 | x[0] = 2 33 | say x # {2} 34 | say y # {1} 35 | say z # {1} 36 | y[0] = 3 37 | say x # {2} 38 | say y # {3} 39 | say z # {1} 40 | 41 | x = {2, nil, 3, 4} 42 | y = x[1:2] ||= {6, 7} 43 | say x # {2, 6, 3, 4} 44 | say y # {6, 3} 45 | 46 | x = {1, {2, nil, 3, 4}, 5} 47 | y = x[1][1:2] ||= {6, 7} 48 | say x # {1, {2, 6, 3, 4}, 5} 49 | say y # {6, 3} 50 | 51 | x = 'hello' 52 | x[2:2] = nil 53 | say x 54 | x = {'hello'} 55 | x[0][2:2] = nil 56 | say x 57 | 58 | x = {1, {1, 2, 3, 4}, 2} 59 | x[1][1:2] += {5, 6, 7} 60 | say x # {1, {1, 7, 9, 4}, 2} 61 | 62 | x = {1, 'hello', 2} 63 | {x[1][2:2], y} = {'LL', 5} 64 | say x # {1, 'heLLo', 2} 65 | say y # 5 66 | 67 | x = 'hello' 68 | say ({x[2:2]} = {'LL'}) # {'LL'} 69 | say x # heLLo 70 | -------------------------------------------------------------------------------- /tests/23.str-escape/pass.txt: -------------------------------------------------------------------------------- 1 | how's it going 2 | -------------------------------------------------------------------------------- /tests/23.str-escape/str-escape.sink: -------------------------------------------------------------------------------- 1 | 2 | var x = 'how''s it going' 3 | say x 4 | 5 | -------------------------------------------------------------------------------- /tests/24.large-slices/large-slices.sink: -------------------------------------------------------------------------------- 1 | 2 | declare test 3 | 4 | # test for both lists and strings 5 | say 'lists' 6 | test {1, 2, 3, 4, 5} 7 | say 'strings' 8 | test '12345' 9 | 10 | def test x 11 | # indexing model: 12 | # 13 | # ---+---+---+---+---+---+---+---+---+---+---+--- 14 | # nil | 1 | 2 | 3 | 4 | 5 | 1 | 2 | 3 | 4 | 5 | nil 15 | # ---+---+---+---+---+---+---+---+---+---+---+--- 16 | # -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 17 | 18 | say x[-1] # 5 19 | say x[-5] # 1 20 | say x[-6] # nil 21 | say x[4] # 5 22 | say x[5] # nil 23 | 24 | # slicing model: 25 | # 26 | # ---+---+---+---+---+---+ +---+---+---+---+---+--- 27 | # nil | 1 | 2 | 3 | 4 | 5 | | 1 | 2 | 3 | 4 | 5 | nil 28 | # ---+---+---+---+---+---+ +---+---+---+---+---+--- 29 | # -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 30 | 31 | say x[:] # 12345 32 | say x[0:] # 12345 33 | say x[-1:] # 5 34 | say x[-5:] # 12345 35 | say x[-6:] # 12345 36 | say x[4:] # 5 37 | say x[5:] # empty 38 | say x[6:] # empty 39 | 40 | say x[:1] # 1 41 | say x[:5] # 12345 42 | say x[:6] # 12345 43 | say x[:-1] # 5 44 | say x[:-5] # 12345 45 | say x[:-6] # 12345 46 | 47 | say x[-1:-1] # 4 48 | say x[-1:0] # empty 49 | say x[-1:1] # 5 50 | say x[-1:6] # 5 51 | say x[-2:-1] # 3 52 | say x[-2:0] # empty 53 | say x[-2:1] # 4 54 | say x[-2:6] # 45 55 | say x[-4:-1] # 1 56 | say x[-4:-3] # 1 57 | say x[:-100] # 12345 58 | 59 | say x[-6:2] # 1 60 | say x[-6:-1] # empty 61 | say x[-7:7] # 12345 62 | say x[-7:10] # 12345 63 | say x[-7:5] # 123 64 | 65 | say x[0:100] # 12345 66 | say x[0:5] # 12345 67 | say x[0:4] # 1234 68 | say x[0:6] # 12345 69 | say x[3:2] # 45 70 | say x[3:1] # 4 71 | say x[3:3] # 45 72 | say x[1:-10] # 1 73 | say x[1:-1] # 1 74 | say x[3:-2] # 23 75 | say x[3:-3] # 123 76 | say x[3:-4] # 123 77 | say x[5:-1] # 5 78 | say x[5:-2] # 45 79 | say x[6:-1] # empty 80 | say x[6:-2] # 5 81 | say x[6:-3] # 45 82 | say x[9:-9] # 12345 83 | say x[9:-4] # empty 84 | say x[9:-5] # 5 85 | end 86 | -------------------------------------------------------------------------------- /tests/24.large-slices/pass.txt: -------------------------------------------------------------------------------- 1 | lists 2 | 5 3 | 1 4 | nil 5 | 5 6 | nil 7 | {1, 2, 3, 4, 5} 8 | {1, 2, 3, 4, 5} 9 | {5} 10 | {1, 2, 3, 4, 5} 11 | {1, 2, 3, 4, 5} 12 | {5} 13 | {} 14 | {} 15 | {1} 16 | {1, 2, 3, 4, 5} 17 | {1, 2, 3, 4, 5} 18 | {5} 19 | {1, 2, 3, 4, 5} 20 | {1, 2, 3, 4, 5} 21 | {4} 22 | {} 23 | {5} 24 | {5} 25 | {3} 26 | {} 27 | {4} 28 | {4, 5} 29 | {1} 30 | {1} 31 | {1, 2, 3, 4, 5} 32 | {1} 33 | {} 34 | {1, 2, 3, 4, 5} 35 | {1, 2, 3, 4, 5} 36 | {1, 2, 3} 37 | {1, 2, 3, 4, 5} 38 | {1, 2, 3, 4, 5} 39 | {1, 2, 3, 4} 40 | {1, 2, 3, 4, 5} 41 | {4, 5} 42 | {4} 43 | {4, 5} 44 | {1} 45 | {1} 46 | {2, 3} 47 | {1, 2, 3} 48 | {1, 2, 3} 49 | {5} 50 | {4, 5} 51 | {} 52 | {5} 53 | {4, 5} 54 | {1, 2, 3, 4, 5} 55 | {} 56 | {5} 57 | strings 58 | 5 59 | 1 60 | nil 61 | 5 62 | nil 63 | 12345 64 | 12345 65 | 5 66 | 12345 67 | 12345 68 | 5 69 | 70 | 71 | 1 72 | 12345 73 | 12345 74 | 5 75 | 12345 76 | 12345 77 | 4 78 | 79 | 5 80 | 5 81 | 3 82 | 83 | 4 84 | 45 85 | 1 86 | 1 87 | 12345 88 | 1 89 | 90 | 12345 91 | 12345 92 | 123 93 | 12345 94 | 12345 95 | 1234 96 | 12345 97 | 45 98 | 4 99 | 45 100 | 1 101 | 1 102 | 23 103 | 123 104 | 123 105 | 5 106 | 45 107 | 108 | 5 109 | 45 110 | 12345 111 | 112 | 5 113 | -------------------------------------------------------------------------------- /tests/25.assign-self/assign-self.sink: -------------------------------------------------------------------------------- 1 | 2 | var x = 'hello' 3 | x = x[0:2] ~ 'LL' ~ x[4:] 4 | say x # heLLo 5 | 6 | x = x ~ 1 ~ 2 && x[:] 7 | say x # heLLo 8 | -------------------------------------------------------------------------------- /tests/25.assign-self/pass.txt: -------------------------------------------------------------------------------- 1 | heLLo 2 | heLLo 3 | -------------------------------------------------------------------------------- /tests/26.tail-call/pass.txt: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /tests/26.tail-call/tail-call.sink: -------------------------------------------------------------------------------- 1 | 2 | def test a 3 | if a > 100 4 | return stacktrace 5 | end 6 | return test a + 1 7 | end 8 | 9 | say &test 0 10 | -------------------------------------------------------------------------------- /tests/27.range/pass.txt: -------------------------------------------------------------------------------- 1 | 0 0 2 | 1 1 3 | 2 2 4 | 2 0 5 | 3 1 6 | 4 2 7 | 4 0 8 | 6 1 9 | 8 2 10 | -1.25 0 11 | -1.75 1 12 | -2.25 2 13 | {0, 1, 2, 3, 4} 14 | {} 15 | {0, 1, 2, 3} 16 | {2, 3, 4} 17 | {-1, 0, 1, 2, 3, 4} 18 | {1.5, 2.5} 19 | {0, 3, 6, 9} 20 | {-1, -1.25, -1.5, -1.75} 21 | {0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2} 22 | -------------------------------------------------------------------------------- /tests/27.range/range.sink: -------------------------------------------------------------------------------- 1 | 2 | for var v, i: range 3 3 | say v, i 4 | # 0 0 5 | # 1 1 6 | # 2 2 7 | end 8 | 9 | for var v, i: range 2, 5 10 | say v, i 11 | # 2 0 12 | # 3 1 13 | # 4 2 14 | end 15 | 16 | for var v, i: range 4, 10, 2 17 | say v, i 18 | # 4 0 19 | # 6 1 20 | # 8 2 21 | end 22 | 23 | for var v, i: range -1.25, -2.75, -0.5 24 | say v, i 25 | # -1.25 0 26 | # -1.75 1 27 | # -2.25 2 28 | end 29 | 30 | say range 5 # {0, 1, 2, 3, 4} 31 | say range -1 # {} 32 | say range 3.4 # {0, 1, 2, 3} 33 | 34 | say range 2, 5 # {2, 3, 4} 35 | say range -1, 5 # {-1, 0, 1, 2, 3, 4} 36 | say range 1.5, 3 # {1.5, 2.5} 37 | 38 | say range 0, 10, 3 # {0, 3, 6, 9} 39 | say range -1, -2, -0.25 # {-1, -1.25, -1.5, -1.75} 40 | say range 0, 2.25, 0.25 # {0, 0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2} 41 | -------------------------------------------------------------------------------- /tests/28.str-split/pass.txt: -------------------------------------------------------------------------------- 1 | {'crY', '', '', '', 'crY'} 2 | {'R8fx', 'R8fxDz8nR8fxDz8nR8fxDz8n'} 3 | {'', 'bsgp', '', 'bsgp'} 4 | {'', 'dd', '', 'd', '', '', ''} 5 | {'YOcb7'} 6 | {'', '', 'xPxP', '', '', ''} 7 | {'qf7cqf7c', '', 'qf7c'} 8 | {'meI1bcmeI', '', '', '', ''} 9 | {'a', 'a'} 10 | {'k', 'k', 'k', '', '', 'k'} 11 | {'PLf', ''} 12 | {'jHM', 'aEaEjHM', '', 'aE'} 13 | {'', 'aRT'} 14 | {'9dw', '9dw9dw', '', ''} 15 | {'RC5C5', ''} 16 | {'exBexBBa'} 17 | {'7M7M7M7M7M7M7M'} 18 | {'UiUi', 'UiUi', 'Ui', '', ''} 19 | {'', 'e', '', 'eee'} 20 | {'MYMYMY', 'MYMY'} 21 | {'', '', ''} 22 | {'1', '2', '3', '4'} 23 | -------------------------------------------------------------------------------- /tests/28.str-split/str-split.sink: -------------------------------------------------------------------------------- 1 | 2 | say str.split 'crYy2oy2oy2oy2ocrY', 'y2o' 3 | say str.split 'R8fxfPR8fxDz8nR8fxDz8nR8fxDz8n', 'fP' 4 | say str.split 'wmlTbsgpwmlTwmlTbsgp', 'wmlT' 5 | say str.split '7dd77d777', '7' 6 | say str.split 'YOcb7', 'Fswk' 7 | say str.split 'oGoGxPxPoGoGoG', 'oG' 8 | say str.split 'qf7cqf7crrqf7c', 'r' 9 | say str.split 'meI1bcmeI8AC8AC8AC8AC', '8AC' 10 | say str.split 'aT9mxa', 'T9mx' 11 | say str.split 'ktVmYktVmYktVmYtVmYtVmYk', 'tVmY' 12 | say str.split 'PLfP1', 'P1' 13 | say str.split 'jHMVddIaEaEjHMVddIVddIaE', 'VddI' 14 | say str.split 'CeYaRT', 'CeY' 15 | say str.split '9dwp9dw9dwpp', 'p' 16 | say str.split 'RC5C5jkth', 'jkth' 17 | say str.split 'exBexBBa', 'Z' 18 | say str.split '7M7M7M7M7M7M7M', 'k299' 19 | say str.split 'UiUi2NUiUi2NUi2N2N', '2N' 20 | say str.split '69VWe69VW69VWeee', '69VW' 21 | say str.split 'MYMYMYGDS1MYMY', 'GDS1' 22 | say str.split 'AAAA', 'AA' 23 | say str.split '1234', '' 24 | -------------------------------------------------------------------------------- /tests/29.no-newline/no-newline.sink: -------------------------------------------------------------------------------- 1 | say 'hi' -------------------------------------------------------------------------------- /tests/29.no-newline/pass.txt: -------------------------------------------------------------------------------- 1 | hi 2 | -------------------------------------------------------------------------------- /tests/3.math/math.sink: -------------------------------------------------------------------------------- 1 | def r1000 a 2 | return num.round a * 1000 3 | end 4 | 5 | say num.floor 1.5 6 | say num.floor { -1.5, -2.5 } 7 | say (r1000 num.sin (num.tau) * 0.25), (r1000 num.cos (num.tau) * 0.25) 8 | say r1000 (num.atan2 1, 1) / (num.tau) 9 | say r1000 num.lerp {1, 2, 3}, {2, 3, 4}, {0.25, 0.5, 0.75} 10 | say num.max {6, 2, 4, 8, 1, 3} 11 | say num.min {6, 2, 4, 8, 1, 3} 12 | -------------------------------------------------------------------------------- /tests/3.math/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 | {-2, -3} 3 | 1000 0 4 | 125 5 | {1250, 2500, 3750} 6 | 8 7 | 1 8 | -------------------------------------------------------------------------------- /tests/30.list-sort/list-sort.sink: -------------------------------------------------------------------------------- 1 | say order nil, 0 # -1 2 | say order nil, '' # -1 3 | say order nil, {} # -1 4 | 5 | say order 0, nil # 1 6 | say order 0, '' # -1 7 | say order 0, {} # -1 8 | 9 | say order '', nil # 1 10 | say order '', 0 # 1 11 | say order '', {} # -1 12 | 13 | say order {}, nil # 1 14 | say order {}, 0 # 1 15 | say order {}, '' # 1 16 | 17 | say order nil, nil # 0 18 | 19 | say order 0, 0 # 0 20 | say order 5, 0 # 1 21 | say order 0, 5 # -1 22 | 23 | say order '', '' # 0 24 | say order '', 'a' # -1 25 | say order 'a', '' # 1 26 | 27 | say order 'abc', 'abc' # 0 28 | say order 'abcd', 'abc' # 1 29 | say order 'abc', 'abcd' # -1 30 | say order 'ab', 'ac' # -1 31 | say order 'ac', 'ab' # 1 32 | 33 | var x = {1, 2, 3, 4} 34 | list.push x, x 35 | say order x, x # 0 36 | 37 | say order {}, {} # 0 38 | say order {}, {1} # -1 39 | say order {1}, {} # 1 40 | 41 | say order {1, 5}, {1, nil} # 1 42 | say order {1, nil}, {1, 5} # -1 43 | 44 | say order {1}, {1, 5} # -1 45 | say order {1, 5}, {1} # 1 46 | say order {1}, {1} # 0 47 | 48 | say order num.nan, num.nan # 0 49 | say order num.nan, 0 # -1 50 | say order 0, num.nan # 1 51 | 52 | say list.sort {'a', 0, num.nan, num.inf} 53 | say list.rsort {'a', 0, num.nan, num.inf} 54 | 55 | x = {1, 2, 3} 56 | list.rsort x 57 | say x 58 | 59 | rand.seed 1234 60 | for: range 1000 61 | var ls = {} 62 | for: range 500 + rand.int % 100 63 | list.push ls, rand.int % 1000 64 | end 65 | list.sort ls 66 | for var i: range 1, &ls 67 | if ls[i - 1] > ls[i] 68 | say 'fail' 69 | say ls 70 | end 71 | end 72 | end 73 | say 'done' 74 | -------------------------------------------------------------------------------- /tests/30.list-sort/pass.txt: -------------------------------------------------------------------------------- 1 | -1 2 | -1 3 | -1 4 | 1 5 | -1 6 | -1 7 | 1 8 | 1 9 | -1 10 | 1 11 | 1 12 | 1 13 | 0 14 | 0 15 | 1 16 | -1 17 | 0 18 | -1 19 | 1 20 | 0 21 | 1 22 | -1 23 | -1 24 | 1 25 | 0 26 | 0 27 | -1 28 | 1 29 | 1 30 | -1 31 | -1 32 | 1 33 | 0 34 | 0 35 | -1 36 | 1 37 | {nan, 0, inf, 'a'} 38 | {'a', inf, 0, nan} 39 | {3, 2, 1} 40 | done 41 | -------------------------------------------------------------------------------- /tests/31.range-bug/pass.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 1 3 | 2 4 | 3 5 | 4 6 | 5 7 | 6 8 | 7 9 | 8 10 | 9 11 | 0 12 | 2 13 | 4 14 | 6 15 | 8 16 | -------------------------------------------------------------------------------- /tests/31.range-bug/range-bug.sink: -------------------------------------------------------------------------------- 1 | 2 | var y = 10 3 | for var x: range y 4 | say x 5 | y -= 1 6 | end 7 | 8 | y = 10 9 | for var x: range 0, y, 2 10 | say x 11 | y -= 4 12 | end 13 | -------------------------------------------------------------------------------- /tests/32.num-ints/num-ints.sink: -------------------------------------------------------------------------------- 1 | 2 | say 0 3 | say -1 4 | say 1 5 | 6 | say -255 7 | say -256 8 | say -257 9 | 10 | say 255 11 | say 256 12 | say 257 13 | 14 | say -65535 15 | say -65536 16 | say -65537 17 | 18 | say 65535 19 | say 65536 20 | say 65537 21 | 22 | say -4294967295 23 | say -4294967296 24 | say -4294967297 25 | 26 | say 4294967295 27 | say 4294967296 28 | say 4294967297 29 | -------------------------------------------------------------------------------- /tests/32.num-ints/pass.txt: -------------------------------------------------------------------------------- 1 | 0 2 | -1 3 | 1 4 | -255 5 | -256 6 | -257 7 | 255 8 | 256 9 | 257 10 | -65535 11 | -65536 12 | -65537 13 | 65535 14 | 65536 15 | 65537 16 | -4294967295 17 | -4294967296 18 | -4294967297 19 | 4294967295 20 | 4294967296 21 | 4294967297 22 | -------------------------------------------------------------------------------- /tests/33.str-replace/pass.txt: -------------------------------------------------------------------------------- 1 | heAAo worAd 2 | asdf 3 | BBB 4 | -------------------------------------------------------------------------------- /tests/33.str-replace/str-replace.sink: -------------------------------------------------------------------------------- 1 | 2 | say str.replace 'hello world', 'l', 'A' 3 | say str.replace 'asdf', 'A', 'B' 4 | say str.replace 'AAAAAA', 'AA', 'B' 5 | -------------------------------------------------------------------------------- /tests/34.str-beginsends/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 | nil 3 | nil 4 | 1 5 | 1 6 | 1 7 | nil 8 | nil 9 | 1 10 | 1 11 | -------------------------------------------------------------------------------- /tests/34.str-beginsends/str-beginsends.sink: -------------------------------------------------------------------------------- 1 | 2 | say str.begins 'hello world', 'hello' 3 | say str.begins 'hello world', 'hellO' 4 | say str.begins 'a', 'abcd' 5 | say str.begins 'aaa', 'aaa' 6 | say str.begins 'asdf', '' 7 | 8 | say str.ends 'hello world', 'world' 9 | say str.ends 'hello world', 'worlD' 10 | say str.ends 'a', 'abcd' 11 | say str.ends 'aaa', 'aaa' 12 | say str.ends 'asdf', '' 13 | -------------------------------------------------------------------------------- /tests/35.str-pad/pass.txt: -------------------------------------------------------------------------------- 1 | ' hello' 2 | 'hello' 3 | 'hello' 4 | 'hello' 5 | 'hello' 6 | 'hello' 7 | 'hello ' 8 | ' X' 9 | 'X ' 10 | -------------------------------------------------------------------------------- /tests/35.str-pad/str-pad.sink: -------------------------------------------------------------------------------- 1 | 2 | say "'${str.pad 'hello', -7}'" 3 | say "'${str.pad 'hello', -5}'" 4 | say "'${str.pad 'hello', -1}'" 5 | say "'${str.pad 'hello', 0}'" 6 | say "'${str.pad 'hello', 1}'" 7 | say "'${str.pad 'hello', 5}'" 8 | say "'${str.pad 'hello', 7}'" 9 | 10 | say "'${str.pad 'X', -2.5}'" 11 | say "'${str.pad 'X', 2.5}'" 12 | -------------------------------------------------------------------------------- /tests/36.str-find/pass.txt: -------------------------------------------------------------------------------- 1 | find 2 | nil 3 | 0 4 | 3 5 | 0 6 | 1 7 | 1 8 | 5 9 | 5 10 | nil 11 | nil 12 | 5 13 | 5 14 | 1 15 | 1 16 | 1 17 | rfind 18 | nil 19 | 0 20 | 3 21 | 4 22 | 5 23 | 1 24 | 1 25 | 5 26 | 5 27 | 5 28 | 5 29 | 1 30 | 1 31 | nil 32 | nil 33 | -------------------------------------------------------------------------------- /tests/36.str-find/str-find.sink: -------------------------------------------------------------------------------- 1 | say 'find' 2 | 3 | say str.find 'asdf', 'A' 4 | say str.find 'asdf', 'a' 5 | say str.find 'asdf', 'f' 6 | say str.find 'asdf', '' 7 | 8 | say str.find 'abcdabcd', 'bc' 9 | say str.find 'abcdabcd', 'bc', 1 10 | say str.find 'abcdabcd', 'bc', 2 11 | say str.find 'abcdabcd', 'bc', 5 12 | say str.find 'abcdabcd', 'bc', 6 13 | 14 | say str.find 'abcdabcd', 'bc', -2 15 | say str.find 'abcdabcd', 'bc', -3 16 | say str.find 'abcdabcd', 'bc', -6 17 | say str.find 'abcdabcd', 'bc', -7 18 | say str.find 'abcdabcd', 'bc', -8 19 | say str.find 'abcdabcd', 'bc', -999 20 | 21 | say 'rfind' 22 | 23 | say str.rfind 'asdf', 'A' 24 | say str.rfind 'asdf', 'a' 25 | say str.rfind 'asdf', 'f' 26 | say str.rfind 'asdf', '' 27 | 28 | say str.rfind 'abcdabcd', 'bc' 29 | say str.rfind 'abcdabcd', 'bc', 1 30 | say str.rfind 'abcdabcd', 'bc', 2 31 | say str.rfind 'abcdabcd', 'bc', 5 32 | say str.rfind 'abcdabcd', 'bc', 6 33 | 34 | say str.rfind 'abcdabcd', 'bc', -2 35 | say str.rfind 'abcdabcd', 'bc', -3 36 | say str.rfind 'abcdabcd', 'bc', -6 37 | say str.rfind 'abcdabcd', 'bc', -7 38 | say str.rfind 'abcdabcd', 'bc', -8 39 | say str.rfind 'abcdabcd', 'bc', -999 40 | -------------------------------------------------------------------------------- /tests/37.str-case/pass.txt: -------------------------------------------------------------------------------- 1 | {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 2 | {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} 3 | {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47} 4 | {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63} 5 | {64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79} 6 | {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95} 7 | {96, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79} 8 | {80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 123, 124, 125, 126, 127} 9 | {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143} 10 | {144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159} 11 | {160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175} 12 | {176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191} 13 | {192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207} 14 | {208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223} 15 | {224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239} 16 | {240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255} 17 | {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15} 18 | {16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31} 19 | {32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47} 20 | {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63} 21 | {64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111} 22 | {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 91, 92, 93, 94, 95} 23 | {96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111} 24 | {112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127} 25 | {128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143} 26 | {144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159} 27 | {160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175} 28 | {176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191} 29 | {192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207} 30 | {208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223} 31 | {224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239} 32 | {240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255} 33 | -------------------------------------------------------------------------------- /tests/37.str-case/str-case.sink: -------------------------------------------------------------------------------- 1 | 2 | for var x: range 0, 256, 16 3 | say (range x, x + 16 | list.str | str.upper | str.list) 4 | end 5 | 6 | for var x: range 0, 256, 16 7 | say (range x, x + 16 | list.str | str.lower | str.list) 8 | end 9 | -------------------------------------------------------------------------------- /tests/38.str-trimrevrep/pass.txt: -------------------------------------------------------------------------------- 1 | 9 1 2 | 10 1 3 | 11 1 4 | 12 1 5 | 13 1 6 | 32 1 7 | cba 8 | a 9 | 10 | 11 | abc 12 | abcabcabc 13 | 14 | "" 15 | -------------------------------------------------------------------------------- /tests/38.str-trimrevrep/str-trimrevrep.sink: -------------------------------------------------------------------------------- 1 | 2 | for var x: range 0, 256 3 | var s = list.str {x, x, x, 65, x} 4 | if (str.trim s) != s 5 | say x, &str.trim s 6 | end 7 | end 8 | 9 | say str.rev 'abc' 10 | say str.rev 'a' 11 | say str.rev '' 12 | 13 | say str.rep 'abc', 0 14 | say str.rep 'abc', 1 15 | say str.rep 'abc', 3 16 | say str.rep '', 99 17 | 18 | say '"' ~ (str.trim ' ') ~ '"' 19 | -------------------------------------------------------------------------------- /tests/39.str-hash/pass.txt: -------------------------------------------------------------------------------- 1 | 0 0 2 | 5 4185040151 3 | 10 14207971054 4 | 15 11122314274 5 | 20 10949885412 6 | 25 9579205148 7 | 30 9283294372 8 | 35 9054236106 9 | 40 10528512579 10 | 45 8613392511 11 | 50 13915211121 12 | 55 9723034611 13 | 60 7558516780 14 | 65 10551955782 15 | 70 11397275566 16 | 75 5995058400 17 | 80 4790697866 18 | 85 8199235121 19 | 90 11513033061 20 | 95 6812116845 21 | {296480703, 512771809, 1907580628, 1377802961} 22 | {3421180703, 626138256, 2950103748, 755914226} 23 | {296480703, 512771809, 1907580628, 1377802961} 24 | {3421180703, 626138256, 2950103748, 755914226} 25 | -------------------------------------------------------------------------------- /tests/39.str-hash/str-hash.sink: -------------------------------------------------------------------------------- 1 | 2 | for var i: range 0, 100, 5 3 | var h = str.hash (str.rep 'x', i), i * 2 4 | say i, h[0] + h[1] + h[2] + h[3] 5 | end 6 | 7 | # test static hashes 8 | say str.hash "test\0one", 123 9 | say str.hash "test\0two" 10 | 11 | # compared to runtime hashing 12 | var s1 = "test\0one" 13 | var n1 = 123 14 | var s2 = "test\0two" 15 | say str.hash s1, n1 16 | say str.hash s2 17 | -------------------------------------------------------------------------------- /tests/4.list-ops/list-ops.sink: -------------------------------------------------------------------------------- 1 | 2 | var a = {1, 2, 3, 4, 5} 3 | 4 | list.push a, 6 5 | 6 | var b = a 7 | 8 | say "~=", (a ~= {7}) 9 | 10 | say "b: $b" 11 | 12 | var c = b 13 | 14 | list.append c, {8, 9} 15 | 16 | list.prepend c, {-1, 0} 17 | 18 | say "b: $b" 19 | 20 | say "push", list.push {1, 2, 3}, 4 21 | 22 | say list.new 5, 300 23 | 24 | say list.find b, 5 25 | say list.find b, 100 26 | list.rev b 27 | say "b: $b" 28 | 29 | say list.join b, '_' 30 | say list.join {'a', 'b'} 31 | -------------------------------------------------------------------------------- /tests/4.list-ops/pass.txt: -------------------------------------------------------------------------------- 1 | ~= {1, 2, 3, 4, 5, 6, 7} 2 | b: {1, 2, 3, 4, 5, 6} 3 | b: {-1, 0, 1, 2, 3, 4, 5, 6, 8, 9} 4 | push {1, 2, 3, 4} 5 | {300, 300, 300, 300, 300} 6 | 6 7 | nil 8 | b: {9, 8, 6, 5, 4, 3, 2, 1, 0, -1} 9 | 9_8_6_5_4_3_2_1_0_-1 10 | ab 11 | -------------------------------------------------------------------------------- /tests/40.utf8/pass.txt: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /tests/40.utf8/utf8.sink: -------------------------------------------------------------------------------- 1 | 2 | for var test1: { 3 | # list of codepoints are codepoints valid 4 | {{0, 0, 0 }, 1 }, 5 | {{0x7F, 0x7F, 0x7F }, 1 }, 6 | {{0x80, 0x80, 0x80 }, 1 }, 7 | {{0x7FF, 0x7FF, 0x7FF }, 1 }, 8 | {{0x800, 0x800, 0x800 }, 1 }, 9 | {{0x0FFFF, 0x0FFFF, 0x0FFFF}, 1 }, 10 | {{0x10000, 0x10000, 0x10000}, 1 }, 11 | {{0, 0x7F, 0x80, 0x7FF }, 1 }, 12 | {{0x7FF, 0x800, 0x0FFFF }, 1 }, 13 | {{0x0FFFF, 0x10000, 0 }, 1 }, 14 | {{0x10FFFF, 0x10FFFE, 0, 1 }, 1 }, 15 | {{0xD7FF, 0xE000 }, 1 }, 16 | {{0xFFFD, 0x10FFFF }, 1 }, 17 | {{0x110000 }, nil }, 18 | {{0xD800 }, nil }, 19 | {{0xDE00 }, nil }, 20 | {{0xDFFF }, nil } 21 | } 22 | var ls = test1[0] 23 | var valid = test1[1] 24 | if valid != utf8.valid ls 25 | say 'utf8.valid fail' 26 | end 27 | if valid 28 | var ls2 = ls | utf8.str | utf8.list 29 | if 0 != order ls, ls2 30 | say 'utf8.str/utf8.list fail' 31 | end 32 | end 33 | end 34 | 35 | for var test2: { 36 | # bytes are bytes valid UTF8 37 | {"\x00\x00\x00\x00\x00\x00" , 1 }, 38 | {"\x7F\x7F\x7F\x7F\x7F\x7F" , 1 }, 39 | {"\xC2\x80\xC2\x80" , 1 }, 40 | {"\xDF\xBF\xDF\xBF" , 1 }, 41 | {"\xE0\xA0\x80" , 1 }, 42 | {"\xEF\xBF\xBF" , 1 }, 43 | {"\xF0\x90\x80\x80" , 1 }, 44 | {"\xF4\x8F\xBF\xBF" , 1 }, 45 | {"\xF4\x9F\xBF\xBF" , nil }, 46 | {"\xED\xA0\x80" , nil }, 47 | {"\x80" , nil }, 48 | {"\xC2" , nil } 49 | } 50 | var st = test2[0] 51 | var valid = test2[1] 52 | if valid != utf8.valid st 53 | say 'utf8.valid fail' 54 | end 55 | if valid 56 | var st2 = st | utf8.list | utf8.str 57 | if st != st2 58 | say 'utf8.str/utf8.list fail' 59 | end 60 | end 61 | end 62 | 63 | say 'pass' 64 | -------------------------------------------------------------------------------- /tests/41.struct/pass.txt: -------------------------------------------------------------------------------- 1 | AB 2 | {1111572801} 3 | {1094795586} 4 | 7 5 | nil 6 | {nan} 7 | {0, 0, 0, 0, 0, 0, 248, 63} 8 | {63, 248, 0, 0, 0, 0, 0, 0} 9 | {1111572801, 1094795586, 1111572801, 1094795586} 10 | {1, 0, 2, 0, 0, 0, 3, 0, 4, 0, 0, 0, 5, 0, 6, 0, 0, 0, 7, 0, 8, 0, 0, 0} 11 | nil 12 | nil 13 | nil 14 | nil 15 | pass 16 | -------------------------------------------------------------------------------- /tests/41.struct/struct.sink: -------------------------------------------------------------------------------- 1 | 2 | say struct.str {0x41, 0x42}, {struct.U8, struct.U8} 3 | say struct.list 'AAAB', {struct.UL32} 4 | say struct.list 'AAAB', {struct.UB32} 5 | say struct.size {struct.F32, struct.U8, struct.S16} 6 | say struct.size {'hello'} 7 | 8 | var s = list.str { 0x7F, 0xF0, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01 } 9 | say struct.list s, {struct.FB64} 10 | 11 | say (struct.str {1.5}, {struct.FL64} | str.list) 12 | say (struct.str {1.5}, {struct.FB64} | str.list) 13 | 14 | # test arrays 15 | 16 | say struct.list 'AAABAAABAAABAAAB', {struct.UL32, struct.UB32} 17 | say (struct.str {1, 2, 3, 4, 5, 6, 7, 8}, {struct.UL16, struct.UL32} | str.list) 18 | 19 | say struct.size 20 | say struct.size 'asdf' 21 | say struct.size struct.U8 22 | say struct.size {} 23 | 24 | if struct.isLE 25 | # pass no matter what, just want to make sure the command exists 26 | say 'pass' 27 | else 28 | say 'pass' 29 | end 30 | -------------------------------------------------------------------------------- /tests/42.circular/circular.sink: -------------------------------------------------------------------------------- 1 | 2 | var x = {1, 2, 3} 3 | say {x, x} 4 | -------------------------------------------------------------------------------- /tests/42.circular/pass.txt: -------------------------------------------------------------------------------- 1 | {{1, 2, 3}, {1, 2, 3}} 2 | -------------------------------------------------------------------------------- /tests/43.include-err/include-err.sink: -------------------------------------------------------------------------------- 1 | 2 | var x 3 | include x 'test' 4 | -------------------------------------------------------------------------------- /tests/43.include-err/test.sink: -------------------------------------------------------------------------------- 1 | 2 | say 'test' 3 | -------------------------------------------------------------------------------- /tests/44.tonum/pass.txt: -------------------------------------------------------------------------------- 1 | nil 2 | nil 3 | 0 4 | 0 5 | 1 6 | nil 7 | nil 8 | -1 9 | nil 10 | nil 11 | 0 12 | 0 13 | 0 14 | 1 15 | -1 16 | 0 17 | 0.5 18 | -0.5 19 | 0 20 | 0 21 | 1 22 | -1 23 | 1 24 | -1 25 | 1 26 | -1 27 | 1 28 | 0 29 | 1 30 | 1 31 | 1 32 | 20 33 | 1234567890 34 | -9876.54321 35 | 1.23456789e-13 36 | 1.23456789e+34 37 | 2.55443322e+24 38 | 0.5 39 | 98.25 40 | 99.25 41 | 1066 42 | 10 43 | 1 44 | 0.1 45 | 1 46 | 2 47 | 2 48 | -0.5 49 | -------------------------------------------------------------------------------- /tests/44.tonum/tonum.sink: -------------------------------------------------------------------------------- 1 | 2 | say +' ' 3 | say +' -' 4 | say +' 0' 5 | say +' -0' 6 | say +' 1' 7 | say +' .' 8 | say +' -.' 9 | say +' -1' 10 | say +' - 1' 11 | say +' a' 12 | say +' 0b' 13 | say +' -0b' 14 | say +' 0y' 15 | say +' 0_1' 16 | say +' -0_1' 17 | say +' -0_' 18 | say +' 0.5' 19 | say +' -0.5' 20 | say +' 0e5' 21 | say +' -0e5' 22 | say +' 001' 23 | say +' -001' 24 | say +' 0b_1' 25 | say +' -0b_1' 26 | say +' 0b12' 27 | say +' -0b12' 28 | say +' 0b__1' 29 | say +' 0e' 30 | say +' 1e' 31 | say +' 1e+' 32 | say +' 1e+-1' 33 | say +' 0x14' 34 | say +' 1234567890 ' 35 | say +' -9876.543210 ' 36 | say +' 0.123456789e-12 ' 37 | say +' 1.234567890E+34 ' 38 | say +' 255443322E16 ' 39 | say +' 0.5 ' 40 | say +' 98.25 ' 41 | say +' 99.25 ' 42 | say +' 1066 ' 43 | say +' 1e1 ' 44 | say +' 0.1e1 ' 45 | say +' 1e-1 ' 46 | say +' 1e00 ' 47 | say +' 2e+00 ' 48 | say +' 2e-00 ' 49 | say +'-.5' 50 | -------------------------------------------------------------------------------- /tests/45.include-def/include-def.sink: -------------------------------------------------------------------------------- 1 | 2 | def foo a, b 3 | include 'test.sink' 4 | end 5 | 6 | foo 1, 2 7 | -------------------------------------------------------------------------------- /tests/45.include-def/pass.txt: -------------------------------------------------------------------------------- 1 | 1 + 2 = 3 2 | -------------------------------------------------------------------------------- /tests/45.include-def/test.sink: -------------------------------------------------------------------------------- 1 | 2 | say "$a + $b = ${a + b}" 3 | -------------------------------------------------------------------------------- /tests/46.num-hex/num-hex.sink: -------------------------------------------------------------------------------- 1 | say num.hex 1 2 | say num.hex 255 3 | say num.hex 256 4 | say num.hex {1, 2, 3, 254, 255, 256} 5 | say num.bin 15 6 | say num.oct 15 7 | -------------------------------------------------------------------------------- /tests/46.num-hex/pass.txt: -------------------------------------------------------------------------------- 1 | 0x1 2 | 0xFF 3 | 0x100 4 | {'0x1', '0x2', '0x3', '0xFE', '0xFF', '0x100'} 5 | 0b1111 6 | 0c17 7 | -------------------------------------------------------------------------------- /tests/47.valid-json/pass.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velipso/sink/b7d86bf4f0e4246bdf90fc0f39bca9a9d61486f2/tests/47.valid-json/pass.txt -------------------------------------------------------------------------------- /tests/47.valid-json/valid-json.sink: -------------------------------------------------------------------------------- 1 | def N a 2 | if (pickle.valid a) != nil 3 | say "fail: expecting invalid\n> \"$a\"" 4 | end 5 | end 6 | 7 | def Y a 8 | if (pickle.valid a) != 1 9 | say "fail: expecting json\n> \"$a\"" 10 | end 11 | end 12 | 13 | N '' 14 | N 'hi' 15 | Y '[]' 16 | Y '0' 17 | Y '1' 18 | Y '-0' 19 | Y '-1' 20 | Y 'null' 21 | N '-.1' 22 | Y '-0.1' 23 | Y '0e0' 24 | Y '0e+0' 25 | Y '1000000e-10000000' 26 | Y ' null' 27 | Y 'null ' 28 | N ' 99.e10 ' 29 | Y ' 99.0e-1 ' 30 | Y '[-1,1]' 31 | Y '[[[[[]]]]]' 32 | Y '[[[[[1],2],3],4],5]' 33 | Y '[ ]' 34 | Y '[ 10 ]' 35 | N ' ' 36 | N ' n' 37 | N ' nu' 38 | N ' nul' 39 | N ' n ' 40 | N ' nu ' 41 | N ' nul ' 42 | Y ' null ' 43 | Y '0.0' 44 | N '*' 45 | N '{}' 46 | N 'true' 47 | N 'false' 48 | N '{"hi":"yo"}' 49 | N '"\u' 50 | N '"\u"' 51 | N '"\"' 52 | N '"\u1000"' 53 | N '"\u0100"' 54 | N '"\a"' 55 | Y '"\u0099"' 56 | Y '"\u00eF"' 57 | -------------------------------------------------------------------------------- /tests/48.valid-bin/pass.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velipso/sink/b7d86bf4f0e4246bdf90fc0f39bca9a9d61486f2/tests/48.valid-bin/pass.txt -------------------------------------------------------------------------------- /tests/48.valid-bin/valid-bin.sink: -------------------------------------------------------------------------------- 1 | def N a 2 | if (pickle.valid list.str a) != nil 3 | say "fail: expecting invalid\n> \"$a\"" 4 | end 5 | end 6 | 7 | def Y a 8 | if (pickle.valid list.str a) != 2 9 | say "fail: expecting bin\n> \"$a\"" 10 | end 11 | end 12 | 13 | N {} 14 | N { 0x01 } 15 | N { 0x01, 0x00 } 16 | Y { 0x01, 0x00, 0xF7 } 17 | N { 0x01, 0x00, 0xF7, 0x00 } 18 | Y { 0x01, 0x02, 0x01, 0x61, 0x04, 0x61, 0x62, 0x63, 0x64, 0xF9, 0x04, 0xF8, 0x00, 0xF9, 0x01, 0xF0, 19 | 0x00, 0xF8, 0x01, 0xF8, 0x00 } 20 | N { 0x01, 0x01, 0x01, 0x61, 0x04, 0x61, 0x62, 0x63, 0x64, 0xF9, 0x04, 0xF8, 0x00, 0xF9, 0x01, 0xF0, 21 | 0x00, 0xF8, 0x01, 0xF8, 0x00 } 22 | N { 0x01, 0x02, 0x02, 0x61, 0x04, 0x61, 0x62, 0x63, 0x64, 0xF9, 0x04, 0xF8, 0x00, 0xF9, 0x01, 0xF0, 23 | 0x00, 0xF8, 0x01, 0xF8, 0x00 } 24 | N { 0x01, 0x02, 0x01, 0x61, 0xFF, 0x61, 0x62, 0x63, 0x64, 0xF9, 0x04, 0xF8, 0x00, 0xF9, 0x01, 0xF0, 25 | 0x00, 0xF8, 0x01, 0xF8, 0x00 } 26 | N { 0x01, 0x02, 0x01, 0x61, 0x04, 0x61, 0x62, 0x63, 0x64, 0xF8, 0x04, 0xF8, 0x00, 0xF9, 0x01, 0xF0, 27 | 0x00, 0xF8, 0x01, 0xF8, 0x00 } 28 | N { 0x01, 0x02, 0x01, 0x61, 0x04, 0x61, 0x62, 0x63, 0x64, 0xF9, 0x05, 0xF8, 0x00, 0xF9, 0x01, 0xF0, 29 | 0x00, 0xF8, 0x01, 0xF8, 0x00 } 30 | N { 0x01, 0x02, 0x01, 0x61, 0x04, 0x61, 0x62, 0x63, 0x64, 0xF8, 0x04, 0xF8, 0x0F, 0xF9, 0x01, 0xF0, 31 | 0x00, 0xF8, 0x01, 0xF8, 0x00 } 32 | N { 0x01, 0x02, 0x01, 0x61, 0x04, 0x61, 0x62, 0x63, 0x64, 0xF8, 0x04, 0xF8, 0x00, 0xF9, 0x01, 0xF0, 33 | 0x02, 0xF8, 0x01, 0xF8, 0x00 } 34 | N { 0x01, 0x02, 0x01, 0x61, 0x04, 0x61, 0x62, 0x63, 0x64, 0xF8, 0x04, 0xF8, 0x00, 0xF9, 0x01, 0xF0, 35 | 0x00, 0xF8, 0x01, 0xF9, 0x00 } 36 | Y { 0x01, 0x00, 0xF9, 0x02, 0xF9, 0x01, 0xF1, 0x06, 0xFA, 0x00 } 37 | Y { 0x01, 0x00, 0xF9, 0x02, 0xF9, 0x01, 0xF1, 0x06, 0xFA, 0x01 } 38 | N { 0x01, 0x00, 0xF9, 0x02, 0xF9, 0x01, 0xF1, 0x06, 0xFA, 0x02 } 39 | Y { 0x01, 0x00, 0xF0, 0x00 } 40 | Y { 0x01, 0x00, 0xF1, 0x00 } 41 | Y { 0x01, 0x00, 0xF2, 0x00, 0x00 } 42 | Y { 0x01, 0x00, 0xF3, 0x00, 0x00 } 43 | Y { 0x01, 0x00, 0xF4, 0x00, 0x00, 0x00, 0x00 } 44 | Y { 0x01, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00 } 45 | Y { 0x01, 0x00, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } 46 | N { 0x01, 0x00, 0xF0, 0x00, 0x00 } 47 | N { 0x01, 0x00, 0xF1, 0x00, 0x00 } 48 | N { 0x01, 0x00, 0xF2, 0x00, 0x00, 0x00 } 49 | N { 0x01, 0x00, 0xF3, 0x00, 0x00, 0x00 } 50 | N { 0x01, 0x00, 0xF4, 0x00, 0x00, 0x00, 0x00, 0x00 } 51 | N { 0x01, 0x00, 0xF5, 0x00, 0x00, 0x00, 0x00, 0x00 } 52 | N { 0x01, 0x00, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } 53 | N { 0x01, 0x00, 0xF0 } 54 | N { 0x01, 0x00, 0xF1 } 55 | N { 0x01, 0x00, 0xF2, 0x00 } 56 | N { 0x01, 0x00, 0xF3, 0x00 } 57 | N { 0x01, 0x00, 0xF4, 0x00, 0x00, 0x00 } 58 | N { 0x01, 0x00, 0xF5, 0x00, 0x00, 0x00 } 59 | N { 0x01, 0x00, 0xF6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } 60 | -------------------------------------------------------------------------------- /tests/49.pickle-json/pass.txt: -------------------------------------------------------------------------------- 1 | null 2 | 0 3 | "" 4 | [] 5 | 100.125 6 | -100.125 7 | "r: \r, n: \n, b: \b, f: \f, t: \t" 8 | "\u0000\u0001\u000E\u000F\u0010\u0011\u001E\u001F " 9 | [1,2,3] 10 | [1,[2,[3,[4,null],null],null],null] 11 | [["hi"],[["hi"]],["hi"]] 12 | -------------------------------------------------------------------------------- /tests/49.pickle-json/pickle-json.sink: -------------------------------------------------------------------------------- 1 | say pickle.json nil 2 | say pickle.json 0 3 | say pickle.json '' 4 | say pickle.json {} 5 | 6 | say pickle.json 100.125 7 | say pickle.json -100.125 8 | say pickle.json "r: \r, n: \n, b: \b, f: \f, t: \t" 9 | say pickle.json list.str {0x00, 0x01, 0x0E, 0x0F, 0x10, 0x11, 0x1E, 0x1F, 0x20} 10 | 11 | say pickle.json {1, 2, 3} 12 | say pickle.json {1, {2, {3, {4, nil}, nil}, nil}, nil} 13 | 14 | var a = {'hi'} 15 | say pickle.json {a, {a}, a} 16 | -------------------------------------------------------------------------------- /tests/5.multi-push/multi-push.sink: -------------------------------------------------------------------------------- 1 | var a = {1} 2 | a | list.push 2 | list.push 3 3 | say a 4 | -------------------------------------------------------------------------------- /tests/5.multi-push/pass.txt: -------------------------------------------------------------------------------- 1 | {1, 2, 3} 2 | -------------------------------------------------------------------------------- /tests/50.pickle-bin/pass.txt: -------------------------------------------------------------------------------- 1 | 0100F7 2 | 01010161F902F800F800 3 | 010201610461626364F904F800F901F000F801F800 4 | 0100F902F901F106FA00 5 | -------------------------------------------------------------------------------- /tests/50.pickle-bin/pickle-bin.sink: -------------------------------------------------------------------------------- 1 | def T obj 2 | var out = {} 3 | for var p: pickle.bin obj | str.list 4 | list.push out, (num.hex p, 2)[2:] 5 | end 6 | say list.join out 7 | end 8 | 9 | T nil 10 | T {'a', 'a'} 11 | T {'a', {0}, 'abcd', 'a'} 12 | var a = {{-250}}; list.push a, a 13 | T a 14 | -------------------------------------------------------------------------------- /tests/51.pickle-copy/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 | {1, 2, 3} 3 | {1, 2, 3} 4 | nil 5 | {1, 2, 3, {circular}} 6 | {1, 2, 3, {circular}} 7 | nil 8 | 1 9 | 1 10 | {{1, 2, 3, {circular}}, {1, 2, 3, {circular}}} 11 | {{1, 2, 3, {circular}}, {1, 2, 3, {circular}}} 12 | nil 13 | 1 14 | 1 15 | 1 16 | 1 17 | -------------------------------------------------------------------------------- /tests/51.pickle-copy/pickle-copy.sink: -------------------------------------------------------------------------------- 1 | 2 | var a = {1, 2, 3} 3 | say a == a 4 | 5 | var c = pickle.copy a 6 | say a 7 | say c 8 | say a == c 9 | 10 | list.push a, a 11 | c = pickle.copy a 12 | say a 13 | say c 14 | say a == c 15 | say a[3] == a 16 | say c[3] == c 17 | 18 | a = {a, a} 19 | say a 20 | c = pickle.copy a 21 | 22 | say c 23 | say a == c 24 | say a[0] == a[1] 25 | say c[0] == c[1] 26 | say a[0][3] == a[1][3] 27 | say c[0][3] == c[1][3] 28 | -------------------------------------------------------------------------------- /tests/52.reassign/pass.txt: -------------------------------------------------------------------------------- 1 | {1} 2 | 1 3 | 1 4 | -------------------------------------------------------------------------------- /tests/52.reassign/reassign.sink: -------------------------------------------------------------------------------- 1 | var a = 1 2 | a = {a} 3 | say a 4 | 5 | var b = 1 6 | b = "$b" 7 | say isstr b 8 | say b 9 | -------------------------------------------------------------------------------- /tests/53.pickle-ref/pass.txt: -------------------------------------------------------------------------------- 1 | {1, 2, 3} 2 | nil nil 3 | {1, 2, 3, {circular}} 4 | 1 nil 5 | {{1, 2, 3, {circular}}, {1, 2, 3, {circular}}} 6 | 1 1 7 | {1, 2, 3, {circular}} 8 | 1 nil 9 | {{1, 2, 3, {circular}}, {{{{1, 2, 3, {circular}}}}}} 10 | 1 1 11 | {{1}, {{{{{1, 2, 3, {1}}}}}}} 12 | nil 1 13 | -------------------------------------------------------------------------------- /tests/53.pickle-ref/pickle-ref.sink: -------------------------------------------------------------------------------- 1 | def T a 2 | say a 3 | say (pickle.circular a), (pickle.sibling a) 4 | end 5 | 6 | var a = {1, 2, 3} 7 | T a 8 | list.push a, a 9 | T a 10 | a = {a, a} 11 | T a 12 | a = a[0] 13 | T a 14 | a = {a, {{{a}}}} 15 | T a 16 | a = {1} 17 | a = {a, {{{{{1, 2, 3, a}}}}}} 18 | T a 19 | -------------------------------------------------------------------------------- /tests/54.pickle-val/pass.txt: -------------------------------------------------------------------------------- 1 | done 2 | -------------------------------------------------------------------------------- /tests/54.pickle-val/pickle-val.sink: -------------------------------------------------------------------------------- 1 | def JB a 2 | var j = pickle.json a 3 | var b = pickle.bin a 4 | if (order (pickle.val j), a) != 0 5 | say "JSON isn't equal: '$a' != '${pickle.val j}'" 6 | say 'fail' 7 | end 8 | if (order (pickle.val b), a) != 0 9 | say "BIN isn't equal: '$a' != '${pickle.val b}'" 10 | say 'fail' 11 | end 12 | end 13 | 14 | def randstr len 15 | var s = {} 16 | for var i: range len 17 | list.push s, num.floor rand.num * 256 18 | end 19 | return list.str s 20 | end 21 | 22 | JB nil 23 | JB 1 24 | JB -1 25 | JB 0 26 | JB -1e-100 27 | JB -10e-100 28 | JB 10e-100 29 | JB 10e100 30 | JB 300 31 | JB -300 32 | JB 70000 33 | JB -70000 34 | JB -1.5 35 | JB 1.5 36 | rand.seed 0 37 | for var i: range 100 38 | JB randstr 10 39 | end 40 | for var i: range 100 41 | var s = {} 42 | for var j: range 10 43 | if rand.num < 0.3 44 | s = {s} 45 | else 46 | var v 47 | if rand.num < 0.5 48 | v = (num.floor rand.num * 256) / 16 49 | elseif rand.num < 0.5 50 | v = randstr 10 51 | end 52 | list.push s, v 53 | end 54 | end 55 | JB s 56 | end 57 | say 'done' 58 | -------------------------------------------------------------------------------- /tests/55.int-sizes/int-sizes.sink: -------------------------------------------------------------------------------- 1 | say 0, 255 2 | say -1, -256 3 | say 256, 65535 4 | say -257, -65536 5 | say 65536, 4294967295 6 | say -65537, -4294967296 7 | say 4294967296 8 | say -4294967297 9 | -------------------------------------------------------------------------------- /tests/55.int-sizes/pass.txt: -------------------------------------------------------------------------------- 1 | 0 255 2 | -1 -256 3 | 256 65535 4 | -257 -65536 5 | 65536 4294967295 6 | -65537 -4294967296 7 | 4294967296 8 | -4294967297 9 | -------------------------------------------------------------------------------- /tests/56.all-args/all-args.sink: -------------------------------------------------------------------------------- 1 | def test ...args 2 | say args 3 | end 4 | 5 | test 1, 2, 3 6 | -------------------------------------------------------------------------------- /tests/56.all-args/pass.txt: -------------------------------------------------------------------------------- 1 | {1, 2, 3} 2 | -------------------------------------------------------------------------------- /tests/57.num-nans/num-nans.sink: -------------------------------------------------------------------------------- 1 | say 1, -num.nan 2 | say 2, num.nan + 2 3 | say 3, 2 + num.nan 4 | say 4, num.nan - 2 5 | say 5, 2 - num.nan 6 | say 6, num.nan * 2 7 | say 7, 2 * num.nan 8 | say 8, num.nan / 2 9 | say 9, 2 / num.nan 10 | say 10, num.nan % 2 11 | say 11, 2 % num.nan 12 | say 12, num.nan ^ 2 13 | say 13, 2 ^ num.nan 14 | say 14, num.abs num.nan 15 | say 15, num.sign num.nan 16 | say 16, num.max num.nan, 1 17 | say 17, num.min num.nan, 1 18 | say 18, num.clamp num.nan, 0, 1 19 | say 19, num.clamp 0, num.nan, 1 20 | say 20, num.clamp 1, 0, num.nan 21 | say 21, num.floor num.nan 22 | say 22, num.ceil num.nan 23 | say 23, num.round num.nan 24 | say 24, num.trunc num.nan 25 | say 25, num.sin num.nan 26 | say 26, num.cos num.nan 27 | say 27, num.tan num.nan 28 | say 28, num.asin num.nan 29 | say 29, num.acos num.nan 30 | say 30, num.atan num.nan 31 | say 31, num.atan2 1, num.nan 32 | say 32, num.atan2 num.nan, 1 33 | say 33, num.log num.nan 34 | say 34, num.log2 num.nan 35 | say 35, num.log10 num.nan 36 | say 36, num.exp num.nan 37 | say 37, num.lerp num.nan, 1, 0.5 38 | say 38, num.lerp 0, num.nan, 0.5 39 | say 39, num.lerp 0, 1, num.nan 40 | say 40, num.hex num.nan, 2 41 | say 41, num.hex 2, num.nan 42 | say 42, num.oct num.nan, 2 43 | say 43, num.oct 2, num.nan 44 | say 44, num.bin num.nan, 2 45 | say 45, num.bin 2, num.nan 46 | say 46, int.new num.nan 47 | say 47, int.not num.nan 48 | say 48, int.and 1, num.nan 49 | say 49, int.and num.nan, 1 50 | say 50, int.and 1, 1, 1, 1, 1, 1, num.nan 51 | say 51, int.or 1, num.nan 52 | say 52, int.or num.nan, 1 53 | say 53, int.or 1, 1, 1, 1, 1, 1, num.nan 54 | say 54, int.xor 1, num.nan 55 | say 55, int.xor num.nan, 1 56 | say 56, int.xor 1, 1, 1, 1, 1, 1, num.nan 57 | say 57, int.shl 256, num.nan 58 | say 58, int.shl num.nan, 1 59 | say 59, int.shr 256, num.nan 60 | say 60, int.shr num.nan, 1 61 | say 61, int.sar 256, num.nan 62 | say 62, int.sar num.nan, 1 63 | say 63, int.add num.nan, 2 64 | say 64, int.add 2, num.nan 65 | say 65, int.sub num.nan, 2 66 | say 66, int.sub 2, num.nan 67 | say 67, int.mul num.nan, 2 68 | say 68, int.mul 2, num.nan 69 | say 69, int.div num.nan, 2 70 | say 70, int.div 2, num.nan 71 | say 71, int.mod num.nan, 2 72 | say 72, int.mod 2, num.nan 73 | say 73, int.clz num.nan 74 | say 74, int.pop num.nan 75 | say 75, int.bswap num.nan 76 | -------------------------------------------------------------------------------- /tests/57.num-nans/pass.txt: -------------------------------------------------------------------------------- 1 | 1 nan 2 | 2 nan 3 | 3 nan 4 | 4 nan 5 | 5 nan 6 | 6 nan 7 | 7 nan 8 | 8 nan 9 | 9 nan 10 | 10 nan 11 | 11 nan 12 | 12 nan 13 | 13 nan 14 | 14 nan 15 | 15 nan 16 | 16 nan 17 | 17 nan 18 | 18 nan 19 | 19 nan 20 | 20 nan 21 | 21 nan 22 | 22 nan 23 | 23 nan 24 | 24 nan 25 | 25 nan 26 | 26 nan 27 | 27 nan 28 | 28 nan 29 | 29 nan 30 | 30 nan 31 | 31 nan 32 | 32 nan 33 | 33 nan 34 | 34 nan 35 | 35 nan 36 | 36 nan 37 | 37 nan 38 | 38 nan 39 | 39 nan 40 | 40 nan 41 | 41 0x2 42 | 42 nan 43 | 43 0c2 44 | 44 nan 45 | 45 0b10 46 | 46 0 47 | 47 -1 48 | 48 0 49 | 49 0 50 | 50 0 51 | 51 1 52 | 52 1 53 | 53 1 54 | 54 1 55 | 55 1 56 | 56 0 57 | 57 256 58 | 58 0 59 | 59 256 60 | 60 0 61 | 61 256 62 | 62 0 63 | 63 2 64 | 64 2 65 | 65 -2 66 | 66 2 67 | 67 0 68 | 68 0 69 | 69 0 70 | 70 0 71 | 71 0 72 | 72 0 73 | 73 32 74 | 74 0 75 | 75 0 76 | -------------------------------------------------------------------------------- /tests/58.label-bug/label-bug.sink: -------------------------------------------------------------------------------- 1 | var i = 0 2 | label1: 3 | say 'one' 4 | label2: 5 | say 'two' 6 | i += 1 7 | if i < 10 8 | goto label2 9 | end 10 | -------------------------------------------------------------------------------- /tests/58.label-bug/pass.txt: -------------------------------------------------------------------------------- 1 | one 2 | two 3 | two 4 | two 5 | two 6 | two 7 | two 8 | two 9 | two 10 | two 11 | two 12 | -------------------------------------------------------------------------------- /tests/59.large-cat/large-cat.sink: -------------------------------------------------------------------------------- 1 | var i = 0 2 | say "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 3 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 4 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 5 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 6 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 7 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 8 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 9 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 10 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 11 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 12 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 13 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 14 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 15 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}\n" ~ 16 | "${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1} ${i+1}" 17 | -------------------------------------------------------------------------------- /tests/59.large-cat/pass.txt: -------------------------------------------------------------------------------- 1 | 1 1 1 1 1 1 1 1 2 | 1 1 1 1 1 1 1 1 3 | 1 1 1 1 1 1 1 1 4 | 1 1 1 1 1 1 1 1 5 | 1 1 1 1 1 1 1 1 6 | 1 1 1 1 1 1 1 1 7 | 1 1 1 1 1 1 1 1 8 | 1 1 1 1 1 1 1 1 9 | 1 1 1 1 1 1 1 1 10 | 1 1 1 1 1 1 1 1 11 | 1 1 1 1 1 1 1 1 12 | 1 1 1 1 1 1 1 1 13 | 1 1 1 1 1 1 1 1 14 | 1 1 1 1 1 1 1 1 15 | 1 1 1 1 1 1 1 1 16 | -------------------------------------------------------------------------------- /tests/6.lvalues/lvalues.sink: -------------------------------------------------------------------------------- 1 | 2 | var x, y, z 3 | 4 | y = 5 5 | y += 10 6 | say y # 15 7 | 8 | x = y += 20 9 | say x, y # 35 35 10 | 11 | {x, y} = {1, 2} 12 | say x, y # 1 2 13 | 14 | {x, y} += {3, 4} 15 | say x, y # 4 6 16 | 17 | z = {x, y} = {5, 6} 18 | say x, y, z # 5 6 {5, 6} 19 | 20 | x = {1, 2} 21 | x[0] += 5 22 | say x # {6, 2} 23 | 24 | y = {1, x, 2} 25 | y[1][0] += 5 26 | say x # {11, 2} 27 | 28 | y[0:0] = {5, 6, 7} 29 | say y # {5, 6, 7, 1, {11, 2}, 2} 30 | 31 | y[4][1:1] = {10, 9} 32 | y[0:4] = nil 33 | say y # {{11, 10, 9}, 2} 34 | 35 | y[:] = {1} 36 | say y # {1} 37 | 38 | y = {1, 2, 3, 4} 39 | x = y[1:2] = {9, 9} 40 | say x, y # {9, 9} {1, 9, 9, 4} 41 | 42 | y[1:2] += {1, 1} 43 | say y # {1, 10, 10, 4} 44 | 45 | z = {1, 2} 46 | x = nil 47 | y = 10 48 | {x, y} ||= z 49 | say x, y # 1 10 50 | 51 | x = {1, 2, nil, 3, 4} 52 | {x[1:3]} ||= {{6, 7, 8}} 53 | say x # {1, 2, 7, 3, 4} 54 | 55 | x = {1, 2, nil, 3, 4} 56 | x[1:3] ||= {6, 7, 8} 57 | say x # {1, 2, 7, 3, 4} 58 | 59 | x = {1, nil, 3, nil, 4} 60 | x[1:3] &&= {6, 7, 8} 61 | say x # {1, nil, 7, nil, 4} 62 | 63 | x = {1, nil, 3, nil, 4} 64 | {x[1:3]} &&= {{6, 7, 8}} 65 | say x # {1, nil, 7, nil, 4} 66 | 67 | x &&= 1 68 | say x # 1 69 | 70 | x = nil 71 | x ||= 2 72 | say x # 2 73 | 74 | def add a, b 75 | say "add $a, $b" 76 | return a + b 77 | end 78 | 79 | x = 1 80 | x ||= add 1, 2 # shouldn't output anything 81 | 82 | x = nil 83 | x &&= add 3, 4 # shouldn't output anything 84 | 85 | x = {1, 2, 3, 4} 86 | x[:] ||= {add 5, 6} # shouldn't output anything 87 | 88 | x = {nil, nil, nil} 89 | x[:] &&= {add 7, 8} # shouldn't output anything 90 | 91 | x = nil 92 | {{x}} &&= {{add 9, 10}} # shouldn't output anything 93 | 94 | x = 1 95 | y = 2 96 | {x, {y, x}} ||= {add 11, 12} # shouldn't output anything 97 | 98 | -------------------------------------------------------------------------------- /tests/6.lvalues/pass.txt: -------------------------------------------------------------------------------- 1 | 15 2 | 35 35 3 | 1 2 4 | 4 6 5 | 5 6 {5, 6} 6 | {6, 2} 7 | {11, 2} 8 | {5, 6, 7, 1, {11, 2}, 2} 9 | {{11, 10, 9}, 2} 10 | {1} 11 | {9, 9} {1, 9, 9, 4} 12 | {1, 10, 10, 4} 13 | 1 10 14 | {1, 2, 7, 3, 4} 15 | {1, 2, 7, 3, 4} 16 | {1, nil, 7, nil, 4} 17 | {1, nil, 7, nil, 4} 18 | 1 19 | 2 20 | -------------------------------------------------------------------------------- /tests/60.enums/enums.sink: -------------------------------------------------------------------------------- 1 | enum zero, one, two, three 2 | 3 | say zero, one, two, three 4 | 5 | enum five = 5, six, seven, eight, ten = (10), eleven 6 | 7 | say five, six, seven, eight, ten, eleven 8 | 9 | enum twenty = eleven * 2 - 2 10 | say twenty 11 | -------------------------------------------------------------------------------- /tests/60.enums/pass.txt: -------------------------------------------------------------------------------- 1 | 0 1 2 3 2 | 5 6 7 8 10 11 3 | 20 4 | -------------------------------------------------------------------------------- /tests/61.range-override/pass.txt: -------------------------------------------------------------------------------- 1 | 6 2 | 7 3 | 8 4 | -------------------------------------------------------------------------------- /tests/61.range-override/range-override.sink: -------------------------------------------------------------------------------- 1 | 2 | namespace test 3 | def range a, b, c 4 | return {6, 7, 8} 5 | end 6 | for var v: range 10 7 | say v 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /tests/62.for-novars/for-novars.sink: -------------------------------------------------------------------------------- 1 | 2 | for: range 5 3 | say 'hi' 4 | end 5 | 6 | for: {1, 2, 3} 7 | say 'yo' 8 | end 9 | -------------------------------------------------------------------------------- /tests/62.for-novars/pass.txt: -------------------------------------------------------------------------------- 1 | hi 2 | hi 3 | hi 4 | hi 5 | hi 6 | yo 7 | yo 8 | yo 9 | -------------------------------------------------------------------------------- /tests/63.bitops/bitops.sink: -------------------------------------------------------------------------------- 1 | 2 | say num.hex (int.and 0x7777, 0x8FFF, 0xF89A, 0xFFFF), 4 3 | say num.hex (int.or 0x1000, 0x0200, 0x0030, 0x0004), 4 4 | say num.hex (int.xor 0x1234, 0x5678, 0x0011, 0x8800), 4 5 | 6 | say int.and {} 7 | say int.and {15, 7}, 30 8 | say int.and {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} 9 | say int.and {3, 3, 3}, {6, 6}, {2} 10 | 11 | int.pop {0x00000000, 0xFFFFFFFF, 0x12345678} | say 12 | int.bswap {0x12345678, 0xAABBCC0D, 0x1A2B3C4D} | num.hex 8 | say 13 | -------------------------------------------------------------------------------- /tests/63.bitops/pass.txt: -------------------------------------------------------------------------------- 1 | 0x0012 2 | 0x1234 3 | 0xCC5D 4 | {} 5 | {14, 6} 6 | {1, 2, 3, 0} 7 | {2, 0, 0} 8 | {0, 32, 13} 9 | {'0x78563412', '0x0DCCBBAA', '0x4D3C2B1A'} 10 | -------------------------------------------------------------------------------- /tests/64.include-plus/include-plus.sink: -------------------------------------------------------------------------------- 1 | 2 | include + './test' 3 | include A './test', B './test', + './test' 4 | 5 | foo 1 6 | -------------------------------------------------------------------------------- /tests/64.include-plus/pass.txt: -------------------------------------------------------------------------------- 1 | included 2 | included 3 | included 4 | included 5 | foo {1} 6 | -------------------------------------------------------------------------------- /tests/64.include-plus/test.sink: -------------------------------------------------------------------------------- 1 | 2 | say 'included' 3 | 4 | def foo ...args 5 | say 'foo', args 6 | end 7 | -------------------------------------------------------------------------------- /tests/65.embed/data1.txt: -------------------------------------------------------------------------------- 1 | hello 2 | -------------------------------------------------------------------------------- /tests/65.embed/embed.sink: -------------------------------------------------------------------------------- 1 | 2 | embed './data1.txt' | str.upper | str.trim | say 3 | 4 | include './test' 5 | -------------------------------------------------------------------------------- /tests/65.embed/pass.txt: -------------------------------------------------------------------------------- 1 | HELLO 2 | ydwoh 3 | -------------------------------------------------------------------------------- /tests/65.embed/test/data2.txt: -------------------------------------------------------------------------------- 1 | howdy 2 | -------------------------------------------------------------------------------- /tests/65.embed/test/index.sink: -------------------------------------------------------------------------------- 1 | 2 | embed './data2.txt' | str.rev | str.trim | say 3 | -------------------------------------------------------------------------------- /tests/66.static-cat/pass.txt: -------------------------------------------------------------------------------- 1 | 2 | ab 3 | cd 4 | abcd 5 | abcd 6 | {} 7 | {1, 2} 8 | {3, 4} 9 | {1, 2, 3, 4} 10 | {1, 2, 3, 4} 11 | -------------------------------------------------------------------------------- /tests/66.static-cat/static-cat.sink: -------------------------------------------------------------------------------- 1 | 2 | say '' ~ '' 3 | say 'ab' ~ '' 4 | say '' ~ 'cd' 5 | say 'ab' ~ 'cd' 6 | say 'a' ~ 'b' ~ 'c' ~ 'd' 7 | 8 | say {} ~ {} 9 | say {1, 2} ~ {} 10 | say {} ~ {3, 4} 11 | say {1, 2} ~ {3, 4} 12 | say {1} ~ {2} ~ {3} ~ {4} 13 | -------------------------------------------------------------------------------- /tests/67.stacktrace/pass.txt: -------------------------------------------------------------------------------- 1 | gt2 (stacktrace.sink:5:11) 2 | gt1 (stacktrace.sink:7:10) 3 | gettrace (stacktrace.sink:9:22) 4 | gettrace (stacktrace.sink:9:27) 5 | gettrace (stacktrace.sink:9:27) 6 | gettrace (stacktrace.sink:9:27) 7 | stacktrace.sink:12:12 8 | -------------------------------------------------------------------------------- /tests/67.stacktrace/stacktrace.sink: -------------------------------------------------------------------------------- 1 | 2 | def gettrace a 3 | def gt1 4 | def gt2 5 | return stacktrace 6 | end 7 | return gt2 8 | end 9 | return pick a == 0, gt1, gettrace a - 1 10 | end 11 | 12 | for var i: gettrace 3 13 | say i 14 | end 15 | -------------------------------------------------------------------------------- /tests/68.lookup-bug/lookup-bug.sink: -------------------------------------------------------------------------------- 1 | 2 | def a 3 | end 4 | def b.a 5 | return 1 6 | end 7 | say b.a 8 | -------------------------------------------------------------------------------- /tests/68.lookup-bug/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 | -------------------------------------------------------------------------------- /tests/69.gc/gc.sink: -------------------------------------------------------------------------------- 1 | say gc.getlevel 2 | say gc.NONE 3 | say gc.DEFAULT 4 | say gc.LOWMEM 5 | gc.setlevel gc.NONE 6 | say gc.getlevel 7 | gc.setlevel gc.LOWMEM 8 | say gc.getlevel 9 | gc.setlevel gc.DEFAULT 10 | say gc.getlevel 11 | gc.run 12 | -------------------------------------------------------------------------------- /tests/69.gc/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 | 0 3 | 1 4 | 2 5 | 0 6 | 2 7 | 1 8 | -------------------------------------------------------------------------------- /tests/7.varargs/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 {3, 4, 5} 2 | 4 nil {} 3 | 1 2 {3, 4} 4 | 1 nil {} 5 | 1 2 {3, 4, 5} 6 | 1 nil {} 2 {3} 7 | 1 2 {3, 4, 5} 6 {} 8 | -------------------------------------------------------------------------------- /tests/7.varargs/varargs.sink: -------------------------------------------------------------------------------- 1 | 2 | def test1 a, b, ...c 3 | say a, b, c 4 | end 5 | 6 | test1 1, 2, 3, 4, 5 # 1 2 {3, 4, 5} 7 | 8 | test1 4 # 4 nil {} 9 | 10 | test1 1, 2, 3, 4 # 1 2 {3, 4} 11 | 12 | def test2 {a, b, ...c} = {1, 2, 3, 4, 5} 13 | say a, b, c 14 | end 15 | 16 | test2 {1} # 1 nil {} 17 | 18 | test2 # 1 2 {3, 4, 5} 19 | 20 | def test3 {a, b, ...c} = {1, 2, 3, 4, 5}, d = 6, ...e 21 | say a, b, c, d, e 22 | end 23 | 24 | test3 {1}, 2, 3 # 1 nil {} 2 {3} 25 | 26 | test3 # 1 2 {3, 4, 5} 6 {} 27 | -------------------------------------------------------------------------------- /tests/70.big-str-split/big-str-split.sink: -------------------------------------------------------------------------------- 1 | var s = '0123456789012345012345678901234501234567890123450123456789012345012345678901234501234567890123450123456789012345012345678901234501234567890123450123456789012345012345678901234501234567890123450123456789012345012345678901234501234567890123450123456789012345' 2 | var len = &str.split s, '' 3 | if len == 256 4 | say 'pass' 5 | else 6 | say 'fail' 7 | end 8 | 9 | -------------------------------------------------------------------------------- /tests/70.big-str-split/pass.txt: -------------------------------------------------------------------------------- 1 | pass 2 | -------------------------------------------------------------------------------- /tests/71.var-reinit/pass.txt: -------------------------------------------------------------------------------- 1 | nil nil nil nil nil nil nil nil 2 | nil nil nil nil nil nil nil nil 3 | -------------------------------------------------------------------------------- /tests/71.var-reinit/var-reinit.sink: -------------------------------------------------------------------------------- 1 | 2 | for: range 2 3 | var a, b, {c}, {d, ...e} 4 | var {f} 5 | var {g, ...h} 6 | say a, b, c, d, e, f, g, h 7 | a = 1 8 | b = 2 9 | c = 3 10 | d = 4 11 | e = 5 12 | f = 6 13 | g = 7 14 | h = 8 15 | end 16 | -------------------------------------------------------------------------------- /tests/72.no-def/no-def.sink: -------------------------------------------------------------------------------- 1 | declare foo 2 | -------------------------------------------------------------------------------- /tests/73.no-label/no-label.sink: -------------------------------------------------------------------------------- 1 | def foo 2 | goto fail 3 | end 4 | -------------------------------------------------------------------------------- /tests/74.d-error/d-error.sink: -------------------------------------------------------------------------------- 1 | 2 | include 'testinc' 3 | 4 | foo 1 5 | -------------------------------------------------------------------------------- /tests/74.d-error/testinc-d.sink: -------------------------------------------------------------------------------- 1 | 2 | decalre foo 'bar' 3 | -------------------------------------------------------------------------------- /tests/75.rand-range/pass.txt: -------------------------------------------------------------------------------- 1 | 0 2 | 18 3 | 15 4 | -------------------------------------------------------------------------------- /tests/75.rand-range/rand-range.sink: -------------------------------------------------------------------------------- 1 | 2 | rand.seed 0 3 | 4 | say rand.range 10 5 | say rand.range 10, 20 6 | say rand.range 10, 20, 5 7 | -------------------------------------------------------------------------------- /tests/76.isnative/isnative.sink: -------------------------------------------------------------------------------- 1 | include 'shell' 2 | declare notnative 'not.native' 3 | 4 | if isnative dir.work 5 | say isstr dir.work 6 | else 7 | say 'fail' 8 | end 9 | 10 | if isnative notnative 11 | notnative 12 | say 'fail' 13 | else 14 | say 'pass' 15 | end 16 | -------------------------------------------------------------------------------- /tests/76.isnative/pass.txt: -------------------------------------------------------------------------------- 1 | 1 2 | pass 3 | -------------------------------------------------------------------------------- /tests/8.open-eof/open-eof.sink: -------------------------------------------------------------------------------- 1 | 2 | say 1 3 | 4 | do 5 | -------------------------------------------------------------------------------- /tests/9.rand/pass.txt: -------------------------------------------------------------------------------- 1 | 0xFB4153C8 2 | 0x5D9A39CC 3 | 0x42312A62 4 | 0x57EE40B0 5 | {4, 1, 5, 7, 6, 2, 3} 6 | 6 7 | {157905783, 18} 8 | 502 9 | 0x4DC332DB 10 | -------------------------------------------------------------------------------- /tests/9.rand/rand.sink: -------------------------------------------------------------------------------- 1 | 2 | rand.seed 988 3 | 4 | say num.hex rand.int, 8 5 | say num.hex rand.int, 8 6 | say num.hex rand.int, 8 7 | say num.hex rand.int, 8 8 | 9 | var x = {1, 2, 3, 4, 5, 6, 7} 10 | 11 | rand.shuffle x 12 | say x 13 | 14 | say rand.pick x 15 | 16 | x = rand.getstate 17 | say x 18 | 19 | var i = 0 20 | var t = 0 21 | do while i < 1000 22 | t += rand.num 23 | i += 1 24 | end 25 | say num.floor t 26 | 27 | rand.setstate x 28 | say num.hex rand.int, 8 29 | -------------------------------------------------------------------------------- /tests/run.sink: -------------------------------------------------------------------------------- 1 | # 2 | # sink - Minimal programming language for embedding small scripts in larger programs 3 | # by Sean Connelly (@velipso), https://sean.fun 4 | # Project Home: https://github.com/velipso/sink 5 | # SPDX-License-Identifier: 0BSD 6 | # 7 | 8 | # 9 | # this script will search for sink executables to test, and run the test suite against them 10 | # 11 | 12 | include 'shell' 13 | 14 | # shitty windows detection 15 | # TODO: fix 16 | var windows = nil 17 | if which 'node.exe' 18 | windows = 1 19 | end 20 | 21 | # TODO: use `path.join` once that works 22 | var sep = pick windows, '\', '/' 23 | var path_list = file.script | str.split sep 24 | list.pop path_list 25 | var tests_dir = path_list | list.join sep 26 | list.pop path_list 27 | var sink_dir = path_list | list.join sep 28 | list.push path_list, 'tgt' 29 | var tgt_dir = path_list | list.join sep 30 | list.push path_list, 'bin.sink' 31 | var bin_sink = path_list | list.join sep 32 | list.pop path_list 33 | list.push path_list, 'sink' 34 | var sink_posix = path_list | list.join sep 35 | list.pop path_list 36 | list.push path_list, 'sink.exe' 37 | var sink_win = path_list | list.join sep 38 | list.pop path_list 39 | list.push path_list, 'driver.js' 40 | var sink_js = path_list | list.join sep 41 | 42 | # search for sink executables to test 43 | var sink_exes = {} 44 | if file.exists sink_win 45 | say (str.pad 'Testing Windows:', 20), sink_win 46 | list.push sink_exes, sink_win 47 | elseif file.exists sink_posix 48 | say (str.pad 'Testing POSIX:', 20), sink_posix 49 | list.push sink_exes, sink_posix 50 | end 51 | if file.exists sink_js 52 | say (str.pad 'Testing JavaScript:', 20), sink_js 53 | list.push sink_exes, sink_js 54 | end 55 | if &sink_exes == 0 56 | abort 'No sink executables found to test' 57 | end 58 | 59 | def dorun exe, args 60 | # if windows is attempting to run the node.js version, we have to pass through node.exe, instead 61 | # of running the .js file directly 62 | if windows && exe == sink_js 63 | return run 'node.exe', {exe} ~ args, nil, nil, 1 64 | end 65 | return run exe, args, nil, nil, 1 66 | end 67 | 68 | # get list of tests 69 | var tests = {} 70 | var tests_list = dir.list tests_dir 71 | for var test: tests_list 72 | if +test[0] && test[1] == '.' 73 | list.push tests, test 74 | end 75 | end 76 | for var test: tests_list 77 | if +test[0] && +test[1] && test[2] == '.' 78 | list.push tests, test 79 | end 80 | end 81 | 82 | # grab the list of tests to run from args 83 | if &args > 0 84 | var ftests = {} 85 | var a = args 86 | for var test: tests 87 | var found = nil 88 | for var f: a 89 | if str.begins test, f ~ '.' 90 | found = 1 91 | break 92 | end 93 | end 94 | if found 95 | list.push ftests, test 96 | end 97 | end 98 | tests = ftests 99 | end 100 | 101 | for var test: tests 102 | var test_dir = tests_dir ~ sep ~ test # TODO: path.join 103 | var test_num = +test 104 | var test_name = (test | str.split '.')[1:] | list.join '.' 105 | var test_script = test_dir ~ sep ~ test_name ~ '.sink' # TODO: path.join 106 | var pass_file = test_dir ~ sep ~ 'pass.txt' # TODO: path.join 107 | var fail_file = test_dir ~ sep ~ 'fail.txt' # TODO: path.join 108 | 109 | declare test_failed 110 | declare check_diff 111 | 112 | var bin_data = nil 113 | for var sink_exe: sink_exes 114 | var pargs = {'-D', 'testinc', test_dir ~ sep ~ 'testinc-d.sink', test_script} 115 | 116 | # run the script 117 | var {status, stdout, stderr} = dorun sink_exe, pargs 118 | 119 | if status == 0 120 | # 121 | # script succeeded 122 | # 123 | 124 | # check stdout 125 | if !file.exists pass_file 126 | say stdout 127 | test_failed 'Expecting script to fail but it passed' 128 | end 129 | var answer = file.read pass_file 130 | check_diff answer, stdout 131 | 132 | # check compiling 133 | var {cstatus, cstdout, cstderr} = dorun sink_exe, {'-c', '-d'} ~ pargs 134 | if cstatus != 0 135 | say cstderr 136 | test_failed 'Failed to compile script' 137 | end 138 | if bin_data == nil 139 | bin_data = cstdout 140 | else 141 | if bin_data != cstdout 142 | say 'Compiler 1:', sink_exes[0] 143 | say 'Compiler 2:', sink_exe 144 | test_failed 'Mismatched compiler results' 145 | end 146 | end 147 | 148 | # check bytecode execution results 149 | file.write bin_sink, cstdout 150 | var {rstatus, rstdout, rstderr} = dorun sink_exe, {bin_sink} 151 | if rstatus != 0 152 | say rstderr 153 | test_failed 'Failed to execute bytecode' 154 | end 155 | check_diff answer, stdout 156 | else 157 | # 158 | # script failed 159 | # 160 | 161 | if file.exists pass_file 162 | say stderr 163 | test_failed 'Expecting script to pass but it failed' 164 | end 165 | end 166 | end 167 | 168 | def test_result res 169 | say (str.pad test_num, -4) ~ '.' ~ (str.pad test_name, 20) ~ res 170 | end 171 | 172 | def test_failed reason 173 | test_result 'FAIL' 174 | abort reason 175 | end 176 | 177 | def check_diff answer, actual 178 | if windows 179 | actual = actual | str.split "\r\n" | list.join "\n" 180 | end 181 | if answer == actual 182 | return 183 | end 184 | var ansf = tgt_dir ~ sep ~ 'temp1.txt' 185 | var actf = tgt_dir ~ sep ~ 'temp2.txt' 186 | file.write ansf, answer 187 | file.write actf, actual 188 | if which 'diff' 189 | run 'diff', {'-u', '--label', 'Expected Output', ansf, '--label', 'Actual Output', actf} 190 | else 191 | say '>> EXPECTED:' 192 | say answer 193 | say '>> ACTUAL:' 194 | say actual 195 | end 196 | test_failed 'Incorrect results' 197 | end 198 | 199 | # test passed 200 | test_result 'PASS' 201 | end 202 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "umd", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "skipLibCheck": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "outDir": "./tgt", 10 | "declaration": true 11 | }, 12 | "include": [ 13 | "src" 14 | ], 15 | "exclude": [ 16 | "node_modules", 17 | "dist", 18 | "tgt" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------