├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── notes ├── c-api.md ├── metamethods.md └── undefined-behavior.md ├── src ├── compiler.rs ├── compiler │ ├── exp_desc.rs │ ├── lexer.rs │ ├── parser.rs │ └── token.rs ├── error.rs ├── instr.rs ├── lib.rs ├── lua_std.rs ├── lua_std │ └── basic.rs ├── main.rs ├── vm.rs ├── vm │ ├── frame.rs │ ├── lua_val.rs │ ├── object.rs │ └── table.rs └── vm_aux.rs └── tests ├── test.rs ├── test01.lua ├── test02.lua ├── test03.lua ├── test04.lua ├── test05.lua ├── test06.lua ├── test07.lua ├── test08.lua ├── test09.lua ├── test10.lua └── test11.lua /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | .vscode 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lua" 3 | version = "0.1.0" 4 | authors = ["Chris Neidhart "] 5 | description = "The Lua programming language, implemented in Rust." 6 | edition = "2021" 7 | license = "MIT" 8 | readme = "README.md" 9 | repository = "https://github.com/cjneidhart/lua-in-rust" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chris Neidhart 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lua-in-rust 2 | The Lua programming language, implemented in Rust. 3 | 4 | ### Overview 5 | The code is primarily grouped into three modules: 6 | - `compiler` deals with parsing lua code and converting it into bytecode. 7 | - `lexer` converts Lua source code into tokens. 8 | - `parser` converts those tokens into bytecode. 9 | - `exp_desc` and `token` are type definitions. 10 | - `vm` is the largest module. It deals with actually evaluating lua code, and 11 | the Rust API. 12 | - `vm` itself holds the core functionality of the interpreter. 13 | - `frame` deals with evaluating bytecode. 14 | - `lua_val` defines the type for values in the VM. 15 | - `object` deals with garbage collection. 16 | - `table` implements Lua tables. 17 | - `lua_std` is where any functions in the lua standard library are implemented. 18 | - Other modules: 19 | - `error` defines the `Error` type used throughout the crate. 20 | - `instr` defines the VM's instruction set. 21 | - `lib` and `main` are the basic entrypoints for the library/interpreter. 22 | 23 | ### Goals 24 | The goals, in rough order of priority, are: 25 | - [x] Basic comparisons and equality 26 | - [x] `and`/`or` expressions with proper short-circuiting 27 | - [x] Basic strings 28 | - [x] `if`/`else`/`elseif` 29 | - [x] `while` and `repeat ... until` loops 30 | - [x] Local variables with proper scoping 31 | - [x] Numeric `for` loops 32 | - [x] Multiple assignment 33 | - [x] Single-line comments 34 | - [x] Function calls 35 | - [x] Function definition 36 | - [x] Tables 37 | - [x] Garbage Collection 38 | - [x] Full table literals 39 | - [x] Error when line starts with left paren and parses as function call 40 | - [ ] Multiple return values 41 | - [ ] `break` and `continue` 42 | - [ ] Interned strings 43 | - [ ] Unparenthesized function calls 44 | - [ ] Better error messages 45 | - [ ] Closures 46 | - [ ] Lua's `next` function 47 | - [ ] Generic `for` loops 48 | - [ ] Metatables 49 | - [ ] Separate array part of tables for integer keys 50 | - [ ] Lua's standard library 51 | - [ ] A Rust API to parallel Lua's C API 52 | - [ ] Coroutines 53 | - [ ] Multi-line comments 54 | - [ ] Use actual bytecode with variable-length instructions 55 | - [ ] Separate `luac` executable 56 | 57 | ### Building 58 | Like the real Lua, this project currently has zero dependencies. 59 | Just run `cargo run` in the root to launch the interpreter. 60 | 61 | ### Debug options 62 | There are a few environment options which enable debug features. 63 | These are all disabled by default. 64 | To enable an option, just set it in the environment before compiling 65 | (e.g. `export LUA_DEBUG_VM=1; cargo build`). 66 | For details on a debug option, look in the corresponding module. 67 | 68 | The options are: 69 | - `LUA_DEBUG_PARSER` 70 | - `LUA_DEBUG_VM` 71 | - `LUA_DEBUG_GC` 72 | -------------------------------------------------------------------------------- /notes/c-api.md: -------------------------------------------------------------------------------- 1 | Notes on the C API for Lua 2 | 3 | ## Types 4 | ```c 5 | typedef struct lua_State lua_State; 6 | typedef long long lua_Integer; 7 | typedef double lua_Number; 8 | typedef unsigned long long lua_Unsigned; 9 | typedef intptr_t lua_KContext; 10 | 11 | typedef void* (*lua_Alloc) (void* ud, void* ptr, size_t osize, size_t nsize); 12 | typedef int (*lua_CFunction) (lua_State* L); 13 | typedef int (*lua_KFunction) (lua_State* L, int status, lua_KContext ctx); 14 | typedef const char* (*lua_Reader) (lua_State *L, void* data, size_t* size); 15 | typedef int (*lua_Writer) (lua_State* L, const void* p, size_t sz, void* ud); 16 | 17 | typedef struct luaL_Stream { 18 | FILE* f; 19 | lua_CFunction closef; 20 | } 21 | ``` 22 | 23 | ## Indices 24 | _valid indices_ are positions in the stack (`1 <= abs(index) <= top`) and 25 | pseudo-indices. _acceptable indices_ are valid indices, as well as any positive 26 | indices which are greater than the top but still within the space allocated for 27 | the stack. Reading from an acceptable index returns a value of type 28 | `LUA_TNONE`, which is effectively `nil`. 29 | 30 | ## Functions 31 | - [State Management](#state-management) 32 | - [Stack Manipulation](#stack-manipulation) 33 | - [Basic Lua Operations](#basic-lua-ops) 34 | - [Tables](#tables) 35 | - [Push into Lua](#push-into-lua) 36 | - [Pull from Lua](#pull-from-lua) 37 | - [Type Checking](#type-checking) 38 | - [Type Conversion](#type-conversion) 39 | - [Metatables](#metatables) 40 | - [Loading Code](#loading-code) 41 | - [Userdata](#userdata) 42 | - [Coroutines](#coroutines) 43 | - [Formatting Strings](#formatting-strings) 44 | - [Buffer](#buffer) 45 | - [Registry](#registry) 46 | - [Standard Library](#standard-library) 47 | - [Misc.](#misc) 48 | 49 | ### State Management 50 | ```c 51 | lua_State* lua_newstate(lua_Alloc f, void* ud); 52 | void lua_close(lua_State* L); 53 | int lua_status(lua_State* L); 54 | const lua_Number* lua_version(lua_State* L); 55 | int lua_gc(lua_State* L, int what, int data); 56 | lua_Alloc lua_getallocf(lua_State* L, void** ud); 57 | void* lua_getextraspace(lua_State* L); 58 | lua_CFunction lua_atpanic(lua_State* L, lua_CFunction panicf); 59 | void lua_setallocf (lua_State *L, lua_Alloc f, void *ud); 60 | 61 | lua_State* luaL_newstate(void); 62 | void luaL_checkversion(lua_State* L); 63 | ``` 64 | 65 | ### Stack Manipulation 66 | ```c 67 | int lua_absindex(lua_State* L, int idx); 68 | int lua_checkstack(lua_State* L, int n); 69 | void lua_copy(lua_State* L, int fromidx, int toidx); 70 | int lua_gettop(lua_State* L); 71 | void lua_insert(lua_State* L, int index); 72 | void lua_pop(lua_State* L, int n); 73 | void lua_pushvalue(lua_State* L, int index); 74 | void lua_remove(lua_State* L, int index); 75 | void lua_replace(lua_State* L, int index); 76 | void lua_rotate(lua_State* L, int idx, int n); 77 | 78 | void luaL_checkstack(lua_State* L, int sz, const char* msg); 79 | ``` 80 | 81 | ### Basic Lua Ops 82 | ```c 83 | void lua_arith(lua_State* L, int op); 84 | int lua_compare(lua_State* L, int index1, int index2, int op); 85 | void lua_concat(lua_State* L, int n); 86 | int lua_error(lua_State* L); 87 | int lua_getglobal (lua_State *L, const char *name); 88 | void lua_len (lua_State *L, int index); 89 | int lua_rawequal (lua_State *L, int index1, int index2); 90 | size_t lua_rawlen (lua_State *L, int index); 91 | void lua_setglobal (lua_State *L, const char *name); 92 | 93 | lua_Integer luaL_len (lua_State *L, int index); 94 | ``` 95 | 96 | ### Tables 97 | ```c 98 | void lua_createtable (lua_State *L, int narr, int nrec); 99 | int lua_getfield (lua_State *L, int index, const char *k); 100 | int lua_geti (lua_State *L, int index, lua_Integer i); 101 | int lua_gettable (lua_State *L, int index); 102 | void lua_newtable (lua_State *L); 103 | int lua_next (lua_State *L, int index); 104 | int lua_rawget (lua_State *L, int index); 105 | int lua_rawgeti (lua_State *L, int index, lua_Integer n); 106 | int lua_rawgetp (lua_State *L, int index, const void *p); 107 | void lua_rawset (lua_State *L, int index); 108 | void lua_rawseti (lua_State *L, int index, lua_Integer i); 109 | void lua_rawsetp (lua_State *L, int index, const void *p); 110 | void lua_setfield (lua_State *L, int index, const char *k); 111 | void lua_seti (lua_State *L, int index, lua_Integer n); 112 | void lua_settable (lua_State *L, int index); 113 | ``` 114 | 115 | ### Push into Lua 116 | ```c 117 | void lua_pushboolean(lua_State* L, int b); 118 | void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n); 119 | void lua_pushcfunction (lua_State *L, lua_CFunction f); 120 | void lua_pushglobaltable (lua_State *L); 121 | void lua_pushinteger (lua_State *L, lua_Integer n); 122 | void lua_pushlightuserdata (lua_State *L, void *p); 123 | const char *lua_pushliteral (lua_State *L, const char *s); 124 | const char *lua_pushlstring (lua_State *L, const char *s, size_t len); 125 | void lua_pushnil (lua_State *L); 126 | void lua_pushnumber (lua_State *L, lua_Number n); 127 | const char *lua_pushstring (lua_State *L, const char *s); 128 | int lua_pushthread (lua_State *L); 129 | void lua_register(lua_State* L, const char* name, lua_CFunction f); 130 | ``` 131 | 132 | ### Pull from Lua 133 | ```c 134 | int lua_toboolean(lua_State* L, int index); 135 | lua_CFunction lua_tocfunction(lua_State* L, int index); 136 | lua_Integer lua_tointeger(lua_State* L, int index); 137 | lua_Integer lua_tointegerx(lua_State* L, int index, int* isnum); 138 | const char* lua_tolstring(lua_State* L, int index, size_t* len); 139 | lua_Number lua_tonumber(lua_State* L, int index); 140 | lua_Number lua_tonumberx(lua_State* L, int index, int* isnum); 141 | const void* lua_topointer(lua_State* L, int index); 142 | const char* lua_tostring(lua_State* L, int index); 143 | lua_State* lua_tothread(lua_State* L, int index); 144 | void* lua_touserdata(lua_State* L, int index); 145 | 146 | const char* luaL_tolstring(lua_State* L, int idx, size_t* len); 147 | ``` 148 | 149 | ### Functions 150 | ```c 151 | void lua_call(lua_State* L, int nargs, int nresults); 152 | void lua_callk(lua_State* L, int nargs, int nresults, lua_KContext ctx, 153 | lua_KFunction k); 154 | int lua_upvalueindex(int i); 155 | 156 | int luaL_callmeta (lua_State *L, int obj, const char *e); 157 | ``` 158 | 159 | ### Errors 160 | ```c 161 | int lua_error(lua_State *L); 162 | int lua_pcall(lua_State *L, int nargs, int nresults, int msgh); 163 | int lua_pcallk(lua_State *L, int nargs, int nresults, int msgh, 164 | lua_KContext ctx, lua_KFunction k); 165 | 166 | int luaL_error(lua_State* L, const char *fmt, ...); 167 | ``` 168 | 169 | ### Type Checking 170 | ```c 171 | int lua_isboolean(lua_State* L, int index); 172 | int lua_iscfunction(lua_State* L, int index); 173 | int lua_isfunction(lua_State* L, int index); 174 | int lua_isintger(lua_State* L, int index); 175 | int lua_islightuserdata(lua_State* L, int index); 176 | int lua_isnil(lua_State* L, int index); 177 | int lua_isnone(lua_State* L, int index); 178 | int lua_isnoneornil(lua_State* L, int index); 179 | int lua_isnumber(lua_State* L, int index); 180 | int lua_isstring(lua_State* L, int index); 181 | int lua_istable(lua_State* L, int index); 182 | int lua_isthread(lua_State* L, int index); 183 | int lua_isuserdata(lua_State* L, int index); 184 | int lua_isyieldable(lua_State* L, int index); 185 | 186 | int lua_type(lua_State* L, int index); 187 | const char* lua_typename(lua_State* L, int typ); 188 | 189 | const char* luaL_typename(lua_State* L, int index); 190 | 191 | void luaL_checkany(lua_State* L, int arg); 192 | lua_Integer luaL_checkinteger(lua_State* L, int arg); 193 | const char* luaL_checklstring(lua_State* L, int arg, size_t* l); 194 | lua_Number luaL_checknumber(lua_State* L, int arg); 195 | int luaL_checkoption(lua_State* L, int arg, const char* def, 196 | const char* const lst[]); 197 | const char* luaL_checkstring(lua_State* L, int arg); 198 | void luaL_checktype(lua_State* L, int arg, int t); 199 | void* luaL_checkudata(lua_State* L, int arg, const char* tname); 200 | void* luaL_testudata(lua_State* L, int arg, const char* tname); 201 | 202 | lua_Integer luaL_optinteger(lua_State* L, int arg, lua_Integer d); 203 | const char* luaL_optlstring(lua_State* L, int arg, const char* d, 204 | size_t* l); 205 | lua_Number luaL_optnumber(lua_State* L, int arg, lua_Number d); 206 | const char* luaL_optstring(lua_State* L, int arg, const char* d); 207 | 208 | #define luaL_opt(L, func, arg, dflt) \ 209 | (lua_isnoneornil(L, (arg)) \ 210 | ? (dflt) \ 211 | : func(L, (arg))) 212 | 213 | void luaL_argcheck(lua_State* L, int cond, int arg, const char* extramsg); 214 | void luaL_argerror(lua_State* L, int arg, const char* extramsg); 215 | ``` 216 | 217 | ### Type Conversion 218 | ```c 219 | int lua_numbertointeger(lua_Number n, lua_Integer* p); 220 | size_t lua_stringtonumber(lua_State* L, const char* s); 221 | ``` 222 | 223 | ### Metatables 224 | ```c 225 | int lua_getmetatable (lua_State *L, int index); 226 | void lua_setmetatable (lua_State *L, int index); 227 | 228 | int luaL_getmetafield (lua_State *L, int obj, const char *e); 229 | ``` 230 | 231 | ### Loading code 232 | ```c 233 | int lua_dump(lua_State* L, lua_Writer writer, void* data, int strip); 234 | int lua_load(lua_State* L, lua_Reader reader, void* data, const char* chunkname, 235 | const char* mode); 236 | 237 | int luaL_dofile(lua_State* L, const char* filename); 238 | int luaL_dostring(lua_State* L, const char* str); 239 | int luaL_loadbuffer(lua_State* L, const char* buff, size_t sz, const char* name); 240 | int luaL_loadbufferx(lua_State* L, const char* buff, size_t sz, 241 | const char* name, const char* mode); 242 | int luaL_loadfile(lua_State* L, const char* filename); 243 | int luaL_loadfilex(lua_State* L, const char* filename, const char* mode); 244 | int luaL_loadstring(lua_State* L, const char* s); 245 | ``` 246 | 247 | ### Userdata 248 | ```c 249 | int lua_getuservalue(lua_State* L, int index); 250 | void* lua_newuserdata(lua_State* L, size_t size); 251 | ``` 252 | 253 | ### Coroutines 254 | ```c 255 | lua_State* lua_newthread(lua_State* L); 256 | int lua_resume (lua_State *L, lua_State *from, int nargs); 257 | int lua_status(lua_State* L); 258 | void lua_xmove (lua_State *from, lua_State *to, int n); 259 | int lua_yield (lua_State *L, int nresults); 260 | int lua_yieldk(lua_State* L, int nresults, lua_KContext ctx, lua_KFunction k); 261 | ``` 262 | 263 | ### Formatting strings 264 | ```c 265 | const char *lua_pushfstring (lua_State *L, const char *fmt, ...); 266 | const char* lua_pushvfstring(lua_State* L, const char* fmt, va_list argp); 267 | 268 | const char* luaL_gsub(lua_State* L, const char* s, const char* p, 269 | const char* r); 270 | ``` 271 | 272 | ### Buffer 273 | ```c 274 | typedef struct luaL_Buffer luaL_Buffer; 275 | 276 | void luaL_addchar(luaL_Buffer* B, char c); 277 | void luaL_addlstring (luaL_Buffer *B, const char *s, size_t l); 278 | void luaL_addsize (luaL_Buffer *B, size_t n); 279 | void luaL_addstring (luaL_Buffer *B, const char *s); 280 | void luaL_addvalue (luaL_Buffer *B); 281 | void luaL_buffinit (lua_State *L, luaL_Buffer *B); 282 | char *luaL_buffinitsize (lua_State *L, luaL_Buffer *B, size_t sz); 283 | char *luaL_prepbuffer (luaL_Buffer *B); 284 | char *luaL_prepbuffsize (luaL_Buffer *B, size_t sz); 285 | void luaL_pushresult (luaL_Buffer *B); 286 | void luaL_pushresultsize (luaL_Buffer *B, size_t sz); 287 | ``` 288 | 289 | ### Registry 290 | ```c 291 | typedef struct luaL_Reg { 292 | const char* name; 293 | lua_CFunction func; 294 | } luaL_Reg; 295 | 296 | int luaL_getmetatable (lua_State *L, const char *tname); 297 | int luaL_getsubtable (lua_State *L, int idx, const char *fname); 298 | void luaL_newlib(lua_State* L, const luaL_Reg l[]); 299 | void luaL_newlibtable (lua_State *L, const luaL_Reg l[]); 300 | int luaL_newmetatable (lua_State *L, const char *tname); 301 | void luaL_setfuncs (lua_State *L, const luaL_Reg *l, int nup); 302 | void luaL_setmetatable (lua_State *L, const char *tname); 303 | ``` 304 | 305 | ### Standard library 306 | ```c 307 | #include 308 | 309 | void luaL_openlibs(lua_State *L); 310 | // luaopen_* 311 | ``` 312 | 313 | ### Misc. 314 | ```c 315 | int luaL_execresult (lua_State *L, int stat); 316 | int luaL_fileresult (lua_State *L, int stat, const char *fname); 317 | int luaL_ref(lua_State *L, int t); 318 | void luaL_requiref(lua_State* L, const char* modname, lua_CFunction openf, 319 | int glb); 320 | void luaL_traceback(lua_State* L, lua_State* L1, const char* msg, int level); 321 | void luaL_unref (lua_State *L, int t, int ref); 322 | void luaL_where (lua_State *L, int lvl); 323 | ``` 324 | -------------------------------------------------------------------------------- /notes/metamethods.md: -------------------------------------------------------------------------------- 1 | A list of all the metamethods used by the standard library: 2 | 3 | ### Arithmetic 4 | Both values must be numbers (or strings coercible to numbers). Otherwise, 5 | Lua will try a metamethod, prioritizing the left value. 6 | - `__add`: `+` 7 | - `__sub`: `-` 8 | - `__mul`: `*` 9 | - `__div`: `/` 10 | - `__mod`: `%` 11 | - `__pow`: `^` 12 | - `__idiv`: `//` 13 | - `__unm`: Unary `-` 14 | 15 | ### Bitwise operations 16 | Same as normal arithmetic, except the values must be integers, floats coercible 17 | to integers, or strings coercible to integers. 18 | - `__band`: `&` 19 | - `__bor`: `|` 20 | - `__bxor`: `~` (binary XOR) 21 | - `__bnot`: Unary `~` 22 | - `__shl`: `<<` 23 | - `__shr`: `>>` 24 | 25 | ### Equality 26 | - `__eq`: Called by `==` and `~=`, only if the values are both tables or both 27 | userdata, and they are not primitively equal. 28 | 29 | ### Comparisons 30 | It's easier to list the operators. These are not called if both values are 31 | strings or both are numbers (strings will be compared lexographically). 32 | - `<`: Looks for `__lt`. 33 | - `>`: Same as `__lt`, but swaps the arguments. 34 | - `<=`: First looks for `__le` in both. Then, proceeds as if it were 35 | `not (rhs < lhs)`. 36 | - `>=`: Same as `<=`, but swaps the arguments. 37 | 38 | ### Indexing 39 | - `__index`: Called by `table[key]` when `table` is not a table or `key` is not 40 | in `table`. Can be another table instead of a function, in which case it will 41 | index that table (which can trigger another metamethod). 42 | - `__newindex`: Called by `table[key] = value`, same as `__index`. 43 | 44 | ### Other 45 | - `__concat`: `..` Only called if one value is not a string or number (numbers 46 | will be coerced to strings). 47 | - `__len`: `#`. Only called if the value is not a string. If there's no 48 | metamethod and it's a table, then it will perform the normal `#` operation. 49 | - `__call`: Called by `func(args...)` when `func` is not a function. The 50 | metamethod will receive `func` as its first argument, followed by the values 51 | in `args...`. 52 | 53 | ### Mentioned elsewhere 54 | - `__gc`: Must be in the metatable before it is set as a metatable. The object 55 | must be a table or userdata. Calls the `__gc` metamethod when the object 56 | is garbage-collected. 57 | - `__mode`: Should be a string, not a function. If a table's metatable has 58 | field `__mode` and that string contains `'k'`, it has weak keys. If the 59 | string contains `'v'`, it has weak values. 60 | - `__metatable`: Used by `getmetatable` and `setmetatable` in the standard 61 | library (but not the equivalent functions in the C/Rust API). If this field 62 | is set, `getmetatable` will return this value instead of the actual 63 | metatable, and `setmetatable` will raise an error. 64 | - `__name`: Used by the registry system. 65 | - `__pairs`: Called by the `pairs` function, if it exists. Otherwise, pairs 66 | returns `next`, the table, and `nil`. 67 | - `__tostring`: Used by the `tostring` function. 68 | -------------------------------------------------------------------------------- /notes/undefined-behavior.md: -------------------------------------------------------------------------------- 1 | ## Weak tables 2 | Changing the `__mode` field of a table after it has become a metatable is 3 | undefined behavior: 4 | ```lua 5 | t, meta = {}, {} 6 | setmetatable(t, meta) 7 | meta.__mode = 'kv' -- BAD! 8 | ``` 9 | 10 | ## Modification during traversal 11 | The standard library function `next` by default is guaranteed to visit every 12 | element exactly once. If you assign to an empty field in the table 13 | mid-traversal, and then try to continue from the same key, its behavior is 14 | undefined. Note that it is perfectly fine to modify existing fields, and to 15 | clear them by assigning `nil`. 16 | 17 | ## The Length Operator 18 | If a table has `holes` among its numeric keys, the length operator `#` may 19 | return any of the holes as its length. 20 | ```lua 21 | t = { 'a', 'b', 'c' } 22 | t[2] = nil 23 | print(#t) -- Could be 1 or 3 24 | ``` 25 | 26 | ## Invalid table keys 27 | This is not in the reference, but `nil` and any numbers which are NaN (i.e. 28 | `a != a`) are disallowed as table keys. When reading from a table, using these 29 | keys will always return `nil`. When assigning to a table, using these keys will 30 | cause an error. 31 | -------------------------------------------------------------------------------- /src/compiler.rs: -------------------------------------------------------------------------------- 1 | //! Functions and types associated with converting source code into bytecode. 2 | 3 | mod exp_desc; 4 | mod lexer; 5 | mod parser; 6 | mod token; 7 | 8 | use super::error; 9 | use super::Instr; 10 | use super::Result; 11 | 12 | #[derive(Clone, Debug, Default, PartialEq)] 13 | pub(super) struct Chunk { 14 | pub(super) code: Vec, 15 | pub(super) number_literals: Vec, 16 | pub(super) string_literals: Vec, 17 | pub(super) num_params: u8, 18 | pub(super) num_locals: u8, 19 | pub(super) nested: Vec, 20 | } 21 | 22 | pub(super) fn parse_str(source: impl AsRef) -> Result { 23 | parser::parse_str(source.as_ref()) 24 | } 25 | -------------------------------------------------------------------------------- /src/compiler/exp_desc.rs: -------------------------------------------------------------------------------- 1 | //! This module holds enums which describe the different types of Lua 2 | //! expressions. 3 | 4 | #[derive(Debug)] 5 | pub(super) enum ExpDesc { 6 | Prefix(PrefixExp), 7 | Other, 8 | } 9 | 10 | /// A "prefix expression" is an expression which could be followed by certain 11 | /// extensions and still be a valid expression. 12 | #[derive(Clone, Debug)] 13 | pub(super) enum PrefixExp { 14 | /// One of the variants of `PlaceExp` 15 | Place(PlaceExp), 16 | /// A function call, and the number of arguments 17 | FunctionCall(u8), 18 | /// An expression wrapped in parentheses 19 | Parenthesized, 20 | } 21 | 22 | /// This represents an expression which can appear on the left-hand side of an assignment. 23 | /// Also called an "lvalue" in other languages. 24 | #[derive(Clone, Debug)] 25 | pub(super) enum PlaceExp { 26 | /// A local variable, and its index in the list of locals 27 | Local(u8), 28 | /// A global variable, and its index in the list of string literals 29 | Global(u8), 30 | /// A table index, with `[` and `]` 31 | TableIndex, 32 | /// A field access, and the index of the field's identifier in the list of 33 | /// string literals 34 | FieldAccess(u8), 35 | } 36 | 37 | impl From for ExpDesc { 38 | fn from(exp: PrefixExp) -> Self { 39 | Self::Prefix(exp) 40 | } 41 | } 42 | 43 | impl From for PrefixExp { 44 | fn from(exp: PlaceExp) -> Self { 45 | Self::Place(exp) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/compiler/lexer.rs: -------------------------------------------------------------------------------- 1 | //! This module contains functions which can tokenize a string input. 2 | 3 | use super::error::Error; 4 | use super::error::SyntaxError; 5 | use super::token::Token; 6 | use super::token::TokenType::{self, *}; 7 | use super::Result; 8 | 9 | use std::iter::Peekable; 10 | use std::slice::SliceIndex; 11 | use std::str::CharIndices; 12 | 13 | /// A `TokenStream` is a wrapper around a `Lexer`. It provides a lookahead buffer and several 14 | /// helper methods. 15 | #[derive(Debug)] 16 | pub(super) struct TokenStream<'a> { 17 | lexer: Lexer<'a>, 18 | lookahead: Option, 19 | } 20 | 21 | /// A `Lexer` handles the raw conversion of characters to tokens. 22 | #[derive(Debug)] 23 | pub(super) struct Lexer<'a> { 24 | /// The starting position of the next character. 25 | pos: usize, 26 | /// `linebreaks[i]` is the byte offset of the start of line `i`. 27 | linebreaks: Vec, 28 | iter: Peekable>, 29 | source: &'a str, 30 | } 31 | 32 | impl<'a> TokenStream<'a> { 33 | /// Constructs a new `TokenStream`. 34 | #[must_use] 35 | pub(super) fn new(source: &'a str) -> Self { 36 | Self { 37 | lexer: Lexer::new(source), 38 | lookahead: None, 39 | } 40 | } 41 | 42 | /// Returns the next `Token`. 43 | pub(super) fn next(&mut self) -> Result { 44 | match self.lookahead.take() { 45 | Some(token) => Ok(token), 46 | None => self.lexer.next_token(), 47 | } 48 | } 49 | 50 | /// Returns the next `Token`, without popping it from the stream. 51 | pub(super) fn peek(&mut self) -> Result<&Token> { 52 | if self.lookahead.is_none() { 53 | self.lookahead = Some(self.lexer.next_token()?); 54 | } 55 | Ok(self.lookahead.as_ref().unwrap()) 56 | } 57 | 58 | /// Returns the type of the next token. 59 | pub(super) fn peek_type(&mut self) -> Result { 60 | Ok(self.peek()?.typ) 61 | } 62 | 63 | /// Returns whether the next token is of the given type. 64 | pub(super) fn check_type(&mut self, expected_type: TokenType) -> Result { 65 | Ok(self.peek_type()? == expected_type) 66 | } 67 | 68 | /// Checks the next token's type. If it matches `expected_type`, it is popped off and 69 | /// returned as `Some`. Otherwise, returns `None`. 70 | pub(super) fn try_pop(&mut self, expected_type: TokenType) -> Result> { 71 | if self.check_type(expected_type)? { 72 | Ok(Some(self.next().unwrap())) 73 | } else { 74 | Ok(None) 75 | } 76 | } 77 | 78 | /// Returns the current position of the `TokenStream`. 79 | #[must_use] 80 | pub(super) fn line_and_column(&self, pos: usize) -> (usize, usize) { 81 | self.lexer.line_and_col(pos) 82 | } 83 | 84 | /// Returns how many bytes have been read. 85 | #[must_use] 86 | pub(super) fn pos(&self) -> usize { 87 | match &self.lookahead { 88 | Some(token) => token.start, 89 | None => self.lexer.pos, 90 | } 91 | } 92 | 93 | /// Returns a substring from the source code. 94 | #[must_use] 95 | pub(super) fn substring(&self, index: impl SliceIndex) -> &'a str { 96 | &self.lexer.source[index] 97 | } 98 | } 99 | 100 | impl<'a> Lexer<'a> { 101 | /// Constructs a new `Lexer`. 102 | #[must_use] 103 | pub(super) fn new(source: &'a str) -> Self { 104 | let linebreaks = vec![0]; 105 | Self { 106 | iter: source.char_indices().peekable(), 107 | linebreaks, 108 | pos: 0, 109 | source, 110 | } 111 | } 112 | 113 | /// Returns the next `Token`. 114 | pub(super) fn next_token(&mut self) -> Result { 115 | let starts_line = self.consume_whitespace(); 116 | let tok_start = self.pos; 117 | if let Some(first_char) = self.next_char() { 118 | let tok_type = match first_char { 119 | '+' => Plus, 120 | '*' => Star, 121 | '/' => Slash, 122 | '%' => Mod, 123 | '^' => Caret, 124 | '#' => Hash, 125 | ';' => Semi, 126 | ':' => Colon, 127 | ',' => Comma, 128 | '(' if starts_line => LParenLineStart, 129 | '(' => LParen, 130 | ')' => RParen, 131 | '{' => LCurly, 132 | '}' => RCurly, 133 | ']' => RSquare, 134 | 135 | '.' => self.peek_dot(tok_start)?, 136 | 137 | '=' | '<' | '>' | '~' => self.peek_equals(tok_start, first_char)?, 138 | 139 | '-' => { 140 | if self.try_next('-') { 141 | return self.comment(); 142 | } else { 143 | Minus 144 | } 145 | } 146 | 147 | '\'' | '\"' => self.lex_string(first_char, tok_start)?, 148 | '[' => { 149 | if let Some('=') | Some('[') = self.peek_char() { 150 | panic!("Long strings are not supported yet."); 151 | } else { 152 | LSquare 153 | } 154 | } 155 | 156 | '0'..='9' => self.lex_full_number(tok_start, first_char)?, 157 | 158 | 'a'..='z' | 'A'..='Z' | '_' => self.lex_word(first_char), 159 | 160 | _ => return Err(self.error(SyntaxError::InvalidCharacter(first_char))), 161 | }; 162 | let len = (self.pos - tok_start) as u32; 163 | let token = Token { 164 | typ: tok_type, 165 | start: tok_start, 166 | len, 167 | }; 168 | Ok(token) 169 | } else { 170 | Ok(self.end_of_file()) 171 | } 172 | } 173 | 174 | /// Skips over the characters in a comment. 175 | fn comment(&mut self) -> Result { 176 | // TODO multi-line comments 177 | while let Some(c) = self.next_char() { 178 | if c == '\n' { 179 | return self.next_token(); 180 | } 181 | } 182 | Ok(self.end_of_file()) 183 | } 184 | 185 | /// Peeks the next character. 186 | #[must_use] 187 | fn peek_char(&mut self) -> Option { 188 | self.iter.peek().map(|(_, c)| *c) 189 | } 190 | 191 | /// Pops and returns the next character. 192 | fn next_char(&mut self) -> Option { 193 | match self.iter.next() { 194 | Some((pos, c)) => { 195 | self.pos = pos + c.len_utf8(); 196 | if c == '\n' { 197 | self.linebreaks.push(self.pos); 198 | } 199 | Some(c) 200 | } 201 | None => None, 202 | } 203 | } 204 | 205 | /// Consumes any whitespace characters. Returns whether or not a newline was consumed. 206 | fn consume_whitespace(&mut self) -> bool { 207 | let mut ret = false; 208 | while let Some(c) = self.peek_char() { 209 | if !c.is_ascii_whitespace() { 210 | break; 211 | } 212 | if c == '\n' { 213 | ret = true; 214 | } 215 | self.next_char(); 216 | } 217 | ret 218 | } 219 | 220 | /// Move a character forward, only if the current character matches 221 | /// `expected`. 222 | fn try_next(&mut self, expected: char) -> bool { 223 | match self.peek_char() { 224 | Some(c) if c == expected => { 225 | self.next_char(); 226 | true 227 | } 228 | _ => false, 229 | } 230 | } 231 | 232 | /// Constructs an error of the given kind at the current position. 233 | #[must_use] 234 | fn error(&self, kind: SyntaxError) -> Error { 235 | let (line_num, column) = self.line_and_col(self.pos); 236 | Error::new(kind, line_num, column) 237 | } 238 | 239 | /// The lexer just read a `.`. 240 | /// Determines whether it is part of a `Dot`, `DotDot`, `DotDotDot` or `Number`. 241 | fn peek_dot(&mut self, tok_start: usize) -> Result { 242 | let typ = match self.peek_char() { 243 | Some('.') => { 244 | self.next_char(); 245 | if self.try_next('.') { 246 | DotDotDot 247 | } else { 248 | DotDot 249 | } 250 | } 251 | Some(c) if c.is_ascii_digit() => { 252 | self.next_char(); 253 | self.lex_number_after_decimal(tok_start)?; 254 | LiteralNumber 255 | } 256 | _ => Dot, 257 | }; 258 | Ok(typ) 259 | } 260 | 261 | /// The lexer just read something which might be part of a two-character 262 | /// operator, with `=` as the second character. 263 | /// 264 | /// Returns `Err` if the first character is `~` and it is not paired with a 265 | /// `=`. 266 | fn peek_equals(&mut self, _tok_start: usize, first_char: char) -> Result { 267 | if self.try_next('=') { 268 | let typ = match first_char { 269 | '=' => Equal, 270 | '~' => NotEqual, 271 | '<' => LessEqual, 272 | '>' => GreaterEqual, 273 | _ => panic!("peek_equals was called with first_char = {}", first_char), 274 | }; 275 | Ok(typ) 276 | } else { 277 | match first_char { 278 | '=' => Ok(Assign), 279 | '<' => Ok(Less), 280 | '>' => Ok(Greater), 281 | '~' => Err(self.error(SyntaxError::InvalidCharacter(first_char))), 282 | _ => panic!("peek_equals was called with first_char = {}", first_char), 283 | } 284 | } 285 | } 286 | 287 | /// Tokenizes a 'short' literal string, AKA a string denoted by single or 288 | /// double quotes and not by two square brackets. 289 | fn lex_string(&mut self, quote: char, _tok_start: usize) -> Result { 290 | while let Some(c) = self.next_char() { 291 | if c == quote { 292 | return Ok(LiteralString); 293 | } else if c == '\\' { 294 | // TODO make backslash-escapes actually work. For now, we just 295 | // ignore the next character, which is the correct behavior for 296 | // newlines and quotes, but not escapes like '\n'. 297 | self.next_char(); 298 | } else if c == '\n' { 299 | return Err(self.error(SyntaxError::UnclosedString)); 300 | } 301 | } 302 | 303 | Err(self.error(SyntaxError::UnclosedString)) 304 | } 305 | 306 | /// Reads in a number which starts with a digit (as opposed to a decimal point). 307 | fn lex_full_number(&mut self, tok_start: usize, first_char: char) -> Result { 308 | // Check for hex values 309 | if first_char == '0' && self.try_next('x') { 310 | // Has to be at least one digit 311 | match self.next_char() { 312 | Some(c) if c.is_ascii_hexdigit() => (), 313 | _ => return Err(self.error(SyntaxError::BadNumber)), 314 | }; 315 | // Read the rest of the numbers 316 | while let Some(c) = self.peek_char() { 317 | if c.is_ascii_hexdigit() { 318 | self.next_char(); 319 | } else { 320 | break; 321 | } 322 | } 323 | 324 | match self.peek_char() { 325 | Some(c) if c.is_ascii_hexdigit() => Err(self.error(SyntaxError::BadNumber)), 326 | _ => Ok(LiteralHexNumber), 327 | } 328 | } else { 329 | // Read in the rest of the base 330 | self.lex_digits(); 331 | 332 | // Handle the fraction and exponent components. 333 | if self.try_next('.') { 334 | match self.peek_char() { 335 | Some(c) if c.is_ascii_digit() => self.lex_number_after_decimal(tok_start)?, 336 | _ => self.lex_exponent(tok_start)?, 337 | } 338 | } else { 339 | self.lex_exponent(tok_start)?; 340 | } 341 | 342 | Ok(LiteralNumber) 343 | } 344 | } 345 | 346 | /// Reads in a literal number which starts with a decimal point. 347 | fn lex_number_after_decimal(&mut self, tok_start: usize) -> Result<()> { 348 | self.lex_digits(); 349 | self.lex_exponent(tok_start) 350 | } 351 | 352 | /// Consumes an unbroken sequence of digits. 353 | fn lex_digits(&mut self) { 354 | while let Some(c) = self.peek_char() { 355 | if c.is_ascii_digit() { 356 | self.next_char(); 357 | } else { 358 | break; 359 | } 360 | } 361 | } 362 | 363 | /// Consumes the optional exponent part of a literal number, then checks 364 | /// for any trailing letters. 365 | fn lex_exponent(&mut self, _tok_start: usize) -> Result<()> { 366 | if self.try_next('E') || self.try_next('e') { 367 | // The exponent might have a sign. 368 | if let Some(c) = self.peek_char() { 369 | if c == '+' || c == '-' { 370 | self.next_char(); 371 | } 372 | } 373 | 374 | self.lex_digits(); 375 | } 376 | match self.peek_char() { 377 | Some(c) if c.is_ascii_hexdigit() => Err(self.error(SyntaxError::BadNumber)), 378 | _ => Ok(()), 379 | } 380 | } 381 | 382 | /// Reads a word and returns it as an identifier or keyword. 383 | fn lex_word(&mut self, first_char: char) -> TokenType { 384 | let mut word = String::new(); 385 | word.push(first_char); 386 | while let Some(c) = self.peek_char() { 387 | if c.is_ascii_alphabetic() || c.is_ascii_digit() || c == '_' { 388 | word.push(c); 389 | self.next_char(); 390 | } else { 391 | break; 392 | } 393 | } 394 | 395 | keyword_match(&word) 396 | } 397 | 398 | /// Returns the current position of the `Lexer`. 399 | #[must_use] 400 | fn line_and_col(&self, pos: usize) -> (usize, usize) { 401 | let iter = self.linebreaks.windows(2).enumerate(); 402 | for (line_num, linebreak_pair) in iter { 403 | if pos < linebreak_pair[1] { 404 | let column = pos - linebreak_pair[0]; 405 | // lines and columns start counting at 1 406 | return (line_num + 1, column + 1); 407 | } 408 | } 409 | let line_num = self.linebreaks.len() - 1; 410 | let column = pos - self.linebreaks.last().unwrap(); 411 | (line_num + 1, column + 1) 412 | } 413 | 414 | #[must_use] 415 | const fn end_of_file(&self) -> Token { 416 | Token { 417 | typ: TokenType::EndOfFile, 418 | start: self.pos, 419 | len: 0, 420 | } 421 | } 422 | } 423 | 424 | /// Checks if a word is a keyword, then returns the appropriate `TokenType`. 425 | #[must_use] 426 | fn keyword_match(s: &str) -> TokenType { 427 | match s { 428 | "and" => And, 429 | "break" => Break, 430 | "do" => Do, 431 | "else" => Else, 432 | "elseif" => ElseIf, 433 | "end" => End, 434 | "false" => False, 435 | "for" => For, 436 | "function" => Function, 437 | "if" => If, 438 | "in" => In, 439 | "local" => Local, 440 | "nil" => Nil, 441 | "not" => Not, 442 | "or" => Or, 443 | "repeat" => Repeat, 444 | "return" => Return, 445 | "then" => Then, 446 | "true" => True, 447 | "until" => Until, 448 | "while" => While, 449 | _ => Identifier, 450 | } 451 | } 452 | 453 | #[cfg(test)] 454 | mod tests { 455 | use super::*; 456 | 457 | fn check(input: &str, tokens: &[(TokenType, usize, u32)], lines: &[usize]) { 458 | let mut lexer = Lexer::new(input); 459 | let mut tokens = tokens 460 | .iter() 461 | .map(|&(typ, start, len)| Token { typ, start, len }); 462 | loop { 463 | let actual = lexer.next_token().unwrap(); 464 | if actual.typ == TokenType::EndOfFile { 465 | break; 466 | } 467 | let expected = tokens.next().unwrap(); 468 | assert_eq!(expected, actual); 469 | } 470 | assert!(tokens.next().is_none()); 471 | assert_eq!(lines, lexer.linebreaks.as_slice()); 472 | } 473 | 474 | fn check_line(input: &str, tokens: &[(TokenType, usize, u32)]) { 475 | check(input, tokens, &[0]); 476 | } 477 | 478 | #[test] 479 | fn test_lexer01() { 480 | let tokens = &[(LiteralNumber, 0, 2)]; 481 | check_line("50", tokens); 482 | } 483 | 484 | #[test] 485 | fn test_lexer02() { 486 | let input = "hi 4 false"; 487 | let tokens = &[(Identifier, 0, 2), (LiteralNumber, 3, 1), (False, 5, 5)]; 488 | check_line(input, tokens); 489 | } 490 | 491 | #[test] 492 | fn test_lexer03() { 493 | let input = "hi5"; 494 | let tokens = &[(Identifier, 0, 3)]; 495 | check_line(input, tokens); 496 | } 497 | 498 | #[test] 499 | fn test_lexer04() { 500 | let input = "5 + 5"; 501 | let tokens = &[(LiteralNumber, 0, 1), (Plus, 2, 1), (LiteralNumber, 4, 1)]; 502 | check_line(input, tokens); 503 | } 504 | 505 | #[test] 506 | fn test_lexer05() { 507 | let input = "print 5 or 6;"; 508 | let tokens = &[ 509 | (Identifier, 0, 5), 510 | (LiteralNumber, 6, 1), 511 | (Or, 8, 2), 512 | (LiteralNumber, 11, 1), 513 | (Semi, 12, 1), 514 | ]; 515 | check_line(input, tokens); 516 | } 517 | 518 | #[test] 519 | fn test_lexer06() { 520 | let input = "t = {x = 3}"; 521 | let tokens = &[ 522 | (Identifier, 0, 1), 523 | (Assign, 2, 1), 524 | (LCurly, 4, 1), 525 | (Identifier, 5, 1), 526 | (Assign, 7, 1), 527 | (LiteralNumber, 9, 1), 528 | (RCurly, 10, 1), 529 | ]; 530 | check_line(input, tokens); 531 | } 532 | 533 | #[test] 534 | fn test_lexer07() { 535 | let input = "0x5rad"; 536 | let tokens = &[(LiteralHexNumber, 0, 3), (Identifier, 3, 3)]; 537 | check_line(input, tokens); 538 | } 539 | 540 | #[test] 541 | fn test_lexer08() { 542 | let input = "print {x = 5,}"; 543 | let tokens = &[ 544 | (Identifier, 0, 5), 545 | (LCurly, 6, 1), 546 | (Identifier, 7, 1), 547 | (Assign, 9, 1), 548 | (LiteralNumber, 11, 1), 549 | (Comma, 12, 1), 550 | (RCurly, 13, 1), 551 | ]; 552 | check_line(input, tokens); 553 | } 554 | 555 | #[test] 556 | fn test_lexer09() { 557 | let input = "print()\nsome_other_function(an_argument)\n"; 558 | let tokens = &[ 559 | (Identifier, 0, 5), 560 | (LParen, 5, 1), 561 | (RParen, 6, 1), 562 | (Identifier, 8, 19), 563 | (LParen, 27, 1), 564 | (Identifier, 28, 11), 565 | (RParen, 39, 1), 566 | ]; 567 | let linebreaks = &[0, 8, 41]; 568 | check(input, tokens, linebreaks); 569 | } 570 | 571 | #[test] 572 | fn test_lexer10() { 573 | let input = "\n\n2\n456\n"; 574 | let tokens = &[(LiteralNumber, 2, 1), (LiteralNumber, 4, 3)]; 575 | let linebreaks = &[0, 1, 2, 4, 8]; 576 | check(input, tokens, linebreaks); 577 | } 578 | 579 | #[test] 580 | fn test_lexer11() { 581 | let input = "-- basic test\nprint('hi' --comment\n )\n"; 582 | let tokens = &[ 583 | (Identifier, 14, 5), 584 | (LParen, 19, 1), 585 | (LiteralString, 20, 4), 586 | (RParen, 36, 1), 587 | ]; 588 | let linebreaks = &[0, 14, 35, 38]; 589 | check(input, tokens, linebreaks); 590 | } 591 | 592 | #[test] 593 | fn test_lexer12() { 594 | let input = "print()\n(some_other_function)(an_argument)\n"; 595 | let tokens = &[ 596 | (Identifier, 0, 5), 597 | (LParen, 5, 1), 598 | (RParen, 6, 1), 599 | (LParenLineStart, 8, 1), 600 | (Identifier, 9, 19), 601 | (RParen, 28, 1), 602 | (LParen, 29, 1), 603 | (Identifier, 30, 11), 604 | (RParen, 41, 1), 605 | ]; 606 | let linebreaks = &[0, 8, 43]; 607 | check(input, tokens, linebreaks); 608 | } 609 | } 610 | -------------------------------------------------------------------------------- /src/compiler/parser.rs: -------------------------------------------------------------------------------- 1 | use super::error::Error; 2 | use super::error::ErrorKind; 3 | use super::error::SyntaxError; 4 | use super::exp_desc::ExpDesc; 5 | use super::exp_desc::PlaceExp; 6 | use super::exp_desc::PrefixExp; 7 | use super::lexer::TokenStream; 8 | use super::token::Token; 9 | use super::token::TokenType; 10 | use super::Chunk; 11 | use super::Instr; 12 | use super::Result; 13 | 14 | use std::borrow::Borrow; 15 | use std::cmp::Ordering; 16 | 17 | /// Tracks the current state, to make parsing easier. 18 | #[derive(Debug)] 19 | struct Parser<'a> { 20 | /// The input token stream. 21 | input: TokenStream<'a>, 22 | chunk: Chunk, 23 | nest_level: i32, 24 | locals: Vec<(String, i32)>, 25 | outer_chunks: Vec, 26 | } 27 | 28 | /// Parses Lua source code into a `Chunk`. 29 | pub(super) fn parse_str(source: &str) -> Result { 30 | let parser = Parser { 31 | input: TokenStream::new(source), 32 | chunk: Chunk::default(), 33 | nest_level: 0, 34 | locals: Vec::new(), 35 | outer_chunks: Vec::new(), 36 | }; 37 | parser.parse_all() 38 | } 39 | 40 | impl<'a> Parser<'a> { 41 | // Helper functions 42 | 43 | /// Creates a new local slot at the current nest_level. 44 | /// Fails if we have exceeded the maximum number of locals. 45 | fn add_local(&mut self, name: &str) -> Result<()> { 46 | if self.locals.len() == u8::MAX as usize { 47 | Err(self.error(SyntaxError::TooManyLocals)) 48 | } else { 49 | self.locals.push((name.to_string(), self.nest_level)); 50 | if self.locals.len() > self.chunk.num_locals as usize { 51 | self.chunk.num_locals += 1; 52 | } 53 | Ok(()) 54 | } 55 | } 56 | 57 | /// Constructs an error of the given kind at the current position. 58 | // TODO: rename to error_here 59 | #[must_use] 60 | fn error(&self, kind: impl Into) -> Error { 61 | let pos = self.input.pos(); 62 | self.error_at(kind, pos) 63 | } 64 | 65 | /// Constructs an error of the given kind and position. 66 | #[must_use] 67 | fn error_at(&self, kind: impl Into, pos: usize) -> Error { 68 | let (line, column) = self.input.line_and_column(pos); 69 | Error::new(kind, line, column) 70 | } 71 | 72 | /// Constructs an error for when a specific `TokenType` was expected but not found. 73 | #[must_use] 74 | fn err_unexpected(&self, token: Token, _expected: TokenType) -> Error { 75 | let error_kind = if token.typ == TokenType::EndOfFile { 76 | SyntaxError::UnexpectedEof 77 | } else { 78 | SyntaxError::UnexpectedTok 79 | }; 80 | self.error_at(error_kind, token.start) 81 | } 82 | 83 | /// Pulls a token off the input and checks it against `expected`. 84 | /// Returns the token if it matches, `Err` otherwise. 85 | fn expect(&mut self, expected: TokenType) -> Result { 86 | let token = self.input.next()?; 87 | if token.typ == expected { 88 | Ok(token) 89 | } else { 90 | Err(self.err_unexpected(token, expected)) 91 | } 92 | } 93 | 94 | /// Expects an identifier token and returns the identifier as a string. 95 | fn expect_identifier(&mut self) -> Result<&'a str> { 96 | let token = self.expect(TokenType::Identifier)?; 97 | let name = self.get_text(token); 98 | Ok(name) 99 | } 100 | 101 | /// Expects an identifier and returns the id of its string literal. 102 | fn expect_identifier_id(&mut self) -> Result { 103 | let name = self.expect_identifier()?; 104 | self.find_or_add_string(name) 105 | } 106 | 107 | /// Stores a literal string and returns its index. 108 | fn find_or_add_string(&mut self, string: &str) -> Result { 109 | find_or_add(&mut self.chunk.string_literals, string) 110 | .ok_or_else(|| self.error(SyntaxError::TooManyStrings)) 111 | } 112 | 113 | /// Stores a literal number and returns its index. 114 | fn find_or_add_number(&mut self, num: f64) -> Result { 115 | find_or_add(&mut self.chunk.number_literals, &num) 116 | .ok_or_else(|| self.error(SyntaxError::TooManyNumbers)) 117 | } 118 | 119 | /// Converts a literal string's offsets into a real String. 120 | #[must_use] 121 | fn get_literal_string_contents(&self, tok: Token) -> &'a str { 122 | // Chop off the quotes 123 | let Token { start, len, typ } = tok; 124 | assert_eq!(typ, TokenType::LiteralString); 125 | assert!(len >= 2); 126 | let range = (start + 1)..(start + len as usize - 1); 127 | self.input.substring(range) 128 | } 129 | 130 | /// Gets the original source code contained by a token. 131 | #[must_use] 132 | fn get_text(&self, token: Token) -> &'a str { 133 | self.input.substring(token.range()) 134 | } 135 | 136 | /// Lowers the nesting level by one, discarding any locals from that block. 137 | fn level_down(&mut self) { 138 | while let Some((_, lvl)) = self.locals.last() { 139 | if *lvl == self.nest_level { 140 | self.locals.pop(); 141 | } else { 142 | break; 143 | } 144 | } 145 | self.nest_level -= 1; 146 | } 147 | 148 | /// Adds an instruction to the output. 149 | fn push(&mut self, instr: Instr) { 150 | self.chunk.code.push(instr); 151 | } 152 | 153 | // Actual parsing 154 | 155 | /// The main entry point for the parser. This parses the entire input. 156 | fn parse_all(mut self) -> Result { 157 | let c = self.parse_chunk(&[])?; 158 | let token = self.input.next()?; 159 | assert_eq!(self.nest_level, 0); 160 | if let TokenType::EndOfFile = token.typ { 161 | Ok(c) 162 | } else { 163 | Err(self.err_unexpected(token, TokenType::EndOfFile)) 164 | } 165 | } 166 | 167 | /// Parses a `Chunk`. 168 | fn parse_chunk(&mut self, params: &[&str]) -> Result { 169 | self.outer_chunks.push(self.chunk.clone()); 170 | self.chunk = Chunk::default(); 171 | 172 | self.chunk.num_params = params.len() as u8; 173 | for ¶m in params { 174 | self.locals.push((param.into(), self.nest_level)); 175 | } 176 | 177 | self.parse_statements()?; 178 | self.push(Instr::Return(0)); 179 | 180 | let tmp_chunk = self.chunk.clone(); 181 | self.chunk = self.outer_chunks.pop().unwrap(); 182 | 183 | if option_env!("LUA_DEBUG_PARSER").is_some() { 184 | println!("Compiled chunk: {:#?}", &tmp_chunk); 185 | } 186 | 187 | Ok(tmp_chunk) 188 | } 189 | 190 | /// Parses 0 or more statements, possibly separated by semicolons. 191 | fn parse_statements(&mut self) -> Result<()> { 192 | loop { 193 | match self.input.peek_type()? { 194 | TokenType::Identifier | TokenType::LParen | TokenType::LParenLineStart => { 195 | self.parse_assign_or_call()? 196 | } 197 | TokenType::If => self.parse_if()?, 198 | TokenType::While => self.parse_while()?, 199 | TokenType::Repeat => self.parse_repeat()?, 200 | TokenType::Do => self.parse_do()?, 201 | TokenType::Local => self.parse_locals()?, 202 | TokenType::For => self.parse_for()?, 203 | TokenType::Function => self.parse_fndecl()?, 204 | TokenType::Semi => { 205 | self.input.next()?; 206 | } 207 | TokenType::Return => break self.parse_return(), 208 | _ => break Ok(()), 209 | } 210 | } 211 | } 212 | 213 | /// Parses a function declaration, which is any statement that starts with 214 | /// the keyword `function`. 215 | fn parse_fndecl(&mut self) -> Result<()> { 216 | self.input.next()?; // 'function' keyword 217 | let name = self.expect_identifier()?; 218 | match self.input.peek_type()? { 219 | TokenType::Dot => self.parse_fndecl_table(name), 220 | _ => self.parse_fndecl_basic(name), 221 | } 222 | } 223 | 224 | /// Parses a basic function declaration, which just assigns the function to 225 | /// a local or global variable. 226 | fn parse_fndecl_basic(&mut self, name: &'a str) -> Result<()> { 227 | let place_exp = self.parse_prefix_identifier(name)?; 228 | let instr = match place_exp { 229 | PlaceExp::Local(i) => Instr::SetLocal(i), 230 | PlaceExp::Global(i) => Instr::SetGlobal(i), 231 | _ => unreachable!("place expression was not a local or global variable"), 232 | }; 233 | self.parse_fndef()?; 234 | self.push(instr); 235 | Ok(()) 236 | } 237 | 238 | fn parse_fndecl_table(&mut self, table_name: &'a str) -> Result<()> { 239 | // Push the table onto the stack. 240 | let table_instr = match self.parse_prefix_identifier(table_name)? { 241 | PlaceExp::Local(i) => Instr::GetLocal(i), 242 | PlaceExp::Global(i) => Instr::GetGlobal(i), 243 | _ => unreachable!("place expression was not a local or global variable"), 244 | }; 245 | self.push(table_instr); 246 | 247 | // Parse all the fields. There must be at least one. 248 | self.expect(TokenType::Dot)?; 249 | let mut last_field_id = self.expect_identifier_id()?; 250 | while self.input.try_pop(TokenType::Dot)?.is_some() { 251 | self.push(Instr::GetField(last_field_id)); 252 | last_field_id = self.expect_identifier_id()?; 253 | } 254 | 255 | // Parse the function params and body. 256 | self.parse_fndef()?; 257 | self.push(Instr::SetField(0, last_field_id)); 258 | Ok(()) 259 | } 260 | 261 | /// Parses a return statement. Return statements must always come last in a 262 | /// block. 263 | fn parse_return(&mut self) -> Result<()> { 264 | self.input.next()?; // 'return' keyword 265 | let (n, _) = self.parse_explist()?; 266 | self.push(Instr::Return(n)); 267 | self.input.try_pop(TokenType::Semi)?; 268 | Ok(()) 269 | } 270 | 271 | /// Parses a statement which could be a variable assignment or a function call. 272 | fn parse_assign_or_call(&mut self) -> Result<()> { 273 | match self.parse_prefix_exp()? { 274 | PrefixExp::Parenthesized => { 275 | let tok = self.input.next()?; 276 | Err(self.err_unexpected(tok, TokenType::Assign)) 277 | } 278 | PrefixExp::FunctionCall(num_args) => { 279 | self.push(Instr::Call(num_args, 0)); 280 | Ok(()) 281 | } 282 | PrefixExp::Place(first_place) => self.parse_assign(first_place), 283 | } 284 | } 285 | 286 | /// Parses a variable assignment. 287 | fn parse_assign(&mut self, first_exp: PlaceExp) -> Result<()> { 288 | let mut places = vec![first_exp]; 289 | while self.input.try_pop(TokenType::Comma)?.is_some() { 290 | places.push(self.parse_place_exp()?); 291 | } 292 | 293 | self.expect(TokenType::Assign)?; 294 | let num_lvals = places.len() as isize; 295 | let (num_rvals, last_exp) = self.parse_explist()?; 296 | let num_rvals = num_rvals as isize; 297 | let diff = num_lvals - num_rvals; 298 | if diff > 0 { 299 | if let ExpDesc::Prefix(PrefixExp::FunctionCall(_)) = last_exp { 300 | let num_args = match self.chunk.code.pop() { 301 | Some(Instr::Call(args, _)) => args, 302 | i => unreachable!("PrefixExp::FunctionCall but last instruction was {:?}", i), 303 | }; 304 | self.push(Instr::Call(num_args, 1 + diff as u8)); 305 | } else { 306 | for _ in 0..diff { 307 | self.push(Instr::PushNil); 308 | } 309 | } 310 | } else { 311 | // discard excess rvals 312 | for _ in diff..0 { 313 | self.push(Instr::Pop); 314 | } 315 | } 316 | 317 | places.reverse(); 318 | for (i, place_exp) in places.into_iter().enumerate() { 319 | let instr = match place_exp { 320 | PlaceExp::Local(i) => Instr::SetLocal(i), 321 | PlaceExp::Global(i) => Instr::SetGlobal(i), 322 | PlaceExp::FieldAccess(literal_id) => { 323 | let stack_offset = num_lvals as u8 - i as u8 - 1; 324 | Instr::SetField(stack_offset, literal_id) 325 | } 326 | PlaceExp::TableIndex => { 327 | let stack_offset = num_lvals as u8 - i as u8 - 1; 328 | Instr::SetTable(stack_offset) 329 | } 330 | }; 331 | self.push(instr); 332 | } 333 | 334 | Ok(()) 335 | } 336 | 337 | /// Parses an expression which can appear on the left side of an assignment. 338 | fn parse_place_exp(&mut self) -> Result { 339 | match self.parse_prefix_exp()? { 340 | PrefixExp::Parenthesized | PrefixExp::FunctionCall(_) => { 341 | let tok = self.input.next()?; 342 | Err(self.err_unexpected(tok, TokenType::Assign)) 343 | } 344 | PrefixExp::Place(place) => Ok(place), 345 | } 346 | } 347 | 348 | /// Emits code to evaluate the prefix expression as a normal expression. 349 | fn eval_prefix_exp(&mut self, exp: &PrefixExp) { 350 | match exp { 351 | PrefixExp::FunctionCall(num_args) => { 352 | self.push(Instr::Call(*num_args, 1)); 353 | } 354 | PrefixExp::Parenthesized => (), 355 | PrefixExp::Place(place) => { 356 | let instr = match place { 357 | PlaceExp::Local(i) => Instr::GetLocal(*i), 358 | PlaceExp::Global(i) => Instr::GetGlobal(*i), 359 | PlaceExp::FieldAccess(i) => Instr::GetField(*i), 360 | PlaceExp::TableIndex => Instr::GetTable, 361 | }; 362 | self.push(instr); 363 | } 364 | } 365 | } 366 | 367 | /// Parses a variable's name. This should only ever return `Local` or `Global`. 368 | fn parse_prefix_identifier(&mut self, name: &str) -> Result { 369 | match find_last_local(&self.locals, name) { 370 | Some(i) => Ok(PlaceExp::Local(i as u8)), 371 | None => { 372 | let i = self.find_or_add_string(name)?; 373 | Ok(PlaceExp::Global(i)) 374 | } 375 | } 376 | } 377 | 378 | /// Parses a `local` declaration. 379 | fn parse_locals(&mut self) -> Result<()> { 380 | self.input.next().unwrap(); // `local` keyword 381 | let old_local_count = self.locals.len() as u8; 382 | 383 | let names = self.parse_namelist()?; 384 | 385 | let num_names = names.len() as u8; 386 | if self.input.try_pop(TokenType::Assign)?.is_some() { 387 | // Also perform the assignment 388 | let (num_rvalues, last_exp) = self.parse_explist()?; 389 | match num_names.cmp(&num_rvalues) { 390 | Ordering::Less => { 391 | for _ in num_names..num_rvalues { 392 | self.push(Instr::Pop); 393 | } 394 | } 395 | Ordering::Greater => { 396 | if let ExpDesc::Prefix(PrefixExp::FunctionCall(num_args)) = last_exp { 397 | self.chunk.code.pop(); // Pop the old 'Call' instruction 398 | self.push(Instr::Call(num_args, 1 + num_names - num_rvalues)); 399 | } else { 400 | for _ in num_rvalues..num_names { 401 | self.push(Instr::PushNil); 402 | } 403 | } 404 | } 405 | Ordering::Equal => (), 406 | } 407 | } else { 408 | // They've only been declared, just set them all nil 409 | for _ in &names { 410 | self.push(Instr::PushNil); 411 | } 412 | } 413 | 414 | // Actually perform the assignment 415 | for i in (0..num_names).rev() { 416 | self.push(Instr::SetLocal(i + old_local_count)); 417 | } 418 | 419 | // Bring the new variables into scope. It is important they are not 420 | // in scope until after this statement. 421 | for name in names { 422 | self.add_local(name)?; 423 | } 424 | 425 | Ok(()) 426 | } 427 | 428 | /// Parse a comma-separated list of identifiers. 429 | fn parse_namelist(&mut self) -> Result> { 430 | let mut names = vec![self.expect_identifier()?]; 431 | while self.input.try_pop(TokenType::Comma)?.is_some() { 432 | names.push(self.expect_identifier()?); 433 | } 434 | Ok(names) 435 | } 436 | 437 | /// Parses a `for` loop, before we know whether it's generic (`for i in t do`) or 438 | /// numeric (`for i = 1,5 do`). 439 | fn parse_for(&mut self) -> Result<()> { 440 | self.input.next()?; // `for` keyword 441 | let name = self.expect_identifier()?; 442 | self.nest_level += 1; 443 | self.expect(TokenType::Assign)?; 444 | self.parse_numeric_for(name)?; 445 | self.level_down(); 446 | Ok(()) 447 | } 448 | 449 | /// Parses a numeric `for` loop, starting with the first expression after the `=`. 450 | fn parse_numeric_for(&mut self, name: &str) -> Result<()> { 451 | // The start(current), stop and step are stored in three "hidden" local slots. 452 | let current_local_slot = self.locals.len() as u8; 453 | self.add_local("")?; 454 | self.add_local("")?; 455 | self.add_local("")?; 456 | 457 | // The actual local is in a fourth slot, so that it can be reassigned to. 458 | self.add_local(name)?; 459 | 460 | // First, all 3 control expressions are evaluated. 461 | self.parse_expr()?; 462 | self.expect(TokenType::Comma)?; 463 | self.parse_expr()?; 464 | 465 | // optional step value 466 | self.parse_numeric_for_step()?; 467 | 468 | // The ForPrep command pulls three values off the stack and places them 469 | // into locals to use in the loop. 470 | let loop_start_instr_index = self.chunk.code.len(); 471 | self.push(Instr::ForPrep(current_local_slot, -1)); 472 | 473 | // body 474 | self.parse_statements()?; 475 | self.expect(TokenType::End)?; 476 | let body_length = (self.chunk.code.len() - loop_start_instr_index) as isize; 477 | self.push(Instr::ForLoop(current_local_slot, -(body_length))); 478 | 479 | // Correct the ForPrep instruction. 480 | self.chunk.code[loop_start_instr_index] = Instr::ForPrep(current_local_slot, body_length); 481 | 482 | Ok(()) 483 | } 484 | 485 | /// Parses the optional step value of a numeric `for` loop. 486 | fn parse_numeric_for_step(&mut self) -> Result<()> { 487 | let next_token = self.input.next()?; 488 | match next_token.typ { 489 | TokenType::Comma => { 490 | self.parse_expr()?; 491 | self.expect(TokenType::Do)?; 492 | Ok(()) 493 | } 494 | TokenType::Do => { 495 | let i = self.find_or_add_number(1.0)?; 496 | self.push(Instr::PushNum(i)); 497 | Ok(()) 498 | } 499 | _ => Err(self.err_unexpected(next_token, TokenType::Do)), 500 | } 501 | } 502 | 503 | /// Parses a `do ... end` statement. 504 | fn parse_do(&mut self) -> Result<()> { 505 | self.input.next()?; // `do` keyword 506 | self.nest_level += 1; 507 | self.parse_statements()?; 508 | self.expect(TokenType::End)?; 509 | self.level_down(); 510 | Ok(()) 511 | } 512 | 513 | /// Parses a `repeat ... until` statement. 514 | fn parse_repeat(&mut self) -> Result<()> { 515 | self.input.next()?; // `repeat` keyword 516 | self.nest_level += 1; 517 | let body_start = self.chunk.code.len() as isize; 518 | self.parse_statements()?; 519 | self.expect(TokenType::Until)?; 520 | self.parse_expr()?; 521 | let expr_end = self.chunk.code.len() as isize; 522 | self.push(Instr::BranchFalse(body_start - (expr_end + 1))); 523 | self.level_down(); 524 | Ok(()) 525 | } 526 | 527 | /// Parses a `while ... do ... end` statement. 528 | fn parse_while(&mut self) -> Result<()> { 529 | // Structure of while loop instructions: 530 | // - Condition instructions 531 | // - `BranchFalse` to evaluate condition and skip body 532 | // - Body instructions 533 | // - `Jump` back to condition start 534 | self.input.next()?; 535 | self.nest_level += 1; 536 | let condition_start = self.chunk.code.len(); 537 | self.parse_expr()?; 538 | self.expect(TokenType::Do)?; 539 | 540 | let test_position = self.chunk.code.len(); 541 | self.push(Instr::BranchFalse(0)); 542 | 543 | self.parse_statements()?; 544 | self.expect(TokenType::End)?; 545 | 546 | let body_end = self.chunk.code.len(); 547 | self.push(Instr::Jump(-((body_end + 1 - condition_start) as isize))); 548 | 549 | let body_len = body_end - test_position; 550 | self.chunk.code[test_position] = Instr::BranchFalse(body_len as isize); 551 | 552 | self.level_down(); 553 | 554 | Ok(()) 555 | } 556 | 557 | /// Parses an if-then statement, including any attached `else` or `elseif` branches. 558 | fn parse_if(&mut self) -> Result<()> { 559 | self.parse_if_arm() 560 | } 561 | 562 | /// Parses an `if` or `elseif` block and any subsequent `elseif` or `else` 563 | /// blocks in the same chain. 564 | fn parse_if_arm(&mut self) -> Result<()> { 565 | self.input.next()?; // `if` or `elseif` keyword 566 | self.parse_expr()?; 567 | self.expect(TokenType::Then)?; 568 | self.nest_level += 1; 569 | 570 | let branch_instr_index = self.chunk.code.len(); 571 | self.push(Instr::BranchFalse(0)); 572 | 573 | self.parse_statements()?; 574 | let mut branch_target = self.chunk.code.len(); 575 | 576 | self.close_if_arm()?; 577 | if self.chunk.code.len() > branch_target { 578 | // If the size has changed, the first instruction added was a 579 | // Jump, so we need to skip it. 580 | branch_target += 1; 581 | } 582 | 583 | let branch_offset = (branch_target - branch_instr_index - 1) as isize; 584 | self.chunk.code[branch_instr_index] = Instr::BranchFalse(branch_offset); 585 | Ok(()) 586 | } 587 | 588 | /// Parses the closing keyword of an `if` or `elseif` arms, and any arms 589 | /// that may follow. 590 | fn close_if_arm(&mut self) -> Result<()> { 591 | self.level_down(); 592 | match self.input.peek_type()? { 593 | TokenType::ElseIf => self.parse_else_or_elseif(true), 594 | TokenType::Else => self.parse_else_or_elseif(false), 595 | _ => { 596 | self.expect(TokenType::End)?; 597 | Ok(()) 598 | } 599 | } 600 | } 601 | 602 | /// Parses an `elseif` or `else` block, and handles the `Jump` instruction 603 | /// for the end of the preceding block. 604 | fn parse_else_or_elseif(&mut self, elseif: bool) -> Result<()> { 605 | let jump_instr_index = self.chunk.code.len(); 606 | self.push(Instr::Jump(0)); 607 | if elseif { 608 | self.parse_if_arm()?; 609 | } else { 610 | self.parse_else()?; 611 | } 612 | let new_len = self.chunk.code.len(); 613 | let jump_len = new_len - jump_instr_index - 1; 614 | self.chunk.code[jump_instr_index] = Instr::Jump(jump_len as isize); 615 | Ok(()) 616 | } 617 | 618 | /// Parses an `else` block. 619 | fn parse_else(&mut self) -> Result<()> { 620 | self.nest_level += 1; 621 | self.input.next()?; // `else` keyword 622 | self.parse_statements()?; 623 | self.expect(TokenType::End)?; 624 | self.level_down(); 625 | Ok(()) 626 | } 627 | 628 | /// Parses a comma-separated list of expressions. Trailing and leading 629 | /// commas are not allowed. Returns how many expressions were parsed and 630 | /// a descriptor of the last expression. 631 | fn parse_explist(&mut self) -> Result<(u8, ExpDesc)> { 632 | // An explist has to have at least one expression. 633 | let mut last_exp_desc = self.parse_expr()?; 634 | let mut num_expressions = 1; 635 | while let Some(token) = self.input.try_pop(TokenType::Comma)? { 636 | if num_expressions == u8::MAX { 637 | return Err(self.error_at(SyntaxError::Complexity, token.start)); 638 | } 639 | last_exp_desc = self.parse_expr()?; 640 | num_expressions += 1; 641 | } 642 | 643 | Ok((num_expressions, last_exp_desc)) 644 | } 645 | 646 | /// Parses a single expression. 647 | fn parse_expr(&mut self) -> Result { 648 | self.parse_or() 649 | } 650 | 651 | /// Parses an `or` expression. Precedence 8. 652 | fn parse_or(&mut self) -> Result { 653 | let mut exp_desc = self.parse_and()?; 654 | 655 | while self.input.try_pop(TokenType::Or)?.is_some() { 656 | exp_desc = ExpDesc::Other; 657 | let branch_instr_index = self.chunk.code.len(); 658 | self.push(Instr::BranchTrueKeep(0)); 659 | // If we don't short-circuit, pop the left-hand expression 660 | self.push(Instr::Pop); 661 | self.parse_and()?; 662 | let branch_offset = (self.chunk.code.len() - branch_instr_index - 1) as isize; 663 | self.chunk.code[branch_instr_index] = Instr::BranchTrueKeep(branch_offset); 664 | } 665 | 666 | Ok(exp_desc) 667 | } 668 | 669 | /// Parses `and` expression. Precedence 7. 670 | fn parse_and(&mut self) -> Result { 671 | let mut exp_desc = self.parse_comparison()?; 672 | 673 | while self.input.try_pop(TokenType::And)?.is_some() { 674 | exp_desc = ExpDesc::Other; 675 | let branch_instr_index = self.chunk.code.len(); 676 | self.push(Instr::BranchFalseKeep(0)); 677 | // If we don't short-circuit, pop the left-hand expression 678 | self.push(Instr::Pop); 679 | self.parse_comparison()?; 680 | let branch_offset = (self.chunk.code.len() - branch_instr_index - 1) as isize; 681 | self.chunk.code[branch_instr_index] = Instr::BranchFalseKeep(branch_offset); 682 | } 683 | 684 | Ok(exp_desc) 685 | } 686 | 687 | /// Parses a comparison expression. Precedence 6. 688 | /// 689 | /// `==`, `~=`, `<`, `<=`, `>`, `>=` 690 | fn parse_comparison(&mut self) -> Result { 691 | let mut exp_desc = self.parse_concat()?; 692 | loop { 693 | let instr = match self.input.peek_type()? { 694 | TokenType::Less => Instr::Less, 695 | TokenType::LessEqual => Instr::LessEqual, 696 | TokenType::Greater => Instr::Greater, 697 | TokenType::GreaterEqual => Instr::GreaterEqual, 698 | TokenType::Equal => Instr::Equal, 699 | TokenType::NotEqual => Instr::NotEqual, 700 | _ => break, 701 | }; 702 | exp_desc = ExpDesc::Other; 703 | self.input.next()?; 704 | self.parse_concat()?; 705 | self.push(instr); 706 | } 707 | Ok(exp_desc) 708 | } 709 | 710 | /// Parses a string concatenation expression (`..`). Precedence 5. 711 | fn parse_concat(&mut self) -> Result { 712 | let mut exp_desc = self.parse_addition()?; 713 | if self.input.try_pop(TokenType::DotDot)?.is_some() { 714 | exp_desc = ExpDesc::Other; 715 | self.parse_concat()?; 716 | self.push(Instr::Concat); 717 | } 718 | 719 | Ok(exp_desc) 720 | } 721 | 722 | /// Parses an addition expression (`+`, `-`). Precedence 4. 723 | fn parse_addition(&mut self) -> Result { 724 | let mut exp_desc = self.parse_multiplication()?; 725 | loop { 726 | let instr = match self.input.peek_type()? { 727 | TokenType::Plus => Instr::Add, 728 | TokenType::Minus => Instr::Subtract, 729 | _ => break, 730 | }; 731 | exp_desc = ExpDesc::Other; 732 | self.input.next()?; 733 | self.parse_multiplication()?; 734 | self.push(instr); 735 | } 736 | Ok(exp_desc) 737 | } 738 | 739 | /// Parses a multiplication expression (`*`, `/`, `%`). Precedence 3. 740 | fn parse_multiplication(&mut self) -> Result { 741 | let mut exp_desc = self.parse_unary()?; 742 | loop { 743 | let instr = match self.input.peek_type()? { 744 | TokenType::Star => Instr::Multiply, 745 | TokenType::Slash => Instr::Divide, 746 | TokenType::Mod => Instr::Mod, 747 | _ => break, 748 | }; 749 | exp_desc = ExpDesc::Other; 750 | self.input.next()?; 751 | self.parse_unary()?; 752 | self.push(instr); 753 | } 754 | Ok(exp_desc) 755 | } 756 | 757 | /// Parses a unary expression (`not`, `#`, `-`). Precedence 2. 758 | fn parse_unary(&mut self) -> Result { 759 | let instr = match self.input.peek_type()? { 760 | TokenType::Not => Instr::Not, 761 | TokenType::Hash => Instr::Length, 762 | TokenType::Minus => Instr::Negate, 763 | _ => { 764 | return self.parse_pow(); 765 | } 766 | }; 767 | self.input.next()?; 768 | self.parse_unary()?; 769 | self.push(instr); 770 | 771 | Ok(ExpDesc::Other) 772 | } 773 | 774 | /// Parse an exponentiation expression (`^`). Right-associative, Precedence 1. 775 | fn parse_pow(&mut self) -> Result { 776 | let mut exp_desc = self.parse_primary()?; 777 | if self.input.try_pop(TokenType::Caret)?.is_some() { 778 | exp_desc = ExpDesc::Other; 779 | self.parse_unary()?; 780 | self.push(Instr::Pow); 781 | } 782 | 783 | Ok(exp_desc) 784 | } 785 | 786 | /// Parses a 'primary' expression. See `parse_prefix_exp` and `parse_expr_base` for details. 787 | fn parse_primary(&mut self) -> Result { 788 | match self.input.peek_type()? { 789 | TokenType::Identifier | TokenType::LParen | TokenType::LParenLineStart => { 790 | let prefix = self.parse_prefix_exp()?; 791 | self.eval_prefix_exp(&prefix); 792 | Ok(prefix.into()) 793 | } 794 | _ => self.parse_expr_base(), 795 | } 796 | } 797 | 798 | /// Parses a `prefix expression`. Prefix expressions are the expressions 799 | /// which can appear on the left side of a function call, table index, or 800 | /// field access. 801 | fn parse_prefix_exp(&mut self) -> Result { 802 | let tok = self.input.next()?; 803 | let prefix = match tok.typ { 804 | TokenType::Identifier => { 805 | let text = self.get_text(tok); 806 | let place = self.parse_prefix_identifier(text)?; 807 | place.into() 808 | } 809 | TokenType::LParen | TokenType::LParenLineStart => { 810 | self.parse_expr()?; 811 | self.expect(TokenType::RParen)?; 812 | PrefixExp::Parenthesized 813 | } 814 | _ => { 815 | return Err(self.err_unexpected(tok, TokenType::Identifier)); 816 | } 817 | }; 818 | self.parse_prefix_extension(prefix) 819 | } 820 | 821 | /// Attempts to parse an extension to a prefix expression: a field access, 822 | /// table index, or function/method call. 823 | fn parse_prefix_extension(&mut self, base_expr: PrefixExp) -> Result { 824 | match self.input.peek_type()? { 825 | TokenType::Dot => { 826 | self.eval_prefix_exp(&base_expr); 827 | self.input.next()?; 828 | let field_name = self.expect_identifier()?; 829 | let name_idx = self.find_or_add_string(field_name)?; 830 | let prefix = PlaceExp::FieldAccess(name_idx).into(); 831 | self.parse_prefix_extension(prefix) 832 | } 833 | TokenType::LSquare => { 834 | self.eval_prefix_exp(&base_expr); 835 | self.input.next()?; 836 | self.parse_expr()?; 837 | self.expect(TokenType::RSquare)?; 838 | let prefix = PlaceExp::TableIndex.into(); 839 | self.parse_prefix_extension(prefix) 840 | } 841 | TokenType::LParen => { 842 | self.eval_prefix_exp(&base_expr); 843 | self.input.next()?; 844 | let (num_args, _) = self.parse_call()?; 845 | let prefix = PrefixExp::FunctionCall(num_args); 846 | self.parse_prefix_extension(prefix) 847 | } 848 | TokenType::LParenLineStart => { 849 | let pos = self.input.next()?.start; 850 | Err(self.error_at(SyntaxError::LParenLineStart, pos)) 851 | } 852 | TokenType::Colon => panic!("Method calls unsupported"), 853 | TokenType::LiteralString | TokenType::LCurly => { 854 | panic!("Unparenthesized function calls unsupported") 855 | } 856 | _ => Ok(base_expr), 857 | } 858 | } 859 | 860 | /// Parses a 'base' expression, after eliminating any operators. This can be: 861 | /// * A literal number 862 | /// * A literal string 863 | /// * A function definition 864 | /// * One of the keywords `nil`, `false` or `true 865 | /// * A table constructor 866 | fn parse_expr_base(&mut self) -> Result { 867 | let tok = self.input.next()?; 868 | match tok.typ { 869 | TokenType::LCurly => self.parse_table()?, 870 | TokenType::LiteralNumber => { 871 | let text = self.get_text(tok); 872 | let number = text.parse().unwrap(); 873 | let idx = self.find_or_add_number(number)?; 874 | self.push(Instr::PushNum(idx)); 875 | } 876 | TokenType::LiteralHexNumber => { 877 | // Cut off the "0x" 878 | let text = &self.get_text(tok)[2..]; 879 | let number = u128::from_str_radix(text, 16).unwrap() as f64; 880 | let idx = self.find_or_add_number(number)?; 881 | self.push(Instr::PushNum(idx)); 882 | } 883 | TokenType::LiteralString => { 884 | let text = self.get_literal_string_contents(tok); 885 | let idx = self.find_or_add_string(text)?; 886 | self.push(Instr::PushString(idx)); 887 | } 888 | TokenType::Function => self.parse_fndef()?, 889 | TokenType::Nil => self.push(Instr::PushNil), 890 | TokenType::False => self.push(Instr::PushBool(false)), 891 | TokenType::True => self.push(Instr::PushBool(true)), 892 | TokenType::DotDotDot => { 893 | return Err(self.error(ErrorKind::UnsupportedFeature)); 894 | } 895 | _ => { 896 | return Err(self.err_unexpected(tok, TokenType::Nil)); 897 | } 898 | } 899 | Ok(ExpDesc::Other) 900 | } 901 | 902 | /// Parses the parameters in a function definition. 903 | fn parse_params(&mut self) -> Result> { 904 | let lparen_tok = self.input.next()?; 905 | match lparen_tok.typ { 906 | TokenType::LParen | TokenType::LParenLineStart => (), 907 | _ => return Err(self.err_unexpected(lparen_tok, TokenType::LParen)), 908 | } 909 | let mut args = Vec::new(); 910 | if self.input.try_pop(TokenType::RParen)?.is_some() { 911 | return Ok(args); 912 | } 913 | args.push(self.expect_identifier()?); 914 | while self.input.try_pop(TokenType::Comma)?.is_some() { 915 | args.push(self.expect_identifier()?); 916 | } 917 | self.expect(TokenType::RParen)?; 918 | Ok(args) 919 | } 920 | 921 | /// Parses the parameters and body of a function definition. 922 | fn parse_fndef(&mut self) -> Result<()> { 923 | let params = self.parse_params()?; 924 | if self.chunk.nested.len() >= u8::MAX as usize { 925 | return Err(self.error(SyntaxError::Complexity)); 926 | } 927 | 928 | self.nest_level += 1; 929 | let new_chunk = self.parse_chunk(¶ms)?; 930 | self.level_down(); 931 | 932 | self.chunk.nested.push(new_chunk); 933 | self.push(Instr::Closure(self.chunk.nested.len() as u8 - 1)); 934 | self.expect(TokenType::End)?; 935 | Ok(()) 936 | } 937 | 938 | /// Parses a table constructor. 939 | fn parse_table(&mut self) -> Result<()> { 940 | self.push(Instr::NewTable); 941 | if self.input.try_pop(TokenType::RCurly)?.is_none() { 942 | // i is the number of array-style entries. 943 | let mut i = 0; 944 | i = self.parse_table_entry(i)?; 945 | while let TokenType::Comma | TokenType::Semi = self.input.peek_type()? { 946 | self.input.next()?; 947 | if self.input.check_type(TokenType::RCurly)? { 948 | break; 949 | } else { 950 | i = self.parse_table_entry(i)?; 951 | } 952 | } 953 | self.expect(TokenType::RCurly)?; 954 | 955 | if i > 0 { 956 | self.push(Instr::SetList(i)); 957 | } 958 | } 959 | Ok(()) 960 | } 961 | 962 | /// Parses a table entry. 963 | fn parse_table_entry(&mut self, counter: u8) -> Result { 964 | match self.input.peek_type()? { 965 | TokenType::Identifier => { 966 | let index = self.expect_identifier_id().unwrap(); 967 | self.expect(TokenType::Assign)?; 968 | self.parse_expr()?; 969 | self.push(Instr::InitField(counter, index)); 970 | Ok(counter) 971 | } 972 | TokenType::LSquare => { 973 | self.input.next().unwrap(); 974 | self.parse_expr()?; 975 | self.expect(TokenType::RSquare)?; 976 | self.expect(TokenType::Assign)?; 977 | self.parse_expr()?; 978 | self.push(Instr::InitIndex(counter)); 979 | Ok(counter) 980 | } 981 | _ => { 982 | if counter == u8::MAX { 983 | return Err(self.error(SyntaxError::Complexity)); 984 | } 985 | self.parse_expr()?; 986 | Ok(counter + 1) 987 | } 988 | } 989 | } 990 | 991 | /// Parses a function call. Returns the number of arguments. 992 | fn parse_call(&mut self) -> Result<(u8, ExpDesc)> { 993 | let tup = if self.input.check_type(TokenType::RParen)? { 994 | (0, ExpDesc::Other) 995 | } else { 996 | self.parse_explist()? 997 | }; 998 | self.expect(TokenType::RParen)?; 999 | Ok(tup) 1000 | } 1001 | } 1002 | 1003 | /// Finds the index of the last local entry which matches `name`. 1004 | #[must_use] 1005 | fn find_last_local(locals: &[(String, i32)], name: &str) -> Option { 1006 | let mut i = locals.len(); 1007 | while i > 0 { 1008 | i -= 1; 1009 | if locals[i].0 == name { 1010 | return Some(i); 1011 | } 1012 | } 1013 | 1014 | None 1015 | } 1016 | 1017 | /// Returns the index of an entry in the literals list, adding it if it does not exist. 1018 | fn find_or_add(queue: &mut Vec, x: &E) -> Option 1019 | where 1020 | T: Borrow + PartialEq, 1021 | E: PartialEq + ToOwned + ?Sized, 1022 | { 1023 | match queue.iter().position(|y| y == x) { 1024 | Some(i) => Some(i as u8), 1025 | None => { 1026 | let i = queue.len(); 1027 | if i == u8::MAX as usize { 1028 | None 1029 | } else { 1030 | queue.push(x.to_owned()); 1031 | Some(i as u8) 1032 | } 1033 | } 1034 | } 1035 | } 1036 | 1037 | #[cfg(test)] 1038 | mod tests { 1039 | use super::parse_str; 1040 | use super::Chunk; 1041 | use super::Instr::{self, *}; 1042 | 1043 | fn check_it(input: &str, output: Chunk) { 1044 | assert_eq!(parse_str(input).unwrap(), output); 1045 | } 1046 | 1047 | #[test] 1048 | fn test01() { 1049 | let text = "x = 5 + 6"; 1050 | let out = Chunk { 1051 | code: vec![PushNum(0), PushNum(1), Add, SetGlobal(0), Return(0)], 1052 | number_literals: vec![5.0, 6.0], 1053 | string_literals: vec!["x".into()], 1054 | ..Chunk::default() 1055 | }; 1056 | check_it(text, out); 1057 | } 1058 | 1059 | #[test] 1060 | fn test02() { 1061 | let text = "x = -5^2"; 1062 | let out = Chunk { 1063 | code: vec![PushNum(0), PushNum(1), Pow, Negate, SetGlobal(0), Return(0)], 1064 | number_literals: vec![5.0, 2.0], 1065 | string_literals: vec!["x".into()], 1066 | ..Chunk::default() 1067 | }; 1068 | check_it(text, out); 1069 | } 1070 | 1071 | #[test] 1072 | fn test03() { 1073 | let text = "x = 5 + true .. 'hi'"; 1074 | let out = Chunk { 1075 | code: vec![ 1076 | PushNum(0), 1077 | PushBool(true), 1078 | Add, 1079 | PushString(1), 1080 | Concat, 1081 | SetGlobal(0), 1082 | Return(0), 1083 | ], 1084 | number_literals: vec![5.0], 1085 | string_literals: vec!["x".into(), "hi".into()], 1086 | ..Chunk::default() 1087 | }; 1088 | check_it(text, out); 1089 | } 1090 | 1091 | #[test] 1092 | fn test04() { 1093 | let text = "x = 1 .. 2 + 3"; 1094 | let output = Chunk { 1095 | code: vec![ 1096 | PushNum(0), 1097 | PushNum(1), 1098 | PushNum(2), 1099 | Add, 1100 | Concat, 1101 | SetGlobal(0), 1102 | Return(0), 1103 | ], 1104 | number_literals: vec![1.0, 2.0, 3.0], 1105 | string_literals: vec!["x".into()], 1106 | ..Chunk::default() 1107 | }; 1108 | check_it(text, output); 1109 | } 1110 | 1111 | #[test] 1112 | fn test05() { 1113 | let text = "x = 2^-3"; 1114 | let output = Chunk { 1115 | code: vec![PushNum(0), PushNum(1), Negate, Pow, SetGlobal(0), Return(0)], 1116 | number_literals: vec![2.0, 3.0], 1117 | string_literals: vec!["x".into()], 1118 | ..Chunk::default() 1119 | }; 1120 | check_it(text, output); 1121 | } 1122 | 1123 | #[test] 1124 | fn test06() { 1125 | let text = "x= not not 1"; 1126 | let output = Chunk { 1127 | code: vec![PushNum(0), Instr::Not, Instr::Not, SetGlobal(0), Return(0)], 1128 | number_literals: vec![1.0], 1129 | string_literals: vec!["x".into()], 1130 | ..Chunk::default() 1131 | }; 1132 | check_it(text, output); 1133 | } 1134 | 1135 | #[test] 1136 | fn test07() { 1137 | let text = "a = 5"; 1138 | let output = Chunk { 1139 | code: vec![PushNum(0), SetGlobal(0), Return(0)], 1140 | number_literals: vec![5.0], 1141 | string_literals: vec!["a".to_string()], 1142 | ..Chunk::default() 1143 | }; 1144 | check_it(text, output); 1145 | } 1146 | 1147 | #[test] 1148 | fn test08() { 1149 | let text = "x = true and false"; 1150 | let output = Chunk { 1151 | code: vec![ 1152 | PushBool(true), 1153 | BranchFalseKeep(2), 1154 | Pop, 1155 | PushBool(false), 1156 | SetGlobal(0), 1157 | Return(0), 1158 | ], 1159 | string_literals: vec!["x".into()], 1160 | ..Chunk::default() 1161 | }; 1162 | check_it(text, output); 1163 | } 1164 | 1165 | #[test] 1166 | fn test09() { 1167 | let text = "x = 5 or nil and true"; 1168 | let code = vec![ 1169 | PushNum(0), 1170 | BranchTrueKeep(5), 1171 | Pop, 1172 | PushNil, 1173 | BranchFalseKeep(2), 1174 | Pop, 1175 | PushBool(true), 1176 | SetGlobal(0), 1177 | Return(0), 1178 | ]; 1179 | let output = Chunk { 1180 | code, 1181 | number_literals: vec![5.0], 1182 | string_literals: vec!["x".into()], 1183 | ..Chunk::default() 1184 | }; 1185 | check_it(text, output); 1186 | } 1187 | 1188 | #[test] 1189 | fn test10() { 1190 | let text = "if true then a = 5 end"; 1191 | let code = vec![ 1192 | PushBool(true), 1193 | BranchFalse(2), 1194 | PushNum(0), 1195 | SetGlobal(0), 1196 | Return(0), 1197 | ]; 1198 | let chunk = Chunk { 1199 | code, 1200 | number_literals: vec![5.0], 1201 | string_literals: vec!["a".to_string()], 1202 | ..Chunk::default() 1203 | }; 1204 | check_it(text, chunk); 1205 | } 1206 | 1207 | #[test] 1208 | fn test11() { 1209 | let text = "if true then a = 5 if true then b = 4 end end"; 1210 | let code = vec![ 1211 | PushBool(true), 1212 | BranchFalse(6), 1213 | PushNum(0), 1214 | SetGlobal(0), 1215 | PushBool(true), 1216 | BranchFalse(2), 1217 | PushNum(1), 1218 | SetGlobal(1), 1219 | Return(0), 1220 | ]; 1221 | let chunk = Chunk { 1222 | code, 1223 | number_literals: vec![5.0, 4.0], 1224 | string_literals: vec!["a".to_string(), "b".to_string()], 1225 | ..Chunk::default() 1226 | }; 1227 | check_it(text, chunk); 1228 | } 1229 | 1230 | #[test] 1231 | fn test12() { 1232 | let text = "if true then a = 5 else a = 4 end"; 1233 | let code = vec![ 1234 | PushBool(true), 1235 | BranchFalse(3), 1236 | PushNum(0), 1237 | SetGlobal(0), 1238 | Jump(2), 1239 | PushNum(1), 1240 | SetGlobal(0), 1241 | Return(0), 1242 | ]; 1243 | let chunk = Chunk { 1244 | code, 1245 | number_literals: vec![5.0, 4.0], 1246 | string_literals: vec!["a".to_string()], 1247 | ..Chunk::default() 1248 | }; 1249 | check_it(text, chunk); 1250 | } 1251 | 1252 | #[test] 1253 | fn test13() { 1254 | let text = "if true then a = 5 elseif 6 == 7 then a = 3 else a = 4 end"; 1255 | let code = vec![ 1256 | PushBool(true), 1257 | BranchFalse(3), 1258 | PushNum(0), 1259 | SetGlobal(0), 1260 | Jump(9), 1261 | PushNum(1), 1262 | PushNum(2), 1263 | Instr::Equal, 1264 | BranchFalse(3), 1265 | PushNum(3), 1266 | SetGlobal(0), 1267 | Jump(2), 1268 | PushNum(4), 1269 | SetGlobal(0), 1270 | Return(0), 1271 | ]; 1272 | let chunk = Chunk { 1273 | code, 1274 | number_literals: vec![5.0, 6.0, 7.0, 3.0, 4.0], 1275 | string_literals: vec!["a".to_string()], 1276 | ..Chunk::default() 1277 | }; 1278 | check_it(text, chunk); 1279 | } 1280 | 1281 | #[test] 1282 | fn test14() { 1283 | let text = "while a < 10 do a = a + 1 end"; 1284 | let code = vec![ 1285 | GetGlobal(0), 1286 | PushNum(0), 1287 | Instr::Less, 1288 | BranchFalse(5), 1289 | GetGlobal(0), 1290 | PushNum(1), 1291 | Add, 1292 | SetGlobal(0), 1293 | Jump(-9), 1294 | Return(0), 1295 | ]; 1296 | let chunk = Chunk { 1297 | code, 1298 | number_literals: vec![10.0, 1.0], 1299 | string_literals: vec!["a".to_string()], 1300 | ..Chunk::default() 1301 | }; 1302 | check_it(text, chunk); 1303 | } 1304 | 1305 | #[test] 1306 | fn test15() { 1307 | let text = "repeat local x = 5 until a == b y = 4"; 1308 | let code = vec![ 1309 | PushNum(0), 1310 | SetLocal(0), 1311 | GetGlobal(0), 1312 | GetGlobal(1), 1313 | Instr::Equal, 1314 | BranchFalse(-6), 1315 | PushNum(1), 1316 | SetGlobal(2), 1317 | Return(0), 1318 | ]; 1319 | let chunk = Chunk { 1320 | code, 1321 | number_literals: vec![5.0, 4.0], 1322 | string_literals: vec!["a".into(), "b".into(), "y".into()], 1323 | num_locals: 1, 1324 | ..Chunk::default() 1325 | }; 1326 | check_it(text, chunk); 1327 | } 1328 | 1329 | #[test] 1330 | fn test16() { 1331 | let text = "local i i = 2"; 1332 | let code = vec![PushNil, SetLocal(0), PushNum(0), SetLocal(0), Return(0)]; 1333 | let chunk = Chunk { 1334 | code, 1335 | number_literals: vec![2.0], 1336 | num_locals: 1, 1337 | ..Chunk::default() 1338 | }; 1339 | check_it(text, chunk); 1340 | } 1341 | 1342 | #[test] 1343 | fn test17() { 1344 | let text = "local i, j print(j)"; 1345 | let code = vec![ 1346 | PushNil, 1347 | PushNil, 1348 | SetLocal(1), 1349 | SetLocal(0), 1350 | GetGlobal(0), 1351 | GetLocal(1), 1352 | Call(1, 0), 1353 | Return(0), 1354 | ]; 1355 | let chunk = Chunk { 1356 | code, 1357 | string_literals: vec!["print".into()], 1358 | num_locals: 2, 1359 | ..Chunk::default() 1360 | }; 1361 | check_it(text, chunk); 1362 | } 1363 | 1364 | #[test] 1365 | fn test18() { 1366 | let text = "local i do local i x = i end x = i"; 1367 | let code = vec![ 1368 | PushNil, 1369 | SetLocal(0), 1370 | PushNil, 1371 | SetLocal(1), 1372 | GetLocal(1), 1373 | SetGlobal(0), 1374 | GetLocal(0), 1375 | SetGlobal(0), 1376 | Return(0), 1377 | ]; 1378 | let chunk = Chunk { 1379 | code, 1380 | string_literals: vec!["x".into()], 1381 | num_locals: 2, 1382 | ..Chunk::default() 1383 | }; 1384 | check_it(text, chunk); 1385 | } 1386 | 1387 | #[test] 1388 | fn test19() { 1389 | let text = "do local i x = i end x = i"; 1390 | let code = vec![ 1391 | PushNil, 1392 | SetLocal(0), 1393 | GetLocal(0), 1394 | SetGlobal(0), 1395 | GetGlobal(1), 1396 | SetGlobal(0), 1397 | Return(0), 1398 | ]; 1399 | let chunk = Chunk { 1400 | code, 1401 | string_literals: vec!["x".into(), "i".into()], 1402 | num_locals: 1, 1403 | ..Chunk::default() 1404 | }; 1405 | check_it(text, chunk); 1406 | } 1407 | 1408 | #[test] 1409 | fn test20() { 1410 | let text = "local i if false then local i else x = i end"; 1411 | let code = vec![ 1412 | PushNil, 1413 | SetLocal(0), 1414 | PushBool(false), 1415 | BranchFalse(3), 1416 | PushNil, 1417 | SetLocal(1), 1418 | Jump(2), 1419 | GetLocal(0), 1420 | SetGlobal(0), 1421 | Return(0), 1422 | ]; 1423 | let chunk = Chunk { 1424 | code, 1425 | string_literals: vec!["x".into()], 1426 | num_locals: 2, 1427 | ..Chunk::default() 1428 | }; 1429 | check_it(text, chunk); 1430 | } 1431 | 1432 | #[test] 1433 | fn test21() { 1434 | let text = "for i = 1,5 do x = i end"; 1435 | let code = vec![ 1436 | PushNum(0), 1437 | PushNum(1), 1438 | PushNum(0), 1439 | ForPrep(0, 3), 1440 | GetLocal(3), 1441 | SetGlobal(0), 1442 | ForLoop(0, -3), 1443 | Return(0), 1444 | ]; 1445 | let chunk = Chunk { 1446 | code, 1447 | number_literals: vec![1.0, 5.0], 1448 | string_literals: vec!["x".into()], 1449 | num_locals: 4, 1450 | ..Chunk::default() 1451 | }; 1452 | check_it(text, chunk); 1453 | } 1454 | 1455 | #[test] 1456 | fn test22() { 1457 | let text = "a, b = 1"; 1458 | let code = vec![PushNum(0), PushNil, SetGlobal(1), SetGlobal(0), Return(0)]; 1459 | let chunk = Chunk { 1460 | code, 1461 | number_literals: vec![1.0], 1462 | string_literals: vec!["a".to_string(), "b".to_string()], 1463 | ..Chunk::default() 1464 | }; 1465 | check_it(text, chunk); 1466 | } 1467 | 1468 | #[test] 1469 | fn test23() { 1470 | let text = "a, b = 1, 2"; 1471 | let code = vec![ 1472 | PushNum(0), 1473 | PushNum(1), 1474 | SetGlobal(1), 1475 | SetGlobal(0), 1476 | Return(0), 1477 | ]; 1478 | let chunk = Chunk { 1479 | code, 1480 | number_literals: vec![1.0, 2.0], 1481 | string_literals: vec!["a".to_string(), "b".to_string()], 1482 | ..Chunk::default() 1483 | }; 1484 | check_it(text, chunk); 1485 | } 1486 | 1487 | #[test] 1488 | fn test24() { 1489 | let text = "a, b = 1, 2, 3"; 1490 | let code = vec![ 1491 | PushNum(0), 1492 | PushNum(1), 1493 | PushNum(2), 1494 | Pop, 1495 | SetGlobal(1), 1496 | SetGlobal(0), 1497 | Return(0), 1498 | ]; 1499 | let chunk = Chunk { 1500 | code, 1501 | number_literals: vec![1.0, 2.0, 3.0], 1502 | string_literals: vec!["a".to_string(), "b".to_string()], 1503 | ..Chunk::default() 1504 | }; 1505 | check_it(text, chunk); 1506 | } 1507 | 1508 | #[test] 1509 | fn test25() { 1510 | let text = "puts()"; 1511 | let code = vec![GetGlobal(0), Call(0, 0), Return(0)]; 1512 | let chunk = Chunk { 1513 | code, 1514 | string_literals: vec!["puts".to_string()], 1515 | ..Chunk::default() 1516 | }; 1517 | check_it(text, chunk); 1518 | } 1519 | 1520 | #[test] 1521 | fn test26() { 1522 | let text = "y = {x = 5,}"; 1523 | let code = vec![ 1524 | NewTable, 1525 | PushNum(0), 1526 | InitField(0, 1), 1527 | SetGlobal(0), 1528 | Return(0), 1529 | ]; 1530 | let chunk = Chunk { 1531 | code, 1532 | number_literals: vec![5.0], 1533 | string_literals: vec!["y".into(), "x".into()], 1534 | ..Chunk::default() 1535 | }; 1536 | check_it(text, chunk); 1537 | } 1538 | 1539 | #[test] 1540 | fn test27() { 1541 | let text = "local x = t.x.y"; 1542 | let code = vec![ 1543 | GetGlobal(0), 1544 | GetField(1), 1545 | GetField(2), 1546 | SetLocal(0), 1547 | Return(0), 1548 | ]; 1549 | let chunk = Chunk { 1550 | code, 1551 | string_literals: vec!["t".to_string(), "x".to_string(), "y".to_string()], 1552 | num_locals: 1, 1553 | ..Chunk::default() 1554 | }; 1555 | check_it(text, chunk); 1556 | } 1557 | 1558 | #[test] 1559 | fn test28() { 1560 | let text = "x = function () end"; 1561 | let code = vec![Closure(0), SetGlobal(0), Return(0)]; 1562 | let string_literals = vec!["x".into()]; 1563 | let nested = vec![Chunk { 1564 | code: vec![Return(0)], 1565 | ..Chunk::default() 1566 | }]; 1567 | let chunk = Chunk { 1568 | code, 1569 | string_literals, 1570 | nested, 1571 | ..Chunk::default() 1572 | }; 1573 | check_it(text, chunk); 1574 | } 1575 | 1576 | #[test] 1577 | fn test29() { 1578 | let default = Chunk::default(); 1579 | let text = "x = function () local y = 7 end"; 1580 | let inner_chunk = Chunk { 1581 | code: vec![PushNum(0), SetLocal(0), Return(0)], 1582 | number_literals: vec![7.0], 1583 | num_locals: 1, 1584 | ..default 1585 | }; 1586 | let outer_chunk = Chunk { 1587 | code: vec![Closure(0), SetGlobal(0), Return(0)], 1588 | string_literals: vec!["x".into()], 1589 | nested: vec![inner_chunk], 1590 | ..default 1591 | }; 1592 | check_it(text, outer_chunk); 1593 | } 1594 | 1595 | #[test] 1596 | fn test30() { 1597 | let text = " 1598 | z = function () local z = 21 end 1599 | x = function () 1600 | local y = function () end 1601 | print(y) 1602 | end"; 1603 | let z = Chunk { 1604 | code: vec![PushNum(0), SetLocal(0), Return(0)], 1605 | number_literals: vec![21.0], 1606 | num_locals: 1, 1607 | ..Chunk::default() 1608 | }; 1609 | let y = Chunk { 1610 | code: vec![Return(0)], 1611 | ..Chunk::default() 1612 | }; 1613 | let x = Chunk { 1614 | code: vec![ 1615 | Closure(0), 1616 | SetLocal(0), 1617 | GetGlobal(0), 1618 | GetLocal(0), 1619 | Call(1, 0), 1620 | Return(0), 1621 | ], 1622 | string_literals: vec!["print".into()], 1623 | nested: vec![y], 1624 | num_locals: 1, 1625 | ..Chunk::default() 1626 | }; 1627 | let outer_chunk = Chunk { 1628 | code: vec![ 1629 | Closure(0), 1630 | SetGlobal(0), 1631 | Closure(1), 1632 | SetGlobal(1), 1633 | Return(0), 1634 | ], 1635 | nested: vec![z, x], 1636 | string_literals: vec!["z".into(), "x".into()], 1637 | ..Chunk::default() 1638 | }; 1639 | check_it(text, outer_chunk); 1640 | } 1641 | 1642 | #[test] 1643 | fn test31() { 1644 | let text = "local s = type(4)"; 1645 | let code = vec![GetGlobal(0), PushNum(0), Call(1, 1), SetLocal(0), Return(0)]; 1646 | let chunk = Chunk { 1647 | code, 1648 | num_locals: 1, 1649 | number_literals: vec![4.0], 1650 | string_literals: vec!["type".into()], 1651 | ..Chunk::default() 1652 | }; 1653 | check_it(text, chunk); 1654 | } 1655 | 1656 | #[test] 1657 | fn test32() { 1658 | let text = "local type, print print(type(nil))"; 1659 | let code = vec![ 1660 | PushNil, 1661 | PushNil, 1662 | SetLocal(1), 1663 | SetLocal(0), 1664 | GetLocal(1), 1665 | GetLocal(0), 1666 | PushNil, 1667 | Call(1, 1), 1668 | Call(1, 0), 1669 | Return(0), 1670 | ]; 1671 | let chunk = Chunk { 1672 | code, 1673 | num_locals: 2, 1674 | ..Chunk::default() 1675 | }; 1676 | check_it(text, chunk); 1677 | } 1678 | 1679 | #[test] 1680 | fn test33() { 1681 | use super::*; 1682 | let text = "print()\n(foo)()\n"; 1683 | match parse_str(text) { 1684 | Err(Error { 1685 | kind: ErrorKind::SyntaxError(SyntaxError::LParenLineStart), 1686 | line_num, 1687 | column, 1688 | }) => { 1689 | assert_eq!(line_num, 2); 1690 | assert_eq!(column, 1); 1691 | } 1692 | _ => panic!("Should detect ambiguous function call because of linebreak"), 1693 | } 1694 | } 1695 | 1696 | #[test] 1697 | fn test34() { 1698 | let text = "while false do local b end b()"; 1699 | let code = vec![ 1700 | PushBool(false), 1701 | BranchFalse(3), 1702 | PushNil, 1703 | SetLocal(0), 1704 | Jump(-5), 1705 | GetGlobal(0), 1706 | Call(0, 0), 1707 | Return(0), 1708 | ]; 1709 | let chunk = Chunk { 1710 | code, 1711 | num_locals: 1, 1712 | string_literals: vec!["b".to_string()], 1713 | ..Chunk::default() 1714 | }; 1715 | check_it(text, chunk); 1716 | } 1717 | } 1718 | -------------------------------------------------------------------------------- /src/compiler/token.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | /// A lexical unit in the source code. 4 | #[derive(Debug, PartialEq)] 5 | pub(super) struct Token { 6 | pub(super) typ: TokenType, 7 | pub(super) start: usize, 8 | pub(super) len: u32, 9 | } 10 | 11 | #[rustfmt::skip] 12 | #[derive(Clone, Copy, Debug, PartialEq)] 13 | pub(super) enum TokenType { 14 | // Keywords 15 | And, Break, Do, Else, ElseIf, End, False, For, Function, If, In, Local, 16 | Nil, Not, Or, Repeat, Return, Then, True, Until, While, 17 | // Operator symbols 18 | Plus, Minus, Star, Slash, Mod, Caret, Hash, 19 | // Comparisons 20 | Equal, NotEqual, LessEqual, GreaterEqual, Less, Greater, 21 | // L/R stuff 22 | LParen, LParenLineStart, RParen, LCurly, RCurly, LSquare, RSquare, 23 | // Other symbols 24 | Semi, Colon, Comma, Dot, DotDot, DotDotDot, Assign, 25 | // Others 26 | Identifier, 27 | LiteralNumber, 28 | LiteralHexNumber, 29 | LiteralString, 30 | 31 | EndOfFile, 32 | } 33 | 34 | impl Token { 35 | pub(super) fn range(&self) -> Range { 36 | let start = self.start; 37 | let end = start + self.len as usize; 38 | start..end 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io; 3 | 4 | use crate::LuaType; 5 | 6 | // Types 7 | 8 | #[derive(Debug)] 9 | pub struct Error { 10 | pub kind: ErrorKind, 11 | pub line_num: usize, 12 | pub column: usize, 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum ErrorKind { 17 | AssertionFail, 18 | Io(io::Error), 19 | UnsupportedFeature, 20 | TypeError(TypeError), 21 | WithMessage(String), 22 | ArgError(ArgError), 23 | SyntaxError(SyntaxError), 24 | } 25 | 26 | #[derive(Debug)] 27 | pub struct ArgError { 28 | pub arg_number: isize, 29 | pub func_name: Option, 30 | pub expected: Option, 31 | pub received: Option, 32 | } 33 | 34 | #[derive(Debug)] 35 | pub enum SyntaxError { 36 | BadNumber, 37 | Complexity, 38 | InvalidCharacter(char), 39 | TooManyLocals, 40 | TooManyNumbers, 41 | TooManyStrings, 42 | UnclosedString, 43 | UnexpectedEof, 44 | UnexpectedTok, 45 | LParenLineStart, 46 | } 47 | 48 | #[derive(Debug)] 49 | pub enum TypeError { 50 | Arithmetic(LuaType), 51 | Comparison(LuaType, LuaType), 52 | Concat(LuaType), 53 | FunctionCall(LuaType), 54 | Length(LuaType), 55 | TableIndex(LuaType), 56 | TableKeyNan, 57 | TableKeyNil, 58 | } 59 | 60 | // main impls 61 | 62 | impl Error { 63 | pub fn new(kind: impl Into, line_num: usize, column: usize) -> Self { 64 | Error { 65 | kind: kind.into(), 66 | line_num, 67 | column, 68 | } 69 | } 70 | 71 | pub fn without_location(kind: ErrorKind) -> Self { 72 | Error::new(kind, 0, 0) 73 | } 74 | 75 | pub fn column(&self) -> usize { 76 | self.column 77 | } 78 | 79 | pub fn line_num(&self) -> usize { 80 | self.line_num 81 | } 82 | 83 | pub fn is_recoverable(&self) -> bool { 84 | self.kind.is_recoverable() 85 | } 86 | } 87 | 88 | impl ErrorKind { 89 | pub fn is_recoverable(&self) -> bool { 90 | if let Self::SyntaxError(e) = self { 91 | e.is_recoverable() 92 | } else { 93 | false 94 | } 95 | } 96 | } 97 | 98 | impl SyntaxError { 99 | /// Returns true if this is a SyntaxError that can be fixed by appending 100 | /// more text to the source code. 101 | pub fn is_recoverable(&self) -> bool { 102 | // matches!(self, Self::UnclosedString | Self::UnexpectedEof) 103 | matches!(self, Self::UnexpectedEof) 104 | } 105 | } 106 | 107 | // `Display` impls 108 | 109 | impl fmt::Display for Error { 110 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 111 | write!(f, "{}:{}: {}", self.line_num, self.column, self.kind) 112 | } 113 | } 114 | 115 | impl fmt::Display for ErrorKind { 116 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 117 | use ErrorKind::*; 118 | match self { 119 | AssertionFail => write!(f, "assertion failed!"), 120 | ArgError(e) => e.fmt(f), 121 | Io(e) => e.fmt(f), 122 | SyntaxError(e) => e.fmt(f), 123 | TypeError(e) => e.fmt(f), 124 | UnsupportedFeature => write!(f, "unsupported feature"), 125 | WithMessage(msg) => msg.fmt(f), 126 | } 127 | } 128 | } 129 | 130 | impl fmt::Display for ArgError { 131 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 132 | let func_name = match &self.func_name { 133 | Some(s) => s.as_str(), 134 | None => "?", 135 | }; 136 | let extra = match (&self.expected, &self.received) { 137 | (Some(expected), Some(got)) => format!("{} expected, got {}", expected, got), 138 | (Some(expected), None) => format!("{} expected, got no value", expected), 139 | (None, _) => "value expected".into(), 140 | }; 141 | 142 | write!( 143 | f, 144 | "bad argument #{} to {} ({})", 145 | self.arg_number, func_name, extra 146 | ) 147 | } 148 | } 149 | 150 | impl fmt::Display for SyntaxError { 151 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 152 | use SyntaxError::*; 153 | match self { 154 | BadNumber => write!(f, "malformed number"), 155 | Complexity => write!(f, "complexity"), 156 | InvalidCharacter(c) => write!(f, "invalid character {c}"), 157 | TooManyLocals => write!(f, "too many local variables"), 158 | TooManyNumbers => write!(f, "too many literal numbers"), 159 | TooManyStrings => write!(f, "too many literal strings"), 160 | UnclosedString => write!(f, "unfinished string"), 161 | UnexpectedEof => write!(f, "unexpected "), 162 | UnexpectedTok => write!(f, "syntax error"), 163 | LParenLineStart => write!(f, "ambiguous function call"), 164 | } 165 | } 166 | } 167 | 168 | impl fmt::Display for TypeError { 169 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 170 | use TypeError::*; 171 | match self { 172 | Arithmetic(typ) => write!(f, "attempt to perform arithmetic on a {} value", typ), 173 | Comparison(type1, type2) => write!(f, "attempt to compare {} with {}", type1, type2), 174 | Concat(typ) => write!(f, "attempt to concatenate a {} value", typ), 175 | FunctionCall(typ) => write!(f, "attempt to call a {} value", typ), 176 | Length(typ) => write!(f, "attempt to get length of a {} value", typ), 177 | TableIndex(typ) => write!(f, "attempt to index a {} value", typ), 178 | TableKeyNan => write!(f, "table index was NaN"), 179 | TableKeyNil => write!(f, "table index was nil"), 180 | } 181 | } 182 | } 183 | 184 | // `From` impls 185 | 186 | impl From for Error { 187 | fn from(io_err: io::Error) -> Self { 188 | Error::without_location(ErrorKind::Io(io_err)) 189 | } 190 | } 191 | 192 | impl From for ErrorKind { 193 | fn from(e: ArgError) -> Self { 194 | Self::ArgError(e) 195 | } 196 | } 197 | 198 | impl From for ErrorKind { 199 | fn from(e: SyntaxError) -> Self { 200 | Self::SyntaxError(e) 201 | } 202 | } 203 | 204 | impl From for ErrorKind { 205 | fn from(e: TypeError) -> Self { 206 | Self::TypeError(e) 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/instr.rs: -------------------------------------------------------------------------------- 1 | /// Instr is the instruction which can be read by the VM. 2 | /// 3 | /// Many of the variants use an `isize` parameter, as an offset for the VM to 4 | /// jump. 5 | /// 6 | /// Several others use a u8 parameter to index either the locals, the number 7 | /// literals, or the string literals. 8 | #[derive(Clone, Copy, Debug, PartialEq)] 9 | pub(super) enum Instr { 10 | /// Move the instruction pointer by the given offset. 11 | Jump(isize), 12 | 13 | /// Pop a value from the stack. If it's truthy, add the given offset to the 14 | /// instruction pointer. 15 | //BranchTrue(isize), 16 | 17 | /// Pop a value from the stack. If it's falsey, add the given offset to the 18 | /// instruction pointer. 19 | BranchFalse(isize), 20 | 21 | /// Examine, but do not pop a value from the stack. If it's truthy, add the 22 | /// given offset to the instruction pointer. 23 | BranchTrueKeep(isize), 24 | 25 | /// Examine, but do not pop a value from the stack. If it's falsey, add the 26 | /// given offset to the instruction pointer. 27 | BranchFalseKeep(isize), 28 | 29 | /// Pop a value from the stack and discard it. 30 | Pop, 31 | 32 | /// Use the param as an index into the string literal set. Using that 33 | /// string, index the global table and push onto the stack. 34 | GetGlobal(u8), 35 | 36 | /// Use the param as an index into the string literal set. Using that 37 | /// string as a key, pop a value from the stack and assign to the global 38 | /// table. 39 | SetGlobal(u8), 40 | 41 | /// Copy the given local to the top of the stack. 42 | GetLocal(u8), 43 | 44 | /// Pop the value at the top of the stack, and place it in the given local 45 | /// index. 46 | SetLocal(u8), 47 | 48 | /// Create a new table and place it on the stack. 49 | NewTable, 50 | 51 | /// Pop a table from the top of the stack, index it with the string literal 52 | /// with the given index, and push the value onto the stack. 53 | GetField(u8), 54 | 55 | /// Assign to a table. The key will be string literal `op1`. 56 | /// From the top, the stack should contain: 57 | /// * The new value, which will be popped 58 | /// * `op0` number of other values 59 | /// * The table, which will be removed 60 | SetField(u8, u8), 61 | 62 | /// Pop a value from the stack. Use `op1` as a string literal's id to get 63 | /// the key. The table will be `op0` positions from the top of the stack. 64 | /// Put the table back where it was afterwards. 65 | InitField(u8, u8), 66 | 67 | /// Pop a value then a key. The table will be `op0` positions from the top 68 | /// of the stack. Put the table back after the assignment. 69 | InitIndex(u8), 70 | 71 | /// Get a value from a table. 72 | GetTable, 73 | 74 | /// Assign to a table. 75 | /// From the top, the stack should contain: 76 | /// * The new value 77 | /// * The given number of other values, which will be ignored 78 | /// * The key 79 | /// * The table 80 | SetTable(u8), 81 | 82 | /// Push a `nil` value onto the stack. 83 | PushNil, 84 | 85 | /// Push the given boolean value onto the stack. 86 | PushBool(bool), 87 | 88 | /// Fetch the number (float) from the literal set at the given index. 89 | PushNum(u8), 90 | 91 | /// Fetch the string from the literal set at the given index. 92 | PushString(u8), 93 | 94 | /// Initializes a for loop, which will use the four local slots starting 95 | /// at `param0`. End the loop by jumping `param1` forward. 96 | ForPrep(u8, isize), 97 | 98 | /// End a for loop, using the locals starting at the first parameter to 99 | /// track its progress. If the loop isn't over, jump using the second 100 | /// parameter. 101 | ForLoop(u8, isize), 102 | 103 | /// Function call (number of arguments, number of needed return values). 104 | Call(u8, u8), 105 | 106 | /// Add the two values on the top of the stack. 107 | Add, 108 | 109 | /// Subtract the top value on the stack from the second value on the stack. 110 | Subtract, 111 | 112 | /// Multiply the two values on the top of the stack. 113 | Multiply, 114 | 115 | /// Divide the second value on the stack by the first. 116 | Divide, 117 | 118 | /// Raise the second value on the stack to the power of the first. 119 | Pow, 120 | 121 | /// Take the remainder, after dividing the second value on the stack by the 122 | /// first. 123 | Mod, 124 | 125 | /// Concatenate the two values on the top of the stack. 126 | Concat, 127 | 128 | /// `true` if the second value on the stack is less than the first; `false` 129 | /// otherwise. 130 | Less, 131 | 132 | /// `true` if the second value on the stack is less than or equal to the 133 | /// first; `false` otherwise. 134 | LessEqual, 135 | 136 | /// `true` if the second value on the stack is greater than the first; 137 | /// `false` otherwise. 138 | Greater, 139 | 140 | /// `true` if the second value on the stack is greater than or equal to the 141 | /// first; `false` otherwise. 142 | GreaterEqual, 143 | 144 | /// `true` if and only if the two values at the top of the stack are equal. 145 | Equal, 146 | 147 | /// `true` if and only if the two values at the top of the stack are not 148 | /// equal. 149 | NotEqual, 150 | 151 | /// `true` if the value at the top of the stack is `false` or `nil`, 152 | /// `false` otherwise. 153 | Not, 154 | 155 | /// Applies the length operator (`#`) to the value at the top of the stack. 156 | Length, 157 | 158 | /// Applies the unary negation operator to the value at the top of the 159 | /// stack. 160 | Negate, 161 | 162 | /// Return n values from the chunk. 163 | Return(u8), 164 | 165 | /// Create a closure from a Chunk and push it onto the stack. 166 | Closure(u8), 167 | 168 | /// Pop n values from the stack, then pop a table. Assign the last value 169 | /// popped to `table[1]`, the second-to-last value to `table[2]`, etc. 170 | /// Push the table back afterwards. 171 | SetList(u8), 172 | } 173 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! TODO: Crate-level description 2 | 3 | #![warn(future_incompatible)] 4 | #![warn(non_ascii_idents)] 5 | #![warn(rust_2018_idioms)] 6 | #![warn(single_use_lifetimes)] 7 | #![warn(trivial_casts)] 8 | #![warn(trivial_numeric_casts)] 9 | #![warn(unreachable_pub)] 10 | #![warn(unused)] 11 | #![warn(variant_size_differences)] 12 | 13 | mod compiler; 14 | mod instr; 15 | mod lua_std; 16 | mod vm; 17 | mod vm_aux; 18 | 19 | pub mod error; 20 | 21 | pub use vm::LuaType; 22 | pub use vm::RustFunc; 23 | pub use vm::State; 24 | 25 | use compiler::Chunk; 26 | use instr::Instr; 27 | 28 | /// Custom result type for evaluating Lua. 29 | pub type Result = std::result::Result; 30 | -------------------------------------------------------------------------------- /src/lua_std.rs: -------------------------------------------------------------------------------- 1 | //! Lua's standard library 2 | 3 | mod basic; 4 | 5 | pub(crate) use basic::open_base; 6 | 7 | use crate::State; 8 | 9 | pub(crate) fn open_libs(state: &mut State) { 10 | open_base(state); 11 | } 12 | -------------------------------------------------------------------------------- /src/lua_std/basic.rs: -------------------------------------------------------------------------------- 1 | //! Lua's Standard Library 2 | 3 | use crate::error::ErrorKind; 4 | use crate::LuaType; 5 | use crate::State; 6 | 7 | pub(crate) fn open_base(state: &mut State) { 8 | let mut add = |name, func| { 9 | state.push_rust_fn(func); 10 | state.set_global(name); 11 | }; 12 | 13 | // Issues an error when the value of its first argument is false; otherwise, 14 | // returns all its arguments. `message` is an error message; when absent, 15 | // it defaults to "assertion failed!". 16 | add("assert", |state| { 17 | state.check_any(1)?; 18 | let cond = state.to_boolean(1); 19 | state.remove(1); 20 | if cond { 21 | Ok(state.get_top() as u8) 22 | } else if state.get_top() == 0 { 23 | Err(state.error(ErrorKind::AssertionFail)) 24 | } else { 25 | let s = state.to_string(1); 26 | Err(state.error(ErrorKind::WithMessage(s))) 27 | } 28 | }); 29 | 30 | add("ipairs", |state| { 31 | state.check_type(1, LuaType::Table)?; 32 | state.set_top(1); 33 | state.push_rust_fn(|state| { 34 | state.check_type(1, LuaType::Table)?; 35 | state.check_type(2, LuaType::Number)?; 36 | state.set_top(2); 37 | let old_index = state.to_number(2).unwrap(); 38 | let new_index = old_index + 1.0; 39 | state.pop(1); // pop the old number 40 | state.push_number(new_index); 41 | state.get_table(1)?; 42 | if state.to_boolean(-1) { 43 | state.push_number(new_index); 44 | state.replace(1); // Replaces the table with the index 45 | Ok(2) 46 | } else { 47 | state.set_top(0); 48 | state.push_nil(); 49 | Ok(1) 50 | } 51 | }); 52 | // Swap the table and function 53 | state.push_value(1); 54 | state.remove(1); 55 | // Push the initial index 56 | state.push_number(0.0); 57 | Ok(3) 58 | }); 59 | 60 | // Receives any number of arguments, and prints their values to `stdout`. 61 | add("print", |state| { 62 | let range = 1..=state.get_top(); 63 | let mut strings = range.map(|i| state.to_string(i as isize)); 64 | if let Some(s) = strings.next() { 65 | print!("{}", s); 66 | for s in strings { 67 | print!("\t{}", s); 68 | } 69 | } 70 | println!(); 71 | Ok(0) 72 | }); 73 | 74 | // Returns the type of its only argument, coded as a string. 75 | add("type", |state| { 76 | state.check_any(1)?; 77 | let typ = state.typ(1); 78 | let type_str = typ.to_string(); 79 | state.pop(state.get_top() as isize); 80 | state.push_string(type_str); 81 | Ok(1) 82 | }); 83 | 84 | // unpack(list) 85 | // 86 | // Returns list[1], list[2], ... list[#list]. The Lua version can take 87 | // additional arguments to return only part of the list, but that isn't 88 | // supported yet. 89 | add("unpack", |state| { 90 | state.check_type(1, LuaType::Table)?; 91 | let mut i = 1.0; 92 | loop { 93 | state.push_number(i); 94 | state.get_table(1)?; 95 | if let LuaType::Nil = state.typ(-1) { 96 | state.pop(1); 97 | break; 98 | } else { 99 | i += 1.0; 100 | } 101 | } 102 | Ok(i as u8 - 1) 103 | }); 104 | } 105 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env::args; 2 | use std::io::{self, Write}; 3 | use std::process::exit; 4 | 5 | use lua::State; 6 | 7 | fn main() { 8 | let mut args = args(); 9 | match args.nth(1) { 10 | None => run_prompt(), 11 | Some(s) => run_file(&s), 12 | } 13 | } 14 | 15 | fn run_file(filename: &str) { 16 | let mut state = State::new(); 17 | let result = state.do_file(filename); 18 | if let Err(e) = result { 19 | eprintln!("{}", e); 20 | exit(1); 21 | } 22 | } 23 | 24 | fn run_prompt() { 25 | let version = env!("CARGO_PKG_VERSION"); 26 | println!("Lua in Rust {} by Chris Neidhart", version); 27 | let mut state = State::new(); 28 | loop { 29 | let load_result = read_stdin(&mut state); 30 | match load_result { 31 | Ok(0) => { 32 | println!(); 33 | return; 34 | } 35 | Err(e) => { 36 | eprintln!("stdin: {}", e); 37 | continue; 38 | } 39 | Ok(_) => (), 40 | } 41 | 42 | let run_result = state.call(0, 0); 43 | if let Err(e) = run_result { 44 | eprintln!("stdin: {}", e); 45 | } 46 | } 47 | } 48 | 49 | fn read_stdin(state: &mut State) -> lua::Result { 50 | let stdin = io::stdin(); 51 | let mut stdout = io::stdout(); 52 | let mut is_first_line = true; 53 | let mut buffer = String::new(); 54 | let mut total_bytes_read = 0; 55 | loop { 56 | if is_first_line { 57 | print!("> "); 58 | is_first_line = false; 59 | } else { 60 | print!(">> "); 61 | } 62 | let _ = stdout.flush(); 63 | 64 | total_bytes_read += stdin.read_line(&mut buffer)?; 65 | if total_bytes_read == 0 { 66 | return Ok(0); 67 | } 68 | 69 | let load_result = state.load_string(&buffer); 70 | match load_result { 71 | Ok(()) => { 72 | return Ok(total_bytes_read); 73 | } 74 | Err(e) => { 75 | if e.is_recoverable() { 76 | continue; 77 | } else { 78 | return Err(e); 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/vm.rs: -------------------------------------------------------------------------------- 1 | //! This module provides the `State` struct, which handles the primary 2 | //! components of the VM. 3 | 4 | mod frame; 5 | mod lua_val; 6 | mod object; 7 | mod table; 8 | 9 | pub use lua_val::LuaType; 10 | pub use lua_val::RustFunc; 11 | 12 | use std::cmp::Ordering; 13 | use std::collections::HashMap; 14 | use std::io; 15 | 16 | use super::compiler; 17 | use super::error::Error; 18 | use super::error::ErrorKind; 19 | use super::error::TypeError; 20 | use super::Chunk; 21 | use super::Instr; 22 | use super::Result; 23 | 24 | use frame::Frame; 25 | use lua_val::Val; 26 | use object::{GcHeap, Markable}; 27 | use table::Table; 28 | 29 | /// The main interface into the Lua VM. 30 | pub struct State { 31 | /// The global environment. This may be changed to an actual Table in the future. 32 | globals: HashMap, 33 | /// The main stack which stores values. 34 | stack: Vec, 35 | /// The bottom index of the current frame in the stack. 36 | stack_bottom: usize, 37 | /// The heap which holds any garbage-collected Objects. 38 | heap: GcHeap, 39 | /// The string literals (as `Val`s) of every active `Frame`. 40 | string_literals: Vec, 41 | } 42 | 43 | // Important note on how the stack is tracked: 44 | // A State uses a single stack for all local variables, temporary values, 45 | // function arguments, and function return values. Both Lua frames and Rust 46 | // frames use this stack. `self.stack_bottom` refers to the first value in the 47 | // stack which belongs to the current frame. Note that Rust functions access 48 | // the stack using 1-based indexing, but Lua code uses 0-based indexing. 49 | 50 | impl Markable for State { 51 | fn mark_reachable(&self) { 52 | self.stack.mark_reachable(); 53 | self.globals.mark_reachable(); 54 | self.string_literals.mark_reachable(); 55 | } 56 | } 57 | 58 | impl State { 59 | const GC_INITIAL_THRESHOLD: usize = 20; 60 | 61 | /// Creates a new, independent state. 62 | pub fn new() -> Self { 63 | let mut me = Self::empty(); 64 | me.open_libs(); 65 | me 66 | } 67 | 68 | /// Creates a new state without opening any of the standard libs. 69 | /// The global namespace of this state is entirely empty. This corresponds 70 | /// to the `lua_newstate' function in the C API. 71 | pub fn empty() -> Self { 72 | Self { 73 | globals: HashMap::new(), 74 | stack: Vec::new(), 75 | stack_bottom: 0, 76 | heap: GcHeap::with_threshold(Self::GC_INITIAL_THRESHOLD), 77 | string_literals: Vec::new(), 78 | } 79 | } 80 | 81 | /// Calls a function. 82 | /// 83 | /// To call a function you must use the following protocol: first, the 84 | /// function to be called is pushed onto the stack; then, the arguments to 85 | /// the function are pushed in direct order; that is, the first argument is 86 | /// pushed first. Finally you call `lua_call`; `num_args` is the number of 87 | /// arguments that you pushed onto the stack. All arguments and the function 88 | /// value are popped from the stack when the function is called. The 89 | /// function results are pushed onto the stack when the function returns. 90 | /// The number of results is adjusted to `num_ret_expected`. The function 91 | /// results are pushed onto the stack in direct order (the first result is 92 | /// pushed first), so that after the call the last result is on the top of 93 | /// the stack. 94 | pub fn call(&mut self, num_args: u8, num_ret_expected: u8) -> Result<()> { 95 | let idx = self.stack.len() - num_args as usize - 1; 96 | let func_val = self.stack.remove(idx); 97 | let num_ret_actual = if let Val::RustFn(f) = func_val { 98 | let old_stack_bottom = self.stack_bottom; 99 | self.stack_bottom = idx; 100 | let num_ret_reported = f(self)?; 101 | let num_ret_actual = self.get_top() as u8; 102 | match num_ret_reported.cmp(&num_ret_actual) { 103 | Ordering::Greater => { 104 | for _ in num_ret_actual..num_ret_reported { 105 | self.push_nil(); 106 | } 107 | } 108 | Ordering::Less => { 109 | let slc = &mut self.stack[self.stack_bottom..]; 110 | slc.rotate_right(num_ret_reported as usize); 111 | let new_len = 112 | self.stack.len() - num_ret_actual as usize + num_ret_reported as usize; 113 | self.stack.truncate(new_len); 114 | } 115 | Ordering::Equal => (), 116 | } 117 | self.stack_bottom = old_stack_bottom; 118 | num_ret_reported 119 | } else if let Some(chunk) = func_val.as_lua_function() { 120 | self.eval_chunk(chunk, num_args)? 121 | } else { 122 | return Err(self.type_error(TypeError::FunctionCall(func_val.typ()))); 123 | }; 124 | self.balance_stack(num_ret_expected as usize, num_ret_actual as usize); 125 | Ok(()) 126 | } 127 | 128 | /// Pops `n` values from the stack, concatenates them, and pushes the 129 | /// result. If `n` is 1, the result is the single value on the stack (that 130 | /// is, the function does nothing); if `n` is 0, the result is the empty 131 | /// string. 132 | pub fn concat(&mut self, n: usize) -> Result<()> { 133 | assert!(n == 2, "Can only concatenate two at a time for now"); 134 | self.concat_helper(n) 135 | } 136 | 137 | /// Copies the element at `from` into the valid index `to`, replacing the 138 | /// value at that position. Equivalent to Lua's `lua_copy`. 139 | pub fn copy_val(&mut self, from: isize, to: isize) { 140 | let val = self.at_index(from); 141 | let to = self.convert_idx(to); 142 | self.stack[to] = val; 143 | } 144 | 145 | /// Pushes onto the stack the value of the global `name`. 146 | pub fn get_global(&mut self, name: &str) { 147 | let val = self.globals.get(name).cloned().unwrap_or_default(); 148 | self.stack.push(val); 149 | } 150 | 151 | /// Pushes onto the stack the value `t[k]`, where `t` is the value at the given 152 | /// valid index and `k` is the value at the top of the stack. 153 | /// 154 | /// This function pops the key from the stack (putting the resulting value in 155 | /// its place). As in Lua, this function may trigger a metamethod for the 156 | /// "index" event. 157 | pub fn get_table(&mut self, i: isize) -> Result<()> { 158 | let idx = self.convert_idx(i); 159 | assert!(idx != self.stack.len() - 1); 160 | let mut table = self.stack[idx].clone(); 161 | match table.as_table() { 162 | Some(t) => { 163 | let key = self.pop_val(); 164 | let val = t.get(&key); 165 | self.stack.push(val); 166 | Ok(()) 167 | } 168 | None => Err(self.type_error(TypeError::TableIndex(self.stack[idx].typ()))), 169 | } 170 | } 171 | 172 | /// Returns the index of the top element in the stack. Because indices start 173 | /// at 1, this result is equal to the number of elements in the stack (and 174 | /// so 0 means an empty stack). 175 | pub fn get_top(&self) -> usize { 176 | self.stack.len() - self.stack_bottom 177 | } 178 | 179 | /// Moves the top element into the given valid index, shifting up the 180 | /// elements above this index to open space. 181 | pub fn insert(&mut self, index: isize) { 182 | let idx = self.convert_idx(index); 183 | let slice = &mut self.stack[idx..]; 184 | slice.rotate_right(1); 185 | } 186 | 187 | /// Calls `reader` to produce source code, then parses that code and returns 188 | /// the chunk. If the code is syntactically invalid, but could be valid if 189 | /// more code was appended, then `reader` will be called again. A common use 190 | /// for this function is for `reader` to query the user for a line of input. 191 | pub fn load(&mut self, reader: &mut impl io::Read) -> Result<()> { 192 | let mut buffer = String::new(); 193 | // TODO make the lexer actually use a Reader? 194 | reader.read_to_string(&mut buffer)?; 195 | compiler::parse_str(&buffer).map(|chunk| { 196 | self.push_chunk(chunk); 197 | }) 198 | } 199 | 200 | /// Loads a string as a Lua chunk. This function uses `load` to load the 201 | /// chunk in the string `s`. 202 | pub fn load_string(&mut self, s: impl AsRef) -> Result<()> { 203 | let c = compiler::parse_str(s)?; 204 | self.push_chunk(c); 205 | Ok(()) 206 | } 207 | 208 | /// Creates a new empty table and pushes it onto the stack. 209 | pub fn new_table(&mut self) { 210 | let val = self.alloc_table(); 211 | self.stack.push(val); 212 | } 213 | 214 | /// Pops `n` elements from the stack. 215 | pub fn pop(&mut self, n: isize) { 216 | assert!( 217 | n <= self.get_top() as isize, 218 | "Tried to pop too many elements ({})", 219 | n 220 | ); 221 | for _ in 0..n { 222 | self.pop_val(); 223 | } 224 | } 225 | 226 | /// Pushes a boolean onto the stack. 227 | pub fn push_boolean(&mut self, b: bool) { 228 | self.stack.push(Val::Bool(b)); 229 | } 230 | 231 | /// Pushes a `nil` value onto the stack. 232 | pub fn push_nil(&mut self) { 233 | self.stack.push(Val::Nil); 234 | } 235 | 236 | /// Pushes a number with value `n` onto the stack. 237 | pub fn push_number(&mut self, n: f64) { 238 | self.stack.push(Val::Num(n)); 239 | } 240 | 241 | /// Pushes a Rust function onto the stack. 242 | pub fn push_rust_fn(&mut self, f: RustFunc) { 243 | self.stack.push(Val::RustFn(f)); 244 | } 245 | 246 | /// Pushes the given string onto the stack. 247 | pub fn push_string(&mut self, s: String) { 248 | let val = self.alloc_string(s); 249 | self.stack.push(val); 250 | } 251 | 252 | /// Pushes a copy of the element at the given index onto the stack. 253 | pub fn push_value(&mut self, i: isize) { 254 | // TODO: figure out what lua does when index is invalid 255 | let val = self.at_index(i); 256 | self.stack.push(val); 257 | } 258 | 259 | pub fn remove(&mut self, i: isize) { 260 | let idx = self.convert_idx(i); 261 | self.stack.remove(idx); 262 | } 263 | 264 | /// Pops a value from the stack, then replaces the value at the given index 265 | /// with that value. 266 | pub fn replace(&mut self, i: isize) { 267 | let idx = self.convert_idx(i); 268 | let val = self.stack.pop().unwrap(); 269 | self.stack[idx] = val; 270 | } 271 | 272 | /// Pops a value from the stack and sets it as the new value of global 273 | /// `name`. 274 | pub fn set_global(&mut self, name: &str) { 275 | let val = self.pop_val(); 276 | self.globals.insert(name.to_string(), val); 277 | } 278 | 279 | /// Accepts any acceptable index, or 0, and sets the stack top to this index. 280 | /// If the new top is larger than the old one, then the new elements are filled 281 | /// with `nil`. If `index` is 0, then all stack elements are removed. 282 | pub fn set_top(&mut self, i: isize) { 283 | match i.cmp(&0) { 284 | Ordering::Less => { 285 | panic!("negative not supported yet ({})", i); 286 | } 287 | Ordering::Equal => { 288 | self.stack.truncate(self.stack_bottom); 289 | } 290 | Ordering::Greater => { 291 | let i = i as usize; 292 | let old_top = self.get_top(); 293 | match i.cmp(&old_top) { 294 | Ordering::Less => { 295 | self.pop((old_top - i) as isize); 296 | } 297 | Ordering::Equal => (), 298 | Ordering::Greater => { 299 | for _ in old_top..i { 300 | self.push_nil(); 301 | } 302 | } 303 | } 304 | } 305 | } 306 | } 307 | 308 | /// Returns whether the value at the given index is not `false` or `nil`. 309 | pub fn to_boolean(&self, idx: isize) -> bool { 310 | let val = self.at_index(idx); 311 | val.truthy() 312 | } 313 | 314 | /// Attempts to convert the value at the given index to a number. 315 | pub fn to_number(&self, idx: isize) -> Result { 316 | let i = self.convert_idx(idx); 317 | let val = &self.stack[i]; 318 | val.as_num() 319 | .ok_or_else(|| self.type_error(TypeError::Arithmetic(val.typ()))) 320 | } 321 | 322 | /// Converts the value at the given index to a string. 323 | pub fn to_string(&self, idx: isize) -> String { 324 | let i = self.convert_idx(idx); 325 | self.stack[i].to_string() 326 | } 327 | 328 | /// Returns the type of the value in the given acceptable index. 329 | pub fn typ(&self, idx: isize) -> LuaType { 330 | self.at_index(idx).typ() 331 | } 332 | 333 | fn alloc_string(&mut self, s: String) -> Val { 334 | let Self { 335 | stack, 336 | globals, 337 | string_literals, 338 | .. 339 | } = self; 340 | let ptr = self.heap.new_string(s, || { 341 | stack.mark_reachable(); 342 | globals.mark_reachable(); 343 | string_literals.mark_reachable(); 344 | }); 345 | Val::Str(ptr) 346 | } 347 | 348 | fn alloc_table(&mut self) -> Val { 349 | let Self { 350 | stack, 351 | globals, 352 | string_literals, 353 | .. 354 | } = self; 355 | let obj = self.heap.new_table(|| { 356 | stack.mark_reachable(); 357 | globals.mark_reachable(); 358 | string_literals.mark_reachable(); 359 | }); 360 | Val::Obj(obj) 361 | } 362 | 363 | /// Get the value at the given index. Panics if out of bounds. 364 | fn at_index(&self, idx: isize) -> Val { 365 | let i = self.convert_idx(idx); 366 | self.stack[i].clone() 367 | } 368 | 369 | /// Balances a stack after an operation that returns an indefinite number of 370 | /// results. 371 | fn balance_stack(&mut self, expected: usize, received: usize) { 372 | match expected.cmp(&received) { 373 | Ordering::Greater => { 374 | for _ in received..expected { 375 | self.push_nil(); 376 | } 377 | } 378 | Ordering::Less => { 379 | for _ in expected..received { 380 | self.pop_val(); 381 | } 382 | } 383 | Ordering::Equal => (), 384 | } 385 | } 386 | 387 | fn concat_helper(&mut self, n: usize) -> Result<()> { 388 | let mut buffer = String::new(); 389 | let idx = self.stack.len() - n; 390 | let drain = self.stack.drain(idx..); 391 | let mut abort = None; 392 | for val in drain { 393 | if let Some(s) = val.as_string() { 394 | buffer.push_str(s); 395 | } else { 396 | abort = Some(TypeError::Concat(val.typ())); 397 | break; 398 | } 399 | } 400 | if let Some(e) = abort { 401 | return Err(self.type_error(e)); 402 | } 403 | 404 | let val = self.alloc_string(buffer); 405 | self.stack.push(val); 406 | Ok(()) 407 | } 408 | 409 | /// Given a relative index, convert it to an absolute index to the stack. 410 | fn convert_idx(&self, fake_idx: isize) -> usize { 411 | let stack_top = self.stack.len() as isize; 412 | let stack_bottom = self.stack_bottom as isize; 413 | let stack_len = stack_top - stack_bottom; 414 | if fake_idx > 0 && fake_idx <= stack_len { 415 | (fake_idx - 1 + stack_bottom) as usize 416 | } else if fake_idx < 0 && fake_idx >= -stack_len { 417 | (stack_top + fake_idx) as usize 418 | } else { 419 | panic!("index out of bounds"); 420 | } 421 | } 422 | 423 | pub fn error(&self, kind: ErrorKind) -> Error { 424 | // TODO actually find position 425 | let pos = 0; 426 | let column = 0; 427 | Error::new(kind, pos, column) 428 | } 429 | 430 | fn eval_chunk(&mut self, chunk: Chunk, num_args: u8) -> Result { 431 | let old_stack_bottom = self.stack_bottom; 432 | self.stack_bottom = self.stack.len() - num_args as usize; 433 | 434 | match num_args.cmp(&chunk.num_params) { 435 | Ordering::Less => { 436 | for _ in num_args..chunk.num_params { 437 | self.push_nil(); 438 | } 439 | } 440 | Ordering::Greater => { 441 | self.pop((num_args - chunk.num_params) as isize); 442 | } 443 | Ordering::Equal => (), 444 | } 445 | 446 | for _ in 0..(chunk.num_locals) { 447 | self.push_nil(); 448 | } 449 | 450 | let mut frame = self.initialize_frame(chunk); 451 | let num_vals_returned = frame.eval(self)?; 452 | match num_vals_returned { 453 | 0 => { 454 | self.stack.truncate(self.stack_bottom); 455 | self.stack_bottom = old_stack_bottom; 456 | } 457 | 1 => { 458 | let ret_val = self.pop_val(); 459 | self.stack.truncate(self.stack_bottom); 460 | self.stack_bottom = old_stack_bottom; 461 | self.stack.push(ret_val); 462 | } 463 | _ => { 464 | panic!("Can't handle multiple return values"); 465 | } 466 | } 467 | Ok(num_vals_returned) 468 | } 469 | 470 | fn initialize_frame(&mut self, chunk: Chunk) -> Frame { 471 | let string_literal_start = self.string_literals.len(); 472 | for s in &chunk.string_literals { 473 | let string_ptr = { 474 | let Self { 475 | stack, 476 | globals, 477 | string_literals, 478 | .. 479 | } = self; 480 | self.heap.new_string(s.into(), || { 481 | stack.mark_reachable(); 482 | globals.mark_reachable(); 483 | string_literals.mark_reachable(); 484 | }) 485 | }; 486 | self.string_literals.push(Val::Str(string_ptr)); 487 | } 488 | Frame::new(chunk, string_literal_start) 489 | } 490 | 491 | /// Pop a value from the stack 492 | fn pop_val(&mut self) -> Val { 493 | self.stack.pop().unwrap() 494 | } 495 | 496 | fn push_chunk(&mut self, chunk: Chunk) { 497 | let Self { 498 | stack, 499 | globals, 500 | string_literals, 501 | .. 502 | } = self; 503 | let obj = self.heap.new_lua_fn(chunk, || { 504 | stack.mark_reachable(); 505 | globals.mark_reachable(); 506 | string_literals.mark_reachable(); 507 | }); 508 | self.stack.push(Val::Obj(obj)); 509 | } 510 | 511 | fn type_error(&self, e: TypeError) -> Error { 512 | self.error(ErrorKind::TypeError(e)) 513 | } 514 | } 515 | 516 | impl Default for State { 517 | fn default() -> Self { 518 | Self::new() 519 | } 520 | } 521 | 522 | #[cfg(test)] 523 | mod tests { 524 | use super::compiler::parse_str; 525 | use super::lua_val::Val; 526 | use super::Chunk; 527 | use super::Instr::*; 528 | use super::State; 529 | 530 | #[test] 531 | fn vm_test01() { 532 | let mut state = State::new(); 533 | let input = parse_str("a = 1").unwrap(); 534 | state.eval_chunk(input, 0).unwrap(); 535 | assert_eq!(Val::Num(1.0), *state.globals.get("a").unwrap()); 536 | } 537 | 538 | #[test] 539 | fn vm_test02() { 540 | let mut state = State::new(); 541 | let input = Chunk { 542 | code: vec![ 543 | PushString(1), 544 | PushString(2), 545 | Concat, 546 | SetGlobal(0), 547 | Return(0), 548 | ], 549 | string_literals: vec!["key".to_string(), "a".to_string(), "b".to_string()], 550 | ..Chunk::default() 551 | }; 552 | state.eval_chunk(input, 0).unwrap(); 553 | let val = state.globals.get("key").unwrap(); 554 | assert_eq!("ab".to_string(), val.as_string().unwrap()); 555 | } 556 | 557 | #[test] 558 | fn vm_test04() { 559 | let mut state = State::new(); 560 | let input = Chunk { 561 | code: vec![PushNum(0), PushNum(0), Equal, SetGlobal(0), Return(0)], 562 | number_literals: vec![2.5], 563 | string_literals: vec!["a".to_string()], 564 | ..Chunk::default() 565 | }; 566 | state.eval_chunk(input, 0).unwrap(); 567 | assert_eq!(Val::Bool(true), *state.globals.get("a").unwrap()); 568 | } 569 | 570 | #[test] 571 | fn vm_test05() { 572 | let mut state = State::new(); 573 | let input = Chunk { 574 | code: vec![ 575 | PushBool(true), 576 | BranchFalseKeep(2), 577 | Pop, 578 | PushBool(false), 579 | SetGlobal(0), 580 | Return(0), 581 | ], 582 | string_literals: vec!["key".to_string()], 583 | ..Chunk::default() 584 | }; 585 | state.eval_chunk(input, 0).unwrap(); 586 | assert_eq!(Val::Bool(false), *state.globals.get("key").unwrap()); 587 | } 588 | 589 | #[test] 590 | fn vm_test06() { 591 | let mut state = State::new(); 592 | let code = vec![ 593 | PushBool(true), 594 | BranchFalse(3), 595 | PushNum(0), 596 | SetGlobal(0), 597 | Return(0), 598 | ]; 599 | let chunk = Chunk { 600 | code, 601 | number_literals: vec![5.0], 602 | string_literals: vec!["a".to_string()], 603 | ..Chunk::default() 604 | }; 605 | state.eval_chunk(chunk, 0).unwrap(); 606 | assert_eq!(Val::Num(5.0), *state.globals.get("a").unwrap()); 607 | } 608 | 609 | #[test] 610 | fn vm_test07() { 611 | let mut state = State::new(); 612 | let code = vec![ 613 | PushNum(0), 614 | PushNum(0), 615 | Less, 616 | BranchFalse(2), 617 | PushBool(true), 618 | SetGlobal(0), 619 | Return(0), 620 | ]; 621 | let chunk = Chunk { 622 | code, 623 | number_literals: vec![2.0], 624 | string_literals: vec!["a".to_string()], 625 | ..Chunk::default() 626 | }; 627 | state.eval_chunk(chunk, 0).unwrap(); 628 | assert!(state.globals.get("a").is_none()); 629 | } 630 | 631 | #[test] 632 | fn vm_test08() { 633 | let code = vec![ 634 | PushNum(2), // a = 2 635 | SetGlobal(0), 636 | GetGlobal(0), // a <0 637 | PushNum(0), 638 | Less, 639 | BranchFalse(5), 640 | GetGlobal(0), 641 | PushNum(1), 642 | Add, 643 | SetGlobal(0), 644 | Jump(-9), 645 | Return(0), 646 | ]; 647 | let chunk = Chunk { 648 | code, 649 | number_literals: vec![1.0, 10.0, 0.0], 650 | string_literals: vec!["a".to_string()], 651 | ..Chunk::default() 652 | }; 653 | let mut state = State::new(); 654 | state.eval_chunk(chunk, 0).unwrap(); 655 | } 656 | 657 | #[test] 658 | fn vm_test09() { 659 | // local a = 1 660 | // while a < 10 do 661 | // a = a + 1 662 | // end 663 | // x = a 664 | let code = vec![ 665 | PushNum(0), 666 | SetLocal(0), 667 | GetLocal(0), 668 | PushNum(1), 669 | Less, 670 | BranchFalse(5), 671 | GetLocal(0), 672 | PushNum(2), 673 | Add, 674 | SetLocal(0), 675 | Jump(-9), 676 | GetLocal(0), 677 | SetGlobal(0), 678 | Return(0), 679 | ]; 680 | let chunk = Chunk { 681 | code, 682 | number_literals: vec![1.0, 10.0, 1.0], 683 | string_literals: vec!["x".to_string()], 684 | num_locals: 1, 685 | ..Chunk::default() 686 | }; 687 | let mut state = State::new(); 688 | state.eval_chunk(chunk, 0).unwrap(); 689 | assert_eq!(Val::Num(10.0), *state.globals.get("x").unwrap()); 690 | } 691 | 692 | #[test] 693 | fn vm_test10() { 694 | let code = vec![ 695 | // For loop control variables 696 | PushNum(0), // start = 6 697 | PushNum(1), // limit = 2 698 | PushNum(1), // step = 2 699 | // Start loop 700 | ForPrep(0, 3), 701 | PushNum(0), 702 | SetGlobal(0), // a = 2 703 | // End loop 704 | ForLoop(0, -3), 705 | Return(0), 706 | ]; 707 | let chunk = Chunk { 708 | code, 709 | number_literals: vec![6.0, 2.0], 710 | string_literals: vec!["a".to_string()], 711 | num_locals: 4, 712 | ..Chunk::default() 713 | }; 714 | let mut state = State::new(); 715 | state.eval_chunk(chunk, 0).unwrap(); 716 | assert!(state.globals.get("a").is_none()); 717 | } 718 | 719 | #[test] 720 | fn vm_test11() { 721 | let text = " 722 | a = 0 723 | for i = 1, 3 do 724 | a = a + i 725 | end"; 726 | let chunk = parse_str(&text).unwrap(); 727 | let mut state = State::new(); 728 | state.eval_chunk(chunk, 0).unwrap(); 729 | let a = state.globals.get("a").unwrap().as_num().unwrap(); 730 | assert_eq!(a, 6.0); 731 | } 732 | } 733 | -------------------------------------------------------------------------------- /src/vm/frame.rs: -------------------------------------------------------------------------------- 1 | use std::ops; 2 | 3 | use super::super::error::TypeError; 4 | use super::Chunk; 5 | use super::Instr; 6 | use super::LuaType; 7 | use super::Result; 8 | use super::State; 9 | use super::Val; 10 | 11 | /// A `Frame` represents a single stack-frame of a Lua function. 12 | pub(super) struct Frame { 13 | /// The chunk being executed 14 | chunk: Chunk, 15 | /// The index of the next (not current) instruction 16 | ip: usize, 17 | /// Offset into `State.string_literals` where this chunk's literals are 18 | /// stored. 19 | string_literal_start: usize, 20 | } 21 | 22 | impl Frame { 23 | /// Create a new Frame. 24 | #[must_use] 25 | pub(super) fn new(chunk: Chunk, string_literal_start: usize) -> Self { 26 | let ip = 0; 27 | Self { 28 | chunk, 29 | ip, 30 | string_literal_start, 31 | } 32 | } 33 | 34 | /// Jump forward/back by `offset` instructions. 35 | fn jump(&mut self, offset: isize) { 36 | self.ip = self.ip.wrapping_add(offset as usize); 37 | } 38 | 39 | /// Get the instruction at the instruction pointer, and advance the 40 | /// instruction pointer accordingly. 41 | fn get_instr(&mut self) -> Instr { 42 | let i = self.chunk.code[self.ip]; 43 | self.ip += 1; 44 | i 45 | } 46 | 47 | #[must_use] 48 | fn get_nested_chunk(&mut self, i: u8) -> Chunk { 49 | self.chunk.nested[i as usize].clone() 50 | } 51 | 52 | #[must_use] 53 | fn get_number_constant(&self, i: u8) -> f64 { 54 | self.chunk.number_literals[i as usize] 55 | } 56 | 57 | /// Start evaluating instructions from the current position. 58 | pub(super) fn eval(&mut self, state: &mut State) -> Result { 59 | loop { 60 | let inst = self.get_instr(); 61 | if option_env!("LUA_DEBUG_VM").is_some() { 62 | println!("{:?}", inst); 63 | } 64 | match inst { 65 | // General control flow 66 | Instr::Pop => { 67 | state.pop_val(); 68 | } 69 | Instr::Jump(offset) => self.jump(offset), 70 | Instr::BranchFalse(ofst) => state.instr_branch(self, false, ofst, false), 71 | Instr::BranchFalseKeep(ofst) => state.instr_branch(self, false, ofst, true), 72 | //Instr::BranchTrue(ofst) => state.instr_branch(self, true, ofst, false), 73 | Instr::BranchTrueKeep(ofst) => state.instr_branch(self, true, ofst, true), 74 | 75 | // Local variables 76 | Instr::GetLocal(i) => state.instr_get_local(i), 77 | Instr::SetLocal(i) => state.instr_set_local(i), 78 | 79 | Instr::GetGlobal(i) => state.instr_get_global(self, i), 80 | Instr::SetGlobal(i) => state.instr_set_global(self, i), 81 | 82 | // Functions 83 | Instr::Closure(i) => state.instr_closure(self, i), 84 | Instr::Call(num_args, num_rets) => state.call(num_args, num_rets)?, 85 | Instr::Return(n) => { 86 | return Ok(n); 87 | } 88 | 89 | // Literals 90 | Instr::PushNil => state.push_nil(), 91 | Instr::PushBool(b) => state.push_boolean(b), 92 | Instr::PushNum(i) => { 93 | let n = self.get_number_constant(i); 94 | state.push_number(n); 95 | } 96 | Instr::PushString(i) => { 97 | let val = state.get_string_constant(self, i); 98 | state.stack.push(val); 99 | } 100 | 101 | // Arithmetic 102 | Instr::Add => state.eval_float_float(::add)?, 103 | Instr::Subtract => state.eval_float_float(::sub)?, 104 | Instr::Multiply => state.eval_float_float(::mul)?, 105 | Instr::Divide => state.eval_float_float(::div)?, 106 | Instr::Mod => state.eval_float_float(::rem)?, 107 | Instr::Pow => state.eval_float_float(f64::powf)?, 108 | 109 | // Equality 110 | Instr::Equal => { 111 | let val2 = state.pop_val(); 112 | let val1 = state.pop_val(); 113 | state.push_boolean(val1 == val2); 114 | } 115 | Instr::NotEqual => { 116 | let val2 = state.pop_val(); 117 | let val1 = state.pop_val(); 118 | state.push_boolean(val1 != val2); 119 | } 120 | 121 | // Orderings 122 | Instr::Less => state.eval_float_bool(::lt)?, 123 | Instr::Greater => state.eval_float_bool(::gt)?, 124 | Instr::LessEqual => state.eval_float_bool(::le)?, 125 | Instr::GreaterEqual => state.eval_float_bool(::ge)?, 126 | 127 | // `for` loops 128 | Instr::ForLoop(slot, offset) => state.instr_for_loop(self, slot, offset)?, 129 | Instr::ForPrep(slot, len) => state.instr_for_prep(self, slot, len)?, 130 | 131 | // Unary 132 | Instr::Length => state.instr_length()?, 133 | Instr::Negate => state.instr_negate()?, 134 | Instr::Not => state.instr_not(), 135 | 136 | // Manipulating tables 137 | Instr::NewTable => state.new_table(), 138 | Instr::GetField(i) => state.instr_get_field(self, i)?, 139 | Instr::GetTable => state.instr_get_table()?, 140 | Instr::InitField(offset, key_id) => state.instr_init_field(self, offset, key_id)?, 141 | Instr::InitIndex(offset) => state.instr_init_index(offset)?, 142 | Instr::SetField(offset, i) => state.instr_set_field(self, offset, i)?, 143 | Instr::SetTable(offset) => state.instr_set_table(offset)?, 144 | 145 | Instr::SetList(n) => state.instr_set_list(n)?, 146 | 147 | // Misc. 148 | Instr::Concat => state.concat_helper(2)?, 149 | } 150 | } 151 | } 152 | } 153 | 154 | // Instruction-specific methods 155 | impl State { 156 | /// Pop a value. If its truthiness matches `cond`, jump with `offset`. 157 | /// If `keep_cond`, then push the value back after jumping. 158 | fn instr_branch(&mut self, frame: &mut Frame, cond: bool, offset: isize, keep_cond: bool) { 159 | let val = self.pop_val(); 160 | let truthy = val.truthy(); 161 | if cond == truthy { 162 | frame.jump(offset); 163 | } 164 | if keep_cond { 165 | self.stack.push(val); 166 | } 167 | } 168 | 169 | fn instr_closure(&mut self, frame: &mut Frame, i: u8) { 170 | let chunk = frame.get_nested_chunk(i); 171 | self.push_chunk(chunk); 172 | } 173 | 174 | fn instr_for_prep(&mut self, frame: &mut Frame, local: u8, body_len: isize) -> Result<()> { 175 | // These slots should only be assigned to during this function. 176 | let step = self.pop_val().as_num().unwrap(); 177 | let end = self.pop_val().as_num().unwrap(); 178 | let start = self.pop_val().as_num().unwrap(); 179 | if check_numeric_for_condition(start, end, step) { 180 | let mut local_slot = local as usize + self.stack_bottom; 181 | for &n in &[start, end, step, start] { 182 | self.stack[local_slot] = Val::Num(n); 183 | local_slot += 1; 184 | } 185 | } else { 186 | frame.jump(body_len); 187 | } 188 | Ok(()) 189 | } 190 | 191 | fn instr_for_loop(&mut self, frame: &mut Frame, local_slot: u8, offset: isize) -> Result<()> { 192 | let slot = local_slot as usize + self.stack_bottom; 193 | let mut var = self.stack[slot].as_num().unwrap(); 194 | let limit = self.stack[slot + 1].as_num().unwrap(); 195 | let step = self.stack[slot + 2].as_num().unwrap(); 196 | var += step; 197 | if check_numeric_for_condition(var, limit, step) { 198 | self.stack[slot] = Val::Num(var); 199 | self.stack[slot + 3] = Val::Num(var); 200 | frame.jump(offset); 201 | } 202 | Ok(()) 203 | } 204 | 205 | fn instr_get_field(&mut self, frame: &mut Frame, field_id: u8) -> Result<()> { 206 | let mut tbl_val = self.pop_val(); 207 | if let Some(t) = tbl_val.as_table() { 208 | let key = self.get_string_constant(frame, field_id); 209 | let val = t.get(&key); 210 | self.stack.push(val); 211 | Ok(()) 212 | } else { 213 | Err(self.type_error(TypeError::TableIndex(tbl_val.typ()))) 214 | } 215 | } 216 | 217 | fn instr_get_global(&mut self, frame: &Frame, string_num: u8) { 218 | let s = &frame.chunk.string_literals[string_num as usize]; 219 | self.get_global(s); 220 | } 221 | 222 | fn instr_get_local(&mut self, local_num: u8) { 223 | let i = local_num as usize + self.stack_bottom; 224 | let val = self.stack[i].clone(); 225 | self.stack.push(val); 226 | } 227 | 228 | fn instr_get_table(&mut self) -> Result<()> { 229 | let key = self.pop_val(); 230 | let mut tbl = self.pop_val(); 231 | if let Some(t) = tbl.as_table() { 232 | self.stack.push(t.get(&key)); 233 | Ok(()) 234 | } else { 235 | Err(self.type_error(TypeError::TableIndex(tbl.typ()))) 236 | } 237 | } 238 | 239 | fn instr_init_field(&mut self, frame: &Frame, negative_offset: u8, key_id: u8) -> Result<()> { 240 | let val = self.pop_val(); 241 | let positive_offset = self.stack.len() - negative_offset as usize - 1; 242 | let mut tbl_value = self.stack[positive_offset].clone(); 243 | if let Some(tbl) = tbl_value.as_table() { 244 | let key = self.get_string_constant(frame, key_id); 245 | tbl.insert(key, val)?; 246 | Ok(()) 247 | } else { 248 | panic!( 249 | "Table for constructor was a {}, not a table", 250 | tbl_value.typ() 251 | ); 252 | } 253 | } 254 | 255 | fn instr_init_index(&mut self, negative_offset: u8) -> Result<()> { 256 | let val = self.pop_val(); 257 | let key = self.pop_val(); 258 | let positive_offset = self.stack.len() - negative_offset as usize - 1; 259 | let tbl = &mut self.stack[positive_offset]; 260 | match tbl.as_table() { 261 | Some(tbl) => { 262 | tbl.insert(key, val)?; 263 | Ok(()) 264 | } 265 | None => { 266 | panic!("Table for constructor was a {}, not a table", tbl.typ()); 267 | } 268 | } 269 | } 270 | 271 | fn instr_length(&mut self) -> Result<()> { 272 | let val = self.pop_val(); 273 | match val.typ() { 274 | LuaType::String => { 275 | let s = val.as_string().unwrap(); 276 | let len = s.len(); 277 | self.stack.push(Val::Num(len as f64)); 278 | Ok(()) 279 | } 280 | LuaType::Table => { 281 | panic!("Unsupported: Length of tables"); 282 | } 283 | typ => Err(self.type_error(TypeError::Length(typ))), 284 | } 285 | } 286 | 287 | fn instr_negate(&mut self) -> Result<()> { 288 | let n = self.pop_num()?; 289 | self.stack.push(Val::Num(-n)); 290 | Ok(()) 291 | } 292 | 293 | fn instr_not(&mut self) { 294 | let b = self.pop_val().truthy(); 295 | self.stack.push(Val::Bool(!b)); 296 | } 297 | 298 | fn instr_set_field(&mut self, frame: &Frame, stack_offset: u8, field_id: u8) -> Result<()> { 299 | let val = self.pop_val(); 300 | let idx = self.stack.len() - stack_offset as usize - 1; 301 | let mut tbl = self.stack.remove(idx); 302 | if let Some(t) = tbl.as_table() { 303 | let key = self.get_string_constant(frame, field_id); 304 | t.insert(key, val)?; 305 | Ok(()) 306 | } else { 307 | Err(self.type_error(TypeError::TableIndex(tbl.typ()))) 308 | } 309 | } 310 | 311 | fn instr_set_global(&mut self, frame: &Frame, string_num: u8) { 312 | let s = self.get_string_constant(frame, string_num); 313 | let val = self.pop_val(); 314 | if let Some(s) = s.as_string() { 315 | self.globals.insert(s.into(), val); 316 | } else { 317 | // TODO handle this better 318 | panic!("Tried to index globals with {} instead of string", s.typ()); 319 | } 320 | } 321 | 322 | fn instr_set_list(&mut self, count: u8) -> Result<()> { 323 | assert!(count > 0, "Shouldn't use SetList with count 0"); 324 | let values = self.stack.split_off(self.stack.len() - count as usize); 325 | let mut tbl_value = self.pop_val(); 326 | if let Some(tbl) = tbl_value.as_table() { 327 | let counter = 1..; 328 | for (i, val) in counter.zip(values) { 329 | let key = Val::Num(i as f64); 330 | tbl.insert(key, val)?; 331 | } 332 | self.stack.push(tbl_value); 333 | Ok(()) 334 | } else { 335 | panic!("Used Instr::SetList on a {}", tbl_value.typ()) 336 | } 337 | } 338 | 339 | fn instr_set_local(&mut self, local_num: u8) { 340 | let val = self.pop_val(); 341 | let i = local_num as usize + self.stack_bottom; 342 | self.stack[i] = val; 343 | } 344 | 345 | fn instr_set_table(&mut self, offset: u8) -> Result<()> { 346 | let val = self.pop_val(); 347 | let index = self.stack.len() - offset as usize - 2; 348 | let mut tbl = self.stack.remove(index); 349 | let key = self.stack.remove(index); 350 | if let Some(t) = tbl.as_table() { 351 | t.insert(key, val)?; 352 | Ok(()) 353 | } else { 354 | Err(self.type_error(TypeError::TableIndex(tbl.typ()))) 355 | } 356 | } 357 | 358 | // Helper methods 359 | 360 | fn eval_float_bool(&mut self, f: impl Fn(&f64, &f64) -> bool) -> Result<()> { 361 | let n2 = self.pop_num()?; 362 | let n1 = self.pop_num()?; 363 | self.stack.push(Val::Bool(f(&n1, &n2))); 364 | Ok(()) 365 | } 366 | 367 | fn eval_float_float(&mut self, f: impl Fn(f64, f64) -> f64) -> Result<()> { 368 | let n2 = self.pop_num()?; 369 | let n1 = self.pop_num()?; 370 | self.stack.push(Val::Num(f(n1, n2))); 371 | Ok(()) 372 | } 373 | 374 | fn get_string_constant(&self, frame: &Frame, i: u8) -> Val { 375 | // self.string_literals[i as usize].clone() 376 | let index = frame.string_literal_start + i as usize; 377 | self.string_literals[index].clone() 378 | } 379 | 380 | fn pop_num(&mut self) -> Result { 381 | let val = self.pop_val(); 382 | val.as_num() 383 | .ok_or_else(|| self.type_error(TypeError::Arithmetic(val.typ()))) 384 | } 385 | } 386 | 387 | fn check_numeric_for_condition(var: f64, limit: f64, step: f64) -> bool { 388 | if step > 0.0 { 389 | var <= limit 390 | } else if step <= 0.0 { 391 | var >= limit 392 | } else { 393 | false 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /src/vm/lua_val.rs: -------------------------------------------------------------------------------- 1 | use super::object::{ObjectPtr, StringPtr}; 2 | use super::Chunk; 3 | use super::Markable; 4 | use super::Result; 5 | use super::State; 6 | use super::Table; 7 | 8 | use std::fmt; 9 | use std::hash::{Hash, Hasher}; 10 | 11 | pub type RustFunc = fn(&mut State) -> Result; 12 | 13 | #[derive(Clone, Default)] 14 | pub(super) enum Val { 15 | #[default] 16 | Nil, 17 | Bool(bool), 18 | Num(f64), 19 | Str(StringPtr), 20 | RustFn(RustFunc), 21 | Obj(ObjectPtr), 22 | } 23 | use Val::*; 24 | 25 | impl Val { 26 | pub(super) fn as_lua_function(&self) -> Option { 27 | if let Obj(o) = self { 28 | o.as_lua_function() 29 | } else { 30 | None 31 | } 32 | } 33 | 34 | pub(super) fn as_num(&self) -> Option { 35 | match self { 36 | Num(f) => Some(*f), 37 | _ => None, 38 | } 39 | } 40 | 41 | pub(super) fn as_string(&self) -> Option<&str> { 42 | if let Str(s) = self { 43 | Some(s.as_str()) 44 | } else { 45 | None 46 | } 47 | } 48 | 49 | pub(super) fn as_table(&mut self) -> Option<&mut Table> { 50 | if let Obj(o) = self { 51 | o.as_table() 52 | } else { 53 | None 54 | } 55 | } 56 | 57 | pub(super) fn truthy(&self) -> bool { 58 | !matches!(self, Nil | Bool(false)) 59 | } 60 | 61 | /// Returns the value's type. 62 | pub(super) fn typ(&self) -> LuaType { 63 | match self { 64 | Nil => LuaType::Nil, 65 | Bool(_) => LuaType::Boolean, 66 | Num(_) => LuaType::Number, 67 | RustFn(_) => LuaType::Function, 68 | Str(_) => LuaType::String, 69 | Obj(o) => o.typ(), 70 | } 71 | } 72 | } 73 | 74 | impl fmt::Debug for Val { 75 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 76 | match self { 77 | Nil => write!(f, "nil"), 78 | Bool(b) => b.fmt(f), 79 | Num(n) => n.fmt(f), 80 | RustFn(func) => write!(f, "", func), 81 | Obj(o) => o.fmt(f), 82 | Str(s) => s.fmt(f), 83 | } 84 | } 85 | } 86 | 87 | impl fmt::Display for Val { 88 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 89 | match self { 90 | Nil => write!(f, "nil"), 91 | Bool(b) => b.fmt(f), 92 | Num(n) => n.fmt(f), 93 | Obj(o) => o.fmt(f), 94 | _ => write!(f, "{:#?}", self), 95 | } 96 | } 97 | } 98 | 99 | /// This is very dangerous, since f64 doesn't implement Eq. 100 | impl Eq for Val {} 101 | 102 | impl Hash for Val { 103 | fn hash(&self, hasher: &mut H) { 104 | match self { 105 | Nil => (), 106 | Bool(b) => b.hash(hasher), 107 | Obj(o) => o.hash(hasher), 108 | Num(n) => { 109 | debug_assert!(!n.is_nan(), "Can't hash NaN"); 110 | let mut bits = n.to_bits(); 111 | if bits == 1 << 63 { 112 | bits = 0; 113 | } 114 | bits.hash(hasher); 115 | }, 116 | RustFn(func) => { 117 | let f: *const RustFunc = func; 118 | f.hash(hasher); 119 | }, 120 | Str(s) => s.hash(hasher), 121 | } 122 | } 123 | } 124 | 125 | impl PartialEq for Val { 126 | fn eq(&self, other: &Val) -> bool { 127 | match (self, other) { 128 | (Nil, Nil) => true, 129 | (Bool(a), Bool(b)) => a == b, 130 | (Num(a), Num(b)) => a == b, 131 | (RustFn(a), RustFn(b)) => { 132 | let x: *const RustFunc = a; 133 | let y: *const RustFunc = b; 134 | x == y 135 | } 136 | (Obj(a), Obj(b)) => a == b, 137 | (Str(a), Str(b)) => StringPtr::eq_physical(a, b), 138 | _ => false, 139 | } 140 | } 141 | } 142 | 143 | impl Markable for Val { 144 | fn mark_reachable(&self) { 145 | match self { 146 | Obj(o) => o.mark_reachable(), 147 | Str(s) => s.mark_reachable(), 148 | _ => (), 149 | } 150 | } 151 | } 152 | 153 | #[derive(Debug, Eq, PartialEq)] 154 | pub enum LuaType { 155 | Nil, 156 | Boolean, 157 | Number, 158 | String, 159 | Table, 160 | Function, 161 | } 162 | 163 | impl LuaType { 164 | pub fn as_str(&self) -> &'static str { 165 | use LuaType::*; 166 | match self { 167 | Nil => "nil", 168 | Boolean => "boolean", 169 | Number => "number", 170 | String => "string", 171 | Table => "table", 172 | Function => "function", 173 | } 174 | } 175 | } 176 | 177 | impl fmt::Display for LuaType { 178 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 179 | self.as_str().fmt(f) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /src/vm/object.rs: -------------------------------------------------------------------------------- 1 | //! An `Object` is some data which: 2 | //! - Has an unknown lifetime 3 | //! - May have references to other `Object`s 4 | //! 5 | //! Because of this, it needs to be garbage collected. 6 | 7 | use std::borrow::Borrow; 8 | use std::cell::Cell; 9 | use std::collections::{HashMap, HashSet}; 10 | use std::fmt; 11 | use std::hash::Hash; 12 | use std::ops::Drop; 13 | use std::ptr::{self, NonNull}; 14 | 15 | use super::Chunk; 16 | use super::LuaType; 17 | use super::Table; 18 | 19 | /// A wrapper around the `LuaVal`s which need to be garbage-collected. 20 | struct WrappedObject { 21 | /// The value this object holds. 22 | raw: RawObject, 23 | /// The next object in the heap. 24 | next: *mut WrappedObject, 25 | /// A flag used in garbage-collection. This is behind a `Cell` so that 26 | /// we can alter the keys of a table. 27 | color: Cell, 28 | } 29 | 30 | enum RawObject { 31 | // Wrap this in a box to reduce the memory usage. Minimal performance impact 32 | // because functions are rarely accessed. 33 | LuaFn(Box), 34 | Table(Table), 35 | } 36 | 37 | impl RawObject { 38 | #[must_use] 39 | pub(super) const fn typ(&self) -> LuaType { 40 | match self { 41 | RawObject::LuaFn(_) => LuaType::Function, 42 | RawObject::Table(_) => LuaType::Table, 43 | } 44 | } 45 | } 46 | 47 | /// The internal pointer type objects use to point to each other. 48 | #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] 49 | pub(super) struct ObjectPtr { 50 | ptr: NonNull, 51 | } 52 | 53 | impl ObjectPtr { 54 | pub(super) fn as_lua_function(self) -> Option { 55 | match &self.deref().raw { 56 | RawObject::LuaFn(chunk) => Some((**chunk).clone()), 57 | _ => None, 58 | } 59 | } 60 | 61 | pub(super) fn as_table(&mut self) -> Option<&mut Table> { 62 | match &mut self.deref_mut().raw { 63 | RawObject::Table(t) => Some(t), 64 | _ => None, 65 | } 66 | } 67 | 68 | pub(super) fn typ(self) -> LuaType { 69 | self.deref().raw.typ() 70 | } 71 | 72 | fn deref(&self) -> &WrappedObject { 73 | unsafe { self.ptr.as_ref() } 74 | } 75 | 76 | fn deref_mut(&mut self) -> &mut WrappedObject { 77 | unsafe { self.ptr.as_mut() } 78 | } 79 | } 80 | 81 | impl fmt::Display for ObjectPtr { 82 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 83 | match &self.deref().raw { 84 | RawObject::LuaFn(_) => write!(f, "function: {:p}", self.ptr), 85 | RawObject::Table(_) => write!(f, "table: {:p}", self.ptr), 86 | } 87 | } 88 | } 89 | 90 | #[derive(Clone, Copy)] 91 | enum Color { 92 | Unmarked, 93 | Reachable, 94 | } 95 | 96 | /// A collection of objects which need to be garbage-collected. 97 | pub(super) struct GcHeap { 98 | /// The start of the linked list which contains every Object. 99 | start: *mut WrappedObject, 100 | /// The number of objects currently in the heap. 101 | size: usize, 102 | /// When the heap grows this large, run the GC. 103 | threshold: usize, 104 | /// The collection of interned Strings. 105 | strings: HashSet, 106 | } 107 | 108 | impl GcHeap { 109 | /// Create a new heap, with the given initial threshold. 110 | pub(super) fn with_threshold(threshold: usize) -> Self { 111 | Self { 112 | start: ptr::null_mut(), 113 | size: 0, 114 | threshold, 115 | strings: HashSet::new(), 116 | } 117 | } 118 | 119 | /// Run the garbage-collector. 120 | /// Make sure you mark all the roots before calling this function. 121 | pub(super) fn collect(&mut self) { 122 | if option_env!("LUA_DEBUG_GC").is_some() { 123 | println!("Running garbage collector"); 124 | println!("Initial size: {}", self.size); 125 | } 126 | 127 | let mut next_ptr_ref = &mut self.start; 128 | while !next_ptr_ref.is_null() { 129 | // From right-to-left, this unsafe block means: 130 | // - deref the reference (safe) to get a pointer 131 | // - deref the pointer (unsafe) to get a WrappedObject 132 | // - make a mutable reference to that WrappedObject 133 | let next_obj = unsafe { &mut **next_ptr_ref }; 134 | match next_obj.color.get() { 135 | Color::Reachable => { 136 | // Reset its color. 137 | next_obj.color.set(Color::Unmarked); 138 | next_ptr_ref = &mut next_obj.next; 139 | } 140 | Color::Unmarked => { 141 | let boxed = unsafe { Box::from_raw(*next_ptr_ref) }; 142 | *next_ptr_ref = boxed.next; 143 | self.size -= 1; 144 | } 145 | } 146 | } 147 | 148 | let mut strings_to_remove = Vec::new(); 149 | for ptr in &self.strings { 150 | let val = unsafe { ptr.0.as_ref() }; 151 | match val.color.get() { 152 | Color::Reachable => { 153 | val.color.set(Color::Unmarked); 154 | } 155 | Color::Unmarked => { 156 | strings_to_remove.push(ptr.clone()); 157 | } 158 | } 159 | } 160 | for ptr in strings_to_remove { 161 | self.strings.remove(&ptr); 162 | let _boxed = unsafe { Box::from_raw(ptr.0.as_ptr()) }; 163 | } 164 | 165 | self.threshold = self.size * 2; 166 | } 167 | 168 | #[must_use] 169 | pub(super) const fn is_full(&self) -> bool { 170 | self.size >= self.threshold 171 | } 172 | 173 | pub(super) fn new_lua_fn(&mut self, chunk: Chunk, mark: impl FnOnce()) -> ObjectPtr { 174 | let raw = RawObject::LuaFn(Box::new(chunk)); 175 | self.new_obj_from_raw(raw, mark) 176 | } 177 | 178 | pub(super) fn new_string(&mut self, s: String, mark: impl FnOnce()) -> StringPtr { 179 | if let Some(ptr) = self.strings.get(s.as_str()) { 180 | ptr.clone() 181 | } else { 182 | if self.is_full() { 183 | mark(); 184 | self.collect(); 185 | } 186 | let boxed = Box::new(MarkedString { 187 | data: s, 188 | color: Cell::new(Color::Unmarked), 189 | }); 190 | let ptr = StringPtr(NonNull::new(Box::into_raw(boxed)).unwrap()); 191 | self.strings.insert(ptr.clone()); 192 | ptr 193 | } 194 | } 195 | 196 | pub(super) fn new_table(&mut self, mark: impl FnOnce()) -> ObjectPtr { 197 | let raw = RawObject::Table(Table::default()); 198 | self.new_obj_from_raw(raw, mark) 199 | } 200 | 201 | fn new_obj_from_raw(&mut self, raw: RawObject, mark: impl FnOnce()) -> ObjectPtr { 202 | if self.is_full() { 203 | mark(); 204 | self.collect(); 205 | } 206 | let new_object = WrappedObject { 207 | next: self.start, 208 | color: Cell::new(Color::Unmarked), 209 | raw, 210 | }; 211 | let boxed = Box::new(new_object); 212 | let raw_ptr = Box::into_raw(boxed); 213 | // Pointers from Box::into_raw are guaranteed to not be null. 214 | let obj_ptr = ObjectPtr { 215 | ptr: NonNull::new(raw_ptr).unwrap(), 216 | }; 217 | 218 | self.start = raw_ptr; 219 | self.size += 1; 220 | 221 | obj_ptr 222 | } 223 | } 224 | 225 | impl Drop for GcHeap { 226 | fn drop(&mut self) { 227 | let mut next_ptr = self.start; 228 | while !next_ptr.is_null() { 229 | let boxed = unsafe { Box::from_raw(next_ptr) }; 230 | next_ptr = boxed.next; 231 | // Now the boxed object is dropped. 232 | } 233 | for ptr in self.strings.drain() { 234 | let _boxed = unsafe { Box::from_raw(ptr.0.as_ptr()) }; 235 | } 236 | } 237 | } 238 | 239 | /// An item is `Markable` if it can be marked as reachable, and thus it and 240 | /// anything it references will not be collected by the GC. 241 | pub(super) trait Markable { 242 | /// Mark this item and the references it contains as reachable. 243 | fn mark_reachable(&self); 244 | } 245 | 246 | impl Markable for WrappedObject { 247 | fn mark_reachable(&self) { 248 | if let Color::Unmarked = self.color.get() { 249 | self.color.set(Color::Reachable); 250 | self.raw.mark_reachable(); 251 | } 252 | } 253 | } 254 | 255 | impl Markable for RawObject { 256 | fn mark_reachable(&self) { 257 | match self { 258 | RawObject::LuaFn(_) => (), 259 | RawObject::Table(tbl) => tbl.mark_reachable(), 260 | } 261 | } 262 | } 263 | 264 | impl Markable for ObjectPtr { 265 | fn mark_reachable(&self) { 266 | self.deref().mark_reachable() 267 | } 268 | } 269 | 270 | /// This impl is mainly for any `Vec`s we use. 271 | impl Markable for [T] { 272 | fn mark_reachable(&self) { 273 | for val in self { 274 | val.mark_reachable(); 275 | } 276 | } 277 | } 278 | 279 | /// This is just for the `globals` field of `State`. It can be removed once 280 | /// globals are stored in a normal `Table`. 281 | impl Markable for HashMap { 282 | fn mark_reachable(&self) { 283 | for val in self.values() { 284 | val.mark_reachable(); 285 | } 286 | } 287 | } 288 | 289 | struct MarkedString { 290 | data: String, 291 | color: Cell, 292 | } 293 | 294 | /// Wrapper type around a pointer to a String. 295 | /// Behaves like a String for the purposes of Hashing and Equality. 296 | #[derive(Clone, Debug)] 297 | pub(crate) struct StringPtr(NonNull); 298 | 299 | impl StringPtr { 300 | #[must_use] 301 | pub(crate) fn eq_physical(a: &Self, b: &Self) -> bool { 302 | a.0 == b.0 303 | } 304 | 305 | #[must_use] 306 | pub(crate) fn as_str(&self) -> &str { 307 | &(unsafe { self.0.as_ref() }).data 308 | } 309 | } 310 | 311 | impl Borrow for StringPtr { 312 | fn borrow(&self) -> &str { 313 | self.as_str() 314 | } 315 | } 316 | 317 | impl Hash for StringPtr { 318 | fn hash(&self, state: &mut H) { 319 | self.as_str().hash(state); 320 | } 321 | } 322 | 323 | impl PartialEq for StringPtr { 324 | fn eq(&self, other: &Self) -> bool { 325 | self.as_str() == other.as_str() 326 | } 327 | } 328 | impl Eq for StringPtr {} 329 | 330 | impl Markable for StringPtr { 331 | fn mark_reachable(&self) { 332 | unsafe { self.0.as_ref().color.set(Color::Reachable) } 333 | } 334 | } 335 | 336 | impl fmt::Display for StringPtr { 337 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 338 | f.write_str(self.as_str()) 339 | } 340 | } 341 | 342 | #[cfg(test)] 343 | mod test { 344 | use super::*; 345 | 346 | #[test] 347 | fn gc_test_strings() { 348 | let mut gc = GcHeap::with_threshold(2000); 349 | gc.new_string("good".into(), || {}); 350 | assert_eq!(1, gc.strings.len()); 351 | gc.new_string("good".into(), || {}); 352 | assert_eq!(1, gc.strings.len()); 353 | gc.new_string("jorts".into(), || {}); 354 | assert_eq!(2, gc.strings.len()); 355 | gc.collect(); 356 | assert_eq!(0, gc.strings.len()); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/vm/table.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use super::Error; 4 | use super::Markable; 5 | use super::Result; 6 | use super::TypeError; 7 | use super::Val; 8 | 9 | #[derive(Debug, Default)] 10 | pub(super) struct Table { 11 | map: HashMap, 12 | } 13 | 14 | impl Table { 15 | pub(super) fn get(&self, key: &Val) -> Val { 16 | match key { 17 | Val::Nil => Val::Nil, 18 | Val::Num(n) if n.is_nan() => Val::Nil, 19 | _ => self.map.get(key).cloned().unwrap_or_default(), 20 | } 21 | } 22 | 23 | pub(super) fn insert(&mut self, key: Val, value: Val) -> Result<()> { 24 | match key { 25 | Val::Nil => Err(Error::new(TypeError::TableKeyNil, 0, 0)), 26 | Val::Num(n) if n.is_nan() => Err(Error::new(TypeError::TableKeyNan, 0, 0)), 27 | _ => { 28 | self.map.insert(key, value); 29 | Ok(()) 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl Markable for Table { 36 | fn mark_reachable(&self) { 37 | for (k, v) in &self.map { 38 | k.mark_reachable(); 39 | v.mark_reachable(); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/vm_aux.rs: -------------------------------------------------------------------------------- 1 | //! More functions on State. 2 | //! 3 | //! Analagous to the Lua header `lauxlib.h`, which contains all the `luaL_*` 4 | //! functions. 5 | 6 | use std::fs::File; 7 | use std::path::Path; 8 | 9 | use crate::error::ArgError; 10 | use crate::error::ErrorKind; 11 | use crate::lua_std; 12 | use crate::LuaType; 13 | use crate::Result; 14 | use crate::State; 15 | 16 | impl State { 17 | pub fn check_any(&mut self, arg_number: isize) -> Result<()> { 18 | assert!(arg_number != 0); 19 | if self.get_top() < arg_number.unsigned_abs() { 20 | let e = ArgError { 21 | arg_number, 22 | func_name: None, 23 | expected: None, 24 | received: None, 25 | }; 26 | Err(self.error(ErrorKind::ArgError(e))) 27 | } else { 28 | Ok(()) 29 | } 30 | } 31 | 32 | pub fn check_type(&mut self, arg_number: isize, expected_type: LuaType) -> Result<()> { 33 | assert!(arg_number != 0); 34 | if self.get_top() < arg_number.unsigned_abs() { 35 | let e = ArgError { 36 | arg_number, 37 | func_name: None, 38 | expected: Some(expected_type), 39 | received: None, 40 | }; 41 | return Err(self.error(ErrorKind::ArgError(e))); 42 | } 43 | let received_type = self.typ(arg_number); 44 | if received_type != expected_type { 45 | let e = ArgError { 46 | arg_number, 47 | func_name: None, 48 | expected: Some(expected_type), 49 | received: Some(received_type), 50 | }; 51 | return Err(self.error(ErrorKind::ArgError(e))); 52 | } 53 | Ok(()) 54 | } 55 | 56 | /// Loads and runs the given file. 57 | pub fn do_file(&mut self, filename: impl AsRef) -> Result<()> { 58 | self.load_file(filename)?; 59 | self.call(0, 0) 60 | } 61 | 62 | /// Loads and runs the given string. 63 | pub fn do_string(&mut self, s: impl AsRef) -> Result<()> { 64 | self.load_string(s)?; 65 | self.call(0, 0) 66 | } 67 | 68 | /// Loads a file as a Lua chunk. This function uses `load` to load the chunk 69 | /// in the file named `filename`. 70 | /// 71 | /// This function only loads the chunk; it does not run it. 72 | pub fn load_file(&mut self, filename: impl AsRef) -> Result<()> { 73 | let mut reader = File::open(filename)?; 74 | self.load(&mut reader) 75 | } 76 | 77 | /// Opens all standard Lua libraries. 78 | pub fn open_libs(&mut self) { 79 | lua_std::open_libs(self) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | use lua::Result; 2 | use lua::State; 3 | 4 | fn run_file(filename: &str) -> Result<()> { 5 | let mut state = State::new(); 6 | state.do_file(filename) 7 | } 8 | 9 | #[test] 10 | fn test01() -> Result<()> { 11 | run_file("tests/test01.lua") 12 | } 13 | 14 | #[test] 15 | fn test02() -> Result<()> { 16 | run_file("tests/test02.lua") 17 | } 18 | 19 | #[test] 20 | fn test03() -> Result<()> { 21 | run_file("tests/test03.lua") 22 | } 23 | 24 | #[test] 25 | fn test04() -> Result<()> { 26 | run_file("tests/test04.lua") 27 | } 28 | 29 | #[test] 30 | fn test05() -> Result<()> { 31 | run_file("tests/test05.lua") 32 | } 33 | 34 | #[test] 35 | fn test06() -> Result<()> { 36 | run_file("tests/test06.lua") 37 | } 38 | 39 | #[test] 40 | fn test07() -> Result<()> { 41 | run_file("tests/test07.lua") 42 | } 43 | 44 | #[test] 45 | fn test08() -> Result<()> { 46 | run_file("tests/test08.lua") 47 | } 48 | 49 | #[test] 50 | fn test09() -> Result<()> { 51 | run_file("tests/test09.lua") 52 | } 53 | 54 | #[test] 55 | fn test10() -> Result<()> { 56 | run_file("tests/test10.lua") 57 | } 58 | 59 | #[test] 60 | fn test11() -> Result<()> { 61 | run_file("tests/test11.lua") 62 | } 63 | -------------------------------------------------------------------------------- /tests/test01.lua: -------------------------------------------------------------------------------- 1 | assert(true) 2 | 3 | assert(2 + 6 == 8) 4 | 5 | assert(6 / 2 == 3) 6 | 7 | assert(2^3 == 8) 8 | 9 | assert(-2^2 == -4) 10 | 11 | assert(#'hello world' == 11) 12 | -------------------------------------------------------------------------------- /tests/test02.lua: -------------------------------------------------------------------------------- 1 | -- Test local variables 2 | 3 | local l0 = 'a' 4 | assert(l0 == 'a') 5 | 6 | do 7 | local l0 = 'b' 8 | local l1 = 'c' 9 | assert(l0 == 'b') 10 | assert(l1 == 'c') 11 | end 12 | assert(l0 == 'a') 13 | assert(l1 == nil) 14 | 15 | local cond = true 16 | while cond do 17 | assert(l1 == nil) 18 | local l0 = 'd' 19 | local l2 = 'e' 20 | assert(l0 == 'd') 21 | assert(l2 == 'e') 22 | cond = false 23 | end 24 | assert(l0 == 'a') 25 | assert(l2 == nil) 26 | 27 | repeat 28 | local l0 = 'f' 29 | local l3 = 'g' 30 | assert(l0 == 'f') 31 | assert(l3 == 'g') 32 | until true 33 | assert(l0 == 'a') 34 | assert(l3 == nil) 35 | 36 | for i = 1, 10 do 37 | local l0 = 'h' 38 | local l3 = 'i' 39 | assert(l0 == 'h') 40 | assert(l3 == 'i') 41 | end 42 | assert(l0 == 'a') 43 | assert(l1 == nil) 44 | 45 | local x, y = 3, 4, 5 46 | do 47 | local x = x 48 | assert(x == 3) 49 | x = 5 50 | assert(x == 5) 51 | end 52 | assert(x == 3) 53 | -------------------------------------------------------------------------------- /tests/test03.lua: -------------------------------------------------------------------------------- 1 | local a = 1 or 2 2 | assert(a == 1) 3 | 4 | local b = nil or 3 5 | assert(b == 3) 6 | 7 | local c = 4 and 5 8 | assert(c == 5) 9 | 10 | local d = nil and 6 11 | assert(d == nil) 12 | -------------------------------------------------------------------------------- /tests/test04.lua: -------------------------------------------------------------------------------- 1 | assert(type(nil) == 'nil') 2 | assert(type(4) == 'number') 3 | assert(type('hi') == 'string') 4 | assert(type(type) == 'function') 5 | assert(type({}) == 'table') 6 | 7 | local s = type('hi') .. type(4) .. type(nil) 8 | assert(s == 'stringnumbernil') 9 | -------------------------------------------------------------------------------- /tests/test05.lua: -------------------------------------------------------------------------------- 1 | local t = {} 2 | t.type = type 3 | assert(t.type(t.type) == 'function') 4 | -------------------------------------------------------------------------------- /tests/test06.lua: -------------------------------------------------------------------------------- 1 | local return_nil = function () 2 | -- Do nothing 3 | end 4 | 5 | local return_5 = function () 6 | return 5 7 | end 8 | 9 | local should_be_nil = return_nil() 10 | local should_be_5 = return_5() 11 | 12 | assert(should_be_nil == nil) 13 | assert(should_be_5 == 5) 14 | -------------------------------------------------------------------------------- /tests/test07.lua: -------------------------------------------------------------------------------- 1 | add = function (x, y) 2 | local sum = x + y 3 | return sum 4 | end 5 | 6 | identity = function () end 7 | 8 | assert(add(2, 7) == 9) 9 | assert(add(5, 11, 41) == 16) 10 | assert(identity() == nil) 11 | -------------------------------------------------------------------------------- /tests/test08.lua: -------------------------------------------------------------------------------- 1 | -- Test function declarations. 2 | 3 | -- A basic function declaration 4 | function return_five() 5 | return 5 6 | end 7 | assert(return_five() == 5) 8 | 9 | do 10 | local my_local 11 | -- A basic function declaration, which assigns to a local instead 12 | function my_local() 13 | return 'hello' 14 | end 15 | assert(my_local() == 'hello') 16 | my_global = my_local 17 | end 18 | assert(my_local == nil) 19 | assert(my_global() == 'hello') 20 | 21 | -- A table function declaration 22 | tbl = {} 23 | function tbl.return_forty_two() 24 | return 42 25 | end 26 | assert(tbl.return_forty_two() == 42) 27 | 28 | -------------------------------------------------------------------------------- /tests/test09.lua: -------------------------------------------------------------------------------- 1 | -- Test table constructors 2 | 3 | -- Basic field=expr style 4 | local table1 = { name = 'Chris', id = '42' } 5 | assert(table1.name == 'Chris') 6 | assert(table1.id == '42') 7 | 8 | -- Square brackets allow arbitrary expressions as keys 9 | local table2 = { 10 | [6 * 7] = 'forty-two'; 11 | ['hello' .. ' ' .. 'world'] = 'hi'; 12 | } 13 | assert(table2[42] == 'forty-two') 14 | assert(table2['hello world'] == 'hi') 15 | 16 | -- Array-style 17 | local table3 = { 18 | 'one', 'two', 'three' 19 | } 20 | assert(table3[1] == 'one') 21 | assert(table3[2] == 'two') 22 | assert(table3[3] == 'three') 23 | 24 | -- Mixed style 25 | local table4 = { 26 | 'one', 27 | name = 'chris', 28 | 'two', 29 | [10 * 10 + 11] = 'eleventy-one', 30 | 'three', 31 | } 32 | assert(table4[1] == 'one') 33 | assert(table4.name == 'chris') 34 | assert(table4[2] == 'two') 35 | assert(table4[111] == 'eleventy-one') 36 | assert(table4[3] == 'three') 37 | -------------------------------------------------------------------------------- /tests/test10.lua: -------------------------------------------------------------------------------- 1 | -- Test some less-common standard library functions 2 | -- (but not any of the functions in separate modules like `os`, `string`, etc.) 3 | 4 | -- unpack 5 | 6 | local tbl = {'x', 'y', 'z'} 7 | x, y, z = unpack(tbl) 8 | assert(x == 'x') 9 | assert(y == 'y') 10 | assert(z == 'z') 11 | 12 | local x, y, z = unpack(tbl) 13 | assert(x == 'x') 14 | assert(y == 'y') 15 | assert(z == 'z') 16 | 17 | local x, y, z, a, b = unpack(tbl) 18 | assert(x == 'x') 19 | assert(y == 'y') 20 | assert(z == 'z') 21 | assert(a == nil) 22 | assert(b == nil) 23 | 24 | -- ipairs 25 | -- TODO: rewrite this using for loops 26 | local tbl = {'a', 'b', 'c'} 27 | local f, t, n = ipairs(tbl) 28 | assert(t == tbl) 29 | assert(n == 0) 30 | local key, value = f(t, n) 31 | assert(key == 1) 32 | assert(value == 'a') 33 | local key, value = f(t, key) 34 | assert(key == 2) 35 | assert(value == 'b') 36 | local key, value = f(t, key) 37 | assert(key == 3) 38 | assert(value == 'c') 39 | local key, value = f(t, key) 40 | assert(key == nil and value == nil) 41 | -------------------------------------------------------------------------------- /tests/test11.lua: -------------------------------------------------------------------------------- 1 | -- Stress-test the GC 2 | 3 | local tbl = {} 4 | 5 | local say_hi = function () 6 | return 'hello' 7 | end 8 | 9 | local count, step = 100000, 1000 10 | for i = 1, count do 11 | local t1 = {} 12 | local t2 = {} 13 | t1.x = t2 14 | t2.y = t1 15 | if i % step == 0 then 16 | tbl[i] = t1 17 | end 18 | end 19 | for i = step, count, step do 20 | assert(type(tbl[i]) == 'table') 21 | assert(type(tbl[i].x) == 'table') 22 | assert(tbl[i] == tbl[i].x.y) 23 | end 24 | 25 | assert(say_hi() == 'hel' .. 'lo') 26 | 27 | -- TODO: run the gc now, then check its size 28 | --------------------------------------------------------------------------------