├── .gitignore ├── LICENSE.js ├── README.md ├── boot ├── boot.js ├── doc ├── extend.md ├── ffi.md ├── internals.md ├── node.md ├── threads.md └── web.md ├── get_icons ├── get_kl ├── make.shen ├── module.shen ├── package.json ├── patch.shen ├── runtime.js ├── shen-js.shen ├── shen-node.js ├── shen.html ├── shen.js ├── tests └── test.js └── web ├── .gitignore ├── boot.js ├── document.png ├── download.png ├── edit.js ├── embed.js ├── folder.png ├── folder_new.png ├── folder_open.png ├── fs.js ├── html.png ├── jsfile.js ├── loader_github.js ├── loader_http.js ├── maximize.png ├── new.png ├── repl.js ├── revert.png ├── rm.png ├── run.png ├── save.png ├── shen.css ├── shen_source.png ├── store.js ├── upload.png ├── util.js └── wait.gif /.gitignore: -------------------------------------------------------------------------------- 1 | *.kl 2 | primitives.js 3 | ./node_modules/* 4 | shen_bare.js 5 | shen_boot_image.js -------------------------------------------------------------------------------- /LICENSE.js: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2015, Mark Tarver 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 3. The name of Mark Tarver may not be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY Mark Tarver ''AS IS'' AND ANY EXPRESS OR IMPLIED 16 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 17 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 18 | EVENT SHALL Mark Tarver BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 21 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 22 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. */ 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Shen-js 2 | ======= 3 | Javascript port of [Shen](http://shenlanguage.org) language. You may want to 4 | try it [online](http://gravicappa.github.io/shen-js/shen.html#.doc/welcome.html). 5 | 6 | ## Running console REPL 7 | 8 | Type in your terminal (choose a line depending on your js interpreter): 9 | 10 | d8 -e 'load("shen.js"); shen.console_repl()' 11 | js -e 'load("shen.js"); shen.console_repl()' 12 | 13 | If you want to carry console Shen-js around, you need only `shen.js` file. 14 | 15 | ## Running in Node.js 16 | See `doc/node.md`. 17 | 18 | ## Running in a browser 19 | 20 | Just open `shen.html` in a browser. 21 | 22 | If you want to set up your own web REPL then copy `shen.html`, `shen.js` and 23 | `web` directory somewhere to your webroot. Also see `doc/web.md`. 24 | 25 | ## JS integration 26 | See `doc/ffi.md` 27 | 28 | ## Making your own REPL and I/O 29 | See `doc/extend.md`. 30 | 31 | ## Building shen.js from sources 32 | First ensure that you have latest 33 | [modulesys and shen-libs](https://github.com/vasil-sd/shen-libs), 34 | [klvm](https://github.com/gravicappa/klvm), 35 | [js-kl](https://github.com/gravicappa/js-kl) and 36 | [shen-js](https://github.com/gravicappa/shen-js). Then if you have 37 | [shen_run](https://github.com/gravicappa/shen_run) and have set up your 38 | `modulesys` correctly you can just call 39 | 40 | ./make.shen new_shen.js && ./boot 41 | 42 | to have shen-js built into new_shen.js file. The `boot` step is unneccessary 43 | and it makes file twice as big, but it significantly decreases start time. 44 | -------------------------------------------------------------------------------- /boot: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | js="${1:-d8}" 3 | tmp=shen_tmp.js 4 | img=shen_boot_image.js 5 | 6 | which $js >/dev/null 2>&1 || js=d8 7 | which $js >/dev/null 2>&1 || js=js 8 | code=' 9 | shen_bootstrap = true; 10 | load("shen.js"); 11 | load("boot.js"); 12 | ' 13 | $js -d -e "$code" >"$img" 14 | cat shen.js "$img" >"$tmp" 15 | cp shen.js shen_bare.js 16 | mv "$tmp" shen.js 17 | -------------------------------------------------------------------------------- /boot.js: -------------------------------------------------------------------------------- 1 | boot = (function() { 2 | var self = {}; 3 | 4 | function sub(a, b) { 5 | var index = {}, na = a.length, nb = b.length, i, ret = []; 6 | for (i = 0; i < nb; ++i) 7 | index[b[i]] = true; 8 | for (i = 0; i < na; ++i) { 9 | var x = a[i]; 10 | if (!index[x]) 11 | ret.push(x); 12 | } 13 | return ret; 14 | } 15 | 16 | function emit_glob(buf, key, x) { 17 | buf.push("vm.glob[\"" + key + "\"] = " + x + ";"); 18 | } 19 | 20 | function esc(s) { 21 | return '"' + s.replace(/"/g, '\\"') + '"'; 22 | } 23 | 24 | function bootstrap() { 25 | var prev_glob_keys = Object.keys(shen.glob); 26 | orig_eval = shen.eval, 27 | buf = []; 28 | try { 29 | shen.eval = function(x) { 30 | buf.push(x); 31 | return orig_eval.call(this, x); 32 | }; 33 | shen.init({io: shen.console_io}); 34 | } finally { 35 | shen.eval = orig_eval; 36 | } 37 | var glob_keys = sub(sub(Object.keys(shen.glob), prev_glob_keys), 38 | ["*stoutput*", "*stinput*"]), 39 | nglob_keys = glob_keys.length; 40 | var glob = []; 41 | for (var i = 0; i < nglob_keys; ++i) { 42 | var key = glob_keys[i], obj = shen.glob[key]; 43 | switch (typeof(obj)) { 44 | case "number": case "boolean": emit_glob(glob, key, obj); break; 45 | case "string": emit_glob(glob, key, esc(obj)); break; 46 | default: 47 | if (obj instanceof Array && !obj.length) 48 | emit_glob(glob, key, "[]"); 49 | else { 50 | var json = shen.json_from_obj(obj); 51 | emit_glob(glob, key, "vm.obj_from_json(" + esc(json) + ")"); 52 | } 53 | } 54 | } 55 | print("(function(vm) {\n " 56 | + buf.join("\n") 57 | + "\n\n" + glob.join("\n ") 58 | + "\n vm.toplevels = [];" 59 | + "\n})(shen);"); 60 | } 61 | 62 | bootstrap(); 63 | return self; 64 | })(); 65 | -------------------------------------------------------------------------------- /doc/extend.md: -------------------------------------------------------------------------------- 1 | # Extending Shen-js 2 | 3 | ## Implementing own I/O 4 | It's easier to look into `web/embed.js` to see how to set up custom I/O. It 5 | consists of defining I/O initialisation function and passing it to `shen.init` 6 | by `io` key: 7 | 8 | function io(vm) { 9 | var io_obj = {}; 10 | io_obj.open = function(name, direction, vm) { 11 | … 12 | }; 13 | vm.glob["*stoutput*"] = vm.Stream(null, out_write_byte, out_close); 14 | 15 | /* you can either directly assign a stream as an input */ 16 | vm.glob["*stinput*"] = vm.Stream(in_read_byte, null, in_close); 17 | /* 18 | or you can initialise input channel: 19 | 20 | vm.ensure_chan_input(); 21 | 22 | and then use 23 | 24 | vm.send_str(some_string); 25 | 26 | to pass data to it 27 | */ 28 | } 29 | 30 | shen.init({io: io, …}); 31 | 32 | ### Stream class 33 | Objects (stream in) or a (stream out) are represented in Shen-js as objects of 34 | Stream class. Output streams has "w" character in their `dir` member and input 35 | streams has "r" character. 36 | 37 | /* defining output stream */ 38 | var out = vm.Stream(null, 39 | function write_byte(byte, vm) { 40 | … 41 | }, 42 | function close(vm) { 43 | … 44 | }); 45 | 46 | /* defining input stream */ 47 | var inp = vm.Stream(function read_byte(vm) { 48 | … 49 | }, 50 | null, 51 | function close(vm) { 52 | … 53 | }); 54 | 55 | Notes: 56 | 57 | * `vm` parameter is a reference to shen-js thread that is calling 58 | corresponding methods. 59 | * `read_byte` should return -1 when end of file is reached. 60 | -------------------------------------------------------------------------------- /doc/ffi.md: -------------------------------------------------------------------------------- 1 | # Shen-js FFI 2 | 3 | ## General Javascipt code 4 | `js.'` injects raw Javascript code. Use it with caution. Trying to access 5 | internal interpreter variables (`vm`, `reg`, `sp`) may lead to unexpected 6 | results. 7 | 8 | (let Add (js.' "function(x, y) { 9 | return x + y; 10 | }") 11 | (js.call Add 3 7)) 12 | 13 | ## Referencing object properties or array items 14 | Use `js.` to reference object's property: 15 | 16 | (js. document body children 0) 17 | 18 | that will be translated to 19 | 20 | document.body.children[0] 21 | 22 | Keep in mind that `js.` primitive will strip package names from symbols to 23 | simplify using it inside packages. But it also means that the code 24 | `(js. document.body children)` may not do that is expected to. 25 | 26 | ## Calling native code 27 | `js.call` wraps it's arguments to a function call: 28 | 29 | (js. console (js.call log "Yellow pants! Double qu!")) 30 | 31 | is expanded to 32 | 33 | console.log("Yellow pants! Double qu!") 34 | 35 | ## Setting values 36 | 37 | (js.set (js. document (js.call getElementById "some_button")) 38 | (js.' "function() { 39 | console.log('click!'); 40 | }")) 41 | 42 | => 43 | 44 | document.getElementById("some_button") = function() { 45 | console.log('click!'); 46 | }; 47 | 48 | ## Creating objects 49 | 50 | (js.new Array 3 1 2 3) 51 | 52 | => 53 | 54 | new Array(3, 1, 2, 3) 55 | 56 | ## Creating literal objects 57 | 58 | (js.obj language "Shen" host_language "Javascript" ui "Web") 59 | 60 | => 61 | 62 | {language: "Shen", host_language: "Javascript", ui: "Web") 63 | 64 | ## Creating arrays 65 | 66 | (js.arr 1 "two" 3 "four" 5) 67 | 68 | => 69 | 70 | [1, "two", 3, "four", 5] 71 | 72 | ## Example 73 | See examples/ffi.shen. 74 | -------------------------------------------------------------------------------- /doc/internals.md: -------------------------------------------------------------------------------- 1 | # Shen-js internals 2 | ## Translating to Javascript 3 | There are several function for translating from different kind of source data 4 | to Javascript code: 5 | 6 | (js.from-file Filename) 7 | (js.from-files List-of-filenames) 8 | (js.from-string String) 9 | (js.from-shen List-of-Shen-code) 10 | (js.from-kl List-of-Kl-code) 11 | (js.save-from-files List-of-filenames Target-filename) 12 | 13 | ## Interrupting vm 14 | Shen-js vm can be stopped at some point of time and resumed later. It can be 15 | useful when dealing with asynchronous Javascript tasks: 16 | 17 | shen.defun("xml-http-req", function(arg) { 18 | var vm = this; 19 | xml_http_req(function ondone(result) { 20 | vm.resume(result); 21 | }, function onerr(err) { 22 | vm.resume(vm.fail_obj); 23 | }); 24 | this.interrupt(); // Note that it should be the last expression 25 | ); 26 | 27 | To be used like 28 | 29 | (let Data (xml-http-req Url) 30 | (if (= Data (fail)) 31 | (error "Request failed") 32 | (process-response Data))) 33 | 34 | You also can throw exception directly resuming with `Error` object: 35 | 36 | vm.resume(new Error("Request failed")); 37 | 38 | ## Defining Shen function 39 | Use `shen.defun` to define a Shen function. It has two modes. Called with a 40 | single function argument it takes passed function's name: 41 | 42 | // defines `hello` function in Shen 43 | shen.defun(function hello(s) { 44 | console.log("Hello, " + s); 45 | }); 46 | 47 | But it is possible to manually specify a name: 48 | 49 | // defines `hello-there` function in Shen 50 | shen.defun("hello-there", function hello(s) { 51 | console.log("Hello there, " + s); 52 | }); 53 | 54 | The function will be executed with `this` set to current interpreter vm. 55 | 56 | ## Running Shen code 57 | 58 | shen.call("some-func", [arg1, arg2, ...]); 59 | 60 | ## Shen objects 61 | ### Sym 62 | Represents a symbol. 63 | 64 | Creating 65 | 66 | var s = shen.Sym("symbol-name"); 67 | 68 | Accessing 69 | 70 | console.log(s.str); 71 | 72 | ### Cons 73 | Represents a cons cell. 74 | 75 | Creating 76 | 77 | var a = shen.Cons(head, tail); 78 | var b = shen.list([a, b, c, ...]); 79 | 80 | Accessing 81 | 82 | console.log(a.head, a.tail); 83 | 84 | ### Vector 85 | Creating 86 | 87 | var v = shen.vector(n); 88 | 89 | When accessing keep in mind that the first element `v[0]` contains the length 90 | of the vector. 91 | 92 | ### Fail object 93 | 94 | shen.fail_obj 95 | 96 | ### Func 97 | A function object. 98 | 99 | Creating 100 | 101 | var f = shen.Func(name, arity, func, closure_vars); 102 | 103 | **Note:** using `shen.defun` is recommended instead. Bare `Func` objects do 104 | not handle partial application. 105 | 106 | ### Stream 107 | Creating 108 | 109 | var stream = shen.Stream(read_byte, write_byte, close); 110 | 111 | * `read_byte(vm)`: returns next byte from a stream. Returns -1 if stream is 112 | exhausted. 113 | * `write_byte(byte, vm)`: writes a byte to a stream. Returns the byte written. 114 | * `close`: a function which is called when a stream is closed. 115 | 116 | ### Other 117 | 118 | There are other objects which you can discover in the beginning of `shen.js` 119 | (or in `runtime.js`). 120 | -------------------------------------------------------------------------------- /doc/node.md: -------------------------------------------------------------------------------- 1 | # Running in Node.js 2 | 3 | The current Node.js port support compiling, executing source files as well as 4 | REPL. It also reads '~/.shen.shen' initialization file where you can setup 5 | things like modulesys paths. Use `-noinit` flag to skip init file. 6 | 7 | For REPL execute `shen-node.js` script 8 | 9 | ./shen-node.js 10 | 11 | To load a list of files do 12 | 13 | ./shen-node.js file1.shen ... fileN.shen 14 | 15 | To compile files into .js 16 | 17 | ./shen-node.js -c target.js file1.shen ... fileN.shen 18 | -------------------------------------------------------------------------------- /doc/threads.md: -------------------------------------------------------------------------------- 1 | # Shen-js threads 2 | Shen-js's threads are green threads. 3 | 4 | ## Threads 5 | `js.make-thread` creates a thread and starts it. It receives a single argument 6 | which can be either zero-place function object or freeze object: 7 | 8 | (js.make-thread (function some-code)) 9 | (js.make-thread (freeze (some-code Arg1))) 10 | 11 | It returns a thread id which is not used anywhere outside. 12 | 13 | ## Channels 14 | Channels are stream-like facilities suitable for inter-thread communication. 15 | If a thread tries to read from channel with empty buffer it becomes dormant 16 | until channel is written to. Reading from closed channel returns `fail`. 17 | 18 | Create a channel 19 | 20 | (js.chan) 21 | 22 | Read from a channel 23 | 24 | (js.chan-read Ch) 25 | 26 | Write to a channel 27 | 28 | (js.chan-write Value Ch) 29 | 30 | Close a channel 31 | 32 | (js.chan-close Ch) 33 | 34 | ## Miscellaneous 35 | To stop current thread for a time of given milliseconds 36 | 37 | (js.sleep-ms Milliseconds) 38 | 39 | ## Example 40 | See examples/threads.shen. 41 | -------------------------------------------------------------------------------- /doc/web.md: -------------------------------------------------------------------------------- 1 | # Shen-js web 2 | ## Github loader 3 | If you want to load file 'dir/file.shen' from github repo 4 | 'https://github.com/user/repo' just type 5 | 6 | (load "https://github.com/user/repo/dir/file.shen") 7 | 8 | Also you can add use [shen-libs](https://github.com/vasil-sd/shen-libs) by 9 | just adding it to modulesys search path: 10 | 11 | (module.add-path "https://github.com/vasil-sd/shen-libs") 12 | 13 | Then you can simply use libraries from the repo: 14 | 15 | (module.use [maths defstruct]) 16 | 17 | ## Initialization file 18 | You can create `.init.shen` file which is loaded on startup. `module.add-path` 19 | can be added there. 20 | 21 | ## Deploying Shen-js repl 22 | To simply deploy Shen-js repl copy the following files/directories to some 23 | place in your webroot: 24 | 25 | * shen.js 26 | * shen.html 27 | * web/ 28 | 29 | It will have its filesystem empty though. If you want to fill it with some 30 | initial content then read the following section. 31 | 32 | ### Deploying synthetic filesystem 33 | Shen-js web UI tries to load (via xmlhttprequest/ajax) file `fs.json` located 34 | in the same directory with `shen.html` which defines which files to load and 35 | has simple structure: 36 | 37 | [ 38 | {"from": "./relative/link", to: "local/path"}, 39 | {"from": "http://absolute/link/...", to: "local/path"}, 40 | ... 41 | ] 42 | 43 | ### Adding own plugins 44 | Add your file to `files` list in 'web/boot.js'. Plugin initialisation code 45 | should be added to `shen_web.plugins` array. See 'web/loader_github.js' for a 46 | reference. 47 | -------------------------------------------------------------------------------- /get_icons: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #\ 3 | exec jimsh "$0" "$@" 4 | 5 | set size 16x16 6 | set theme [expr {([lindex $argv 0] ne "") ? [lindex $argv 0] : "tango"}] 7 | 8 | set themes { 9 | gnome2 { 10 | paths { 11 | ~/.icons/OldGNOME2/16x16/*/* 12 | } 13 | } 14 | late-afternoon { 15 | paths { 16 | ~/.icons/LateAfternoon/*/small/* 17 | ~/.icons/LateAfternoon/*/big/* 18 | } 19 | } 20 | faenza { 21 | paths { 22 | ~/.icons/Faenza/*/16/* 23 | } 24 | } 25 | faience { 26 | paths { 27 | ~/.icons/Faience/*/16/* 28 | } 29 | } 30 | baku { 31 | paths { 32 | ~/.icons/Baku/16x16/*/* 33 | ~/.icons/Baku/scalable/*/* 34 | } 35 | map { 36 | user-trash stock_delete 37 | } 38 | } 39 | tango { 40 | paths { 41 | ~/.icons/tango/16x16/*/* 42 | ~/.icons/tango/scalable/*/* 43 | /usr/share/icons/Tango/16x16/*/* 44 | } 45 | map { 46 | utilities-terminal system-run 47 | user-trash stock_delete 48 | view-refresh document-revert 49 | } 50 | } 51 | } 52 | 53 | proc collect {} { 54 | global theme themes env index 55 | set i 0 56 | set paths [concat [dict get $themes $theme paths] \ 57 | [dict get $themes tango paths]] 58 | set map {} 59 | catch {set map [dict get $themes $theme map]} 60 | foreach p $paths { 61 | switch -glob -- $p { 62 | #* {continue} 63 | ~/* {set p [regsub {^~} $p $env(HOME)]} 64 | } 65 | foreach f [glob -nocomplain $p] { 66 | set key [regsub {\.[^.]*$} [file tail $f] {}] 67 | if {[dict exists $map $key]} { 68 | set index($i/[dict get $map $key]) $f 69 | } else { 70 | set index($i/$key) $f 71 | } 72 | } 73 | incr i 74 | } 75 | set index(n) $i 76 | } 77 | 78 | proc find_icon {name} { 79 | global index 80 | for {set i 0} {$i < $index(n)} {incr i} { 81 | set key $i/$name 82 | if {[info exists index($key)]} { 83 | return $index($key) 84 | } 85 | } 86 | } 87 | 88 | proc from_svg {from to} { 89 | global size 90 | exec convert -background none $from -resize $size $to 91 | } 92 | 93 | proc get {from to} { 94 | set src [find_icon $from] 95 | puts "; $from <- $src" 96 | switch -glob -- $src { 97 | *.png {file copy -force $src $to} 98 | *.svg {from_svg $src $to} 99 | default {puts stderr "Cannot find $from"} 100 | } 101 | } 102 | 103 | collect 104 | get document web/document.png 105 | get down web/download.png 106 | get folder web/folder.png 107 | get folder-new web/folder_new.png 108 | get folder-open web/folder_open.png 109 | get html web/html.png 110 | get view-fullscreen web/maximize.png 111 | get document web/new.png 112 | get document-revert web/revert.png 113 | get stock_delete web/rm.png 114 | get system-run web/run.png 115 | get document-save web/save.png 116 | get text-x-generic web/shen_source.png 117 | get up web/upload.png 118 | -------------------------------------------------------------------------------- /get_kl: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | root=../official 3 | 4 | set -e 5 | 6 | latest() { 7 | find "$root"/'Shen '[0-9]* -type d -maxdepth 0 | sort -V | tail -n 1 8 | } 9 | 10 | dir="$(latest)" 11 | 12 | kl_dir() { 13 | kldir="" 14 | for x in "K Lambda" "KLambda"; do 15 | test -d "$dir/$x" && kldir="$dir/$x" 16 | done 17 | test -z "$kldir" && { echo "Unable to find KLambda files">&2; exit 1;} 18 | echo "$kldir" 19 | } 20 | 21 | cp "$(kl_dir)"/*.kl "." 22 | -------------------------------------------------------------------------------- /make.shen: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env shen_run 2 | 3 | (define js.extra 4 | \\ Add extra files you want to include in Shen build (like custom 5 | \\ libraries). 6 | -> ["../shen-libs/modulesys.shen"]) 7 | 8 | (define kl.files 9 | -> ["toplevel.kl" "core.kl" "sys.kl" "sequent.kl" "yacc.kl" "reader.kl" 10 | "prolog.kl" "track.kl" "load.kl" "writer.kl" "macros.kl" 11 | "declarations.kl" "types.kl" "t-star.kl"]) 12 | 13 | (define main 14 | [Target] -> (let Fs (module.files-to-translate "shen-js" "javascript" "all") 15 | (js.save-from-files ["LICENSE.js" 16 | "runtime.js" 17 | "primitives.js" 18 | | (append (kl.files) 19 | Fs 20 | (js.extra) 21 | ["patch.shen"])] 22 | Target)) 23 | _ -> (error "Usage: make.shen target")) 24 | -------------------------------------------------------------------------------- /module.shen: -------------------------------------------------------------------------------- 1 | (register-module [[depends: "js-kl"] 2 | [author: "Ramil Farkshatov"] 3 | [translate-fn: js.translate-shen]]) 4 | 5 | (define js.translate-shen 6 | {string --> string --> (list string)} 7 | "javascript" _ -> (let . (write-to-file "primitives.js" 8 | (js.generate-primitives)) 9 | ["shen-js.shen"])) 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shen", 3 | "version": "0.2.0", 4 | "license": "BSD-3-Clause", 5 | "author": "xkxx ", 6 | "contributors": ["Ramil Farkhshatov "], 7 | "description": "Shen Node.js port", 8 | "keywords": [ 9 | "javascript", 10 | "language", 11 | "Shen", 12 | "compiler" 13 | ], 14 | "main": "shen-node.js", 15 | "bin": { 16 | "shen-node-js": "shen-node.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/gravicappa/shen-js" 21 | } 22 | } -------------------------------------------------------------------------------- /patch.shen: -------------------------------------------------------------------------------- 1 | (package shen [] 2 | (define resolve-macro-functions 3 | [] Acc -> (reverse Acc) 4 | [X | Xs] Acc -> (resolve-macro-functions 5 | Xs (trap-error [(function X) | Acc] (/. E Acc))) 6 | where (symbol? X) 7 | [X | Xs] Acc -> (resolve-macro-functions Xs [X | Acc])) 8 | 9 | (define macroexpand' 10 | X -> (let Y (compose (value *macros*) X) 11 | (if (= X Y) 12 | X 13 | (walk (/. Z (macroexpand' Z)) Y)))) 14 | 15 | \* 16 | (define macroexpand 17 | X -> (do (set *macros* (resolve-macro-functions (value *macros*))) 18 | (macroexpand' X))) 19 | *\ 20 | 21 | (defun macroexpand (X) 22 | (do (set *macros* (resolve-macro-functions (value *macros*) [])) 23 | (macroexpand' X))) 24 | 25 | (define add-macro 26 | F -> (let MacroReg (value *macroreg*) 27 | NewMacroReg (set *macroreg* (adjoin F (value *macroreg*))) 28 | (if (= MacroReg NewMacroReg) 29 | skip 30 | (set *macros* [F | (value *macros*)]))))) 31 | -------------------------------------------------------------------------------- /runtime.js: -------------------------------------------------------------------------------- 1 | shen = (function() { 2 | var id = 0, 3 | threads = {}; 4 | 5 | function Shen() { 6 | this.id = id++; 7 | this.sp = 0; 8 | this.nargs = 0; 9 | this.reg = Array(1024); 10 | this.start = null; 11 | this.ip = null; 12 | this.ret = null; 13 | this.error_handlers = []; 14 | this.call_toplevel = null; 15 | this.io = null; 16 | this.chan_in = null; 17 | this.interrupted = false; 18 | } 19 | 20 | Shen.prototype.toString = function() { 21 | return "[object Shen " + id + "]"; 22 | }; 23 | 24 | Shen.prototype.lambdas = {}; 25 | Shen.prototype.tags = {}; 26 | Shen.prototype.toplevels = []; 27 | Shen.prototype.glob = {}; 28 | Shen.prototype.fns = {}; 29 | Shen.prototype.run_span_len = 1000; 30 | Shen.prototype.run_span_interval_ms = 20; 31 | Shen.prototype.glob = { 32 | "*language*": "Javascript", 33 | "*implementation*": "generic", 34 | "*port*": "3.0.0", 35 | "*porters*": "Ramil Farkhshatov", 36 | "*home-directory*": "", 37 | "js.show-error-stack": false 38 | }; 39 | 40 | Shen.prototype.Tag = function Tag(name) { 41 | if (!(this instanceof Tag)) 42 | return new Tag(name); 43 | Shen.prototype.tags[name] = this; 44 | this.name = name; 45 | }; 46 | 47 | Shen.prototype.Tag.prototype.toString = function() { 48 | return "[object shen.Tag " + this.name + "]"; 49 | }; 50 | 51 | Shen.prototype.Tag.prototype.toJSON = function() { 52 | return {"#" : this.name}; 53 | }; 54 | 55 | Shen.prototype.fail_obj = new Shen.prototype.Tag("fail_obj"); 56 | Shen.prototype.interrupt_obj = new Shen.prototype.Tag("interrupt_obj"); 57 | 58 | Shen.prototype.Func = function Func(name, arity, fn, vars) { 59 | if (!(this instanceof Func)) 60 | return new Func(name, arity, fn, vars); 61 | this.name = name; 62 | this.arity = arity; 63 | this.fn = fn; 64 | this.vars = vars || []; 65 | }; 66 | 67 | Shen.prototype.Func.prototype.toJSON = function() { 68 | if (this.name && !this.vars.length) 69 | return {"#" : "Func", name: this.name}; 70 | else 71 | return {"#" : "Func", name: this.name, arity: this.arity, 72 | fn: this.fn.name, vars: this.vars}; 73 | }; 74 | 75 | Shen.prototype.Sym = function Sym(str) { 76 | if (!(this instanceof Sym)) 77 | return new Sym(str); 78 | this.str = str; 79 | }; 80 | 81 | Shen.prototype.Sym.prototype.toJSON = function() { 82 | return {"#" : "Sym", str: this.str}; 83 | }; 84 | 85 | Shen.prototype.Cons = function Cons(head, tail) { 86 | if (!(this instanceof Cons)) 87 | return new Cons(head, tail); 88 | this.head = head; 89 | this.tail = tail; 90 | }; 91 | 92 | Shen.prototype.Cons.prototype.toJSON = function() { 93 | var t = this.tail, r = {"#": "Cons", head: this.head}; 94 | if (!((t instanceof Array) && !t.length)) 95 | r.tail = t; 96 | return r; 97 | }; 98 | 99 | Shen.prototype.Stream = function Stream(read_byte, write_byte, close) { 100 | if (!(this instanceof Stream)) 101 | return new Stream(read_byte, write_byte, close); 102 | this.dir = "" + (read_byte ? "r" : "") + (write_byte ? "w" : ""); 103 | this.read_byte = read_byte || function(vm) { 104 | return vm.error("read-byte: Wrong stream type."); 105 | }; 106 | this.write_byte = write_byte || function(byte, vm) { 107 | vm.error("write-byte: Wrong stream type."); 108 | }; 109 | this.close = close || (function(vm) {return [];}); 110 | }; 111 | 112 | Shen.prototype.Chan = function Chan() { 113 | if (!(this instanceof Chan)) 114 | return new Chan(); 115 | this.buf = []; 116 | this.readers = []; 117 | this.closed = false; 118 | this.end = Shen.prototype.fail_obj; 119 | }; 120 | 121 | Shen.prototype.Chan.prototype.write = function(x) { 122 | if (this.closed) 123 | return false; 124 | var reader = this.readers.shift(); 125 | if (reader) 126 | reader.resume(x); 127 | else 128 | this.buf.push(x); 129 | return true; 130 | }; 131 | 132 | Shen.prototype.Chan.prototype.read = function(vm) { 133 | if (this.buf.length) { 134 | var r = this.buf.shift(); 135 | if (r instanceof Error) 136 | throw r; 137 | return r; 138 | } 139 | if (this.closed) 140 | return this.end; 141 | this.readers.push(vm); 142 | vm.interrupt(); 143 | }; 144 | 145 | Shen.prototype.Chan.prototype.close = function(vm) { 146 | this.closed = true; 147 | var r = this.readers; 148 | for (var i = 0; i < r.length; ++i) 149 | r[i].resume(this.end); 150 | this.readers.length = 0; 151 | return true; 152 | }; 153 | 154 | Shen.prototype.reg_lambda = function(f) { 155 | this.lambdas[f.name] = f; 156 | }; 157 | 158 | Shen.prototype.json_from_obj = function(obj) { 159 | return JSON.stringify(obj); 160 | }; 161 | 162 | Shen.prototype.obj_from_json = function(json) { 163 | var vm = this; 164 | return JSON.parse(json, function(k, v) { 165 | if (!v) 166 | return v; 167 | var type = v["#"], tag; 168 | if (!type) 169 | return v; 170 | if ((tag = vm.tags[type])) 171 | return tag; 172 | switch (type) { 173 | case "Sym": return vm.Sym(v.str); 174 | case "Cons": 175 | if (v.tail === undefined) 176 | v.tail = []; 177 | return vm.Cons(v.head, v.tail); 178 | case "Func": 179 | var fn = vm.fns[v.name]; 180 | if (fn) 181 | return fn; 182 | var code = vm.lambdas[v.fn]; 183 | if (!code) 184 | code = eval(v.fn); 185 | return vm.Func(v.name, v.arity, code, v.args); 186 | } 187 | return v; 188 | }); 189 | }; 190 | 191 | Shen.prototype.clone = function() { 192 | var obj = new this.constructor(), 193 | keys = ["io", "run", "fn_entry", "fn_return", "run_span_len"], 194 | key; 195 | for (var i = 0; i < keys.length; ++i) { 196 | key = keys[i]; 197 | obj[key] = this[key]; 198 | } 199 | return obj; 200 | }; 201 | 202 | Shen.prototype.handle_exception = function(e) { 203 | var err_handler = this.error_handlers.pop(); 204 | this.wipe_stack(0); 205 | this.sp = err_handler.sp; 206 | this.next = err_handler.next; 207 | return this.prep_func_call(err_handler.fn, [e]); 208 | }; 209 | 210 | Shen.prototype.push_error_handler = function(e) { 211 | this.error_handlers.push({sp: this.sp, next: this.next, fn: e}); 212 | }; 213 | 214 | Shen.prototype.interrupt = function() { 215 | throw this.interrupt_obj; 216 | }; 217 | 218 | Shen.prototype.resume = function(value) { 219 | if (this.interrupted) { 220 | this.interrupted = false; 221 | if (value instanceof Error) { 222 | this.start = trap(value, this); 223 | } else { 224 | this.ret = value || true; 225 | } 226 | this.run(); 227 | } 228 | }; 229 | 230 | Shen.prototype.sleep_ms = function(ms) { 231 | var vm = this; 232 | this.post_async(function() { 233 | vm.resume(true); 234 | }, ms); 235 | this.interrupt(); 236 | }; 237 | 238 | function run(ip, vm) { 239 | while (ip) { 240 | if (vm.dump_state_enabled) 241 | vm.dump_state({ip: ip}); 242 | ip = ip(vm); 243 | } 244 | return ip; 245 | } 246 | 247 | function trap(e, vm) { 248 | if (e !== vm.interrupt_obj) { 249 | if (vm.error_handlers.length > 0) 250 | return vm.handle_exception(e); 251 | else 252 | throw e; 253 | } else { 254 | vm.interrupted = true; 255 | vm.start = vm.next; 256 | return null; 257 | } 258 | } 259 | 260 | Shen.prototype.run = function() { 261 | var ip = this.start; 262 | threads[this] = true; 263 | while (ip) { 264 | try { 265 | ip = run(ip, this); 266 | } catch (e) { 267 | ip = trap(e, this); 268 | } 269 | } 270 | delete threads[this]; 271 | if (this.receive) 272 | this.receive(this.ret); 273 | }; 274 | 275 | Shen.prototype.step = function step() { 276 | var ip = this.start, n = this.run_span_len; 277 | function run_n(ip, n, vm) { 278 | while (ip && n--) 279 | ip = ip(vm); 280 | return ip; 281 | } 282 | while (ip && n > 0) { 283 | try { 284 | ip = run_n(ip, n, this); 285 | } catch (e) { 286 | ip = trap(e, this); 287 | } 288 | } 289 | if (ip) { 290 | this.start = ip; 291 | this.run_interval(); 292 | } else { 293 | delete threads[this]; 294 | if (!this.interrupted && this.receive) 295 | this.receive(this.ret); 296 | } 297 | }; 298 | 299 | Shen.prototype.run_interval = function() { 300 | var vm = this; 301 | this.post_async(function() {vm.step();}, 0); 302 | threads[this] = true; 303 | }; 304 | 305 | Shen.prototype.prep_func_call = function(proc, args) { 306 | var n = args.length, closure_vars, 307 | reg = this.reg; 308 | n2 = 0; 309 | if (proc instanceof this.Func) 310 | n2 = proc.vars.length; 311 | for (var i = this.sp, j = n - 1; j >= 0; ++i, --j) 312 | reg[i] = args[j]; 313 | i = this.sp + n; 314 | if (proc instanceof this.Func) { 315 | fn = proc.fn; 316 | closure_vars = proc.vars; 317 | for (var j = 0; j < n2; ++i, ++j) 318 | reg[i] = closure_vars[j]; 319 | } else if (typeof(proc) == "function") 320 | fn = proc; 321 | else 322 | throw new Error("" + proc + " is not a function"); 323 | this.nargs = n + n2; 324 | return fn; 325 | }; 326 | 327 | Shen.prototype.find_func = function(fn) { 328 | if (fn instanceof this.Func) 329 | return fn; 330 | if (fn instanceof this.Sym) 331 | fn = fn.str; 332 | var ret = this.fns[fn]; 333 | if (!ret) 334 | return this.error("No such function: " + fn); 335 | return ret; 336 | }; 337 | 338 | Shen.prototype.ensure_func = function(fn) { 339 | if (fn instanceof this.Sym) 340 | return this.find_func(fn.str); 341 | return fn; 342 | }; 343 | 344 | Shen.prototype.post_async = function(x, ms) { 345 | setTimeout(x, ms); 346 | }; 347 | 348 | Shen.prototype.is_async = function() { 349 | return (this.run !== Shen.prototype.run); 350 | }; 351 | 352 | Shen.prototype.set_async = function(is_async) { 353 | if (!is_async || is_async === "sync") 354 | this.run = Shen.prototype.run; 355 | else { 356 | if (!this.is_async()) 357 | this.calibrate(); 358 | if (typeof(setTimeout) === "undefined") 359 | throw new Error("Cannot set async mode: no setTimeout"); 360 | this.run = Shen.prototype.run_interval; 361 | } 362 | }; 363 | 364 | Shen.prototype.exec = function(proc, args, receive) { 365 | if (threads[this]) 366 | this.error("Recursive shen.exec is not allowed"); 367 | if (typeof(proc) === "string") 368 | proc = this.find_func(proc); 369 | 370 | // DBG 371 | this.reg.length = 0; 372 | 373 | this.receive = receive; 374 | this.next = null; 375 | this.start = this.prep_func_call(proc, args); 376 | this.run(); 377 | if (!this.is_async()) 378 | return this.exec_result(); 379 | }; 380 | 381 | Shen.prototype.exec_result = function() { 382 | var r = this.ret; 383 | this.ret = null; 384 | 385 | // DBG 386 | if (this.sp < 0) 387 | this.error("sp < 0"); 388 | 389 | return r; 390 | }; 391 | 392 | Shen.prototype.call = function(proc, args, receive) { 393 | var self = this; 394 | var vm = this.clone(); 395 | if (this.run !== Shen.prototype.run) { 396 | var parent = this; 397 | function xreceive(x) { 398 | if (receive) 399 | receive(x); 400 | parent.resume(x); 401 | } 402 | this.interrupt(); 403 | return vm.exec(proc, args, xreceive); 404 | } else 405 | return vm.exec(proc, args, receive); 406 | }; 407 | 408 | Shen.prototype.call_toplevel_boot = function(proc) { 409 | this.toplevels.push(proc); 410 | } 411 | 412 | Shen.prototype.call_toplevel_run = function(proc) { 413 | this.exec(proc, []); 414 | }; 415 | 416 | Shen.prototype.put_closure_args = function(closure) { 417 | var vars = closure.vars; 418 | if (vars && vars.length) { 419 | var n = vars.length, 420 | i = this.sp + this.nargs, 421 | reg = this.reg; 422 | for (var j = 0; j < n; ++i, ++j) 423 | reg[i] = vars[j]; 424 | this.nargs += vars.length; 425 | } 426 | }; 427 | 428 | Shen.prototype.equal_boolean = function(b, x) { 429 | return ((x instanceof this.Sym) 430 | && ((x.str == "true" && b === true) 431 | || (x.str == "false" && b === false))); 432 | }; 433 | 434 | Shen.prototype.equal_function = function(f, x) { 435 | return (x instanceof this.Sym) && x.str == f.name; 436 | }; 437 | 438 | Shen.prototype.is_array_equal = function(x, y) { 439 | if (x.length != y.length) 440 | return false; 441 | var n = x.length; 442 | for (var i = 0; i < n; ++i) 443 | if (!this.is_equal(x[i], y[i])) 444 | return false; 445 | return true; 446 | }; 447 | 448 | Shen.prototype.trace = function(name) { 449 | var fn = this[name]; 450 | function tostr(x) { 451 | return this.xstr(x); 452 | }; 453 | var replaced = function() { 454 | var args = Array.prototype.slice.call(arguments); 455 | log("(" + name + " " + args.map(tostr).join(" ") + ")"); 456 | var ret = fn.apply(this, arguments); 457 | log("" + name + " => " + this.xstr(ret)); 458 | return ret; 459 | }; 460 | replaced.old = fn; 461 | this[name] = replaced; 462 | }; 463 | 464 | Shen.prototype.untrace = function(name) { 465 | var fn = this[name]; 466 | if (typeof(fn) === "function" && fn.old) 467 | this[name] = fn.old; 468 | }; 469 | 470 | Shen.prototype.is_equal = function(x, y) { 471 | if (x === y) 472 | return true; 473 | if ((x instanceof Array) && (y instanceof Array)) 474 | return this.is_array_equal(x, y); 475 | if ((x instanceof this.Sym) && (y instanceof this.Sym)) 476 | return x.str === y.str; 477 | if (typeof(x) === "boolean" && this.equal_boolean(x, y)) 478 | return true; 479 | if (typeof(y) === "boolean" && this.equal_boolean(y, x)) 480 | return true; 481 | if ((x instanceof this.Cons) && (y instanceof this.Cons)) 482 | return this.is_equal(x.head, y.head) && this.is_equal(x.tail, y.tail); 483 | if ((x instanceof this.Func) && (y instanceof this.Func)) 484 | return x.fn == y.fn && x.arity == y.arity 485 | && this.is_array_equal(x.vars, y.vars); 486 | if (this.equal_function(x, y) || this.equal_function(y, x)) 487 | return true; 488 | if ((x instanceof this.Func) && (y instanceof this.Sym) && !x.vars.length 489 | && x.name === y.str) 490 | return true; 491 | if ((y instanceof this.Func) && (x instanceof this.Sym) && !y.vars.length 492 | && y.name === x.str) 493 | return true; 494 | return false; 495 | }; 496 | 497 | Shen.prototype.is_empty = function(x) { 498 | return ((x instanceof Array) && !x.length); 499 | }; 500 | 501 | Shen.prototype.is_bool = function(x) { 502 | return (typeof(x) == "boolean") 503 | || ((x instanceof this.Sym) 504 | && (x.str === "true" || x.str === "false")); 505 | }; 506 | 507 | Shen.prototype.is_vector = function(x) { 508 | return (x instanceof Array) && (typeof(x[0]) === "number") 509 | && x.length === (x[0] + 1); 510 | }; 511 | 512 | Shen.prototype.is_absvector = function(x) { 513 | return (x instanceof Array) && x.length > 0; 514 | }; 515 | 516 | Shen.prototype.absvector = function(n) { 517 | return new Array(n); 518 | }; 519 | 520 | Shen.prototype.is_true = function(x) { 521 | return x != false || ((x instanceof this.Sym) && (x.str != "false")); 522 | }; 523 | 524 | Shen.prototype.absvector_ref = function(x, i) { 525 | if (x.length <= i || i < 0) 526 | this.error("out of range"); 527 | return x[i]; 528 | }; 529 | 530 | Shen.prototype.absvector_set = function(x, i, v) { 531 | if (x.length <= i || i < 0) 532 | this.error("out of range"); 533 | x[i] = v; 534 | return x; 535 | }; 536 | 537 | Shen.prototype.value = function(x) { 538 | var y = this.glob[x.str]; 539 | if (y === undefined) 540 | this.error("The variable " + x.str + " is unbound."); 541 | else 542 | return y; 543 | }; 544 | 545 | Shen.prototype.set = function(x, y) { 546 | if (!(x instanceof this.Sym)) 547 | this.error("The value " + x + " is not a symbol"); 548 | return (this.glob[x.str] = y); 549 | }; 550 | 551 | Shen.prototype.vector = function(n) { 552 | var r = new Array(n + 1); 553 | r[0] = n; 554 | for (var i = 1; i <= n; ++i) 555 | r[i] = this.fail_obj; 556 | return r; 557 | }; 558 | 559 | Shen.prototype.esc = function(x) { 560 | var ret = ""; 561 | for (var i = 0; i < x.length; ++i) 562 | switch (x[i]) { 563 | case '"': ret += '\\"'; break; 564 | default: ret += x[i]; break; 565 | } 566 | return ret; 567 | }; 568 | 569 | Shen.prototype.str = function(x) { 570 | switch (typeof(x)) { 571 | case "string": return "\"" + this.esc(x) + "\""; 572 | case "number": 573 | case "boolean": return String(x); 574 | case "function": return "#"; 575 | case "object": 576 | if (x === this.fail_obj) 577 | return "..."; 578 | if (x instanceof this.Sym) 579 | return x.str; 580 | if (x instanceof this.Func) { 581 | if (!x.vars.length && x.name) 582 | return x.name; 583 | var n = (x.name) ? (" " + x.name) : " [nil]"; 584 | return (!x.vars.length) ? "#" : "#"; 585 | } 586 | } 587 | var err = " is not an atom in Shen; str cannot print it to a string." 588 | return this.error(String(x) + err); 589 | }; 590 | 591 | Shen.prototype.intern = function(x) { 592 | switch (x) { 593 | case "true": return true; 594 | case "false": return false; 595 | default: return new this.Sym(x); 596 | } 597 | }; 598 | 599 | Shen.prototype.tlstr = function(x) { 600 | if (x === "") 601 | return new this.Sym("shen.eos"); 602 | return x.substring(1, x.length); 603 | }; 604 | 605 | Shen.prototype.str_from_n = function(x) { 606 | return String.fromCharCode(x); 607 | }; 608 | 609 | Shen.prototype.n_from_str = function(x) { 610 | return x.charCodeAt(0); 611 | }; 612 | 613 | Shen.prototype.wipe_stack = function(start) { 614 | this.reg.length = this.sp + start; 615 | }; 616 | 617 | Shen.prototype.error = function(s) { 618 | throw new Error(s); 619 | return this.fail_obj; 620 | }; 621 | 622 | Shen.prototype.error_to_string = function(s) { 623 | var stack = s.stack, 624 | show = (stack !== undefined), 625 | s = s.toString().replace(/^Error: /, ""); 626 | show &= this.is_true(this.glob["js.show-error-stack"]); 627 | return (show) ? (s + " " + stack) : s; 628 | }; 629 | 630 | Shen.prototype.write_string = function(s, stream) { 631 | for (var i = 0; i < s.length; ++i) 632 | stream.write_byte(s.charCodeAt(i), this); 633 | return s; 634 | }; 635 | 636 | Shen.prototype.defun_x = function(name, arity, fn) { 637 | var fobj = new this.Func(name, arity, fn); 638 | this.fns[name] = fobj; 639 | return fobj; 640 | }; 641 | 642 | Shen.prototype.defun = function() { 643 | function dashify(s) { 644 | return s.replace(/_/g, "-"); 645 | } 646 | var arg0 = arguments[0], arg1 = arguments[1], fn, name, arity, fobj; 647 | switch (typeof(arg0)) { 648 | case "function": 649 | fn = arg0; 650 | name = dashify(fn.name); 651 | break; 652 | case "string": 653 | fn = arguments[1]; 654 | name = arguments[0]; 655 | break; 656 | default: 657 | return this.error("defun: wrong arguments"); 658 | } 659 | arity = fn.length; 660 | fobj = new this.Func(name, arity, function f(vm) { 661 | var x = vm.fn_entry(f, arity, name), sp, args; 662 | if (x !== vm.fail_obj) return x; 663 | sp = vm.sp; 664 | args = vm.reg.slice(sp, sp + arity).reverse(); 665 | return vm.fn_return(fn.apply(vm, args), vm.next); 666 | }); 667 | this.fns[name] = fobj; 668 | return fobj; 669 | }; 670 | 671 | Shen.prototype.partial_func = function(name, arity, fn) { 672 | var vars = this.reg.slice(this.sp, this.sp + this.nargs); 673 | return new this.Func(name, arity, fn, vars); 674 | }; 675 | 676 | Shen.prototype.defun_x("klvm.mk-closure", -1, function mk_closure(vm) { 677 | var r = vm.reg, sp = vm.sp, n = vm.nargs, fn = r[sp + n - 1]; 678 | if (fn instanceof vm.Func) 679 | fn = fn.fn; 680 | vm.ret = new vm.Func(null, n - 1, fn, r.slice(sp, sp + n - 1)); 681 | return vm.next; 682 | }); 683 | 684 | Shen.prototype.eval = function(s) { 685 | this.ret = false; 686 | var vm = this, toplevel_next = this.next; 687 | if (this.dump_eval_enabled) 688 | log("EVAL:\n" + s + "\n"); 689 | this.wipe_stack(0); 690 | eval("(function(vm) { " + s + " })(vm);"); 691 | return toplevel_next; 692 | }; 693 | 694 | // UTILS { 695 | Shen.prototype.xstr_arr = function(x, nmax) { 696 | var xstr = this.xstr.bind(this); 697 | if (nmax) 698 | return x.slice(0, nmax).join(" ") + " ..."; 699 | return x.map(xstr).join(" "); 700 | }; 701 | 702 | Shen.prototype.xstr_cons = function(x, nmax) { 703 | var lst = [], i = 0; 704 | do { 705 | lst.push(this.xstr(x.head, nmax)); 706 | x = x.tail; 707 | i++; 708 | } while (x instanceof this.Cons && (!nmax || i < nmax)); 709 | var str = lst.join(" "); 710 | if (nmax) { 711 | str += " ..."; 712 | } else if (!this.is_empty(x)) 713 | str += " | " + this.xstr(x, nmax); 714 | return "[" + str + "]"; 715 | }; 716 | 717 | Shen.prototype.xstr = function(x, nmax) { 718 | switch (typeof(x)) { 719 | case "string": return '"' + this.esc(x) + '"'; 720 | case "boolean": case "number": return String(x); 721 | case "function": 722 | if (x.name) 723 | return "#"; 724 | return "#"; 725 | } 726 | if (x instanceof this.Sym) 727 | return x.str; 728 | if (x instanceof this.Cons) 729 | return this.xstr_cons(x, nmax); 730 | if (x instanceof this.Func) 731 | return "#>"; 732 | if (this.is_empty(x)) 733 | return "[]"; 734 | if (this.is_vector(x)) 735 | return "<" + this.xstr_arr(x, nmax) + ">"; 736 | if (x instanceof Array) 737 | return "<<" + this.xstr_arr(x, nmax) + ">>"; 738 | return String(x); 739 | }; 740 | 741 | Shen.prototype.list = function(x) { 742 | var ret = []; 743 | for (var i = x.length - 1; i >= 0; --i) 744 | ret = new this.Cons(x[i], ret); 745 | return ret; 746 | }; 747 | 748 | function log(s) { 749 | try { 750 | console.log(new Date(), s); 751 | } catch (e) { 752 | print(s); 753 | } 754 | }; 755 | Shen.prototype.log = log; 756 | 757 | Shen.prototype.dump_regs = function(start, n) { 758 | var r = this.reg, 759 | n = n || 20, 760 | start = (start === undefined) ? this.sp : start; 761 | for (var i = start, j = start; i < r.length; ++i) { 762 | if (r[i] !== undefined) { 763 | if (j + 1 == i - 1) 764 | log(" " + (i - 1) + ": nil"); 765 | else if (j + 1 < i) 766 | log(" " + (j + 1) + ".." + (i - 1) + ": nil"); 767 | var x = this.dbg_str_prefixed(this.xstr(r[i], n), " "); 768 | log(" " + i + ": " + x); 769 | j = i; 770 | } 771 | } 772 | }; 773 | 774 | Shen.prototype.dbg_str_prefixed = function(x, prefix) { 775 | function pre(x) {return prefix + x;} 776 | var lines = String(x).split("\n"); 777 | if (lines.length < 2) 778 | return x; 779 | return lines[0] + "\n" + lines.slice(1).map(pre).join("\n"); 780 | }; 781 | 782 | Shen.prototype.dump_state = function(extra) { 783 | var n = 20; 784 | log("# STEP ################"); 785 | for (var x in extra) 786 | log(" " + x + ": " + this.xstr(extra[x])); 787 | log(" next: " + this.xstr(this.next)); 788 | log(" ret: " + this.xstr(this.ret, n)); 789 | log(" nargs: " + this.nargs); 790 | log(" sp: " + this.sp); 791 | log(" regs:"); 792 | this.dump_regs(this.sp, n); 793 | log("\n"); 794 | }; 795 | // } UTILS 796 | 797 | // IO { 798 | Shen.prototype.Utf8_reader = function(str) { 799 | this.str = str || ""; 800 | this.strpos = 0; 801 | this.bytes = Array(6); 802 | this.bytepos = 0; 803 | this.nbytes = 0; 804 | this.append = function(data) { 805 | this.str += data; 806 | }; 807 | this.read_byte = function() { 808 | if (this.bytepos < this.nbytes) 809 | return this.bytes[this.bytepos++]; 810 | if (this.strpos >= this.str.length) 811 | return -1; 812 | var c = this.str.charCodeAt(this.strpos++); 813 | this.bytepos = 0; 814 | this.nbytes = 0; 815 | if (c <= 0x7f) 816 | return c; 817 | if (c <= 0x7ff) { 818 | var n = 1, c0 = (c >> 6) | 192; 819 | } else if (c <= 0xffff) { 820 | var n = 2, c0 = (c >> 12) | 224; 821 | } else if (c <= 0x1fffff) { 822 | var n = 3, c0 = (c >> 18) | 240; 823 | } else if (c <= 0x3ffffff) { 824 | var n = 4, c0 = (c >> 24) | 248; 825 | } else if (c <= 0x7fffffff) { 826 | var n = 5, c0 = (c >> 30) | 252; 827 | } else 828 | throw new Error("Character " + c + " cannot be coded to UTF-8"); 829 | this.nbytes = n; 830 | var shift = (n - 1) * 6; 831 | for (var i = 0; i < n; ++i, shift -= 6) 832 | this.bytes[i] = ((c >> shift) & 63) | 128; 833 | return c0; 834 | }; 835 | }; 836 | 837 | Shen.prototype.Utf8_writer = function(char_fn) { 838 | this.nbytes = 0; 839 | this.char = 0; 840 | this.bytespos = 0; 841 | this.write_byte = function(byte) { 842 | if (!(byte & 0x80)) { 843 | char_fn(byte); 844 | this.bytespos = 0; 845 | } else if ((byte & 224) == 192) { 846 | this.char = byte & 31; 847 | this.nbytes = 2; 848 | this.bytespos = 1; 849 | } else if ((byte & 240) == 224) { 850 | this.char = byte & 15; 851 | this.nbytes = 3; 852 | this.bytespos = 1; 853 | } else if ((byte & 248) == 240) { 854 | this.char = byte & 7; 855 | this.nbytes = 4; 856 | this.bytespos = 1; 857 | } else if ((byte & 252) == 248) { 858 | this.char = byte & 3; 859 | this.nbytes = 5; 860 | this.bytespos = 1; 861 | } else if ((byte & 254) == 252) { 862 | this.char = byte & 1; 863 | this.nbytes = 6; 864 | this.bytespos = 1; 865 | } else { 866 | this.char = (this.char << 6) | (byte & 0x7f); 867 | this.bytespos++; 868 | if (this.bytespos >= this.nbytes) { 869 | char_fn(this.char); 870 | this.bytespos = 0; 871 | this.nbytes = 0; 872 | } 873 | } 874 | } 875 | }; 876 | 877 | Shen.prototype.str_from_utf8 = function(s) { 878 | var ret = ""; 879 | function emit(x) {ret += String.fromCharCode(x);} 880 | var w = new this.Utf8_writer(emit), n = s.length; 881 | for (var i = 0; i < n; ++i) 882 | w.write_byte(s[i]); 883 | return ret; 884 | }; 885 | 886 | Shen.prototype.utf8_from_str = function(s) { 887 | var c, r = new this.Utf8_reader(s), bytes = []; 888 | while ((c = r.read_byte()) >= 0) 889 | bytes.push(c); 890 | return bytes; 891 | }; 892 | 893 | Shen.prototype.buf_stream = function(buf) { 894 | var arr = new Uint8Array(buf); 895 | function r() { 896 | var buf = this.buf; 897 | if (this.pos >= buf.length) 898 | return -1; 899 | return buf[this.pos++]; 900 | } 901 | var stream = this.Stream(r, null, function() {}); 902 | stream.pos = 0; 903 | stream.buf = arr; 904 | return stream; 905 | }; 906 | 907 | Shen.prototype.console_io = function(vm) { 908 | var io = {}; 909 | 910 | function file_reader() { 911 | try { 912 | return readbuffer; 913 | } catch(e) { 914 | read; 915 | return function(name) {return read(name, "binary")}; 916 | } 917 | } 918 | 919 | function open(name, dir, vm) { 920 | var filename = vm.glob["*home-directory*"] + name; 921 | if (typeof($HOME) !== "undefined") 922 | var filename = filename.replace(/^~/, $HOME); 923 | if (dir.str === "in") { 924 | var buf = file_reader()(filename); 925 | if (buf instanceof ArrayBuffer) 926 | return vm.buf_stream(buf); 927 | else if (typeof(buf) === "string") { 928 | var strbuf = new vm.Utf8_reader(buf); 929 | return vm.Stream(function(vm) {return strbuf.read_byte();}); 930 | } else 931 | return vm.error("open: unsupported file read result"); 932 | } else if (dir.str === "out") 933 | return vm.error("open: writing files is not supported in cli"); 934 | return vm.error("open: unsupported flags"); 935 | }; 936 | 937 | var putchars = null; 938 | try {putchars = putstr;} catch(e) {} 939 | try {putchars = write;} catch(e) {} 940 | if (!putchars) 941 | throw new Error("You JS implementation's IO is not supported"); 942 | 943 | var writer = new vm.Utf8_writer(function(char) { 944 | putchars(String.fromCharCode(char)); 945 | }); 946 | var stdout = vm.Stream(null, function(byte, vm) { 947 | writer.write_byte(byte); 948 | }); 949 | var strbuf = new vm.Utf8_reader(); 950 | 951 | var stdin = vm.Stream(function(vm) { 952 | var x = strbuf.read_byte(); 953 | if (x >= 0) 954 | return x; 955 | var str = readline(); 956 | if (str == null) { 957 | quit(); 958 | return -1; 959 | } 960 | strbuf = new vm.Utf8_reader(str + "\n"); 961 | return this.read_byte(vm); 962 | }); 963 | vm.glob["*stinput*"] = stdin; 964 | vm.glob["*stoutput*"] = stdout; 965 | io.open = open; 966 | return io; 967 | }; 968 | 969 | Shen.prototype.chan_in_stream = function(chan) { 970 | return this.Stream(function(vm) { 971 | return chan.read(vm); 972 | }); 973 | }; 974 | 975 | Shen.prototype.ensure_chan_input = function() { 976 | if (!this.chan_in) { 977 | this.chan_in = this.Chan(); 978 | this.glob["*stinput*"] = this.chan_in_stream(this.chan_in); 979 | } 980 | }; 981 | 982 | Shen.prototype.send_str = function(s) { 983 | this.ensure_chan_input(); 984 | var i, n = s.length, chan = this.chan_in; 985 | for (i = 0; i < n; ++i) 986 | chan.write(s.charCodeAt(i)); 987 | return s; 988 | }; 989 | 990 | Shen.prototype.print = function(s) { 991 | var i, n = s.length, out = this.glob["*stoutput*"]; 992 | for (i = 0; i < n; ++i) 993 | out.write_byte(s.charCodeAt(i), this); 994 | }; 995 | 996 | // } IO 997 | 998 | Shen.prototype.nop = function(vm) { 999 | return vm.next; 1000 | }; 1001 | 1002 | Shen.prototype.calibrate = function() { 1003 | var n = 1000, fn = "=", 1004 | lst = this.list([1, 2, 3, this.Sym("four"), this.list(["five"])]), 1005 | args = [lst, lst], t_ms = Date.now(); 1006 | if (this.fns["reverse"]) { 1007 | fn = "reverse"; 1008 | args = [lst]; 1009 | } 1010 | for (var i = 0; i < n; ++i) 1011 | this.exec(fn, args); 1012 | t_ms = Date.now() - t_ms; 1013 | this.run_span_len = Math.floor(n * this.run_span_interval_ms / t_ms); 1014 | }; 1015 | 1016 | Shen.prototype.make_thread = function(fn) { 1017 | var thread = this.clone(); 1018 | thread.run = Shen.prototype.run_interval; 1019 | thread.exec(fn, []); 1020 | return thread; 1021 | }; 1022 | 1023 | function call_toplevels(vm, i, toplevels, ondone) { 1024 | if (i >= toplevels.length) 1025 | return ondone(); 1026 | vm.exec(toplevels[i], [], function(ret) { 1027 | call_toplevels(vm, i + 1, toplevels, ondone); 1028 | }); 1029 | } 1030 | 1031 | Shen.prototype.init = function(opts) { 1032 | var vm = this; 1033 | this.io = opts.io(this); 1034 | if (!this.io) 1035 | return this.error("shen: IO is not set"); 1036 | var keys = ["open"]; 1037 | for (var i = 0; i < keys.length; ++i) 1038 | if (!this.io[keys[i]]) 1039 | throw new Error("shen: IO has no method " + keys[i]); 1040 | this.set_async(opts.async || false); 1041 | this.call_toplevel = this.call_toplevel_run; 1042 | if (this.toplevels.length) { 1043 | call_toplevels(this, 0, this.toplevels, function() { 1044 | vm.toplevels = []; 1045 | end.call(vm); 1046 | }); 1047 | } else 1048 | end.call(this); 1049 | function end() { 1050 | this.reg_lambda = function() {}; 1051 | this.lambdas = {}; 1052 | if (opts.repl) 1053 | this.start_repl(); 1054 | if (opts.ondone) 1055 | opts.ondone(); 1056 | } 1057 | }; 1058 | 1059 | Shen.prototype.start_repl = function() { 1060 | this.exec("shen.shen", []); 1061 | }; 1062 | 1063 | Shen.prototype.console_repl = function(opts) { 1064 | opts.repl = true; 1065 | opts.io = this.console_io; 1066 | this.init(opts); 1067 | }; 1068 | 1069 | var sh = new Shen(); 1070 | sh.call_toplevel = sh.call_toplevel_boot; 1071 | 1072 | sh.defun_x("js.eval", 1, function js_eval(vm) { 1073 | return vm.eval(vm.reg[vm.sp]); 1074 | }); 1075 | 1076 | sh.defun_x("js.interrupt", 0, function js_interrupt(vm) { 1077 | vm.interrupt(); 1078 | }); 1079 | 1080 | sh.defun("open", function open(dir, name) { 1081 | return this.io.open(dir, name, this); 1082 | }); 1083 | 1084 | sh.defun("read-byte", function read_byte(stream) { 1085 | return stream.read_byte(this); 1086 | }); 1087 | 1088 | sh.defun("write-byte", function write_byte(byte, stream) { 1089 | return stream.write_byte(byte, this); 1090 | }); 1091 | 1092 | sh.defun("get-time", function get_time(type) { 1093 | switch (type.str) { 1094 | case "run": 1095 | case "unix": return Math.floor(Date.now() / 1000); 1096 | case "run/ms": 1097 | case "unix/ms": return Date.now(); 1098 | default: return this.error("get-time does not understand the parameter " 1099 | + type.str); 1100 | } 1101 | }); 1102 | 1103 | sh.defun("js.gc_stack", function gc_stack() { 1104 | vm.wipe_stack(0); 1105 | return true; 1106 | }); 1107 | 1108 | sh.defun("js.chan", function chan() { 1109 | return this.Chan(); 1110 | }); 1111 | 1112 | sh.defun("js.chan-read", function chan_read(chan) { 1113 | return chan.read(this); 1114 | }); 1115 | 1116 | sh.defun("js.chan-write", function chan_write(x, chan) { 1117 | return chan.write(x, this); 1118 | }); 1119 | 1120 | sh.defun("js.chan-close", function chan_write(chan) { 1121 | return chan.close(this); 1122 | }); 1123 | 1124 | sh.defun("js.make-thread", function make_thread(fn) { 1125 | var thread = this.make_thread(fn); 1126 | return thread.id; 1127 | }); 1128 | 1129 | sh.defun("js.sleep-ms", function sleep_ms(ms) { 1130 | this.sleep_ms(ms); 1131 | return true; 1132 | }); 1133 | 1134 | sh.defun("js.list", function js_list(x) { 1135 | var ret = []; 1136 | while (x instanceof this.Cons) { 1137 | ret.push(x.head); 1138 | x = x.tail; 1139 | } 1140 | return ret; 1141 | }); 1142 | 1143 | sh.defun("js.shen_list", function js_shen_list(x) {return this.list(x);}); 1144 | 1145 | return sh; 1146 | })(); 1147 | 1148 | try {module.exports = shen;} catch (e) {} 1149 | -------------------------------------------------------------------------------- /shen-js.shen: -------------------------------------------------------------------------------- 1 | (defun eval-kl (X) 2 | (trap-error (let . (set js.evaluated? true) 3 | (let R (js.eval (js.from-kl (cons X ()))) 4 | (let . (set js.evaluated? false) 5 | R))) 6 | (lambda E (do (set js.evaluated? false) 7 | (error (error-to-string E)))))) 8 | 9 | (define js.eval-str 10 | S -> (eval [package null [] | (read-from-string S)])) 11 | -------------------------------------------------------------------------------- /shen-node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | module.exports = (function() { 4 | var usage = ["Usage: shen-node.js [-noinit] [-c target_file] "], 5 | shen = require("./shen"), 6 | fs = require("fs"), 7 | stream = require("stream"), 8 | self = {}, 9 | home = process.env[(process.platform == "win32") 10 | ? "USERPROFILE" : "HOME"]; 11 | 12 | function main() { 13 | var i, compile_dest, init = true; 14 | for (i = 2; i < process.argv.length && process.argv[i].match(/^-/); ++i) { 15 | switch (process.argv[i]) { 16 | case "-noinit": init = false; break; 17 | case "-c": case "-compile": compile_dest = process.argv[++i]; break; 18 | default: usage_die(); 19 | } 20 | } 21 | if (init) 22 | read_init_file(start); 23 | else 24 | start(); 25 | 26 | function start() { 27 | if (i >= process.argv.length) 28 | shen.start_repl(); 29 | else if (compile_dest) 30 | compile(process.argv, i, compile_dest); 31 | else 32 | on_files(process.argv, i, load, function() {process.exit()}); 33 | } 34 | } 35 | 36 | function mk_read_byte(stream, vm) { 37 | var chan = shen.Chan(); 38 | chan.end = -1; 39 | stream.vm = vm; 40 | stream.on("error", function(err) { 41 | chan.write(new Error(err), stream.vm); 42 | }); 43 | stream.on("data", function(data) { 44 | var i, vm = stream.vm; 45 | for (i = 0; i < data.length; ++i) 46 | chan.write(data[i], vm); 47 | }); 48 | stream.on("end", function() { 49 | chan.close(stream.vm); 50 | }); 51 | return function(vm) { 52 | stream.vm = vm; 53 | return chan.read(vm); 54 | }; 55 | } 56 | 57 | function mk_write_byte(stream) { 58 | return function(byte, vm) { 59 | var ret = stream.write(new Buffer([byte]), null, function(err) { 60 | vm.resume(err ? err : byte); 61 | }); 62 | vm.interrupt(); 63 | } 64 | } 65 | 66 | function mk_close(stream) { 67 | return function() { 68 | if (stream.end) 69 | stream.end(); 70 | stream.removeAllListeners(); 71 | stream.on("error", function() {}); 72 | } 73 | } 74 | 75 | function wrap_stream(stream, dir, vm) { 76 | switch (dir) { 77 | case "r": 78 | return shen.Stream(mk_read_byte(stream, vm), null, mk_close(stream)); 79 | 80 | case "w": 81 | return shen.Stream(null, mk_write_byte(stream), mk_close(stream)); 82 | 83 | case "rw": case "wr": case "w+": case "r+": 84 | return shen.Stream(mk_read_byte(stream, vm), mk_write_byte(stream), 85 | mk_close(stream)); 86 | 87 | default: throw new Error("Unsupported stream type: " + dir); 88 | } 89 | } 90 | 91 | function fix_path(path) { 92 | return path.replace(/^~/, home); 93 | } 94 | 95 | function open(name, dir, vm) { 96 | var path = fix_path(vm.glob["*home-directory*"] + name); 97 | switch (dir.str) { 98 | case "in": 99 | wait_result(fs.createReadStream(path), "readable", function(err) { 100 | vm.resume((err) ? err : wrap_stream(this, "r", vm)); 101 | }); 102 | break; 103 | 104 | case "out": 105 | wait_result(fs.createWriteStream(path), "drain", function(err) { 106 | vm.resume((err) ? err : wrap_stream(this, "w", vm)); 107 | }); 108 | break; 109 | 110 | default: return vm.error("Unsupported 'open' flags"); 111 | } 112 | 113 | function wait_result(stream, ev, onready) { 114 | stream.once("error", onready); 115 | stream.once(ev, onready); 116 | vm.interrupt(); 117 | } 118 | } 119 | 120 | function io(vm) { 121 | var io = {}; 122 | io.open = open; 123 | vm.glob["*stinput*"] = wrap_stream(process.stdin, "r", vm); 124 | vm.glob["*stoutput*"] = wrap_stream(process.stdout, "w", vm); 125 | return io; 126 | } 127 | 128 | function load(src, callback) { 129 | shen.exec("load", [src], callback); 130 | } 131 | 132 | function on_files(files, i, fn, callback) { 133 | if (i < files.length) 134 | fn(files[i], function() { 135 | on_files(files, i + 1, fn, callback); 136 | }); 137 | else if (callback) 138 | callback(); 139 | } 140 | 141 | function compile(files, i, dest, callback) { 142 | var list = shen.list(files.slice(i)) 143 | shen.exec("js.save-from-files", [list, dest], callback); 144 | } 145 | 146 | function read_init_file(callback) { 147 | var path = home + "/.shen.shen"; 148 | fs.access(path, fs.R_OK, function(err) { 149 | if (err) 150 | callback(); 151 | else { 152 | shen.glob["*hush*"] = true; 153 | shen.exec("load", [path], function() { 154 | shen.glob["*hush*"] = false; 155 | callback(); 156 | }); 157 | } 158 | }); 159 | } 160 | 161 | function usage_die() { 162 | process.stdout.write(usage.join(" ") + "\n"); 163 | process.exit(1); 164 | } 165 | 166 | self.wrap_stream = wrap_stream; 167 | self.main = main; 168 | 169 | shen.init({io: io, async: true}); 170 | return self; 171 | })(); 172 | 173 | if (require.main === module) 174 | module.exports.main(); 175 | -------------------------------------------------------------------------------- /shen.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | Shen REPL 13 | 14 | 15 | 16 |
17 |
18 |
Please wait…
19 |
Loading
20 | 21 |
22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 |
46 | 47 | 57 |
58 |
59 |
60 | 63 |
64 |
65 |
66 |
67 | 68 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | 77 |
78 |
79 |
80 | 83 |
84 |
85 |
86 |
87 | 88 |
89 |
90 |
91 |
92 |
*output*
93 |
94 | 97 |
98 |
99 |
100 |
101 | 102 |
103 |
104 |
105 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | 119 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /tests/test.js: -------------------------------------------------------------------------------- 1 | Test = { 2 | async: false, 3 | cases: [], 4 | 5 | init_shen: function() { 6 | load("../runtime.js"); 7 | load("../primitives.js"); 8 | load("interval.js"); 9 | shen.init({io: shen.console_io, async: this.async}); 10 | shen.defun("vector->", function(v, i, x) { 11 | v[i] = x; 12 | return v; 13 | }); 14 | shen.defun("<-vector", function(v, i) {return this.absvector_ref(v, i);}); 15 | load("test1.js"); 16 | }, 17 | 18 | init_cases: function() { 19 | this.cases = []; 20 | load("test_def.js"); 21 | 22 | this.add_eq_case(0, 0, true); 23 | this.add_eq_case(101, 101, true); 24 | this.add_eq_case(true, 1, false); 25 | this.add_eq_case(true, "true", false); 26 | this.add_eq_case(true, 0, false); 27 | this.add_eq_case(true, new shen.Sym("true"), true); 28 | this.add_eq_case(false, new shen.Sym("false"), true); 29 | this.add_eq_case(new shen.Sym("one"), new shen.Sym("one"), true); 30 | this.add_eq_case(new shen.Sym("one"), new shen.Sym("two"), false); 31 | this.add_eq_case(new shen.Sym("<-vector"), shen.fns["<-vector"], true); 32 | this.add_eq_case(new shen.Sym("<-vector"), shen.fns["vector->"], false); 33 | this.add_eq_case([], [], true); 34 | this.add_eq_case(shen.fail_obj, shen.fail_obj, true); 35 | this.add_eq_case(shen.list([1]), shen.list([1]), true); 36 | this.add_eq_case(shen.list([1, new shen.Sym("one")]), shen.list([1, new shen.Sym("one")]), true); 37 | this.add_cases(this.t1); 38 | }, 39 | 40 | add_cases: function(cases) { 41 | for (var i in cases) 42 | this.add_case(cases[i]); 43 | }, 44 | 45 | add_case: function(cs) { 46 | var fn = cs[0][0]; 47 | var args = cs[0].slice(1); 48 | var msgfn = [fn].concat(args.map(function(x) {return shen.xstr(x);})); 49 | this.cases.push({ 50 | str: ("(" + msgfn.join(" ") + ")"), 51 | fn: function() { 52 | return shen.call(shen.fns[fn], args); 53 | }, 54 | expected: cs[1] 55 | }); 56 | }, 57 | 58 | add_eq_case: function(x, y, expected) { 59 | this.cases.push({ 60 | str: ("(= " + shen.xstr(x) + " " + shen.xstr(y) + ")"), 61 | fn: function() {return shen.is_equal(x, y);}, 62 | expected: expected 63 | }); 64 | }, 65 | 66 | run_case: function(def, i) { 67 | if (typeof(def) === "number") { 68 | var i = def; 69 | var def = this.cases[i]; 70 | } 71 | try { 72 | var ret = def.fn(); 73 | var eq = shen.is_equal(def.expected, ret); 74 | var result_str = ""; 75 | if (eq) { 76 | this.nok++; 77 | } else { 78 | this.nerr++; 79 | var result_str = "[ERROR: expected " + shen.xstr(def.expected) + "]"; 80 | } 81 | } catch(e) { 82 | this.nerr++; 83 | var stack = e.stack; 84 | var result_str = "[EXCEPTION: " + e + " " + stack + "]"; 85 | var eq = false; 86 | } 87 | if (0 && typeof(ret) === "object") 88 | for (var key in ret) 89 | print(" ret." + key + ": " + ret[key]); 90 | print("" + i + ": " + def.str + " => " + shen.xstr(ret) + " " 91 | + result_str); 92 | return eq; 93 | }, 94 | 95 | run: function(cases) { 96 | var t = Date.now(); 97 | this.nerr = this.nok = 0; 98 | if (cases) { 99 | if (typeof(cases) === "number") 100 | var cases = [cases]; 101 | var cases = cases.map(function(i) {return i}); 102 | } else { 103 | cases = []; 104 | for (var i in this.cases) 105 | cases.push(i); 106 | } 107 | var errs = []; 108 | for (var i in cases) 109 | if (!this.run_case(this.cases[cases[i]], i)) 110 | errs.push(i); 111 | var t = Date.now() - t; 112 | print("# DONE " + cases.length + " tests in " + t + "ms"); 113 | if (this.nerr) 114 | print("" + this.nerr + " errors: [" + errs.join(", ") + "]"); 115 | }, 116 | 117 | init: function(cases) { 118 | this.init_shen(); 119 | this.init_cases(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /web/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | -------------------------------------------------------------------------------- /web/boot.js: -------------------------------------------------------------------------------- 1 | shen_web = (function() { 2 | var files = ["web/util.js", "web/jsfile.js", "web/fs.js", "web/edit.js", 3 | "web/repl.js", "web/embed.js", "web/store.js", "shen.js", 4 | "web/loader_http.js", "web/loader_github.js"]; 5 | 6 | if (Function.prototype.name === undefined && Object.defineProperty) { 7 | Object.defineProperty(Function.prototype, "name", { 8 | get: function() { 9 | var re = /function\s*([\w$_]*)\s*\(/, 10 | x = re.exec(this.toString())[1]; 11 | return x ? x : ""; 12 | }, 13 | set: function() {} 14 | }); 15 | } 16 | 17 | var self = {}, init_status; 18 | self.plugins = []; 19 | self.set_init_status = function(s) { 20 | init_status.innerHTML = ""; 21 | init_status.appendChild(document.createTextNode(s)); 22 | }; 23 | 24 | self.init = function(opts) { 25 | self.opts = opts = opts || {}; 26 | var ondone = opts.ondone; 27 | 28 | function init_plugins(i, done) { 29 | if (i < self.plugins.length) 30 | self.plugins[i](function() {init_plugins(i + 1, done);}); 31 | else 32 | done(); 33 | } 34 | 35 | function script(file, fn) { 36 | var s = document.createElement("script") 37 | s.type = "text/javascript"; 38 | s.src = file; 39 | s.async = true; 40 | document.head.appendChild(s); 41 | } 42 | files.forEach(script); 43 | 44 | function apply_hash() { 45 | var path = location.hash.replace(/^#/, ""); 46 | if (path === "") 47 | shen_web.edit.unload(); 48 | else 49 | shen_web.edit.load(shen_web.fs.root, path); 50 | shen_web.fs.select(path); 51 | } 52 | 53 | function onerror(msg) { 54 | var p = document.getElementById("wait_pane"), 55 | t = document.getElementById("wait_text"), 56 | img = document.getElementById("wait_progress"); 57 | p.classList.add("wait_error"); 58 | while (t.firstChild) 59 | t.removeChild(t.firstChild); 60 | t.appendChild(document.createTextNode("Error occured: " + msg)); 61 | if (img && img.parentNode === p) 62 | p.removeChild(img); 63 | } 64 | 65 | function done() { 66 | window.onhashchange = apply_hash; 67 | if (ondone) 68 | ondone(); 69 | apply_hash(); 70 | var wait = document.getElementById("wait_frame"); 71 | if (wait) 72 | wait.parentNode.removeChild(wait); 73 | window.onerror = null; 74 | } 75 | 76 | window.onload = function() { 77 | window.onerror = onerror; 78 | init_status = document.getElementById("wait_status"); 79 | shen_web.init_repl(); 80 | shen_web.init_edit(function(path) {shen_web.send_file(path);}); 81 | shen_web.init_fs(function(file, path) { 82 | window.location.hash = "#" + path; 83 | }); 84 | shen_web.set_init_status("Initializing plug-ins"); 85 | init_plugins(0, function() { 86 | opts.ondone = done; 87 | shen_web.embed_shen(opts); 88 | }); 89 | }; 90 | } 91 | return self; 92 | })(); 93 | -------------------------------------------------------------------------------- /web/document.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/document.png -------------------------------------------------------------------------------- /web/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/download.png -------------------------------------------------------------------------------- /web/edit.js: -------------------------------------------------------------------------------- 1 | shen_web.init_edit = function(run) { 2 | shen_web.set_init_status("Initializing editor"); 3 | var edit = {}; 4 | edit.file = null; 5 | edit.touched = false; 6 | edit.welcome = ".doc/welcome.html"; 7 | edit.runner = shen_web.img_btn("Run", "web/run.png"); 8 | edit.runner.classList.add("popup"); 9 | edit.runner.onclick = function() { 10 | var t = this.x_shen_text; 11 | shen_web.send(t); 12 | }; 13 | 14 | edit.set_title = function(title) { 15 | var t = document.getElementById("editor_title"); 16 | shen_web.clean(t); 17 | t.appendChild(document.createTextNode(title)); 18 | }; 19 | 20 | edit.load = function(root, file) { 21 | function process_links(where) { 22 | var links = where.getElementsByTagName("a"), i, n = links.length; 23 | for (i = 0; i < n; ++i) { 24 | var a = links[i]; 25 | if (a.hostname !== window.location.hostname) 26 | a.target = "_blank"; 27 | } 28 | } 29 | 30 | function process_code(where) { 31 | var items = where.querySelectorAll(".editor_view pre > code"), i; 32 | for (i = 0; i < items.length; ++i) { 33 | items[i].onclick = items[i].onmouseenter = function() { 34 | this.appendChild(edit.runner); 35 | edit.runner.x_shen_text = (this.textContent || this.innerText); 36 | }; 37 | items[i].onmouseleave = function() { 38 | edit.runner.x_shen_text = null; 39 | if (edit.runner.parentNode) 40 | edit.runner.parentNode.removeChild(edit.runner); 41 | }; 42 | } 43 | } 44 | 45 | function load_html(html, where) { 46 | var html = str.replace(/(^.*]*>)|(<\/body>.*$)/g, ""); 47 | where.innerHTML = "
" + html + "
"; 48 | var scr = where.getElementsByTagName("script"); 49 | for (var i = 0; i < scr.length; ++i) 50 | eval(scr[i].innerHTML); 51 | process_links(where); 52 | process_code(where); 53 | } 54 | 55 | var file = (typeof(file) === "string") ? root.get(file) : file, 56 | ed_cont = document.getElementById("editor_edit_container"), 57 | ed = document.getElementById("editor_edit"), 58 | view_cont = document.getElementById("editor_view_container"), 59 | view = document.getElementById("editor_view"), 60 | ctl = document.getElementById("editor_toolbar"), 61 | in_html = false, 62 | str; 63 | this.unload(); 64 | if (!file || file.type === "d") 65 | return; 66 | this.set_title(file.path()); 67 | this.file = file; 68 | try { 69 | str = this.file.str(); 70 | } catch(e) { 71 | in_html = true; 72 | str = "
Cannot show. File is probably binary
"; 73 | } 74 | if (in_html || file.path().match(/\.html$/)) 75 | load_html(str, view); 76 | else { 77 | view_cont.classList.add("undisplayed"); 78 | ed_cont.classList.remove("undisplayed"); 79 | ctl.classList.remove("undisplayed"); 80 | ed.value = str; 81 | edit.touched = false; 82 | } 83 | }; 84 | 85 | edit.unload = function() { 86 | var edit_cont = document.getElementById("editor_edit_container"), 87 | view_cont = document.getElementById("editor_view_container"), 88 | view = document.getElementById("editor_view"), 89 | ed = document.getElementById("editor_edit"), 90 | ctl = document.getElementById("editor_toolbar"); 91 | this.set_title(""); 92 | this.file = null; 93 | ctl.classList.add("undisplayed"); 94 | view_cont.classList.remove("undisplayed"); 95 | edit_cont.classList.add("undisplayed"); 96 | ed.value = ""; 97 | view.parentNode.scrollTop = ed.parentNode.scrollTop = 0; 98 | shen_web.clean(view); 99 | }; 100 | 101 | edit.reload = function(force) { 102 | if (!force && !(this.file && this.touched)) 103 | return; 104 | shen_web.confirm({ 105 | action_text: "Restore", 106 | text: "Do you want to restore file? All unsaved changes will be lost", 107 | onpositive: function() { 108 | var text = document.getElementById("editor_edit"); 109 | text.value = edit.file.str(); 110 | edit.touched = false; 111 | } 112 | }); 113 | }; 114 | 115 | edit.save = function() { 116 | var text = document.getElementById("editor_edit"); 117 | if (this.touched && this.file) 118 | this.file.put(text.value); 119 | this.touched = false; 120 | }; 121 | 122 | edit.run = function(fn) { 123 | this.save(); 124 | if (this.file) 125 | fn(this.file.path()); 126 | }; 127 | 128 | function init_text_entry() { 129 | var t = document.getElementById("editor_edit"); 130 | t.wrap = "off"; 131 | t.spellcheck = false; 132 | t.oninput = function() {edit.touched = true;}; 133 | } 134 | 135 | function init_toolbar() { 136 | var ctl = document.getElementById("editor_toolbar"); 137 | shen_web.toolbar(ctl, [ 138 | { 139 | title: "Save and send to REPL", 140 | icon: "web/run.png", 141 | onclick: function() {edit.run(run);} 142 | }, 143 | { 144 | title: "Save", 145 | icon: "web/save.png", 146 | onclick: function() {edit.save();} 147 | }, 148 | { 149 | title: "Reload", 150 | icon: "web/revert.png", 151 | onclick: function() {edit.reload();} 152 | }, 153 | { 154 | title: "Download", 155 | icon: "web/download.png", 156 | onclick: function() {shen_web.fs.download(edit.file, edit.path);} 157 | }, 158 | { 159 | title: "Upload", 160 | icon: "web/upload.png", 161 | onclick: function() { 162 | if (edit.file) 163 | shen_web.fs.upload(edit.file.path(), false, function(files) { 164 | if (files[0]) { 165 | shen_web.fs.read_fileio(edit.file.path(), files[0]); 166 | setTimeout(function() { 167 | edit.load(shen_web.fs.root, edit.file.path()); 168 | }, 50); 169 | } 170 | }); 171 | } 172 | }]); 173 | } 174 | init_text_entry(); 175 | init_toolbar(); 176 | edit.unload(); 177 | shen_web.init_maximize(document.getElementById("editor")); 178 | shen_web.edit = edit; 179 | }; 180 | -------------------------------------------------------------------------------- /web/embed.js: -------------------------------------------------------------------------------- 1 | shen_web.embed_shen = function(opts) { 2 | function io(vm) { 3 | var io = {}; 4 | io.open = open; 5 | vm.glob["*stoutput*"] = vm.Stream(null, write_byte); 6 | vm.ensure_chan_input(); 7 | return io; 8 | 9 | function write_byte(byte, vm) { 10 | shen_web.puts(String.fromCharCode(byte)); 11 | } 12 | } 13 | 14 | function open(name, dir, vm) { 15 | var filename = vm.glob["*home-directory*"] + name; 16 | var loader = shen_web.fs.find_loader(filename); 17 | if (loader) 18 | return loader(filename, dir, vm); 19 | switch (dir.str) { 20 | case "in": 21 | var file = shen_web.fs.root.get(filename); 22 | if (!file) 23 | return vm.error("open: '" + filename + "' does not exist"); 24 | switch (file.type) { 25 | case "f": return vm.buf_stream(file.contents); 26 | case "d": return vm.error("open: '" + filename + "' is directory"); 27 | default: return vm.error("open: '" + filename + "' has unknown type"); 28 | } 29 | case "out": return file_out_stream(shen_web.fs.root.put(null, filename), vm); 30 | default: return vm.error("Unsupported 'open' flags"); 31 | } 32 | } 33 | 34 | function buf_append(a, b) { 35 | var alen = a.length, blen = b.length, r = new Uint8Array(alen + blen); 36 | r.set(a); 37 | r.set(b, alen); 38 | return r; 39 | } 40 | 41 | function file_out_stream(file, vm) { 42 | var buf = new Uint8Array(0); 43 | return vm.Stream(null, 44 | function write_byte(byte, vm) { 45 | buf = buf_append(buf, [byte]); 46 | return byte; 47 | }, 48 | function close(vm) { 49 | file.put(buf); 50 | file.data = buf; 51 | }); 52 | } 53 | 54 | function send(s) { 55 | shen_web.puts(s, "input"); 56 | shen.send_str(s); 57 | } 58 | 59 | function send_file(path) { 60 | send("(load \"" + path + "\")\n"); 61 | } 62 | 63 | function load_init() { 64 | var name = ".init.shen", 65 | f = shen_web.fs.root.get(name); 66 | if (f) 67 | send_file(name); 68 | } 69 | 70 | var posts = {}, posts_id = 0; 71 | 72 | function recv_step(ev) { 73 | if (ev.source !== window) 74 | return; 75 | var data = ev.data, f = posts[data]; 76 | if (f) { 77 | ev.stopPropagation(); 78 | delete posts[data]; 79 | if (!Object.keys(posts).length) 80 | posts_id = 0; 81 | f(); 82 | } 83 | } 84 | 85 | function post(fn, ms) { 86 | if (!ms) { 87 | var id = posts_id++; 88 | posts[id] = fn; 89 | window.postMessage(id, "*"); 90 | } else 91 | setTimeout(fn, ms); 92 | } 93 | 94 | shen.defun("js.redeploy-fs", function() { 95 | var vm = this; 96 | shen_web.fs.deploy(shen_web.fs_index, function() { 97 | vm.resume(true); 98 | }); 99 | vm.interrupt(); 100 | }); 101 | 102 | this.file_out_stream = file_out_stream; 103 | window.addEventListener("message", recv_step, true); 104 | this.post = post; 105 | this.send = send; 106 | this.send_file = send_file; 107 | shen_web.fs_index = opts.fs_index || "fs.json"; 108 | shen_web.set_init_status("Deploying stored filesystem"); 109 | shen_web.init_store(function() { 110 | shen_web.set_init_status("Deploying remote filesystem"); 111 | shen_web.fs.deploy(shen_web.fs_index, function() { 112 | shen_web.set_init_status("Initializing Shen runtime"); 113 | shen.post_async = post; 114 | shen.init({io: io, async: true, ondone: opts.ondone, repl: true}); 115 | load_init(); 116 | }); 117 | }); 118 | }; 119 | -------------------------------------------------------------------------------- /web/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/folder.png -------------------------------------------------------------------------------- /web/folder_new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/folder_new.png -------------------------------------------------------------------------------- /web/folder_open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/folder_open.png -------------------------------------------------------------------------------- /web/fs.js: -------------------------------------------------------------------------------- 1 | shen_web.init_fs = function(file_fn) { 2 | shen_web.set_init_status("Initializing filesystem"); 3 | var fs = {}; 4 | fs.root = new Jsfile(null, "d"); 5 | fs.selected = null; 6 | fs.items = {}; 7 | 8 | window.BlobBuilder = window.BlobBuilder 9 | || window.MozBlobBuilder 10 | || window.WebKitBlobBuilder 11 | || window.MSBlobBuilder; 12 | 13 | fs.download = function(file) { 14 | if (!file || file.type !== "f") 15 | return; 16 | try { 17 | var b = new Blob([file.contents], {type: "application/octet-stream"}); 18 | } catch (e) { 19 | var bb = new window.BlobBuilder(); 20 | bb.append(file.contents.buffer); 21 | var b = bb.getBlob("application/octet-stream"); 22 | } 23 | window.URL = window.URL || window.webkitURL; 24 | var url = window.URL.createObjectURL(b); 25 | var a = document.createElement("a"); 26 | a.style["display"] = "none"; 27 | a.target = "_blank"; 28 | a.href = url; 29 | a.download = file.path().match(/[^/]*$/)[0]; 30 | if (document.createEvent) { 31 | var ev = document.createEvent("MouseEvents"); 32 | ev.initMouseEvent("click", true, true, window, 1, 0, 0, 0, 0, false, 33 | false, false, false, 0, null); 34 | a.dispatchEvent(ev); 35 | } else if (a.click) 36 | a.click(); 37 | setTimeout(function() {URL.revokeObjectURL(url)}, 100); 38 | }; 39 | 40 | fs.read_fileio = function(path, file) { 41 | var fs = this; 42 | var reader = new FileReader(); 43 | reader.onload = function(e) { 44 | fs.root.put(e.target.result, path); 45 | }; 46 | reader.readAsArrayBuffer(file); 47 | }; 48 | 49 | fs.upload = function(path, multiple, fn) { 50 | var fs = this; 51 | shen_web.dialog({ 52 | title: "Upload", 53 | oncreate: function(content, ctl, close) { 54 | var text = document.createElement("div"), 55 | input = document.createElement("input"), 56 | msg; 57 | text.className = "dlg_msg"; 58 | input.type = "file"; 59 | msg = "Choose a file to upload"; 60 | if (multiple) { 61 | msg = "Choose file(s) to upload"; 62 | input.directory = input.webkitdirectory = input.multiple = true; 63 | } 64 | text.appendChild(document.createTextNode(msg)); 65 | input.onchange = function(ev) { 66 | var files = ev.target.files || ev.target.webkitEntries; 67 | fn(files); 68 | close(); 69 | }; 70 | content.appendChild(text); 71 | content.appendChild(input); 72 | } 73 | }); 74 | }; 75 | 76 | fs.upload_to = function(path) { 77 | var fs = this; 78 | this.upload(path, true, function(files) { 79 | for (var i = 0; i < files.length; ++i) { 80 | var f = files[i]; 81 | fs.read_fileio(path + "/" + f.name, f); 82 | } 83 | }); 84 | }; 85 | 86 | function init_handle() { 87 | var handle = document.getElementById("fs_toggle"), 88 | main = document.getElementById("main"); 89 | handle.checked = main.classList.contains("fs_opened"); 90 | handle.onclick = function() { 91 | if (this.checked) 92 | main.classList.add("fs_opened"); 93 | else 94 | main.classList.remove("fs_opened"); 95 | }; 96 | } 97 | 98 | fs.loaders = []; 99 | 100 | fs.find_loader = function(name) { 101 | var hs = this.loaders, f; 102 | for (i = hs.length - 1; i >= 0; --i) { 103 | f = hs[i](name); 104 | if (f) 105 | return f; 106 | } 107 | return null; 108 | }; 109 | 110 | fs.mk_match_loader = function(re, fn) { 111 | return function(name) { 112 | return (name.match(re)) ? fn : null; 113 | }; 114 | }; 115 | 116 | fs.deploy = function deploy_fs(url, fn) { 117 | function load_index(fn) { 118 | shen_web.xhr({url: url, responseType: "text"}, function(data) { 119 | var obj = []; 120 | try { 121 | obj = JSON.parse(data); 122 | } catch(err) { 123 | console.log("fs.deploy", url, "parse error:", err); 124 | } 125 | fn(obj); 126 | }, function(err) { 127 | console.log("fs.deploy", url, "error:", err); 128 | fn([]); 129 | }); 130 | } 131 | 132 | function load_files(entries, i, fn) { 133 | if (i < entries.length) { 134 | var entry = entries[i], from = entry.from, to = entry.to; 135 | if (from && to) { 136 | shen_web.xhr({url: from, responseType: "arraybuffer"}, 137 | function(data) { 138 | shen_web.fs.root.put(data, to); 139 | load_files(entries, i + 1, fn); 140 | }, function(err) { 141 | console.log("fs.deploy entry err", err); 142 | load_files(entries, i + 1, fn); 143 | }); 144 | } else 145 | load_files(entries, i + 1, fn); 146 | } else 147 | fn(); 148 | } 149 | 150 | load_index(function(index) { 151 | load_files(index, 0, function() { 152 | if (fn) 153 | fn(); 154 | }); 155 | }); 156 | }; 157 | 158 | function mk_file_dlg(dir, type) { 159 | if (!dir || dir.type !== "d") 160 | return; 161 | var fn, opts = { 162 | onpositive: function(name) { 163 | var path = dir.path() + "/" + name; 164 | if (!fs.root.get(path)) 165 | fn(path); 166 | } 167 | }; 168 | switch (type) { 169 | case "f": 170 | opts.action_text = "Create file"; 171 | opts.label = "File name:"; 172 | fn = function(path) {fs.root.put("", path);}; 173 | break; 174 | case "d": 175 | opts.action_text = "Create directory"; 176 | opts.label = "Directory name:"; 177 | fn = function(path) {fs.root.mkdir(path);}; 178 | break; 179 | } 180 | shen_web.prompt(opts); 181 | } 182 | 183 | function rm_dlg(file) { 184 | if (!file) 185 | return; 186 | var path = file.path(), opts = { 187 | action_text: "Remove", 188 | text: "Remove '" + path + "'?", 189 | onpositive: function() {file.rm();} 190 | }; 191 | if (!file.parent) 192 | opts.text = "Remove whole filesystem contents?"; 193 | shen_web.confirm(opts); 194 | } 195 | 196 | var rm_btn_def = { 197 | title: "Delete", 198 | icon: "web/rm.png", 199 | onclick: function() {rm_dlg(fs.selected);} 200 | }; 201 | 202 | function init_file_ctl(div) { 203 | return shen_web.toolbar(div, [ 204 | { 205 | title: "Download file", 206 | icon: "web/download.png", 207 | onclick: function() { 208 | if (fs.selected) 209 | fs.download(fs.selected); 210 | } 211 | }, 212 | rm_btn_def 213 | ]); 214 | } 215 | 216 | function init_dir_ctl(div) { 217 | return shen_web.toolbar(div, [ 218 | { 219 | title: "Create file", 220 | icon: "web/new.png", 221 | onclick: function() {mk_file_dlg(fs.selected, "f");} 222 | }, 223 | { 224 | title: "Create dir", 225 | icon: "web/folder_new.png", 226 | onclick: function() {mk_file_dlg(fs.selected, "d");} 227 | }, 228 | { 229 | title: "Upload file", 230 | icon: "web/upload.png", 231 | onclick: function() { 232 | if (fs.selected && fs.selected.type === "d") 233 | fs.upload_to(fs.selected.path()); 234 | } 235 | }, 236 | rm_btn_def, 237 | ]); 238 | } 239 | 240 | function dir_onclick_icon(icon, contents) { 241 | if (contents.classList.toggle("fs_subdir_collapsed")) 242 | icon.src = "web/folder.png"; 243 | else 244 | icon.src = "web/folder_open.png"; 245 | return true; 246 | } 247 | 248 | function toggle_item_select(entry, select) { 249 | var fn = (select) ? "add" : "remove"; 250 | for (var c = entry.childNodes, i = 0; i < c.length; ++i) { 251 | var sub = c[i]; 252 | if (sub.classList.contains("fs_name")) 253 | sub.classList[fn]("accent_bg", "accent_fg"); 254 | } 255 | } 256 | 257 | fs.select = function(file, call_event) { 258 | if (fs.selected) { 259 | toggle_item_select(fs.selected.container, false); 260 | fs.selected.container.classList.remove("fs_selection"); 261 | } 262 | if (typeof(file) === "string") 263 | file = fs.root.get(file); 264 | if (!file) 265 | return; 266 | if (call_event === undefined || call_event) 267 | file_fn(file, file.path()); 268 | fs.file_ctl.classList.remove("undisplayed"); 269 | fs.dir_ctl.classList.remove("undisplayed"); 270 | switch (file.type) { 271 | case "f": fs.dir_ctl.classList.add("undisplayed"); break; 272 | case "d": fs.file_ctl.classList.add("undisplayed"); break; 273 | } 274 | fs.selected = file; 275 | file.container.classList.add("fs_selection"); 276 | toggle_item_select(fs.selected.container, true); 277 | }; 278 | 279 | function basename(path) { 280 | return path.match(/[^/]*$/)[0]; 281 | } 282 | 283 | function file_icon(file, path) { 284 | var icon = document.createElement("img"); 285 | icon.className = "fs_icon"; 286 | if (file.type === "d") { 287 | icon.src = "web/folder_open.png"; 288 | } else if (path.match(/\.html$/)) 289 | icon.src = "web/html.png"; 290 | else if (path.match(/\.shen$/)) 291 | icon.src = "web/shen_source.png"; 292 | else 293 | icon.src = "web/document.png"; 294 | return icon; 295 | } 296 | 297 | function mk_entry_name(file) { 298 | var name = document.createElement("div"), 299 | name_text = document.createElement("span"), 300 | icon = file_icon(file, file.name), 301 | caption = file.parent ? file.name : "Filesystem"; 302 | 303 | if (file.type === "d") { 304 | icon.onclick = function(ev) { 305 | ev.stopPropagation(); 306 | var subdir = shen_web.by_class("fs_dir", file.container)[0]; 307 | return dir_onclick_icon(icon, subdir); 308 | }; 309 | } 310 | 311 | name.className = "fs_name"; 312 | name.onclick = function() {fs.select(file);}; 313 | name_text.className = "fs_name_text"; 314 | name_text.appendChild(document.createTextNode(caption)); 315 | name.appendChild(icon); 316 | name.appendChild(name_text); 317 | return name; 318 | } 319 | 320 | function oncreate_dir(file) { 321 | file.container.classList.add("fs_entry", "fs_dir_entry"); 322 | var subdir = document.createElement("ul"); 323 | subdir.className = "fs_dir"; 324 | file.container.appendChild(mk_entry_name(file)); 325 | file.container.appendChild(subdir); 326 | } 327 | 328 | function oncreate_file(file) { 329 | file.container.classList.add("fs_entry", "fs_file_entry"); 330 | file.container.appendChild(mk_entry_name(file)); 331 | } 332 | 333 | function attach_sorted(entry, container) { 334 | var items = container.childNodes, n = items.length, i, 335 | name = entry.shen_file.name; 336 | for (i = 0; i < n; ++i) { 337 | var item = items[i]; 338 | if (item.shen_file && item.shen_file.name > name) 339 | break; 340 | } 341 | if (i < n) 342 | container.insertBefore(entry, item); 343 | else 344 | container.appendChild(entry); 345 | } 346 | 347 | function oncreate() { 348 | var entry = document.createElement("li"); 349 | this.container = entry; 350 | entry.shen_file = this; 351 | switch (this.type) { 352 | case "d": 353 | oncreate_dir(this); 354 | shen_web.store.mkdir(this.path()); 355 | break; 356 | case "f": 357 | oncreate_file(this); 358 | shen_web.store.touch(this.path()); 359 | break; 360 | } 361 | if (this.parent) { 362 | var container = shen_web.by_tag("ul", this.parent.container); 363 | attach_sorted(entry, container); 364 | } 365 | } 366 | 367 | function onrm() { 368 | var call_event; 369 | if (this === fs.selected) 370 | fs.select(this.parent ? this.parent : fs.root, (call_event = false)); 371 | if (this.parent) 372 | this.container.parentNode.removeChild(this.container); 373 | shen_web.store.rm(this.path()); 374 | } 375 | 376 | function onwrite() { 377 | shen_web.store.put(this.path(), this.type, this.contents); 378 | } 379 | 380 | init_handle(); 381 | 382 | fs.dir = document.getElementById("fs_tree"); 383 | fs.file_ctl = init_file_ctl(document.getElementById("file_ctl")); 384 | fs.dir_ctl = init_dir_ctl(document.getElementById("dir_ctl")); 385 | fs.root.oncreate = oncreate; 386 | fs.root.onwrite = onwrite; 387 | fs.root.onrm = onrm; 388 | fs.root.container = fs.dir; 389 | oncreate_dir(fs.root, ""); 390 | this.fs = fs; 391 | }; 392 | -------------------------------------------------------------------------------- /web/html.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/html.png -------------------------------------------------------------------------------- /web/jsfile.js: -------------------------------------------------------------------------------- 1 | function Jsfile(name, type, parent) { 2 | if (!(this instanceof Jsfile)) 3 | return new Jsfile(name, type, parent); 4 | var keys = ["oncreate", "onrm", "onwrite"], files; 5 | 6 | this.name = name = name || ""; 7 | this.type = type; 8 | this.parent = parent; 9 | 10 | this.toString = function() { 11 | return "[object Jsfile " + (this.name || "/") + "]"; 12 | }; 13 | 14 | switch (type) { 15 | case "d": 16 | files = {}; 17 | this.get_file = function(name) {return files[name];}; 18 | this.add = function(file) {files[file.name] = file;}; 19 | this.detach_file = function(name) {delete files[name];}; 20 | this.mkdir = mkdir; 21 | break; 22 | case "f": 23 | this.contents = null; 24 | this.get_file = function() {return null;}; 25 | this.add = function() {return null;}; 26 | this.mkdir = function() {return null;}; 27 | this.set_contents = function(x) { 28 | this.contents = ensure_array(x); 29 | if (this.onwrite) 30 | this.onwrite(); 31 | }; 32 | break; 33 | } 34 | 35 | this.path = function() { 36 | return (parent) ? (parent.path() + "/" + name) : name; 37 | }; 38 | 39 | this.get = function(path) { 40 | path = split_path(path); 41 | if (path.length === 1 && path[0] === "") 42 | return this; 43 | var file = this; 44 | for (var i = 0; i < path.length && file; ++i) 45 | file = file.get_file(path[i]); 46 | return (i >= path.length) ? file : null; 47 | }; 48 | 49 | this.put = function(data, path) { 50 | path = split_path(path); 51 | var file = this.get(path); 52 | if (!file) 53 | file = Jsfile(basename(path), "f", this.mkdir(dirname(path))); 54 | file.set_contents(data); 55 | return file; 56 | }; 57 | 58 | this.rm = function(path) { 59 | if (path) { 60 | var file = this.get(path); 61 | if (file) 62 | file.rm(); 63 | return; 64 | } 65 | if (type === "d") { 66 | var fnames = Object.keys(files), n = fnames.length, i; 67 | for (i = 0; i < n; ++i) 68 | files[fnames[i]].rm(); 69 | } 70 | if (this.onrm) 71 | this.onrm(); 72 | if (this.parent) 73 | this.parent.detach_file(this.name); 74 | }; 75 | 76 | this.walk = function(fn, context, name, parent, path) { 77 | name = name || ""; 78 | path = path || ""; 79 | if (name) 80 | path = path + "/" + name; 81 | var x = fn({file: this, name: name, path: path, parent: parent}, context); 82 | if (this.type === "d") 83 | for (var i in files) 84 | files[i].walk(fn, x, i, this, path); 85 | }; 86 | 87 | this.show = function(pr) { 88 | try { 89 | pr = pr || console.log.bind(console); 90 | } catch(e) { 91 | pr = pr || print; 92 | } 93 | function p(entry, prefix) { 94 | pr(entry.path + ((entry.file.type == "d") ? "/" : "")); 95 | } 96 | this.walk(p); 97 | }; 98 | 99 | function mkdir(path) { 100 | path = split_path(path); 101 | var file = this, i, sub; 102 | for (i = 0; i < path.length; ++i) { 103 | sub = file.get_file(path[i]); 104 | if (!sub) 105 | sub = Jsfile(path[i], "d", file); 106 | file = sub; 107 | } 108 | return (i >= path.length) ? file : null; 109 | } 110 | 111 | function basename(path) { 112 | return path[path.length - 1]; 113 | } 114 | 115 | function dirname(path) { 116 | return path.slice(0, path.length - 1); 117 | } 118 | 119 | function split_path(path) { 120 | if (!path) 121 | return [""]; 122 | if (typeof(path) === "string") 123 | return path.replace(/\/\/*/g, "/").replace(/^\//, "").replace(/\/$/, "") 124 | .split("/"); 125 | return path; 126 | } 127 | 128 | function bytes_from_str(str) { 129 | return shen.utf8_from_str(str); 130 | } 131 | 132 | function str_from_bytes(arr) { 133 | return shen.str_from_utf8(arr); 134 | } 135 | 136 | this.str = function() { 137 | return str_from_bytes(this.contents); 138 | }; 139 | 140 | function ensure_array(x) { 141 | if (x instanceof Uint8Array) 142 | return x; 143 | if (typeof(x) === "string") 144 | return new Uint8Array(bytes_from_str(x)); 145 | if (x instanceof Array || x instanceof ArrayBuffer) 146 | return new Uint8Array(x); 147 | return new Uint8Array(); 148 | } 149 | 150 | if (parent) { 151 | for (var i = 0; i < keys.length; ++i) { 152 | var key = keys[i]; 153 | this[key] = parent[key]; 154 | } 155 | parent.add(this); 156 | } 157 | if (this.oncreate) 158 | this.oncreate(); 159 | } 160 | -------------------------------------------------------------------------------- /web/loader_github.js: -------------------------------------------------------------------------------- 1 | shen_web.plugins.push(function(done) { 2 | var match_re = /^(http|https):\/\/github\.com\/./, 3 | path_re = /^(http|https):\/\/github\.com\/([^/]*\/[^/]*)\/(.*)$/; 4 | 5 | function api_url(name) { 6 | var p = name.match(path_re); 7 | repo = p[2].replace(/\/\/*/g, "/"), 8 | path = p[3].replace(/\/\/*/g, "/"); 9 | return "https://api.github.com/repos/" + repo + "/contents/" + path; 10 | } 11 | 12 | function load(name, dir, vm) { 13 | switch (dir.str) { 14 | case "out": 15 | return vm.error("open: writing files to github is not supported"); 16 | case "in": 17 | shen_web.xhr({ 18 | url: api_url(name), 19 | responseType: "arraybuffer", 20 | req_headers: {"Accept": "application/vnd.github.3.raw"} 21 | }, function(data) { 22 | vm.resume(vm.buf_stream(data)); 23 | }, function(err) { 24 | console.log("github err", err); 25 | vm.resume(new Error("open: Unable to load file " + name)); 26 | }); 27 | vm.interrupt(); 28 | break; 29 | default: return vm.error("open: unsupported flags"); 30 | } 31 | } 32 | shen_web.fs.loaders.push(shen_web.fs.mk_match_loader(match_re, load)); 33 | done(); 34 | }); 35 | -------------------------------------------------------------------------------- /web/loader_http.js: -------------------------------------------------------------------------------- 1 | shen_web.plugins.push(function(done) { 2 | var match_re = /^(http|https):\/\/./; 3 | function load(name, dir, vm) { 4 | switch (dir.str) { 5 | case "out": 6 | return vm.error("open: writing files via http is not supported"); 7 | case "in": 8 | // Let's hope that resource have CORS enabled 9 | shen_web.xhr({ 10 | url: name, 11 | responseType: "arraybuffer", 12 | }, function(data) { 13 | vm.resume(vm.buf_stream(data)); 14 | }, function(err) { 15 | console.log("http loader err:", err); 16 | vm.resume(new Error("open: Unable to load file " + name)); 17 | }); 18 | vm.interrupt(); 19 | break; 20 | default: return vm.error("open: unsupported flags"); 21 | } 22 | } 23 | shen_web.fs.loaders.push(shen_web.fs.mk_match_loader(match_re, load)); 24 | done(); 25 | }); 26 | -------------------------------------------------------------------------------- /web/maximize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/maximize.png -------------------------------------------------------------------------------- /web/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/new.png -------------------------------------------------------------------------------- /web/repl.js: -------------------------------------------------------------------------------- 1 | shen_web.init_repl = function() { 2 | shen_web.set_init_status("Initializing repl"); 3 | var hist = [], 4 | hist_index = 0, 5 | max_hist_size = 300, 6 | saved_line = null, 7 | repl = {}; 8 | 9 | function puts(str, tag) { 10 | if (!str.length) 11 | return; 12 | var cont = repl.out.parentNode, 13 | sd = cont.scrollHeight - cont.scrollTop, 14 | diff = Math.abs(sd - cont.clientHeight), 15 | lines = str.split("\n"), i, objs = []; 16 | add_line(lines[0]); 17 | for (i = 1; i < lines.length; ++i) { 18 | objs.push(document.createElement("br")); 19 | add_line(lines[i]); 20 | } 21 | if (tag) { 22 | var s = document.createElement("span"); 23 | s.className = "repl_tag_" + tag; 24 | for (i = 0; i < objs.length; ++i) 25 | s.appendChild(objs[i]); 26 | repl.out.insertBefore(s, repl.inp); 27 | } else 28 | for (i = 0; i < objs.length; ++i) 29 | repl.out.insertBefore(objs[i], repl.inp); 30 | repl.out.normalize(); 31 | if (diff < 5) 32 | cont.scrollTop = cont.scrollHeight; 33 | 34 | function add_line(s) { 35 | if (s.length) 36 | objs.push(document.createTextNode(s)); 37 | } 38 | } 39 | 40 | function set_caret_pos(off) { 41 | var range = document.createRange(), 42 | sel = window.getSelection(), 43 | obj = repl.inp.childNodes[0], 44 | t; 45 | if (!obj) 46 | return; 47 | if (off === undefined) 48 | off = (repl.inp.textContent || repl.inp.innerText).length; 49 | range.setStart(obj, off); 50 | range.collapse(true); 51 | sel.removeAllRanges(); 52 | sel.addRange(range); 53 | } 54 | 55 | function selection_len() { 56 | var sel = window.getSelection(), len = 0; 57 | for (var i = 0; i < sel.rangeCount; ++i) { 58 | var r = sel.getRangeAt(i); 59 | len += r.endOffset - r.startOffset; 60 | } 61 | return len; 62 | } 63 | 64 | function init_input() { 65 | var t = document.getElementById("repl_in"), 66 | b = document.getElementById("repl_in_send"), 67 | out = document.getElementById("repl_out"); 68 | 69 | repl.inp = t; 70 | repl.out = out; 71 | 72 | repl.inp.contentEditable = true; 73 | repl.inp.spellcheck = false; 74 | repl.out.onclick = function() { 75 | if (!selection_len() && document.activeElement !== repl.inp) 76 | repl.inp.focus(); 77 | }; 78 | 79 | repl.inp.onkeydown = function(e) { 80 | switch (e.keyCode || e.which) { 81 | case 0xd: return false; 82 | default: return true; 83 | } 84 | }; 85 | 86 | repl.inp.onkeyup = function(e) { 87 | switch (e.keyCode || e.which) { 88 | case 0xd: 89 | var line = repl.inp.textContent || repl.inp.innerText; 90 | if (line !== undefined) { 91 | line = line.replace(/\n*$/, ""); 92 | shen_web.send(line + "\n"); 93 | if (hist.length == 0 || line != hist[hist.length - 1]) { 94 | hist.push(line); 95 | if (hist.length > max_hist_size) 96 | hist.shift(); 97 | } 98 | hist_index = hist.length; 99 | } 100 | shen_web.clean(repl.inp); 101 | return true; 102 | 103 | case 0x26: 104 | if (hist_index > 0) { 105 | if (hist_index == hist.length) 106 | saved_line = (repl.inp.textContent || repl.inp.innerText || ""); 107 | var new_text = hist[--hist_index]; 108 | repl.inp.textContent = new_text; 109 | set_caret_pos(); 110 | } 111 | break; 112 | 113 | case 0x28: 114 | if (hist_index < hist.length - 1) { 115 | repl.inp.textContent = hist[++hist_index]; 116 | set_caret_pos(); 117 | } else if (saved_line !== null) { 118 | hist_index = hist.length; 119 | repl.inp.textContent = saved_line; 120 | set_caret_pos(); 121 | } 122 | break; 123 | } 124 | return false; 125 | }; 126 | } 127 | 128 | shen_web.puts = puts; 129 | shen_web.init_maximize(document.getElementById("repl")); 130 | shen_web.repl = repl; 131 | init_input(); 132 | }; 133 | -------------------------------------------------------------------------------- /web/revert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/revert.png -------------------------------------------------------------------------------- /web/rm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/rm.png -------------------------------------------------------------------------------- /web/run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/run.png -------------------------------------------------------------------------------- /web/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/save.png -------------------------------------------------------------------------------- /web/shen.css: -------------------------------------------------------------------------------- 1 | /* { GENERAL */ 2 | *, *:before, *:after { 3 | -moz-box-sizing: border-box; 4 | -webkit-box-sizing: border-box; 5 | box-sizing: border-box; 6 | } 7 | 8 | .tbl { 9 | display: table; 10 | border-collapse: collapse; 11 | } 12 | 13 | .tbl_row {display: table-row;} 14 | .tbl_cell {display: table-cell;} 15 | 16 | .hidden {visibility: hidden;} 17 | .undisplayed {display: none;} 18 | .tt_font {font-family: Courier, Fixed, monospace;} 19 | 20 | .norm_fg {color: #212121;} 21 | .norm_bg {background-color: #ffffff; border-color: #7f7f7f;} 22 | 23 | .accent_fg {color: #212121;} 24 | .accent_bg {background-color: #9ee776; border-color: #0070c0;} 25 | 26 | .btn_font {font-family: serif; font-size: 1em;} 27 | 28 | .btn_fg {color: #222;} 29 | .btn_fg:active {color: #fff;} 30 | .btn_bg {background-color: transparent; border-color: #8bc34a;} 31 | .btn_bg:hover {background-color: #8bc34a; border-color: #7f7f7f;} 32 | .btn_bg:active {background-color: #000; border-color: #000;} 33 | 34 | .dim_btn_fg {color: #444;} 35 | .dim_btn_fg:active {color: #fff;} 36 | .dim_btn_bg {background-color: #eee; border-color: transparent;} 37 | .dim_btn_bg:hover {background-color: #ddd; border-color: #ccc;} 38 | .dim_btn_bg:active {background-color: #000; border-color: #000;} 39 | 40 | .dmg_btn_fg {color: #444;} 41 | .dmg_btn_fg:active {color: #fff;} 42 | .dmg_btn_bg {background-color: #eee; border-color: transparent;} 43 | .dmg_btn_bg:hover {background-color: #ddd; border-color: #ccc;} 44 | .dmg_btn_bg:active {background-color: #000; border-color: #000;} 45 | 46 | .hdr_fg {color: #ffffff;} 47 | .hdr_bg {background-color: #556; border-color: #445;} 48 | 49 | .alt_fg {color: #212121;} 50 | .alt_bg {background-color: #e4ffd5; border-color: #e4ffd5;} 51 | .alt_hdr_fg {color: #222;} 52 | .alt_hdr_bg {background-color: #d9ffc3; border-color: #d0f0c0;} 53 | 54 | .entry_fg {color: #212121;} 55 | .entry_bg { 56 | background-color: #ffffff; 57 | border-color: #7f7f7f; 58 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.3); 59 | border-width: 1px; 60 | } 61 | 62 | .repl_tag_input {color: #567;} 63 | .repl_tag_err {color: #c43;} 64 | 65 | .noselect { 66 | user-select: none; 67 | -moz-user-select: none; 68 | -webkit-user-select: none; 69 | -ms-user-select: none; 70 | -o-user-select: none; 71 | } 72 | 73 | .icon_btn { 74 | margin: 0; 75 | padding: 2px; 76 | border: 0; 77 | transition: 0.4s; 78 | -webkit-transition: 0.4s; 79 | } 80 | 81 | .icon_btn > img { 82 | vertical-align: middle; 83 | } 84 | 85 | .code { 86 | font-family: monospace; 87 | padding: 4px; 88 | outline: 0; 89 | } 90 | 91 | .expand { 92 | position: absolute; 93 | top: 0; 94 | right: 0; 95 | bottom: 0; 96 | left: 0; 97 | } 98 | 99 | .maximized { 100 | position: absolute; 101 | top: 0 !important; 102 | right: 0 !important; 103 | bottom: 0 !important; 104 | left: 0 !important; 105 | z-index: 100; 106 | background: #fff; 107 | width: 100% !important; 108 | height: 100% !important; 109 | transition: 0.4s; 110 | -webkit-transition: 0.4s; 111 | } 112 | 113 | .overlay { 114 | position: fixed; 115 | top: 0; 116 | right: 0; 117 | bottom: 0; 118 | left: 0; 119 | z-index: 1000; 120 | background-color: rgba(20, 20, 30, 0.7); 121 | cursor: pointer; 122 | } 123 | 124 | body { 125 | padding: 0; 126 | margin: 0; 127 | border: 0; 128 | overflow: hidden; 129 | position: fixed !important; 130 | font-size: 14px; 131 | } 132 | 133 | /* } GENERAL */ 134 | 135 | .fs_opened { 136 | transform: translate(18em, 0); 137 | -webkit-transform: translate(18em, 0); 138 | -ms-transform: translate(18em, 0); 139 | transition: transform 0.5s; 140 | -webkit-transition: -webkit-transform 0.5s; 141 | } 142 | 143 | .main { 144 | transition: transform 0.5s; 145 | -webkit-transition: -webkit-transform 0.5s; 146 | } 147 | 148 | .main_inner { 149 | width: 100%; 150 | height: 100%; 151 | position: absolute; 152 | } 153 | 154 | .editor { 155 | right: 50%; 156 | width: 50%; 157 | height: 100%; 158 | margin: 0; 159 | padding: 0; 160 | } 161 | 162 | .outer { 163 | width: 100%; 164 | height: 100%; 165 | } 166 | 167 | .inner { 168 | width: 100%; 169 | height: 100%; 170 | overflow: auto; 171 | position: relative; 172 | } 173 | 174 | .editor_edit { 175 | resize: none; 176 | width: 100%; 177 | height: 100%; 178 | margin: 0; 179 | border: 0; 180 | display: block; 181 | } 182 | 183 | .editor_border { 184 | border-left: 1px solid; 185 | border-right: 1px solid; 186 | } 187 | 188 | .hdr { 189 | position: relative; 190 | white-space: nowrap; 191 | } 192 | 193 | .hdr_title { 194 | width: 100%; 195 | max-width: 0; 196 | vertical-align: middle; 197 | font-family: sans-serif; 198 | text-align: center; 199 | font-weight: bold; 200 | text-overflow: ellipsis; 201 | overflow-x: hidden; 202 | white-space: nowrap; 203 | } 204 | 205 | .hdr_title::after { 206 | content: "\00a0"; 207 | } 208 | 209 | .hdr_container { 210 | border-width: 0; 211 | border-bottom-width: 1px; 212 | border-style: solid; 213 | } 214 | 215 | .hdr_tool { 216 | white-space: nowrap; 217 | vertical-align: middle; 218 | } 219 | 220 | .hdr_tool > * { 221 | vertical-align: middle; 222 | } 223 | 224 | /* FS { */ 225 | 226 | .repl { 227 | width: 50%; 228 | height: 100%; 229 | left: 50%; 230 | margin: 0; 231 | padding: 0; 232 | } 233 | 234 | .repl_out_container { 235 | width: 100%; 236 | height: 100%; 237 | } 238 | 239 | .repl_out { 240 | width: 100%; 241 | height: 100%; 242 | white-space: pre-wrap; 243 | cursor: text; 244 | } 245 | 246 | .repl_out > span { 247 | margin: 0; 248 | padding: 0 1px; 249 | border: 0; 250 | } 251 | 252 | .repl_out > span.repl_tag_input { 253 | color: #555; 254 | } 255 | 256 | .repl_in { 257 | height: 10px; 258 | min-width: 100px; 259 | padding-left: 1px !important; 260 | } 261 | 262 | .repl_in::after { 263 | content:"\200d"; 264 | border-left: 2px solid Black; 265 | } 266 | 267 | .repl_in:focus::after { 268 | border: 0; 269 | } 270 | 271 | .repl_in:focus:empty::after { 272 | content:"\200d"; 273 | border-left: 1px solid Black; 274 | } 275 | 276 | /* REPL } */ 277 | 278 | /* FS { */ 279 | input.fs_toggle { 280 | display: none; 281 | } 282 | 283 | input.fs_toggle + label { 284 | padding: 0 8px; 285 | box-shadow: 2px 0 2px -2px rgba(0, 0, 0, 0.9); 286 | border-right: 1px solid; 287 | vertical-align: middle; 288 | } 289 | 290 | .fs { 291 | position: absolute; 292 | top: 0; 293 | bottom: 0; 294 | left: 0; 295 | transform: translate(-100%, 0); 296 | -ms-transform: translate(-100%, 0); 297 | -webkit-transform: translate(-100%, 0); 298 | width: 18em; 299 | height: 100%; 300 | } 301 | 302 | .fs_subdir_collapsed { 303 | display: none; 304 | } 305 | 306 | ul.fs_dir { 307 | margin: 0; 308 | padding-left: 1.3em; 309 | } 310 | 311 | .fs_name { 312 | white-space: nowrap; 313 | display: inline-block; 314 | border: 1px solid transparent; 315 | padding: 0 6px 0 6px; 316 | line-height: 130%; 317 | font-family: sans-serif; 318 | } 319 | 320 | .fs_name:hover { 321 | cursor: pointer; 322 | } 323 | 324 | .fs_icon { 325 | margin-right: 3px; 326 | } 327 | 328 | .fs_name_text, .fs_icon { 329 | vertical-align: middle; 330 | } 331 | 332 | .fs_entry { 333 | list-style: none; 334 | } 335 | 336 | .fs_toggle_arrow { 337 | vertical-align: middle; 338 | padding-left: 4px; 339 | } 340 | 341 | .fs_toggle_icon { 342 | vertical-align: middle; 343 | } 344 | 345 | .fs_toggle_arrow::after {content: "»";} 346 | .fs_opened .fs_toggle_arrow::after {content: "«";} 347 | 348 | /* FS } */ 349 | 350 | 351 | /* WAIT { */ 352 | 353 | .wait { 354 | background-color: #667; 355 | cursor: wait; 356 | } 357 | 358 | .wait_pane { 359 | position: absolute; 360 | text-align: center; 361 | top: 50%; 362 | left: 0; 363 | right: 0; 364 | transform: translate(0, -50%); 365 | -webkit-transform: translate(0, -50%); 366 | -ms-transform: translate(0, -50%); 367 | padding: 16px; 368 | outline: 1px solid; 369 | box-shadow: 0 1px 5px rgba(0, 0, 0, 0.9); 370 | } 371 | 372 | .wait_text { 373 | font-family: serif; 374 | font-weight: bold; 375 | padding: 2px 0; 376 | } 377 | 378 | .wait_status { 379 | font-weight: normal; 380 | color: #454; 381 | padding-bottom: 4px; 382 | } 383 | 384 | .wait_error { 385 | background-color: Red; 386 | } 387 | /* WAIT } */ 388 | 389 | /* DLG { */ 390 | 391 | .dlg { 392 | position: absolute; 393 | top: 50%; 394 | left: 50%; 395 | transform: translate(-50%, -50%); 396 | -webkit-transform: translate(-50%, -50%); 397 | -ms-transform: translate(-50%, -50%); 398 | background-color: #fff; 399 | box-shadow: 1px 1px 10px #000; 400 | cursor: default; 401 | display: table; 402 | } 403 | 404 | .dlg_title_outer { 405 | background: #456; 406 | color: #fff; 407 | font-weight: bold; 408 | display: table-row; 409 | } 410 | 411 | .dlg_title { 412 | padding: 4px; 413 | display: table-cell; 414 | text-align: center; 415 | } 416 | 417 | .dlg_content_outer { 418 | display: table-row; 419 | } 420 | 421 | .dlg_content { 422 | padding: 16px; 423 | display: table-cell; 424 | } 425 | 426 | .dlg_content label { 427 | padding-right: 1em; 428 | } 429 | 430 | .dlg_content input[type="text"] { 431 | outline: 0; 432 | } 433 | 434 | .dlg_msg { 435 | margin-bottom: 16px; 436 | } 437 | 438 | .dlg_ctl { 439 | display: table-row; 440 | padding: 16px; 441 | float: right; 442 | } 443 | 444 | .dlg_ctl > * { 445 | display: table-cell; 446 | margin: 0; 447 | margin-left: 8px; 448 | } 449 | 450 | .dlg_ctl > *:first-child { 451 | display: table-cell; 452 | margin: 0; 453 | margin-right: 8px; 454 | } 455 | 456 | .dlg_close { 457 | border: 0; 458 | padding: 0; 459 | margin: 0; 460 | font-weight: bold; 461 | float: right; 462 | background: transparent; 463 | color: #f67; 464 | } 465 | 466 | .dlg_ctl button { 467 | padding: 4px 8px; 468 | border-width: 1px; 469 | border-style: solid; 470 | outline: 0; 471 | font-weight: bold; 472 | box-shadow: 1px 1px 2px rgba(0, 0, 0, 1.0); 473 | } 474 | 475 | button.dlg_cancel { 476 | font-weight: normal; 477 | box-shadow: none; 478 | } 479 | 480 | /* DLG } */ 481 | 482 | /* SHEN DOC { */ 483 | 484 | .editor_view { 485 | font-family: serif; 486 | } 487 | 488 | .editor_view h1 { 489 | font-size: 1.8em; 490 | font-variant: small-caps; 491 | line-height: 0.9em;} 492 | 493 | .editor_view h2 { 494 | font-size: 1.3em; 495 | border-bottom: 1px solid #dde; 496 | margin: 1em 0; 497 | } 498 | 499 | .editor_view h3 {font-size: 1.2em;} 500 | .editor_view h4 {font-size: 1em;} 501 | 502 | .editor_view code { 503 | font-family: monospace; 504 | } 505 | 506 | .editor_view pre > code { 507 | margin: 0 2em; 508 | padding: 8px 4px; 509 | display: block; 510 | position: relative; 511 | } 512 | 513 | .editor_view pre > code:hover { 514 | background-color: #efd; 515 | transition: background-color 0.2s ease-in; 516 | } 517 | 518 | .editor_view hr { 519 | border: 0; 520 | height: 1px; 521 | background-color: #ddd; 522 | margin: 4px 2em; 523 | } 524 | 525 | .editor_view pre + hr { 526 | background-color: transparent; 527 | } 528 | 529 | .editor_view .popup { 530 | position: absolute; 531 | bottom: 0; 532 | left: 0; 533 | top: 0; 534 | 535 | transform: translate(-100%, 0); 536 | -webkit-transform: translate(-100%, 0); 537 | -ms-transform: translate(-100%, 0); 538 | } 539 | 540 | /* } SHEN DOC */ 541 | 542 | /* HACKS */ 543 | button::-moz-focus-inner, 544 | input[type="button"]::-moz-focus-inner, 545 | input[type="submit"]::-moz-focus-inner, 546 | input[type="reset"]::-moz-focus-inner { 547 | padding: 0 !important; 548 | border: 0 !important; 549 | } 550 | -------------------------------------------------------------------------------- /web/shen_source.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/shen_source.png -------------------------------------------------------------------------------- /web/store.js: -------------------------------------------------------------------------------- 1 | shen_web.init_store = function(done) { 2 | var fsdb = {}, 3 | idb = window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB 4 | || window.OIndexedDB || window.msIndexedDB, 5 | ver = 1; 6 | 7 | function on(r, fn) { 8 | r[fn.name] = fn; 9 | return r; 10 | } 11 | 12 | function onreq(req) { 13 | for (var i = 1; i < arguments.length; ++i) { 14 | var fn = arguments[i]; 15 | req[fn.name] = fn; 16 | } 17 | return req; 18 | } 19 | 20 | function with_db(self, fn) { 21 | if (self.db) 22 | fn.call(self); 23 | else if (idb) 24 | return self.open(function(e) {fn.call(self, e);}); 25 | else { 26 | fn("No support"); 27 | } 28 | } 29 | 30 | function get_store(self, access) { 31 | var trans = self.db.transaction(["fs"], access); 32 | return trans.objectStore("fs"); 33 | } 34 | 35 | fsdb.open = function(done) { 36 | var self = this; 37 | if (idb) 38 | onreq(idb.open("shen", ver), 39 | function onsuccess(e) { 40 | self.db = e.target.result; 41 | done(); 42 | }, 43 | function onerror(e) { 44 | console.log("IndexedDB error", e); 45 | done(e); 46 | }, 47 | function onupgradeneeded(e) { 48 | var db = e.target.result; 49 | if (!db.objectStoreNames.contains("fs")) { 50 | var store = db.createObjectStore("fs", {keyPath: "name"}); 51 | store.createIndex("name", "name", {unique: true}); 52 | } 53 | }); 54 | else 55 | done("No support"); 56 | }; 57 | 58 | fsdb.deploy = function(done) { 59 | done = done || function() {}; 60 | with_db(this, function(err) { 61 | if (err) 62 | return done(err); 63 | var store = get_store(this, "readonly"), 64 | keys = [], 65 | root = shen_web.fs.root; 66 | onreq(store.index("name").openCursor(), 67 | function onsuccess(e) { 68 | var cur = e.target.result; 69 | if (cur) { 70 | switch(cur.value.type) { 71 | case "d": root.mkdir(cur.value.name); break; 72 | case "f": root.put(cur.value.data, cur.value.name); break; 73 | } 74 | cur.continue(); 75 | } else { 76 | done(); 77 | } 78 | }, 79 | function onerror(e) { 80 | done(e); 81 | }); 82 | }); 83 | }; 84 | 85 | fsdb.put = function(name, type, data, done) { 86 | done = done || function() {}; 87 | with_db(this, function(err) { 88 | if (err) 89 | return done(err); 90 | var store = get_store(this, "readwrite"), 91 | file = {name: name, type: type, data: data}; 92 | onreq(store.put(file), 93 | function onsuccess() {done();}, 94 | function onerror(e) {done(e);}); 95 | }); 96 | }; 97 | 98 | fsdb.mkdir = function(name, done) { 99 | return fsdb.put(name, "d", null, done); 100 | }; 101 | 102 | fsdb.touch = function(name, done) { 103 | return fsdb.put(name, "f", "", done); 104 | }; 105 | 106 | fsdb.get = function(name, done) { 107 | done = done || function() {}; 108 | with_db(this, function(err) { 109 | if (err) 110 | return done(err); 111 | var store = get_store(this, "readonly"); 112 | onreq(store.get(name), 113 | function onsuccess(e) { 114 | var res = e.target.result; 115 | console.log("get res", res); 116 | done(res); 117 | }, 118 | function onerror(e) { 119 | console.log("get err", e); 120 | done(null, e); 121 | }); 122 | }); 123 | }; 124 | 125 | fsdb.rm = function(name, done) { 126 | done = done || function() {}; 127 | with_db(this, function(err) { 128 | if (err) 129 | return done(err); 130 | var store = get_store(this, "readwrite"); 131 | onreq(store.delete(name), 132 | function onsuccess() {done();}, 133 | function onerror(e) {done(e);}); 134 | }); 135 | }; 136 | 137 | shen_web.store = fsdb; 138 | shen_web.store.deploy(done); 139 | }; 140 | -------------------------------------------------------------------------------- /web/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/upload.png -------------------------------------------------------------------------------- /web/util.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | shen_web.clean = function(obj) { 3 | if (obj) 4 | while (obj.firstChild) 5 | obj.removeChild(obj.firstChild); 6 | }; 7 | 8 | shen_web.by_class = function(classname, obj) { 9 | if (obj.getElementsByClassName) 10 | return obj.getElementsByClassName(classname); 11 | else 12 | return obj.querySelectorAll('.' + classname); 13 | }; 14 | 15 | shen_web.by_tag = function(tag, obj) { 16 | tag = tag.toUpperCase(); 17 | for (var i = 0; i < obj.childNodes.length; i++) { 18 | var x = obj.childNodes[i]; 19 | if (x.tagName === tag) 20 | return x; 21 | } 22 | }; 23 | 24 | shen_web.img_btn = function(title, icon) { 25 | var btn = document.createElement("button"); 26 | btn.className = "icon_btn btn_bg btn_fg"; 27 | btn.title = title; 28 | var img = document.createElement("img"); 29 | img.src = icon; 30 | btn.appendChild(img); 31 | return btn; 32 | }; 33 | 34 | shen_web.tool_sep = function() { 35 | var sep = document.createElement("div"); 36 | sep.style["display"] = "inline-block"; 37 | sep.style["width"] = "20px"; 38 | return sep; 39 | }; 40 | 41 | shen_web.toolbar = function(tb, items) { 42 | var i, n = items.length; 43 | for (i = 0; i < n; ++i) { 44 | var item = items[i]; 45 | var b = this.img_btn(item.title, item.icon); 46 | b.classList.add("toolbar_btn"); 47 | if (item.classes && item.classes.length) 48 | b.classList.add.apply(b.classList, item.classes); 49 | b.onclick = item.onclick; 50 | tb.appendChild(b); 51 | } 52 | return tb; 53 | }; 54 | 55 | shen_web.init_maximize = function(div) { 56 | var btns = div.getElementsByClassName("maximize_btn"); 57 | for (var i = 0; i < btns.length; ++i) { 58 | var b = btns[i]; 59 | b.classList.add("alt_hdr_bg"); 60 | b.title = "Maximize pane"; 61 | b.onclick = function() { 62 | div.classList.toggle("maximized"); 63 | }; 64 | } 65 | }; 66 | 67 | shen_web.dialog = function(opts) { 68 | var over = document.createElement("div"); 69 | over.className = "overlay"; 70 | over.onclick = function() { 71 | over.parentNode.removeChild(over); 72 | }; 73 | 74 | var dlg = document.createElement("div"); 75 | dlg.className = "dlg"; 76 | dlg.onclick = function(ev) { 77 | if (ev.stopPropagation) 78 | ev.stopPropagation(); 79 | else 80 | ev.cancelBubble = true; 81 | return true; 82 | }; 83 | 84 | var t = el("dlg_title"), 85 | content = el("dlg_content"), 86 | ctl = document.createElement("div"), 87 | cancel = document.createElement("button"); 88 | t.classList.add("hdr_title"); 89 | t.appendChild(document.createTextNode(opts.title || "")); 90 | 91 | ctl.className = "dlg_ctl"; 92 | 93 | cancel.appendChild(document.createTextNode("Cancel")); 94 | cancel.className = "btn_font dim_btn_fg dim_btn_bg dlg_cancel"; 95 | cancel.onclick = function() { 96 | if (opts.onnegative) 97 | opts.onnegative(); 98 | over.parentNode.removeChild(over); 99 | return false; 100 | }; 101 | 102 | ctl.appendChild(cancel); 103 | if (opts.oncreate) 104 | opts.oncreate(content, ctl, close); 105 | dlg.appendChild(ctl); 106 | over.appendChild(dlg); 107 | document.body.appendChild(over); 108 | return over; 109 | 110 | function el(name) { 111 | var cont = document.createElement("div"); 112 | cont.className = name + "_outer"; 113 | var obj = document.createElement("div"); 114 | obj.className = name; 115 | cont.appendChild(obj); 116 | dlg.appendChild(cont); 117 | return obj; 118 | } 119 | 120 | function close() { 121 | over.parentNode.removeChild(over); 122 | } 123 | }; 124 | 125 | shen_web.prompt = function(opts) { 126 | opts.oncreate = function(dlg, ctl, close) { 127 | var lb = document.createElement("label"), 128 | inp = document.createElement("input"); 129 | inp.id = "dlg_entry"; 130 | inp.type = "text"; 131 | inp.className = "entry_bg entry_fg"; 132 | inp.onkeyup = function(e) { 133 | var key = e.keyCode || e.which; 134 | switch (key) { 135 | case 0xd: ok.onclick(); 136 | } 137 | }; 138 | lb.appendChild(document.createTextNode(opts.label || "")); 139 | lb.htmlFor = "dlg_entry"; 140 | dlg.appendChild(lb); 141 | dlg.appendChild(inp); 142 | var ok = shen_web.btn(opts.action_text || ""); 143 | ok.onclick = function() { 144 | if (inp.value && inp.value !== "" && opts.onpositive) 145 | opts.onpositive(inp.value); 146 | close(); 147 | }; 148 | ctl.appendChild(ok); 149 | setTimeout(function() {inp.focus()}, 50); 150 | }; 151 | this.dialog(opts); 152 | }; 153 | 154 | shen_web.confirm = function(opts) { 155 | opts.oncreate = function(dlg, ctl, close) { 156 | dlg.appendChild(document.createTextNode(opts.text || "")); 157 | var btn = shen_web.btn(opts.action_text || ""); 158 | if (opts.positive_class_name) 159 | btn.className += " " + opts.positive_class_name; 160 | btn.onclick = function() { 161 | if (opts.onpositive) 162 | opts.onpositive(); 163 | close(); 164 | }; 165 | ctl.appendChild(btn); 166 | }; 167 | this.dialog(opts); 168 | } 169 | 170 | shen_web.btn = function(title) { 171 | var b = document.createElement("button"); 172 | b.className = "btn_font btn_fg alt_hdr_bg btn_bg"; 173 | b.appendChild(document.createTextNode(title)); 174 | return b; 175 | } 176 | 177 | shen_web.dlg_okcancel = function(show, fn) { 178 | var div = document.createElement("div"), ok, cancel; 179 | div.className = "dlg_btns"; 180 | 181 | if (!show || show.match(/[oy]/)) { 182 | ok = document.createElement("button"); 183 | ok.className = "dlg_btn_ok"; 184 | ok.appendChild(document.createTextNode("OK")); 185 | ok.onclick = function() {fn(true);}; 186 | } else { 187 | ok = document.createElement("div"); 188 | ok.className = "dlg_btn_ok"; 189 | ok.appendChild(document.createTextNode(" ")); 190 | } 191 | div.appendChild(ok); 192 | 193 | if (!show || show.match(/[cn]/)) { 194 | cancel = document.createElement("a"); 195 | cancel.className = "dlg_btn_cancel"; 196 | cancel.onclick = function() {fn(false);}; 197 | 198 | var ct = document.createElement("span"); 199 | ct.className = "dlg_btn_cancel_text"; 200 | ct.appendChild(document.createTextNode("Cancel")); 201 | cancel.appendChild(ct); 202 | 203 | div.appendChild(cancel); 204 | } 205 | return div; 206 | }; 207 | 208 | shen_web.xhr = function(opts, fn, errfn) { 209 | if (typeof(opts) === "string") 210 | opts = {url: opts}; 211 | opts.method = opts.method || "GET"; 212 | opts.req_headers = opts.req_headers || {}; 213 | 214 | var keys = Object.keys(opts), i, key, hdrs = opts.req_headers, 215 | hkeys = Object.keys(hdrs), req = new XMLHttpRequest(); 216 | 217 | req.open(opts.method, opts.url, true); 218 | for (i = 0; i < keys.length; ++i) { 219 | key = keys[i]; 220 | switch (key) { 221 | case "method": case "url": case "req_headers": break; 222 | default: req[key] = opts[key]; 223 | } 224 | } 225 | for (i = 0; i < hkeys.length; ++i) { 226 | key = hkeys[i]; 227 | req.setRequestHeader(key, opts.req_headers[key]); 228 | } 229 | errfn = errfn || function() {}; 230 | req.onreadystatechange = function() { 231 | if (req.readyState === 4) 232 | switch (req.status) { 233 | case 200: fn(req.response); break; 234 | default: errfn(req.statusText); 235 | } 236 | }; 237 | try { 238 | req.send(); 239 | } catch(e) { 240 | errfn(e); 241 | } 242 | }; 243 | })(); 244 | -------------------------------------------------------------------------------- /web/wait.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gravicappa/shen-js/3532a6c912af385ea6cc06f6e2b38b66eb9173e5/web/wait.gif --------------------------------------------------------------------------------