├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── build ├── liblua.js ├── raw.js ├── weblua-0.0.1.js ├── weblua-0.1.0.js ├── weblua-0.1.1.js ├── weblua-0.1.2.js ├── weblua-0.1.3.js ├── weblua-0.1.4.js ├── weblua-0.1.5.js └── weblua.js ├── example.html ├── lua_makefile_override └── src └── API.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | lua-5.2.1* 3 | closure* 4 | *.o 5 | *.a 6 | *.a.ll 7 | *.so 8 | *.so.ll 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2014 Philip Horger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LUA_VERSION=5.2.1 2 | WEBLUA_VERSION=0.1.5 3 | 4 | DOWNLOAD_PROGRAM=wget 5 | LUA_SRC_URL=http://www.lua.org/ftp/lua-$(LUA_VERSION).tar.gz 6 | LUA_SRC_LOCATION=src/lua-$(LUA_VERSION).tar.gz 7 | LUA_ROOT=src/lua-$(LUA_VERSION) 8 | 9 | CLOSURE_SRC_URL=http://closure-compiler.googlecode.com/files/compiler-latest.zip 10 | CLOSURE_SRC_LOCATION=src/closure.zip 11 | CLOSURE_UNPACK_LOCATION=src/closure 12 | CLOSURE_COMMAND=java -jar $(CLOSURE_UNPACK_LOCATION)/compiler.jar 13 | 14 | COMPILED_LIB=liblua.so.ll 15 | COMPILED_LIB_LOCATION=$(LUA_ROOT)/src/$(COMPILED_LIB) 16 | 17 | WEBLUA_NAME=weblua-$(WEBLUA_VERSION).js 18 | WEBLUA_LOCATION=build/$(WEBLUA_NAME) 19 | 20 | REQUIRED_FUNCTIONS=$(shell grep -oP '_lua[a-zA-Z_0-9]+' src/API.js | uniq) 21 | REQUIRED_FUNCTION_STRING="[$(foreach func,$(REQUIRED_FUNCTIONS),\"$(func)\",)]" 22 | 23 | # ------------------------------------------------------------------------------ 24 | # Emscripten options 25 | 26 | EM_DEBUG=0 27 | EM_EXCEPTION_DEBUG=$(EM_DEBUG) 28 | EM_LABEL_DEBUG=$(EM_DEBUG) 29 | EM_ASSERTIONS=$(EM_DEBUG) 30 | 31 | EM_STACK_MB=10 32 | EM_STACK_SIZE=$(shell echo $(EM_STACK_MB)\*1024\*1024 | bc) 33 | EM_ALLOW_MEMORY_GROWTH=1 34 | 35 | EM_ASM_JS=0 36 | 37 | # ------------------------------------------------------------------------------ 38 | 39 | all: build/weblua.js 40 | 41 | clean: 42 | rm -r $(LUA_ROOT) build 43 | 44 | diagnose: 45 | echo REQUIRED_FUNCTION_STRING=$(REQUIRED_FUNCTION_STRING) 46 | echo EM_STACK_SIZE=$(EM_STACK_SIZE) 47 | 48 | build/weblua.js : $(WEBLUA_LOCATION) 49 | # Remove old symlink if it exists 50 | rm -f build/weblua.js 51 | ln -s $(WEBLUA_NAME) build/weblua.js 52 | 53 | $(WEBLUA_LOCATION) : build/liblua.js $(CLOSURE_UNPACK_LOCATION) 54 | $(CLOSURE_COMMAND) \ 55 | --js build/liblua.js \ 56 | --js_output_file $(WEBLUA_LOCATION) \ 57 | --language_in ECMASCRIPT5 \ 58 | --compilation_level ADVANCED_OPTIMIZATIONS 59 | 60 | build/raw.js : $(COMPILED_LIB_LOCATION) src/API.js Makefile 61 | mkdir -p build 62 | emcc -o build/raw.js $(COMPILED_LIB_LOCATION) -s INVOKE_RUN=0 \ 63 | -s CLOSURE_ANNOTATIONS=1 \ 64 | -s OPTIMIZE=1 \ 65 | -s ASSERTIONS=$(EM_ASSERTIONS) \ 66 | -s CORRECT_SIGNS=1 \ 67 | -s CORRECT_OVERFLOWS=1 \ 68 | -s WARN_ON_UNDEFINED_SYMBOLS=1 \ 69 | -s EXPORTED_FUNCTIONS=$(REQUIRED_FUNCTION_STRING) \ 70 | -s ALLOW_MEMORY_GROWTH=$(EM_ALLOW_MEMORY_GROWTH) \ 71 | -s EXCEPTION_DEBUG=$(EM_EXCEPTION_DEBUG) \ 72 | -s LABEL_DEBUG=$(EM_LABEL_DEBUG) \ 73 | -s TOTAL_STACK=$(EM_STACK_SIZE) \ 74 | -s ASM_JS=$(EM_ASM_JS) 75 | 76 | build/liblua.js : build/raw.js src/API.js 77 | cat build/raw.js src/API.js > build/liblua.js 78 | 79 | $(COMPILED_LIB_LOCATION) : $(LUA_ROOT) 80 | emmake make linux -C $(LUA_ROOT)/src 81 | 82 | $(LUA_ROOT) : $(LUA_SRC_LOCATION) 83 | tar -C src -xf $(LUA_SRC_LOCATION) 84 | cp lua_makefile_override $(LUA_ROOT)/src/Makefile 85 | 86 | $(LUA_SRC_LOCATION): 87 | $(DOWNLOAD_PROGRAM) $(LUA_SRC_URL) -O $(LUA_SRC_LOCATION) 88 | 89 | $(CLOSURE_UNPACK_LOCATION): $(CLOSURE_SRC_LOCATION) 90 | unzip $(CLOSURE_SRC_LOCATION) -d $(CLOSURE_UNPACK_LOCATION) 91 | 92 | $(CLOSURE_SRC_LOCATION): 93 | $(DOWNLOAD_PROGRAM) $(CLOSURE_SRC_URL) -O $(CLOSURE_SRC_LOCATION) 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # weblua 2 | 3 | A project for the consistent, reproducible, and simple compilation of the Lua interpreter for the web. It uses Emscripten and produces an interpreter of version 5.2.1. 4 | 5 | ## Generating JS files 6 | 7 | You need a working Emscripten setup, with the Emscripten binaries in the PATH of the shell you're using to compile the JS files. 8 | 9 | The whole project is created from scratch via a single command, `make`, which will download, decompress, and compile the latest version of the Lua interpreter, transcode that to liblua.js, and add convenience hooks/compression/optimization for the final product, weblua.js. 10 | 11 | ## Using weblua.js 12 | 13 | You don't have to install or set up Emscripten to use weblua.js, unless you want to actually make changes to weblua.js. The binaries are available precompiled for you in this repository. You can get straight to including it in your projects. 14 | 15 | To test out the demo and make sure things work, run `python -m SimpleHTTPServer 8770` in the root of this git repository, and go to `localhost:8770` in your browser. 16 | 17 | ### API reference 18 | 19 | #### The Lua object 20 | 21 | When you include the weblua.js script in your page, you will have a window.Lua object available to you. Currently you can only have one global interpreter for your whole page. In the future, this will be changed such that you can have as many as you want. 22 | 23 | #### Lua.initialize() 24 | 25 | Must be called exactly once, to initialize the internal state of the Lua library. 26 | 27 | Other functions will not work until this has been called. 28 | 29 | #### Lua.eval("5 + 2") 30 | 31 | Evaluates a Lua _expression._ Returns the result. 32 | 33 | Lua functions are automatically wrapped so that you can call them directly: 34 | 35 | Lua.initialize(); 36 | Lua.exec("function add(a,b)\n return a + b\nend"); 37 | my add_func = Lua.eval("add") 38 | add_func(13, 7) // Returns 20 39 | 40 | This function expects expressions, not statements. For defining functions and other such things, use Lua.exec. 41 | 42 | #### Lua.exec("x = 5") 43 | 44 | Evaluates a block of Lua code. If it encounters a return statement, that value will be returned. This is the function you want to use to manipulate an interpreter's global state, define functions, etc. 45 | 46 | **BEHIND THE SCENES MAGIC:** Both exec and eval use a part of the Lua API for turning a string into the contents of a function, and then running that function. Internally, there is no difference between Lua.eval("x") and Lua.exec("return x"). Literally, all eval does is prepend "return " to the command before calling this.exec(). 47 | 48 | #### Lua.inject(object, name, final_location) 49 | 50 | Turns a JS object into a Lua object. If you want to create a global object, call with two arguments, where `name` is the name of the global. If you want to put your value in an arbitrary place, leave `name` undefined, and set `final_location` to where you want your value to end up. 51 | 52 | Call with just one argument if you want a peek into the intermediate storage technique. 53 | 54 | **BEHIND THE SCENES MAGIC:** JavaScript doesn't understand the concept of integer keys for objects, and coerces them to strings behind your back. The consequence of this is that you can't create Lua tables with integer keys very conveniently, *except* by providing them as arrays - so if you want to intermix, you can, but it's awfully inconvenient. I make no attempt to fix this failing of JS, because I consider it even more surprising to add a layer of "this key looks like an int so I'll coerce it" to pushStack() object parsing. 55 | 56 | #### Lua.anon_lua_object(object) 57 | 58 | Return an anonymous Lua object based on the JS object given. Will use Lua.inject for complex structures, or a literal where possible. 59 | 60 | Always returns a string that will be eval-able by the interpreter. 61 | 62 | ## Current progress 63 | 64 | All basic functionality seems done. Work on filesystems code and such will proceed as I try to integrate weblua into [love-webplayer](https://github.com/ghoulsblade/love-webplayer). 65 | 66 | While the output is (of course) cross-platform, building weblua.js is currently only supported on Linux, but may work by accident/with a little tuning on other platforms. Likewise, due to API differences between Lua 5.1 and 5.2, currently API.js is only engineered to work with 5.2, and will need tweaking to work with 5.1, for anyone looking for that. 67 | 68 | ## Aren't other people already working on this, a la JSREPL? 69 | 70 | The ultimate goal is to have a high-level interface to the Lua interpreter that requires no special skill or knowledge to use (aside from JS and Lua), and supports exporting/wrapping functions from one side to the other. It will in no way require modifications to the source of Lua. 71 | 72 | JSREPL has been a great reference implementation, but it's poorly documented and contains a lot of stuff we don't care about. JSREPL Lua is a mediocre-quality port with out-of-date build ingredients that are hard to reproduce, nested among a large codebase of unrelated interpreters - and that's all it'll ever need to be. I don't mean to dog on those people, because they did a fantastic job figuring out the hard stuff for the rest of us, and this project would probably be impossible without their open source contributions. 73 | 74 | Weblua aims a bit higher, and with a narrower scope. It ought to be trivial to include a Lua interpreter in your page, that can call JS functions and vice versa without difficulty or complication. Introspection should be simple, sane and clear. The code should also be optimized, so that it can compete with projects like lua\_parser.js in terms of speed, while still retaining the massive advantage of being a complete and verbatim implementation of the official Lua interpreter. 75 | -------------------------------------------------------------------------------- /build/weblua.js: -------------------------------------------------------------------------------- 1 | weblua-0.1.5.js -------------------------------------------------------------------------------- /example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |This is a demo of weblua.js. You can enter Lua code with the single-line or multi-line textbox.
88 |For extra fun, try the JS injector. It evaluates your source as Javascript, converts it into a form Lua can understand, and evals it as Lua. This is very much a work in progress, so don't be surprised that things barely work.
89 | 90 | 105 | 106 | 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /lua_makefile_override: -------------------------------------------------------------------------------- 1 | # Makefile for building Lua 2 | # See ../doc/readme.html for installation and customization instructions. 3 | 4 | # == CHANGE THE SETTINGS BELOW TO SUIT YOUR ENVIRONMENT ======================= 5 | 6 | # Your platform. See PLATS for possible values. 7 | PLAT= generic 8 | 9 | CC= emcc 10 | CFLAGS= #-O2 -Wall -DLUA_COMPAT_ALL $(SYSCFLAGS) $(MYCFLAGS) 11 | LDFLAGS= $(SYSLDFLAGS) $(MYLDFLAGS) 12 | LIBS= -lm $(SYSLIBS) $(MYLIBS) 13 | 14 | AR= llvm-link 15 | RANLIB= llvm-dis 16 | RM= rm -f 17 | 18 | SYSCFLAGS= 19 | SYSLDFLAGS= 20 | SYSLIBS= 21 | 22 | MYCFLAGS= 23 | MYLDFLAGS= 24 | MYLIBS= 25 | MYOBJS= 26 | 27 | # == END OF USER SETTINGS -- NO NEED TO CHANGE ANYTHING BELOW THIS LINE ======= 28 | 29 | PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris 30 | 31 | LUA_A= liblua.so 32 | CORE_O= lapi.o lcode.o lctype.o ldebug.o ldo.o ldump.o lfunc.o lgc.o llex.o \ 33 | lmem.o lobject.o lopcodes.o lparser.o lstate.o lstring.o ltable.o \ 34 | ltm.o lundump.o lvm.o lzio.o 35 | LIB_O= lauxlib.o lbaselib.o lbitlib.o lcorolib.o ldblib.o liolib.o \ 36 | lmathlib.o loslib.o lstrlib.o ltablib.o loadlib.o linit.o 37 | BASE_O= $(CORE_O) $(LIB_O) $(MYOBJS) 38 | 39 | LUA_T= lua 40 | LUA_O= lua.o 41 | 42 | LUAC_T= luac 43 | LUAC_O= luac.o 44 | 45 | ALL_O= $(BASE_O) $(LUA_O) $(LUAC_O) 46 | ALL_T= $(LUA_A) $(LUA_T) $(LUAC_T) 47 | ALL_A= $(LUA_A) 48 | 49 | # Targets start here. 50 | default: $(PLAT) 51 | 52 | all: $(ALL_T) 53 | 54 | o: $(ALL_O) 55 | 56 | a: $(ALL_A) 57 | 58 | $(LUA_A): $(BASE_O) 59 | $(AR) -o=$@ $(BASE_O) 60 | $(RANLIB) $@ 61 | 62 | $(LUA_T): $(LUA_O) $(LUA_A) 63 | $(CC) -o $@ $(LDFLAGS) $(LUA_O) $(LUA_A) $(LIBS) 64 | 65 | $(LUAC_T): $(LUAC_O) $(LUA_A) 66 | $(CC) -o $@ $(LDFLAGS) $(LUAC_O) $(LUA_A) $(LIBS) 67 | 68 | clean: 69 | $(RM) $(ALL_T) $(ALL_O) 70 | 71 | depend: 72 | @$(CC) $(CFLAGS) -MM l*.c 73 | 74 | echo: 75 | @echo "PLAT= $(PLAT)" 76 | @echo "CC= $(CC)" 77 | @echo "CFLAGS= $(CFLAGS)" 78 | @echo "LDFLAGS= $(SYSLDFLAGS)" 79 | @echo "LIBS= $(LIBS)" 80 | @echo "AR= $(AR)" 81 | @echo "RANLIB= $(RANLIB)" 82 | @echo "RM= $(RM)" 83 | 84 | # Convenience targets for popular platforms 85 | ALL= all 86 | 87 | none: 88 | @echo "Please do 'make PLATFORM' where PLATFORM is one of these:" 89 | @echo " $(PLATS)" 90 | 91 | aix: 92 | $(MAKE) $(ALL) CC="xlc" CFLAGS="-O2 -DLUA_USE_POSIX -DLUA_USE_DLOPEN" SYSLIBS="-ldl" SYSLDFLAGS="-brtl -bexpall" 93 | 94 | ansi: 95 | $(MAKE) $(ALL) SYSCFLAGS="-DLUA_ANSI" 96 | 97 | bsd: 98 | $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" SYSLIBS="-Wl,-E" 99 | 100 | freebsd: 101 | $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_LINUX" SYSLIBS="-Wl,-E -lreadline" 102 | 103 | generic: $(ALL) 104 | 105 | linux: 106 | $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_LINUX" SYSLIBS="-Wl,-E -ldl -lreadline -lncurses" 107 | 108 | macosx: 109 | $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_MACOSX" SYSLIBS="-lreadline" 110 | 111 | mingw: 112 | $(MAKE) "LUA_A=lua52.dll" "LUA_T=lua.exe" \ 113 | "AR=$(CC) -shared -o" "RANLIB=strip --strip-unneeded" \ 114 | "SYSCFLAGS=-DLUA_BUILD_AS_DLL" "SYSLIBS=" "SYSLDFLAGS=-s" lua.exe 115 | $(MAKE) "LUAC_T=luac.exe" luac.exe 116 | 117 | posix: 118 | $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_POSIX" 119 | 120 | solaris: 121 | $(MAKE) $(ALL) SYSCFLAGS="-DLUA_USE_POSIX -DLUA_USE_DLOPEN" SYSLIBS="-ldl" 122 | 123 | # list targets that do not create files (but not all makes understand .PHONY) 124 | .PHONY: all $(PLATS) default o a clean depend echo none 125 | 126 | # DO NOT DELETE 127 | 128 | lapi.o: lapi.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h ltm.h \ 129 | lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h lstring.h ltable.h lundump.h \ 130 | lvm.h 131 | lauxlib.o: lauxlib.c lua.h luaconf.h lauxlib.h 132 | lbaselib.o: lbaselib.c lua.h luaconf.h lauxlib.h lualib.h 133 | lbitlib.o: lbitlib.c lua.h luaconf.h lauxlib.h lualib.h 134 | lcode.o: lcode.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \ 135 | lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h lgc.h \ 136 | lstring.h ltable.h lvm.h 137 | lcorolib.o: lcorolib.c lua.h luaconf.h lauxlib.h lualib.h 138 | lctype.o: lctype.c lctype.h lua.h luaconf.h llimits.h 139 | ldblib.o: ldblib.c lua.h luaconf.h lauxlib.h lualib.h 140 | ldebug.o: ldebug.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h \ 141 | ltm.h lzio.h lmem.h lcode.h llex.h lopcodes.h lparser.h ldebug.h ldo.h \ 142 | lfunc.h lstring.h lgc.h ltable.h lvm.h 143 | ldo.o: ldo.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h ltm.h \ 144 | lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h lopcodes.h lparser.h \ 145 | lstring.h ltable.h lundump.h lvm.h 146 | ldump.o: ldump.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h \ 147 | lzio.h lmem.h lundump.h 148 | lfunc.o: lfunc.c lua.h luaconf.h lfunc.h lobject.h llimits.h lgc.h \ 149 | lstate.h ltm.h lzio.h lmem.h 150 | lgc.o: lgc.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ 151 | lzio.h lmem.h ldo.h lfunc.h lgc.h lstring.h ltable.h 152 | linit.o: linit.c lua.h luaconf.h lualib.h lauxlib.h 153 | liolib.o: liolib.c lua.h luaconf.h lauxlib.h lualib.h 154 | llex.o: llex.c lua.h luaconf.h lctype.h llimits.h ldo.h lobject.h \ 155 | lstate.h ltm.h lzio.h lmem.h llex.h lparser.h lstring.h lgc.h ltable.h 156 | lmathlib.o: lmathlib.c lua.h luaconf.h lauxlib.h lualib.h 157 | lmem.o: lmem.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ 158 | ltm.h lzio.h lmem.h ldo.h lgc.h 159 | loadlib.o: loadlib.c lua.h luaconf.h lauxlib.h lualib.h 160 | lobject.o: lobject.c lua.h luaconf.h lctype.h llimits.h ldebug.h lstate.h \ 161 | lobject.h ltm.h lzio.h lmem.h ldo.h lstring.h lgc.h lvm.h 162 | lopcodes.o: lopcodes.c lopcodes.h llimits.h lua.h luaconf.h 163 | loslib.o: loslib.c lua.h luaconf.h lauxlib.h lualib.h 164 | lparser.o: lparser.c lua.h luaconf.h lcode.h llex.h lobject.h llimits.h \ 165 | lzio.h lmem.h lopcodes.h lparser.h ldebug.h lstate.h ltm.h ldo.h lfunc.h \ 166 | lstring.h lgc.h ltable.h 167 | lstate.o: lstate.c lua.h luaconf.h lapi.h llimits.h lstate.h lobject.h \ 168 | ltm.h lzio.h lmem.h ldebug.h ldo.h lfunc.h lgc.h llex.h lstring.h \ 169 | ltable.h 170 | lstring.o: lstring.c lua.h luaconf.h lmem.h llimits.h lobject.h lstate.h \ 171 | ltm.h lzio.h lstring.h lgc.h 172 | lstrlib.o: lstrlib.c lua.h luaconf.h lauxlib.h lualib.h 173 | ltable.o: ltable.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h \ 174 | ltm.h lzio.h lmem.h ldo.h lgc.h lstring.h ltable.h lvm.h 175 | ltablib.o: ltablib.c lua.h luaconf.h lauxlib.h lualib.h 176 | ltm.o: ltm.c lua.h luaconf.h lobject.h llimits.h lstate.h ltm.h lzio.h \ 177 | lmem.h lstring.h lgc.h ltable.h 178 | lua.o: lua.c lua.h luaconf.h lauxlib.h lualib.h 179 | luac.o: luac.c lua.h luaconf.h lauxlib.h lobject.h llimits.h lstate.h \ 180 | ltm.h lzio.h lmem.h lundump.h ldebug.h lopcodes.h 181 | lundump.o: lundump.c lua.h luaconf.h ldebug.h lstate.h lobject.h \ 182 | llimits.h ltm.h lzio.h lmem.h ldo.h lfunc.h lstring.h lgc.h lundump.h 183 | lvm.o: lvm.c lua.h luaconf.h ldebug.h lstate.h lobject.h llimits.h ltm.h \ 184 | lzio.h lmem.h ldo.h lfunc.h lgc.h lopcodes.h lstring.h ltable.h lvm.h 185 | lzio.o: lzio.c lua.h luaconf.h llimits.h lmem.h lstate.h lobject.h ltm.h \ 186 | lzio.h 187 | -------------------------------------------------------------------------------- /src/API.js: -------------------------------------------------------------------------------- 1 | // This file is appended to the end of build/liblua.js 2 | 3 | // WEBLUA API ================================================================= 4 | // 5 | // Written by Philip Horger 6 | // Based on https://github.com/replit/jsrepl/blob/master/extern/lua/entry_point.js 7 | // 8 | // ============================================================================ 9 | 10 | this['Lua'] = { 11 | isInitialized: false, 12 | state: null, 13 | tmp_id: 0, 14 | default_source_name: 'stdin', 15 | preallocated_strings: { 16 | '__handle': null, 17 | '__index': null, 18 | }, 19 | initialize: function (source_name, stdout, stderr) { 20 | if (this.isInitialized) throw new Error('Lua already initialized'); 21 | this.default_source_name = source_name || this.default_source_name; 22 | this.stdout = stdout || this.stdout; 23 | this.stderr = stderr || this.stderr; 24 | run(); 25 | this.state = _luaL_newstate(); 26 | _luaL_openlibs(this.state); 27 | for (var key in this.preallocated_strings) { 28 | this.preallocated_strings[key] = this.allocate_string(key); 29 | } 30 | this.isInitialized = true; 31 | }, 32 | require_initialization: function(){ 33 | if (!this.isInitialized) throw new Error('Lua not yet initialized'); 34 | }, 35 | parse: function (command, source_name) { 36 | // Put new function, from buffer, at the top of the stack 37 | this.require_initialization(); 38 | var commandPtr = this.allocate_string(command); 39 | var namePtr = this.allocate_string(source_name); 40 | var parseFailed = _luaL_loadbufferx( 41 | this.state, commandPtr, command.length, namePtr 42 | ); 43 | if (parseFailed) { 44 | this.report_error("Parsing failure"); 45 | } 46 | _free(commandPtr); 47 | _free(namePtr); 48 | return !parseFailed; 49 | }, 50 | eval: function (command, source_name, source) { 51 | source_name = source_name || this.default_source_name; 52 | source = source || command; 53 | return this.exec("return "+command, source_name, source); 54 | }, 55 | exec: function (command, source_name, source) { 56 | this.require_initialization(); 57 | source_name = source_name || this.default_source_name; 58 | source = source || command; 59 | 60 | if (this.parse(command, source_name)) { 61 | // Parse success, now try calling func at top of stack 62 | var callFailed = _lua_pcallk(this.state, 0, -1, 0); 63 | if (callFailed) { 64 | this.report_error("Evaluation failure"); 65 | } else { 66 | return this.get_stack_args(); 67 | } 68 | } else { 69 | this.report_error("Parsing failure"); 70 | } 71 | }, 72 | inject: function (object, name, final_location, metatable) { 73 | name = name || this.get_tmp_name(); 74 | this.pushStack(object); 75 | if (metatable) { 76 | this.pushStack(metatable); 77 | _lua_setmetatable(this.state, -2); 78 | } 79 | var strptr = this.allocate_string(name); 80 | _lua_setglobal(this.state, strptr); 81 | _free(strptr); 82 | if (final_location) { 83 | this.exec(final_location + " = " + name + "\n" + name + " = nil"); 84 | } 85 | return (final_location || name); 86 | }, 87 | cache: function (evalstring) { 88 | if (!(evalstring in this.cache['items'])) { 89 | this.cache['items'][evalstring] = this.eval(evalstring) 90 | } 91 | return this.cache['items'][evalstring]; 92 | }, 93 | call: function (evalstring, args) { 94 | var func = this.cache(evalstring)[0]; 95 | return func.apply(null, args); 96 | }, 97 | allocate_string: function(str) { 98 | var arr = intArrayFromString(str); 99 | return allocate(arr, 'i8', 0); // ALLOC_NORMAL 100 | }, 101 | inspect: function(index) { 102 | var type = _lua_type(this.state, index); 103 | var ptr = _lua_typename(this.state, type); 104 | var typename = Pointer_stringify(ptr) 105 | var address = _lua_topointer(this.state, index); 106 | return { 107 | 'type': type, 108 | 'typename': typename, 109 | 'address': address, 110 | 'addrstr': address.toString(16), 111 | } 112 | }, 113 | peekStack: function(index, source) { 114 | this.require_initialization(); 115 | var ret; 116 | var type = _lua_type(this.state, index); 117 | switch (type) { 118 | case -1: // LUA_TNONE 119 | case 0: // LUA_TNIL 120 | ret = null; 121 | break; 122 | case 1: // LUA_TBOOLEAN 123 | var result = _lua_toboolean(this.state, index); 124 | ret = result ? true : false; 125 | break; 126 | case 3: // LUA_TNUMBER 127 | ret = _lua_tonumberx(this.state, index); 128 | break; 129 | case 4: // LUA_TSTRING 130 | var ptr = _lua_tolstring(this.state, index, 0); 131 | var len = _lua_rawlen(this.state, index); 132 | var buffer = []; 133 | for (var i = 0; i < len; i++) 134 | buffer.push(String.fromCharCode(HEAP8[ptr+i])); 135 | ret = buffer.join(''); 136 | break; 137 | case 5: // LUA_TTABLE 138 | var is_array = true; 139 | var max_key = 0; 140 | 141 | // Check for handle 142 | _lua_pushstring(this.state, this.preallocated_strings['__handle']); 143 | _lua_rawget(this.state, index-1); 144 | var handle = this.popStack(); 145 | if (handle) { 146 | // Return original value 147 | var ptr = this.preallocated_strings["__index"]; 148 | var success = _luaL_getmetafield( 149 | this.state, 150 | index, 151 | ptr 152 | ); 153 | var __indexfunc = this.popStack(); 154 | var source = __indexfunc.source; 155 | return source; 156 | } 157 | 158 | ret = {}; 159 | // Populate with values 160 | _lua_pushnil(this.state); 161 | _lua_pushnil(this.state); 162 | while (_lua_next(this.state, index-2)) { 163 | var value = this.popStack(); 164 | var key = this.peekStack(-1); 165 | ret[key] = value; 166 | 167 | if (is_array && typeof key === "number") { 168 | if (key > max_key) 169 | max_key = key; 170 | } else { 171 | is_array = false; 172 | } 173 | } 174 | this.popStack(); // Clear out leftover key 175 | if (is_array) { 176 | newret = []; 177 | for (var i = 1; i <= max_key; i++) { 178 | if (ret[i] === undefined) { 179 | // Abort 180 | is_array = false; 181 | break; 182 | } 183 | newret.push(ret[i]); 184 | } 185 | if (is_array) // not aborted 186 | ret = newret; 187 | } 188 | break; 189 | case 6: // LUA_TFUNCTION 190 | var self = this; 191 | var address = _lua_topointer(this.state, index); 192 | 193 | if (_lua_iscfunction(this.state, index)) { 194 | var func = FUNCTION_TABLE[address]; 195 | if (func.unwrapped) { 196 | return func.unwrapped; 197 | } 198 | } 199 | 200 | // Don't allocate this stuff for wrapped funcs 201 | var name = this.get_tmp_name(); 202 | var aname = this.allocate_string(name); 203 | 204 | _lua_pushvalue(this.state, index); // For non-destructive pop 205 | _lua_setglobal(this.state, aname); 206 | _free(aname); 207 | ret = function () { 208 | var orig_top = _lua_gettop(self.state); 209 | 210 | // Push function to stack 211 | var aname = self.allocate_string(name); 212 | _lua_getglobal(self.state, aname); 213 | _free(aname); 214 | 215 | // Convert arguments to Lua 216 | for (var i = 0; i < arguments.length; i++) { 217 | self.pushStack(arguments[i]) 218 | } 219 | 220 | // Call 221 | var failure = _lua_pcallk(self.state, arguments.length, -1, 0) // LUA_MULTRET 222 | if (failure) { 223 | self.report_error("Failure calling Lua function"); 224 | } 225 | var num_args = _lua_gettop(self.state) - orig_top ; 226 | return self.get_stack_args(num_args); 227 | } 228 | source = source || ""; 229 | ret.toString = function() { 230 | return "Lua function " + source + ": " + name + " at " + address; 231 | }; 232 | ret.source = source; 233 | ret.name = name; 234 | ret.address = address; 235 | break; 236 | default: // Other Lua type 237 | var inspection = this.inspect(index); 238 | ret = inspection.typename + " (typecode "+type+"): 0x" + inspection.addrstr; 239 | } 240 | return ret; 241 | }, 242 | popStack: function(source) { 243 | var ret = this.peekStack(-1, source); 244 | _lua_settop(this.state, -2); 245 | return ret; 246 | }, 247 | pushStack: function(object) { 248 | if (object === null) { 249 | object = undefined; 250 | } 251 | switch(typeof object) { 252 | case "undefined" : 253 | _lua_pushnil(this.state); 254 | return 1; 255 | case "boolean" : 256 | _lua_pushboolean(this.state, object); 257 | return 1; 258 | case "number" : 259 | _lua_pushnumber(this.state, object); 260 | return 1; 261 | case "string" : 262 | var strptr = this.allocate_string(object); 263 | _lua_pushstring(this.state, strptr); 264 | _free(strptr); 265 | return 1; 266 | case "function" : 267 | var self = this; 268 | var wrapper = function (state) { 269 | var result = object.apply(self, self.get_stack_args()); 270 | if (result == undefined || result == null) { 271 | result = []; 272 | } 273 | if (!( typeof result == 'object' && typeof result.length == "number")) { 274 | throw new Error("Expected array return type from JS function"); 275 | } 276 | for (var i = 0; i < result.length; i++) { 277 | self.pushStack(result[i]); 278 | } 279 | return result.length; 280 | } 281 | wrapper.unwrapped = object; 282 | var pointer = Runtime.addFunction(wrapper); 283 | _lua_pushcclosure(this.state, pointer, 0); 284 | return 1; 285 | case "object" : 286 | if (object.length === undefined) { 287 | // Object 288 | _lua_createtable(this.state, 0, 0); 289 | if (object['__handle']) { 290 | // Handled object 291 | var source = object; 292 | var metatable = { 293 | '__index': function (table, key) { 294 | return [source[key]]; 295 | }, 296 | '__newindex': function (table, key, value) { 297 | source[key] = value; 298 | return []; 299 | }, 300 | } 301 | metatable['__index'].source = source; 302 | 303 | this.pushStack(metatable); 304 | _lua_setmetatable(this.state, -2); 305 | 306 | object = {'__handle': object.toString()}; 307 | } 308 | for (var k in object) { 309 | this.pushStack(k); 310 | this.pushStack(object[k]); 311 | _lua_rawset(this.state, -3); 312 | } 313 | } else { 314 | // Array 315 | _lua_createtable(this.state, object.length, 0); 316 | for (var k in object) { 317 | k = 1*k; 318 | this.pushStack(k+1) 319 | this.pushStack(object[k]); 320 | _lua_rawset(this.state, -3); 321 | } 322 | } 323 | return 1; 324 | default: 325 | throw new Error("Cannot push object to stack: " + object); 326 | } 327 | }, 328 | get_stack_args: function(num_args) { 329 | num_args = (num_args === undefined) ? _lua_gettop(this.state) : num_args; 330 | var args = []; 331 | for (var i = 0; i < num_args; i++) { 332 | args.push(this.popStack()); 333 | } 334 | return args.reverse(); 335 | }, 336 | anon_lua_object: function (object) { 337 | // Create anonymous Lua object or literal from JS object 338 | if (object == undefined || object == null) { 339 | return "nil"; 340 | } 341 | switch (typeof object) { 342 | case "string": 343 | return '"' + object.replace('"','\\"') + '"'; 344 | case "function": 345 | case "object": 346 | return this.inject(object); 347 | default: 348 | return object.toString(); 349 | } 350 | }, 351 | get_tmp_name: function() { 352 | return "_weblua_tmp_" + this.tmp_id++; 353 | }, 354 | cleanup_tmp: function(name) { 355 | if (name == "_weblua_tmp_" + (this.tmp_id-1)) { 356 | // Latest tmp_id, can safely decrement 357 | tmp_id--; 358 | } 359 | // Set global to nil 360 | _lua_pushnil(this.state); 361 | var strptr = this.allocate_string(name); 362 | _lua_setglobal(this.state, strptr); 363 | _free(strptr); 364 | }, 365 | stdout: function (str) {console.log("stdout: " +str)}, 366 | stderr: function (str) {console.log("stderr: " +str)}, 367 | report_error: function(defaultMessage) { 368 | if (this.isInitialized) { 369 | var errorMessage = this.popStack(); 370 | if (!(errorMessage && errorMessage.length)) errorMessage = defaultMessage; 371 | this.stderr(errorMessage); 372 | } else { 373 | this.stderr(defaultMessage); 374 | } 375 | _lua_settop(this.state, 0); 376 | } 377 | } 378 | // Public functions 379 | this['Lua']['initialize'] = this['Lua'].initialize; 380 | this['Lua']['stdout'] = this['Lua'].stdout; 381 | this['Lua']['stderr'] = this['Lua'].stderr; 382 | this['Lua']['eval'] = this['Lua'].eval; 383 | this['Lua']['exec'] = this['Lua'].exec; 384 | this['Lua']['anon_lua_object'] = this['Lua'].anon_lua_object; 385 | this['Lua']['inject'] = this['Lua'].inject; 386 | this['Lua']['cache'] = this['Lua'].cache; 387 | 388 | Lua.cache['items'] = {}; 389 | Lua.cache['clear'] = function (evalstring) { delete Lua.cache['items'][evalstring] } 390 | --------------------------------------------------------------------------------