├── .gitignore ├── LICENSE ├── README.md ├── eg ├── dummy.lua ├── example-mac.hy ├── examples.hua └── examples.lua ├── hua ├── __init__.py ├── cmdline.hy ├── compiler.hy ├── core │ ├── __init__.py │ ├── assignment.hy │ ├── comp.hy │ ├── hua_stdlib.hua │ ├── hua_stdlib.lua │ ├── initialize.hy │ ├── macros.hy │ ├── module.hy │ ├── moses.lua │ ├── oo.hy │ ├── op.hy │ └── utils.hy ├── lua.hy ├── mlast.hy └── tlcode.lua ├── setup.py └── tests └── native_tests ├── core.hua ├── defclass.hua ├── language.hua ├── luaunit.lua ├── reader_macros.hua ├── reader_macros.hy ├── runtest.sh └── testall.hua /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.egg-info 3 | venv/ 4 | tests/native_tests/*.lua 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a 2 | copy of this software and associated documentation files (the "Software"), 3 | to deal in the Software without restriction, including without limitation 4 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 5 | and/or sell copies of the Software, and to permit persons to whom the 6 | Software is furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 14 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 16 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 17 | DEALINGS IN THE SOFTWARE. 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hua 2 | 3 | Hua (㕦) is a [hy](https://github.com/hylang/hy)-like lisp language to lua compiler. It utilizes hy's parser and macro expansion mechanism and compiles the parsed hy syntax tree to [metalua AST](https://github.com/fab13n/metalua/blob/master/doc/ast.txt). The result is then passed to a lua runtime (using the python/lua bridge [lupa](https://github.com/scoder/lupa)) and use [Typed Lua](https://github.com/andremm/typedlua)'s code generator to generate lua codes. Hua is a work in progress and should be considered pre-alpha quality code right now (though I have been using hua to write lua codes scripting [Max](https://cycling74.com/products/max/) for the past month and everything seems to be OK). 4 | 5 | ## Why? 6 | 7 | The main benefit of hua over lua is the ability of meta-programming. Hua includes macros to do hy-style list and dict comprehension, a (still naive) OO system and some other tiny neat features. Though currently [l2l](https://github.com/meric/l2l) and [moonlisp](https://github.com/leafo/moonlisp) may be more mature alternatives. 8 | 9 | One of the benefit of hua over hy is speed, especially if you run the compiled lua code with [luajit](http://luajit.org). Hua (and lua) has a proper lexical scoping implementation. For example, nested `let` is broken in hy, while hua has full support of nested `let`. Lua is widely used to be embedded in host application so for many softwares lua is the only option to extend the softwares capability. 10 | 11 | ## Quickstart 12 | 13 | 1. Create and activate a virtualenv 14 | 2. Install [lupa](https://github.com/scoder/lupa). It's a little bit tricky on Mac. If you have any troubles, please refer to [this guide](https://gist.github.com/larme/9079789cdb1f2fb72b34). (Side notes: currently hua uses [Typed Lua](https://github.com/andremm/typedlua)'s ast->code compiler to generate lua codes so a lua runtime in python (i.e. lupa) is required. I plan to write a native compiler that compiles hua to lua so at least the compiling process doesn't require a lua runtime. However I also plan to add repl to hua which will need the lua runtime. Hence lupa will still be a dependancy in future if you want to use the repl.) 15 | 3. You need to install hua from the git repository: `git clone https://github.com/larme/hua.git; cd hua` 16 | 4. `pip install -e .` 17 | 5. Now you can try the compiler by cding into `eg/` and typing `huac example.hua`. The output will be `example.lua`. 18 | 19 | 20 | ## Brief Introduction aka Comparison with Hy 21 | 22 | Read `example.hua` and its output to get a quick idea of how hua codes look like and what they produce. 23 | 24 | Like hy is a lispy python, hua is a lispy lua. It follow the conventions of lua, which means though hua compiler try to act like hy as much as possible, some aspects are still different. 25 | 26 | ### hua macros are written in hy 27 | 28 | Because currently hua is just a compiler and the compiler is written in hy, hua macros also need to written in hy. 29 | 30 | ### `require`, `require-macro` and `hua-import` 31 | 32 | Because lua use `require` to import a module, we use `require-macro` to require macros from a hy file. 33 | 34 | You can just use `require` to require lua modules, or you can use `hua-import`, which works like `import` in hy. 35 | 36 | ### `setv` vs. `def` 37 | 38 | Because lua is not local by default, `setv` and `def` have different meanings in hua (unlike in hy). `def` is used to introduce a local variable and give it an initial value. `setv` will mutate a variable's value or introduce a global variable. 39 | 40 | Frankly speaking I think in this way the code is more clear and readable. 41 | 42 | ``` lisp 43 | (def x 10) 44 | (setv x 20) 45 | ``` 46 | 47 | is compiled to 48 | 49 | ``` 50 | local x = 10 51 | x = 20 52 | ``` 53 | 54 | There's no way to declare a local variable without assigning an initial value to the variable (like `local x` in lua), just use `(def x nil)`. (Or we can introduce a new keyword `local` which works just like lua's `local`) 55 | 56 | ### Multiple Assignment / Returned Value 57 | 58 | The multiple assignment in lua and python are quite different. Assuming a function `foo` return two value `1, 2`, then after `x, y = foo()`, `x` will be 1 and `y` will be 2 in both python and lua. However after the execution of the following codes: 59 | 60 | ``` 61 | x = foo() 62 | ``` 63 | 64 | In python `x` will have the value of a tuple `(1, 2)`, while in lua `x` will have the value 1. 65 | 66 | Hua follows lua's behavior, sometimes it may cause unexpected problems. Considering the following codes: 67 | 68 | ``` lisp 69 | (print 70 | (if true 71 | (foo) 72 | (bar))) 73 | ``` 74 | 75 | You may think this expression will print out `1 2`. However due to the way how hua compiler works, it will actually print out `1`. The reason is that because the above hua code will be compiled to the following lua code: 76 | 77 | ``` lua 78 | local _hua_anon_var_1 79 | if true then 80 | _hua_anon_var_1 = foo() 81 | else 82 | _hua_anon_var_1 = bar() 83 | end 84 | print(_hua_anon_var_1) 85 | ``` 86 | 87 | To prevent this kinds of mistakes, you have to handle the multiple returned values explicitly. One way is to use multiple assignment to assign several variables to the returned values. You need to know how many returned values you will use in advance. 88 | 89 | ``` lisp 90 | (setv (, x y) 91 | (if true 92 | (foo) 93 | (bar))) 94 | (print x y) 95 | ``` 96 | 97 | which will compile to: 98 | 99 | ``` lua 100 | local _hua_anon_var_2, _hua_anon_var_3 101 | if true then 102 | _hua_anon_var_2, _hua_anon_var_3 = foo() 103 | else 104 | _hua_anon_var_2, _hua_anon_var_3 = bar() 105 | end 106 | x, y = _hua_anon_var_2, _hua_anon_var_3 107 | print(x,y) 108 | ``` 109 | 110 | Another way is packing the multiple returned values into a table using `[(foo)]` syntax. 111 | 112 | ``` lisp 113 | (print 114 | (unpack 115 | (if true 116 | [(foo)] 117 | [(bar)]))) 118 | ``` 119 | 120 | will compile to 121 | 122 | ``` lua 123 | local _hua_anon_var_1 124 | if true then 125 | _hua_anon_var_1 = {foo()} 126 | else 127 | _hua_anon_var_1 = {bar()} 128 | end 129 | print(unpack(_hua_anon_var_1)) 130 | ``` 131 | 132 | 133 | 134 | ### `for` loop syntax 135 | 136 | Lua has both generic for and numeric for. The generic for works similar to python's for. However because lua's table is not iterable itself, you need to call `pairs` or `ipairs` on the table. The following hua code: 137 | 138 | ``` lisp 139 | (def t1 [3 4 5 6]) 140 | (def t2 {"one" 1 "two" 2}) 141 | (for [(, i v1) (ipairs t1) 142 | (, k v2) (pairs t2)] 143 | (print i v1 k v2)) 144 | 145 | ``` 146 | 147 | will compiled to: 148 | 149 | ``` lua 150 | local t1 = {3, 4, 5, 6} 151 | local t2 = {two = 2, one = 1} 152 | for i, v1 in ipairs(t1) do 153 | for k, v2 in pairs(t2) do 154 | print(i,v1,k,v2) 155 | end 156 | end 157 | ``` 158 | 159 | Because you will always call functions like `pairs` and `iparis` on table in the `for` expression, I save the syntax `(for [x [i1 i2 i3]] ...)` for the numeric for statement in lua. Something like `(for [i [1 14 3]] (print i))` will compile to: 160 | 161 | ``` lua 162 | for i = 1, 14, 3 do 163 | print(i) 164 | end 165 | ``` 166 | 167 | You can mix numeric for and generic for in the same `for` expression. The following codes: 168 | 169 | ``` lisp 170 | (for [i [1 3] 171 | key (pairs {"pos" 3 "cons" 2})] 172 | (print "mixed:" i key)) 173 | ``` 174 | 175 | will compiles to: 176 | 177 | ``` lua 178 | for i = 1, 3 do 179 | for key in pairs({pos = 3, cons = 2}) do 180 | print("mixed:",i,key) 181 | end 182 | end 183 | ``` 184 | 185 | 186 | 187 | ### List and Dict comprehensive 188 | 189 | Hua's list and dict comprehensive are macros that expanded to `for` expressions. So the syntax is different to hy's but similar to hua's `for` 190 | 191 | ``` lisp 192 | (def l (list-comp (* i value) 193 | [i [1 10] 194 | (, _ value) (pairs {"T" 1 "F" 0})])) 195 | ``` 196 | 197 | will compile to 198 | 199 | ``` lua 200 | local l 201 | do 202 | local _hua_result_1235 = {} 203 | for i = 1, 10 do 204 | for _, value in pairs({T = 1, F = 0}) do 205 | _hua_result_1235[(1 + #(_hua_result_1235))] = (i * value) 206 | end 207 | end 208 | l = _hua_result_1235 209 | end 210 | ``` 211 | 212 | 213 | 214 | ### Tail Call Optimization 215 | 216 | Unlike python, lua has tail call optimization. However if you define some recursive functions like below, it may blow the stack: 217 | 218 | ``` lisp 219 | (defn test-tco [n] 220 | (if (> n 1) 221 | (test-tco (- n 1)) 222 | (print "May overflow here"))) 223 | ``` 224 | 225 | The compiled lua code explained why: 226 | 227 | ``` lua 228 | local test_tco = nil 229 | test_tco = function (n) 230 | local _hua_anon_var_7 231 | if not (n <= 1) then 232 | _hua_anon_var_7 = test_tco((n - 1)) 233 | else 234 | _hua_anon_var_7 = print("May overflow here") 235 | end 236 | return _hua_anon_var_7 237 | end 238 | ``` 239 | 240 | Use `return` directly will solve this problem (at least for simple recursive form). 241 | 242 | ``` lisp 243 | (defn test-tco2 [n] 244 | (if (> n 1) 245 | (return (test-tco (- n 1))) 246 | (print "No overflow!"))) 247 | ``` 248 | 249 | will compiles to: 250 | 251 | ``` lua 252 | local test_tco2 = nil 253 | test_tco2 = function (n) 254 | local _hua_anon_var_2 255 | if not (n <= 1) then 256 | return test_tco((n - 1)) 257 | else 258 | _hua_anon_var_2 = print("No overflow!") 259 | end 260 | return _hua_anon_var_2 261 | end 262 | ``` 263 | 264 | However please don't overuse `return`. If you want to return a table at the end of a file (as a module table), consider using `export` at the end 265 | 266 | ### No Keyword Arguments for function 267 | 268 | Because lua doesn't support named arguments, hua will not support keyword arguments. 269 | 270 | ### "dot call"s 271 | 272 | In hy `(foo.bar "hello")` is the same as `(.bar foo "hello")`. They both invoke a method `bar` of object `foo` with a single parameter `"hello"`. However in hua it's entirely different. `(foo.bar "hello")` compiles to `foo.bar("hello")` while `(.bar foo "hello")` compiles to `foo:bar("hello")`. In both case `bar` is a property of table `foo`, however in the first case `bar` is called with a single argument `"hello"` while in the second case `bar` is called with two arguments: table `foo` and `"hello"`. 273 | 274 | ### `defclass` difference 275 | 276 | `defclass` works like hy's one. Use `--init` instead of `--init--`. When defining a class without parent class, `--init` method is mandatory. 277 | 278 | ``` lisp 279 | ;; A class without parent class. 280 | ;; A --init method is required for class without parent class. 281 | (defclass Animal [] 282 | [[--init 283 | (fn [self steps-per-turn] 284 | (setv self.steps-per-turn steps-per-turn))] 285 | [move 286 | (fn [self] 287 | (print (concat "I moved " 288 | (tostring self.steps-per-turn) 289 | " steps!")))]]) 290 | 291 | ;; Hua only support single inheritance. 292 | ;; Use `(super method paras...)' to call parent's method 293 | (defclass Cat [Animal] 294 | [[--init 295 | (fn [self steps-per-turn sound] 296 | (super --init steps-per-turn) 297 | (setv self.sound sound))] 298 | [move 299 | (fn [self] 300 | (print self.sound) 301 | (super move))]]) 302 | ``` 303 | 304 | 305 | 306 | ### Misc 307 | 308 | Nearly any lua vs python differences will hold in hua vs hy. Some examples: hua has correct lexical scoping so nested `let` works; only `nil` and `false` are false in hua while empty string and list are also false in hy etc; hua table are not iterable like python's dict and list. 309 | 310 | ## To be Improved 311 | 312 | By priority: 313 | 314 | - No sane error messages, no error codes line numbers! 315 | 316 | - No enough test cases yet! 317 | 318 | - A repl may be needed for some test cases. 319 | 320 | - Currently every hua file need to begin with some initializing codes mainly to import standard macros into the hua file (see examples.hua). This should be done automatically. 321 | 322 | - Currently every lua file output by hua compiler will add `hua/hua/core/` into lua's package path and auto require some functions in `hua/hua/core/hua_stdlib.lua`. The motif is that every lua file compiled by hua is usable immediately without any further configuration. However this is not a good idea because that means every lua file produced by hua compiler is not portable between machines. Later we may pack the `hua_stdlib.lua` as a luarock or ask users to copy the file to their lua package path. 323 | 324 | - Talking about `hua_stdlib.lua`, we may add more functional list/dict manipulating functions using the excellent [Moses](https://github.com/Yonaba/Moses) library. 325 | 326 | - ~~`huac` can only compile one file at one time.~~ 327 | 328 | - Repl, again 329 | 330 | - Native compiler in hy. 331 | 332 | - Docstring for function definition? It's quite doable because `defn/defun` is just a macro. 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | ## Acknowledgements 342 | 343 | Thanks the authors of [hy](https://github.com/hylang/hy), [lupa](https://github.com/scoder/lupa) and [Typed Lua](https://github.com/andremm/typedlua). They wrote the most crucial parts and I'm just glueing these parts together. -------------------------------------------------------------------------------- /eg/dummy.lua: -------------------------------------------------------------------------------- 1 | local m = {} 2 | 3 | m.dummy1 = 1 4 | m.dummy2 = 2 5 | 6 | return m 7 | -------------------------------------------------------------------------------- /eg/example-mac.hy: -------------------------------------------------------------------------------- 1 | (import [hy.models.string [HyString]]) 2 | 3 | (defmacro defsection [sect-name &rest body] 4 | (def sep (HyString "----------------------------")) 5 | (def end (HyString "------- section ends -------\n")) 6 | `(do 7 | (print ~sect-name) 8 | (print ~sep) 9 | (do ~@body) 10 | (print ~end))) 11 | 12 | -------------------------------------------------------------------------------- /eg/examples.hua: -------------------------------------------------------------------------------- 1 | ;;; Currently you need put these two lines below to the beginning of 2 | ;;; each hua file. As it will requires some core hua macros like 3 | ;;; `let', `for' etc. 4 | 5 | (require-macro "hua.core.initialize") 6 | (--hua-initialize--) 7 | 8 | ;;; Require the macro `defsection' from in hy file "example-mac.hy" 9 | (require-macro "example-mac") 10 | 11 | ;;; Simple assignments and arithmetic 12 | (defsection "Simple Assignments and Arithmetic" 13 | (def x 10) 14 | (setv x 20) 15 | ;; a global variable y is introduced 16 | (setv y 40) 17 | (setv z 20) 18 | 19 | ;; arithmetic operators need not be binary 20 | (print "Some arithmetic operations" 21 | (+ x y z) 22 | (< x y z) ; you can chain comparison operators 23 | (- x))) 24 | 25 | ;;; Table 26 | (defsection "Table as Dict and List" 27 | ;; Lua has table as its only data structure. However in hua we still 28 | ;; have dictionary and list syntax like hy. 29 | (def a-table {"name" "John"}) 30 | (setv a-table.greeting "Hello, ") 31 | (print (concat a-table.greeting 32 | a-table.name 33 | "!")) ; `concat' works like .. in lua 34 | (def a-list [1 2 3 4]) 35 | (print "a-list length:" (len a-list)) 36 | 37 | ;; access table use `get', use integer index for the array part and 38 | ;; string index for the dict part 39 | (setv a-table.l a-list) 40 | (print "a-table.l[3]:" (get a-table "l" 3)) 41 | 42 | ;; change the value of a key in table using assoc 43 | (assoc a-table "l" 3 13) 44 | (print "a-table.l[3] after assoc change the value:" 45 | (get a-table "l" 3))) 46 | 47 | (defsection "Statements" 48 | 49 | ;; multiple assignment works in left hand side, use `unpack' in 50 | ;; right hand side 51 | (def (, m n) (unpack [1 2])) 52 | (print "m, n:" m n) 53 | 54 | ;; `if' and `cond' work like hy 55 | (if (= x y) 56 | (print "x is equal to y") 57 | (print "x is not equal to y")) 58 | 59 | (cond [(< z 20) (print "z is smaller than 20")] 60 | [(= z 20) (print "z is equal to 20")] 61 | [(> z 20) (print "z is larger than 20")] 62 | [true (print "z is 42")]) 63 | 64 | ;; generic `for' works like hy. However table in lua is not 65 | ;; iterable, you need `pairs' or `ipairs' 66 | (for [(, i v) (ipairs [1 3 5])] 67 | (print "index and value in list:" i v)) 68 | 69 | ;; numeric for 70 | (for [i [1 11 2]] 71 | (print "numeric for:" i)) 72 | 73 | ;; You can mix both syntax in one `for' expression 74 | (for [i [1 3] 75 | key (pairs {"pos" 3 "cons" 2})] 76 | (print "mixed:" i key)) 77 | 78 | ;; You can do hy style list and dict comprehensive! They are just 79 | ;; macros expanded to 80 | (def l (list-comp (* i value) 81 | [i [1 10] 82 | (, _ value) (pairs {"T" 1 "F" 0})])) 83 | (for [(, _ v) (ipairs l)] 84 | (print "list comprehensive result:" v))) 85 | 86 | (defsection "Functions" 87 | ;; Function definition 88 | (defn double [n] 89 | (* n 2)) 90 | (print "double of 21:" (double 21)) 91 | 92 | ;; You can use &rest keyword now. I may add support of &optional 93 | ;; later, but lua makes &optional quite redundant. 94 | ;; Notice that &rest arguments cannot contain `nil's 95 | 96 | (defn print-alot [a &rest rest] 97 | (print "first parameter:" a) 98 | (for [(, i para) (ipairs rest)] 99 | (print "rest parameter:" para))) 100 | 101 | (print-alot "That's" "really" "a" "lot") 102 | (print-alot "That's" nil "really" "a" "lot") 103 | 104 | ;; Normal function definition in hua cannot do proper tail calls, 105 | ;; some recursive functions like below will blow the stack. 106 | 107 | (defn test-tco [n] 108 | (if (> n 1) 109 | (test-tco (- n 1)) 110 | (print "May overflow here, read the lua code to find out why"))) 111 | ;; Uncomment the following line to make a stack overflow error. 112 | ;; (test-tco 10000000) 113 | 114 | ;; To overcome this problem use `(return (f))' at the end of a 115 | ;; branch. FIXME: make this part more clear. 116 | 117 | (defn test-tco2 [n] 118 | (if (> n 1) 119 | (return (test-tco2 (- n 1))) 120 | (print "No overflow!"))) 121 | (test-tco2 10000000) 122 | 123 | (defn test-tco3 [n] 124 | (cond [(> n 10) 125 | (return (test-tco3 (- n 2)))] 126 | [(>= 10 n 2) 127 | (return (test-tco3 (- n 1)))] 128 | [true 129 | (print "No overflow too!")])) 130 | (test-tco3 10000000)) 131 | 132 | (defsection "Object Oriented" 133 | 134 | ;; hua's OO system is not very complete yet, for example it 135 | ;; currently lacks something like `isinstance' in python 136 | 137 | (defclass Animal [] 138 | [[--init 139 | (fn [self steps-per-turn] 140 | (setv self.steps-per-turn steps-per-turn))] 141 | [move 142 | (fn [self] 143 | (print (concat "I moved " 144 | (tostring self.steps-per-turn) 145 | " steps!")))]]) 146 | 147 | ;; Hua only support single inheritance. 148 | ;; Use `(super method paras...)' to call parent's method 149 | (defclass Cat [Animal] 150 | [[--init 151 | (fn [self steps-per-turn sound] 152 | (super --init steps-per-turn) 153 | (setv self.sound sound))] 154 | [move 155 | (fn [self] 156 | (print self.sound) 157 | (super move))]]) 158 | 159 | (def a-cat (Cat 3 "meow meow meow!")) 160 | (.move a-cat)) 161 | 162 | 163 | (defsection "Modules" 164 | ;; you can use `require' as in lua, but you can also `hua-import' to 165 | ;; import like hy. 166 | (hua-import [dummy [dummy1 dummy2]]) 167 | (print "Variables in module \"dummy\"" dummy1 dummy2) 168 | 169 | ;; at the end of a file, use `export' to export some local 170 | ;; variable. Of course you can use `return' to manually do that. 171 | (export test-tco3)) 172 | -------------------------------------------------------------------------------- /eg/examples.lua: -------------------------------------------------------------------------------- 1 | package.path = string.format("%s;%s/?.lua",package.path,"/Users/larme/codes/hua/hua/core") 2 | local unpack = (unpack or table.unpack) 3 | local apply = nil 4 | local dec = nil 5 | local first = nil 6 | local inc = nil 7 | local _hua_anon_var_1 8 | do 9 | local __hua_import_m__ = require("hua_stdlib") 10 | apply = __hua_import_m__.apply 11 | dec = __hua_import_m__.dec 12 | first = __hua_import_m__.first 13 | inc = __hua_import_m__.inc 14 | _hua_anon_var_1 = nil 15 | end 16 | print("Simple Assignments and Arithmetic") 17 | print("----------------------------") 18 | local x = 10 19 | x = 20 20 | y = 40 21 | z = 20 22 | print("Some arithmetic operations",(x + y + z),(x < y and y < z), - (x)) 23 | print("------- section ends -------\n") 24 | print("Table as Dict and List") 25 | print("----------------------------") 26 | local a_table = {name = "John"} 27 | a_table.greeting = "Hello, " 28 | print((a_table.greeting .. a_table.name .. "!")) 29 | local a_list = {1, 2, 3, 4} 30 | print("a-list length:",#(a_list)) 31 | a_table.l = a_list 32 | print("a-table.l[3]:",a_table.l[3]) 33 | a_table.l[3] = 13 34 | print("a-table.l[3] after assoc change the value:",a_table.l[3]) 35 | print("------- section ends -------\n") 36 | print("Statements") 37 | print("----------------------------") 38 | local m, n = unpack({1, 2}) 39 | print("m, n:",m,n) 40 | local _hua_anon_var_2 41 | if x == y then 42 | _hua_anon_var_2 = print("x is equal to y") 43 | else 44 | _hua_anon_var_2 = print("x is not equal to y") 45 | end 46 | local _hua_anon_var_6 47 | if z < 20 then 48 | _hua_anon_var_6 = print("z is smaller than 20") 49 | else 50 | local _hua_anon_var_5 51 | if z == 20 then 52 | _hua_anon_var_5 = print("z is equal to 20") 53 | else 54 | local _hua_anon_var_4 55 | if not (z <= 20) then 56 | _hua_anon_var_4 = print("z is larger than 20") 57 | else 58 | local _hua_anon_var_3 59 | if true then 60 | _hua_anon_var_3 = print("z is 42") 61 | else 62 | _hua_anon_var_3 = nil 63 | end 64 | _hua_anon_var_4 = _hua_anon_var_3 65 | end 66 | _hua_anon_var_5 = _hua_anon_var_4 67 | end 68 | _hua_anon_var_6 = _hua_anon_var_5 69 | end 70 | for i, v in ipairs({1, 3, 5}) do 71 | print("index and value in list:",i,v) 72 | end 73 | for i = 1, 11, 2 do 74 | print("numeric for:",i) 75 | end 76 | for i = 1, 3 do 77 | for key in pairs({pos = 3, cons = 2}) do 78 | print("mixed:",i,key) 79 | end 80 | end 81 | local l 82 | do 83 | local _hua_result_1235 = {} 84 | for i = 1, 10 do 85 | for _, value in pairs({T = 1, F = 0}) do 86 | _hua_result_1235[(1 + #(_hua_result_1235))] = (i * value) 87 | end 88 | end 89 | l = _hua_result_1235 90 | end 91 | for _, v in ipairs(l) do 92 | print("list comprehensive result:",v) 93 | end 94 | print("------- section ends -------\n") 95 | print("Functions") 96 | print("----------------------------") 97 | local double = nil 98 | double = function (n) 99 | return (n * 2) 100 | end 101 | print("double of 21:",double(21)) 102 | local print_alot = nil 103 | print_alot = function (a, ...) 104 | local rest = {...} 105 | print("first parameter:",a) 106 | for i, para in ipairs(rest) do 107 | print("rest parameter:",para) 108 | end 109 | end 110 | print_alot("That's","really","a","lot") 111 | print_alot("That's",nil,"really","a","lot") 112 | local test_tco = nil 113 | test_tco = function (n) 114 | local _hua_anon_var_8 115 | if not (n <= 1) then 116 | _hua_anon_var_8 = test_tco((n - 1)) 117 | else 118 | _hua_anon_var_8 = print("May overflow here, read the lua code to find out why") 119 | end 120 | return _hua_anon_var_8 121 | end 122 | local test_tco2 = nil 123 | test_tco2 = function (n) 124 | local _hua_anon_var_9 125 | if not (n <= 1) then 126 | return test_tco2((n - 1)) 127 | else 128 | _hua_anon_var_9 = print("No overflow!") 129 | end 130 | return _hua_anon_var_9 131 | end 132 | test_tco2(10000000) 133 | local test_tco3 = nil 134 | test_tco3 = function (n) 135 | local _hua_anon_var_12 136 | if not (n <= 10) then 137 | return test_tco3((n - 2)) 138 | else 139 | local _hua_anon_var_11 140 | if (not (10 < n) and not (n < 2)) then 141 | return test_tco3((n - 1)) 142 | else 143 | local _hua_anon_var_10 144 | if true then 145 | _hua_anon_var_10 = print("No overflow too!") 146 | else 147 | _hua_anon_var_10 = nil 148 | end 149 | _hua_anon_var_11 = _hua_anon_var_10 150 | end 151 | _hua_anon_var_12 = _hua_anon_var_11 152 | end 153 | return _hua_anon_var_12 154 | end 155 | test_tco3(10000000) 156 | print("------- section ends -------\n") 157 | print("Object Oriented") 158 | print("----------------------------") 159 | local Animal = nil 160 | local _hua_anon_var_15 161 | do 162 | local __hua_parent__ = nil 163 | local __hua_base__ = {__init = function (self, steps_per_turn) 164 | self.steps_per_turn = steps_per_turn 165 | end, move = function (self) 166 | return print(("I moved " .. tostring(self.steps_per_turn) .. " steps!")) 167 | end} 168 | local __hua_class_string__ = "Animal" 169 | __hua_base__.__index = __hua_base__ 170 | local _hua_anon_var_13 171 | if __hua_parent__ then 172 | _hua_anon_var_13 = setmetatable(__hua_base__,__hua_parent__.__base) 173 | else 174 | _hua_anon_var_13 = nil 175 | end 176 | local __hua_cls_call__ = nil 177 | __hua_cls_call__ = function (cls, ...) 178 | local __hua_self__ = setmetatable({},__hua_base__) 179 | __hua_self__:__init(...) 180 | return __hua_self__ 181 | end 182 | local __hua_class__ = setmetatable({__base = __hua_base__, __name = __hua_class_string__, __parent = __hua_parent__},{__call = __hua_cls_call__, __index = __hua_base__}) 183 | __hua_base__.__class = __hua_class__ 184 | local _hua_anon_var_14 185 | if (__hua_parent__ and __hua_parent__.__inherited) then 186 | _hua_anon_var_14 = __hua_parent__.__inherited(__hua_parent__,__hua_class__) 187 | else 188 | _hua_anon_var_14 = nil 189 | end 190 | Animal = __hua_class__ 191 | _hua_anon_var_15 = nil 192 | end 193 | local Cat = nil 194 | local _hua_anon_var_19 195 | do 196 | local __hua_parent__ = Animal 197 | local __hua_base__ = {move = function (self) 198 | print(self.sound) 199 | return __hua_parent__.move(self) 200 | end, __init = function (self, steps_per_turn, sound) 201 | __hua_parent__.__init(self,steps_per_turn) 202 | self.sound = sound 203 | end} 204 | local __hua_class_string__ = "Cat" 205 | __hua_base__.__index = __hua_base__ 206 | local _hua_anon_var_16 207 | if __hua_parent__ then 208 | _hua_anon_var_16 = setmetatable(__hua_base__,__hua_parent__.__base) 209 | else 210 | _hua_anon_var_16 = nil 211 | end 212 | local __hua_cls_call__ = nil 213 | __hua_cls_call__ = function (cls, ...) 214 | local __hua_self__ = setmetatable({},__hua_base__) 215 | __hua_self__:__init(...) 216 | return __hua_self__ 217 | end 218 | local __hua_class__ = setmetatable({__base = __hua_base__, __name = __hua_class_string__, __parent = __hua_parent__},{__call = __hua_cls_call__, __index = function (cls, name) 219 | local val = rawget(__hua_base__,name) 220 | local _hua_anon_var_17 221 | if val == nil then 222 | _hua_anon_var_17 = __hua_parent__[name] 223 | else 224 | _hua_anon_var_17 = val 225 | end 226 | return _hua_anon_var_17 227 | end}) 228 | __hua_base__.__class = __hua_class__ 229 | local _hua_anon_var_18 230 | if (__hua_parent__ and __hua_parent__.__inherited) then 231 | _hua_anon_var_18 = __hua_parent__.__inherited(__hua_parent__,__hua_class__) 232 | else 233 | _hua_anon_var_18 = nil 234 | end 235 | Cat = __hua_class__ 236 | _hua_anon_var_19 = nil 237 | end 238 | local a_cat = Cat(3,"meow meow meow!") 239 | a_cat:move() 240 | print("------- section ends -------\n") 241 | print("Modules") 242 | print("----------------------------") 243 | local dummy1 = nil 244 | local dummy2 = nil 245 | local _hua_anon_var_20 246 | do 247 | local __hua_import_m__ = require("dummy") 248 | dummy1 = __hua_import_m__.dummy1 249 | dummy2 = __hua_import_m__.dummy2 250 | _hua_anon_var_20 = nil 251 | end 252 | print("Variables in module \"dummy\"",dummy1,dummy2) 253 | local _hua_anon_var_21 254 | do 255 | local _hua_module_1236 = {} 256 | _hua_module_1236.test_tco3 = test_tco3 257 | return _hua_module_1236 258 | end 259 | print("------- section ends -------\n") 260 | -------------------------------------------------------------------------------- /hua/__init__.py: -------------------------------------------------------------------------------- 1 | import hy 2 | -------------------------------------------------------------------------------- /hua/cmdline.hy: -------------------------------------------------------------------------------- 1 | (import argparse) 2 | (import sys) 3 | 4 | (import [hua.compiler [compile-file]]) 5 | 6 | (defn huac-main [] 7 | 8 | (def options {"prog" "huac" "usage" "%(prog)s [options] FILE"}) 9 | (def parser (apply argparse.ArgumentParser [] options)) 10 | (apply .add-argument 11 | [parser "args"] 12 | {"nargs" argparse.REMAINDER "help" argparse.SUPPRESS}) 13 | 14 | 15 | (setv options (.parse-args parser (slice sys.argv 1))) 16 | 17 | (for [filename options.args] 18 | (compile-file filename))) 19 | -------------------------------------------------------------------------------- /hua/compiler.hy: -------------------------------------------------------------------------------- 1 | (import os.path) 2 | 3 | (import [hy.models.expression [HyExpression]] 4 | [hy.models.keyword [HyKeyword]] 5 | [hy.models.integer [HyInteger]] 6 | [hy.models.float [HyFloat]] 7 | [hy.models.string [HyString]] 8 | [hy.models.symbol [HySymbol]] 9 | [hy.models.list [HyList]] 10 | [hy.models.dict [HyDict]] 11 | [hy.compiler [checkargs]] 12 | [hy.macros] 13 | [hy.importer [import-file-to-hst]]) 14 | 15 | (import [hua.mlast :as ast] 16 | [hua.lua [tlast->src]]) 17 | 18 | (def -compile-table {}) 19 | 20 | (defn ast-str (s) 21 | (% "%s" s)) 22 | 23 | (defn builds [-type] 24 | "assoc decorated function to compile-table" 25 | (lambda [f] 26 | (assoc -compile-table -type f) 27 | f)) 28 | 29 | (defclass Result [object] 30 | [[--init-- 31 | (fn [self &rest args &kwargs kwargs] 32 | (setv self.stmts []) 33 | (setv self.temp-vars []) 34 | (setv self.-expr nil) 35 | (setv self.--used-expr false) 36 | 37 | (for [kwarg kwargs] 38 | (unless (in kwarg ["stmts" 39 | "expr" 40 | "temp_vars"]) 41 | (print "something wrong")) 42 | (setattr self kwarg (. kwargs [kwarg]))) 43 | 44 | nil)] 45 | 46 | [expr 47 | (with-decorator property 48 | (defn expr [self] 49 | (setv self.--used-expr true) 50 | self.-expr))] 51 | [expr 52 | (with-decorator expr.setter 53 | (defn expr [self value] 54 | (setv self.--used-expr false) 55 | (setv self.-expr value)))] 56 | 57 | [expr? 58 | (fn [self] 59 | "Check whether I am a pure expression" 60 | (and self.-expr 61 | (empty? [])))] 62 | 63 | [force-expr 64 | (with-decorator property 65 | (defn force-expr [self] 66 | "Force the expression context of the Result" 67 | (if self.expr 68 | self.expr 69 | ;; FIXME 70 | (ast.Nil))))] 71 | 72 | [expr-as-stmt 73 | (fn [self] 74 | "Convert the Result's expression context to a statement 75 | 76 | Unlike python, only function/method call can be pure expression statement" 77 | 78 | (if (and self.expr 79 | (instance? ast.Apply self.expr)) 80 | (+ (Result) (apply Result [] {"stmts" [self.expr]})) 81 | (Result)))] 82 | 83 | [rename 84 | (fn [self new-names-] 85 | "Rename the Result's temporary variables to a `new-name`" 86 | (def new-names (if (coll? new-names-) 87 | (list-comp (ast-str new-name-) 88 | [new-name- new-names-]) 89 | [new-names-])) 90 | 91 | (for [var self.temp-vars] 92 | (cond [(instance? ast.Id var) 93 | (setv var.nodes (get new-names 0))] 94 | [(instance? ast.Multi var) 95 | (do 96 | (def new-ids (list-comp (ast.Id new-name) 97 | [new-name new-names])) 98 | (setv var.exprs new-ids))] 99 | [true 100 | (raise "FIXME")])) 101 | (setv self.temp-vars []))] 102 | 103 | [--add-- 104 | (fn [self other] 105 | (cond 106 | 107 | ;; ast.expr case come first because ast.Apply is both statement and expression. By default we will treat them as expression. 108 | [(ast.expr? other) 109 | (+ self (apply Result [] {"expr" other}))] 110 | [(ast.stat? other) 111 | (+ self (apply Result [] {"stmts" [other]}))] 112 | 113 | ;; FIXME 114 | [true 115 | (let [[result (Result)]] 116 | (setv result.stmts (+ self.stmts 117 | other.stmts)) 118 | (setv result.expr other.expr) 119 | (setv result.temp-vars other.temp-vars) 120 | result)]))]]) 121 | 122 | (defn -branch [results-] 123 | "make a branch out of a list of Result objects" 124 | (let [[results (list results-)] 125 | [ret (Result)]] 126 | (for [result (slice results 0 -1)] 127 | (+= ret result) 128 | (+= ret (.expr-as-stmt result))) 129 | (for [result (slice results -1)] 130 | (+= ret result)) 131 | ret)) 132 | 133 | (defn -assign-expr-for-result [result var expr] 134 | "If the result's last statement is not ast.Return, append an ast.Set statement of assigning var to expr to the result." 135 | (when (or (empty? result.stmts) 136 | (not (instance? ast.Return 137 | (get result.stmts -1)))) 138 | (+= result (ast.Set var expr))) 139 | result) 140 | 141 | (defclass HuaASTCompiler [object] 142 | [[--init-- 143 | (fn [self module-name] 144 | (setv self.anon-fn-count 0) 145 | (setv self.anon-var-count 0) 146 | (setv self.module-name module-name) 147 | nil)] 148 | 149 | [get-anon-var 150 | (fn [self] 151 | (+= self.anon-var-count 1) 152 | (% "_hua_anon_var_%s" self.anon-var-count))] 153 | 154 | [get-anon-fn 155 | (fn [self] 156 | (+= self.anon-fn-count 1) 157 | (% "_hua_anon_fn_%s" self.anon-fn-count))] 158 | 159 | [compile-atom 160 | (fn [self atom-type atom] 161 | ;; (print atom-type) 162 | ;; (print atom) 163 | ;; (print (in atom-type -compile-table)) 164 | ;; (print "compile-atom ======") 165 | (when (in atom-type -compile-table) 166 | ;; (print "compile-f: " (get -compile-table atom-type)) 167 | ;; (print "atom: " atom) 168 | ;; (print "\n") 169 | (let [[compile-f (get -compile-table atom-type)] 170 | [ret (compile-f self atom)]] 171 | (if (instance? Result ret) 172 | ret 173 | (+ (Result) ret)))))] 174 | 175 | [compile 176 | (fn [self tree] 177 | ;;; FIXME compiler errors 178 | ;; (print "compile =====") 179 | (let [[-type (type tree)]] 180 | (.compile-atom self -type tree)))] 181 | 182 | [-compile-collect 183 | (fn [self exprs] 184 | "Collect the expression contexts from a list of compiled expression." 185 | (let [[compiled-exprs []] 186 | [ret (Result)]] 187 | (for [expr exprs] 188 | (+= ret (.compile self expr)) 189 | (.append compiled-exprs ret.force_expr)) 190 | (, compiled-exprs ret)))] 191 | 192 | [-compile-branch 193 | (fn [self exprs] 194 | (-branch (list-comp (.compile self expr) [expr exprs])))] 195 | 196 | ;;; FIXME no keyword and kwargs yet, maybe never 197 | [-parse-lambda-list 198 | (fn [self exprs] 199 | (def ll-keywords (, "&rest" "&optional")) 200 | (def ret (Result)) 201 | (def args []) 202 | (def defaults []) 203 | (def varargs nil) 204 | (def lambda-keyword nil) 205 | (for [expr exprs] 206 | (if (in expr ll-keywords) 207 | ;; FIXME &optional error handling 208 | (setv lambda-keyword expr) 209 | (cond 210 | [(nil? lambda-keyword) 211 | (.append args expr)] 212 | [(= lambda-keyword "&rest") 213 | (if (nil? varargs) 214 | (setv varargs (str expr)) 215 | (print "FIXME only one &rest error"))] 216 | [(= lambda-keyword "&optional") 217 | (do 218 | (if (instance? HyList expr) 219 | (if (not (= 2 (len expr))) 220 | (print "FIXME optinal rags hould be bare names" 221 | "or 2-item lists") 222 | (setv (, k v) expr)) 223 | (do 224 | (setv k expr) 225 | (setv v (.replace (HySymbol "nil") k)))) 226 | (.append args k) 227 | (+= ret (.compile self v)) 228 | (.append defaults ret.force_expr))]))) 229 | (, ret args defaults varargs))] 230 | 231 | 232 | ;;; FIXME _storeize do we really need this? 233 | [-storeize 234 | (fn [self name] 235 | (if-not (.expr? name) 236 | (print "FIXME: type error") 237 | (setv name name.expr)) 238 | 239 | ;;; FIXME multiple assign, index etc. 240 | (cond [(instance? (, ast.Id ast.Index ast.Multi) name) 241 | name] 242 | [true 243 | (print "FIXME: type error")]))] 244 | 245 | [compile-raw-list 246 | (with-decorator (builds list) 247 | (fn [self entries] 248 | (let [[ret (.-compile-branch self entries)]] 249 | (+= ret (.expr-as-stmt ret)) 250 | ret)))] 251 | 252 | ;;; FIXME quote related. or no quote because we don't have macro? 253 | 254 | ;;; FIXME a lot of functions in between 255 | 256 | [compile-progn 257 | (with-decorator (builds "do") (builds "progn") 258 | (fn [self expression] 259 | (.pop expression 0) 260 | (.-compile-branch self expression)))] 261 | 262 | [compile-do-block 263 | (with-decorator (builds "do_block") 264 | (fn [self expression] 265 | (.pop expression 0) 266 | (def branch (.-compile-branch self expression)) 267 | (def var-name (.get-anon-var self)) 268 | (def var (ast.Multi (ast.Id var-name))) 269 | (setv branch 270 | (-assign-expr-for-result branch var branch.force-expr)) 271 | (+ (Result) 272 | (ast.Local var) 273 | (ast.Do branch.stmts) 274 | (apply Result 275 | [] 276 | {"expr" var "temp_vars" [var]}))))] 277 | 278 | [compile-if 279 | (with-decorator 280 | (builds "if") 281 | (apply checkargs [] {"min" 2 "max" 3}) 282 | (fn [self expression] 283 | (.pop expression 0) 284 | (let [[condition (.compile self (.pop expression 0))] 285 | [body (.compile self (.pop expression 0))] 286 | [orel (if (empty? expression) 287 | (Result) 288 | (.compile self (.pop expression 0)))] 289 | [ret condition] 290 | 291 | [var-name (.get-anon-var self)] 292 | [var (ast.Multi (ast.Id var-name))] 293 | 294 | [expr-name (ast.Multi (ast.Id (ast-str var-name)))]] 295 | 296 | ;; we won't test if statements in body or orel because lua doesn't have official ternary operator support 297 | 298 | ;; (+= ret (ast.Local [var])) 299 | (setv ret (+ (Result) (ast.Local var) ret)) 300 | (setv body 301 | (-assign-expr-for-result body var body.force-expr)) 302 | (setv orel 303 | (-assign-expr-for-result orel var orel.force-expr)) 304 | (+= ret (ast.If ret.force-expr body.stmts orel.stmts)) 305 | (+= ret (apply Result [] 306 | {"expr" expr-name "temp_vars" [expr-name 307 | var]})) 308 | ret 309 | )))] 310 | 311 | ;;; FIXME break, assert etc. 312 | 313 | ;;; FIXME import/require 314 | 315 | [compile-index-expression 316 | (with-decorator 317 | (builds "get") 318 | (apply checkargs [] {"min" 2}) 319 | (fn [self expr] 320 | (.pop expr 0) 321 | 322 | (def val (.compile self (.pop expr 0))) 323 | (def (, indexes ret) (.-compile-collect self expr)) 324 | 325 | (when (not (empty? val.stmts)) 326 | (+= ret val)) 327 | 328 | (for [index indexes] 329 | (setv val (+ (Result) 330 | (ast.Index 331 | (let [[val-expr val.force-expr]] 332 | ;; if val.force-expr is a literal table, we 333 | ;; need a pair of parentheses around the 334 | ;; literal table to make the index work 335 | (if (instance? ast.Table val-expr) 336 | (ast.Paren val-expr) 337 | val-expr)) 338 | index)))) 339 | (+ ret val)))] 340 | 341 | [compile-multi 342 | (with-decorator (builds ",") 343 | (fn [self expr] 344 | (.pop expr 0) 345 | (def (, elts ret) (.-compile-collect self expr)) 346 | (def multi (ast.Multi elts)) 347 | (+= ret multi) 348 | ret))] 349 | 350 | [compile-require-macro 351 | (with-decorator (builds "require_macro") 352 | (fn [self expression] 353 | (.pop expression 0) 354 | (for [entry expression] 355 | (--import-- entry) 356 | (hy.macros.require entry self.module-name)) 357 | (Result)))] 358 | 359 | [compile-compare-op-expression 360 | (with-decorator 361 | (builds "=*") 362 | (builds "<*") 363 | (builds "<=*") 364 | (checkargs 2) 365 | (fn [self expression] 366 | (def operator (.pop expression 0)) 367 | (def op-id (ast.get-op-id operator)) 368 | (def (, exprs ret) (.-compile-collect self expression)) 369 | (+ ret (ast.Op op-id 370 | (get exprs 0) 371 | (get exprs 1)))))] 372 | 373 | [compile-unary-operator 374 | (with-decorator 375 | (builds "not" ) 376 | (builds "len") 377 | (checkargs 1) 378 | (fn [self expression] 379 | (def operator (.pop expression 0)) 380 | (def op-id (ast.get-op-id operator)) 381 | (def operand (.compile self (.pop expression 0))) 382 | (+= operand (ast.Op op-id operand.expr)) 383 | operand))] 384 | 385 | [compile-binary-operators 386 | (with-decorator 387 | (builds "and") 388 | (builds "or") 389 | (builds "%") 390 | (builds "/") 391 | (builds "//") 392 | (builds "^") 393 | ;; bitwise for lua 5.3 394 | (builds "|") 395 | (builds "bor") 396 | (builds "&") 397 | (builds "<<") 398 | (builds ">>") 399 | (builds "concat") 400 | (fn [self expression] 401 | (def operator (.pop expression 0)) 402 | (def op-id (ast.get-op-id operator)) 403 | 404 | (def ret (.compile self (.pop expression 0))) 405 | 406 | (for [child expression] 407 | (def left-expr ret.force-expr) 408 | (+= ret (.compile self child)) 409 | (def right-expr ret.force-expr) 410 | (+= ret (ast.Op op-id left-expr right-expr))) 411 | (+ ret (ast.Paren ret.expr))))] 412 | 413 | [compile-add-and-mul-expression 414 | (with-decorator 415 | (builds "+") 416 | (builds "*") 417 | (fn [self expression] 418 | (if (> (len expression) 2) 419 | (.compile-binary-operators self expression) 420 | (do 421 | (def id-op {"+" (HyInteger 0) "*" (HyInteger 1)}) 422 | (def op (.pop expression 0)) 423 | (def arg (if (empty? expression) 424 | (get id-op op) 425 | (.pop expression 0))) 426 | (def expr (.replace (HyExpression [(HySymbol op) 427 | (get id-op op) 428 | arg]) 429 | expression)) 430 | (.compile-binary-operators self expr)))))] 431 | 432 | [compile-sub-expression 433 | (with-decorator 434 | (builds "-") 435 | (fn [self expression] 436 | (if (> (len expression) 2) 437 | (.compile-binary-operators self expression) 438 | (do 439 | (def arg (get expression 1)) 440 | (def ret (.compile self arg)) 441 | (+= ret (ast.Op "sub" ret.force-expr)) 442 | ret))))] 443 | 444 | [compile-expression 445 | (with-decorator (builds HyExpression) 446 | (fn [self expression] 447 | ;;; FIXME: macroexpand and "." and a lot more 448 | (setv expression (hy.macros.macroexpand expression 449 | self.module-name)) 450 | (cond [(not (instance? HyExpression expression)) 451 | (.compile self expression)] 452 | [(= expression []) 453 | (.compile-list self expression)] 454 | [true 455 | (let [[fun (get expression 0)]] 456 | (cond [(instance? HyKeyword fun) 457 | (print "FIXME: keyword call")] 458 | [(instance? HyString fun) 459 | (do 460 | (setv ret (.compile-atom self fun expression)) 461 | (if (not (nil? ret)) 462 | ret 463 | (.-compile-fun-call self expression)))] 464 | [true 465 | (let [[func (.compile self fun)]] 466 | (def (, args ret) 467 | (.-compile-collect self 468 | (slice expression 1))) 469 | (def call (ast.Call func.expr 470 | args)) 471 | (+ func ret call))]))])))] 472 | 473 | [-compile-fun-call 474 | (fn [self expression] 475 | (setv fun (get expression 0)) 476 | (setv func nil) 477 | (setv method-call? false) 478 | 479 | (when (.startswith fun ".") 480 | (setv method-call? true) 481 | (setv ofun fun) 482 | (setv fun (HySymbol (slice ofun 1))) 483 | (.replace fun ofun) 484 | 485 | (when (< (len expression) 2) 486 | (print "FIXME error message")) 487 | 488 | ;; FIXME: this line should we ensure the compiled result is a string? 489 | (setv method-name (ast.String (ast-str fun))) 490 | (setv func (.compile self (.pop expression 1)))) 491 | (when (nil? func) 492 | (setv func (.compile self fun))) 493 | 494 | ;; FIXME: no kwargs for lua? 495 | (setv (, args ret) (.-compile-collect self 496 | (slice expression 1))) 497 | 498 | (setv call (if method-call? 499 | (ast.Invoke func.expr 500 | method-name 501 | args) 502 | (ast.Call func.expr 503 | args))) 504 | 505 | (+ func ret call))] 506 | 507 | [compile-def-expression 508 | (with-decorator 509 | (builds "def") 510 | (checkargs 2) 511 | (fn [self expression] 512 | (.-compile-define self 513 | (get expression 1) 514 | (get expression 2))))] 515 | 516 | [-compile-define 517 | (fn [self name result] 518 | (setv str-name (% "%s" name)) 519 | 520 | ;;; FIXME test builtin 521 | (setv result (.compile self result)) 522 | (setv ident (.compile self name)) 523 | 524 | (if (and (empty? ident.stmts) 525 | (instance? (, ast.Multi ast.Id) ident.expr)) 526 | (setv ident ident.expr) 527 | (raise "FIXME: identities required")) 528 | 529 | (if (empty? result.temp-vars) 530 | (+= result (ast.Local ident 531 | result.force-expr)) 532 | (.rename result (if (instance? ast.Id ident) 533 | ident.name 534 | (list-comp (. idn name) 535 | [idn ident.nodes])))) 536 | 537 | (+= result ident) 538 | result)] 539 | 540 | [compile-setv-expression 541 | (with-decorator 542 | (builds "setv") 543 | (checkargs 2) 544 | (fn [self expression] 545 | (let [[name (get expression 1)] 546 | [result (get expression 2)]] 547 | (setv result (.compile self result)) 548 | (setv ld-name (.compile self name)) 549 | 550 | (when (and (instance? ast.Multi ld-name.expr) 551 | (not (empty? result.temp-vars))) 552 | (.rename result 553 | (list-comp (.get-anon-var self) 554 | [i (range (.count ld-name.expr))]))) 555 | 556 | ;; FIXME do we need this? (setv st-name (.-storeize self ld-name)) 557 | 558 | (setv result (+ result 559 | (ast.Set [ld-name.expr] 560 | [result.force-expr]) 561 | ld-name)) 562 | result)))] 563 | 564 | [compile-for-expression 565 | (with-decorator 566 | (builds "for*") 567 | (apply checkargs [] {"min" 1}) 568 | (fn [self expression] 569 | (.pop expression 0) 570 | (def args (.pop expression 0)) 571 | (when (not (instance? HyList args)) 572 | (raise (.format "FIXME for expects a list, received `{0}'" 573 | (. (type args) --name--)))) 574 | ;; FIXME for args number checkign 575 | (def (, target-name iterable) args) 576 | 577 | (def target (.-storeize self (.compile self target-name))) 578 | 579 | (def body (.-compile-branch self expression)) 580 | (+= body (.expr-as-stmt body)) 581 | 582 | (def ret (Result)) 583 | (+= ret (.compile self iterable)) 584 | 585 | ;; two form of for 586 | ;; generic for: (for* [expr iterable] body) 587 | ;; numeric for: (for* [expr [init final step]] body) 588 | 589 | (if (is HyList (type iterable)) ; this looks ugly, but it prevent HyExpression go into the true branch 590 | (+= ret (ast.Fornum target ret.force-expr.nodes body.stmts)) 591 | (+= ret (ast.Forin target ret.force-expr body.stmts))) 592 | ret))] 593 | 594 | [compile-integer 595 | (with-decorator (builds HyInteger) 596 | (fn [self number] 597 | (ast.Number number)))] 598 | 599 | [compile-float 600 | (with-decorator (builds HyFloat) 601 | (fn [self number] 602 | (ast.Number number)))] 603 | 604 | [compile-string 605 | (with-decorator (builds HyString) 606 | (fn [self string] 607 | (ast.String string)))] 608 | 609 | [compile-symbol 610 | (with-decorator (builds HySymbol) 611 | (fn [self symbol] 612 | ;;; FIXME more complex case 613 | 614 | (if (in "." symbol) 615 | (do 616 | (setv (, glob local) (.rsplit symbol "." 1)) 617 | (setv glob (.replace (HySymbol glob) symbol)) 618 | (setv ret (.compile-symbol self glob)) 619 | (setv ret (ast.Index ret (ast.String (ast-str local)))) 620 | ret) 621 | (cond 622 | [(= symbol "True") (ast.MLTrue)] 623 | [(= symbol "False") (ast.MLFalse)] 624 | [(or (= symbol "None") 625 | (= symbol "nil")) 626 | (ast.Nil)] 627 | [(= symbol "DOTDOTDOT") (ast.Dots)] 628 | [true (ast.Id (ast-str symbol))]))))] 629 | 630 | [compile-list 631 | (with-decorator (builds HyList) 632 | (fn [self expression] 633 | (setv (, elts ret) (.-compile-collect self expression)) 634 | (+= ret (ast.Table elts nil)) 635 | ret))] 636 | 637 | [compile-function-def 638 | (with-decorator 639 | (builds "lambda") 640 | (builds "fn") 641 | (apply checkargs [] {"min" 1}) 642 | (fn [self expression] 643 | (.pop expression 0) 644 | (def arglist (.pop expression 0)) 645 | (def (, ret -args defaults vararg) 646 | (.-parse-lambda-list self arglist)) 647 | (def args (list-comp (. (.compile self arg) expr) 648 | [arg -args])) 649 | (def body (Result)) 650 | 651 | ;; FIXME &optional parameters 652 | ;; use var = var == nil and default_value or var 653 | 654 | (when vararg 655 | (.append args (ast.Dots)) 656 | (+= body (apply Result 657 | [] 658 | {"stmts" [(ast.Local [(ast.Id vararg)] 659 | [(ast.Table 660 | [(ast.Dots)] 661 | nil)])]}))) 662 | 663 | (+= body (.-compile-branch self expression)) 664 | 665 | (when body.expr 666 | (+= body (ast.Return body.expr))) 667 | 668 | (+= ret (ast.Function args 669 | body.stmts)) 670 | ret))] 671 | 672 | [compile-return 673 | (with-decorator 674 | (builds "return") 675 | (apply checkargs [] {"min" 1}) 676 | (fn [self expression] 677 | (.pop expression 0) 678 | (def (, return-exprs ret) (.-compile-collect self expression)) 679 | (+ ret (ast.Return return-exprs))))] 680 | 681 | [compile-dispatch-reader-macro 682 | (with-decorator 683 | (builds "dispatch_reader_macro") 684 | (checkargs 2) 685 | (fn [self expression] 686 | (.pop expression 0) 687 | (def str-char (.pop expression 0)) 688 | (when (not (type str-char)) 689 | (raise "FIXME")) 690 | 691 | (def module self.module-name) 692 | (def expr (hy.macros.reader-macroexpand str-char 693 | (.pop expression 0) 694 | module)) 695 | (.compile self expr)))] 696 | 697 | [compile-dict 698 | (with-decorator (builds HyDict) 699 | (fn [self m] 700 | (def (, kv ret) (.-compile-collect self m)) 701 | (def length (len kv)) 702 | (if (= length 1) 703 | (+= ret (ast.Table kv nil)) 704 | (do 705 | (setv half-length (int (/ length 2))) 706 | (setv hash-part (dict-comp (get kv (* 2 i)) 707 | (get kv (inc (* 2 i))) 708 | [i (range half-length)])) 709 | (+= ret (ast.Table nil hash-part)) )) 710 | 711 | ret))]]) 712 | 713 | (defn compile-file-to-string [filename] 714 | (def metalua-ast (let [[hst (import-file-to-hst filename)] 715 | ;; use filename as module name here since the 716 | ;; only function of module name in hua 717 | ;; compiler is to track which file requires 718 | ;; which macros 719 | [compiler (HuaASTCompiler filename)]] 720 | (.compile compiler hst))) 721 | (def stmts (ast.to-ml-table metalua-ast.stmts)) 722 | (tlast->src stmts)) 723 | 724 | (defn compile-file [filename] 725 | (def result (compile-file-to-string filename)) 726 | (def (, basename extname) (os.path.splitext filename)) 727 | (def lua-filename (+ basename ".lua")) 728 | (with [[lua-f (open lua-filename "w")]] 729 | (.write lua-f result))) 730 | 731 | -------------------------------------------------------------------------------- /hua/core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/larme/hua/c7462e691fc1e5196fd476b4d75a8a280701190f/hua/core/__init__.py -------------------------------------------------------------------------------- /hua/core/assignment.hy: -------------------------------------------------------------------------------- 1 | (import [hua.core.utils [hua-gensym simple-form?]]) 2 | 3 | (defmacro assoc [table &rest indexes-and-value] 4 | (def last-index (dec (len indexes-and-value))) 5 | (def indexes (slice indexes-and-value 6 | 0 7 | last-index)) 8 | (def value (get indexes-and-value last-index)) 9 | `(setv (get ~table ~@indexes) ~value)) 10 | 11 | (defmacro --hua-augmented-assignment-- [op op-assign] 12 | `(defmacro ~op-assign [target val] 13 | (def op-symbol (quote ~op)) 14 | (if (simple-form? target) 15 | `(setv ~target (~op-symbol ~target ~val)) 16 | (macro-error target 17 | "Sorry, currently the first argument of augmented assignment can be only a symbol")))) 18 | 19 | (--hua-augmented-assignment-- + +=) 20 | (--hua-augmented-assignment-- - -=) 21 | (--hua-augmented-assignment-- * *=) 22 | (--hua-augmented-assignment-- / /=) 23 | -------------------------------------------------------------------------------- /hua/core/comp.hy: -------------------------------------------------------------------------------- 1 | (import [hua.core.utils [hua-gensym]]) 2 | 3 | (defmacro list-comp [expr gen &optional condition] 4 | (def temp-var-l (hua-gensym "result")) 5 | (def result-assignment 6 | `(assoc ~temp-var-l 7 | (+ 1 (len ~temp-var-l)) 8 | ~expr)) 9 | (def for-body (if condition 10 | `(when ~condition 11 | ~result-assignment) 12 | `(do 13 | ~result-assignment))) 14 | `(let [[~temp-var-l []]] 15 | (for ~gen 16 | ~for-body) 17 | ~temp-var-l)) 18 | 19 | (defmacro dict-comp [key-expr value-expr gen &optional condition] 20 | (def temp-var-l (hua-gensym "result")) 21 | (def result-assignment 22 | `(assoc ~temp-var-l 23 | ~key-expr 24 | ~value-expr)) 25 | (def for-body (if condition 26 | `(when ~condition 27 | ~result-assignment) 28 | `(do 29 | ~result-assignment))) 30 | `(let [[~temp-var-l []]] 31 | (for ~gen 32 | ~for-body) 33 | ~temp-var-l)) 34 | -------------------------------------------------------------------------------- /hua/core/hua_stdlib.hua: -------------------------------------------------------------------------------- 1 | (require-macro hua.core.macros) 2 | (require-macro hua.core.assignment) 3 | (require-macro hua.core.module) 4 | 5 | (hua-import [moses [clone 6 | isArray 7 | isTable 8 | isEqual 9 | isCallable 10 | isIterable 11 | isString 12 | isFunction 13 | isNil 14 | isNumber 15 | isNaN 16 | isFinite 17 | isBoolean 18 | isInteger]]) 19 | 20 | (defn apply [f args] 21 | (f (unpack args))) 22 | 23 | (defn dec [n] 24 | (- n 1)) 25 | 26 | (defn first [tbl] 27 | (get tbl 1)) 28 | 29 | (defn inc [n] 30 | (+ n 1)) 31 | 32 | 33 | ;;; core functions from moses 34 | 35 | (def array? isArray) 36 | (def table? isTable) 37 | (def equal? isEqual) 38 | (def callable? isCallable) 39 | (def iterable? isIterable) 40 | (def string? isString) 41 | (def function? isFunction) 42 | (def nil? isNil) 43 | (def number? isNumber) 44 | (def nan? isNaN) 45 | (def finite? isFinite) 46 | (def boolean? isBoolean) 47 | (def int? isInteger) 48 | 49 | (export apply 50 | dec 51 | first 52 | inc 53 | 54 | ;; moses functions 55 | clone 56 | array? 57 | table? 58 | equal? 59 | callable? 60 | iterable? 61 | string? 62 | function? 63 | nil? 64 | number? 65 | nan? 66 | finite? 67 | boolean? 68 | int? 69 | ) 70 | -------------------------------------------------------------------------------- /hua/core/hua_stdlib.lua: -------------------------------------------------------------------------------- 1 | local clone = nil 2 | local isArray = nil 3 | local isTable = nil 4 | local isEqual = nil 5 | local isCallable = nil 6 | local isIterable = nil 7 | local isString = nil 8 | local isFunction = nil 9 | local isNil = nil 10 | local isNumber = nil 11 | local isNaN = nil 12 | local isFinite = nil 13 | local isBoolean = nil 14 | local isInteger = nil 15 | local _hua_anon_var_1 16 | do 17 | local __hua_import_m__ = require("moses") 18 | clone = __hua_import_m__["clone"] 19 | isArray = __hua_import_m__["isArray"] 20 | isTable = __hua_import_m__["isTable"] 21 | isEqual = __hua_import_m__["isEqual"] 22 | isCallable = __hua_import_m__["isCallable"] 23 | isIterable = __hua_import_m__["isIterable"] 24 | isString = __hua_import_m__["isString"] 25 | isFunction = __hua_import_m__["isFunction"] 26 | isNil = __hua_import_m__["isNil"] 27 | isNumber = __hua_import_m__["isNumber"] 28 | isNaN = __hua_import_m__["isNaN"] 29 | isFinite = __hua_import_m__["isFinite"] 30 | isBoolean = __hua_import_m__["isBoolean"] 31 | isInteger = __hua_import_m__["isInteger"] 32 | _hua_anon_var_1 = isInteger 33 | end 34 | local apply = nil 35 | apply = function (f, args) 36 | return f(unpack(args)) 37 | end 38 | local dec = nil 39 | dec = function (n) 40 | return (n - 1) 41 | end 42 | local first = nil 43 | first = function (tbl) 44 | return tbl[1] 45 | end 46 | local inc = nil 47 | inc = function (n) 48 | return (n + 1) 49 | end 50 | local is_array = isArray 51 | local is_table = isTable 52 | local is_equal = isEqual 53 | local is_callable = isCallable 54 | local is_iterable = isIterable 55 | local is_string = isString 56 | local is_function = isFunction 57 | local is_nil = isNil 58 | local is_number = isNumber 59 | local is_nan = isNaN 60 | local is_finite = isFinite 61 | local is_boolean = isBoolean 62 | local is_int = isInteger 63 | local _hua_anon_var_2 64 | do 65 | local _hua_module_1235 = {} 66 | _hua_module_1235["apply"] = apply 67 | _hua_module_1235["dec"] = dec 68 | _hua_module_1235["first"] = first 69 | _hua_module_1235["inc"] = inc 70 | _hua_module_1235["clone"] = clone 71 | _hua_module_1235["is_array"] = is_array 72 | _hua_module_1235["is_table"] = is_table 73 | _hua_module_1235["is_equal"] = is_equal 74 | _hua_module_1235["is_callable"] = is_callable 75 | _hua_module_1235["is_iterable"] = is_iterable 76 | _hua_module_1235["is_string"] = is_string 77 | _hua_module_1235["is_function"] = is_function 78 | _hua_module_1235["is_nil"] = is_nil 79 | _hua_module_1235["is_number"] = is_number 80 | _hua_module_1235["is_nan"] = is_nan 81 | _hua_module_1235["is_finite"] = is_finite 82 | _hua_module_1235["is_boolean"] = is_boolean 83 | _hua_module_1235["is_int"] = is_int 84 | return _hua_module_1235 85 | end 86 | -------------------------------------------------------------------------------- /hua/core/initialize.hy: -------------------------------------------------------------------------------- 1 | (import os) 2 | (import [hy.models.string [HyString]]) 3 | 4 | ;;; In case the future lua release removes the unpack function entirely 5 | 6 | (defmacro --hua-def-unpack-- [] 7 | `(def unpack (or unpack table.unpack))) 8 | 9 | 10 | ;;; add the current path in the lua load path 11 | 12 | (defmacro --hua-add-stdlib-path-- [] 13 | (def this-path 14 | (HyString 15 | (os.path.dirname 16 | (os.path.realpath --file--)))) 17 | `(setv package.path 18 | (string.format "%s;%s/?.lua" 19 | package.path 20 | ~this-path))) 21 | 22 | ;;; import standard library 23 | (defmacro --hua-import-stdlib-- [] 24 | `(hua-import [hua_stdlib [apply 25 | dec 26 | first 27 | inc 28 | 29 | ;; moses functions 30 | clone 31 | array? 32 | table? 33 | equal? 34 | callable? 35 | iterable? 36 | string? 37 | function? 38 | nil? 39 | number? 40 | nan? 41 | finite? 42 | boolean? 43 | int?]])) 44 | 45 | ;;; a macro to import all core macros 46 | 47 | (defmacro --hua-init-macros-- [] 48 | `(do 49 | (require-macro hua.core.macros) 50 | (require-macro hua.core.op) 51 | (require-macro hua.core.assignment) 52 | (require-macro hua.core.comp) 53 | (require-macro hua.core.oo) 54 | (require-macro hua.core.module))) 55 | 56 | 57 | (defmacro --hua-initialize-- [] 58 | `(do 59 | (--hua-add-stdlib-path--) 60 | (--hua-def-unpack--) 61 | (--hua-init-macros--) 62 | (--hua-import-stdlib--))) 63 | -------------------------------------------------------------------------------- /hua/core/macros.hy: -------------------------------------------------------------------------------- 1 | (import [hy.models.list [HyList]] 2 | [hy.models.symbol [HySymbol]]) 3 | 4 | (defn for-helper [body] 5 | (defn for-helper* [args-iter] 6 | (try 7 | `(for* [~(next args-iter) 8 | ~(next args-iter)] 9 | ~(for-helper* args-iter)) 10 | (catch [e StopIteration] 11 | `(progn ~@body))))) 12 | 13 | (defmacro for [args &rest body] 14 | "shorthand for nested for loops: 15 | (for [x foo 16 | y bar] 17 | baz) -> 18 | (for* [x foo] 19 | (for* [y bar] 20 | baz))" 21 | (cond 22 | [(odd? (len args)) 23 | (macro-error args "`for' requires an even number of args.")] 24 | [(empty? body) 25 | (macro-error None "`for' requires a body to evaluate")] 26 | [(empty? args) `(do ~@body)] 27 | [(= (len args) 2) `(for* [~@args] ~@body)] 28 | [true 29 | (do 30 | (def args-iter (iter args)) 31 | ((for-helper body) args-iter))])) 32 | 33 | (defmacro let [variables &rest body] 34 | "Execute `body` in the lexical context of `variables`" 35 | (def macroed-variables []) 36 | (if (not (isinstance variables HyList)) 37 | (macro-error variables "let lexical context must be a list")) 38 | (for* [variable variables] 39 | (if (isinstance variable HyList) 40 | (do 41 | (if (!= (len variable) 2) 42 | (macro-error variable "let variable assignments must contain two items")) 43 | (.append macroed-variables `(def ~(get variable 0) ~(get variable 1)))) 44 | (if (isinstance variable HySymbol) 45 | (.append macroed-variables `(def ~variable None)) 46 | (macro-error variable "let lexical context element must be a list or symbol")))) 47 | `(do-block ~@macroed-variables 48 | ~@body)) 49 | 50 | 51 | (defmacro-alias [defn defun] [name lambda-list &rest body] 52 | "define a function `name` with signature `lambda-list` and body `body`" 53 | (if (not (= (type name) HySymbol)) 54 | (macro-error name "defn/defun takes a name as first argument")) 55 | (if (not (isinstance lambda-list HyList)) 56 | (macro-error name "defn/defun takes a parameter list as second argument")) 57 | `(do 58 | (def ~name nil) 59 | (setv ~name (fn ~lambda-list ~@body)))) 60 | 61 | -------------------------------------------------------------------------------- /hua/core/module.hy: -------------------------------------------------------------------------------- 1 | (import [hy.models.list [HyList]] 2 | [hy.models.string [HyString]] 3 | [hy.models.symbol [HySymbol]] 4 | [hua.core.utils [hua-gensym]]) 5 | 6 | (defn import-helper [item] 7 | (when (not (instance? (, HySymbol HyList) item)) 8 | (macro-error item "(import) requires a Symbol or a List")) 9 | 10 | (if (instance? HySymbol item) 11 | `(def ~item (require ~(HyString item))) 12 | (cond [(= 2 (len item)) 13 | (let [[module (get item 0)] 14 | [syms (get item 1)] 15 | [declares (list-comp `(def ~sym nil) 16 | [sym syms])] 17 | [bindings (list-comp 18 | `(setv ~sym 19 | (get --hua-import-m-- 20 | ~(HyString sym))) 21 | [sym syms])]] 22 | `(do 23 | ~@declares 24 | (let [[--hua-import-m-- (require ~(HyString module))]] 25 | ~@bindings)))] 26 | [(and (= 3 (len item)) 27 | (= (get item 1) :as)) 28 | (let [[module (get item 0)] 29 | [alias (get item 2)]] 30 | `(def ~alias (require ~(HyString module))))] 31 | [true 32 | (macro-error item 33 | "When the argument of (import) is a List, it need to be in one of the two following form: 1. [module [var1 var2 ...]] or 2. [module :as alias]")]))) 34 | 35 | ;;; well, before we have a way to name it import 36 | (defmacro hua-import [&rest items] 37 | (def body (list-comp (import-helper item) [item items])) 38 | `(do ~@body)) 39 | 40 | 41 | ;;; export a list o variables, use at the end of a hua file 42 | (defmacro export [&rest vars] 43 | (def module-var (hua-gensym "module")) 44 | (def assignments (list-comp `(assoc ~module-var 45 | ~(HyString var) 46 | ~var) 47 | [var vars])) 48 | `(let [[~module-var {}]] 49 | (do ~@assignments) 50 | (return ~module-var))) 51 | -------------------------------------------------------------------------------- /hua/core/moses.lua: -------------------------------------------------------------------------------- 1 | --- *Utility-belt library for functional programming in Lua.*
2 | -- Source on [Github](http://github.com/Yonaba/Moses) 3 | -- @author [Roland Yonaba](http://github.com/Yonaba) 4 | -- @copyright 2012-2014 5 | -- @license [MIT](http://www.opensource.org/licenses/mit-license.php) 6 | -- @release 1.4.0 7 | -- @module moses 8 | 9 | local _MODULEVERSION = '1.4.0' 10 | 11 | -- Internalisation 12 | local next, type, unpack, select, pcall = next, type, unpack, select, pcall 13 | local setmetatable, getmetatable = setmetatable, getmetatable 14 | local t_insert, t_sort = table.insert, table.sort 15 | local t_remove,t_concat = table.remove, table.concat 16 | local randomseed, random, huge = math.randomseed, math.random, math.huge 17 | local floor, max, min = math.floor, math.max, math.min 18 | local rawget = rawget 19 | local unpack = unpack 20 | local pairs,ipairs = pairs,ipairs 21 | local _ = {} 22 | 23 | 24 | -- ======== Private helpers 25 | 26 | local function f_max(a,b) return a>b end 27 | local function f_min(a,b) return ab and b or var) end 29 | local function isTrue(_,value) return value and true end 30 | local function iNot(value) return not value end 31 | 32 | local function count(t) -- raw count of items in an map-table 33 | local i = 0 34 | for k,v in pairs(t) do i = i + 1 end 35 | return i 36 | end 37 | 38 | local function extract(list,comp,transform,...) -- extracts value from a list 39 | local _ans 40 | local transform = transform or _.identity 41 | for index,value in pairs(list) do 42 | if not _ans then _ans = transform(value,...) 43 | else 44 | local value = transform(value,...) 45 | _ans = comp(_ans,value) and _ans or value 46 | end 47 | end 48 | return _ans 49 | end 50 | 51 | local function partgen(t, n, f) -- generates array partitions 52 | for i = 0, #t, n do 53 | local s = _.slice(t, i+1, i+n) 54 | if #s>0 then f(s) end 55 | end 56 | end 57 | 58 | local function permgen(t, n, f) -- taken from PiL: http://www.lua.org/pil/9.3.html 59 | if n == 0 then f(t) end 60 | for i = 1,n do 61 | t[n], t[i] = t[i], t[n] 62 | permgen(t, n-1, f) 63 | t[n], t[i] = t[i], t[n] 64 | end 65 | end 66 | 67 | -- Internal counter for unique ids generation 68 | local unique_id_counter = -1 69 | 70 | --- Table functions 71 | -- @section Table functions 72 | 73 | --- Iterates on each key-value pairs in a table. Calls function `f(key, value)` at each step of iteration. 74 | --
Aliased as `forEach`. 75 | -- @name each 76 | -- @tparam table t a table 77 | -- @tparam function f an iterator function, prototyped as `f(key, value, ...)` 78 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 79 | -- @see eachi 80 | function _.each(t, f, ...) 81 | for index,value in pairs(t) do 82 | f(index,value,...) 83 | end 84 | end 85 | 86 | --- Iterates on each integer key-value pairs in a table. Calls function `f(key, value)` 87 | -- only on values at integer key in a given collection. The table can be a sparse array, 88 | -- or map-like. Iteration will start from the lowest integer key found to the highest one. 89 | --
Aliased as `forEachi`. 90 | -- @name eachi 91 | -- @tparam table t a table 92 | -- @tparam function f an iterator function, prototyped as `f(key, value, ...)` 93 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 94 | -- @see each 95 | function _.eachi(t, f, ...) 96 | local lkeys = _.sort(_.select(_.keys(t), function(k,v) 97 | return _.isInteger(v) 98 | end)) 99 | for k, key in ipairs(lkeys) do 100 | f(key, t[key],...) 101 | end 102 | end 103 | 104 | --- Returns an array of values at specific indexes and keys. 105 | -- @name at 106 | -- @tparam table t a table 107 | -- @tparam vararg ... A variable number of indexes or keys to extract values 108 | -- @treturn table an array-list of values from the passed-in table 109 | function _.at(t, ...) 110 | local values = {} 111 | for i, key in ipairs({...}) do 112 | if _.has(t, key) then values[#values+1] = t[key] end 113 | end 114 | return values 115 | end 116 | 117 | --- Counts occurrences of a given value in a table. Uses @{isEqual} to compare values. 118 | -- @name count 119 | -- @tparam table t a table 120 | -- @tparam[opt] value value a value to be searched in the table. If not given, the @{size} of the table will be returned 121 | -- @treturn number the count of occurrences of `value` 122 | -- @see countf 123 | -- @see size 124 | function _.count(t, value) 125 | if _.isNil(value) then return _.size(t) end 126 | local count = 0 127 | _.each(t, function(k,v) 128 | if _.isEqual(v, value) then count = count + 1 end 129 | end) 130 | return count 131 | end 132 | 133 | --- Counts occurrences validating a predicate. Same as @{count}, but uses an iterator. 134 | -- Returns the count for values passing the test `f(key, value, ...)` 135 | -- @name countf 136 | -- @tparam table t a table 137 | -- @tparam function f an iterator function, prototyped as `f(key, value, ...)` 138 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 139 | -- @treturn number the count of values validating the predicate 140 | -- @see count 141 | -- @see size 142 | function _.countf(t, f, ...) 143 | return _.count(_.map(t, f, ...), true) 144 | end 145 | 146 | --- Iterates through a table and loops `n` times. The full iteration loop will be 147 | -- repeated `n` times (or forever, if `n` is omitted). In case `n` is lower or equal to 0, it returns 148 | -- an empty function. 149 | --
Aliased as `loop`. 150 | -- @name cycle 151 | -- @tparam table t a table 152 | -- @tparam number n the number of loops 153 | -- @treturn function an iterator function yielding key-value pairs from the passed-in table. 154 | function _.cycle(t, n) 155 | n = n or 1 156 | if n<=0 then return function() end end 157 | local k, fk 158 | local i = 0 159 | while true do 160 | return function() 161 | k = k and next(t,k) or next(t) 162 | fk = not fk and k or fk 163 | if n then 164 | i = (k==fk) and i+1 or i 165 | if i > n then 166 | return 167 | end 168 | end 169 | return k, t[k] 170 | end 171 | end 172 | end 173 | 174 | --- Maps function `f(key, value)` on all key-value pairs. Collects 175 | -- and returns the results as a table. 176 | --
Aliased as `collect`. 177 | -- @name map 178 | -- @tparam table t a table 179 | -- @tparam function f an iterator function, prototyped as `f(key, value, ...)` 180 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 181 | -- @treturn table a table of results 182 | function _.map(t, f, ...) 183 | local _t = {} 184 | for index,value in pairs(t) do 185 | _t[index] = f(index,value,...) 186 | end 187 | return _t 188 | end 189 | 190 | --- Reduces a table, left-to-right. Folds the table from the first element to the last element 191 | -- to into a single value, with respect to a given iterator and an initial state. 192 | -- The given function takes a state and a value and returns a new state. 193 | --
Aliased as `inject`, `foldl`. 194 | -- @name reduce 195 | -- @tparam table t a table 196 | -- @tparam function f an iterator function, prototyped as `f(state, value)` 197 | -- @tparam[opt] state state an initial state of reduction. Defaults to the first value in the table. 198 | -- @treturn state state the final state of reduction 199 | -- @see reduceRight 200 | function _.reduce(t, f, state) 201 | for __,value in pairs(t) do 202 | if state == nil then state = value 203 | else state = f(state,value) 204 | end 205 | end 206 | return state 207 | end 208 | 209 | --- Reduces a table, right-to-left. Folds the table from the last element to the first element 210 | -- to single value, with respect to a given iterator and an initial state. 211 | -- The given function takes a state and a value, and returns a new state. 212 | --
Aliased as `injectr`, `foldr`. 213 | -- @name reduceRight 214 | -- @tparam table t a table 215 | -- @tparam function f an iterator function, prototyped as `f(state,value)` 216 | -- @tparam[opt] state state an initial state of reduction. Defaults to the last value in the table. 217 | -- @treturn state state the final state of reduction 218 | -- @see reduce 219 | function _.reduceRight(t, f, state) 220 | return _.reduce(_.reverse(t),f,state) 221 | end 222 | 223 | --- Reduces a table while saving intermediate states. Folds the table left-to-right 224 | -- to a single value, with respect to a given iterator and an initial state. The given function 225 | -- takes a state and a value, and returns a new state. It returns an array of intermediate states. 226 | --
Aliased as `mapr` 227 | -- @name mapReduce 228 | -- @tparam table t a table 229 | -- @tparam function f an iterator function, prototyped as `f(state, value)` 230 | -- @tparam[opt] state state an initial state of reduction. Defaults to the first value in the table. 231 | -- @treturn table an array of states 232 | -- @see mapReduceRight 233 | function _.mapReduce(t, f, state) 234 | local _t = {} 235 | for i,value in pairs(t) do 236 | _t[i] = not state and value or f(state,value) 237 | state = _t[i] 238 | end 239 | return _t 240 | end 241 | 242 | --- Reduces a table while saving intermediate states. Folds the table right-to-left 243 | -- to a single value, with respect to a given iterator and an initial state. The given function 244 | -- takes a state and a value, and returns a new state. It returns an array of intermediate states. 245 | --
Aliased as `maprr` 246 | -- @name mapReduceRight 247 | -- @tparam table t a table 248 | -- @tparam function f an iterator function, prototyped as `f(state,value)` 249 | -- @tparam[opt] state state an initial state of reduction. Defaults to the last value in the table. 250 | -- @treturn table an array of states 251 | -- @see mapReduce 252 | function _.mapReduceRight(t, f, state) 253 | return _.mapReduce(_.reverse(t),f,state) 254 | end 255 | 256 | --- Search for a value in a table. It does not search in nested tables. 257 | --
Aliased as `any`, `some` 258 | -- @name include 259 | -- @tparam table t a table 260 | -- @tparam value|function value a value to search for 261 | -- @treturn boolean a boolean : `true` when found, `false` otherwise 262 | -- @see detect 263 | -- @see contains 264 | function _.include(t,value) 265 | local _iter = _.isFunction(value) and value or _.isEqual 266 | for __,v in pairs(t) do 267 | if _iter(v,value) then return true end 268 | end 269 | return false 270 | end 271 | 272 | --- Search for a value in a table. Returns the key of the value if found. 273 | -- It does not search in nested tables. 274 | -- @name detect 275 | -- @tparam table t a table 276 | -- @tparam value value a value to search for 277 | -- @treturn key the value key or __nil__ 278 | -- @see include 279 | -- @see contains 280 | function _.detect(t, value) 281 | local _iter = _.isFunction(value) and value or _.isEqual 282 | for key,arg in pairs(t) do 283 | if _iter(arg,value) then return key end 284 | end 285 | end 286 | 287 | --- Checks if a value is present in a table. 288 | -- @name contains 289 | -- @tparam table t a table 290 | -- @tparam value value a value to search for 291 | -- @treturn boolean true if present, otherwise false 292 | -- @see include 293 | -- @see detect 294 | function _.contains(t, value) 295 | return _.toBoolean(_.detect(t, value)) 296 | end 297 | 298 | --- Returns the first value having specified keys `props`. 299 | -- @function findWhere 300 | -- @tparam table t a table 301 | -- @tparam table props a set of keys 302 | -- @treturn value a value from the passed-in table 303 | function _.findWhere(t, props) 304 | local index = _.detect(t, function(v) 305 | for key in pairs(props) do 306 | if props[key] ~= v[key] then return false end 307 | end 308 | return true 309 | end) 310 | return index and t[index] 311 | end 312 | 313 | --- Selects and extracts values passing an iterator test. 314 | --
Aliased as `filter`. 315 | -- @name select 316 | -- @tparam table t a table 317 | -- @tparam function f an iterator function, prototyped as `f(key, value, ...)` 318 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 319 | -- @treturn table the selected values 320 | -- @see reject 321 | function _.select(t, f, ...) 322 | local _mapped = _.map(t, f, ...) 323 | local _t = {} 324 | for index,value in pairs(_mapped) do 325 | if value then _t[#_t+1] = t[index] end 326 | end 327 | return _t 328 | end 329 | 330 | --- Clones a table while dropping values passing an iterator test. 331 | --
Aliased as `discard` 332 | -- @name reject 333 | -- @tparam table t a table 334 | -- @tparam function f an iterator function, prototyped as `f(key, value, ...)` 335 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 336 | -- @treturn table the remaining values 337 | -- @see select 338 | function _.reject(t, f, ...) 339 | local _mapped = _.map(t,f,...) 340 | local _t = {} 341 | for index,value in pairs (_mapped) do 342 | if not value then _t[#_t+1] = t[index] end 343 | end 344 | return _t 345 | end 346 | 347 | --- Checks if all values in a table are passing an iterator test. 348 | --
Aliased as `every` 349 | -- @name all 350 | -- @tparam table t a table 351 | -- @tparam function f an iterator function, prototyped as `f(key, value, ...)` 352 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 353 | -- @treturn boolean `true` if all values passes the predicate, `false` otherwise 354 | function _.all(t, f, ...) 355 | return ((#_.select(_.map(t,f,...), isTrue)) == (#t)) 356 | end 357 | 358 | --- Invokes a method on each value in a table. 359 | -- @name invoke 360 | -- @tparam table t a table 361 | -- @tparam function method a function, prototyped as `f(value, ...)` 362 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `method` 363 | -- @treturn result the result(s) of method call `f(value, ...)` 364 | -- @see pluck 365 | function _.invoke(t, method, ...) 366 | local args = {...} 367 | return _.map(t, function(__,v) 368 | if _.isTable(v) then 369 | if _.has(v,method) then 370 | if _.isCallable(v[method]) then 371 | return v[method](v,unpack(args)) 372 | else 373 | return v[method] 374 | end 375 | else 376 | if _.isCallable(method) then 377 | return method(v,unpack(args)) 378 | end 379 | end 380 | elseif _.isCallable(method) then 381 | return method(v,unpack(args)) 382 | end 383 | end) 384 | end 385 | 386 | --- Extracts property-values from a table of values. 387 | -- @name pluck 388 | -- @tparam table t a table 389 | -- @tparam string a property, will be used to index in each value: `value[property]` 390 | -- @treturn table an array of values for the specified property 391 | function _.pluck(t, property) 392 | return _.reject(_.map(t,function(__,value) 393 | return value[property] 394 | end), iNot) 395 | end 396 | 397 | --- Returns the max value in a collection. If an transformation function is passed, it will 398 | -- be used to extract the value by which all objects will be sorted. 399 | -- @name max 400 | -- @tparam table t a table 401 | -- @tparam[opt] function transform an transformation function, prototyped as `transform(value,...)`, defaults to @{identity} 402 | -- @tparam[optchain] vararg ... Optional extra-args to be passed to function `transform` 403 | -- @treturn value the maximum value found 404 | -- @see min 405 | function _.max(t, transform, ...) 406 | return extract(t, f_max, transform, ...) 407 | end 408 | 409 | --- Returns the min value in a collection. If an transformation function is passed, it will 410 | -- be used to extract the value by which all objects will be sorted. 411 | -- @name min 412 | -- @tparam table t a table 413 | -- @tparam[opt] function transform an transformation function, prototyped as `transform(value,...)`, defaults to @{identity} 414 | -- @tparam[optchain] vararg ... Optional extra-args to be passed to function `transform` 415 | -- @treturn value the minimum value found 416 | -- @see max 417 | function _.min(t, transform, ...) 418 | return extract(t, f_min, transform, ...) 419 | end 420 | 421 | --- Returns a shuffled copy of a given collection. If a seed is provided, it will 422 | -- be used to init the random number generator (via `math.randomseed`). 423 | -- @name shuffle 424 | -- @tparam table t a table 425 | -- @tparam[opt] number seed a seed 426 | -- @treturn table a shuffled copy of the given table 427 | function _.shuffle(t, seed) 428 | if seed then randomseed(seed) end 429 | local _shuffled = {} 430 | _.each(t,function(index,value) 431 | local randPos = floor(random()*index)+1 432 | _shuffled[index] = _shuffled[randPos] 433 | _shuffled[randPos] = value 434 | end) 435 | return _shuffled 436 | end 437 | 438 | --- Checks if two tables are the same. It compares if both tables features the same values, 439 | -- but not necessarily at the same keys. 440 | -- @name same 441 | -- @tparam table a a table 442 | -- @tparam table b another table 443 | -- @treturn boolean `true` or `false` 444 | function _.same(a, b) 445 | return _.all(a, function (i,v) return _.include(b,v) end) 446 | and _.all(b, function (i,v) return _.include(a,v) end) 447 | end 448 | 449 | --- Sorts a table, in-place. If a comparison function is given, it will be used to sort values. 450 | -- @name sort 451 | -- @tparam table t a table 452 | -- @tparam[opt] function comp a comparison function prototyped as `comp(a,b)`, defaults to < operator. 453 | -- @treturn table the given table, sorted. 454 | function _.sort(t, comp) 455 | t_sort(t, comp) 456 | return t 457 | end 458 | 459 | --- Splits a table into subsets. Each subset feature values from the original table grouped 460 | -- by the result of passing it through an iterator. 461 | -- @name groupBy 462 | -- @tparam table t a table 463 | -- @tparam function iter an iterator function, prototyped as `iter(key, value, ...)` 464 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `iter` 465 | -- @treturn table a new table with values grouped by subsets 466 | function _.groupBy(t, iter, ...) 467 | local vararg = {...} 468 | local _t = {} 469 | _.each(t, function(i,v) 470 | local _key = iter(i,v, unpack(vararg)) 471 | if _t[_key] then _t[_key][#_t[_key]+1] = v 472 | else _t[_key] = {v} 473 | end 474 | end) 475 | return _t 476 | end 477 | 478 | --- Groups values in a collection and counts them. 479 | -- @name countBy 480 | -- @tparam table t a table 481 | -- @tparam function iter an iterator function, prototyped as `iter(key, value, ...)` 482 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `iter` 483 | -- @treturn table a new table with subsets names paired with their count 484 | function _.countBy(t, iter, ...) 485 | local vararg = {...} 486 | local stats = {} 487 | _.each(t,function(i,v) 488 | local key = iter(i,v,unpack(vararg)) 489 | stats[key] = (stats[key] or 0) +1 490 | end) 491 | return stats 492 | end 493 | 494 | --- Counts the number of values in a collection. If being passed more than one args 495 | -- it will return the count of all passed-in args. 496 | -- @name size 497 | -- @tparam[opt] vararg ... Optional variable number of arguments 498 | -- @treturn number a count 499 | -- @see count 500 | -- @see countf 501 | function _.size(...) 502 | local args = {...} 503 | local arg1 = args[1] 504 | if _.isNil(arg1) then 505 | return 0 506 | elseif _.isTable(arg1) then 507 | return count(args[1]) 508 | else 509 | return count(args) 510 | end 511 | end 512 | 513 | --- Checks if all the keys of `other` table exists in table `t`. It does not 514 | -- compares values. The test is not commutative, i.e table `t` may contains keys 515 | -- not existing in `other`. 516 | -- @name containsKeys 517 | -- @tparam table t a table 518 | -- @tparam table other another table 519 | -- @treturn boolean `true` or `false` 520 | -- @see sameKeys 521 | function _.containsKeys(t, other) 522 | for key in pairs(other) do 523 | if not t[key] then return false end 524 | end 525 | return true 526 | end 527 | 528 | --- Checks if both given tables have the same keys. It does not compares values. 529 | -- @name sameKeys 530 | -- @tparam table tA a table 531 | -- @tparam table tB another table 532 | -- @treturn boolean `true` or `false` 533 | -- @see containsKeys 534 | function _.sameKeys(tA, tB) 535 | for key in pairs(tA) do 536 | if not tB[key] then return false end 537 | end 538 | for key in pairs(tB) do 539 | if not tA[key] then return false end 540 | end 541 | return true 542 | end 543 | 544 | 545 | --- Array functions 546 | -- @section Array functions 547 | 548 | --- Converts a vararg list to an array-list. 549 | -- @name toArray 550 | -- @tparam[opt] vararg ... Optional variable number of arguments 551 | -- @treturn table an array-list of all passed-in args 552 | function _.toArray(...) return {...} end 553 | 554 | --- Looks for the first occurrence of a given value in an array. Returns the value index if found. 555 | -- @name find 556 | -- @tparam table array an array of values 557 | -- @tparam value value a value to search for 558 | -- @tparam[opt] number from the index from where to start the search. Defaults to 1. 559 | -- @treturn number|nil the index of the value if found in the array, `nil` otherwise. 560 | function _.find(array, value, from) 561 | for i = from or 1, #array do 562 | if _.isEqual(array[i], value) then return i end 563 | end 564 | end 565 | 566 | --- Reverses values in a given array. The passed-in array should not be sparse. 567 | -- @name reverse 568 | -- @tparam table array an array 569 | -- @treturn table a copy of the given array, reversed 570 | function _.reverse(array) 571 | local _array = {} 572 | for i = #array,1,-1 do 573 | _array[#_array+1] = array[i] 574 | end 575 | return _array 576 | end 577 | 578 | --- Collects values from a given array. The passed-in array should not be sparse. 579 | -- This function collects values as long as they satisfy a given predicate. 580 | -- Therefore, it returns on the first falsy test. 581 | --
Aliased as `takeWhile` 582 | -- @name selectWhile 583 | -- @tparam table array an array 584 | -- @tparam function f an iterator function prototyped as `f(key, value, ...)` 585 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 586 | -- @treturn table a new table containing all values collected 587 | -- @see dropWhile 588 | function _.selectWhile(array, f, ...) 589 | local t = {} 590 | for i,v in ipairs(array) do 591 | if f(i,v,...) then t[i] = v else break end 592 | end 593 | return t 594 | end 595 | 596 | --- Collects values from a given array. The passed-in array should not be sparse. 597 | -- This function collects values as long as they do not satisfy a given predicate. 598 | -- Therefore it returns on the first true test. 599 | --
Aliased as `rejectWhile` 600 | -- @name dropWhile 601 | -- @tparam table array an array 602 | -- @tparam function f an iterator function prototyped as `f(key,value,...)` 603 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 604 | -- @treturn table a new table containing all values collected 605 | -- @selectWhile 606 | function _.dropWhile(array, f, ...) 607 | local _i 608 | for i,v in ipairs(array) do 609 | if not f(i,v,...) then 610 | _i = i 611 | break 612 | end 613 | end 614 | if _.isNil(_i) then return {} end 615 | return _.rest(array,_i) 616 | end 617 | 618 | --- Returns the index at which a value should be inserted. This returned index is determined so 619 | -- that it maintains the sort. If a comparison function is passed, it will be used to sort all 620 | -- values. 621 | -- @name sortedIndex 622 | -- @tparam table array an array 623 | -- @tparam value the value to be inserted 624 | -- @tparam[opt] function comp an comparison function prototyped as `f(a, b)`, defaults to < operator. 625 | -- @tparam[optchain] boolean sort whether or not the passed-in array should be sorted 626 | -- @treturn number the index at which the passed-in value should be inserted 627 | function _.sortedIndex(array, value, comp, sort) 628 | local _comp = comp or f_min 629 | if sort then _.sort(array,_comp) end 630 | for i = 1,#array do 631 | if not _comp(array[i],value) then return i end 632 | end 633 | return #array+1 634 | end 635 | 636 | --- Returns the index of a given value in an array. If the passed-in value exists 637 | -- more than once in the array, it will return the index of the first occurrence. 638 | -- @name indexOf 639 | -- @tparam table array an array 640 | -- @tparam value the value to search for 641 | -- @treturn number|nil the index of the passed-in value 642 | -- @see lastIndexOf 643 | function _.indexOf(array, value) 644 | for k = 1,#array do 645 | if array[k] == value then return k end 646 | end 647 | end 648 | 649 | --- Returns the index of the last occurrence of a given value. 650 | -- @name lastIndexOf 651 | -- @tparam table array an array 652 | -- @tparam value the value to search for 653 | -- @treturn number|nil the index of the last occurrence of the passed-in value or __nil__ 654 | -- @see indexOf 655 | function _.lastIndexOf(array, value) 656 | local key = _.indexOf(_.reverse(array),value) 657 | if key then return #array-key+1 end 658 | end 659 | 660 | --- Adds all passed-in values at the top of an array. The last arguments will bubble to the 661 | -- top of the given array. 662 | -- @name addTop 663 | -- @tparam table array an array 664 | -- @tparam vararg ... a variable number of arguments 665 | -- @treturn table the passed-in array 666 | -- @see push 667 | function _.addTop(array, ...) 668 | _.each({...},function(i,v) t_insert(array,1,v) end) 669 | return array 670 | end 671 | 672 | --- Pushes all passed-in values at the end of an array. 673 | -- @name push 674 | -- @tparam table array an array 675 | -- @tparam vararg ... a variable number of arguments 676 | -- @treturn table the passed-in array 677 | -- @see addTop 678 | function _.push(array, ...) 679 | _.each({...}, function(i,v) array[#array+1] = v end) 680 | return array 681 | end 682 | 683 | --- Removes and returns the values at the top of a given array. 684 | --
Aliased as `shift` 685 | -- @name pop 686 | -- @tparam table array an array 687 | -- @tparam[opt] number n the number of values to be popped. Defaults to 1. 688 | -- @treturn vararg a vararg list of values popped from the array 689 | -- @see unshift 690 | function _.pop(array, n) 691 | n = min(n or 1, #array) 692 | local ret = {} 693 | for i = 1, n do 694 | local retValue = array[1] 695 | ret[#ret + 1] = retValue 696 | t_remove(array,1) 697 | end 698 | return unpack(ret) 699 | end 700 | 701 | --- Removes and returns the values at the end of a given array. 702 | -- @name unshift 703 | -- @tparam table array an array 704 | -- @tparam[opt] number n the number of values to be unshifted. Defaults to 1. 705 | -- @treturn vararg a vararg list of values 706 | -- @see pop 707 | function _.unshift(array, n) 708 | n = min(n or 1, #array) 709 | local ret = {} 710 | for i = 1, n do 711 | local retValue = array[#array] 712 | ret[#ret + 1] = retValue 713 | t_remove(array) 714 | end 715 | return unpack(ret) 716 | end 717 | 718 | --- Removes all provided values in a given array. 719 | --
Aliased as `remove` 720 | -- @name pull 721 | -- @tparam table array an array 722 | -- @tparam vararg ... a variable number of values to be removed from the array 723 | -- @treturn table the passed-in array 724 | function _.pull(array, ...) 725 | for __, rmValue in ipairs({...}) do 726 | for i = #array, 1, -1 do 727 | if _.isEqual(array[i], rmValue) then 728 | t_remove(array, i) 729 | end 730 | end 731 | end 732 | return array 733 | end 734 | 735 | --- Trims all values indexed within the range `[start, finish]`. 736 | --
Aliased as `rmRange` 737 | -- @name removeRange 738 | -- @tparam table array an array 739 | -- @tparam[opt] number start the lower bound index, defaults to the first index in the array. 740 | -- @tparam[optchain] number finish the upper bound index, defaults to the array length. 741 | -- @treturn table the passed-in array 742 | function _.removeRange(array, start, finish) 743 | local array = _.clone(array) 744 | local i,n = (next(array)),#array 745 | if n < 1 then return array end 746 | 747 | start = clamp(start or i,i,n) 748 | finish = clamp(finish or n,i,n) 749 | 750 | if finish < start then return array end 751 | 752 | local count = finish - start + 1 753 | local i = start 754 | while count > 0 do 755 | t_remove(array,i) 756 | count = count - 1 757 | end 758 | return array 759 | end 760 | 761 | --- Chunks together consecutive values. Values are chunked on the basis of the return 762 | -- value of a provided predicate `f(key, value, ...)`. Consecutive elements which return 763 | -- the same value are chunked together. Leaves the first argument untouched if it is not an array. 764 | -- @name chunk 765 | -- @tparam table array an array 766 | -- @tparam function f an iterator function prototyped as `f(key, value, ...)` 767 | -- @tparam[opt] vararg ... Optional extra-args to be passed to function `f` 768 | -- @treturn table a table of chunks (arrays) 769 | -- @see zip 770 | function _.chunk(array, f, ...) 771 | if not _.isArray(array) then return array end 772 | local ch, ck, prev = {}, 0 773 | local mask = _.map(array, f,...) 774 | _.each(mask, function(k,v) 775 | prev = (prev==nil) and v or prev 776 | ck = ((v~=prev) and (ck+1) or ck) 777 | if not ch[ck] then 778 | ch[ck] = {array[k]} 779 | else 780 | ch[ck][#ch[ck]+1] = array[k] 781 | end 782 | prev = v 783 | end) 784 | return ch 785 | end 786 | 787 | --- Slices values indexed within `[start, finish]` range. 788 | --
Aliased as `_.sub` 789 | -- @name slice 790 | -- @tparam table array an array 791 | -- @tparam[opt] number start the lower bound index, defaults to the first index in the array. 792 | -- @tparam[optchain] number finish the upper bound index, defaults to the array length. 793 | -- @treturn table a new array 794 | function _.slice(array, start, finish) 795 | return _.select(array, function(index) 796 | return (index >= (start or next(array)) and index <= (finish or #array)) 797 | end) 798 | end 799 | 800 | --- Returns the first N values in an array. 801 | --
Aliased as `head`, `take` 802 | -- @name first 803 | -- @tparam table array an array 804 | -- @tparam[opt] number n the number of values to be collected, defaults to 1. 805 | -- @treturn table a new array 806 | -- @see initial 807 | -- @see last 808 | -- @see rest 809 | function _.first(array, n) 810 | local n = n or 1 811 | return _.slice(array,1, min(n,#array)) 812 | end 813 | 814 | --- Returns all values in an array excluding the last N values. 815 | -- @name initial 816 | -- @tparam table array an array 817 | -- @tparam[opt] number n the number of values to be left, defaults to the array length. 818 | -- @treturn table a new array 819 | -- @see first 820 | -- @see last 821 | -- @see rest 822 | function _.initial(array, n) 823 | if n and n < 0 then return end 824 | return _.slice(array,1, n and #array-(min(n,#array)) or #array-1) 825 | end 826 | 827 | --- Returns the last N values in an array. 828 | -- @name last 829 | -- @tparam table array an array 830 | -- @tparam[opt] number n the number of values to be collected, defaults to the array length. 831 | -- @treturn table a new array 832 | -- @see first 833 | -- @see initial 834 | -- @see rest 835 | function _.last(array,n) 836 | if n and n <= 0 then return end 837 | return _.slice(array,n and #array-min(n-1,#array-1) or 2,#array) 838 | end 839 | 840 | --- Trims all values before index. 841 | --
Aliased as `tail` 842 | -- @name rest 843 | -- @tparam table array an array 844 | -- @tparam[opt] number index an index, defaults to 1 845 | -- @treturn table a new array 846 | -- @see first 847 | -- @see initial 848 | -- @see last 849 | function _.rest(array,index) 850 | if index and index > #array then return {} end 851 | return _.slice(array,index and max(1,min(index,#array)) or 1,#array) 852 | end 853 | 854 | --- Trims all falsy (false and nil) values. 855 | -- @name compact 856 | -- @tparam table array an array 857 | -- @treturn table a new array 858 | function _.compact(array) 859 | return _.reject(array, function (_,value) 860 | return not value 861 | end) 862 | end 863 | 864 | --- Flattens a nested array. Passing `shallow` will only flatten at the first level. 865 | -- @name flatten 866 | -- @tparam table array an array 867 | -- @tparam[opt] boolean shallow specifies the flattening depth 868 | -- @treturn table a new array, flattened 869 | function _.flatten(array, shallow) 870 | local shallow = shallow or false 871 | local new_flattened 872 | local _flat = {} 873 | for key,value in pairs(array) do 874 | if _.isTable(value) then 875 | new_flattened = shallow and value or _.flatten (value) 876 | _.each(new_flattened, function(_,item) _flat[#_flat+1] = item end) 877 | else _flat[#_flat+1] = value 878 | end 879 | end 880 | return _flat 881 | end 882 | 883 | --- Returns values from an array not present in all passed-in args. 884 | --
Aliased as `without` and `diff` 885 | -- @name difference 886 | -- @tparam table array an array 887 | -- @tparam table another array 888 | -- @treturn table a new array 889 | -- @see union 890 | -- @see intersection 891 | -- @see symmetricDifference 892 | function _.difference(array, array2) 893 | if not array2 then return _.clone(array) end 894 | return _.select(array,function(i,value) 895 | return not _.include(array2,value) 896 | end) 897 | end 898 | 899 | --- Returns the duplicate-free union of all passed in arrays. 900 | -- @name union 901 | -- @tparam vararg ... a variable number of arrays arguments 902 | -- @treturn table a new array 903 | -- @see difference 904 | -- @see intersection 905 | -- @see symmetricDifference 906 | function _.union(...) 907 | return _.uniq(_.flatten({...})) 908 | end 909 | 910 | --- Returns the intersection of all passed-in arrays. 911 | -- Each value in the result is present in each of the passed-in arrays. 912 | -- @name intersection 913 | -- @tparam table array an array 914 | -- @tparam vararg ... a variable number of array arguments 915 | -- @treturn table a new array 916 | -- @see difference 917 | -- @see union 918 | -- @see symmetricDifference 919 | function _.intersection(array, ...) 920 | local arg = {...} 921 | local _intersect = {} 922 | for i,value in ipairs(array) do 923 | if _.all(arg,function(i,v) 924 | return _.include(v,value) 925 | end) then 926 | t_insert(_intersect,value) 927 | end 928 | end 929 | return _intersect 930 | end 931 | 932 | --- Performs a symmetric difference. Returns values from `array` not present in `array2` and also values 933 | -- from `array2` not present in `array`. 934 | --
Aliased as `symdiff` 935 | -- @name symmetricDifference 936 | -- @tparam table array an array 937 | -- @tparam table array2 another array 938 | -- @treturn table a new array 939 | -- @see difference 940 | -- @see union 941 | -- @see intersection 942 | function _.symmetricDifference(array, array2) 943 | return _.difference( 944 | _.union(array, array2), 945 | _.intersection(array,array2) 946 | ) 947 | end 948 | 949 | --- Produces a duplicate-free version of a given array. 950 | --
Aliased as `uniq` 951 | -- @name unique 952 | -- @tparam table array an array 953 | -- @treturn table a new array, duplicate-free 954 | -- @see isunique 955 | function _.unique(array) 956 | local ret = {} 957 | for i = 1, #array do 958 | if not _.find(ret, array[i]) then 959 | ret[#ret+1] = array[i] 960 | end 961 | end 962 | return ret 963 | end 964 | 965 | --- Checks if a given array contains distinct values. Such an array is made of distinct elements, 966 | -- which only occur once in this array. 967 | --
Aliased as `isuniq` 968 | -- @name isunique 969 | -- @tparam table array an array 970 | -- @treturn boolean `true` if the given array is unique, `false` otherwise. 971 | -- @see unique 972 | function _.isunique(array) 973 | return _.isEqual(array, _.unique(array)) 974 | end 975 | 976 | --- Merges values of each of the passed-in arrays in subsets. 977 | -- Only values indexed with the same key in the given arrays are merged in the same subset. 978 | -- @name zip 979 | -- @tparam vararg ... a variable number of array arguments 980 | -- @treturn table a new array 981 | function _.zip(...) 982 | local arg = {...} 983 | local _len = _.max(_.map(arg,function(i,v) 984 | return #v 985 | end)) 986 | local _ans = {} 987 | for i = 1,_len do 988 | _ans[i] = _.pluck(arg,i) 989 | end 990 | return _ans 991 | end 992 | 993 | --- Clones `array` and appends `other` values. 994 | -- @name append 995 | -- @tparam table array an array 996 | -- @tparam table other an array 997 | -- @treturn table a new array 998 | function _.append(array, other) 999 | local t = {} 1000 | for i,v in ipairs(array) do t[i] = v end 1001 | for i,v in ipairs(other) do t[#t+1] = v end 1002 | return t 1003 | end 1004 | 1005 | --- Interleaves arrays. It returns a single array made of values from all 1006 | -- passed in arrays in their given order, interleaved. 1007 | -- @name interleave 1008 | -- @tparam vararg ... a variable list of arrays 1009 | -- @treturn table a new array 1010 | -- @see interpose 1011 | function _.interleave(...) return _.flatten(_.zip(...)) end 1012 | 1013 | --- Interposes `value` in-between consecutive pair of values in `array`. 1014 | -- @name interpose 1015 | -- @tparam value value a value 1016 | -- @tparam table array an array 1017 | -- @treturn table a new array 1018 | -- @see interleave 1019 | function _.interpose(value, array) 1020 | return _.flatten(_.zip(array, _.rep(value, #array-1))) 1021 | end 1022 | 1023 | --- Produce a flexible list of numbers. If one positive value is passed, will count from 0 to that value, 1024 | -- with a default step of 1. If two values are passed, will count from the first one to the second one, with the 1025 | -- same default step of 1. A third passed value will be considered a step value. 1026 | -- @name range 1027 | -- @tparam[opt] number from the initial value of the range 1028 | -- @tparam[optchain] number to the final value of the range 1029 | -- @tparam[optchain] number step the count step value 1030 | -- @treturn table a new array of numbers 1031 | function _.range(...) 1032 | local arg = {...} 1033 | local _start,_stop,_step 1034 | if #arg==0 then return {} 1035 | elseif #arg==1 then _stop,_start,_step = arg[1],0,1 1036 | elseif #arg==2 then _start,_stop,_step = arg[1],arg[2],1 1037 | elseif #arg == 3 then _start,_stop,_step = arg[1],arg[2],arg[3] 1038 | end 1039 | if (_step and _step==0) then return {} end 1040 | local _ranged = {} 1041 | local _steps = max(floor((_stop-_start)/_step),0) 1042 | for i=1,_steps do _ranged[#_ranged+1] = _start+_step*i end 1043 | if #_ranged>0 then t_insert(_ranged,1,_start) end 1044 | return _ranged 1045 | end 1046 | 1047 | --- Creates an array list of `n` values, repeated. 1048 | -- @name rep 1049 | -- @tparam value value a value to be repeated 1050 | -- @tparam number n the number of repetitions of the given `value`. 1051 | -- @treturn table a new array of `n` values 1052 | function _.rep(value, n) 1053 | local ret = {} 1054 | for i = 1, n do ret[#ret+1] = value end 1055 | return ret 1056 | end 1057 | 1058 | --- Iterator returning partitions of an array. It returns arrays of length `n` 1059 | -- made of values from the given array. In case the array size is not a multiple 1060 | -- of `n`, the last array returned will be made of the rest of the values. 1061 | -- @name partition. 1062 | -- @tparam table array an array 1063 | -- @tparam[opt] number n the size of each partition. Defaults to 1. 1064 | -- @treturn function an iterator function 1065 | function _.partition(array, n) 1066 | return coroutine.wrap(function() 1067 | partgen(array, n or 1, coroutine.yield) 1068 | end) 1069 | end 1070 | 1071 | --- Iterator returning the permutations of an array. It returns arrays made of all values 1072 | -- from the passed-in array, with values permuted. 1073 | -- @name permutation 1074 | -- @tparam table array an array 1075 | -- @treturn function an iterator function 1076 | function _.permutation(array) 1077 | return coroutine.wrap(function() 1078 | permgen(array, #array, coroutine.yield) 1079 | end) 1080 | end 1081 | 1082 | --- Swaps keys with values. Produces a new array where previous keys are now values, 1083 | -- while previous values are now keys. 1084 | --
Aliased as `mirror` 1085 | -- @name invert 1086 | -- @tparam table array a given array 1087 | -- @treturn table a new array 1088 | function _.invert(array) 1089 | local _ret = {} 1090 | _.each(array,function(i,v) _ret[v] = i end) 1091 | return _ret 1092 | end 1093 | 1094 | --- Concatenates values in a given array. Handles booleans as well. If `sep` string is 1095 | -- passed, it will be used as a separator. Passing `i` and `j` will result in concatenating 1096 | -- values within `[i,j]` range. 1097 | --
Aliased as `join` 1098 | -- @name concat 1099 | -- @tparam table array a given array 1100 | -- @tparam[opt] string sep a separator string, defaults to `''`. 1101 | -- @tparam[optchain] number i the starting index, defaults to 1. 1102 | -- @tparam[optchain] number j the final index, defaults to the array length. 1103 | -- @treturn string a string 1104 | function _.concat(array, sep, i, j) 1105 | local _array = _.map(array,function(i,v) 1106 | return tostring(v) 1107 | end) 1108 | return t_concat(_array,sep,i or 1,j or #array) 1109 | 1110 | end 1111 | 1112 | 1113 | --- Utility functions 1114 | -- @section Utility functions 1115 | 1116 | --- Returns the passed-in value. This function seems useless, but it is used internally 1117 | -- as a default iterator. 1118 | -- @name identity 1119 | -- @tparam value value a value 1120 | -- @treturn value the passed-in value 1121 | function _.identity(value) return value end 1122 | 1123 | --- Returns a version of `f` that runs only once. Successive calls to `f` 1124 | -- will keep yielding the same output, no matter what the passed-in arguments are. 1125 | -- It can be used to initialize variables. 1126 | -- @name once 1127 | -- @tparam function f a function 1128 | -- @treturn function a new function 1129 | -- @see after 1130 | function _.once(f) 1131 | local _internal = 0 1132 | local _args = {} 1133 | return function(...) 1134 | _internal = _internal+1 1135 | if _internal<=1 then _args = {...} end 1136 | return f(unpack(_args)) 1137 | end 1138 | end 1139 | 1140 | --- Memoizes a given function by caching the computed result. 1141 | -- Useful for speeding-up slow-running functions. If function `hash` is passed, 1142 | -- it will be used to compute hash keys for a set of input values to the function for caching. 1143 | --
Aliased as `cache` 1144 | -- @name memoize 1145 | -- @tparam function f a function 1146 | -- @tparam[opt] function hash a hash function, defaults to @{identity} 1147 | -- @treturn function a new function 1148 | function _.memoize(f, hash) 1149 | local _cache = setmetatable({},{__mode = 'kv'}) 1150 | local _hasher = hash or _.identity 1151 | return function (...) 1152 | local _hashKey = _hasher(...) 1153 | local _result = _cache[_hashKey] 1154 | if not _result then _cache[_hashKey] = f(...) end 1155 | return _cache[_hashKey] 1156 | end 1157 | end 1158 | 1159 | --- Returns a version of `f` that runs on the `count-th` call. 1160 | -- Useful when dealing with asynchronous tasks. 1161 | -- @name after 1162 | -- @tparam function f a function 1163 | -- @tparam number count the number of calls before `f` answers 1164 | -- @treturn function a new function 1165 | -- @see once 1166 | function _.after(f, count) 1167 | local _limit,_internal = count, 0 1168 | return function(...) 1169 | _internal = _internal+1 1170 | if _internal >= _limit then return f(...) end 1171 | end 1172 | end 1173 | 1174 | --- Composes functions. Each passed-in function consumes the return value of the function that follows. 1175 | -- In math terms, composing the functions `f`, `g`, and `h` produces the function `f(g(h(...)))`. 1176 | -- @name compose 1177 | -- @tparam vararg ... a variable number of functions 1178 | -- @treturn function a new function 1179 | -- @see pipe 1180 | function _.compose(...) 1181 | local f = _.reverse {...} 1182 | return function (...) 1183 | local _temp 1184 | for i, func in ipairs(f) do 1185 | _temp = _temp and func(_temp) or func(...) 1186 | end 1187 | return _temp 1188 | end 1189 | end 1190 | 1191 | --- Pipes a value through a series of functions. In math terms, 1192 | -- given some functions `f`, `g`, and `h` in that order, it returns `f(g(h(value)))`. 1193 | -- @name pipe 1194 | -- @tparam value value a value 1195 | -- @tparam vararg ... a variable number of functions 1196 | -- @treturn value the result of the composition of function calls. 1197 | -- @see compose 1198 | function _.pipe(value, ...) 1199 | return _.compose(...)(value) 1200 | end 1201 | 1202 | --- Returns the logical complement of a given function. For a given input, the returned 1203 | -- function will output `false` if the original function would have returned `true`, 1204 | -- and vice-versa. 1205 | -- @name complement 1206 | -- @tparam function f a function 1207 | -- @treturn function the logical complement of the given function `f`. 1208 | function _.complement(f) 1209 | return function(...) return not f(...) end 1210 | end 1211 | 1212 | --- Calls a sequence of passed-in functions with the same argument. 1213 | -- Returns a sequence of results. 1214 | --
Aliased as `juxt` 1215 | -- @name juxtapose 1216 | -- @tparam value value a value 1217 | -- @tparam vararg ... a variable number of functions 1218 | -- @treturn vararg a vargarg list of results. 1219 | function _.juxtapose(value, ...) 1220 | local res = {} 1221 | _.each({...}, function(_,f) res[#res+1] = f(value) end) 1222 | return unpack(res) 1223 | end 1224 | 1225 | --- Wraps `f` inside of the `wrapper` function. It passes `f` as the first argument to `wrapper`. 1226 | -- This allows the wrapper to execute code before and after `f` runs, 1227 | -- adjust the arguments, and execute it conditionally. 1228 | -- @name wrap 1229 | -- @tparam function f a function to be wrapped, prototyped as `f(...)` 1230 | -- @tparam function wrapper a wrapper function, prototyped as `wrapper(f,...)` 1231 | -- @treturn function a new function 1232 | function _.wrap(f, wrapper) 1233 | return function (...) return wrapper(f,...) end 1234 | end 1235 | 1236 | --- Runs `iter` function `n` times. 1237 | -- Collects the results of each run and returns them in an array. 1238 | -- @name times 1239 | -- @tparam number n the number of times `iter` should be called 1240 | -- @tparam function iter an iterator function, prototyped as `iter(i, ...)` 1241 | -- @tparam vararg ... extra-args to be passed to `iter` function 1242 | -- @treturn table an array of results 1243 | function _.times(n, iter, ...) 1244 | local results = {} 1245 | for i = 1,n do 1246 | results[i] = iter(i,...) 1247 | end 1248 | return results 1249 | end 1250 | 1251 | --- Binds `v` to be the first argument to function `f`. As a result, 1252 | -- calling `f(...)` will result to `f(v, ...)`. 1253 | -- @name bind 1254 | -- @tparam function f a function 1255 | -- @tparam value v a value 1256 | -- @treturn function a function 1257 | -- @see bindn 1258 | function _.bind(f, v) 1259 | return function (...) 1260 | return f(v,...) 1261 | end 1262 | end 1263 | 1264 | --- Binds `...` to be the N-first arguments to function `f`. As a result, 1265 | -- calling `f(a1, a2, ..., aN)` will result to `f(..., a1, a2, ...,aN)`. 1266 | -- @name bindn 1267 | -- @tparam function f a function 1268 | -- @tparam vararg ... a variable number of arguments 1269 | -- @treturn function a function 1270 | -- @see bind 1271 | function _.bindn(f, ...) 1272 | local iArg = {...} 1273 | return function (...) 1274 | return f(unpack(_.append(iArg,{...}))) 1275 | end 1276 | end 1277 | 1278 | --- Generates a unique ID for the current session. If given a string *template* 1279 | -- will use this template for output formatting. Otherwise, if *template* is a function, 1280 | -- will evaluate `template(id, ...)`. 1281 | --
Aliased as `uid`. 1282 | -- @name uniqueId 1283 | -- @tparam[opt] string|function template either a string or a function template to format the ID 1284 | -- @tparam[optchain] vararg ... a variable number of arguments to be passed to *template*, in case it is a function. 1285 | -- @treturn value an ID 1286 | function _.uniqueId(template, ...) 1287 | unique_id_counter = unique_id_counter + 1 1288 | if template then 1289 | if _.isString(template) then 1290 | return template:format(unique_id_counter) 1291 | elseif _.isFunction(template) then 1292 | return template(unique_id_counter,...) 1293 | end 1294 | end 1295 | return unique_id_counter 1296 | end 1297 | 1298 | --- Object functions 1299 | --@section Object functions 1300 | 1301 | --- Returns the keys of the object properties. 1302 | -- @name keys 1303 | -- @tparam table obj an object 1304 | -- @treturn table an array 1305 | function _.keys(obj) 1306 | local _oKeys = {} 1307 | _.each(obj,function(key) _oKeys[#_oKeys+1]=key end) 1308 | return _oKeys 1309 | end 1310 | 1311 | --- Returns the values of the object properties. 1312 | -- @name values 1313 | -- @tparam table obj an object 1314 | -- @treturn table an array 1315 | function _.values(obj) 1316 | local _oValues = {} 1317 | _.each(obj,function(_,value) _oValues[#_oValues+1]=value end) 1318 | return _oValues 1319 | end 1320 | 1321 | --- Converts any given value to a boolean 1322 | -- @name toBoolean 1323 | -- @tparam value value a value. Can be of any type 1324 | -- @treturn boolean `true` if value is true, `false` otherwise (false or nil). 1325 | function _.toBoolean(value) 1326 | return not not value 1327 | end 1328 | 1329 | --- Extends an object properties. It copies all of the properties of extra passed-in objects 1330 | -- into the destination object, and returns the destination object. 1331 | -- The last object in the `...` set will override properties of the same name in the previous one 1332 | -- @name extend 1333 | -- @tparam table destObj a destination object 1334 | -- @tparam vararg ... a variable number of array arguments 1335 | -- @treturn table the destination object extended 1336 | function _.extend(destObj, ...) 1337 | local sources = {...} 1338 | _.each(sources,function(__,source) 1339 | if _.isTable(source) then 1340 | _.each(source,function(key,value) 1341 | destObj[key] = value 1342 | end) 1343 | end 1344 | end) 1345 | return destObj 1346 | end 1347 | 1348 | --- Returns a sorted list of all methods names found in an object. If the given object 1349 | -- has a metatable implementing an `__index` field pointing to another table, will also recurse on this 1350 | -- table if argument `recurseMt` is provided. If `obj` is omitted, it defaults to the library functions. 1351 | --
Aliased as `methods`. 1352 | -- @name functions 1353 | -- @tparam[opt] table obj an object. Defaults to library functions. 1354 | -- @treturn table an array-list of methods names 1355 | function _.functions(obj, recurseMt) 1356 | obj = obj or _ 1357 | local _methods = {} 1358 | _.each(obj,function(key,value) 1359 | if _.isFunction(value) then 1360 | _methods[#_methods+1]=key 1361 | end 1362 | end) 1363 | if not recurseMt then 1364 | return _.sort(_methods) 1365 | end 1366 | local mt = getmetatable(obj) 1367 | if mt and mt.__index then 1368 | local mt_methods = _.functions(mt.__index) 1369 | _.each(mt_methods, function(k,fn) 1370 | _methods[#_methods+1] = fn 1371 | end) 1372 | end 1373 | return _.sort(_methods) 1374 | end 1375 | 1376 | --- Clones a given object properties. If `shallow` is passed 1377 | -- will also clone nested array properties. 1378 | -- @name clone 1379 | -- @tparam table obj an object 1380 | -- @tparam[opt] boolean shallow whether or not nested array-properties should be cloned, defaults to false. 1381 | -- @treturn table a copy of the passed-in object 1382 | function _.clone(obj, shallow) 1383 | if not _.isTable(obj) then return obj end 1384 | local _obj = {} 1385 | _.each(obj,function(i,v) 1386 | if _.isTable(v) then 1387 | if not shallow then 1388 | _obj[i] = _.clone(v,shallow) 1389 | else _obj[i] = v 1390 | end 1391 | else 1392 | _obj[i] = v 1393 | end 1394 | end) 1395 | return _obj 1396 | end 1397 | 1398 | --- Invokes interceptor with the object, and then returns object. 1399 | -- The primary purpose of this method is to "tap into" a method chain, in order to perform operations 1400 | -- on intermediate results within the chain. 1401 | -- @name tap 1402 | -- @tparam table obj an object 1403 | -- @tparam function f an interceptor function, should be prototyped as `f(obj, ...)` 1404 | -- @tparam[opt] vararg ... Extra-args to be passed to interceptor function 1405 | -- @treturn table the passed-in object 1406 | function _.tap(obj, f, ...) 1407 | f(obj,...) 1408 | return obj 1409 | end 1410 | 1411 | --- Checks if a given object implements a property. 1412 | -- @name has 1413 | -- @tparam table obj an object 1414 | -- @tparam value key a key property to be checked 1415 | -- @treturn boolean `true` or `false` 1416 | function _.has(obj, key) 1417 | return obj[key]~=nil 1418 | end 1419 | 1420 | --- Return a filtered copy of the object. The returned object will only have 1421 | -- the white-listed properties paired with their original values. 1422 | --
Aliased as `choose`. 1423 | -- @name pick 1424 | -- @tparam table obj an object 1425 | -- @tparam vararg ... a variable number of string keys 1426 | -- @treturn table the filtered object 1427 | function _.pick(obj, ...) 1428 | local whitelist = _.flatten {...} 1429 | local _picked = {} 1430 | _.each(whitelist,function(key,property) 1431 | if not _.isNil(obj[property]) then 1432 | _picked[property] = obj[property] 1433 | end 1434 | end) 1435 | return _picked 1436 | end 1437 | 1438 | --- Return a filtered copy of the object. The returned object will not have 1439 | -- the black-listed properties. 1440 | --
Aliased as `drop`. 1441 | -- @name omit 1442 | -- @tparam table obj an object 1443 | -- @tparam vararg ... a variable number of string keys 1444 | -- @treturn table the filtered object 1445 | function _.omit(obj, ...) 1446 | local blacklist = _.flatten {...} 1447 | local _picked = {} 1448 | _.each(obj,function(key,value) 1449 | if not _.include(blacklist,key) then 1450 | _picked[key] = value 1451 | end 1452 | end) 1453 | return _picked 1454 | end 1455 | 1456 | --- Fills nil properties in an object with the given `template` object. Pre-existing 1457 | -- properties will be preserved. 1458 | --
Aliased as `defaults`. 1459 | -- @name template 1460 | -- @tparam table obj an object 1461 | -- @tparam[opt] table template a template object. Defaults to an empty table `{}`. 1462 | -- @treturn table the passed-in object filled 1463 | function _.template(obj, template) 1464 | _.each(template or {},function(i,v) 1465 | if not obj[i] then obj[i] = v end 1466 | end) 1467 | return obj 1468 | end 1469 | 1470 | --- Performs a deep comparison test between two objects. Can compare strings, functions 1471 | -- (by reference), nil, booleans. Compares tables by reference or by values. If `useMt` 1472 | -- is passed, the equality operator `==` will be used if one of the given objects has a 1473 | -- metatable implementing `__eq`. 1474 | --
Aliased as `_.compare` 1475 | -- @name isEqual 1476 | -- @tparam table objA an object 1477 | -- @tparam table objB another object 1478 | -- @tparam[opt] boolean useMt whether or not `__eq` should be used, defaults to false. 1479 | -- @treturn boolean `true` or `false` 1480 | function _.isEqual(objA, objB, useMt) 1481 | local typeObjA = type(objA) 1482 | local typeObjB = type(objB) 1483 | 1484 | if typeObjA~=typeObjB then return false end 1485 | if typeObjA~='table' then return (objA==objB) end 1486 | 1487 | local mtA = getmetatable(objA) 1488 | local mtB = getmetatable(objB) 1489 | 1490 | if useMt then 1491 | if (mtA or mtB) and (mtA.__eq or mtB.__eq) then 1492 | return mtA.__eq(objA, objB) or mtB.__eq(objB, objA) or (objA==objB) 1493 | end 1494 | end 1495 | 1496 | if _.size(objA)~=_.size(objB) then return false end 1497 | 1498 | for i,v1 in pairs(objA) do 1499 | local v2 = objB[i] 1500 | if _.isNil(v2) or not _.isEqual(v1,v2,useMt) then return false end 1501 | end 1502 | 1503 | for i,v1 in pairs(objB) do 1504 | local v2 = objA[i] 1505 | if _.isNil(v2) then return false end 1506 | end 1507 | 1508 | return true 1509 | end 1510 | 1511 | --- Invokes an object method. It passes the object itself as the first argument. if `method` is not 1512 | -- callable, will return `obj[method]`. 1513 | -- @name result 1514 | -- @tparam table obj an object 1515 | -- @tparam string method a string key to index in object `obj`. 1516 | -- @tparam[opt] vararg ... Optional extra-args to be passed to `method` 1517 | -- @treturn value the returned value of `method(obj,...)` call 1518 | function _.result(obj, method, ...) 1519 | if obj[method] then 1520 | if _.isCallable(obj[method]) then 1521 | return obj[method](obj,...) 1522 | else return obj[method] 1523 | end 1524 | end 1525 | if _.isCallable(method) then 1526 | return method(obj,...) 1527 | end 1528 | end 1529 | 1530 | --- Checks if the given arg is a table. 1531 | -- @name isTable 1532 | -- @tparam table t a value to be tested 1533 | -- @treturn boolean `true` or `false` 1534 | function _.isTable(t) 1535 | return type(t) == 'table' 1536 | end 1537 | 1538 | --- Checks if the given argument is an callable. Assumes `obj` is callable if 1539 | -- it is either a function or a table having a metatable implementing `__call` metamethod. 1540 | -- @name isCallable 1541 | -- @tparam table obj an object 1542 | -- @treturn boolean `true` or `false` 1543 | function _.isCallable(obj) 1544 | return (_.isFunction(obj) or 1545 | (_.isTable(obj) and getmetatable(obj) 1546 | and getmetatable(obj).__call~=nil) or false) 1547 | end 1548 | 1549 | --- Checks if the given argument is an array. Assumes `obj` is an array 1550 | -- if is a table with integer numbers starting at 1. 1551 | -- @name isArray 1552 | -- @tparam table obj an object 1553 | -- @treturn boolean `true` or `false` 1554 | function _.isArray(obj) 1555 | if not _.isTable(obj) then return false end 1556 | -- Thanks @Wojak and @Enrique García Cota for suggesting this 1557 | -- See : http://love2d.org/forums/viewtopic.php?f=3&t=77255&start=40#p163624 1558 | local i = 0 1559 | for __ in pairs(obj) do 1560 | i = i + 1 1561 | if _.isNil(obj[i]) then return false end 1562 | end 1563 | return true 1564 | end 1565 | 1566 | --- Checks if the given object is iterable with `pairs` (or `ipairs`). 1567 | -- @name isIterable 1568 | -- @tparam table obj an object 1569 | -- @treturn boolean `true` if the object can be iterated with `pairs`, `false` otherwise 1570 | function _.isIterable(obj) 1571 | return _.toBoolean((pcall(pairs, obj))) 1572 | end 1573 | 1574 | --- Checks if the given is empty. If `obj` is a *string*, will return `true` 1575 | -- if `#obj == 0`. Otherwise, if `obj` is a table, will return whether or not this table 1576 | -- is empty. If `obj` is `nil`, it will return true. 1577 | -- @name isEmpty 1578 | -- @tparam[opt] table|string obj an object 1579 | -- @treturn boolean `true` or `false` 1580 | function _.isEmpty(obj) 1581 | if _.isNil(obj) then return true end 1582 | if _.isString(obj) then return #obj==0 end 1583 | if _.isTable(obj) then return next(obj)==nil end 1584 | return true 1585 | end 1586 | 1587 | --- Checks if the given argument is a *string*. 1588 | -- @name isString 1589 | -- @tparam table obj an object 1590 | -- @treturn boolean `true` or `false` 1591 | function _.isString(obj) 1592 | return type(obj) == 'string' 1593 | end 1594 | 1595 | --- Checks if the given argument is a function. 1596 | -- @name isFunction 1597 | -- @tparam table obj an object 1598 | -- @treturn boolean `true` or `false` 1599 | function _.isFunction(obj) 1600 | return type(obj) == 'function' 1601 | end 1602 | 1603 | --- Checks if the given argument is nil. 1604 | -- @name isNil 1605 | -- @tparam table obj an object 1606 | -- @treturn boolean `true` or `false` 1607 | function _.isNil(obj) 1608 | return obj==nil 1609 | end 1610 | 1611 | --- Checks if the given argument is a number. 1612 | -- @name isNumber 1613 | -- @tparam table obj a number 1614 | -- @treturn boolean `true` or `false` 1615 | -- @see isNaN 1616 | function _.isNumber(obj) 1617 | return type(obj) == 'number' 1618 | end 1619 | 1620 | --- Checks if the given argument is NaN (see [Not-A-Number](http://en.wikipedia.org/wiki/NaN)). 1621 | -- @name isNaN 1622 | -- @tparam table obj a number 1623 | -- @treturn boolean `true` or `false` 1624 | -- @see isNumber 1625 | function _.isNaN(obj) 1626 | return _.isNumber(obj) and obj~=obj 1627 | end 1628 | 1629 | --- Checks if the given argument is a finite number. 1630 | -- @name isFinite 1631 | -- @tparam table obj a number 1632 | -- @treturn boolean `true` or `false` 1633 | function _.isFinite(obj) 1634 | if not _.isNumber(obj) then return false end 1635 | return obj > -huge and obj < huge 1636 | end 1637 | 1638 | --- Checks if the given argument is a boolean. 1639 | -- @name isBoolean 1640 | -- @tparam table obj a boolean 1641 | -- @treturn boolean `true` or `false` 1642 | function _.isBoolean(obj) 1643 | return type(obj) == 'boolean' 1644 | end 1645 | 1646 | --- Checks if the given argument is an integer. 1647 | -- @name isInteger 1648 | -- @tparam table obj a number 1649 | -- @treturn boolean `true` or `false` 1650 | function _.isInteger(obj) 1651 | return _.isNumber(obj) and floor(obj)==obj 1652 | end 1653 | 1654 | -- Aliases 1655 | 1656 | do 1657 | 1658 | -- Table functions aliases 1659 | _.forEach = _.each 1660 | _.forEachi = _.eachi 1661 | _.loop = _.cycle 1662 | _.collect = _.map 1663 | _.inject = _.reduce 1664 | _.foldl = _.reduce 1665 | _.injectr = _.reduceRight 1666 | _.foldr = _.reduceRight 1667 | _.mapr = _.mapReduce 1668 | _.maprr = _.mapReduceRight 1669 | _.any = _.include 1670 | _.some = _.include 1671 | _.filter = _.select 1672 | _.discard = _.reject 1673 | _.every = _.all 1674 | 1675 | -- Array functions aliases 1676 | _.takeWhile = _.selectWhile 1677 | _.rejectWhile = _.dropWhile 1678 | _.shift = _.pop 1679 | _.remove = _.pull 1680 | _.rmRange = _.removeRange 1681 | _.chop = _.removeRange 1682 | _.sub = _.slice 1683 | _.head = _.first 1684 | _.take = _.first 1685 | _.tail = _.rest 1686 | _.skip = _.last 1687 | _.without = _.difference 1688 | _.diff = _.difference 1689 | _.symdiff = _.symmetricDifference 1690 | _.xor = _.symmetricDifference 1691 | _.uniq = _.unique 1692 | _.isuniq = _.isunique 1693 | _.part = _.partition 1694 | _.perm = _.permutation 1695 | _.mirror = _.invert 1696 | _.join = _.concat 1697 | 1698 | -- Utility functions aliases 1699 | _.cache = _.memoize 1700 | _.juxt = _.juxtapose 1701 | _.uid = _.uniqueId 1702 | 1703 | -- Object functions aliases 1704 | _.methods = _.functions 1705 | _.choose = _.pick 1706 | _.drop = _.omit 1707 | _.defaults = _.template 1708 | _.compare = _.isEqual 1709 | 1710 | end 1711 | 1712 | -- Setting chaining and building interface 1713 | 1714 | do 1715 | 1716 | -- Wrapper to Moses 1717 | local f = {} 1718 | 1719 | -- Will be returned upon requiring, indexes into the wrapper 1720 | local __ = {} 1721 | __.__index = f 1722 | 1723 | -- Wraps a value into an instance, and returns the wrapped object 1724 | local function new(value) 1725 | local i = {_value = value, _wrapped = true} 1726 | return setmetatable(i, __) 1727 | end 1728 | 1729 | setmetatable(__,{ 1730 | __call = function(self,v) return new(v) end, -- Calls returns to instantiation 1731 | __index = function(t,key,...) return f[key] end -- Redirects to the wrapper 1732 | }) 1733 | 1734 | --- Returns a wrapped object. Calling library functions as methods on this object 1735 | -- will continue to return wrapped objects until @{obj:value} is used. Can be aliased as `_(value)`. 1736 | -- @class function 1737 | -- @name chain 1738 | -- @tparam value value a value to be wrapped 1739 | -- @treturn object a wrapped object 1740 | function __.chain(value) 1741 | return new(value) 1742 | end 1743 | 1744 | --- Extracts the value of a wrapped object. Must be called on an chained object (see @{chain}). 1745 | -- @class function 1746 | -- @name obj:value 1747 | -- @treturn value the value previously wrapped 1748 | function __:value() 1749 | return self._value 1750 | end 1751 | 1752 | -- Register chaining methods into the wrapper 1753 | f.chain, f.value = __.chain, __.value 1754 | 1755 | -- Register all functions into the wrapper 1756 | for fname,fct in pairs(_) do 1757 | f[fname] = function(v, ...) 1758 | local wrapped = _.isTable(v) and v._wrapped or false 1759 | if wrapped then 1760 | local _arg = v._value 1761 | local _rslt = fct(_arg,...) 1762 | return new(_rslt) 1763 | else 1764 | return fct(v,...) 1765 | end 1766 | end 1767 | end 1768 | 1769 | --- Imports all library functions into a context. 1770 | -- @name import 1771 | -- @tparam[opt] table context a context. Defaults to `_G` (global environment) when not given. 1772 | -- @tparam[optchain] boolean noConflict Skips function import in case its key exists in the given context 1773 | -- @treturn table the passed-in context 1774 | f.import = function(context, noConflict) 1775 | context = context or _G 1776 | local funcs = _.functions() 1777 | _.each(funcs, function(k, fname) 1778 | if rawget(context, fname) then 1779 | if not noConflict then 1780 | context[fname] = _[fname] 1781 | end 1782 | else 1783 | context[fname] = _[fname] 1784 | end 1785 | end) 1786 | return context 1787 | end 1788 | 1789 | -- Descriptive tags 1790 | __._VERSION = 'Moses v'.._MODULEVERSION 1791 | __._URL = 'http://github.com/Yonaba/Moses' 1792 | __._LICENSE = 'MIT ' 1793 | __._DESCRIPTION = 'utility-belt library for functional programming in Lua' 1794 | 1795 | return __ 1796 | 1797 | end 1798 | -------------------------------------------------------------------------------- /hua/core/oo.hy: -------------------------------------------------------------------------------- 1 | (import [hy.models.list [HyList]]) 2 | 3 | (defmacro super [method &rest body] 4 | `((get --hua-parent-- ~(string method)) self ~@body)) 5 | 6 | (defmacro defclass [class-name base-list body] 7 | (when (not (instance? HyList base-list)) 8 | (macro-error base-list "defclass's second argument should be a list")) 9 | (def parent-name 10 | (if (empty? base-list) 11 | nil 12 | (first base-list))) 13 | 14 | (def class-name-string (string class-name)) 15 | 16 | ;; (try 17 | ;; (def body-expression (iter body)) 18 | ;; (catch [e TypeError] 19 | ;; (macro-error body 20 | ;; "Wrong argument type for defclass attributes definition."))) 21 | 22 | (def body-expression (iter body)) 23 | 24 | (def arglist {}) 25 | 26 | (for [b body-expression] 27 | (when (!= 2 (len b)) 28 | (macro-error body-expression 29 | "Wrong number of argument in defclass attribute.")) 30 | (assoc arglist (string (first b)) (second b))) 31 | 32 | (def class-index-expr 33 | (if parent-name 34 | '(fn [cls name] 35 | (def val (rawget --hua-base-- name)) 36 | (if (= val nil) 37 | (get --hua-parent-- name) 38 | val)) 39 | '--hua-base--)) 40 | 41 | `(do 42 | (def ~class-name nil) 43 | (do-block 44 | (def --hua-parent-- ~parent-name) 45 | (def --hua-base-- ~arglist) 46 | (def --hua-class-string-- ~class-name-string) 47 | (setv --hua-base--.--index --hua-base--) 48 | (when --hua-parent-- 49 | (setmetatable --hua-base-- --hua-parent--.--base)) 50 | (defn --hua-cls-call-- [cls *dotdotdot*] 51 | (def --hua-self-- (setmetatable {} --hua-base--)) 52 | (.--init --hua-self-- *dotdotdot*) 53 | --hua-self--) 54 | (def --hua-class-- 55 | (setmetatable {"__base" --hua-base-- "__name" --hua-class-string-- "__parent" --hua-parent--} 56 | 57 | {"__index" ~class-index-expr "__call" --hua-cls-call--})) 58 | (setv --hua-base--.--class --hua-class--) 59 | (when (and --hua-parent-- 60 | --hua-parent--.--inherited) 61 | (--hua-parent--.--inherited --hua-parent-- --hua-class--)) 62 | (setv ~class-name --hua-class--)))) 63 | -------------------------------------------------------------------------------- /hua/core/op.hy: -------------------------------------------------------------------------------- 1 | (import [hua.core.utils [hua-gensym simple-form?]]) 2 | 3 | ;;; compare operation with more than 2 arguments 4 | 5 | (defn my-eq [a b] 6 | (= a b)) 7 | 8 | (defn my-lt [a b] 9 | (< a b)) 10 | 11 | ;;; operators like =* (opstar) can only accept two arguments 12 | ;;; The macro below will generate macros like = (op), which will accept two or more arguments 13 | (defmacro --def-hua-comp-op-- [op opstar] 14 | `(defmacro ~op [&rest exprs] 15 | (def op* (quote ~opstar)) 16 | (when (my-lt (len exprs) 2) 17 | (macro-error exprs 18 | "comparison operator needs at least 2 operands")) 19 | 20 | (if (my-eq (len exprs) 2) 21 | ;; if only two arguments are given, return the normal form of opstar 22 | `(~op* ~(get exprs 0) 23 | ~(get exprs 1)) 24 | 25 | ;; if more than two arguments are given, we will expand to the form of (and (opstar e1 e2) (opstar e2 e3) ...) 26 | ;; The problems is that expressions like e2 will be evaluated twice. We need temporary variables to hold the evaluated value of e2. 27 | (let [[temp-vars (list-comp (if (simple-form? expr) 28 | nil 29 | (hua-gensym)) 30 | [expr exprs])] 31 | [binding-body (list-comp `(def ~(get temp-vars i) ~(get exprs i)) 32 | [i (range (len exprs))] 33 | (not (nil? (get temp-vars i))))] 34 | [compare-vars (list-comp (if (simple-form? expr) 35 | expr 36 | (hua-gensym)) 37 | [expr exprs])] 38 | [comparing-body (list-comp `(~op* ~(get compare-vars (- i 1)) 39 | ~(get compare-vars i)) 40 | [i (range 1 (len compare-vars))])]] 41 | `(do 42 | ~@binding-body 43 | (and ~@comparing-body)))))) 44 | 45 | (--def-hua-comp-op-- = =*) 46 | (--def-hua-comp-op-- < <*) 47 | (--def-hua-comp-op-- <= <=*) 48 | 49 | (defmacro >* [l r] 50 | `(not (<=* ~l ~r))) 51 | 52 | (defmacro >=* [l r] 53 | `(not (<* ~l ~r))) 54 | 55 | (defmacro !=* [l r] 56 | `(not (=* ~l ~r))) 57 | 58 | (--def-hua-comp-op-- > >*) 59 | (--def-hua-comp-op-- >= >=*) 60 | (--def-hua-comp-op-- != !=*) 61 | -------------------------------------------------------------------------------- /hua/core/utils.hy: -------------------------------------------------------------------------------- 1 | (import [hy.models.symbol [HySymbol]]) 2 | 3 | (defn hua-gensym [&optional [sym nil]] 4 | (def temp-sym (string (if sym 5 | (gensym sym) 6 | (gensym)))) 7 | (HySymbol (.replace temp-sym ":" "_hua_"))) 8 | 9 | ;;; if a form is a string, a number or a symbol 10 | (defn simple-form? [o] 11 | (or (string? o) 12 | (numeric? o) 13 | (instance? HySymbol o))) 14 | -------------------------------------------------------------------------------- /hua/lua.hy: -------------------------------------------------------------------------------- 1 | (import [os.path [realpath dirname]]) 2 | (import lupa) 3 | (import [lupa [LuaRuntime]]) 4 | 5 | (def current-path (dirname(realpath --file--))) 6 | 7 | (defn init-lua [] 8 | "return a lua runtime" 9 | (apply LuaRuntime [] {"unpack_returned_tuples true"})) 10 | 11 | (def lua (init-lua)) 12 | 13 | (defn -dict? [o] 14 | "if o is an instance of dictionary" 15 | (instance? dict o)) 16 | 17 | (defn -lt? [o] 18 | "if o is an instance of list or tuple" 19 | (instance? (, list tuple) o)) 20 | 21 | (defn lt->dict [lt] 22 | "convert a list/tuple to lua table style dict (index start with 1)" 23 | (let [[r {}]] 24 | (for [(, i v) (enumerate lt)] 25 | (assoc r (inc i) v)) 26 | r)) 27 | 28 | (defn -dict->ltable [lua-runtime d-] 29 | "recursively convert a python dictionary into lua table" 30 | (let [[r {}] 31 | [d (if (-lt? d-) 32 | (lt->dict d-) 33 | d-)]] 34 | (for [(, k v) (.items d)] 35 | (let [[new-v (cond 36 | [(-dict? v) (-dict->ltable lua-runtime v)] 37 | [(-lt? v) (-dict->ltable lua-runtime (lt->dict v))] 38 | [true v])]] 39 | (assoc r k new-v))) 40 | (.table-from lua-runtime r))) 41 | 42 | 43 | (def tlua (init-lua)) 44 | (let [[lua-package-table (.require tlua "package")] 45 | [lua-package-path (. lua-package-table path)]] 46 | (assoc lua-package-table 47 | "path" 48 | (+ lua-package-path 49 | ";" 50 | current-path 51 | "/?.lua"))) 52 | 53 | (def tlcode (.require tlua "tlcode")) 54 | 55 | (defn tlast->src [ast-table] 56 | (tlcode.generate (.table-from tlua (-dict->ltable tlua ast-table)))) 57 | -------------------------------------------------------------------------------- /hua/mlast.hy: -------------------------------------------------------------------------------- 1 | (import [numbers [Real]]) 2 | 3 | (import [hua.lua [lt->dict]]) 4 | 5 | (def *op-ids* {:+ "add" 6 | :- "sub" 7 | :* "mul" 8 | :/ "div" 9 | :// "idiv" 10 | :% "mod" 11 | :^ "pow" 12 | :& "band" 13 | :bor "bor" 14 | :<< "shl" 15 | :>> "shr" 16 | :concat "concat" 17 | :=* "eq" 18 | :<* "lt" 19 | :<=* "le" 20 | :~=* "ne" 21 | :>* "gt" 22 | :>=* "ge" 23 | :and "and" 24 | :or "or" 25 | :not "not" 26 | :len "len"}) 27 | 28 | ;;; given the operator name in hua, return corresponding op-id 29 | (defn get-op-id [op] 30 | (if (= op "|") 31 | "bor" 32 | (get *op-ids* (keyword op)))) 33 | 34 | (defclass ASTNode [object] 35 | [[--init-- 36 | (fn [self] 37 | (setv self.nodes []) 38 | nil)] 39 | [gen-repr-template 40 | (fn [self] 41 | (.join "" [""]))] 42 | [--repr-- 43 | (fn [self] 44 | (% (.gen-repr-template self) (% "nodes: %s" self.nodes)))] 45 | [to-ml-table 46 | (fn [self] 47 | (let [[res {"tag" self.tag}] 48 | [-nodes (list-comp (cond [(instance? ASTNode node) 49 | (.to-ml-table node)] 50 | [(instance? dict node) 51 | (dict-comp key (.to-ml-table (get node key)) [key (.keys node)])] 52 | [(instance? (, list tuple) node) 53 | (lt->dict (list-comp (.to-ml-table subnode) 54 | [subnode node]))] 55 | [(or (instance? Real node) 56 | (string? node)) 57 | node] 58 | [true 59 | (. node expr nodes)]) 60 | [node self.nodes])] 61 | [nodes (lt->dict -nodes)]] 62 | (.update res nodes) 63 | (if (instance? Multi self) 64 | nodes 65 | res)))]]) 66 | 67 | ;;; return an ast tree as {tag=tag, nodes=[node1, node2, ...]} form 68 | ;;; also "slice unquote" Multi ast 69 | (defn -to-ml-table-pass-1 [tree] 70 | (cond [(instance? (, list tuple) tree) 71 | (do 72 | (def nodes []) 73 | (for [node tree] 74 | (def elt (-to-ml-table-pass-1 node)) 75 | ;; slice unquoting Multi ast 76 | (if (and (instance? dict elt) 77 | (= (get elt "tag") "Multi")) 78 | (+= nodes (get elt "nodes")) 79 | (.append nodes elt))) 80 | nodes)] 81 | [(or (instance? Real tree) 82 | (string? tree)) 83 | tree] 84 | [(instance? ASTNode tree) 85 | (do 86 | (def res {"tag" tree.tag}) 87 | (def nodes (-to-ml-table-pass-1 tree.nodes)) 88 | (.update res {"nodes" nodes}) 89 | res)])) 90 | 91 | (defn -to-ml-table-pass-2 [tree] 92 | (cond [(instance? (, list tuple) tree) 93 | (lt->dict (list-comp (-to-ml-table-pass-2 node) 94 | [node tree]))] 95 | [(instance? dict tree) 96 | (let [[res {"tag" (get tree "tag")}] 97 | [nodes (-to-ml-table-pass-2 (get tree "nodes"))]] 98 | (.update res nodes) 99 | res)] 100 | [(or (instance? Real tree) 101 | (string? tree)) 102 | tree])) 103 | 104 | (defn to-ml-table [tree] 105 | (-> tree 106 | -to-ml-table-pass-1 107 | -to-ml-table-pass-2)) 108 | 109 | 110 | (defclass Stat [ASTNode]) 111 | 112 | (defclass Expr [ASTNode]) 113 | 114 | (defclass LHS [Expr]) 115 | 116 | (defclass Apply [Stat Expr]) 117 | 118 | ;;; (simple) test functions 119 | (defn expr? [o] 120 | (instance? Expr o)) 121 | 122 | (defn stat? [o] 123 | (instance? Stat o)) 124 | 125 | (defn block? [o] 126 | (and (coll? o) 127 | (every? stat? o))) 128 | 129 | 130 | (defclass Nil [Expr] 131 | [[tag "Nil"]]) 132 | 133 | (defclass Dots [Expr] 134 | [[tag "Dots"]]) 135 | 136 | (defclass MLTrue [Expr] 137 | [[tag "True"]]) 138 | 139 | (defclass MLFalse [Expr] 140 | [[tag "False"]]) 141 | 142 | (defclass Number [Expr] 143 | [[tag "Number"] 144 | [--init-- 145 | (fn [self value] 146 | (setv self.value value) 147 | (setv self.nodes [value]) 148 | None)] 149 | [--repr-- 150 | (fn [self] 151 | (% (.gen-repr-template self) (get self.nodes 0)))]]) 152 | 153 | (defclass String [Expr] 154 | [[tag "String"] 155 | [--init-- 156 | (fn [self value] 157 | (setv self.value value) 158 | (setv self.nodes [value]) 159 | None)] 160 | [--repr-- 161 | (fn [self] 162 | (% (.gen-repr-template self) (get self.nodes 0)))]]) 163 | 164 | (defclass Function [Expr] 165 | [[tag "Function"] 166 | [--init-- 167 | (fn [self args body] 168 | (setv self.args args) 169 | (setv self.body body) 170 | nil)] 171 | [nodes 172 | (with-decorator property 173 | (defn nodes [self] 174 | [self.args self.body]))]]) 175 | 176 | ;;; for metalua ast Table internal usage 177 | (defclass -Pair [ASTNode] 178 | [[tag "Pair"] 179 | [--init-- 180 | (fn [self p1 p2] 181 | (setv self.nodes [p1 p2]) 182 | nil)]]) 183 | 184 | (defclass Table [Expr] 185 | [[tag "Table"] 186 | [--init-- 187 | (fn [self array-part hash-part] 188 | (setv self.array-part array-part) 189 | (setv self.hash-part hash-part) 190 | nil)] 191 | [nodes 192 | (with-decorator property 193 | (defn nodes [self] 194 | (let [[array-list (if (nil? self.array-part) 195 | [] 196 | self.array-part)] 197 | [hash-part (if (nil? self.hash-part) 198 | [] 199 | (list-comp (-Pair key value) 200 | [(, key value) (.items self.hash-part)]))]] 201 | (+ array-list hash-part))))]]) 202 | 203 | (defclass Op [Expr] 204 | [[tag "Op"] 205 | [--init-- 206 | (fn [self opid e1 &optional [e2 nil]] 207 | (setv self.opid opid) 208 | (setv self.e1 e1) 209 | (setv self.e2 e2) 210 | nil)] 211 | [nodes 212 | (with-decorator property 213 | (defn nodes [self] 214 | (let [[ret [self.opid self.e1]]] 215 | (if self.e2 216 | (+ ret [self.e2]) 217 | ret))))]]) 218 | 219 | (defclass Paren [Expr] 220 | [[tag "Paren"] 221 | [--init-- 222 | (fn [self expr] 223 | (setv self.nodes [expr]) 224 | nil)]]) 225 | 226 | ;;; not a metalua AST, just for multiple assignment/return 227 | (defclass Multi [Expr] 228 | [[tag "Multi"] 229 | [--init-- 230 | (fn [self exprs] 231 | (setv self.exprs exprs) 232 | nil)] 233 | [nodes 234 | (with-decorator property 235 | (fn [self] 236 | (def ret (cond [(coll? self.exprs) 237 | self.exprs] 238 | [true 239 | [self.exprs]])) 240 | ret))] 241 | [count 242 | (fn [self] 243 | (len self.nodes))]]) 244 | 245 | (defn convert-to-multi [node] 246 | (cond [(instance? Multi node) 247 | node] 248 | [(instance? Expr node) 249 | (Multi [node])] 250 | [(coll? node) 251 | (Multi node)])) 252 | 253 | ;;; lhs 254 | (defclass Id [LHS] 255 | [[tag "Id"] 256 | [--init-- 257 | (fn [self name] 258 | (setv self.name name) 259 | nil)] 260 | [--repr-- 261 | (fn [self] 262 | (% (.gen-repr-template self) self.name))] 263 | [nodes 264 | (with-decorator property 265 | (fn [self] 266 | [self.name]))]]) 267 | 268 | (defclass Index [LHS] 269 | [[tag "Index"] 270 | [--init-- 271 | (fn [self expr1 expr2] 272 | (setv self.nodes [expr1 expr2]) 273 | nil)]]) 274 | 275 | ;;; Statements 276 | (defclass Do [Stat] 277 | [[tag "Do"] 278 | [--init-- 279 | (fn [self stats] 280 | (setv self.nodes stats) 281 | nil)]]) 282 | 283 | (defclass Set [Stat] 284 | [[tag "Set"] 285 | [--init-- 286 | (fn [self lhss rhss] 287 | (setv self.lhss (convert-to-multi lhss)) 288 | (setv self.rhss (convert-to-multi rhss)) 289 | nil)] 290 | [nodes 291 | (with-decorator property 292 | (fn [self] 293 | [[self.lhss] [self.rhss]]))]]) 294 | 295 | (defclass If [Stat] 296 | [[tag "If"] 297 | [--init-- 298 | (fn [self expr1 block1 &rest rest] 299 | (setv self.nodes [expr1 block1]) 300 | (+= self.nodes rest) 301 | nil)]]) 302 | 303 | (defclass Fornum [Stat] 304 | [[tag "Fornum"] 305 | [--init-- 306 | (fn [self target expr-list body] 307 | (setv self.nodes (+ [target] expr-list [body])) 308 | nil)]]) 309 | 310 | (defclass Forin [Stat] 311 | [[tag "Forin"] 312 | [--init-- 313 | (fn [self target iterable body] 314 | (setv self.target (convert-to-multi target)) 315 | (setv self.iterable iterable) 316 | (setv self.body body) 317 | nil)] 318 | [nodes 319 | (with-decorator property 320 | (fn [self] 321 | [[self.target] [self.iterable] self.body]))]]) 322 | 323 | (defclass Local [Stat] 324 | [[tag "Local"] 325 | [--init-- 326 | (fn [self lhss &optional [rhss nil]] 327 | (setv self.lhss (convert-to-multi lhss)) 328 | (setv self.rhss (convert-to-multi rhss)) 329 | nil)] 330 | [nodes 331 | (with-decorator property 332 | (fn [self] 333 | (def lhss-nodes [self.lhss]) 334 | (def rhss-nodes (if (nil? self.rhss) 335 | [] 336 | [self.rhss])) 337 | [lhss-nodes rhss-nodes]))]]) 338 | 339 | (defclass Return [Stat] 340 | [[tag "Return"] 341 | [--init-- 342 | (fn [self return-exprs] 343 | (if (coll? return-exprs) 344 | (setv self.nodes return-exprs) 345 | (setv self.nodes [return-exprs])) 346 | nil)]]) 347 | 348 | ;;; Apply 349 | (defclass Call [Apply] 350 | [[tag "Call"] 351 | [--init-- 352 | (fn [self func args] 353 | (when (instance? Function func) 354 | (setv func (Paren func))) 355 | (setv self.func func) 356 | (setv self.args args) 357 | nil)] 358 | [nodes 359 | (with-decorator property 360 | (defn nodes [self] 361 | (+ [self.func] self.args)))]]) 362 | 363 | (defclass Invoke [Apply] 364 | [[tag "Invoke"] 365 | [--init-- 366 | (fn [self obj method args] 367 | (setv self.obj obj) 368 | (setv self.method method) 369 | (setv self.args args) 370 | nil)] 371 | [nodes 372 | (with-decorator property 373 | (defn nodes [self] 374 | (+ [self.obj self.method] self.args)))]]) 375 | 376 | ;;; test 377 | ;; (import [lua [init-lua lua lua-astc -dict->ltable]]) 378 | ;; (setv test-expr (Local [(Id "a") (Id "b")] [(Number 1)])) 379 | ;; (setv test-expr2 (Set [(Id "a") (Id "b")] [(Number 1) (Number 3)])) 380 | ;; (setv test-3 (If (MLTrue) [test-expr2] [test-expr2])) 381 | ;; (print (.to-ml-table test-expr)) 382 | ;; ;(print (.ast-to-src lua-astc lua-astc (.table-from lua (-dict->ltable lua (.to-ml-table test-expr2))))) 383 | ;; ;(print (.ast-to-src lua-astc lua-astc (.table-from lua (-dict->ltable lua (.to-ml-table test-3))))) 384 | 385 | ;; (print (.ast-to-src lua-astc lua-astc (.table-from lua (-dict->ltable lua (.to-ml-table (Index (Index (Id "a") (Number 1)) (Id "b"))))))) 386 | 387 | 388 | 389 | -------------------------------------------------------------------------------- /hua/tlcode.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This file implements the code generator for Typed Lua 3 | ]] 4 | local tlcode = {} 5 | 6 | local code_block, code_stm, code_exp, code_var 7 | local code_explist, code_varlist, code_fieldlist, code_idlist 8 | 9 | local function spaces (n) 10 | return string.rep(" ", 2 * n) 11 | end 12 | 13 | local function indent (s, n) 14 | return spaces(n) .. s 15 | end 16 | 17 | local function iscntrl (x) 18 | if (x >= 0 and x <= 31) or (x == 127) then return true end 19 | return false 20 | end 21 | 22 | local function isprint (x) 23 | return not iscntrl(x) 24 | end 25 | 26 | local function fix_str (str) 27 | local new_str = "" 28 | for i=1,string.len(str) do 29 | char = string.byte(str, i) 30 | if char == 34 then new_str = new_str .. string.format("\\\"") 31 | elseif char == 92 then new_str = new_str .. string.format("\\\\") 32 | elseif char == 7 then new_str = new_str .. string.format("\\a") 33 | elseif char == 8 then new_str = new_str .. string.format("\\b") 34 | elseif char == 12 then new_str = new_str .. string.format("\\f") 35 | elseif char == 10 then new_str = new_str .. string.format("\\n") 36 | elseif char == 13 then new_str = new_str .. string.format("\\r") 37 | elseif char == 9 then new_str = new_str .. string.format("\\t") 38 | elseif char == 11 then new_str = new_str .. string.format("\\v") 39 | else 40 | if isprint(char) then 41 | new_str = new_str .. string.format("%c", char) 42 | else 43 | new_str = new_str .. string.format("\\%03d", char) 44 | end 45 | end 46 | end 47 | return new_str 48 | end 49 | 50 | local op = { add = " + ", 51 | sub = " - ", 52 | mul = " * ", 53 | idiv = " // ", 54 | div = " / ", 55 | mod = " % ", 56 | pow = " ^ ", 57 | concat = " .. ", 58 | eq = " == ", 59 | lt = " < ", 60 | le = " <= ", 61 | bor = "|", 62 | bxor = "~", 63 | band = "&", 64 | shl = "<<", 65 | shr = ">>", 66 | ["and"] = " and ", 67 | ["or"] = " or ", 68 | ["not"] = "not ", 69 | unm = "-", 70 | bnot = "~", 71 | len = "#" } 72 | 73 | local function code_call (call, fmt) 74 | local l = {} 75 | for k = 2, #call do 76 | l[k - 1] = code_exp(call[k], fmt) 77 | end 78 | return code_exp(call[1], fmt) .. "(" .. table.concat(l, ",") .. ")" 79 | end 80 | 81 | local function code_invoke (invoke, fmt) 82 | local l = {} 83 | for k = 3, #invoke do 84 | l[k - 2] = code_exp(invoke[k], fmt) 85 | end 86 | local str = code_exp(invoke[1], fmt) 87 | str = str .. ":" .. invoke[2][1] 88 | str = str .. "(" .. table.concat(l, ",") .. ")" 89 | return str 90 | end 91 | 92 | local function code_parlist (parlist, fmt) 93 | local l = {} 94 | local len = #parlist 95 | local is_vararg = false 96 | if len > 0 and parlist[len].tag == "Dots" then 97 | is_vararg = true 98 | len = len - 1 99 | end 100 | local k = 1 101 | for k=1, len do 102 | l[k] = code_var(parlist[k], fmt) 103 | end 104 | if is_vararg then 105 | table.insert(l, "...") 106 | end 107 | return table.concat(l, ", ") 108 | end 109 | 110 | local function code_fieldlist (fieldlist, fmt) 111 | local l = {} 112 | for k, v in ipairs(fieldlist) do 113 | if v.tag == "Pair" then 114 | l[k] = "[" .. code_exp(v[1], fmt) .. "] = " .. code_exp(v[2], fmt) 115 | else 116 | l[k] = code_exp(v, fmt) 117 | end 118 | end 119 | return table.concat(l, ", ") 120 | end 121 | 122 | function code_var (var, fmt) 123 | local tag = var.tag 124 | if tag == "Id" then 125 | return var[1] 126 | elseif tag == "Index" then 127 | if var[1].tag == "Id" and var[1][1] == "_ENV" and var[2].tag == "String" then 128 | local v = { tag = "Id", [1] = var[2][1] } 129 | return code_exp(v, fmt) 130 | else 131 | return code_exp(var[1], fmt) .. "[" .. code_exp(var[2], fmt) .. "]" 132 | end 133 | else 134 | error("trying to generate code for a variable, but got a " .. tag) 135 | end 136 | end 137 | 138 | function code_varlist (varlist, fmt) 139 | local l = {} 140 | for k, v in ipairs(varlist) do 141 | l[k] = code_var(v, fmt) 142 | end 143 | return table.concat(l, ", ") 144 | end 145 | 146 | function code_exp (exp, fmt) 147 | local tag = exp.tag 148 | if tag == "Nil" then 149 | return "nil" 150 | elseif tag == "Dots" then 151 | return "..." 152 | elseif tag == "True" then 153 | return "true" 154 | elseif tag == "False" then 155 | return "false" 156 | elseif tag == "Number" then 157 | return tostring(exp[1]) 158 | elseif tag == "String" then 159 | return '"' .. fix_str(exp[1]) .. '"' 160 | elseif tag == "Function" then 161 | local str = "function (" 162 | str = str .. code_parlist(exp[1], fmt) .. ")\n" 163 | if not exp[3] then 164 | str = str .. code_block(exp[2], fmt) .. indent("end", fmt) 165 | else 166 | str = str .. code_block(exp[3], fmt) .. indent("end", fmt) 167 | end 168 | return str 169 | elseif tag == "Table" then 170 | local str = "{" .. code_fieldlist(exp, fmt) .. "}" 171 | return str 172 | elseif tag == "Op" then 173 | local str = "" 174 | if exp[3] then 175 | if _VERSION == "Lua 5.3" then 176 | if exp[2].tag == "Call" and exp[2][1].tag == "Index" and 177 | exp[2][1][1].tag == "Id" and exp[2][1][1][1] == "_ENV" and 178 | exp[2][1][2].tag == "String" and exp[2][1][2][1] == "type" and 179 | exp[3].tag == "String" and exp[3][1] == "integer" then 180 | str = "math." 181 | end 182 | end 183 | str = str .. code_exp(exp[2], fmt) .. op[exp[1]] .. code_exp(exp[3], fmt) 184 | else 185 | str = str .. op[exp[1]] .. "(" .. code_exp(exp[2], fmt) .. ")" 186 | end 187 | return str 188 | elseif tag == "Paren" then 189 | local str = "(" .. code_exp(exp[1], fmt) .. ")" 190 | return str 191 | elseif tag == "Call" then 192 | return code_call(exp, fmt) 193 | elseif tag == "Invoke" then 194 | return code_invoke(exp, fmt) 195 | elseif tag == "Id" or 196 | tag == "Index" then 197 | return code_var(exp, fmt) 198 | else 199 | error("trying to generate code for a expression, but got a " .. tag) 200 | end 201 | end 202 | 203 | function code_explist (explist, fmt) 204 | local l = {} 205 | for k, v in ipairs(explist) do 206 | l[k] = code_exp(v, fmt) 207 | end 208 | return table.concat(l, ", ") 209 | end 210 | 211 | function code_stm (stm, fmt) 212 | local tag = stm.tag 213 | if tag == "Do" then 214 | local str = indent("do\n", fmt) .. code_block(stm, fmt) .. indent("end", fmt) 215 | return str 216 | elseif tag == "Set" then 217 | local str = spaces(fmt) 218 | str = str .. code_varlist(stm[1], fmt) .. " = " .. code_explist(stm[2], fmt) 219 | return str 220 | elseif tag == "While" then 221 | local str = indent("while ", fmt) .. code_exp(stm[1], 0) .. " do\n" 222 | str = str .. code_block(stm[2], fmt) .. indent("end", fmt) 223 | return str 224 | elseif tag == "Repeat" then 225 | local str = indent("repeat\n", fmt) 226 | str = str .. code_block(stm[1], fmt) 227 | str = str .. indent("until ", fmt) 228 | str = str .. code_exp(stm[2], fmt) 229 | return str 230 | elseif tag == "If" then 231 | local str = indent("if ", fmt) .. code_exp(stm[1], 0) .. " then\n" 232 | str = str .. code_block(stm[2], fmt) 233 | local len = #stm 234 | if len % 2 == 0 then 235 | for k=3, len, 2 do 236 | str = str .. indent("elseif ", fmt) .. code_exp(stm[k], 0) .. " then\n" 237 | str = str .. code_block(stm[k+1], fmt) 238 | end 239 | else 240 | for k=3, len-1, 2 do 241 | str = str .. indent("elseif ", fmt) .. code_exp(stm[k], 0) .. " then\n" 242 | str = str .. code_block(stm[k+1], fmt) 243 | end 244 | str = str .. indent("else\n", fmt) 245 | str = str .. code_block(stm[len], fmt) 246 | end 247 | str = str .. indent("end", fmt) 248 | return str 249 | elseif tag == "Fornum" then 250 | local str = indent("for ", fmt) 251 | str = str .. code_var(stm[1], fmt) .. " = " .. code_exp(stm[2], fmt) 252 | str = str .. ", " .. code_exp(stm[3], fmt) 253 | if stm[5] then 254 | str = str .. ", " .. code_exp(stm[4], fmt) .. " do\n" 255 | str = str .. code_block(stm[5], fmt) 256 | else 257 | str = str .. " do\n" .. code_block(stm[4], fmt) 258 | end 259 | str = str .. indent("end", fmt) 260 | return str 261 | elseif tag == "Forin" then 262 | local str = indent("for ", fmt) 263 | str = str .. code_varlist(stm[1], fmt) .. " in " 264 | str = str .. code_explist(stm[2], fmt) .. " do\n" 265 | str = str .. code_block(stm[3], fmt) 266 | str = str .. indent("end", fmt) 267 | return str 268 | elseif tag == "Local" then 269 | local str = indent("local ", fmt) .. code_varlist(stm[1], fmt) 270 | if #stm[2] > 0 then 271 | str = str .. " = " .. code_explist(stm[2], fmt) 272 | end 273 | return str 274 | elseif tag == "Localrec" then 275 | local str = indent("local function ", fmt) .. code_var(stm[1][1], fmt) 276 | str = str .. " (" .. code_parlist(stm[2][1][1], fmt) .. ")\n" 277 | if not stm[2][1][3] then 278 | str = str .. code_block(stm[2][1][2], fmt) .. indent("end", fmt) 279 | else 280 | str = str .. code_block(stm[2][1][3], fmt) .. indent("end", fmt) 281 | end 282 | return str 283 | elseif tag == "Goto" then 284 | local str = indent("goto ", fmt) .. stm[1] 285 | return str 286 | elseif tag == "Label" then 287 | local str = indent("::", fmt) .. stm[1] .. "::" 288 | return str 289 | elseif tag == "Return" then 290 | local str = indent("return ", fmt) .. code_explist(stm, fmt) 291 | return str 292 | elseif tag == "Break" then 293 | return indent("break", fmt) 294 | elseif tag == "Call" then 295 | return indent(code_call(stm, fmt), fmt) 296 | elseif tag == "Invoke" then 297 | return indent(code_invoke(stm, fmt), fmt) 298 | elseif tag == "Interface" then 299 | return "" 300 | else 301 | error("tyring to generate code for a statement, but got " .. tag) 302 | end 303 | end 304 | 305 | function code_block (block, fmt) 306 | local l = {} 307 | for k, v in ipairs(block) do 308 | l[k] = code_stm(v, fmt + 1) 309 | end 310 | return table.concat(l, "\n") .. "\n" 311 | end 312 | 313 | function tlcode.generate (ast) 314 | assert(type(ast) == "table") 315 | return code_block(ast, -1) 316 | end 317 | 318 | return tlcode 319 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright (c) 2012, 2013 Paul Tagliamonte 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a 5 | # copy of this software and associated documentation files (the "Software"), 6 | # to deal in the Software without restriction, including without limitation 7 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 | # and/or sell copies of the Software, and to permit persons to whom the 9 | # Software is furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 17 | # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | # DEALINGS IN THE SOFTWARE. 21 | 22 | import os 23 | import re 24 | import sys 25 | 26 | from setuptools import find_packages, setup 27 | 28 | PKG = "hua" 29 | VERSIONFILE = os.path.join(PKG, "version.py") 30 | verstr = "unknown" 31 | try: 32 | verstrline = open(VERSIONFILE, "rt").read() 33 | except EnvironmentError: 34 | pass # Okay, there is no version file. 35 | else: 36 | VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" 37 | mo = re.search(VSRE, verstrline, re.M) 38 | if mo: 39 | __version__ = mo.group(1) 40 | else: 41 | msg = "if %s.py exists, it is required to be well-formed" % VERSIONFILE 42 | raise RuntimeError(msg) 43 | 44 | long_description = """Hua is a Lisp to lua compiler. It gives lua 45 | simple and powerful meta programming ability.""" 46 | 47 | install_requires = ['hy>=0.11.0', 'lupa>=1.1'] 48 | if sys.version_info[:2] < (2, 7): 49 | install_requires.append('argparse>=1.2.1') 50 | install_requires.append('importlib>=1.0.2') 51 | if os.name == 'nt': 52 | install_requires.append('pyreadline==2.0') 53 | 54 | setup( 55 | name=PKG, 56 | #version=__version__, 57 | version="0.0.1", 58 | install_requires=install_requires, 59 | entry_points={ 60 | 'console_scripts': [ 61 | 'hua = hua.cmdline:hua_main', 62 | 'huac = hua.cmdline:huac_main', 63 | ] 64 | }, 65 | packages=find_packages(exclude=['tests*']), 66 | package_data={ 67 | 'hua.core': ['*.hy', '*.hua'], 68 | }, 69 | author="Zhao Shenyang", 70 | author_email="dev@zsy.im", 71 | long_description=long_description, 72 | description='Lisp to lua compiler.', 73 | license="Expat", 74 | url="", 75 | platforms=['any'], 76 | classifiers=[ 77 | "Development Status :: 2 - Pre-Alpha", 78 | "Intended Audience :: Developers", 79 | "License :: DFSG approved", 80 | "License :: OSI Approved :: MIT License", # Really "Expat". Ugh. 81 | "Operating System :: OS Independent", 82 | "Programming Language :: Lisp", 83 | "Programming Language :: lua", 84 | "Programming Language :: Python", 85 | "Programming Language :: Python :: 2", 86 | "Programming Language :: Python :: 2.6", 87 | "Programming Language :: Python :: 2.7", 88 | "Programming Language :: Python :: 3", 89 | "Programming Language :: Python :: 3.3", 90 | "Programming Language :: Python :: 3.4", 91 | "Topic :: Software Development :: Code Generators", 92 | "Topic :: Software Development :: Compilers", 93 | "Topic :: Software Development :: Libraries", 94 | ] 95 | ) 96 | -------------------------------------------------------------------------------- /tests/native_tests/core.hua: -------------------------------------------------------------------------------- 1 | (require-macro hua.core.initialize) 2 | (--hua-initialize--) 3 | 4 | (hua-import luaunit) 5 | 6 | (def assert-equal luaunit.assertEquals) 7 | 8 | (def m {}) 9 | 10 | (setv m.test-def 11 | (fn [] 12 | (def x 1) 13 | (def y 1) 14 | (assert-equal x y) 15 | (def x (def y (fn [x] 9))) 16 | (assert-equal (x y) 9) 17 | (assert-equal (y x) 9))) 18 | 19 | (setv m.test-setv 20 | (fn [] 21 | (def x nil) 22 | (def y nil) 23 | (setv x (setv y 12)) 24 | (assert-equal x 12) 25 | (assert-equal y 12))) 26 | 27 | (return m) 28 | -------------------------------------------------------------------------------- /tests/native_tests/defclass.hua: -------------------------------------------------------------------------------- 1 | (require-macro hua.core.initialize) 2 | (--hua-initialize--) 3 | 4 | (hua-import luaunit) 5 | 6 | (def assert-equal luaunit.assertEquals) 7 | 8 | (def m {}) 9 | 10 | (setv m.test-defclass-attrs 11 | (fn [] 12 | (defclass A [] 13 | [[--init (fn [self] self)] 14 | [x 42]]) 15 | (assert-equal A.x 42) 16 | (assert-equal (get (A) "x") 42))) 17 | 18 | (setv m.test-defclass-attrs-fn 19 | (fn [] 20 | (defclass B [] 21 | [[--init (fn [self] self)] 22 | [x 42] 23 | [y (fn [self value] 24 | (+ self.x value))]]) 25 | (assert-equal B.x 42) 26 | (assert-equal (.y (B) 5) 47) 27 | (let [[b (B)]] 28 | (setv B.x 0) 29 | (assert-equal (.y B 1) 1)))) 30 | 31 | (return m) 32 | -------------------------------------------------------------------------------- /tests/native_tests/language.hua: -------------------------------------------------------------------------------- 1 | (require-macro hua.core.initialize) 2 | (--hua-initialize--) 3 | 4 | (hua-import luaunit) 5 | 6 | (def assert-equal luaunit.assertEquals) 7 | (def assert-true luaunit.assertTrue) 8 | (def assert-false luaunit.assertFalse) 9 | 10 | (def m {}) 11 | 12 | (setv m.test-lists 13 | (fn [] 14 | (assert-equal [1 2 3 4] 15 | {1 1 2 2 3 3 4 4}))) 16 | 17 | (setv m.test-dicts 18 | (fn [] 19 | (assert-equal {1 2 3 4} 20 | {3 4 1 2}) 21 | (assert-equal {1 2 3 4} 22 | {1 (+ 1 1) 3 (+ 2 2)}))) 23 | 24 | (setv m.test-setv-get 25 | (fn [] 26 | (def foo [1 2 3]) 27 | (setv (get foo 1) 12) 28 | (assert-equal (get foo 1) 12))) 29 | 30 | (setv m.test-for-loop 31 | (fn [] 32 | (def count 0) 33 | (for [i [1 5]] 34 | (+= count i)) 35 | (assert-equal count 15) 36 | (setv count 0) 37 | (for [i [1 5] 38 | (, _ j) (ipairs [1 2 3 4 5])] 39 | (setv count (+ count i j))) 40 | (assert-equal count 150))) 41 | 42 | (setv m.test-not 43 | (fn [] 44 | (assert-true (not (= 1 2))) 45 | (assert-true (= true (not false))) 46 | (assert-true (= false (not 42))))) 47 | 48 | (setv m.test-noteq 49 | (fn [] 50 | (assert-true (!= 2 3)))) 51 | 52 | (setv m.test-numops 53 | (fn [] 54 | (assert-true (> 5 4 3 2 1)) 55 | (assert-true (< 1 2 3 4 5)) 56 | (assert-true (<= 5 5 5 5)) 57 | (assert-true (>= 5 5 5 5)))) 58 | 59 | (setv m.test-branching 60 | (fn [] 61 | "NATIVE: test if branching" 62 | (if true 63 | (assert (= 1 1)) 64 | (assert (= 2 1))))) 65 | 66 | (setv m.test-branching 67 | (fn [] 68 | (if true 69 | (assert-true (= 1 1)) 70 | (assert-true (= 2 1))))) 71 | 72 | 73 | (setv m.test-branching-with-do 74 | (fn [] 75 | (if false 76 | (assert-true (= 2 1)) 77 | (do 78 | (assert-true (= 1 1)) 79 | (assert-true (= 1 1)) 80 | (assert-true (= 1 1)))))) 81 | 82 | (setv m.test-branching-expr-count-with-do 83 | (fn [] 84 | (setv counter 0) 85 | (if false 86 | (assert-true (= 2 1)) 87 | (do 88 | (setv counter (+ counter 1)) 89 | (setv counter (+ counter 1)) 90 | (setv counter (+ counter 1)))) 91 | (assert-true (= counter 3)))) 92 | 93 | (setv m.test-cond 94 | (fn [] 95 | "NATIVE: test if cond sorta works." 96 | (cond 97 | [(= 1 2) (assert-true (= true false))] 98 | [(= nil nil) (assert-true (= true true))]))) 99 | 100 | 101 | (setv m.test-index 102 | (fn [] 103 | "NATIVE: Test that dict access works" 104 | ;; in lua you cannot write something like {1, 2, 3}[1] 105 | (def t1 {"one" "two"}) 106 | (assert-equal (get t1 "one") "two") 107 | (assert-equal (get {"one" "two"} "one") "two") 108 | 109 | (def t2 [1 2 3 4 5]) 110 | (assert-equal (get t2 2) 2) 111 | (assert-equal (get [1 2 3 4 5] 2) 2) 112 | 113 | (def t3 {"first" {"second" {"third" "level"}}}) 114 | (assert-equal (get t3 "first" "second" "third") 115 | "level") 116 | (assert-equal (get {"first" {"second" {"third" "level"}}} 117 | "first" "second" "third") 118 | "level") 119 | 120 | (assert-true (= (get ((fn [] {"first" {"second" {"third" "level"}}})) 121 | "first" "second" "third") 122 | "level")) 123 | 124 | (def t4 {"first" {"second" {"third" "level"}}}) 125 | (assert-true (= (get t4 ((fn [] "first")) "second" "third") 126 | "level")) 127 | )) 128 | 129 | 130 | (setv m.test-lambda 131 | (fn [] 132 | "NATIVE: test lambda operator" 133 | (setv square (lambda [x] (* x x))) 134 | (assert-true (= 4 (square 2))) 135 | (setv lambda_list (lambda [test &rest args] [test args])) 136 | (assert-equal [1 [2 3]] (lambda_list 1 2 3)))) 137 | 138 | 139 | (return m) 140 | 141 | -------------------------------------------------------------------------------- /tests/native_tests/reader_macros.hua: -------------------------------------------------------------------------------- 1 | (require-macro hua.core.initialize) 2 | (--hua-initialize--) 3 | 4 | (hua-import luaunit) 5 | 6 | (def assert-equal luaunit.assertEquals) 7 | 8 | (require-macro "reader_macros") 9 | 10 | (def m {}) 11 | 12 | (setv m.test-reader-macros 13 | (fn [] 14 | (assert-equal #^"works" "works") 15 | (assert-equal #x10 16))) 16 | 17 | (return m) 18 | -------------------------------------------------------------------------------- /tests/native_tests/reader_macros.hy: -------------------------------------------------------------------------------- 1 | (defreader ^ [expr] 2 | expr) 3 | 4 | (defreader x [expr] 5 | (int (str expr) 16)) 6 | -------------------------------------------------------------------------------- /tests/native_tests/runtest.sh: -------------------------------------------------------------------------------- 1 | huac *.hua 2 | lua testall.lua -v 3 | -------------------------------------------------------------------------------- /tests/native_tests/testall.hua: -------------------------------------------------------------------------------- 1 | (require-macro hua.core.initialize) 2 | (--hua-initialize--) 3 | 4 | (hua-import luaunit) 5 | 6 | (setv TestCore (require "core")) 7 | (setv TestLanguage (require "language")) 8 | (setv TestReaderMacro (require "reader_macros")) 9 | (setv TestDefclass (require "defclass")) 10 | 11 | (os.exit (luaunit.LuaUnit.run)) 12 | --------------------------------------------------------------------------------