├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── jimtclhacks.patch ├── js ├── postJsJimtcl.js ├── postJsTcl.js └── preJs.js ├── opt └── dom.c └── tclhacks.patch /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | libtcl.bc 3 | libtcl.js 4 | libtcl.js.mem 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tcl"] 2 | path = tcl 3 | url = https://github.com/tcltk/tcl.git 4 | [submodule "jimtcl"] 5 | path = jimtcl 6 | url = https://github.com/msteveb/jimtcl.git 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Whether to build the optional `dom` command into tcl 2 | EMTCLDOM?=1 3 | 4 | # Optimisation to use for generating bc 5 | BCFLAGS?=-Oz 6 | # post-js happens later to write cwrap code conditional on tcl distro 7 | EMFLAGS=\ 8 | --pre-js js/preJs.js\ 9 | --memory-init-file 0 -O3 --llvm-lto 3 --closure 0\ 10 | #-s FORCE_ALIGNED_MEMORY=1 -s CLOSURE_COMPILER=1 -s CLOSURE_ANNOTATIONS=1\ 11 | #-s NODE_STDOUT_FLUSH_WORKAROUND=0 -s RUNNING_JS_OPTS=1 12 | 13 | EMTCLEXPORTS=\ 14 | -s EXPORTED_FUNCTIONS="[\ 15 | '_Tcl_CreateInterp',\ 16 | '_Tcl_Eval',\ 17 | '_Tcl_GetStringResult',\ 18 | $$([ $(EMTCLDOM) = 1 ] && echo "'_CreateDomCmd'," || true)\ 19 | ]" 20 | 21 | EMJIMTCLEXPORTS=\ 22 | -s EXPORTED_FUNCTIONS="[\ 23 | '_Jim_CreateInterp',\ 24 | '_Jim_RegisterCoreCommands',\ 25 | '_Jim_InitStaticExtensions',\ 26 | '_Jim_Eval',\ 27 | '_Jim_GetString',\ 28 | '_Jim_GetResult',\ 29 | ]" 30 | 31 | .PHONY: default tcl jimtcl tclprep jimtclprep reset 32 | 33 | default: emtcl 34 | emtcl: emtcl.js 35 | emjimtcl: emjimtcl.js 36 | 37 | emtcl.js: emtcl.bc 38 | emcc --post-js js/postJsTcl.js $(EMFLAGS) $(EMTCLEXPORTS) \ 39 | $$([ $(EMTCLDOM) = 1 ] && echo '-Itcl/generic opt/dom.c' || true) $< -o $@ 40 | 41 | emjimtcl.js: emjimtcl.bc 42 | emcc --post-js js/postJsJimtcl.js $(EMFLAGS) $(EMJIMTCLEXPORTS) \ 43 | -Ijimtcl jimtcl/jimgetresult.c $< -o $@ 44 | 45 | emtcl.bc: 46 | cd tcl/unix && emmake make 47 | [ -e $@ ] || \ 48 | (T=$$(. ./tcl/unix/tclConfig.sh && echo $$TCL_LIB_FILE) && ln -s tcl/unix/$$T $@) 49 | 50 | emjimtcl.bc: 51 | cd jimtcl && emmake make 52 | [ -e $@ ] || ln -s jimtcl/libjim.a $@ 53 | 54 | tclprep: 55 | cd tcl && git apply ../tclhacks.patch 56 | cd tcl/unix && emconfigure ./configure --disable-threads --disable-load --disable-shared 57 | cd tcl/unix && sed -i 's/-O2/$(BCFLAGS)/g' Makefile 58 | 59 | jimtclprep: 60 | cd jimtcl && emconfigure ./configure --full --disable-docs --without-ext=regexp 61 | cd jimtcl && git apply ../jimtclhacks.patch 62 | cd jimtcl && sed -i 's/^\(CFLAGS = .*\)$$/\1 $(BCFLAGS)/g' Makefile 63 | 64 | reset: 65 | @read -p "This nukes anything not git-controlled in ./tcl/ and ./jimtcl/, are you sure? Type 'y' if so: " P && [ $$P = y ] 66 | cd tcl && git reset --hard && git clean -f -x -d 67 | cd jimtcl && git reset --hard && git clean -f -x -d 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | emtcl 2 | ===== 3 | 4 | The imaginatively named emtcl lets you compile tcl or jimtcl with emscripten. It's pretty easy so there's not much here. The small amount there is is available under the 3-clause BSD license (see "LICENSE"). 5 | 6 | Rather than trying to compile tclsh/jimsh and get stdin working, it instead compiles the tcl library and exposes some C api calls. See js/postJs.js for how this is done. 7 | 8 | Current supported interpreters (and their versions) are: 9 | - tcl - core\_8\_6\_1 10 | - jimtcl - 0.75 11 | 12 | Tcl comes with a toy DOM library as a demo. See EXTRAS. 13 | 14 | BUILD 15 | ----- 16 | 17 | Pretty easy (assuming you've got the emscripten sdk on your path): 18 | 19 | $ make tclprep # One off prep - hacks.patch application and configure 20 | $ make emtcl # Build emtcl.js 21 | 22 | or for jimtcl: 23 | 24 | $ make jimtclprep # One off prep - tweaks appropriate #defines and configure 25 | $ make emjimtcl # Build emjimtcl.js 26 | 27 | If you want to totally reset all build files in ./tcl/ and ./jimtcl/ and start again: 28 | 29 | $ make reset 30 | 31 | This removes all changes and untracked files in there, so be careful! 32 | 33 | EXTRAS 34 | ------ 35 | 36 | EmTcl (not EmJimTcl) comes with a toy DOM library compiled into it by default 37 | as a demonstration of how one could do interesting things. 38 | 39 | It currently is a command of the form `dom attr|css selector key val`. It sets 40 | the appropriate key in either the attribute or style dictionary of each element 41 | returned by the selector. It will return the number of elements modified. Note 42 | that style keys are in their camelcase form (backgroundColor vs background-color) 43 | as the styles are changed in the element style dictionary. 44 | 45 | You can see the code in opt/dom.c. If you really want to compile without it, do 46 | `make emtcl EMTCLDOM=0`. 47 | 48 | TODO 49 | ---- 50 | 51 | There are a number of broken things with each of these Tcl implementations. 52 | 53 | tcl: 54 | 55 | - `clock` command isn't present. Caused by not calling `Tcl_Init`, would 56 | require embedding the `library` directory. Entirely possible, just not put 57 | the effort in. 58 | - `after 2000 {set ::cond_var 0}; vwait ::cond_var` doesn't work. This seems to 59 | be an emscripten problem, not sure how easy it is to solve. 60 | 61 | jimtcl: 62 | 63 | - `package` command isn't present. Not sure of cause. 64 | - `Jim_InitStaticExtensions` isn't called. Not sure what the implications are. 65 | -------------------------------------------------------------------------------- /jimtclhacks.patch: -------------------------------------------------------------------------------- 1 | diff --git a/jimautoconf.h_ b/jimautoconf.h_ 2 | --- a/jimautoconf.h 3 | +++ b/jimautoconf.h 4 | @@ -1,12 +1,10 @@ 5 | #ifndef _JIMAUTOCONF_H 6 | #define _JIMAUTOCONF_H 7 | #define HAVE_ARPA_INET_H 1 8 | -#define HAVE_BACKTRACE 1 9 | /* #undef HAVE_CRT_EXTERNS_H */ 10 | #define HAVE_DIRENT_H 1 11 | #define HAVE_DLFCN_H 1 12 | #define HAVE_DLOPEN 1 13 | -#define HAVE_EXECVPE 1 14 | #define HAVE_FORK 1 15 | #define HAVE_FSEEKO 1 16 | #define HAVE_FTELLO 1 17 | @@ -41,7 +39,6 @@ 18 | #define HAVE_SYSINFO 1 19 | #define HAVE_SYSLOG 1 20 | #define HAVE_SYSTEM 1 21 | -#define HAVE_SYS_SIGLIST 1 22 | /* #undef HAVE_SYS_SIGNAME */ 23 | #define HAVE_SYS_SOCKET_H 1 24 | #define HAVE_SYS_STAT_H 1 25 | diff --git a/jimgetresult.c b/jimgetresult.c 26 | new file mode 100644 27 | --- /dev/null 28 | +++ b/jimgetresult.c 29 | @@ -0,0 +1,3 @@ 30 | +#include 31 | +#undef Jim_GetResult 32 | +Jim_Obj *Jim_GetResult(Jim_Interp *i) { return ((i)->result); } 33 | -------------------------------------------------------------------------------- /js/postJsJimtcl.js: -------------------------------------------------------------------------------- 1 | root['Module'] = Module; 2 | } 3 | }; 4 | 5 | root.emjimtcl = root.TCL; 6 | delete root.TCL; 7 | 8 | root.emjimtcl(); 9 | // Undo pollution of window 10 | delete window.Module; 11 | 12 | // Init emscripten stuff 13 | root.Module.run(); 14 | 15 | var createInterp = root.Module.cwrap('Jim_CreateInterp', 'number', []); 16 | var registerCoreCommands = root.Module.cwrap('Jim_RegisterCoreCommands', 'number', ['number']); 17 | var initStaticExtensions = root.Module.cwrap('Jim_InitStaticExtensions', 'number', ['number']); 18 | root.CreateInterp = function () { 19 | var interp = createInterp(); 20 | registerCoreCommands(interp); 21 | initStaticExtensions(interp); 22 | return interp; 23 | } 24 | root.Eval = root.Module.cwrap('Jim_Eval', 'number', [ 25 | 'number', // interp pointer 26 | 'string' // string to eval 27 | ]); 28 | var getResult = root.Module.cwrap('Jim_GetResult', 'number', ['number']); 29 | var getString = root.Module.cwrap('Jim_GetString', 'string', ['number', 'number']); 30 | root.GetStringResult = function (interp) { 31 | return getString(getResult(interp), 0); 32 | } 33 | 34 | self.emjimtcl = root; 35 | 36 | })(this); 37 | -------------------------------------------------------------------------------- /js/postJsTcl.js: -------------------------------------------------------------------------------- 1 | root['Module'] = Module; 2 | } 3 | }; 4 | 5 | root.emtcl = root.TCL; 6 | delete root.TCL; 7 | 8 | root.emtcl(); 9 | // Undo pollution of window 10 | delete window.Module; 11 | 12 | // Init emscripten stuff 13 | root.Module.run(); 14 | 15 | root.CreateInterp = root.Module.cwrap('Tcl_CreateInterp', 'number', []); 16 | root.Eval = root.Module.cwrap('Tcl_Eval', 'number', [ 17 | 'number', // interp pointer 18 | 'string' // string to eval 19 | ]); 20 | root.GetStringResult = root.Module.cwrap('Tcl_GetStringResult', 'string', [ 21 | 'number' // interp pointer 22 | ]); 23 | 24 | self.emtcl = root; 25 | 26 | })(this); 27 | -------------------------------------------------------------------------------- /js/preJs.js: -------------------------------------------------------------------------------- 1 | (function (self) { 2 | 3 | var root = { 4 | TCL: function () { 5 | var Module = { 6 | noInitialRun: true, 7 | noExitRuntime: true, 8 | preRun: [], 9 | postRun: [] 10 | }; 11 | -------------------------------------------------------------------------------- /opt/dom.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int DomCmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { 6 | 7 | char *argsHelp = "attr|css selector key val"; 8 | if (objc != 5) { 9 | Tcl_WrongNumArgs(interp, 1, objv, argsHelp); 10 | return TCL_ERROR; 11 | } 12 | 13 | const char *action = Tcl_GetString(objv[1]); 14 | const char *selector = Tcl_GetString(objv[2]); 15 | const char *key = Tcl_GetString(objv[3]); 16 | const char *val = Tcl_GetString(objv[4]); 17 | 18 | Tcl_Obj *res; 19 | 20 | if (strcmp(action, "attr") != 0 && strcmp(action, "css") != 0) { 21 | res = Tcl_NewStringObj("Action must be attr or css", -1); 22 | Tcl_SetObjResult(interp, res); 23 | return TCL_ERROR; 24 | } 25 | 26 | // TODO: always catch errors 27 | int numChanged = EM_ASM_INT({ 28 | var action = Pointer_stringify($0); 29 | selector = Pointer_stringify($1); 30 | key = Pointer_stringify($2); 31 | val = Pointer_stringify($3); 32 | var elts = document.querySelectorAll(selector); 33 | for (var i = 0; i < elts.length; i++) { 34 | if (action === "attr") { 35 | elts[i][key] = val; 36 | } else { 37 | elts[i].style[key] = val; 38 | } 39 | } 40 | return elts.length; 41 | }, action, selector, key, val); 42 | 43 | res = Tcl_NewIntObj(numChanged); 44 | Tcl_SetObjResult(interp, res); 45 | return TCL_OK; 46 | } 47 | 48 | void CreateDomCmd(Tcl_Interp *interp) { 49 | Tcl_CreateObjCommand( 50 | interp, "dom", DomCmd, (ClientData) NULL, (Tcl_CmdDeleteProc *) NULL 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /tclhacks.patch: -------------------------------------------------------------------------------- 1 | diff --git a/generic/tcl.h b/generic/tcl.h 2 | --- a/generic/tcl.h 3 | +++ b/generic/tcl.h 4 | @@ -1,3 +1,4 @@ 5 | +#undef TCL_WIDE_INT_IS_LONG 6 | /* 7 | * tcl.h -- 8 | * 9 | diff --git a/unix/tclUnixCompat.c b/unix/tclUnixCompat.c 10 | --- a/unix/tclUnixCompat.c 11 | +++ b/unix/tclUnixCompat.c 12 | @@ -1,3 +1,4 @@ 13 | +#undef HAVE_CPUID 14 | /* 15 | * tclUnixCompat.c 16 | * 17 | --------------------------------------------------------------------------------