├── .gitignore ├── scripts ├── getopt.js ├── lc.js ├── avl.js ├── fqcnt.js ├── regex.js ├── bedcov.js ├── avl-fix │ ├── repro_bug.js │ └── AVL_BUG_FIX.md ├── sudoku.js └── k8.js ├── Makefile ├── LICENSE.txt ├── .circleci └── config.yml ├── test └── hard20.txt ├── NEWS.md ├── README.md ├── scripts-old ├── k8tk.js └── k8.js └── k8.cc /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | *.o 3 | -------------------------------------------------------------------------------- /scripts/getopt.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env k8 2 | 3 | load("k8.js"); 4 | 5 | function main(args) { 6 | for (const o of getopt(args, "x:y", [ "foo=", "bar" ])) 7 | print(`${o.opt}=${o.arg}`); 8 | } 9 | 10 | main(arguments); 11 | -------------------------------------------------------------------------------- /scripts/lc.js: -------------------------------------------------------------------------------- 1 | if (arguments.length == 0) { 2 | warn("Usage: k8 lc.js "); 3 | exit(1); 4 | } 5 | let buf = new Bytes(); 6 | let n = 0, file = new File(arguments[0]); 7 | while (file.readline(buf) >= 0) ++n; 8 | file.close(); 9 | buf.destroy(); 10 | print(n); 11 | -------------------------------------------------------------------------------- /scripts/avl.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env k8 2 | 3 | "use strict"; 4 | load("k8.js"); 5 | 6 | const str = "MNOLKQOPHIA"; 7 | let avl = new AVLtree((x, y) => x == y? 0 : x < y? -1 : 1); 8 | for (let i = 0; i < str.length; ++i) 9 | avl.insert(str[i]); 10 | print(avl.size); 11 | let itr = avl.find_itr("K"); 12 | while (itr.get() != null) { 13 | print(itr.get()); 14 | itr.next(); 15 | } 16 | avl.erase("O"); 17 | print(avl.size); 18 | -------------------------------------------------------------------------------- /scripts/fqcnt.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env k8 2 | 3 | load("k8.js"); 4 | 5 | function main(args) { 6 | if (args.length == 0) { 7 | print("Usage: fqcnt.js "); 8 | return 1; 9 | } 10 | var file = new File(args[0]); 11 | var fx = new Fastx(file); 12 | var n = 0, slen = 0, qlen = 0; 13 | while (fx.read() >= 0) 14 | ++n, slen += fx.s.length, qlen += fx.q.length; 15 | file.close(); 16 | print(n, slen, qlen); 17 | } 18 | 19 | main(arguments); 20 | -------------------------------------------------------------------------------- /scripts/regex.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env k8 2 | 3 | function main(args) { 4 | if (args.length == 0) { 5 | print("Usage: k8 regex.js [file.txt]"); 6 | print("Patterns:"); 7 | print(` URI (protocol://server/path): ([a-zA-Z][a-zA-Z0-9]*)://([^\\s/]+)(/[^\\s]*)?`); 8 | print(` URI|Email: ([a-zA-Z][a-zA-Z0-9]*)://([^\\s/]+)(/[^\\s]*)?|([^\\s@]+)@([^\\s@]+)`); 9 | print("Data:"); 10 | print(" http://people.unipmn.it/manzini/lightweight/corpus/howto.bz2"); 11 | exit(1); 12 | } 13 | let re = new RegExp(args[0]); 14 | let buf = new Bytes(); 15 | let file = args.length >= 2? new File(args[1]) : new File(); 16 | while (file.readline(buf) >= 0) { 17 | const line = buf.toString(); 18 | if (line.match(re)) 19 | print(line); 20 | } 21 | file.close(); 22 | buf.destroy(); 23 | } 24 | 25 | main(arguments); 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CXXFLAGS=-std=c++17 -g -O3 -Wall 2 | NODE_SRC?=.. 3 | EXE=k8 4 | LIB_COMMON=-lv8_snapshot -lv8_libplatform -lv8_base_without_compiler -lv8_libbase -lv8_zlib -lv8_compiler -licutools -lm -ldl -lz 5 | LIB_LINUX=-L$(NODE_SRC)/out/Release -L$(NODE_SRC)/out/Release/obj.target/tools/v8_gypfiles -L$(NODE_SRC)/out/Release/obj.target/tools/icu \ 6 | -Wl,--no-whole-archive -Wl,--start-group $(LIB_COMMON) -Wl,--end-group 7 | LIB_DARWIN=-L$(NODE_SRC)/out/Release $(LIB_COMMON) 8 | LIBS=-pthread 9 | 10 | UNAME := $(shell uname) 11 | 12 | ifeq ($(UNAME), Linux) 13 | LIBS+=$(LIB_LINUX) 14 | endif 15 | ifeq ($(UNAME), Darwin) 16 | LIBS+=$(LIB_DARWIN) 17 | endif 18 | 19 | ifneq ($(asan),) 20 | CXXFLAGS+=-fsanitize=address 21 | LIBS+=-fsanitize=address 22 | endif 23 | 24 | all:$(EXE) 25 | 26 | $(EXE):k8.o 27 | $(CXX) -o $@ $< $(LIBS) 28 | 29 | k8.o:k8.cc 30 | $(CXX) -c $(CXXFLAGS) -I$(NODE_SRC)/deps/v8 -I$(NODE_SRC)/deps/v8/include -o $@ $< 31 | 32 | clean: 33 | rm -f $(EXE) *.o 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018- Dana-Farber Cancer Institute 4 | 2011-2018 Broad Institute, Inc. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | -------------------------------------------------------------------------------- /scripts/bedcov.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env k8 2 | 3 | load("k8.js"); 4 | 5 | function main(args) 6 | { 7 | if (args.length < 2) { 8 | print("Usage: bedcov.js "); 9 | return; 10 | } 11 | let bed = {}, file, buf = new Bytes(); 12 | file = new File(args[0]); 13 | while (file.readline(buf) >= 0) { 14 | const t = buf.toString().split("\t", 3); 15 | if (bed[t[0]] == null) bed[t[0]] = []; 16 | bed[t[0]].push([parseInt(t[1]), parseInt(t[2]), 0]); 17 | } 18 | for (const ctg in bed) iit_index(bed[ctg]); 19 | file.close(); 20 | 21 | file = new File(args[1]); 22 | while (file.readline(buf) >= 0) { 23 | const t = buf.toString().split("\t", 3); 24 | if (bed[t[0]] == null) { 25 | print(t[0], t[1], t[2], 0, 0); 26 | } else { 27 | const st0 = parseInt(t[1]), en0 = parseInt(t[2]); 28 | const a = iit_overlap(bed[t[0]], st0, en0); 29 | let cov_st = 0, cov_en = 0, cov = 0; 30 | for (let i = 0; i < a.length; ++i) { 31 | const st1 = a[i][0] > st0? a[i][0] : st0; 32 | const en1 = a[i][1] < en0? a[i][1] : en0; 33 | if (st1 > cov_en) { 34 | cov += cov_en - cov_st; 35 | cov_st = st1, cov_en = en1; 36 | } else cov_en = cov_en > en1? cov_en : en1; 37 | } 38 | cov += cov_en - cov_st; 39 | print(t[0], t[1], t[2], a.length, cov); 40 | } 41 | } 42 | file.close(); 43 | buf.destroy(); 44 | } 45 | 46 | main(arguments); 47 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | jobs: 4 | build-aarch64: 5 | 6 | machine: 7 | image: ubuntu-2204:current 8 | resource_class: arm.large 9 | 10 | steps: 11 | - checkout 12 | 13 | - run: 14 | name: Install dependencies 15 | command: | 16 | sudo apt update 17 | sudo apt-get install -y file tar wget make 18 | 19 | - run: 20 | name: Build K8 Linux ARM64 binary 21 | environment: 22 | NODE_VERSION: 18.19.1 23 | command: | 24 | # Hack CircleCI Ubuntu Docker image to link 'python' to 'python3' 25 | echo -e '#!/usr/bin/env bash\n\nexport PYENV_ROOT="/opt/circleci/.pyenv"\nexec "/opt/circleci/.pyenv/libexec/pyenv" exec "python3" "$@"' > ~/bin/python 26 | chmod +x ~/bin/python 27 | PATH=~/bin:$PATH 28 | python --version 29 | 30 | wget -O- https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}.tar.gz | tar -zxf - 31 | pushd node-v${NODE_VERSION} 32 | ./configure 33 | make -j3 34 | popd 35 | 36 | # Then compile k8 37 | NODE_SRC=node-v${NODE_VERSION} make -j 38 | file k8 | grep aarch64 39 | mv k8 k8-aarch64-Linux 40 | 41 | - store_artifacts: 42 | path: k8-aarch64-Linux 43 | 44 | workflows: 45 | build: 46 | jobs: 47 | - build-aarch64 48 | -------------------------------------------------------------------------------- /test/hard20.txt: -------------------------------------------------------------------------------- 1 | ..............3.85..1.2.......5.7.....4...1...9.......5......73..2.1........4...9 2 | .......12........3..23..4....18....5.6..7.8.......9.....85.....9...4.5..47...6... 3 | .2..5.7..4..1....68....3...2....8..3.4..2.5.....6...1...2.9.....9......57.4...9.. 4 | ........3..1..56...9..4..7......9.5.7.......8.5.4.2....8..2..9...35..1..6........ 5 | 12.3....435....1....4........54..2..6...7.........8.9...31..5.......9.7.....6...8 6 | 1.......2.9.4...5...6...7...5.9.3.......7.......85..4.7.....6...3...9.8...2.....1 7 | .......39.....1..5..3.5.8....8.9...6.7...2...1..4.......9.8..5..2....6..4..7..... 8 | 12.3.....4.....3....3.5......42..5......8...9.6...5.7...15..2......9..6......7..8 9 | ..3..6.8....1..2......7...4..9..8.6..3..4...1.7.2.....3....5.....5...6..98.....5. 10 | 1.......9..67...2..8....4......75.3...5..2....6.3......9....8..6...4...1..25...6. 11 | ..9...4...7.3...2.8...6...71..8....6....1..7.....56...3....5..1.4.....9...2...7.. 12 | ....9..5..1.....3...23..7....45...7.8.....2.......64...9..1.....8..6......54....7 13 | 4...3.......6..8..........1....5..9..8....6...7.2........1.27..5.3....4.9........ 14 | 7.8...3.....2.1...5.........4.....263...8.......1...9..9.6....4....7.5........... 15 | 3.7.4...........918........4.....7.....16.......25..........38..9....5...2.6..... 16 | ........8..3...4...9..2..6.....79.......612...6.5.2.7...8...5...1.....2.4.5.....3 17 | .......1.4.........2...........5.4.7..8...3....1.9....3..4..2...5.1........8.6... 18 | .......12....35......6...7.7.....3.....4..8..1...........12.....8.....4..5....6.. 19 | 1.......2.9.4...5...6...7...5.3.4.......6........58.4...2...6...3...9.8.7.......1 20 | .....1.2.3...4.5.....6....7..2.....1.8..9..3.4.....8..5....2....9..3.4....67..... 21 | -------------------------------------------------------------------------------- /scripts/avl-fix/repro_bug.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env k8 2 | "use strict"; 3 | load("../k8.js"); 4 | 5 | // Helper to print tree and check balances 6 | function check_avl(node) { 7 | if (!node) return { h: 0, ok: true }; 8 | 9 | let l = check_avl(node.p[0]); 10 | let r = check_avl(node.p[1]); 11 | 12 | if (!l.ok || !r.ok) return { h: 0, ok: false }; 13 | 14 | let h = Math.max(l.h, r.h) + 1; 15 | let bal = r.h - l.h; 16 | 17 | if (bal != node.balance) { 18 | print("ERROR: Node " + node.data + " has stored balance " + node.balance + " but actual balance " + bal); 19 | return { h: h, ok: false }; 20 | } 21 | 22 | if (bal < -1 || bal > 1) { 23 | print("ERROR: Node " + node.data + " is unbalanced (" + bal + ")"); 24 | return { h: h, ok: false }; 25 | } 26 | 27 | return { h: h, ok: true }; 28 | } 29 | 30 | let avl = new AVLtree((x, y) => x - y); 31 | 32 | // Insert nodes to form the specific structure 33 | // 40 34 | // / \ 35 | // 20 60 36 | // / \ / \ 37 | // 10 30 45 70 38 | // / 39 | // 42 40 | 41 | let keys = [40, 20, 60, 10, 30, 45, 70, 42]; 42 | for (let k of keys) avl.insert(k); 43 | 44 | print("Tree size before delete: " + avl.size); 45 | let res = check_avl(avl.root); 46 | if (res.ok) print("Tree valid before delete"); 47 | else print("Tree invalid before delete"); 48 | 49 | print("Deleting 40..."); 50 | avl.erase(40); 51 | 52 | print("Tree size after delete: " + avl.size); 53 | res = check_avl(avl.root); 54 | 55 | if (res.ok) { 56 | print("Tree is valid."); 57 | } else { 58 | print("Tree is INVALID."); 59 | // Print 60's balance specifically 60 | let n60 = avl.find(60); 61 | if (n60) { 62 | print("Node 60 balance: " + n60.balance); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /scripts/avl-fix/AVL_BUG_FIX.md: -------------------------------------------------------------------------------- 1 | # AVL Tree Bug Analysis and Fix 2 | 3 | ## Issue Identified 4 | In `scripts/k8.js`, the `AVLtree.erase` method had a bug in the rebalancing logic when deleting a node whose successor is not its direct child. 5 | 6 | When splicing the successor `r` into the place of the deleted node `p`, the code overwrote the rebalancing stack at index `e` (where the parent of the successor `q` was stored) with `r`. 7 | ```javascript 8 | path[e] = r, dir[e] = 1; 9 | ``` 10 | This removed `q` (and potentially other nodes in certain stack configurations, though `q` is the immediate victim) from the rebalancing `path` stack. Since `q`'s left subtree height decreased (because `r` was moved out), `q`'s balance factor needed to be updated and potentially propagated up. By skipping `q`, the tree could remain with incorrect balance factors, violating AVL properties. 11 | 12 | ## Reproduction 13 | A reproduction script `repro_bug.js` was created with a specific tree structure: 14 | ``` 15 | 40 16 | / \ 17 | 20 60 18 | / \ / \ 19 | 10 30 45 70 20 | / 21 | 42 22 | ``` 23 | Deleting `40` causes `42` (successor) to move. `42` is removed from `45`. `45` height changes. But `45` was skipped in the update chain due to the overwrite. This resulted in `60` (parent of `45`) not being updated correctly. 24 | 25 | ## Fix 26 | The fix is to **insert** `r` into the `path` and `dir` arrays instead of overwriting the existing element. This ensures that `q` remains in the stack and is processed during unwinding. 27 | 28 | ```javascript 29 | path.splice(e, 0, r); 30 | dir.splice(e, 0, 1); 31 | ``` 32 | 33 | ## Verification 34 | The reproduction script was run after applying the fix, and it confirmed that the AVL tree remains valid (correct size, height, and balance factors) after deletion. 35 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | Release 1.2-r137 (27 May 2024) 2 | ------------------------------ 3 | 4 | New functions: 5 | 6 | * `k8_read_file()`: read an entire file as an ArrayBuffer 7 | * `k8_decode()`: convert an ArrayBuffer/Bytes object to a string 8 | * `k8_encode()`: convert a string to an ArrayBuffer 9 | * `k8_revcomp()`: reverse complement a DNA sequence 10 | 11 | Improved: 12 | 13 | * `Bytes.read()`: allow to read the rest of a file 14 | 15 | (1.2: 27 May 2024, r137) 16 | 17 | 18 | 19 | Release 1.1-r129 (26 May 2024) 20 | ------------------------------ 21 | 22 | This version sets v8's default `max_old_space_size` to 16 GB and also adds 23 | option `-m` as a more convenient way to change this parameter. 24 | 25 | (1.1: 26 May 2024, r129) 26 | 27 | 28 | 29 | Release 1.0-r124 (10 August 2023) 30 | --------------------------------- 31 | 32 | The previous version of k8, v0.2.5, was built on top of v8-3.16.4 released on 33 | 2013-01-11. This version updates v8 to v8-10.2.154.26 released on 2023-01-23, 34 | ten years later. It brings ES6 features including but not limited to the "let" 35 | keyword to define local variables, Java-like classes and back-tick strings. 36 | 37 | Due to the lack of the SetIndexedPropertiesToExternalArrayData() API in v8, it 38 | is not possible to keep full backward compatibility with older k8 versions. 39 | Nonetheless, we have tried to retain commonly used methods such that several 40 | popular k8 scripts can mostly work. 41 | 42 | New functions: 43 | 44 | * `k8_version()`: get the k8 version 45 | * `Bytes.buffer`: get ArrayBuffer 46 | 47 | Improved: 48 | 49 | * `load()`: search the script path 50 | * `print()`: print to stdout. Support ArrayBuffer. Faster for Bytes. 51 | * `warn()`: print to stderr. Support ArrayBuffer. Faster for Bytes. 52 | 53 | Mostly unchanged: 54 | 55 | * `exit()`: exit 56 | * `Bytes.length`: get/set Bytes length 57 | * `Bytes.capacity`: get/set Bytes capacity 58 | * `new File()`: open a file 59 | * `File.prototype.close()`: close the file handler 60 | * `File.prototype.read()`: read a byte or bytes 61 | * `File.prototype.readline()`: read a line 62 | * `File.prototype.write()`: write to a plain file 63 | 64 | Changed functions (BREAKING): 65 | 66 | * `new Bytes()` - Bytes only supports `uint8_t` now 67 | * `Bytes.prototype.set()` - Bytes only supports `uint8_t` 68 | 69 | Removed functions (BREAKING): 70 | 71 | * `Map` - JavaScript has `Map` and works better 72 | * `Bytes.prototype.cast()` - Bytes only supports `uint8_t` 73 | * `Bytes[]` - not possible to implement. Use `Bytes.buffer` as a partial remedy 74 | 75 | (1.0: 10 August 2023, r124) 76 | -------------------------------------------------------------------------------- /scripts/sudoku.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env k8 2 | 3 | class SudokuSolver { 4 | #R; 5 | #C; 6 | constructor() { 7 | this.C = [], this.R = []; 8 | let r = 0; 9 | for (let i = 0; i < 9; ++i) 10 | for (let j = 0; j < 9; ++j) 11 | for (let k = 0; k < 9; ++k) 12 | this.C[r++] = [ 9 * i + j, (Math.floor(i/3)*3 + Math.floor(j/3)) * 9 + k + 81, 9 * i + k + 162, 9 * j + k + 243 ]; 13 | for (let c = 0; c < 324; ++c) this.R[c] = []; 14 | for (let r = 0; r < 729; ++r) 15 | for (let c2 = 0; c2 < 4; ++c2) 16 | this.R[this.C[r][c2]].push(r); 17 | } 18 | #update(sr, sc, r, v) { 19 | let min = 10, min_c = 0; 20 | for (let c2 = 0; c2 < 4; ++c2) sc[this.C[r][c2]] += v<<7; 21 | for (let c2 = 0; c2 < 4; ++c2) { 22 | let rr, c = this.C[r][c2]; 23 | if (v > 0) { 24 | for (let r2 = 0; r2 < 9; ++r2) { 25 | if (sr[rr = this.R[c][r2]]++ != 0) continue; 26 | for (let cc2 = 0; cc2 < 4; ++cc2) { 27 | let cc = this.C[rr][cc2]; 28 | if (--sc[cc] < min) 29 | min = sc[cc], min_c = cc; 30 | } 31 | } 32 | } else { // revert 33 | for (let r2 = 0; r2 < 9; ++r2) { 34 | if (--sr[rr = this.R[c][r2]] != 0) continue; 35 | const p = this.C[rr]; 36 | ++sc[p[0]]; ++sc[p[1]]; ++sc[p[2]]; ++sc[p[3]]; 37 | } 38 | } 39 | } 40 | return min<<16 | min_c; 41 | } 42 | solve(_s) { 43 | let hints = 0, sr = [], sc = [], cr = [], cc = [], out = [], ret = []; 44 | for (let r = 0; r < 729; ++r) sr[r] = 0; 45 | for (let c = 0; c < 324; ++c) sc[c] = 9; 46 | for (let i = 0; i < 81; ++i) { 47 | const a = _s[i] >= '1' && _s[i] <= '9'? _s.charCodeAt(i) - 49 : -1; 48 | if (a >= 0) this.#update(sr, sc, i * 9 + a, 1); 49 | if (a >= 0) ++hints; 50 | cr[i] = cc[i] = -1, out[i] = a + 1; 51 | } 52 | let i = 0, dir = 1, cand = 10<<16|0; 53 | for (;;) { 54 | while (i >= 0 && i < 81 - hints) { 55 | if (dir == 1) { 56 | let min = cand>>16; 57 | cc[i] = cand&0xffff; 58 | if (min > 1) { 59 | for (let c = 0; c < 324; ++c) { 60 | if (sc[c] < min) { 61 | min = sc[c], cc[i] = c; 62 | if (min <= 1) break; 63 | } 64 | } 65 | } 66 | if (min == 0 || min == 10) cr[i--] = dir = -1; 67 | } 68 | const c = cc[i]; 69 | if (dir == -1 && cr[i] >= 0) 70 | this.#update(sr, sc, this.R[c][cr[i]], -1); 71 | let r2; 72 | for (r2 = cr[i] + 1; r2 < 9; ++r2) 73 | if (sr[this.R[c][r2]] == 0) break; 74 | if (r2 < 9) { 75 | cand = this.#update(sr, sc, this.R[c][r2], 1); 76 | cr[i++] = r2; dir = 1; 77 | } else cr[i--] = dir = -1; 78 | } 79 | if (i < 0) break; 80 | let y = []; 81 | for (let j = 0; j < 81; ++j) y[j] = out[j]; 82 | for (let j = 0; j < i; ++j) { 83 | const r = this.R[cc[j]][cr[j]]; 84 | y[Math.floor(r/9)] = r%9 + 1; 85 | } 86 | ret.push(y); 87 | --i; dir = -1; 88 | } 89 | return ret; 90 | } 91 | } 92 | 93 | function main(args) { 94 | if (args.length == 0) { 95 | warn("Usage: k8 sudoku.js "); 96 | return; 97 | } 98 | let buf = new Bytes(); 99 | let file = new File(args[0]); 100 | let solver = new SudokuSolver(); 101 | while (file.readline(buf) >= 0) { 102 | const l = buf.toString(); 103 | if (l.length >= 81) { 104 | let r = solver.solve(l); 105 | for (let i = 0; i < r.length; ++i) 106 | print(r[i].join('')); 107 | print(); 108 | } 109 | } 110 | file.close(); 111 | buf.destroy(); 112 | } 113 | 114 | main(arguments); 115 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Getting Started 2 | ```sh 3 | # Download precomiplied binaries 4 | wget -O- https://github.com/attractivechaos/k8/releases/download/v1.2/k8-1.2.tar.bz2 | tar -jxf - 5 | k8-1.2/k8-x86_64-Linux -e 'print(Math.log(2))' 6 | 7 | # Compile from source code. This requires to compile node.js first (or v18.20.3 on Mac): 8 | wget -O- https://nodejs.org/dist/v18.19.1/node-v18.19.1.tar.gz | tar -zxf - 9 | cd node-v18.19.1 && ./configure && make -j16 10 | # Then compile k8 11 | git clone https://github.com/attractivechaos/k8 12 | cd k8 && make 13 | ``` 14 | 15 | The following example counts the number of lines: 16 | ```javascript 17 | if (arguments.length == 0) { // test command-line arguments 18 | warn("Usage: k8 lc.js "); 19 | exit(1); 20 | } 21 | let buf = new Bytes(); 22 | let n = 0, file = new File(arguments[0]); 23 | while (file.readline(buf) >= 0) ++n; 24 | file.close(); 25 | buf.destroy(); 26 | print(n); 27 | ``` 28 | 29 | ## Introduction 30 | 31 | K8 is a JavaScript runtime built on top of Google's [v8 JavaScript engine][v8]. 32 | It provides a resizable binary buffer and synchronous APIs for plain file 33 | writing and gzip'd file reading. 34 | 35 | ## Motivations 36 | 37 | JavaScript is among the fastest scripting languages. It is essential for web 38 | development but not often used for large-scale text processing or command-line 39 | utilities, in my opinion, due to the lack of sensible file I/O. Current 40 | JavaScript runtimes such as [Node.js][node] and [Deno][deno] focus on 41 | [asynchronous I/O][aio] and whole-file reading. Even reading a file line by 42 | line, which is required to work with large files, becomes a cumbersome effort. 43 | K8 aims to solve this problem. With synchronous I/O APIs, JavaScript can be a 44 | powerful language for developing command-line tools. 45 | 46 | ## Installation 47 | 48 | It is recommended to download [precompiled binaries][release] 49 | (also available [from Zenodo][zenodo]). If you want to compile k8, 50 | you need to compile Node.js which bundles v8 and provides a more convenient way 51 | build v8. As the v8 APIs are fast changing, both Node.js and k8 only work with 52 | specific versions of v8. The k8-1.x branch is known to work with node-18.x but 53 | not 19.x or higher. It is also worth noting that node-18.20.x upgraded 54 | [c-ares][c-ares] which is now incompatible with older glibc. Node-18.19.1 is 55 | the most recent version that can be compiled on CentOS 7. 56 | On the other hand, Node-18.19.x cannot be compiled on MacOS with clang-15. 57 | Node-18.20.3 is known to work. 58 | 59 | ## API Documentations 60 | 61 | ### Functions 62 | 63 | ```javascript 64 | // Print to stdout (print) or stderr (warn). TAB delimited if multiple arguments. 65 | function print(data: any) 66 | function warn(data: any) 67 | 68 | // Exit 69 | function exit(code: number) 70 | 71 | // Load a JavaScript file and execute. It searches the working directory, the 72 | // script directory and then the K8_PATH environment variable in order. 73 | function load(fileName: string) 74 | 75 | // Read entire file as an ArrayBuffer 76 | function k8_read_file(fileName: string): ArrayBuffer 77 | 78 | // Decode $buf to string under the $enc encoding; only "utf-8" is supported for now 79 | // Missing or unknown encoding is treated as Latin-1 80 | function k8_decode(buf: ArrayBuffer|Bytes, enc?: string): string 81 | 82 | // Encode $str into an ArrayBuffer 83 | function k8_encode(str: string, enc?: string): ArrayBuffer 84 | 85 | // Reverse complement a DNA sequence in string 86 | function k8_revcomp(seq: string): string 87 | 88 | // Reverse complement a DNA sequence in place 89 | function k8_revcomp(seq: ArrayBuffer|Bytes) 90 | 91 | // Get version string 92 | function k8_version(): string 93 | ``` 94 | 95 | ### The Bytes Object 96 | 97 | `Bytes` provides a resizable byte array. 98 | 99 | ```javascript 100 | // Create an array of byte buffer of $len in size. 101 | new Bytes(len?: number = 0) 102 | 103 | // Property: get/set length of the array 104 | .length: number 105 | 106 | // Property: get/set the max capacity of the array 107 | .capacity: number 108 | 109 | // Property: get ArrayBuffer of the underlying data, not allocated from v8 110 | .buffer: ArrayBuffer 111 | 112 | // Deallocate the array. This is necessary as the memory is not managed by the v8 GC. 113 | Bytes.prototype.destroy() 114 | 115 | // Replace the byte array starting from $offset to $data, where $data can be a number, 116 | // a string, an array or Bytes. The size of the array is modified if the new array 117 | // is larger. Return the number of modified bytes. 118 | Bytes.prototype.set(data: number|string|Array|ArrayBuffer, offset?: number) :number 119 | 120 | // Convert the byte array to string 121 | Bytes.prototype.toString() 122 | ``` 123 | 124 | ### The File Object 125 | 126 | `File` provides buffered file I/O. 127 | 128 | ```javascript 129 | // Open a plain or gzip'd file for reading or a plain file for writing. $file 130 | // is file descriptor if it is an integer or file name if string. Each File 131 | // object can only be read or only be written, not mixed 132 | new File(file?: string|number = 0, mode?: string = "r") 133 | 134 | // Read a byte and return it 135 | File.prototype.read() :number 136 | 137 | // Read the rest of the file into $buf at offset 0. Return the number of bytes read. 138 | File.prototype.read(buf: Bytes) :number 139 | 140 | // Read $len bytes into $buf at $offset. 141 | // Return the number of bytes read on success; 0 on file end; <0 on errors 142 | File.prototype.read(buf: Bytes, offset: number, len: number) :number 143 | 144 | // Read a line or a token to $buf at $offset. $sep=0 for SPACE, 1 for TAB and 2 145 | // for newline. If $sep is a string, only the first character is considered. 146 | // Return the delimiter if non-negative, -1 upon EOF, or <-1 for errors 147 | File.prototype.readline(buf: Bytes, sep?: number|string = 2, offset?: number = 0) :number 148 | 149 | // Write data 150 | File.prototype.write(data: string|ArrayBuffer) :number 151 | 152 | // Close a file 153 | File.prototype.close() 154 | ``` 155 | 156 | [3]: https://github.com/tlrobinson/narwhal 157 | [4]: http://silkjs.net/ 158 | [5]: http://code.google.com/p/teajs/ 159 | [6]: https://github.com/samlecuyer/sorrow.js 160 | [7]: http://nodejs.org/api/fs.html 161 | [8]: http://nodejs.org/api/stream.html 162 | [11]: https://sourceforge.net/projects/lh3/files/ 163 | [v8]: https://v8.dev 164 | [gyp]: https://gyp.gsrc.io/ 165 | [release]: https://github.com/attractivechaos/k8/releases 166 | [deno]: https://deno.land 167 | [node]: https://nodejs.org/ 168 | [commjs]: https://en.wikipedia.org/wiki/CommonJS 169 | [aio]: https://en.wikipedia.org/wiki/Asynchronous_I/O 170 | [typedarray]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray 171 | [arraybuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer 172 | [c-ares]: https://c-ares.org 173 | [zenodo]: https://zenodo.org/records/8245119 174 | -------------------------------------------------------------------------------- /scripts/k8.js: -------------------------------------------------------------------------------- 1 | /********************************* 2 | * Command-line argument parsing * 3 | *********************************/ 4 | 5 | Array.prototype.delete_at = function(i) { 6 | for (let j = i; j < this.length - 1; ++j) 7 | this[j] = this[j + 1]; 8 | --this.length; 9 | } 10 | 11 | function* getopt(argv, ostr, longopts) { 12 | if (argv.length == 0) return; 13 | let pos = 0, cur = 0; 14 | while (cur < argv.length) { 15 | let lopt = "", opt = "?", arg = ""; 16 | while (cur < argv.length) { // skip non-option arguments 17 | if (argv[cur][0] == "-" && argv[cur].length > 1) { 18 | if (argv[cur] == "--") cur = argv.length; 19 | break; 20 | } else ++cur; 21 | } 22 | if (cur == argv.length) break; 23 | let a = argv[cur]; 24 | if (a[0] == "-" && a[1] == "-") { // a long option 25 | pos = -1; 26 | let c = 0, k = -1, tmp = "", o; 27 | const pos_eq = a.indexOf("="); 28 | if (pos_eq > 0) { 29 | o = a.substring(2, pos_eq); 30 | arg = a.substring(pos_eq + 1); 31 | } else o = a.substring(2); 32 | for (let i = 0; i < longopts.length; ++i) { 33 | let y = longopts[i]; 34 | if (y[y.length - 1] == "=") y = y.substring(0, y.length - 1); 35 | if (o.length <= y.length && o == y.substring(0, o.length)) { 36 | k = i, tmp = y; 37 | ++c; // c is the number of matches 38 | if (o == y) { // exact match 39 | c = 1; 40 | break; 41 | } 42 | } 43 | } 44 | if (c == 1) { // find a unique match 45 | lopt = tmp; 46 | if (pos_eq < 0 && longopts[k][longopts[k].length-1] == "=" && cur + 1 < argv.length) { 47 | arg = argv[cur+1]; 48 | argv.delete_at(cur + 1); 49 | } 50 | } 51 | } else { // a short option 52 | if (pos == 0) pos = 1; 53 | opt = a[pos++]; 54 | let k = ostr.indexOf(opt); 55 | if (k < 0) { 56 | opt = "?"; 57 | } else if (k + 1 < ostr.length && ostr[k+1] == ":") { // requiring an argument 58 | if (pos >= a.length) { 59 | arg = argv[cur+1]; 60 | argv.delete_at(cur + 1); 61 | } else arg = a.substring(pos); 62 | pos = -1; 63 | } 64 | } 65 | if (pos < 0 || pos >= argv[cur].length) { 66 | argv.delete_at(cur); 67 | pos = 0; 68 | } 69 | if (lopt != "") yield { opt: `--${lopt}`, arg: arg }; 70 | else if (opt != "?") yield { opt: `-${opt}`, arg: arg }; 71 | else yield { opt: "?", arg: "" }; 72 | } 73 | } 74 | 75 | /****************** 76 | * Interval query * 77 | ******************/ 78 | 79 | function iit_index(a) { 80 | if (a.length == 0) return -1; 81 | a.sort(function(x, y) { return x[0] - y[0] }); 82 | let last, last_i, k; 83 | for (let i = 0; i < a.length; i += 2) last = a[i][2] = a[i][1], last_i = i; 84 | for (k = 1; 1<>k&1? last_i - (1<<(k-1)) : last_i + (1<<(k-1)); 94 | if (last_i < a.length) last = last > a[last_i][2]? last : a[last_i][2]; 95 | } 96 | return k - 1; 97 | } 98 | 99 | function iit_overlap(a, st, en) { 100 | let h = 0, stack = [], b = []; 101 | for (h = 0; 1<> h << h; 109 | let i1 = i0 + (1<<(h+1)) - 1; 110 | if (i1 >= a.length) i1 = a.length; 111 | for (let i = i0; i < i1 && a[i][0] < en; ++i) 112 | if (st < a[i][1]) b.push(a[i]); 113 | } else if (w == 0) { // if left child not processed 114 | stack.push([x, h, 1]); 115 | const y = x - (1<<(h-1)); 116 | if (y >= a.length || a[y][2] > st) 117 | stack.push([y, h - 1, 0]); 118 | } else if (x < a.length && a[x][0] < en) { 119 | if (st < a[x][1]) b.push(a[x]); 120 | stack.push([x + (1<<(h-1)), h - 1, 0]); 121 | } 122 | } 123 | return b; 124 | } 125 | 126 | /**************** 127 | * FASTX reader * 128 | ****************/ 129 | 130 | Fastx = function(f) { 131 | this._file = f; 132 | this._last = 0; 133 | this._line = new Bytes(); 134 | this._finished = false; 135 | this.s = new Bytes(); 136 | this.q = new Bytes(); 137 | this.n = new Bytes(); 138 | this.c = new Bytes(); 139 | } 140 | 141 | Fastx.prototype.read = function() { 142 | var c, f = this._file, line = this._line; 143 | if (this._last == 0) { // then jump to the next header line 144 | while ((c = f.read()) != -1 && c != 62 && c != 64); 145 | if (c == -1) return -1; // end of file 146 | this._last = c; 147 | } // else: the first header char has been read in the previous call 148 | this.c.length = this.s.length = this.q.length = 0; 149 | if ((c = f.readline(this.n, 0)) < 0) return -1; // normal exit: EOF 150 | if (c != 10) f.readline(this.c); // read FASTA/Q comment 151 | if (this.s.capacity == 0) this.s.capacity = 256; 152 | while ((c = f.read()) != -1 && c != 62 && c != 43 && c != 64) { 153 | if (c == 10) continue; // skip empty lines 154 | this.s.set(c); 155 | f.readline(this.s, 2, this.s.length); // read the rest of the line 156 | } 157 | if (c == 62 || c == 64) this._last = c; // the first header char has been read 158 | if (c != 43) return this.s.length; // FASTA 159 | this.q.capacity = this.s.capacity; 160 | c = f.readline(this._line); // skip the rest of '+' line 161 | if (c < 0) return -2; // error: no quality string 162 | var size = this.s.length; 163 | while (f.readline(this.q, 2, this.q.length) >= 0 && this.q.length < size); 164 | f._last = 0; // we have not come to the next header line 165 | if (this.q.length != size) return -2; // error: qual string is of a different length 166 | return size; 167 | } 168 | 169 | /************ 170 | * AVL tree * 171 | ************/ 172 | 173 | class AVLnode { 174 | constructor(data) { 175 | this.data = data; 176 | this.p = [null, null]; 177 | this.balance = 0; 178 | } 179 | } 180 | 181 | class AVLiter { 182 | constructor(tree) { 183 | this.tree = tree; 184 | this.stack = []; 185 | } 186 | #next_bidir(dir) { 187 | if (this.stack.length == 0) return null; 188 | dir = dir == 0? 0 : 1; 189 | let p = this.stack[this.stack.length-1].p[dir]; 190 | if (p != null) { // go down 191 | for (; p != null; p = p.p[1 - dir]) 192 | this.stack.push(p); 193 | return true; 194 | } else { // go up 195 | do { 196 | p = this.stack.pop(); 197 | } while (this.stack.length > 0 && p == this.stack[this.stack.length - 1].p[dir]); 198 | return (this.stack.length > 0); 199 | } 200 | } 201 | get() { return this.stack.length? this.stack[this.stack.length - 1].data : null; } 202 | next() { return this.#next_bidir(1); } 203 | prev() { return this.#next_bidir(0); } 204 | } 205 | 206 | class AVLtree { 207 | constructor(cmp) { 208 | this.root = null; 209 | this.cmp = cmp; 210 | this.size = 0; 211 | } 212 | find(d) { 213 | let p = this.root; 214 | while (p != null) { 215 | const cmp = this.cmp(d, p.data); 216 | if (cmp < 0) p = p.p[0]; 217 | else if (cmp > 0) p = p.p[1]; 218 | else return p; 219 | } 220 | return null; 221 | } 222 | find_itr(d) { 223 | let itr = new AVLiter(this); 224 | let p = this.root; 225 | const has_d = typeof d == "undefined" || d == null? false : true; 226 | while (p != null) { 227 | itr.stack.push(p); 228 | const cmp = has_d? this.cmp(d, p.data) : -1; 229 | if (cmp < 0) p = p.p[0]; 230 | else if (cmp > 0) p = p.p[1]; 231 | else break; 232 | } 233 | return itr; 234 | } 235 | interval(d) { 236 | let p = this.root, l = null, u = null; 237 | while (p != null) { 238 | const cmp = this.cmp(d, p.data); 239 | if (cmp < 0) u = p, p = p.p[0]; 240 | else if (cmp > 0) l = p, p = p.p[1]; 241 | else { l = u = p; break; } 242 | } 243 | return [l, p, u]; 244 | } 245 | #rotate1(p, dir) { // one rotation: (a,(b,c)q)p => ((a,b)p,c)q 246 | const opp = 1 - dir; 247 | let q = p.p[opp]; 248 | p.p[opp] = q.p[dir]; 249 | q.p[dir] = p; 250 | return q; 251 | } 252 | #rotate2(p, dir) { // two rotations: (a,((b,c)r,d)q)p => ((a,b)p,(c,d)q)r 253 | const opp = 1 - dir; 254 | let q = p.p[opp], r = q.p[dir]; 255 | p.p[opp] = r.p[dir]; 256 | r.p[dir] = p; 257 | q.p[dir] = r.p[opp]; 258 | r.p[opp] = q; 259 | const b1 = dir == 0? +1 : -1; 260 | if (r.balance == b1) q.balance = 0, p.balance = -b1; 261 | else if (r.balance == 0) q.balance = p.balance = 0; 262 | else q.balance = b1, p.balance = 0; 263 | r.balance = 0; 264 | return r; 265 | } 266 | insert(d) { 267 | let stack = [], path = []; 268 | let bp = this.root, bq = null; 269 | let which = 0, q = bq; 270 | for (let p = bp; p; q = p, p = p.p[which]) { // find the insertion location 271 | const cmp = this.cmp(d, p.data); 272 | if (cmp == 0) return p; // the data is in the tree; insert nothing 273 | if (p.balance != 0) 274 | bq = q, bp = p, stack.length = 0; 275 | which = cmp > 0? 1 : 0; 276 | stack.push(which); 277 | path.push(p); 278 | } 279 | ++this.size; 280 | let x = new AVLnode(d); // create a new node to insert 281 | if (q == null) this.root = x; 282 | else q.p[which] = x; 283 | if (bp == null) return x; 284 | for (let p = bp, i = 0; p != x; p = p.p[stack[i]], ++i) { // update balance factors 285 | if (stack[i] == 0) --p.balance; 286 | else ++p.balance; 287 | } 288 | if (bp.balance > -2 && bp.balance < 2) return x; // no re-balance needed 289 | // re-balance 290 | let r; 291 | which = bp.balance < 0? 1 : 0; 292 | const b1 = which == 0? +1 : -1; 293 | q = bp.p[1 - which]; 294 | if (q.balance == b1) { 295 | r = this.#rotate1(bp, which); 296 | q.balance = bp.balance = 0; 297 | } else r = this.#rotate2(bp, which); 298 | if (bq == null) this.root = r; 299 | else bq.p[bp != bq.p[0]? 1 : 0] = r; 300 | return x; 301 | } 302 | erase(d) { 303 | let path = [], dir = []; 304 | let fake = new AVLnode(null); 305 | fake.p[0] = this.root; 306 | let p = fake; 307 | if (d != null) { 308 | for (let cmp = -1; cmp != 0; cmp = this.cmp(d, p.data)) { 309 | const which = cmp > 0? 1 : 0; 310 | dir.push(which); 311 | path.push(p); 312 | p = p.p[which]; 313 | if (p == null) return null; // nothing to delete: not present in the tree 314 | } 315 | } else { 316 | for (; p != null; p = p.p[0]) { 317 | dir.push(0); 318 | path.push(p); 319 | } 320 | p = path.pop(); 321 | } 322 | --this.size; 323 | if (p.p[1] == null) { // ((1,.)2,3)4 => (1,3)4; p=2 324 | path[path.length-1].p[dir[dir.length-1]] = p.p[0]; 325 | } else { 326 | let q = p.p[1]; 327 | if (q.p[0] == null) { // ((1,2)3,4)5 => ((1)2,4)5; p=3 328 | q.p[0] = p.p[0]; 329 | q.balance = p.balance; 330 | path[path.length-1].p[dir[dir.length-1]] = q; 331 | path.push(q); 332 | dir.push(1); 333 | } else { // ((1,((.,2)3,4)5)6,7)8 => ((1,(2,4)5)3,7)8; p=6 334 | let r, e = path.length; 335 | for (;;) { 336 | dir.push(0); 337 | path.push(q); 338 | r = q.p[0]; 339 | if (r.p[0] == null) break; 340 | q = r; 341 | } 342 | r.p[0] = p.p[0]; 343 | q.p[0] = r.p[1]; 344 | r.p[1] = p.p[1]; 345 | r.balance = p.balance; 346 | path[e-1].p[dir[e-1]] = r; 347 | // the following two lines are suggested by Gemini 3; see avl-fix/* for details 348 | path.splice(e, 0, r); 349 | dir.splice(e, 0, 1); 350 | } 351 | } 352 | while (path.length > 0) { 353 | let q = path.pop(); 354 | let which = dir.pop(), other = 1 - which; 355 | let b1 = 1, b2 = 2; 356 | if (which) b1 = -b1, b2 = -b2; 357 | q.balance += b1; 358 | if (q.balance == b1) break; 359 | else if (q.balance == b2) { 360 | let r = q.p[other]; 361 | if (r.balance == -b1) { 362 | path[path.length-1].p[dir[dir.length-1]] = this.#rotate2(q, which); 363 | } else { 364 | path[path.length-1].p[dir[dir.length-1]] = this.#rotate1(q, which); 365 | if (r.balance == 0) { 366 | r.balance = -b1; 367 | q.balance = b1; 368 | break; 369 | } else r.balance = q.balance = 0; 370 | } 371 | } 372 | } 373 | this.root = fake.p[0]; 374 | return p; 375 | } 376 | } 377 | 378 | /******************** 379 | * Simpler File I/O * 380 | ********************/ 381 | 382 | function* k8_readline(fn) { 383 | let buf = new Bytes(); 384 | let file = new File(fn); 385 | while (file.readline(buf) >= 0) { 386 | yield buf.toString(); 387 | } 388 | file.close(); 389 | buf.destroy(); 390 | } 391 | -------------------------------------------------------------------------------- /scripts-old/k8tk.js: -------------------------------------------------------------------------------- 1 | /*************************** 2 | *** k8 library routines *** 3 | ***************************/ 4 | 5 | /////////// General /////////// 6 | 7 | var getopt = function(args, ostr) { 8 | var oli; // option letter list index 9 | if (typeof(getopt.place) == 'undefined') 10 | getopt.ind = 0, getopt.arg = null, getopt.place = -1; 11 | if (getopt.place == -1) { // update scanning pointer 12 | if (getopt.ind >= args.length || args[getopt.ind].charAt(getopt.place = 0) != '-') { 13 | getopt.place = -1; 14 | return null; 15 | } 16 | if (getopt.place + 1 < args[getopt.ind].length && args[getopt.ind].charAt(++getopt.place) == '-') { // found "--" 17 | ++getopt.ind; 18 | getopt.place = -1; 19 | return null; 20 | } 21 | } 22 | var optopt = args[getopt.ind].charAt(getopt.place++); // character checked for validity 23 | if (optopt == ':' || (oli = ostr.indexOf(optopt)) < 0) { 24 | if (optopt == '-') return null; // if the user didn't specify '-' as an option, assume it means null. 25 | if (getopt.place < 0) ++getopt.ind; 26 | return '?'; 27 | } 28 | if (oli+1 >= ostr.length || ostr.charAt(++oli) != ':') { // don't need argument 29 | getopt.arg = null; 30 | if (getopt.place < 0 || getopt.place >= args[getopt.ind].length) ++getopt.ind, getopt.place = -1; 31 | } else { // need an argument 32 | if (getopt.place >= 0 && getopt.place < args[getopt.ind].length) 33 | getopt.arg = args[getopt.ind].substr(getopt.place); 34 | else if (args.length <= ++getopt.ind) { // no arg 35 | getopt.place = -1; 36 | if (ostr.length > 0 && ostr.charAt(0) == ':') return ':'; 37 | return '?'; 38 | } else getopt.arg = args[getopt.ind]; // white space 39 | getopt.place = -1; 40 | ++getopt.ind; 41 | } 42 | return optopt; 43 | } 44 | 45 | /////////// Interval manipulation /////////// 46 | 47 | Interval = {}; 48 | 49 | Interval.sort = function(a) 50 | { 51 | if (typeof a[0] == 'number') 52 | a.sort(function(x, y) { return x - y }); 53 | else a.sort(function(x, y) { return x[0] - y[0] }); 54 | } 55 | 56 | Interval.merge = function(a, to_srt) 57 | { 58 | if (typeof to_srt == 'undefined') to_srt = true; 59 | if (to_srt) Interval.sort(a); 60 | var k = 0; 61 | for (var i = 1; i < a.length; ++i) { 62 | if (a[k][1] >= a[i][0]) 63 | a[k][1] = a[k][1] > a[i][1]? a[k][1] : a[i][1]; 64 | else a[++k] = a[i].slice(0); 65 | } 66 | a.length = k + 1; 67 | } 68 | 69 | Interval.find_intv = function(a, x) 70 | { 71 | var left = -1, right = a.length; 72 | if (typeof a[0] == 'number') { 73 | while (right - left > 1) { 74 | var mid = left + ((right - left) >> 1); 75 | if (a[mid] > x) right = mid; 76 | else if (a[mid] < x) left = mid; 77 | else return mid; 78 | } 79 | } else { 80 | while (right - left > 1) { 81 | var mid = left + ((right - left) >> 1); 82 | if (a[mid][0] > x) right = mid; 83 | else if (a[mid][0] < x) left = mid; 84 | else return mid; 85 | } 86 | } 87 | return left; 88 | } 89 | 90 | Interval.trans_coor = function(a, x) 91 | { 92 | var k = Interval.find_intv(a, x); 93 | if (k < 0) return a[0][1]; 94 | if (k == a.length - 1) return a[a.length - 1][1]; 95 | return a[k][1] + (a[k+1][1] - a[k][1]) * ((x - a[k][0]) / (a[k+1][0] - a[k][0])); 96 | } 97 | 98 | Interval.overlap_len = function(a, st, en) 99 | { 100 | if (en <= a[0][0] || st >= a[a.length-1][1]) return 0; 101 | var k_st = Interval.find_intv(a, st); 102 | var k_en = Interval.find_intv(a, en); 103 | var len = 0; 104 | if (k_st == k_en) { 105 | if (st < a[k_st][1]) 106 | len = (a[k_en][1] < en? a[k_en][1] : en) - st; 107 | } else { 108 | len = k_st < 0 || st >= a[k_st][1]? 0 : a[k_st][1] - st; 109 | for (var k = k_st + 1; k < k_en; ++k) 110 | len += a[k][1] - a[k][0]; 111 | len += (a[k_en][1] < en? a[k_en][1] : en) - a[k_en][0]; 112 | } 113 | return len; 114 | } 115 | 116 | Interval.overlap_list = function(a, st, en) 117 | { 118 | if (en <= a[0][0] || st >= a[a.length-1][1]) return []; 119 | var k_st = Interval.find_intv(a, st); 120 | var k_en = Interval.find_intv(a, en); 121 | var o = []; 122 | if (k_st == k_en) { 123 | if (st < a[k_st][1]) 124 | o.push([(a[k_en][1] < en? a[k_en][1] : en) - st].concat(a[k_en].slice(2))); 125 | } else { 126 | if (k_st >= 0 && st < a[k_st][1]) 127 | o.push([a[k_st][1] - st].concat(a[k_st].slice(2))); 128 | for (var k = k_st + 1; k < k_en; ++k) 129 | o.push([a[k][1] - a[k][0]].concat(a[k].slice(2))); 130 | o.push([(a[k_en][1] < en? a[k_en][1] : en) - a[k_en][0]].concat(a[k_en].slice(2))); 131 | } 132 | return o; 133 | } 134 | 135 | /////////// Numerical /////////// 136 | 137 | Math.spearman = function(a) 138 | { 139 | function aux_func(t) { return t == 1? 0 : (t * t - 1) * t / 12; } 140 | var x = [], T = [], S = []; 141 | for (var i = 0; i < a.length; ++i) 142 | x[i] = [a[i][0], a[i][1], 0, 0]; 143 | for (var k = 0; k < 2; ++k) { 144 | x.sort(function(a,b){return a[k]-b[k]}); 145 | var same = 1; 146 | T[k] = 0; 147 | for (var i = 1; i <= x.length; ++i) { 148 | if (i < x.length && x[i-1][k] == x[i][k]) ++same; 149 | else { 150 | var rank = (i<<1) - same + 1; 151 | for (var j = i - same; j < i; ++j) x[j][k+2] = rank; 152 | if (same > 1) T[k] += aux_func(same), same = 1; 153 | } 154 | } 155 | S[k] = aux_func(x.length) - T[k]; 156 | } 157 | var d2 = 0.; 158 | for (var i = 0; i < x.length; ++i) 159 | d2 += .25 * (x[i][2] - x[i][3]) * (x[i][2] - x[i][3]); 160 | return .5 * (S[0] + S[1] - d2) / Math.sqrt(S[0] * S[1]); 161 | } 162 | 163 | Math.kernel_smooth = function(a, radius, bound, func) 164 | { 165 | if (a.length == 0) return null; 166 | if (typeof func == "undefined" || func == null) 167 | func = function(x) { return x > -1 && x < 1? .75 * (1 - x * x) : 0; } // Epanechnikov 168 | a.sort(function(x,y) { return x[0]-y[0] }); 169 | if (typeof bound != "undefined") 170 | bound[0] = a[0][0], bound[1] = a[a.length-1][0]; 171 | var r1 = 1.0 / radius; 172 | return function(x) { 173 | // binary search 174 | var L = 0, R = a.length - 1; 175 | while (R - L > 1) { 176 | var m = Math.floor((L + R) / 2); 177 | if (a[m][0] < x) L = m + 1; 178 | else if (a[m][0] > x) R = m - 1; 179 | else { 180 | L = R = m; 181 | break; 182 | } 183 | } 184 | // smooth 185 | var b = []; 186 | for (var j = 0; j < a[0].length; ++j) b[j] = 0; 187 | for (var i = L; i < a.length && a[i][0] - x <= radius; ++i) { 188 | var w = func((x - a[i][0]) * r1); 189 | b[0] += w; 190 | for (var j = 1; j < a[i].length; ++j) 191 | b[j] += w * a[i][j]; 192 | } 193 | for (var i = L - 1; i >= 0 && x - a[i][0] <= radius; --i) { 194 | var w = func((x - a[i][0]) * r1); 195 | b[0] += w; 196 | for (var j = 1; j < a[i].length; ++j) 197 | b[j] += w * a[i][j]; 198 | } 199 | return b; 200 | } 201 | } 202 | 203 | /************************* 204 | *** k8tk applications *** 205 | *************************/ 206 | 207 | function k8_spearman(args) 208 | { 209 | var c, col1 = 1, col2 = 2, missing = "NA"; 210 | while ((c = getopt(args, "1:2:m:")) != null) { 211 | if (c == '1') col1 = parseInt(getopt.arg); 212 | else if (c == '2') col2 = parseInt(getopt.arg); 213 | else if (c == 'm') missing = getopt.arg; 214 | } 215 | if (args.length == getopt.ind) { 216 | print("Usage: k8 k8tk.js spearman [-1 col1] [-2 col2] "); 217 | return 1; 218 | } 219 | --col1, --col2; 220 | 221 | var buf = new Bytes(); 222 | var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]); 223 | var a = []; 224 | while (file.readline(buf) >= 0) { 225 | var t = buf.toString().split("\t"); 226 | if (t.length <= col1 || t.length <= col2) continue; 227 | if (t[col1] == missing || t[col2] == missing) continue; 228 | a.push([parseFloat(t[col1]), parseFloat(t[col2])]); 229 | } 230 | file.close(); 231 | buf.destroy(); 232 | print(Math.spearman(a)); 233 | return 0; 234 | } 235 | 236 | function k8_ksmooth(args) 237 | { 238 | var c, col_g = null, col_x = 0, cols = [], radius = 1, precision = 6, missing = "NA", step = null, use_uniform = false; 239 | while ((c = getopt(args, "r:g:x:y:p:s:u")) != null) { 240 | if (c == 'r') radius = parseFloat(getopt.arg); 241 | else if (c == 'g') col_g = parseInt(getopt.arg) - 1; 242 | else if (c == 'x') col_x = parseInt(getopt.arg) - 1; 243 | else if (c == 'p') precision = parseInt(getopt.arg); 244 | else if (c == 'u') use_uniform = true; 245 | else if (c == 's') step = parseInt(getopt.arg); 246 | else if (c == 'y') { 247 | var m, s = getopt.arg.split(","); 248 | for (var i = 0; i < s.length; ++i) { 249 | if ((m = /^(\d+)$/.exec(s[i])) != null) 250 | cols.push(parseInt(m[1]) - 1); 251 | else if ((m = /^(\d+)-(\d+)$/.exec(s[i])) != null) { 252 | var st = parseInt(m[1]) - 1, en = parseInt(m[2]); 253 | for (var j = st; j < en; ++j) 254 | cols.push(j); 255 | } 256 | } 257 | } 258 | } 259 | if (args.length == getopt.ind) { 260 | print("Usage: k8 k8tk.js ksmooth [options] "); 261 | print("Options:"); 262 | print(" -r FLOAT radius [1]"); 263 | print(" -g INT group column []"); 264 | print(" -x INT x column [1]"); 265 | print(" -y STR y column(s) [2]"); 266 | print(" -u use uniform kernel [Epanechnikov]"); 267 | print(" -s INT step []"); 268 | print(" -p INT precision [6]"); 269 | return 1; 270 | } 271 | if (cols.length == 0) cols = [1]; 272 | cols.unshift(col_x); 273 | 274 | var buf = new Bytes(); 275 | var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]); 276 | var group = {}, list = [], list_key = []; 277 | while (file.readline(buf) >= 0) { 278 | var t = buf.toString().split("\t"); 279 | var key = col_g == null? "*" : t[col_g]; 280 | if (group[key] == null) { 281 | group[key] = []; 282 | list_key.push(key); 283 | } 284 | var b = []; 285 | for (var i = 0; i < cols.length; ++i) { 286 | if (t[cols[i]] == missing) break; 287 | b.push(parseFloat(t[cols[i]])); 288 | } 289 | list.push([key, b[0]]); 290 | if (b.length < cols.length) continue; 291 | group[key].push(b); 292 | } 293 | file.close(); 294 | buf.destroy(); 295 | 296 | function kernel_uniform(x) { return x >= -1 && x < 1? 0.5 : 0 } 297 | 298 | var smooth = {}, bound = {}; 299 | for (var key in group) { 300 | bound[key] = []; 301 | smooth[key] = Math.kernel_smooth(group[key], radius, bound[key], use_uniform? kernel_uniform : null); 302 | } 303 | if (step != null) { 304 | for (var k = 0; k < list_key.length; ++k) { 305 | var key = list_key[k], st = bound[key][0], en = bound[key][1]; 306 | for (var x = st + (step>>1); x < en - (step>>1); x += step) { 307 | var b = smooth[key](x); 308 | var out = []; 309 | if (col_g != null) out.push(key); 310 | out.push(x); 311 | for (var j = 1; j < b.length; ++j) 312 | out.push(b[0] > 0? (b[j] / b[0]).toFixed(precision) : "NA"); 313 | print(out.join("\t")); 314 | } 315 | } 316 | } else { 317 | for (var i = 0; i < list.length; ++i) { 318 | var b = smooth[list[i][0]](list[i][1]); 319 | var out = []; 320 | if (col_g != null) out.push(list[i][0]); 321 | out.push(list[i][1]); 322 | for (var j = 1; j < b.length; ++j) 323 | out.push(b[0] > 0? (b[j] / b[0]).toFixed(precision) : "NA"); 324 | print(out.join("\t")); 325 | } 326 | } 327 | return 0; 328 | } 329 | 330 | function k8_binavg(args) 331 | { 332 | var c, col_srt = 0, col_thres = -1, thres = 10, precision = 6; 333 | while ((c = getopt(args, "s:t:m:p:")) != null) { 334 | if (c == 's') col_srt = parseInt(getopt.arg) - 1; 335 | else if (c == 't') col_thres = parseInt(getopt.arg) - 1; 336 | else if (c == 'm') thres = parseFloat(getopt.arg); 337 | else if (c == 'p') precision = parseInt(getopt.arg); 338 | } 339 | if (args.length == getopt.ind) { 340 | print("Usage: k8 k8tk.js binavg [-s colSrt] [-t colThres] [-m minThres] "); 341 | return 1; 342 | } 343 | 344 | var buf = new Bytes(); 345 | var file = args[getopt.ind] == '-'? new File() : new File(args[getopt.ind]); 346 | var n_col = null, a = []; 347 | while (file.readline(buf) >= 0) { 348 | var t = buf.toString().split("\t"); 349 | if (n_col == null) n_col = t.length; 350 | else if (n_col != t.length) throw Error("ERROR: lines with a different number of fields: " + n_col + " != " + t.length); 351 | for (var i = 0; i < t.length; ++i) 352 | t[i] = parseFloat(t[i]); 353 | a.push(t); 354 | } 355 | file.close(); 356 | buf.destroy(); 357 | 358 | a.sort(function(x, y) { return x[col_srt] - y[col_srt] }); 359 | var sum = [], n = 0; 360 | for (var j = 0; j < n_col; ++j) sum[j] = 0; 361 | for (var i = 0; i < a.length; ++i) { 362 | for (var j = 0; j < n_col; ++j) 363 | sum[j] += a[i][j]; 364 | ++n; 365 | if ((col_thres < 0 && n >= Math.floor(thres + .499)) || (col_thres >= 0 && sum[col_thres] >= thres)) { 366 | for (var j = 0; j < n_col; ++j) 367 | sum[j] = (sum[j] / n).toFixed(precision); 368 | print(sum.join("\t")); 369 | for (var j = 0; j < n_col; ++j) sum[j] = 0; 370 | n = 0; 371 | } 372 | } 373 | if ((col_thres < 0 && n >= Math.floor(thres/2 + .499)) || (col_thres >= 0 && sum[col_thres] >= thres/2)) { 374 | for (var j = 0; j < n_col; ++j) 375 | sum[j] = (sum[j] / n).toFixed(precision); 376 | print(sum.join("\t")); 377 | } 378 | return 0; 379 | } 380 | 381 | /////////// Bioinformatics /////////// 382 | 383 | function k8_markmut(args) 384 | { 385 | if (args.length < 2) { 386 | print("Usage: k8 k8tk.js markmut "); 387 | return 1; 388 | } 389 | 390 | var A = "A".charCodeAt(0), C = "C".charCodeAt(0); 391 | var G = "G".charCodeAt(0), T = "T".charCodeAt(0); 392 | 393 | var file, buf = new Bytes(); 394 | 395 | warn("Reading the reference genome..."); 396 | var seqs = {}, seq = null; 397 | file = new File(args[0]); 398 | while (file.readline(buf) >= 0) { 399 | if (buf[0] == 62) { 400 | var m, line = buf.toString(); 401 | if ((m = /^>(\S+)/.exec(line)) == null) 402 | throw Error("ERROR: wrong FASTA format"); 403 | seq = seqs[m[1]] = new Bytes(); 404 | } else seq.set(buf); 405 | } 406 | file.close(); 407 | 408 | warn("Processing the list..."); 409 | file = new File(args[1]); 410 | while (file.readline(buf) >= 0) { 411 | var t = buf.toString().split("\t"); 412 | var type = []; 413 | var x = parseInt(t[1]) - 1; 414 | var seq = seqs[t[0]]; 415 | if (seq != null && x >= 0 && x < seq.length) { 416 | var ref = t[2], alt = t[3].split(","); 417 | for (var j = 0; j < alt.length; ++j) { 418 | if (ref.length > alt[j].length) type.push("del"); 419 | else if (ref.length < alt[j].length) type.push("ins"); 420 | else if (ref.length > 1) type.push("mnp"); 421 | else { 422 | var mut = ref < alt[j]? ref + alt[j] : alt[j] + ref; 423 | if (mut == "AG" || mut == "CT") { 424 | var is_cpg = false; 425 | if (mut == "AG" && x > 0 && seq[x-1] == C) is_cpg = true; 426 | else if (mut == "CT" && x < seq.length - 1 && seq[x+1] == G) is_cpg = true; 427 | type.push(is_cpg? "ts_cpg" : "ts_non_cpg"); 428 | } else type.push("tv"); 429 | } 430 | } 431 | } 432 | print(t.join("\t"), type.length? type.join(",") : "NA"); 433 | } 434 | buf.destroy(); 435 | file.close(); 436 | } 437 | 438 | function k8_bedmerge(args) 439 | { 440 | var buf = new Bytes(); 441 | var file = args.length > 0? new File(args[0]) : new File(); 442 | var ch = null, st, en; 443 | while (file.readline(buf) >= 0) { 444 | var t = buf.toString().split("\t", 3); 445 | var s = parseInt(t[1]); 446 | var e = parseInt(t[2]); 447 | if (ch != t[0] || s > en) { // no overlap 448 | if (ch != null) print(ch, st, en); 449 | ch = t[0], st = s, en = e; 450 | } else if (s < st) throw Error("ERROR: input is not sorted by coordinate"); 451 | else en = en > e? en : e; 452 | } 453 | if (ch != null) print(ch, st, en); 454 | file.close(); 455 | buf.destroy(); 456 | return 0; 457 | } 458 | 459 | function k8_bedovlp(args) 460 | { 461 | var c, min_len = 1, print_olen = false, skip_char = '#', print_hdr = false; 462 | while ((c = getopt(args, "pHS:l:")) != null) { 463 | if (c == 'l') min_len = /^(\d+)$/.test(getopt.arg)? parseInt(getopt.arg) : null; 464 | else if (c == 'p') print_olen = true; 465 | else if (c == 'S') skip_char = getopt.arg; 466 | else if (c == 'H') print_hdr = true; 467 | } 468 | if (args.length - getopt.ind < 2) { 469 | print("Usage: k8 k8tk.js bedovlp [options] "); 470 | print("Options:"); 471 | print(" -l INT min overlap length, or 'c' for contained [1]"); 472 | print(" -S STR characters marking header lines [#]"); 473 | print(" -p print the overlap length as the last field on each line"); 474 | print(" -H print header lines"); 475 | return 1; 476 | } 477 | 478 | var file, buf = new Bytes(); 479 | 480 | var bed = {}; 481 | file = new File(args[getopt.ind]); 482 | while (file.readline(buf) >= 0) { 483 | var t = buf.toString().split("\t", 3); 484 | if (t.length < 2) continue; 485 | if (bed[t[0]] == null) bed[t[0]] = []; 486 | var st = parseInt(t[1]), en = null; 487 | if (t.length >= 3 && /^(\d+)$/.test(t[2])) 488 | en = parseInt(t[2]); 489 | if (en == null) en = st--; 490 | bed[t[0]].push([st, en]); 491 | } 492 | file.close(); 493 | for (var key in bed) 494 | Interval.merge(bed[key]); 495 | 496 | file = new File(args[getopt.ind+1]); 497 | while (file.readline(buf) >= 0) { 498 | var line = buf.toString(); 499 | var len = 0, t = line.split("\t", 3); 500 | var is_hdr = skip_char.indexOf(t[0].charAt(0)) < 0? false : true; 501 | if (!is_hdr && bed[t[0]] != null) { 502 | var st = parseInt(t[1]), en = parseInt(t[2]); 503 | len = Interval.overlap_len(bed[t[0]], st, en); 504 | } 505 | if (print_olen) { 506 | if (is_hdr) print(line); 507 | else print(line, len); 508 | } else { 509 | if (is_hdr && print_hdr) print(line); 510 | else if (!is_hdr) { 511 | var l = min_len != null && min_len < en - st? min_len : en - st; 512 | if (len >= l) print(line); 513 | } 514 | } 515 | } 516 | file.close(); 517 | 518 | buf.destroy(); 519 | return 0; 520 | } 521 | 522 | function main(args) 523 | { 524 | if (args.length == 0) { 525 | print("Usage: k8 k8tk.js "); 526 | print("Commands:"); 527 | print(" Numerical:"); 528 | print(" spearman Spearman correlation"); 529 | print(" ksmooth Kernel smoothing"); 530 | print(" binavg Binned average"); 531 | print(" Bioinformatics:"); 532 | print(" markmut Mark mutation type (e.g. ts/tv/cpg/etc)"); 533 | print(" bedmerge Merge overlaps in sorted BED"); 534 | print(" bedovlp Overlap length between two BED files"); 535 | return 1; 536 | } 537 | var cmd = args.shift(); 538 | if (cmd == "spearman") return k8_spearman(args); 539 | else if (cmd == "ksmooth") return k8_ksmooth(args); 540 | else if (cmd == "binavg") return k8_binavg(args); 541 | else if (cmd == "markmut") return k8_markmut(args); 542 | else if (cmd == "bedmerge") return k8_bedmerge(args); 543 | else if (cmd == "bedovlp") return k8_bedovlp(args); 544 | else { 545 | throw Error("ERROR: unknown command '" + cmd + "'"); 546 | } 547 | return 0; 548 | } 549 | 550 | exit(main(arguments)); 551 | -------------------------------------------------------------------------------- /scripts-old/k8.js: -------------------------------------------------------------------------------- 1 | /********************************************* 2 | * getopt(): translated from the BSD version * 3 | *********************************************/ 4 | 5 | var getopt = function(args, ostr) { 6 | var oli; // option letter list index 7 | if (typeof(getopt.place) == 'undefined') 8 | getopt.ind = 0, getopt.arg = null, getopt.place = -1; 9 | if (getopt.place == -1) { // update scanning pointer 10 | if (getopt.ind >= args.length || args[getopt.ind].charAt(getopt.place = 0) != '-') { 11 | getopt.place = -1; 12 | return null; 13 | } 14 | if (getopt.place + 1 < args[getopt.ind].length && args[getopt.ind].charAt(++getopt.place) == '-') { // found "--" 15 | ++getopt.ind; 16 | getopt.place = -1; 17 | return null; 18 | } 19 | } 20 | var optopt = args[getopt.ind].charAt(getopt.place++); // character checked for validity 21 | if (optopt == ':' || (oli = ostr.indexOf(optopt)) < 0) { 22 | if (optopt == '-') return null; // if the user didn't specify '-' as an option, assume it means null. 23 | if (getopt.place < 0) ++getopt.ind; 24 | return '?'; 25 | } 26 | if (oli+1 >= ostr.length || ostr.charAt(++oli) != ':') { // don't need argument 27 | getopt.arg = null; 28 | if (getopt.place < 0 || getopt.place >= args[getopt.ind].length) ++getopt.ind, getopt.place = -1; 29 | } else { // need an argument 30 | if (getopt.place >= 0 && getopt.place < args[getopt.ind].length) 31 | getopt.arg = args[getopt.ind].substr(getopt.place); 32 | else if (args.length <= ++getopt.ind) { // no arg 33 | getopt.place = -1; 34 | if (ostr.length > 0 && ostr.charAt(0) == ':') return ':'; 35 | return '?'; 36 | } else getopt.arg = args[getopt.ind]; // white space 37 | getopt.place = -1; 38 | ++getopt.ind; 39 | } 40 | return optopt; 41 | } 42 | 43 | /* // getopt() example 44 | var c; 45 | while ((c = getopt(arguments, "i:xy")) != null) { 46 | switch (c) { 47 | case 'i': print(getopt.arg); break; 48 | case 'x': print("x"); break; 49 | case 'y': print("y"); break; 50 | } 51 | } 52 | */ 53 | 54 | // print an object in a format similar to JSON 55 | function obj2str(o) 56 | { 57 | if (typeof(o) != 'object') { 58 | return o.toString(); 59 | } else if (o == null) { 60 | return "null"; 61 | } else if (Array.isArray(o)) { 62 | var s = "["; 63 | for (var i = 0; i < o.length; ++i) { 64 | if (i) s += ','; 65 | s += obj2str(o[i]); 66 | } 67 | return s + "]"; 68 | } else { 69 | var i = 0, s = "{"; 70 | for (var key in o) { 71 | if (i++) s += ','; 72 | s += key + ":"; 73 | s += obj2str(o[key]); 74 | } 75 | return s + "}"; 76 | } 77 | } 78 | 79 | /********************* 80 | * Special functions * 81 | *********************/ 82 | 83 | Math.lgamma = function(z) { 84 | var x = 0; 85 | x += 0.1659470187408462e-06 / (z+7); 86 | x += 0.9934937113930748e-05 / (z+6); 87 | x -= 0.1385710331296526 / (z+5); 88 | x += 12.50734324009056 / (z+4); 89 | x -= 176.6150291498386 / (z+3); 90 | x += 771.3234287757674 / (z+2); 91 | x -= 1259.139216722289 / (z+1); 92 | x += 676.5203681218835 / z; 93 | x += 0.9999999999995183; 94 | return Math.log(x) - 5.58106146679532777 - z + (z-0.5) * Math.log(z+6.5); 95 | } 96 | 97 | function _kf_gammap(s, z) 98 | { 99 | var sum, x, k; 100 | for (k = 1, sum = x = 1.; k < 100; ++k) { 101 | sum += (x *= z / (s + k)); 102 | if (x / sum < 1e-290) break; 103 | } 104 | return Math.exp(s * Math.log(z) - z - Math.lgamma(s + 1.) + Math.log(sum)); 105 | } 106 | 107 | function _kf_gammaq(s, z) 108 | { 109 | var C, D, f, KF_TINY = 1e-14; 110 | f = 1. + z - s; C = f; D = 0.; 111 | for (var j = 1; j < 100; ++j) { 112 | var a = j * (s - j), b = (j<<1) + 1 + z - s, d; 113 | D = b + a * D; 114 | if (D < KF_TINY) D = KF_TINY; 115 | C = b + a / C; 116 | if (C < KF_TINY) C = KF_TINY; 117 | D = 1. / D; 118 | d = C * D; 119 | f *= d; 120 | if (Math.abs(d - 1.) < 1e-290) break; 121 | } 122 | return Math.exp(s * Math.log(z) - z - Math.lgamma(s) - Math.log(f)); 123 | } 124 | 125 | Math.gammap = function(s, z) { return z <= 1. || z < s? _kf_gammap(s, z) : 1. - _kf_gammaq(s, z); } 126 | Math.gammaq = function(s, z) { return z <= 1. || z < s? 1. - _kf_gammap(s, z) : _kf_gammaq(s, z); } 127 | Math.chi2 = function(d, x2) { return Math.gammaq(.5 * d, .5 * x2); } 128 | 129 | Math.erfc = function(x) 130 | { 131 | var expntl, z, p; 132 | z = Math.abs(x) * 1.41421356237309504880168872420969808; 133 | if (z > 37.) return x > 0.? 0. : 2.; 134 | expntl = Math.exp(z * z * - .5); 135 | if (z < 7.07106781186547524400844362104849039) // for small z 136 | p = expntl * ((((((.03526249659989109 * z + .7003830644436881) * z + 6.37396220353165) * z + 33.912866078383) * z + 112.0792914978709) * z + 221.2135961699311) * z + 220.2068679123761) 137 | / (((((((.08838834764831844 * z + 1.755667163182642) * z + 16.06417757920695) * z + 86.78073220294608) * z + 296.5642487796737) * z + 637.3336333788311) * z + 793.8265125199484) * z + 440.4137358247522); 138 | else p = expntl / 2.506628274631001 / (z + 1. / (z + 2. / (z + 3. / (z + 4. / (z + .65))))); 139 | return x > 0.? 2. * p : 2. * (1. - p); 140 | } 141 | 142 | Math.normald = function(x) { return .5 * Math.erfc(-x * 0.707106781186547524400844362104849039); } 143 | 144 | Math.rank = function(x) 145 | { 146 | function aux_func(t) { 147 | return t == 1? 0 : (t * t - 1) * t / 12 148 | } 149 | x.sort(function(a,b) { return a-b }); 150 | var same = 1, y = []; 151 | for (var i = 1; i <= x.length; ++i) { 152 | if (i < x.length && x[i-1] == x[i]) ++same; 153 | else { 154 | var rank = (i<<1) - same + 1; 155 | for (var j = i - same; j < i; ++j) y[j] = rank; 156 | if (same > 1) same = 1; 157 | } 158 | } 159 | } 160 | 161 | Math.spearman = function(a) 162 | { 163 | function aux_func(t) { 164 | return t == 1? 0 : (t * t - 1) * t / 12 165 | } 166 | var x = [], T = [], S = []; 167 | for (var i = 0; i < a.length; ++i) 168 | x[i] = [parseFloat(a[i][0]), parseFloat(a[i][1]), 0, 0] 169 | for (var k = 0; k < 2; ++k) { 170 | x.sort(function(a,b){return a[k]-b[k]}); 171 | var same = 1; 172 | T[k] = 0; 173 | for (var i = 1; i <= x.length; ++i) { 174 | if (i < x.length && x[i-1][k] == x[i][k]) ++same; 175 | else { 176 | var rank = (i<<1) - same + 1; 177 | for (var j = i - same; j < i; ++j) x[j][k+2] = rank; 178 | if (same > 1) T[k] += aux_func(same), same = 1; 179 | } 180 | } 181 | S[k] = aux_func(x.length) - T[k]; 182 | } 183 | var sum = 0.; 184 | for (var i = 0; i < x.length; ++i) 185 | sum += .25 * (x[i][2] - x[i][3]) * (x[i][2] - x[i][3]); 186 | return .5 * (S[0] + S[1] - sum) / Math.sqrt(S[0] * S[1]); 187 | } 188 | 189 | Math.fisher_exact = function(n11, n12, n21, n22) 190 | { 191 | function lbinom(n, k) { 192 | if (k == 0 || n == k) return 0; 193 | return Math.lgamma(n+1) - Math.lgamma(k+1) - Math.lgamma(n-k+1); 194 | } 195 | 196 | function hypergeo(n11, n1_, n_1, n) { 197 | return Math.exp(lbinom(n1_, n11) + lbinom(n-n1_, n_1-n11) - lbinom(n, n_1)); 198 | } 199 | 200 | function hypergeo_acc(n11, n1_, n_1, n, aux) { 201 | if (n1_ || n_1 || n) { 202 | aux.n11 = n11; aux.n1_ = n1_; aux.n_1 = n_1; aux.n = n; 203 | } else { // then only n11 changed; the rest fixed 204 | if (n11%11 && n11 + aux.n - aux.n1_ - aux.n_1) { 205 | if (n11 == aux.n11 + 1) { // incremental 206 | aux.p *= (aux.n1_ - aux.n11) / n11 207 | * (aux.n_1 - aux.n11) / (n11 + aux.n - aux.n1_ - aux.n_1); 208 | aux.n11 = n11; 209 | return aux.p; 210 | } 211 | if (n11 == aux.n11 - 1) { // incremental 212 | aux.p *= aux.n11 / (aux.n1_ - n11) 213 | * (aux.n11 + aux.n - aux.n1_ - aux.n_1) / (aux.n_1 - n11); 214 | aux.n11 = n11; 215 | return aux.p; 216 | } 217 | } 218 | aux.n11 = n11; 219 | } 220 | aux.p = hypergeo(aux.n11, aux.n1_, aux.n_1, aux.n); 221 | return aux.p; 222 | } 223 | 224 | var i, j, max, min; 225 | var p, q, left, right, two; 226 | var _aux = { n11:0, n1_:0, n_1:0, n:0, p:0. }; 227 | var n1_, n_1, n; 228 | 229 | n1_ = n11 + n12; n_1 = n11 + n21; n = n11 + n12 + n21 + n22; // calculate n1_, n_1 and n 230 | max = (n_1 < n1_) ? n_1 : n1_; // max n11, for right tail 231 | min = n1_ + n_1 - n; 232 | if (min < 0) min = 0; // min n11, for left tail 233 | if (min == max) return [1., 1., 1.]; // no need to do test 234 | q = hypergeo_acc(n11, n1_, n_1, n, _aux); // the probability of the current table 235 | // left tail 236 | p = hypergeo_acc(min, 0, 0, 0, _aux); 237 | for (left = 0., i = min + 1; p < 0.99999999 * q; ++i) // loop until underflow 238 | left += p, p = hypergeo_acc(i, 0, 0, 0, _aux); 239 | --i; 240 | if (p < 1.00000001 * q) left += p; 241 | else --i; 242 | // right tail 243 | p = hypergeo_acc(max, 0, 0, 0, _aux); 244 | for (right = 0., j = max - 1; p < 0.99999999 * q; --j) // loop until underflow 245 | right += p, p = hypergeo_acc(j, 0, 0, 0, _aux); 246 | ++j; 247 | if (p < 1.00000001 * q) right += p; 248 | else ++j; 249 | // two-tail 250 | two = left + right; 251 | if (two > 1.) two = 1.; 252 | // adjust left and right 253 | if (Math.abs(i - n11) < Math.abs(j - n11)) right = 1. - left + q; 254 | else left = 1.0 - right + q; 255 | return [two, left, right]; 256 | } 257 | 258 | /********************* 259 | * Matrix operations * 260 | *********************/ 261 | 262 | Math.m = {}; 263 | 264 | Math.m.T = function(a) { // matrix transpose 265 | var b = [], m = a.length, n = a[0].length; // m rows and n cols 266 | for (var j = 0; j < n; ++j) b[j] = []; 267 | for (var i = 0; i < m; ++i) 268 | for (var j = 0; j < n; ++j) 269 | b[j].push(a[i][j]); 270 | return b; 271 | } 272 | 273 | Math.m.print = function(a) { // print a matrix to stdout 274 | var m = a.length, n = a[0].length; 275 | for (var i = 0; i < m; ++i) { 276 | var line = ''; 277 | for (var j = 0; j < n; ++j) 278 | line += (j? "\t" : '') + a[i][j]; 279 | print(line); 280 | } 281 | } 282 | 283 | Math.m.mul = function(a, b) { // matrix mul 284 | var m = a.length, n = a[0].length, s = b.length, t = b[0].length; 285 | if (n != s) return null; 286 | var x = [], c = Math.m.T(b); 287 | for (var i = 0; i < m; ++i) { 288 | x[i] = []; 289 | for (var j = 0; j < t; ++j) { 290 | var sum = 0; 291 | var ai = a[i], cj = c[j]; 292 | for (var k = 0; k < n; ++k) sum += ai[k] * cj[k]; 293 | x[i].push(sum); 294 | } 295 | } 296 | return x; 297 | } 298 | 299 | Math.m.add = function(a, b) { // matrix add 300 | var m = a.length, n = a[0].length, s = b.length, t = b[0].length; 301 | var x = []; 302 | if (m != s || n != t) return null; // different dimensions 303 | for (var i = 0; i < m; ++i) { 304 | x[i] = []; 305 | var ai = a[i], bi = b[i]; 306 | for (var j = 0; j < n; ++j) 307 | x[i].push(ai[j] + bi[j]); 308 | } 309 | return x; 310 | } 311 | 312 | Math.m.solve = function(a, b) { // Gauss-Jordan elimination, translated from gaussj() in Numerical Recipes in C. 313 | // on return, a[n][n] is the inverse; b[n][m] is the solution 314 | var n = a.length, m = (b)? b[0].length : 0; 315 | if (a[0].length != n || (b && b.length != n)) return -1; // invalid input 316 | var xc = [], xr = [], ipiv = []; 317 | var i, ic, ir, j, l, tmp; 318 | 319 | for (j = 0; j < n; ++j) ipiv[j] = 0; 320 | for (i = 0; i < n; ++i) { 321 | var big = 0; 322 | for (j = 0; j < n; ++j) { 323 | if (ipiv[j] != 1) { 324 | for (k = 0; k < n; ++k) { 325 | if (ipiv[k] == 0) { 326 | if (Math.abs(a[j][k]) >= big) { 327 | big = Math.abs(a[j][k]); 328 | ir = j; ic = k; 329 | } 330 | } else if (ipiv[k] > 1) return -2; // singular matrix 331 | } 332 | } 333 | } 334 | ++ipiv[ic]; 335 | if (ir != ic) { 336 | for (l = 0; l < n; ++l) tmp = a[ir][l], a[ir][l] = a[ic][l], a[ic][l] = tmp; 337 | if (b) for (l = 0; l < m; ++l) tmp = b[ir][l], b[ir][l] = b[ic][l], b[ic][l] = tmp; 338 | } 339 | xr[i] = ir; xc[i] = ic; 340 | if (a[ic][ic] == 0) return -3; // singular matrix 341 | var pivinv = 1. / a[ic][ic]; 342 | a[ic][ic] = 1.; 343 | for (l = 0; l < n; ++l) a[ic][l] *= pivinv; 344 | if (b) for (l = 0; l < m; ++l) b[ic][l] *= pivinv; 345 | for (var ll = 0; ll < n; ++ll) { 346 | if (ll != ic) { 347 | var dum = a[ll][ic]; 348 | a[ll][ic] = 0; 349 | for (l = 0; l < n; ++l) a[ll][l] -= a[ic][l] * dum; 350 | if (b) for (l = 0; l < m; ++l) b[ll][l] -= b[ic][l] * dum; 351 | } 352 | } 353 | } 354 | for (l = n - 1; l >= 0; --l) 355 | if (xr[l] != xc[l]) 356 | for (var k = 0; k < n; ++k) 357 | tmp = a[k][xr[l]], a[k][xr[l]] = a[k][xc[l]], a[k][xc[l]] = tmp; 358 | return 0; 359 | } 360 | 361 | Math.m.dup = function (a) 362 | { 363 | var b = []; 364 | for (var i = 0; i < a.length; ++i) 365 | b[i] = a[i].slice(0); 366 | return b; 367 | } 368 | 369 | Math.m.eigen = function(a) 370 | { 371 | function SQR(a) { return a * a; } 372 | function pythag(a, b) 373 | { 374 | var absa = Math.abs(a), absb = Math.abs(b); 375 | if (absa > absb) return absa * Math.sqrt(1.0 + SQR(absb / absa)); 376 | else return (absb == 0.0 ? 0.0 : absb * Math.sqrt(1.0 + SQR(absa / absb))); 377 | } 378 | function tred2(a, d, e) 379 | { 380 | var n = a.length, l, i, j, k; // integers 381 | var scale, hh, h, g, f; // real numbers 382 | 383 | for (i = n - 1; i > 0; i--) { 384 | l = i - 1; 385 | h = scale = 0.0; 386 | if (l > 0) { 387 | for (k = 0; k < l + 1; k++) 388 | scale += Math.abs(a[i][k]); 389 | if (scale == 0.0) 390 | e[i] = a[i][l]; 391 | else { 392 | for (k = 0; k < l + 1; k++) { 393 | a[i][k] /= scale; 394 | h += a[i][k] * a[i][k]; 395 | } 396 | f = a[i][l]; 397 | g = (f >= 0.0 ? -Math.sqrt(h) : Math.sqrt(h)); 398 | e[i] = scale * g; 399 | h -= f * g; 400 | a[i][l] = f - g; 401 | f = 0.0; 402 | for (j = 0; j < l + 1; j++) { 403 | /* Next statement can be omitted if eigenvectors not wanted */ 404 | a[j][i] = a[i][j] / h; 405 | g = 0.0; 406 | for (k = 0; k < j + 1; k++) 407 | g += a[j][k] * a[i][k]; 408 | for (k = j + 1; k < l + 1; k++) 409 | g += a[k][j] * a[i][k]; 410 | e[j] = g / h; 411 | f += e[j] * a[i][j]; 412 | } 413 | hh = f / (h + h); 414 | for (j = 0; j < l + 1; j++) { 415 | f = a[i][j]; 416 | e[j] = g = e[j] - hh * f; 417 | for (k = 0; k < j + 1; k++) 418 | a[j][k] -= (f * e[k] + g * a[i][k]); 419 | } 420 | } 421 | } else 422 | e[i] = a[i][l]; 423 | d[i] = h; 424 | } 425 | /* Next statement can be omitted if eigenvectors not wanted */ 426 | d[0] = 0.0; 427 | e[0] = 0.0; 428 | /* Contents of this loop can be omitted if eigenvectors not wanted except for statement d[i]=a[i][i]; */ 429 | for (i = 0; i < n; i++) { 430 | l = i; 431 | if (d[i] != 0.0) { 432 | for (j = 0; j < l; j++) { 433 | g = 0.0; 434 | for (k = 0; k < l; k++) 435 | g += a[i][k] * a[k][j]; 436 | for (k = 0; k < l; k++) 437 | a[k][j] -= g * a[k][i]; 438 | } 439 | } 440 | d[i] = a[i][i]; 441 | a[i][i] = 1.0; 442 | for (j = 0; j < l; j++) 443 | a[j][i] = a[i][j] = 0.0; 444 | } 445 | } 446 | function SIGN(a, b) { return b >= 0.? Math.abs(a) : -Math.abs(a); } 447 | function tqli(d, e, z) 448 | { 449 | var n = d.length, m, l, iter, i, k; // integers 450 | var s, r, p, g, f, dd, c, b; // real numbers 451 | 452 | for (i = 1; i < n; i++) 453 | e[i - 1] = e[i]; 454 | e[n - 1] = 0.0; 455 | for (l = 0; l < n; l++) { 456 | iter = 0; 457 | do { 458 | for (m = l; m < n - 1; m++) { 459 | dd = Math.abs(d[m]) + Math.abs(d[m + 1]); 460 | if (Math.abs(e[m]) + dd == dd) 461 | break; 462 | } 463 | if (m != l) { 464 | if (iter++ == 30) { 465 | warn("[tqli] Too many iterations in tqli.\n"); 466 | break; 467 | } 468 | g = (d[l + 1] - d[l]) / (2.0 * e[l]); 469 | r = pythag(g, 1.0); 470 | g = d[m] - d[l] + e[l] / (g + SIGN(r, g)); 471 | s = c = 1.0; 472 | p = 0.0; 473 | for (i = m - 1; i >= l; i--) { 474 | f = s * e[i]; 475 | b = c * e[i]; 476 | e[i + 1] = (r = pythag(f, g)); 477 | if (r == 0.0) { 478 | d[i + 1] -= p; 479 | e[m] = 0.0; 480 | break; 481 | } 482 | s = f / r; 483 | c = g / r; 484 | g = d[i + 1] - p; 485 | r = (d[i] - g) * s + 2.0 * c * b; 486 | d[i + 1] = g + (p = s * r); 487 | g = c * r - b; 488 | /* Next loop can be omitted if eigenvectors not wanted */ 489 | for (k = 0; k < n; k++) { 490 | f = z[k][i + 1]; 491 | z[k][i + 1] = s * z[k][i] + c * f; 492 | z[k][i] = c * z[k][i] - s * f; 493 | } 494 | } 495 | if (r == 0.0 && i >= l) 496 | continue; 497 | d[l] -= p; 498 | e[l] = g; 499 | e[m] = 0.0; 500 | } 501 | } while (m != l); 502 | } 503 | } 504 | 505 | var ev = [], e = []; 506 | tred2(a, ev, e); 507 | tqli(ev, e, a); 508 | return ev; 509 | } 510 | 511 | Math.m.Laplacian = function(a, is_norm) 512 | { 513 | var d = [], n = a.length, L = []; 514 | for (var i = 0; i < n; ++i) { 515 | var s = 0; 516 | for (var j = 0; j < n; ++j) 517 | s += a[i][j]; 518 | if (s == 0) return null; 519 | d[i] = s; 520 | L[i] = []; 521 | } 522 | if (is_norm) { 523 | for (var i = 0; i < n; ++i) 524 | d[i] = 1. / Math.sqrt(d[i]); 525 | for (var i = 0; i < n; ++i) { 526 | for (var j = 0; j < i; ++j) 527 | L[i][j] = -a[i][j] * d[i] * d[j]; 528 | L[i][i] = 1 - a[i][i] * d[i] * d[i]; 529 | for (var j = i + 1; j < n; ++j) 530 | L[i][j] = -a[i][j] * d[i] * d[j]; 531 | } 532 | } else { 533 | for (var i = 0; i < n; ++i) { 534 | for (var j = 0; j < i; ++j) 535 | L[i][j] = -a[i][j]; 536 | L[i][i] = d[i] - a[i][i]; 537 | for (var j = i + 1; j < n; ++j) 538 | L[i][j] = -a[i][j]; 539 | } 540 | } 541 | return L; 542 | } 543 | 544 | /* // Math.m example 545 | x = [[1,2],[3,4]]; y = [[1,2],[3,4]]; 546 | Math.m.print(Math.m.add(Math.m.mul(x,y), x)); 547 | a = [[1,1],[1,-1]]; b = [[2],[0]]; 548 | a = [[1,2],[3,4]]; 549 | print(Math.m.solve(a)); 550 | Math.m.print(a); 551 | */ 552 | /* // eigen value example 553 | var x = [[10, 1, 2, 3, 4], 554 | [ 1, 9,-1, 2,-3], 555 | [ 2,-1, 7, 3,-5], 556 | [ 3, 2, 3,12,-1], 557 | [ 4,-3,-5,-1,15]]; 558 | var ev = Math.m.eigen(x); 559 | print(ev.join("\t")); // eigen values 560 | Math.m.print(x); // eigen vectors 561 | */ 562 | 563 | /************************************ 564 | * Fasta/Fastq reader in Javascript * 565 | ************************************/ 566 | 567 | Fastx = function(f) { 568 | this._file = f; 569 | this._last = 0; 570 | this._line = new Bytes(); 571 | this._finished = false; 572 | this.s = new Bytes(); 573 | this.q = new Bytes(); 574 | this.n = new Bytes(); 575 | this.c = new Bytes(); 576 | } 577 | 578 | Fastx.prototype.read = function() { 579 | var c, f = this._file, line = this._line; 580 | if (this._last == 0) { // then jump to the next header line 581 | while ((c = f.read()) != -1 && c != 62 && c != 64); 582 | if (c == -1) return -1; // end of file 583 | this._last = c; 584 | } // else: the first header char has been read in the previous call 585 | this.c.length = this.s.length = this.q.length = 0; 586 | if ((c = f.readline(this.n, 0)) < 0) return -1; // normal exit: EOF 587 | if (c != 10) f.readline(this.c); // read FASTA/Q comment 588 | if (this.s.capacity == 0) this.s.capacity = 256; 589 | while ((c = f.read()) != -1 && c != 62 && c != 43 && c != 64) { 590 | if (c == 10) continue; // skip empty lines 591 | this.s.set(c); 592 | f.readline(this.s, 2, this.s.length); // read the rest of the line 593 | } 594 | if (c == 62 || c == 64) this._last = c; // the first header char has been read 595 | if (c != 43) return this.s.length; // FASTA 596 | this.q.capacity = this.s.capacity; 597 | c = f.readline(this._line); // skip the rest of '+' line 598 | if (c < 0) return -2; // error: no quality string 599 | var size = this.s.length; 600 | while (f.readline(this.q, 2, this.q.length) >= 0 && this.q.length < size); 601 | f._last = 0; // we have not come to the next header line 602 | if (this.q.length != size) return -2; // error: qual string is of a different length 603 | return size; 604 | } 605 | 606 | Fastx.prototype.destroy = function() { 607 | this.s.destroy(); this.q.destroy(); this.c.destroy(); this.n.destroy(); this._line.destroy(); 608 | if (typeof(this._file.close) == 'object') this._file.close(); 609 | } 610 | 611 | function intv_ovlp(intv, bits) 612 | { 613 | if (typeof bits == "undefined") bits = 13; 614 | intv.sort(function(a,b) {return a[0]-b[0];}); 615 | // merge overlapping regions 616 | var j = 0; 617 | for (var i = 1; i < intv.length; ++i) { 618 | if (intv[j][1] >= intv[i][0]) 619 | intv[j][1] = intv[j][1] > intv[i][1]? intv[j][1] : intv[i][1]; 620 | else intv[++j] = [intv[i][0], intv[i][1]]; 621 | } 622 | intv.length = j + 1; 623 | // create the index 624 | var idx = [], max = 0; 625 | for (var i = 0; i < intv.length; ++i) { 626 | var b = intv[i][0]>>bits; 627 | var e = (intv[i][1]-1)>>bits; 628 | if (b != e) { 629 | for (var j = b; j <= e; ++j) 630 | if (idx[j] == null) idx[j] = i; 631 | } else if (idx[b] == null) idx[b] = i; 632 | max = max > e? max : e; 633 | } 634 | return function(_b, _e) { // closure 635 | var x = _b >> bits; 636 | if (x > max) return false; 637 | var off = idx[x]; 638 | if (off == null) { 639 | var i; 640 | for (i = ((_e - 1) >> bits) - 1; i >= 0; --i) 641 | if (idx[i] != null) break; 642 | off = i < 0? 0 : idx[i]; 643 | } 644 | for (var i = off; i < intv.length && intv[i][0] < _e; ++i) 645 | if (intv[i][1] > _b) return true; 646 | return false; 647 | } 648 | } 649 | 650 | /************************** 651 | * Bioinformatics related * 652 | **************************/ 653 | /* 654 | Bio = {}; 655 | Bio.Seq.comp_pair = ['WSATUGCYRKMBDHVNwsatugcyrkmbdhvn', 'WSTAACGRYMKVHDBNwstaacgrymkvhdbn']; 656 | Bio.Seq.ts_table = {'AG':1, 'GA':1, 'CT':2, 'TC':2}; 657 | */ 658 | /* // Bio.Seq example 659 | var s = new Bio.Seq(); 660 | var f = new File(arguments[0]); 661 | while (s.next(function(){return f.next()}) != null) s.print(); 662 | */ 663 | -------------------------------------------------------------------------------- /k8.cc: -------------------------------------------------------------------------------- 1 | /* The MIT License 2 | 3 | Copyright (c) 2018- Dana-Farber Cancer Institute 4 | 2011-2018 Broad Institute, Inc 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 21 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 22 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 23 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | #define K8_VERSION "1.2-r137" 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "include/v8-context.h" 37 | #include "include/v8-exception.h" 38 | #include "include/v8-initialization.h" 39 | #include "include/v8-isolate.h" 40 | #include "include/v8-local-handle.h" 41 | #include "include/v8-script.h" 42 | #include "include/v8-container.h" 43 | #include "include/v8-template.h" 44 | #include "include/libplatform/libplatform.h" 45 | 46 | /************************** 47 | *** Global definitions *** 48 | **************************/ 49 | 50 | #define K8_FILE_MAGIC (0x46696c) 51 | #define K8_BYTES_MAGIC (0x427974) 52 | 53 | #define K8_MALLOC(type, cnt) ((type*)malloc((cnt) * sizeof(type))) 54 | #define K8_CALLOC(type, cnt) ((type*)calloc((cnt), sizeof(type))) 55 | #define K8_REALLOC(type, ptr, cnt) ((type*)realloc((ptr), (cnt) * sizeof(type))) 56 | 57 | #define K8_GROW(type, ptr, __i, __m) do { \ 58 | if ((__i) >= (__m)) { \ 59 | (__m) = (__i) + 1; \ 60 | (__m) += ((__m)>>1) + 16; \ 61 | (ptr) = K8_REALLOC(type, ptr, (__m)); \ 62 | } \ 63 | } while (0) 64 | 65 | #define K8_GROW0(type, ptr, __i, __m) do { \ 66 | if ((__i) >= (__m)) { \ 67 | size_t old_m = (__m); \ 68 | (__m) = (__i) + 1; \ 69 | (__m) += ((__m)>>1) + 16; \ 70 | (ptr) = K8_REALLOC(type, ptr, (__m)); \ 71 | memset((ptr) + old_m, 0, ((__m) - old_m) * sizeof(type)); \ 72 | } \ 73 | } while (0) 74 | 75 | #ifdef PATH_MAX 76 | #define K8_PATH_MAX PATH_MAX 77 | #else 78 | #define K8_PATH_MAX 4095 79 | #endif 80 | 81 | static char *k8_src_path = 0; 82 | 83 | typedef struct { 84 | int64_t l, m; 85 | uint8_t *s; 86 | } kstring_t; 87 | 88 | typedef struct { 89 | uint64_t magic; 90 | kstring_t buf; 91 | } k8_bytes_t; 92 | 93 | /**************** 94 | *** File I/O *** 95 | ****************/ 96 | 97 | #define KS_SEP_SPACE 0 98 | #define KS_SEP_TAB 1 99 | #define KS_SEP_LINE 2 100 | 101 | typedef struct { 102 | uint64_t magic; 103 | gzFile fp; 104 | FILE *fpw; 105 | int32_t st, en, buf_size, enc, last_char; 106 | int32_t is_eof:16, is_fastq:16; 107 | uint8_t *buf; 108 | } k8_file_t; 109 | 110 | #define ks_err(ks) ((ks)->en < 0) 111 | #define ks_eof(ks) ((ks)->is_eof && (ks)->st >= (ks)->en) 112 | 113 | static k8_file_t *ks_open(int fd, const char *fn, const char *mode) 114 | { 115 | gzFile fp = 0; 116 | FILE *fpw = 0; 117 | int32_t write_file = (mode && (strchr(mode, 'w') || strchr(mode, 'a')) && strchr(mode, 'r') == 0); 118 | if (fd >= 0) { 119 | if (write_file) fpw = fdopen(fd, mode); 120 | else fp = gzdopen(fd, "r"); 121 | } else if (fn) { 122 | if (write_file) fpw = strcmp(fn, "-")? fopen(fn, mode) : stdout; 123 | else fp = strcmp(fn, "-")? gzopen(fn, "r") : gzdopen(0, "r"); 124 | } else { 125 | if (write_file) fpw = stdout; 126 | else fp = gzdopen(0, "r"); 127 | } 128 | if (fp == 0 && fpw == 0) return 0; 129 | k8_file_t *ks = K8_CALLOC(k8_file_t, 1); 130 | ks->magic = K8_FILE_MAGIC; 131 | ks->fp = fp, ks->fpw = fpw; 132 | if (fp) { 133 | ks->buf_size = 0x40000; 134 | ks->buf = K8_CALLOC(uint8_t, ks->buf_size); 135 | } 136 | return ks; 137 | } 138 | 139 | static void ks_close(k8_file_t *ks) 140 | { 141 | if (ks == 0) return; 142 | if (ks->fp) gzclose(ks->fp); 143 | if (ks->fpw) fclose(ks->fpw); 144 | free(ks->buf); 145 | memset(ks, 0, sizeof(*ks)); 146 | free(ks); 147 | } 148 | 149 | static inline int32_t ks_getc(k8_file_t *ks) 150 | { 151 | if (ks_err(ks)) return -3; 152 | if (ks_eof(ks)) return -1; 153 | if (ks->st >= ks->en) { 154 | ks->st = 0; 155 | ks->en = gzread(ks->fp, ks->buf, ks->buf_size); 156 | if (ks->en == 0) { ks->is_eof = 1; return -1; } 157 | else if (ks->en < 0) { ks->is_eof = 1; return -3; } 158 | } 159 | return (int32_t)ks->buf[ks->st++]; 160 | } 161 | 162 | static int64_t ks_read(k8_file_t *ks, uint8_t *buf, int64_t len) 163 | { 164 | int64_t off = 0; 165 | if (ks->is_eof && ks->st >= ks->en) return 0; 166 | while (len > ks->en - ks->st) { 167 | int64_t l = ks->en - ks->st; 168 | if (l > 0) { 169 | memcpy(buf + off, ks->buf + ks->st, l); 170 | len -= l; off += l; 171 | } 172 | ks->st = 0; 173 | ks->en = gzread(ks->fp, ks->buf, ks->buf_size); 174 | if (ks->en < ks->buf_size) ks->is_eof = 1; 175 | if (ks->en == 0) return off; 176 | else if (ks->en < 0) return -3; // gzread error 177 | } 178 | memcpy(buf + off, ks->buf + ks->st, len); 179 | ks->st += len; 180 | return off + len; 181 | } 182 | 183 | static int64_t ks_read_all(k8_file_t *ks, kstring_t *str) 184 | { 185 | str->l = 0; 186 | while (!ks_eof(ks)) { 187 | int64_t l = ks->en - ks->st; 188 | if (l > 0) { 189 | K8_GROW(uint8_t, str->s, str->l + l, str->m); 190 | memcpy(&str->s[str->l], &ks->buf[ks->st], l); 191 | str->l += l; 192 | } 193 | ks->st = 0; 194 | ks->en = gzread(ks->fp, ks->buf, ks->buf_size); 195 | if (ks->en < ks->buf_size) ks->is_eof = 1; 196 | if (ks->en <= 0) break; 197 | } 198 | str->s[str->l] = 0; // always enough room due to K8_GROW() is requesting on extra byte 199 | return str->l; 200 | } 201 | 202 | static int64_t ks_getuntil2(k8_file_t *ks, int delimiter, kstring_t *str, int *dret, int append) 203 | { 204 | int gotany = 0; 205 | if (dret) *dret = 0; 206 | str->l = append? str->l : 0; 207 | for (;;) { 208 | int i = 0; 209 | if (ks_err(ks)) return -3; 210 | if (ks->st >= ks->en) { 211 | if (!ks->is_eof) { 212 | ks->st = 0; 213 | ks->en = gzread(ks->fp, ks->buf, ks->buf_size); 214 | if (ks->en == 0) { ks->is_eof = 1; break; } 215 | if (ks->en == -1) { ks->is_eof = 1; return -3; } 216 | } else break; 217 | } 218 | if (delimiter == KS_SEP_LINE) { 219 | unsigned char *sep = (unsigned char*)memchr(ks->buf + ks->st, '\n', ks->en - ks->st); 220 | i = sep != NULL ? sep - ks->buf : ks->en; 221 | } else if (delimiter == KS_SEP_SPACE) { 222 | for (i = ks->st; i < ks->en; ++i) 223 | if (ks->buf[i] == ' ' || ks->buf[i] == '\t' || ks->buf[i] == '\n') break; 224 | } else if (delimiter == KS_SEP_TAB) { 225 | for (i = ks->st; i < ks->en; ++i) 226 | if (ks->buf[i] == '\t' || ks->buf[i] == '\n') break; 227 | } else if (delimiter > 0) { 228 | for (i = ks->st; i < ks->en; ++i) 229 | if (ks->buf[i] == delimiter) break; 230 | } else abort(); 231 | K8_GROW(uint8_t, str->s, str->l + (i - ks->st), str->m); 232 | gotany = 1; 233 | memcpy(str->s + str->l, ks->buf + ks->st, i - ks->st); 234 | str->l = str->l + (i - ks->st); 235 | ks->st = i + 1; 236 | if (i < ks->en) { 237 | if (dret) *dret = ks->buf[i]; 238 | break; 239 | } 240 | } 241 | if (!gotany && ks_eof(ks)) return -1; 242 | if (str->s == 0) { 243 | str->m = 1; 244 | str->s = K8_CALLOC(uint8_t, 1); 245 | } else if (delimiter == KS_SEP_LINE && str->l > 1 && str->s[str->l-1] == '\r') { 246 | --str->l; 247 | } 248 | str->s[str->l] = '\0'; 249 | return str->l; 250 | } 251 | 252 | /******************************* 253 | *** Fundamental v8 routines *** 254 | *******************************/ 255 | 256 | #define K8_SAVE_PTR(_args, _index, _ptr) (_args).This()->SetAlignedPointerInInternalField(_index, (void*)(_ptr)) 257 | #define K8_LOAD_PTR(_args, _index, _type) ((_type*)(_args).This()->GetAlignedPointerFromInternalField(_index)) 258 | 259 | static inline const char *k8_cstr(const v8::String::Utf8Value &str) // Convert a v8 string to C string 260 | { 261 | return *str? *str : ""; 262 | } 263 | 264 | static void k8_exception(v8::Isolate* isolate, v8::TryCatch* try_catch) // Exception handling. Adapted from v8/shell.cc 265 | { 266 | v8::HandleScope handle_scope(isolate); 267 | v8::String::Utf8Value exception(isolate, try_catch->Exception()); 268 | const char* exception_string = k8_cstr(exception); 269 | v8::Local message = try_catch->Message(); 270 | if (message.IsEmpty()) { 271 | // v8 didn't provide any extra information about this error; just print the exception. 272 | fprintf(stderr, "%s\n", exception_string); 273 | } else { 274 | // Print (filename):(line number): (message). 275 | v8::String::Utf8Value filename(isolate, message->GetScriptOrigin().ResourceName()); 276 | v8::Local context(isolate->GetCurrentContext()); 277 | const char* filename_string = k8_cstr(filename); 278 | int linenum = message->GetLineNumber(context).FromJust(); 279 | fprintf(stderr, "%s:%i: %s\n", filename_string, linenum, exception_string); 280 | // Print line of source code. 281 | v8::String::Utf8Value sourceline(isolate, message->GetSourceLine(context).ToLocalChecked()); 282 | const char* sourceline_string = k8_cstr(sourceline); 283 | fprintf(stderr, "%s\n", sourceline_string); 284 | // Print wavy underline (GetUnderline is deprecated). 285 | int start = message->GetStartColumn(context).FromJust(); 286 | for (int i = 0; i < start; i++) fputc(' ', stderr); 287 | int end = message->GetEndColumn(context).FromJust(); 288 | for (int i = start; i < end; i++) fputc('^', stderr); 289 | fputc('\n', stderr); 290 | v8::Local stack_trace_string; 291 | if (try_catch->StackTrace(context).ToLocal(&stack_trace_string) && 292 | stack_trace_string->IsString() && 293 | stack_trace_string.As()->Length() > 0) 294 | { 295 | v8::String::Utf8Value stack_trace(isolate, stack_trace_string); 296 | const char* err = k8_cstr(stack_trace); 297 | fputs(err, stderr); fputc('\n', stderr); 298 | } 299 | } 300 | } 301 | 302 | static bool k8_execute(v8::Isolate* isolate, v8::Local source, v8::Local name, bool prt_rst) // Execute JS in a string. Adapted from v8/shell.cc 303 | { 304 | v8::HandleScope handle_scope(isolate); 305 | v8::TryCatch try_catch(isolate); 306 | v8::ScriptOrigin origin(isolate, name); 307 | v8::Local context(isolate->GetCurrentContext()); 308 | v8::Local script; 309 | if (!v8::Script::Compile(context, source, &origin).ToLocal(&script)) { 310 | k8_exception(isolate, &try_catch); 311 | return false; 312 | } else { 313 | v8::Local result; 314 | if (!script->Run(context).ToLocal(&result)) { 315 | assert(try_catch.HasCaught()); 316 | k8_exception(isolate, &try_catch); 317 | return false; 318 | } else { 319 | assert(!try_catch.HasCaught()); 320 | if (prt_rst && !result->IsUndefined()) { 321 | // If all went well and the result wasn't undefined then print the returned value. 322 | v8::String::Utf8Value str(isolate, result); 323 | puts(k8_cstr(str)); 324 | } 325 | return true; 326 | } 327 | } 328 | } 329 | 330 | v8::MaybeLocal k8_readfile(v8::Isolate* isolate, const char *name) // Read an entire file. Adapted from v8/shell.cc 331 | { 332 | kstring_t str = {0,0,0}; 333 | k8_file_t *ks = ks_open(-1, name, 0); 334 | if (ks == 0) { 335 | fprintf(stderr, "ERROR: fail to open file '%s'.\n", name); 336 | return v8::Handle(); 337 | } 338 | ks_read_all(ks, &str); 339 | ks_close(ks); 340 | 341 | if (str.l > 2 && strncmp((char*)str.s, "#!", 2) == 0) { // then skip the "#!" line 342 | int64_t i; 343 | for (i = 0; i < str.l; ++i) 344 | if (str.s[i] == '\n') break; 345 | str.l -= i + 1; 346 | memmove(str.s, &str.s[i+1], str.l); 347 | } 348 | v8::MaybeLocal result = v8::String::NewFromUtf8(isolate, (char*)str.s, v8::NewStringType::kNormal, str.l); 349 | free(str.s); 350 | return result; 351 | } 352 | 353 | /****************************** 354 | *** New built-in functions *** 355 | ******************************/ 356 | 357 | static void k8_write_string(FILE *fp, const v8::FunctionCallbackInfo &args, int32_t i, kstring_t *buf) 358 | { 359 | v8::HandleScope handle_scope(args.GetIsolate()); 360 | if (args[i]->IsString()) { 361 | int64_t len = args[i].As()->Length(); 362 | if (len > 0) { 363 | K8_GROW(uint8_t, buf->s, len, buf->m); 364 | args[i].As()->WriteOneByte(args.GetIsolate(), buf->s); 365 | fwrite(buf->s, 1, len, fp); 366 | } 367 | return; 368 | } else if (args[i]->IsArrayBuffer()) { 369 | void *data = args[i].As()->GetBackingStore()->Data(); 370 | int64_t len = args[i].As()->GetBackingStore()->ByteLength(); 371 | if (len > 0) fwrite(data, 1, len, fp); 372 | return; 373 | } else if (args[i]->IsObject()) { 374 | if (args[i].As()->InternalFieldCount() > 0) { 375 | k8_bytes_t *a = (k8_bytes_t*)args[i].As()->GetAlignedPointerFromInternalField(0); 376 | if (a && a->magic == K8_BYTES_MAGIC) { 377 | if (a->buf.l > 0) fwrite(a->buf.s, 1, a->buf.l, fp); 378 | return; 379 | } 380 | } 381 | } 382 | v8::String::Utf8Value str(args.GetIsolate(), args[i]); 383 | fputs(k8_cstr(str), fp); 384 | } 385 | 386 | static void k8_print(const v8::FunctionCallbackInfo &args) // print(): print to stdout; TAB demilited if multiple arguments are provided 387 | { 388 | kstring_t buf = {0,0,0}; 389 | for (int32_t i = 0; i < args.Length(); i++) { 390 | if (i) fputc('\t', stdout); 391 | k8_write_string(stdout, args, i, &buf); 392 | } 393 | fputc('\n', stdout); 394 | if (buf.s) free(buf.s); 395 | } 396 | 397 | static void k8_warn(const v8::FunctionCallbackInfo &args) // warn(): similar print() but print to stderr 398 | { 399 | kstring_t buf = {0,0,0}; 400 | for (int32_t i = 0; i < args.Length(); i++) { 401 | if (i) fputc('\t', stderr); 402 | k8_write_string(stderr, args, i, &buf); 403 | } 404 | fputc('\n', stderr); 405 | if (buf.s) free(buf.s); 406 | } 407 | 408 | static void k8_exit(const v8::FunctionCallbackInfo &args) 409 | { 410 | v8::HandleScope handle_scope(args.GetIsolate()); 411 | int exit_code = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromMaybe(0); 412 | fflush(stdout); fflush(stderr); 413 | exit(exit_code); 414 | } 415 | 416 | static void k8_load(const v8::FunctionCallbackInfo &args) // load(): Load and execute a JS file. It also searches ONE path in $K8_PATH 417 | { 418 | char *env_path = getenv("K8_PATH"); 419 | char abspath[K8_PATH_MAX+1], buf[K8_PATH_MAX+1]; 420 | FILE *fp; 421 | int32_t k; 422 | realpath(k8_src_path, abspath); 423 | for (k = strlen(abspath) - 1; k >= 0; --k) 424 | if (abspath[k] == '/') break; 425 | assert(k >= 0); 426 | abspath[++k] = 0; 427 | for (int i = 0; i < args.Length(); i++) { 428 | v8::HandleScope handle_scope(args.GetIsolate()); 429 | v8::String::Utf8Value file(args.GetIsolate(), args[i]); 430 | int32_t l_fn = strlen(*file); 431 | buf[0] = 0; 432 | if (buf[0] == 0 && l_fn < K8_PATH_MAX) { // 1) the current directory first 433 | if ((fp = fopen(*file, "r")) != 0) { 434 | strcpy(buf, *file); 435 | fclose(fp); 436 | } 437 | } 438 | if (buf[0] == 0 && k + l_fn < K8_PATH_MAX) { // 2) the script directory 439 | strcpy(&abspath[k], *file); 440 | if ((fp = fopen(abspath, "r")) != 0) { 441 | strcpy(buf, abspath); 442 | fclose(fp); 443 | } 444 | } 445 | if (buf[0] == 0 && env_path && strlen(env_path) + l_fn < K8_PATH_MAX) { // 3) a directory on K8_PATH. TODO: to allow multiple paths separated by ":" 446 | strcpy(buf, env_path); strcat(buf, "/"); strcat(buf, *file); 447 | if ((fp = fopen(buf, "r")) != 0) fclose(fp); 448 | } 449 | if (buf[0] == 0) { 450 | args.GetIsolate()->ThrowError("[load] fail to locate the file"); 451 | return; 452 | } 453 | v8::Local source; 454 | if (!k8_readfile(args.GetIsolate(), buf).ToLocal(&source)) { 455 | args.GetIsolate()->ThrowError("[load] fail to read the file"); 456 | return; 457 | } 458 | if (!k8_execute(args.GetIsolate(), source, args[i], false)) { 459 | args.GetIsolate()->ThrowError("[load] fail to execute the file"); 460 | return; 461 | } 462 | } 463 | } 464 | 465 | static void k8_read_file(const v8::FunctionCallbackInfo &args) 466 | { 467 | if (args.Length() != 1) return; 468 | v8::HandleScope handle_scope(args.GetIsolate()); 469 | v8::String::Utf8Value fn(args.GetIsolate(), args[0]); 470 | k8_file_t *fp = ks_open(-1, *fn, "r"); 471 | if (fp == 0) return; 472 | kstring_t buf = {0,0,0}; 473 | if (ks_read_all(fp, &buf) < 0) return; 474 | v8::Handle ab = v8::ArrayBuffer::New(args.GetIsolate(), buf.l); 475 | memcpy(ab->GetBackingStore()->Data(), buf.s, buf.l); 476 | args.GetReturnValue().Set(ab); 477 | } 478 | 479 | static void k8_encode(const v8::FunctionCallbackInfo &args) 480 | { 481 | if (args.Length() == 0 || !args[0]->IsString()) return; 482 | v8::Isolate *isolate = args.GetIsolate(); 483 | v8::HandleScope handle_scope(isolate); 484 | int32_t type = 0; // Latin-1 485 | if (args.Length() >= 2) { 486 | v8::String::Utf8Value e(isolate, args[1]); 487 | if (strcmp(*e, "utf8") == 0 || strcmp(*e, "utf-8") == 0 || strcmp(*e, "UTF8") == 0 || strcmp(*e, "UTF-8") == 0) 488 | type = 1; // UTF-8 489 | } 490 | if (type == 0) { 491 | v8::Handle ab = v8::ArrayBuffer::New(isolate, args[0].As()->Length()); 492 | args[0].As()->WriteOneByte(isolate, (uint8_t*)ab->GetBackingStore()->Data()); 493 | args.GetReturnValue().Set(ab); 494 | } else if (type == 1) { 495 | v8::Handle ab = v8::ArrayBuffer::New(isolate, args[0].As()->Utf8Length(isolate)); 496 | args[0].As()->WriteUtf8(isolate, (char*)ab->GetBackingStore()->Data()); 497 | args.GetReturnValue().Set(ab); 498 | } 499 | } 500 | 501 | static void k8_decode(const v8::FunctionCallbackInfo &args) 502 | { 503 | if (args.Length() == 0) return; 504 | v8::HandleScope handle_scope(args.GetIsolate()); 505 | void *data = 0; 506 | int64_t len = 0; 507 | if (args[0]->IsArrayBuffer()) { 508 | data = args[0].As()->GetBackingStore()->Data(); 509 | len = args[0].As()->GetBackingStore()->ByteLength(); 510 | } else if (args[0]->IsObject()) { 511 | if (args[0].As()->InternalFieldCount() > 0) { 512 | k8_bytes_t *a = (k8_bytes_t*)args[0].As()->GetAlignedPointerFromInternalField(0); 513 | if (a && a->magic == K8_BYTES_MAGIC) 514 | data = a->buf.s, len = a->buf.l; 515 | } 516 | } 517 | if (data == 0) return; 518 | int32_t type = 0; // Latin-1 519 | if (args.Length() >= 2) { 520 | v8::String::Utf8Value e(args.GetIsolate(), args[1]); 521 | if (strcmp(*e, "utf8") == 0 || strcmp(*e, "utf-8") == 0 || strcmp(*e, "UTF8") == 0 || strcmp(*e, "UTF-8") == 0) 522 | type = 1; // UTF-8 523 | } 524 | v8::Local str; 525 | if (type == 0) { 526 | if (v8::String::NewFromOneByte(args.GetIsolate(), (uint8_t*)data, v8::NewStringType::kNormal, len).ToLocal(&str)) 527 | args.GetReturnValue().Set(str); 528 | } else if (type == 1) { 529 | if (v8::String::NewFromUtf8(args.GetIsolate(), (char*)data, v8::NewStringType::kNormal, len).ToLocal(&str)) 530 | args.GetReturnValue().Set(str); 531 | } 532 | } 533 | 534 | static void k8_version(const v8::FunctionCallbackInfo &args) 535 | { 536 | args.GetReturnValue().Set(v8::String::NewFromUtf8Literal(args.GetIsolate(), K8_VERSION)); 537 | } 538 | 539 | const char k8_comp_tab[] = { 540 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 541 | 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 542 | 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 543 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 544 | 64, 'T', 'V', 'G', 'H', 'E', 'F', 'C', 'D', 'I', 'J', 'M', 'L', 'K', 'N', 'O', 545 | 'P', 'Q', 'Y', 'S', 'A', 'A', 'B', 'W', 'X', 'R', 'Z', 91, 92, 93, 94, 95, 546 | 64, 't', 'v', 'g', 'h', 'e', 'f', 'c', 'd', 'i', 'j', 'm', 'l', 'k', 'n', 'o', 547 | 'p', 'q', 'y', 's', 'a', 'a', 'b', 'w', 'x', 'r', 'z', 123, 124, 125, 126, 127 548 | }; 549 | 550 | static void k8_revcomp(const v8::FunctionCallbackInfo &args) 551 | { 552 | if (args.Length() == 0) return; 553 | v8::HandleScope handle_scope(args.GetIsolate()); 554 | uint8_t *seq = 0; 555 | int64_t len = 0; 556 | int32_t is_str = 0; 557 | if (args[0]->IsArrayBuffer()) { 558 | seq = (uint8_t*)args[0].As()->GetBackingStore()->Data(); 559 | len = args[0].As()->GetBackingStore()->ByteLength(); 560 | } else if (args[0]->IsObject()) { 561 | if (args[0].As()->InternalFieldCount() > 0) { 562 | k8_bytes_t *a = (k8_bytes_t*)args[0].As()->GetAlignedPointerFromInternalField(0); 563 | if (a && a->magic == K8_BYTES_MAGIC) 564 | seq = a->buf.s, len = a->buf.l; 565 | } 566 | } else if (args[0]->IsString()) { 567 | is_str = 1; 568 | len = args[0].As()->Length(); 569 | seq = (uint8_t*)calloc(len + 1, 1); 570 | args[0].As()->WriteOneByte(args.GetIsolate(), seq); 571 | } 572 | if (seq == 0) return; 573 | for (int64_t i = 0; i < len>>1; ++i) { 574 | uint8_t tmp = seq[len - 1 - i]; 575 | seq[len - 1 - i] = seq[i] < 128? k8_comp_tab[seq[i]] : seq[i]; 576 | seq[i] = tmp < 128? k8_comp_tab[tmp] : tmp; 577 | } 578 | if (len & 1) seq[len>>1] = seq[len>>1] < 128? k8_comp_tab[seq[len>>1]] : seq[len>>1]; 579 | if (is_str) { 580 | v8::Local str; 581 | if (v8::String::NewFromOneByte(args.GetIsolate(), seq, v8::NewStringType::kNormal, len).ToLocal(&str)) 582 | args.GetReturnValue().Set(str); 583 | free(seq); 584 | } 585 | } 586 | 587 | /*********************** 588 | *** The Bytes class *** 589 | ***********************/ 590 | 591 | static void k8_bytes_new(const v8::FunctionCallbackInfo &args) 592 | { 593 | v8::HandleScope handle_scope(args.GetIsolate()); 594 | k8_bytes_t *a = K8_CALLOC(k8_bytes_t, 1); 595 | a->magic = K8_BYTES_MAGIC; 596 | if (args.Length()) { 597 | a->buf.m = a->buf.l = args[0]->IntegerValue(args.GetIsolate()->GetCurrentContext()).FromMaybe(0); 598 | a->buf.s = K8_CALLOC(uint8_t, a->buf.l); 599 | } 600 | K8_SAVE_PTR(args, 0, a); 601 | } 602 | 603 | static void k8_bytes_destroy(const v8::FunctionCallbackInfo &args) 604 | { 605 | v8::HandleScope handle_scope(args.GetIsolate()); 606 | k8_bytes_t *a = K8_LOAD_PTR(args, 0, k8_bytes_t); 607 | if (a == 0) return; 608 | free(a->buf.s); free(a); 609 | K8_SAVE_PTR(args, 0, 0); 610 | args.GetReturnValue().Set(0); 611 | } 612 | 613 | static void k8_bytes_set(const v8::FunctionCallbackInfo &args) 614 | { 615 | v8::Isolate *isolate = args.GetIsolate(); 616 | v8::HandleScope handle_scope(isolate); 617 | k8_bytes_t *a = K8_LOAD_PTR(args, 0, k8_bytes_t); 618 | if (a == 0 || args.Length() == 0) return; 619 | int64_t pre = a->buf.l; 620 | int64_t off = args.Length() >= 2? args[1]->IntegerValue(isolate->GetCurrentContext()).FromMaybe(a->buf.l) : a->buf.l; 621 | if (args[0]->IsNumber()) { 622 | K8_GROW0(uint8_t, a->buf.s, off, a->buf.m); 623 | a->buf.s[off] = (uint8_t)args[0]->Uint32Value(isolate->GetCurrentContext()).FromMaybe(0); 624 | a->buf.l = off + 1; 625 | } else if (args[0]->IsString()) { 626 | int32_t len = args[0].As()->Length(); 627 | K8_GROW0(uint8_t, a->buf.s, off + len, a->buf.m); 628 | args[0].As()->WriteOneByte(isolate, &a->buf.s[off]); 629 | a->buf.l = off + len; 630 | } else if (args[0]->IsArray()) { 631 | v8::Handle array = v8::Handle::Cast(args[0]); 632 | K8_GROW0(uint8_t, a->buf.s, off + array->Length(), a->buf.m); 633 | for (size_t i = 0; i < array->Length(); ++i) { 634 | v8::Local x; 635 | if (array->Get(isolate->GetCurrentContext(), i).ToLocal(&x)) 636 | a->buf.s[off++] = (uint8_t)x->Uint32Value(isolate->GetCurrentContext()).FromMaybe(0); 637 | } 638 | a->buf.l = off; 639 | } else if (args[0]->IsArrayBuffer()) { 640 | void *data = args[0].As()->GetBackingStore()->Data(); 641 | int64_t len = args[0].As()->GetBackingStore()->ByteLength(); 642 | K8_GROW0(uint8_t, a->buf.s, off + len, a->buf.m); 643 | memcpy(&a->buf.s[off], data, len); 644 | a->buf.l = off + len; 645 | } else { 646 | isolate->ThrowError("[k8_bytes_set] unsupported type"); 647 | } 648 | args.GetReturnValue().Set((int32_t)(a->buf.l - pre)); 649 | } 650 | 651 | static void k8_bytes_toString(const v8::FunctionCallbackInfo &args) 652 | { 653 | v8::Isolate *isolate = args.GetIsolate(); 654 | v8::HandleScope handle_scope(isolate); 655 | k8_bytes_t *a = K8_LOAD_PTR(args, 0, k8_bytes_t); 656 | if (a == 0) return; 657 | v8::Local str; 658 | if (v8::String::NewFromOneByte(args.GetIsolate(), (uint8_t*)a->buf.s, v8::NewStringType::kNormal, a->buf.l).ToLocal(&str)) 659 | args.GetReturnValue().Set(str); 660 | } 661 | 662 | static void k8_bytes_length_getter(v8::Local property, const v8::PropertyCallbackInfo &info) 663 | { 664 | v8::HandleScope handle_scope(info.GetIsolate()); 665 | k8_bytes_t *a = K8_LOAD_PTR(info, 0, k8_bytes_t); 666 | if (a == 0) return; 667 | info.GetReturnValue().Set((int32_t)a->buf.l); 668 | } 669 | 670 | static void k8_bytes_length_setter(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo &info) 671 | { 672 | v8::HandleScope handle_scope(info.GetIsolate()); 673 | k8_bytes_t *a = K8_LOAD_PTR(info, 0, k8_bytes_t); 674 | if (a == 0) return; 675 | int64_t len = value->IntegerValue(info.GetIsolate()->GetCurrentContext()).FromMaybe(a->buf.l); 676 | if (len > a->buf.m) K8_GROW0(uint8_t, a->buf.s, len - 1, a->buf.m); 677 | a->buf.l = len; 678 | } 679 | 680 | static void k8_bytes_capacity_getter(v8::Local property, const v8::PropertyCallbackInfo &info) 681 | { 682 | v8::HandleScope handle_scope(info.GetIsolate()); 683 | k8_bytes_t *a = K8_LOAD_PTR(info, 0, k8_bytes_t); 684 | if (a == 0) return; 685 | info.GetReturnValue().Set((int32_t)a->buf.m); 686 | } 687 | 688 | static void k8_bytes_capacity_setter(v8::Local property, v8::Local value, const v8::PropertyCallbackInfo &info) 689 | { 690 | v8::HandleScope handle_scope(info.GetIsolate()); 691 | k8_bytes_t *a = K8_LOAD_PTR(info, 0, k8_bytes_t); 692 | if (a == 0) return; 693 | int64_t len = value->IntegerValue(info.GetIsolate()->GetCurrentContext()).FromMaybe(a->buf.m); 694 | if (len < a->buf.l) len = a->buf.l; 695 | a->buf.m = len; 696 | a->buf.s = K8_REALLOC(uint8_t, a->buf.s, a->buf.m); 697 | } 698 | 699 | static void k8_ext_delete_cb(void *data, size_t len, void *aux) {} // do nothing 700 | 701 | static void k8_bytes_buffer_getter(v8::Local property, const v8::PropertyCallbackInfo &info) 702 | { 703 | v8::HandleScope handle_scope(info.GetIsolate()); 704 | k8_bytes_t *a = K8_LOAD_PTR(info, 0, k8_bytes_t); 705 | if (a == 0) return; 706 | info.GetReturnValue().Set(v8::ArrayBuffer::New(info.GetIsolate(), v8::ArrayBuffer::NewBackingStore((uint8_t*)a->buf.s, a->buf.l, k8_ext_delete_cb, 0))); 707 | } 708 | 709 | /********************** 710 | *** The File class *** 711 | **********************/ 712 | 713 | static void k8_file_open(const v8::FunctionCallbackInfo &args) 714 | { 715 | v8::Isolate *isolate = args.GetIsolate(); 716 | v8::HandleScope handle_scope(isolate); 717 | k8_file_t *ks = 0; 718 | int fd = args.Length() >= 1 && args[0]->IsUint32()? args[0]->Int32Value(isolate->GetCurrentContext()).FromMaybe(-1) : -1; 719 | if (args.Length() >= 2) { // File(fn, mode) or File(fd, mode) 720 | v8::String::Utf8Value mode(isolate, args[1]); 721 | if (fd >= 0) { // File(fd, mode) 722 | ks = ks_open(fd, 0, *mode); 723 | } else { // File(fn, mode) 724 | v8::String::Utf8Value fn(isolate, args[0]); 725 | ks = ks_open(-1, *fn, *mode); 726 | } 727 | } else if (args.Length() == 1) { // File(fn) or File(fd) 728 | if (fd >= 0) { 729 | ks = ks_open(fd, 0, 0); 730 | } else { 731 | v8::String::Utf8Value fn(isolate, args[0]); 732 | ks = ks_open(-1, *fn, 0); 733 | } 734 | } else { // File() 735 | ks = ks_open(0, 0, 0); // open stdin for reading 736 | } 737 | if (ks) { 738 | K8_SAVE_PTR(args, 0, ks); 739 | } else { 740 | isolate->ThrowError("k8_open: failed to open file"); 741 | args.GetReturnValue().SetNull(); 742 | } 743 | } 744 | 745 | static void k8_file_close(const v8::FunctionCallbackInfo &args) 746 | { 747 | v8::HandleScope handle_scope(args.GetIsolate()); 748 | k8_file_t *ks = K8_LOAD_PTR(args, 0, k8_file_t); 749 | if (ks == 0) return; 750 | ks_close(ks); 751 | K8_SAVE_PTR(args, 0, 0); 752 | args.GetReturnValue().Set(0); 753 | } 754 | 755 | static int32_t k8_get_sep_off(const v8::FunctionCallbackInfo &args, int32_t *sep) 756 | { 757 | v8::Isolate *isolate = args.GetIsolate(); 758 | *sep = KS_SEP_LINE; 759 | if (args.Length() >= 2) { 760 | if (args[1]->IsString()) { 761 | v8::String::Utf8Value str(isolate, args[0]); 762 | *sep = (*str)[0]; 763 | } else if (args[1]->IsInt32()) { 764 | *sep = args[1]->Int32Value(isolate->GetCurrentContext()).FromMaybe(KS_SEP_LINE); 765 | } 766 | } 767 | return args.Length() >= 3? args[2]->Int32Value(isolate->GetCurrentContext()).FromMaybe(0) : 0; 768 | } 769 | 770 | static void k8_file_readline(const v8::FunctionCallbackInfo &args) 771 | { 772 | v8::Isolate *isolate = args.GetIsolate(); 773 | v8::HandleScope handle_scope(isolate); 774 | k8_file_t *ks = K8_LOAD_PTR(args, 0, k8_file_t); 775 | if (ks == 0) return; 776 | if (!args.Length() || !args[0]->IsObject()) { 777 | args.GetReturnValue().Set(-2); 778 | return; 779 | } 780 | v8::Handle b = v8::Handle::Cast(args[0]); 781 | k8_bytes_t *a = (k8_bytes_t*)b->GetAlignedPointerFromInternalField(0); 782 | if (a->magic != K8_BYTES_MAGIC) { 783 | args.GetReturnValue().Set(-2); 784 | } else { 785 | int32_t dret, sep; 786 | a->buf.l = k8_get_sep_off(args, &sep); 787 | int64_t ret = ks_getuntil2(ks, sep, &a->buf, &dret, 1); 788 | if (ret >= 0) args.GetReturnValue().Set(dret); 789 | else args.GetReturnValue().Set((int32_t)ret); 790 | } 791 | } 792 | 793 | static void k8_file_read(const v8::FunctionCallbackInfo &args) 794 | { 795 | v8::Isolate *isolate = args.GetIsolate(); 796 | v8::HandleScope handle_scope(isolate); 797 | k8_file_t *ks = K8_LOAD_PTR(args, 0, k8_file_t); 798 | if (ks == 0) return; 799 | if (args.Length() == 0) { // prototype.read() 800 | int32_t c = ks_getc(ks); 801 | args.GetReturnValue().Set(c); 802 | } else if (args.Length() >= 1 && args[0]->IsObject()) { 803 | v8::Handle b = v8::Handle::Cast(args[0]); 804 | k8_bytes_t *a = (k8_bytes_t*)b->GetAlignedPointerFromInternalField(0); 805 | if (a->magic != K8_BYTES_MAGIC) { // TODO: well, there got to be a better way 806 | args.GetReturnValue().Set(-2); 807 | return; 808 | } 809 | uint32_t off = a->buf.l; 810 | if (args.Length() >= 2 && args[1]->IsUint32()) 811 | off = args[1]->Uint32Value(isolate->GetCurrentContext()).FromMaybe(0); 812 | if (args.Length() == 3 && args[1]->IsUint32() && args[2]->IsUint32()) { // prototype.read(bytes, off, len) 813 | uint32_t len = args[2]->Uint32Value(isolate->GetCurrentContext()).FromMaybe(0); 814 | K8_GROW(uint8_t, a->buf.s, off + len - 1, a->buf.m); 815 | int64_t ret = ks_read(ks, &a->buf.s[off], len); 816 | if (ret > 0 && a->buf.l < off + ret) a->buf.l = off + ret; 817 | args.GetReturnValue().Set((int32_t)ret); 818 | } else if (args.Length() == 1 || (args.Length() == 2 && args[1]->IsUint32())) { // prototype.read(bytes) or prototype.read(bytes, off) 819 | kstring_t tmp = {0,0,0}; 820 | ks_read_all(ks, &tmp); 821 | K8_GROW(uint8_t, a->buf.s, off + tmp.l, a->buf.m); 822 | memcpy(&a->buf.s[off], tmp.s, tmp.l); 823 | a->buf.l = off + tmp.l; 824 | free(tmp.s); 825 | args.GetReturnValue().Set((int32_t)tmp.l); 826 | } 827 | } 828 | } 829 | 830 | static void k8_file_write(const v8::FunctionCallbackInfo &args) 831 | { 832 | v8::HandleScope handle_scope(args.GetIsolate()); 833 | k8_file_t *ks = K8_LOAD_PTR(args, 0, k8_file_t); 834 | if (ks == 0) return; 835 | if (ks->magic != K8_FILE_MAGIC || ks->fpw == 0) { 836 | args.GetReturnValue().Set(-1); 837 | return; 838 | } else if (args[0]->IsArrayBuffer()) { 839 | void *data = args[0].As()->GetBackingStore()->Data(); 840 | int64_t len = args[0].As()->GetBackingStore()->ByteLength(); 841 | assert(len >= 0 && len < INT32_MAX); 842 | if (len > 0) fwrite(data, 1, len, ks->fpw); 843 | args.GetReturnValue().Set((int32_t)len); 844 | } else if (args[0]->IsString()) { 845 | int32_t len = args[0].As()->Length(); 846 | uint8_t *buf; 847 | buf = K8_MALLOC(uint8_t, len); 848 | args[0].As()->WriteOneByte(args.GetIsolate(), buf); 849 | if (len > 0) fwrite(buf, 1, len, ks->fpw); 850 | free(buf); 851 | args.GetReturnValue().Set(len); 852 | } 853 | } 854 | 855 | /*********************** 856 | *** Getopt from BSD *** 857 | ***********************/ 858 | 859 | // Modified from getopt.c from BSD, 3-clause BSD license. Copyright (c) 1987-2002 The Regents of the University of California. 860 | // We do not use the system getopt() because it may parse "-v" in "k8 prog.js -v". 861 | 862 | int opterr = 1, optind = 1, optopt, optreset; 863 | char *optarg; 864 | 865 | int getopt(int nargc, char * const *nargv, const char *ostr) 866 | { 867 | static char *place = 0; 868 | const char *oli; 869 | if (optreset || !place || !*place) { 870 | optreset = 0; 871 | if (optind >= nargc || *(place = nargv[optind]) != '-') { 872 | place = 0; 873 | return -1; 874 | } 875 | if (place[1] && *++place == '-') { 876 | ++optind, place = 0; 877 | return -1; 878 | } 879 | } 880 | if ((optopt = *place++) == ':' || !(oli = strchr(ostr, optopt))) { 881 | if (optopt == '-') return -1; 882 | if (!*place) ++optind; 883 | if (opterr && *ostr != ':') fprintf(stderr, "%s: illegal option -- %c\n", __FILE__, optopt); 884 | return '?'; 885 | } 886 | if (*++oli != ':') { 887 | optarg = 0; 888 | if (!*place) ++optind; 889 | } else { 890 | if (*place) optarg = place; 891 | else if (nargc <= ++optind) { 892 | place = 0; 893 | if (*ostr == ':') return ':'; 894 | if (opterr) fprintf(stderr, "%s: option requires an argument -- %c\n", __FILE__, optopt); 895 | return '?'; 896 | } else optarg = nargv[optind]; 897 | place = 0; 898 | ++optind; 899 | } 900 | return optopt; 901 | } 902 | 903 | /********************* 904 | *** Main function *** 905 | *********************/ 906 | 907 | static v8::Local k8_create_shell_context(v8::Isolate* isolate) 908 | { 909 | v8::Local global = v8::ObjectTemplate::New(isolate); 910 | global->Set(isolate, "print", v8::FunctionTemplate::New(isolate, k8_print)); 911 | global->Set(isolate, "warn", v8::FunctionTemplate::New(isolate, k8_warn)); 912 | global->Set(isolate, "exit", v8::FunctionTemplate::New(isolate, k8_exit)); 913 | global->Set(isolate, "load", v8::FunctionTemplate::New(isolate, k8_load)); 914 | global->Set(isolate, "k8_read_file", v8::FunctionTemplate::New(isolate, k8_read_file)); 915 | global->Set(isolate, "k8_encode", v8::FunctionTemplate::New(isolate, k8_encode)); 916 | global->Set(isolate, "k8_decode", v8::FunctionTemplate::New(isolate, k8_decode)); 917 | global->Set(isolate, "k8_revcomp", v8::FunctionTemplate::New(isolate, k8_revcomp)); 918 | global->Set(isolate, "k8_version", v8::FunctionTemplate::New(isolate, k8_version)); 919 | { // add the 'Bytes' object 920 | v8::HandleScope scope(isolate); 921 | v8::Handle ft = v8::FunctionTemplate::New(isolate, k8_bytes_new); 922 | ft->SetClassName(v8::String::NewFromUtf8Literal(isolate, "Bytes")); 923 | 924 | v8::Handle ot = ft->InstanceTemplate(); 925 | ot->SetInternalFieldCount(1); 926 | ot->SetAccessor(v8::String::NewFromUtf8Literal(isolate, "length"), k8_bytes_length_getter, k8_bytes_length_setter); 927 | ot->SetAccessor(v8::String::NewFromUtf8Literal(isolate, "capacity"), k8_bytes_capacity_getter, k8_bytes_capacity_setter); 928 | ot->SetAccessor(v8::String::NewFromUtf8Literal(isolate, "buffer"), k8_bytes_buffer_getter); 929 | 930 | v8::Handle pt = ft->PrototypeTemplate(); 931 | pt->Set(isolate, "destroy", v8::FunctionTemplate::New(isolate, k8_bytes_destroy)); 932 | pt->Set(isolate, "set", v8::FunctionTemplate::New(isolate, k8_bytes_set)); 933 | pt->Set(isolate, "toString", v8::FunctionTemplate::New(isolate, k8_bytes_toString)); 934 | global->Set(isolate, "Bytes", ft); 935 | } 936 | { // add the 'File' object 937 | v8::HandleScope scope(isolate); 938 | v8::Handle ft = v8::FunctionTemplate::New(isolate, k8_file_open); 939 | ft->SetClassName(v8::String::NewFromUtf8Literal(isolate, "File")); 940 | 941 | v8::Handle ot = ft->InstanceTemplate(); 942 | ot->SetInternalFieldCount(1); 943 | 944 | v8::Handle pt = ft->PrototypeTemplate(); 945 | pt->Set(isolate, "read", v8::FunctionTemplate::New(isolate, k8_file_read)); 946 | pt->Set(isolate, "readline", v8::FunctionTemplate::New(isolate, k8_file_readline)); 947 | pt->Set(isolate, "write", v8::FunctionTemplate::New(isolate, k8_file_write)); 948 | pt->Set(isolate, "close", v8::FunctionTemplate::New(isolate, k8_file_close)); 949 | pt->Set(isolate, "destroy", v8::FunctionTemplate::New(isolate, k8_file_close)); 950 | global->Set(isolate, "File", ft); 951 | } 952 | return v8::Context::New(isolate, NULL, global); 953 | } 954 | 955 | static int k8_main(v8::Isolate *isolate, v8::Platform *platform, v8::Local &context, int argc, char* argv[]) 956 | { 957 | // parse command-line options 958 | int c; 959 | while ((c = getopt(argc, argv, "e:E:vM:m:")) >= 0) { 960 | if (c == 'e' || c == 'E') { // execute a string 961 | v8::Local file_name = v8::String::NewFromUtf8Literal(isolate, "unnamed"); 962 | v8::Local source; 963 | if (!v8::String::NewFromUtf8(isolate, optarg).ToLocal(&source)) 964 | return 1; 965 | bool success = k8_execute(isolate, source, file_name, (c == 'E')); 966 | while (v8::platform::PumpMessageLoop(platform, isolate)) continue; 967 | return success? 0 : 1; 968 | } else if (c == 'v') { 969 | printf("v8: %s\nk8: %s\n", v8::V8::GetVersion(), K8_VERSION); 970 | return 0; 971 | } else if (c == 'm' || c == 'M') { // do nothing as this has been parsed in k8_set_mem 972 | } else { 973 | fprintf(stderr, "ERROR: unrecognized option\n"); 974 | return 1; 975 | } 976 | } 977 | if (optind == argc) { 978 | fprintf(stderr, "Usage: k8 [options] [arguments]\n"); 979 | fprintf(stderr, "Options:\n"); 980 | fprintf(stderr, " -e STR execute STR\n"); 981 | fprintf(stderr, " -E STR execute STR and print results\n"); 982 | fprintf(stderr, " -m INT v8 max size of the old space (in Mbytes) [16384]\n"); 983 | fprintf(stderr, " -v print version number\n"); 984 | fprintf(stderr, " --help show v8 command-line options\n"); 985 | return 0; 986 | } 987 | 988 | // pass command-line arguments though the "arguments" array 989 | v8::Local args = v8::Array::New(isolate, argc - optind - 1); 990 | for (int i = optind + 1; i < argc; ++i) 991 | args->Set(context, i - optind - 1, v8::String::NewFromUtf8(isolate, argv[i]).ToLocalChecked()).FromJust(); 992 | v8::Local name = v8::String::NewFromUtf8Literal(isolate, "arguments", v8::NewStringType::kInternalized); 993 | context->Global()->Set(context, name, args).FromJust(); 994 | 995 | // load and evaluate the source file 996 | k8_src_path = argv[optind]; 997 | v8::Local file_name = v8::String::NewFromUtf8(isolate, argv[optind]).ToLocalChecked(); 998 | v8::Local source; 999 | if (!k8_readfile(isolate, argv[optind]).ToLocal(&source)) { 1000 | fprintf(stderr, "ERROR: failed to read file '%s'\n", argv[optind]); 1001 | return 1; 1002 | } 1003 | bool success = k8_execute(isolate, source, file_name, false); 1004 | while (v8::platform::PumpMessageLoop(platform, isolate)) continue; 1005 | return success? 0 : 1; 1006 | } 1007 | 1008 | void k8_set_mem(int argc, char *argv[]) 1009 | { 1010 | int c; 1011 | char buf[64], *ptr_size = 0; 1012 | while ((c = getopt(argc, argv, "ve:E:M:m:")) >= 0) 1013 | if (c == 'M' || c == 'm') ptr_size = optarg; 1014 | optreset = optind = opterr = 1; 1015 | if (ptr_size && strlen(ptr_size) > 40) { 1016 | fprintf(stderr, "ERROR: failed to set max_old_space_size\n"); 1017 | return; 1018 | } 1019 | strcat(strcpy(buf, "--max_old_space_size="), ptr_size? ptr_size : "16384"); 1020 | v8::V8::SetFlagsFromString(buf, strlen(buf)); 1021 | } 1022 | 1023 | int main(int argc, char *argv[]) 1024 | { 1025 | int ret = 0; 1026 | k8_set_mem(argc, argv); 1027 | v8::V8::InitializeICUDefaultLocation(argv[0]); 1028 | v8::V8::InitializeExternalStartupData(argv[0]); 1029 | std::unique_ptr platform = v8::platform::NewDefaultPlatform(); 1030 | v8::V8::InitializePlatform(platform.get()); 1031 | v8::V8::SetFlagsFromCommandLine(&argc, argv, true); 1032 | v8::V8::Initialize(); 1033 | v8::Isolate::CreateParams create_params; 1034 | create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator(); 1035 | v8::Isolate* isolate = v8::Isolate::New(create_params); 1036 | { 1037 | v8::Isolate::Scope isolate_scope(isolate); 1038 | v8::HandleScope handle_scope(isolate); 1039 | v8::Local context = k8_create_shell_context(isolate); 1040 | if (context.IsEmpty()) { 1041 | fprintf(stderr, "ERROR: failed to create context\n"); 1042 | return 1; 1043 | } 1044 | v8::Context::Scope context_scope(context); 1045 | ret = k8_main(isolate, platform.get(), context, argc, argv); 1046 | } 1047 | isolate->Dispose(); 1048 | v8::V8::Dispose(); 1049 | v8::V8::DisposePlatform(); 1050 | delete create_params.array_buffer_allocator; 1051 | return ret; 1052 | } 1053 | --------------------------------------------------------------------------------