├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── Makefile ├── README.md ├── lua.hpp ├── lua.l ├── lua.txt ├── lua.y ├── lua2lisp.png ├── luademo.lisp ├── luademo.lua └── support.lisp /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store/ 2 | *.dSYM/ 3 | *.swp 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | Contributor Covenant Code of Conduct 2 | ==================================== 3 | 4 | Our Pledge 5 | ---------- 6 | 7 | In the interest of fostering an open and welcoming environment, we as 8 | contributors and maintainers pledge to making participation in our project and 9 | our community a harassment-free experience for everyone, regardless of age, 10 | body size, disability, ethnicity, gender identity and expression, level of 11 | experience, nationality, personal appearance, race, religion, or sexual 12 | identity and orientation. 13 | 14 | Our Standards 15 | ------------- 16 | 17 | Examples of behavior that contributes to creating a positive environment 18 | include: 19 | 20 | - Using welcoming and inclusive language 21 | - Being respectful of differing viewpoints and experiences 22 | - Gracefully accepting constructive criticism 23 | - Focusing on what is best for the community 24 | - Showing empathy towards other community members 25 | 26 | Examples of unacceptable behavior by participants include: 27 | 28 | - The use of sexualized language or imagery and unwelcome sexual attention or 29 | advances 30 | - Trolling, insulting/derogatory comments, and personal or political attacks 31 | - Public or private harassment 32 | - Publishing others' private information, such as a physical or electronic 33 | address, without explicit permission 34 | - Other conduct which could reasonably be considered inappropriate in a 35 | professional setting 36 | 37 | Our Responsibilities 38 | -------------------- 39 | 40 | Project maintainers are responsible for clarifying the standards of acceptable 41 | behavior and are expected to take appropriate and fair corrective action in 42 | response to any instances of unacceptable behavior. 43 | 44 | Project maintainers have the right and responsibility to remove, edit, or 45 | reject comments, commits, code, wiki edits, issues, and other contributions 46 | that are not aligned to this Code of Conduct, or to ban temporarily or 47 | permanently any contributor for other behaviors that they deem inappropriate, 48 | threatening, offensive, or harmful. 49 | 50 | Scope 51 | ----- 52 | 53 | This Code of Conduct applies both within project spaces and in public spaces 54 | when an individual is representing the project or its community. Examples of 55 | representing a project or community include using an official project e-mail 56 | address, posting via an official social media account, or acting as an 57 | appointed representative at an online or offline event. Representation of a 58 | project may be further defined and clarified by project maintainers. 59 | 60 | Enforcement 61 | ----------- 62 | 63 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 64 | reported by contacting the project team at . All 65 | complaints will be reviewed and investigated and will result in a response that 66 | is deemed necessary and appropriate to the circumstances. The project team is 67 | obligated to maintain confidentiality with regard to the reporter of an 68 | incident. Further details of specific enforcement policies may be posted 69 | separately. 70 | 71 | Project maintainers who do not follow or enforce the Code of Conduct in good 72 | faith may face temporary or permanent repercussions as determined by other 73 | members of the project's leadership. 74 | 75 | Attribution 76 | ----------- 77 | 78 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, 79 | available at . 80 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | How to contribute 2 | ================= 3 | 4 | Thank you for taking the time to contribute. Let's keep moving forward 5 | together to make ugrep the best Universal grep utility on the planet. 6 | 7 | The following is a set of guidelines for contributing. 8 | 9 | Contribution and participation in this project requires adherence to our 10 | [code of conduct](CODE_OF_CONDUCT.md). 11 | 12 | Did you find a bug? 13 | ------------------- 14 | 15 | - Ensure the bug was not already reported by checking reported issues. 16 | - If you're unable to find an open issue addressing the problem, open a new one. 17 | Be sure to include a title and clear description, as much relevant 18 | information as possible, and a code sample or an executable test case 19 | demonstrating the expected behavior that is not occurring. 20 | 21 | Do you have a patch that fixes a bug? 22 | ------------------------------------- 23 | 24 | - Open a new GitHub pull request with the patch. 25 | - Write a clear log message for your commits. One-line messages are fine for 26 | small changes, but bigger changes should look like this: 27 | 28 | $ git commit -m "A brief summary of the commit 29 | > 30 | > A paragraph describing what changed and its impact." 31 | 32 | Do you intend to add a new feature or change an existing one? 33 | ------------------------------------------------------------- 34 | 35 | Contributions are covered under the [BSD-3 license](LICENSE.txt). 36 | 37 | Thanks! 38 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, Robert van Engelen 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # required Bison 3.2 or greater and RE/flex https://github.com/Genivia/RE-flex 2 | 3 | CXX = c++ 4 | REFLEX = reflex 5 | REFLAGS = 6 | LIBREFLEX = -lreflex 7 | # if reflex is not installed: 8 | # LIBREFLEX = libreflex.a 9 | 10 | BISON = bison 11 | 12 | CXXOFLAGS = -O2 13 | CXXWFLAGS = -Wall -Wunused -Wextra 14 | CXXIFLAGS = 15 | CXXMFLAGS = 16 | CXXFLAGS = $(CXXWFLAGS) $(CXXOFLAGS) $(CXXIFLAGS) $(CXXMFLAGS) 17 | 18 | all: lua2lisp 19 | 20 | lua2lisp: lua.l lua.y lua.hpp 21 | $(BISON) -d lua.y 22 | $(REFLEX) $(REFLAGS) lua.l 23 | $(CXX) -std=c++11 $(CXXFLAGS) -Wno-potentially-evaluated-expression -o $@ LuaParser.cpp LuaScanner.cpp $(LIBREFLEX) 24 | 25 | .PHONY: clean 26 | 27 | clean: 28 | -rm -f LuaParser.cpp LuaParser.hpp LuaScanner.cpp LuaScanner.hpp lua2lisp 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lua to Lisp transpiler 2 | ====================== 3 | 4 | I spent a satisfying weekend writing a Lua-to-Lisp transpiler in [Bison](https://www.gnu.org/software/bison/) and [RE/flex](https://github.com/Genivia/RE-flex) a modern alternative to Flex for C++. The transpiler translates Lua language constructs to Lisp. Dare I say that Lua is essentially sugared Lisp? A [quote](http://paulgraham.com/rootsoflisp.html) by Paul Graham comes to mind *"As computers have grown more powerful, the new languages being developed have been moving steadily toward the Lisp model."* 5 | 6 | The project only uses two specialized `assign` and `index` special forms (macros) to satisfy Lua assignment and indexing semantics in Lisp. The rest of the output is constructed from plain and simple Lisp with `let`, `letrec`, `block`, `return-from`, `begin` (or `progn`), `cond`, `if`, `do`, `while`, `until`, `lambda`, `define`, `list`, `cons`, and arithmetic/relational operators. 7 | 8 | The first step was to locate the Lua 5.3 grammar, which can be found in the [Lua 5.3 reference manual](https://www.lua.org/manual/5.3/manual.html). There are also older [Lua grammars](http://lua-users.org/wiki/LuaGrammar) on the web. 9 | 10 | The [Bison LALR grammar for Lua 5.3](#lua-53-grammar-for-bison-32-or-greater) that I put together for this project comes from Lua's manual. It has four shift-reduce conflicts that are due to Lua's well-known ambiguity. This is not a problem at all, because Bison performs a shift by default. This shift corresponds to Lua's rule that an open parenthesis after an expression is part of the current expression or function call, even when the open parenthesis is placed on the next line. Semicolons are optional in Lua and can be used to enforce statement separation. 11 | 12 | All of the Lua 5.3 syntax and semantics are covered by the transpiler, except for gotos and low-level stuff such as metatables and integration with C. Perhaps I will add support for gotos later. 13 | 14 | Syntax error reporting uses fairly sophisticated features of RE/flex combined with Bison's C++ [complete symbols](https://www.gnu.org/software/bison/manual/html_node/Complete-Symbols.html)+[locations](https://www.gnu.org/software/bison/manual/html_node/Tracking-Locations.html) parsing documented in my [lua.l](lua.l) and [lua.y](lua.y) source code files. 15 | 16 | ## How to transpile Lua to Lisp 17 | 18 | The `lua2lisp` transpiler converts Lua to Lisp in two phases, a front-end phase and a back-end phase: 19 | 20 | 1. parse Lua code to build an abstract syntax tree using an [abstract grammar](#lua-abstract-grammar) of C++ classes I've written for this project 21 | 2. recursively invoke `transpile` member functions of the abstract syntax tree to generate Lisp 22 | 23 | Execution `lua2lisp` on a Lua source code file produces a Lisp file: 24 | 25 | $ ./lua2lisp luademo.lua 26 | Saved luademo.lisp 27 | 28 | ![lua2lisp](lua2lisp.png) 29 | 30 | The generated Lisp code is based on Common Lisp, but is not necessarily specific to Common Lisp. 31 | 32 | The `transpile` member functions perform the following translations. 33 | 34 | ### Constants 35 | 36 | Lua constants are translated to Lisp constants: 37 | 38 | nil => () 39 | true => #t 40 | false => #f 41 | => 42 | => 43 | => 44 | 45 | All values are truthy except `nil` and `false` that are falsy in Lua and in Lisp. 46 | 47 | Lua strings are translated to Lisp UTF-8 encoded strings by expanding the appropriate Lua escapes. 48 | 49 | ### Variables 50 | 51 | Lua variables are translated to Lisp with a `lua.` prefix to prevent name clashes with Lisp symbols. The elipsis is translated as is: 52 | 53 | => 54 | ... => ... 55 | 56 | Important: Lisp is assumed to produce `nil` for unassigned variables, like Lua produces `nil` for unassigned variables. Most Lisp don't do this. Either the Lisp interpreter should be adjusted or each Lua `` should be looked up (compile the transpiler source with `-DNAME_LOOKUP`): 57 | 58 | => (lookup ) 59 | 60 | where the `lookup` special form returns `nil` if `` is unassigned: 61 | 62 | (defmacro lookup (var) (list 'ignore-errors var)) 63 | 64 | ### Tables 65 | 66 | A Lua table is translated to a Lisp list of key-value pairs: 67 | 68 | { , = , [ ] = } 69 | => 70 | ( (1 . ) ('name . ) ( . ) ) 71 | 72 | ### Indexing 73 | 74 | Named tables (tables assigned to variables) are (recursively) indexed as follows: 75 | 76 | ..... 77 | => 78 | ((index ' ... (index 'name3 (index ' ))...)) 79 | 80 | [] 81 | => 82 | (index ) 83 | 84 | The Lisp `index` function is very similar to Lisp `assoc` in that it searches a table (a Lisp environment list) for a matching key to return the corresponding value or `nil` when not found. A symbol `'` should match a string "namek" in the table, because Lua `name.key` and `name["key"]` are identical. A possible implementation of `index` is: 85 | 86 | (defun index (field table) 87 | (when table 88 | (if (or (symbolp field) (stringp field)) 89 | (if (string= field (car (car table))) 90 | (cdr (car table)) 91 | (index field (cdr table))) 92 | (if (equal field (car (car table))) 93 | (cdr (car table)) 94 | (index field (cdr table)))))) 95 | 96 | However, some Lisp force upper case symbols. Hence `'a` and `"a"` are incomparable. To avoid this, the transpiler could output `""` instead of `'` to construct and search tables. 97 | 98 | ### function 99 | 100 | All Lua functions and methods are translated to Lisp lambdas with an extra first `yield` parameter. The `yield` parameter is used with iterators. When the function or method is called as an iterator, `yield` is a closure with the `for` iterator loop body. The closure is called by the function or method with the return value(s) of this function or method. That is, instead of the function or method returning to the caller, it passes the return value(s) to the `yield` closure. Otherwise, when the function or method is not called as an iterator, `yield` is `nil`. As a consequence, the translated Lisp code for a Lua `return ` is a bit more complicated to test for `yield` and to locally save the return value(s) in `ret`: 101 | 102 | (let ((ret )) 103 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret))))) 104 | 105 | When a Lua method is defined with a colon name, an extra second `lua.self` parameter is inserted with the object passed to the method. 106 | 107 | A `return` may appear anywhere in a function or a method to return the specified value(s). If no value is specified, `nil` is returned. 108 | 109 | Anonymous function (lambda): 110 | 111 | function () return end 112 | => 113 | (lambda (yield ) (block @func@ 114 | 115 | (let ((ret )) 116 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret))))) 117 | 118 | Function and method parameters with elipsis: 119 | 120 | function ( ...) return end 121 | => 122 | (lambda (yield . ...) (block @func@ 123 | 124 | (let ((ret )) 125 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret))))) 126 | 127 | Function definition: 128 | 129 | function () return end 130 | => 131 | (define (lambda (yield ) (block @func@ 132 | 133 | (let ((ret )) 134 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret)))))) 135 | 136 | Method definition: 137 | 138 | function () return end 139 | => 140 | (assign ((index 'namek ... (index 'name2 lua.name1)...)) ((lambda (yield ) (block @func@ 141 | 142 | (let ((ret )) 143 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret)))))) 144 | 145 | Method definition with a colon name: 146 | 147 | function () return end 148 | => 149 | (assign ((index 'namek ... (index 'name2 lua.name1)...)) ((lambda (yield lua.self ) (block @func@ 150 | 151 | (let ((ret )) 152 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret)))))) 153 | 154 | Function returns are `nil` when no value is specified, one value or a list of multiple values. 155 | 156 | ### local 157 | 158 | Lua local variables and functions are translated to Lisp `let` forms: 159 | 160 | local name1,name2,... = expr1,expr2,... 161 | => 162 | (let ((name1 expr1) (name2 expr2) ...) 163 | 164 | An important point is that a `let` is not closed until the end of the block in which the `local` are declared. 165 | 166 | A local function: 167 | 168 | local function () return end 169 | => 170 | (letrec (( (lambda (yield ) (block @func@ 171 | 172 | (let ((ret )) 173 | (if yield (yield ret) (return-from @func@ ret))))))) 174 | 175 | The `letrec` is not closed until the end of the block in which the `local function` is declared. 176 | 177 | ### do 178 | 179 | A Lua `do` block is translated to a Lisp `begin` (`progn` in Common Lisp) special form: 180 | 181 | do end 182 | => 183 | (begin ) 184 | 185 | ### if 186 | 187 | A Lua `if` with zero of more `elseif` branches is translated to a Lisp `cond` special form: 188 | 189 | if then elseif then ... else end 190 | => 191 | (cond 192 | ( ) 193 | ( ) 194 | ... 195 | (#t )) 196 | 197 | ### while and until 198 | 199 | A `while` and `until` loop is translated to a Lisp `while` and `until` loop special form (or macro), respectively: 200 | 201 | while do break end 202 | => 203 | (block @loop@ (while (return-from @loop@))) 204 | 205 | repeat until 206 | => 207 | (block @loop@ (until (return-from @loop@))) 208 | 209 | A `break` may appear anywhere in a loop to terminate the loop. A `break` is translated to a `return-from @loop@`: 210 | 211 | break 212 | => 213 | (return-from @loop@) 214 | 215 | Some Lisp have no `while`, e.g. Common Lisp. In that case let's define them as macros that expand into `loop-while-do` and `loop-until-do`: 216 | 217 | (defmacro while (x . body) 218 | (list 'loop 'while x 'do (cons 'progn body))) 219 | 220 | (defmacro until (x . body) 221 | (list 'loop 'until x 'do (cons 'progn body))) 222 | 223 | ### for counter loop 224 | 225 | A `for` counter loop is translated to a Lisp `do` loop special form. 226 | 227 | for = ,, do break end 228 | => 229 | (block @loop@ (do (( (+ )) ((> )) 230 | 231 | (return-from @loop@)))) 232 | 233 | The `` value is optional and defaults to one. 234 | 235 | A `break` may appear anywhere in a loop to terminate the loop. 236 | 237 | ### for iterators loop 238 | 239 | A `for` iterators loop is translated to a Lisp `let` to define a `yield` closure that is passed to the iterator functions: 240 | 241 | for ,,..., in ,,..., do end 242 | => 243 | (let ((yield (lambda (ret) (let (( (nth 1 ret)) ( (nth 2 ret)) ... ( (nth k ret))) 244 | )))) 245 | ( yield) 246 | ( yield) 247 | ... 248 | ( yield)) 249 | 250 | The `iterators` are (anonymous) functions that invoke the `yield` closure typically repeatedly with a Lua `return` value(s) until the value is `nil`. The Lua `return` is a Lisp `yield` call if a `yield` closure is specified and `ret` is not `nil`, otherwise it is a `return-from @func@`. See [function](#function). 251 | 252 | Unlike other loops, `break` in `for` iterator loops is not supported at this time. To support a `break` in a `for` iterator loop, the Lua `break` should be translated to throw an exception to exit the iterator function and terminate the loop. This adds Lisp code that clutters the logic, so I left it out for now. 253 | 254 | If only one `` is specified with a `for` iterator loop, then the generated code is simpler: 255 | 256 | for in ,,..., do end 257 | => 258 | (let ((yield (lambda () 259 | ))) 260 | ( yield) 261 | ( yield) 262 | ... 263 | ( yield)) 264 | 265 | If only one `` is specified and only one iterator, then the generated code is even simpler: 266 | 267 | for in do end 268 | => 269 | ( (lambda () 270 | )) 271 | 272 | ### Assignment 273 | 274 | A Lua assignment is a multi-way assignment translated to a Lisp `assign` special form (macro) that implements Lua's assignment semantics: 275 | 276 | ,,..., = ,,..., 277 | => 278 | (assign ( ... ) ( ... )) 279 | 280 | The `` left-hand sides can be expressions such as table indices: 281 | 282 | foo.bar[3] = 7 283 | => 284 | (assign ((index 3 (index 'bar lua.foo))) (7)) 285 | 286 | The `assign` special form does not evaluate its first argument, which is a list of `` expressions to assign. It first evaluates the second argument, which is a list of `` values to assign. Then the list of `` expressions is traversed to assign the named variables and table indices. Lua requires that the index argument of the `index` function is evaluated first for all `index` expressions in `` to determine the table index. For example: 287 | 288 | i = 3 289 | i, a[i] = i+1, 20 290 | 291 | sets `a[3]` to 20. In the Lisp translation the `i` in `(index i a)` is evaluated first before `i` is assigned. This requires two passes over ``. 292 | 293 | The `assign` special form also accounts for cases when we have fewer `` than `` and when a function returns more than one value (in a list). 294 | 295 | ### Goto 296 | 297 | Goto is not supported at this time, "left as an exercise for the reader." It is theoretically possible, but it significantly clutters the logic. 298 | 299 | ## Lua 5.3 grammar for Bison 3.2 or greater 300 | 301 | The Lua 5.3 grammar for Bison 3.2 with reduce/reduce conflict eliminated by expanding `prefixexp`: 302 | 303 | %token 304 | AND "and" 305 | BREAK "break" 306 | DO "do" 307 | ELSE "else" 308 | ELSEIF "elseif" 309 | END "end" 310 | FALSE "false" 311 | FOR "for" 312 | FUNCTION "function" 313 | IF "if" 314 | IN "in" 315 | LOCAL "local" 316 | NIL "nil" 317 | NOT "not" 318 | OR "or" 319 | REPEAT "repeat" 320 | RETURN "return" 321 | THEN "then" 322 | TRUE "true" 323 | UNTIL "until" 324 | WHILE "while" 325 | EQU "==" 326 | NEQ "~=" 327 | LTE "<=" 328 | GTE ">=" 329 | CAT ".." 330 | SHL "<<" 331 | SHR ">>" 332 | DIV "//" 333 | DOTS "..." 334 | ; 335 | 336 | %token '#' '%' '&' '(' ')' '*' '+' ',' '-' '.' '/' ':' ';' '<' '=' '>' '[' ']' '^' '{' '|' '}' '~' 337 | 338 | %left OR 339 | %left AND 340 | %left EQU NEQ LTE '<' GTE '>' 341 | %right CAT 342 | %left '|' 343 | %left '~' 344 | %left '&' 345 | %left SHL SHR 346 | %left '+' '-' 347 | %left '*' '/' '%' DIV 348 | %right NOT '#' 349 | %right '^' 350 | 351 | // we expect four shift-reduce conflicts due to Lua's optional semicolon, must always shift on '(' 352 | %expect 4 353 | 354 | %% 355 | 356 | chunk : block 357 | 358 | semi : ';' 359 | | 360 | 361 | block : scope statlist 362 | | scope statlist laststat semi 363 | 364 | ublock : block UNTIL exp 365 | 366 | scope : 367 | | scope statlist binding semi 368 | 369 | statlist : 370 | | statlist stat semi 371 | 372 | stat : DO block END 373 | | WHILE exp DO block END 374 | | repetition DO block END 375 | | REPEAT ublock 376 | | IF conds END 377 | | FUNCTION funcname funcbody 378 | | setlist '=' explist1 379 | | funccall 380 | 381 | repetition : FOR NAME '=' explist23 382 | | FOR namelist IN explist1 383 | 384 | conds : condlist 385 | | condlist ELSE block 386 | 387 | condlist : cond 388 | | condlist ELSEIF cond 389 | 390 | cond : exp THEN block 391 | 392 | laststat : BREAK 393 | | RETURN 394 | | RETURN explist1 395 | 396 | binding : LOCAL namelist 397 | | LOCAL namelist '=' explist1 398 | | LOCAL FUNCTION NAME funcbody 399 | 400 | funcname : dottedname 401 | | dottedname ':' NAME 402 | 403 | dottedname : NAME 404 | | dottedname '.' NAME 405 | 406 | namelist : NAME 407 | | namelist ',' NAME 408 | 409 | explist1 : exp 410 | | explist1 ',' exp 411 | 412 | explist23 : exp ',' exp 413 | | exp ',' exp ',' exp 414 | 415 | exp : NIL 416 | | TRUE 417 | | FALSE 418 | | NUMBER 419 | | STRING 420 | | DOTS 421 | | function 422 | | var 423 | | funccall 424 | | tableconstr 425 | | NOT exp 426 | | '#' exp 427 | | '-' exp %prec NOT 428 | | '~' exp %prec NOT 429 | | exp OR exp 430 | | exp AND exp 431 | | exp '<' exp 432 | | exp LTE exp 433 | | exp '>' exp 434 | | exp GTE exp 435 | | exp EQU exp 436 | | exp NEQ exp 437 | | exp '|' exp 438 | | exp '~' exp 439 | | exp '&' exp 440 | | exp SHL exp 441 | | exp SHR exp 442 | | exp CAT exp 443 | | exp '+' exp 444 | | exp '-' exp 445 | | exp '*' exp 446 | | exp '/' exp 447 | | exp DIV exp 448 | | exp '%' exp 449 | | exp '^' exp 450 | | '(' exp ')' 451 | 452 | setlist : var 453 | | setlist ',' var 454 | 455 | var : NAME 456 | | var '[' exp ']' 457 | | var '.' NAME 458 | | funccall '[' exp ']' 459 | | funccall '.' NAME 460 | | '(' exp ')' '[' exp ']' 461 | | '(' exp ')' '.' NAME 462 | 463 | funccall : var args 464 | | var ':' NAME args 465 | | funccall args 466 | | funccall ':' NAME args 467 | | '(' exp ')' args 468 | | '(' exp ')' ':' NAME args 469 | 470 | args : '(' ')' 471 | | '(' explist1 ')' 472 | | tableconstr 473 | | STRING 474 | 475 | function : FUNCTION funcbody 476 | 477 | funcbody : params block END 478 | 479 | params : '(' parlist ')' 480 | 481 | parlist : 482 | | namelist 483 | | DOTS 484 | | namelist ',' DOTS 485 | 486 | tableconstr : '{' '}' 487 | | '{' fieldlist '}' 488 | | '{' fieldlist ',' '}' 489 | | '{' fieldlist ';' '}' 490 | 491 | fieldlist : field 492 | | fieldlist ',' field 493 | | fieldlist ';' field 494 | 495 | field : exp 496 | | NAME '=' exp 497 | | '[' exp ']' '=' exp 498 | 499 | %% 500 | 501 | ## Lua abstract grammar 502 | 503 | The C++ abstract syntax tree is composed of the following class instances defined by the `Transpiler` class: 504 | 505 | | class | inherits | contains 506 | | -------------------- | -------------------- | -------- 507 | | `Name` | | pointer to `std::string` in the tokenizer's symbol table 508 | | `NameList` | | `std::list` 509 | | `AbstractSyntaxTree` | | 510 | | `Expression` | `AbstractSyntaxTree` | 511 | | `Statement` | `AbstractSyntaxTree` | 512 | | `Nil` | `Expression` | 513 | | `True` | `Expression` | 514 | | `False` | `Expression` | 515 | | `Integer` | `Expression` | 64 bit integer literal value 516 | | `Float` | `Expression` | 64 bit float literal value 517 | | `String` | `Expression` | string literal value 518 | | `Dots` | `Expression` | 519 | | `Lambda` | `Expression` | `Parameters parameters`, `List block` 520 | | `Table` | `Expression` | `List fields` 521 | | `Call` | `Expression` | `Expression function`, `List arguments`, `Name colonname` 522 | | `UnaryOp` | `Expression` | `const char *op`, `Expression operand` 523 | | `Op` | `Expression` | `const char *op`, `Expression operand1,operand2` 524 | | `Variable` | `Expression` | `Name name` 525 | | `Index` | `Expression` | `Expression var,index` 526 | | `Member` | `Expression` | `Expression var`, `Name member` 527 | | `Block` | `Statement` | `List block` 528 | | `If` | `Statement` | `List conditions` 529 | | `While` | `Statement` | `Expression condition`, `List block` 530 | | `Until` | `Statement` | `List block`, `Expression> condition` 531 | | `ForCounter` | `Statement` | `Range range`, `List block` 532 | | `ForIterator` | `Statement` | `NameList namelist`, `List iterators`, `List block` 533 | | `Function` | `Statement` | `FunctionName functionname`, `Lambda lambda` 534 | | `Goto` | `Statement` | `Name label` 535 | | `Label` | `Statement` | `Name label` 536 | | `Assign` | `Statement` | `List lhs,rhs` 537 | | `FunctionCall` | `Statement` | `Call call` 538 | | `Local` | `Statement` | `NameList namelist`, `List init` 539 | | `LocalFunction` | `Statement` | `Name name`, `Lambda lambda` 540 | | `Break` | `Statement` | 541 | | `Return` | `Statement` | `List list` 542 | | `Condition` | `AbstractSyntaxTree` | `Expression test`, `List block` 543 | | `FunctionName` | `AbstractSyntaxTree` | `NameList namelist`, `bool self` 544 | | `Parameters` | `AbstractSyntaxTree` | `NameList namelist`, `bool dots` 545 | | `Range` | `AbstractSyntaxTree` | `Expression start,end,step` 546 | | `Field` | `AbstractSyntaxTree` | `Expression key,value` 547 | 548 | The `Expression` and `Statement` base classes and derivatives include `transpile` functions to translate Lua to Lisp. 549 | -------------------------------------------------------------------------------- /lua.hpp: -------------------------------------------------------------------------------- 1 | // Lua 5.3 Bison parser and transpiler by Robert van Engelen 2 | 3 | #ifndef LUA_HPP 4 | #define LUA_HPP 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define LUA_NAME_FORMAT "lua.%s" 19 | #define LUA_OPERATOR_FORMAT "%s" 20 | 21 | // identifiers are efficiently compared for equality by their pointer to a string stored in the Scanner::symbols set 22 | typedef const std::string *Name; 23 | // scanner returns unsigned 64 bit integer literals 24 | typedef uint64_t IntegerLiteral; 25 | // scanner returns double precision literals 26 | typedef double FloatLiteral; 27 | // strings are stored in the Scanner::symbols set 28 | typedef const char *StringLiteral; 29 | 30 | // transpiler 31 | class Transpiler { 32 | public: 33 | 34 | // a list of expressions, statements etc. 35 | template struct List : public std::list> { 36 | List() = default; 37 | 38 | List(T *x) { 39 | this->add(x); 40 | } 41 | 42 | List(List *x) { 43 | this->concat(x); 44 | } 45 | 46 | virtual ~List() = default; 47 | 48 | List *add(T *x) 49 | { 50 | this->emplace_back(x); 51 | return this; 52 | } 53 | 54 | List *concat(List *x) 55 | { 56 | this->splice(this->end(), *x, x->begin(), x->end()); 57 | delete x; 58 | return this; 59 | } 60 | }; 61 | 62 | // a list of names, each Name is a pointer to a string stored in the symbol table 63 | struct NameList : public std::list { 64 | NameList() = default; 65 | 66 | NameList(Name x) { 67 | this->add(x); 68 | } 69 | 70 | virtual ~NameList() = default; 71 | 72 | NameList *add(Name x) 73 | { 74 | this->emplace_back(x); 75 | return this; 76 | } 77 | }; 78 | 79 | // abstract syntax tree 80 | struct AbstractSyntaxTree { 81 | virtual ~AbstractSyntaxTree() = default; 82 | virtual void transpile(Transpiler& transpiler) { fprintf(transpiler.out, "???"); } 83 | }; 84 | 85 | // abstract expression 86 | struct Expression : public AbstractSyntaxTree { 87 | // virtual ~Expression() = default; 88 | virtual void transpile(Transpiler& transpiler) { fprintf(transpiler.out, "???expression???"); } 89 | }; 90 | 91 | // abstract statement 92 | struct Statement : public AbstractSyntaxTree { 93 | // virtual ~Statement() = default; 94 | virtual void transpile(Transpiler& transpiler) { fprintf(transpiler.out, "???statement???"); } 95 | }; 96 | 97 | // nil expression 98 | struct Nil : public Expression { 99 | virtual void transpile(Transpiler& transpiler) { fprintf(transpiler.out, "()"); } 100 | }; 101 | 102 | // true expression 103 | struct True : public Expression { 104 | virtual void transpile(Transpiler& transpiler) { fprintf(transpiler.out, "#t"); } 105 | }; 106 | 107 | // false expression 108 | struct False : public Expression { 109 | virtual void transpile(Transpiler& transpiler) { fprintf(transpiler.out, "#f"); } 110 | }; 111 | 112 | // integer literal expression 113 | struct Integer : public Expression { 114 | Integer(IntegerLiteral n) : literal(n) { } 115 | virtual void transpile(Transpiler& transpiler) { fprintf(transpiler.out, "%llu", static_cast(literal)); } 116 | IntegerLiteral literal; 117 | }; 118 | 119 | // floating point literal expression 120 | struct Float : public Expression { 121 | Float(FloatLiteral n) : literal(n) { } 122 | virtual void transpile(Transpiler& transpiler) { fprintf(transpiler.out, "%.16lg", literal); } 123 | FloatLiteral literal; 124 | }; 125 | 126 | // string literal expression 127 | struct String : public Expression { 128 | String(StringLiteral s) : literal(s) { } 129 | virtual void transpile(Transpiler& transpiler) 130 | { 131 | putc('"', transpiler.out); 132 | for (const char *s = literal; *s; ++s) 133 | { 134 | if (*s == '\\' || *s == '"') 135 | fprintf(transpiler.out, "\\%c", *s); 136 | else if (isprint(*s) || static_cast(*s) >= 128) 137 | putc(*s, transpiler.out); 138 | else 139 | fprintf(transpiler.out, "\\x%02x", *s); 140 | } 141 | putc('"', transpiler.out); 142 | } 143 | StringLiteral literal; 144 | }; 145 | 146 | // ... expression 147 | struct Dots : public Expression { 148 | virtual void transpile(Transpiler& transpiler) 149 | { 150 | fprintf(transpiler.out, "..."); 151 | } 152 | }; 153 | 154 | // anonymous function (lambda) expression 155 | struct Parameters; 156 | struct Lambda : public Expression { 157 | Lambda(Parameters *l, List *b) : parameters(l), block(b) { } 158 | virtual void transpile(Transpiler& transpiler) 159 | { 160 | transpile(transpiler, false); 161 | } 162 | void transpile(Transpiler& transpiler, bool self) 163 | { 164 | transpiler.begin_local_scope(); 165 | fprintf(transpiler.out, "(lambda"); 166 | parameters->transpile(transpiler, self); 167 | fprintf(transpiler.out, " (block @func@"); 168 | for (auto& statement : *block) 169 | { 170 | transpiler.newline(); 171 | statement->transpile(transpiler); 172 | } 173 | fprintf(transpiler.out, "))"); 174 | transpiler.end_local_scope(); 175 | } 176 | std::unique_ptr parameters; 177 | std::unique_ptr> block; 178 | }; 179 | 180 | // table constructor expression 181 | struct Field; 182 | struct Table : public Expression { 183 | Table(List *f) : fields(f) { } 184 | virtual void transpile(Transpiler& transpiler) 185 | { 186 | if (fields->empty()) 187 | { 188 | fprintf(transpiler.out, "()"); 189 | } 190 | else 191 | { 192 | fprintf(transpiler.out, "(list"); 193 | size_t index = 0; 194 | for (auto& field : *fields) 195 | { 196 | fprintf(transpiler.out, " (cons "); 197 | if (field->key) 198 | { 199 | if (typeid(*field->key) == typeid(Variable)) 200 | fprintf(transpiler.out, "'%s", dynamic_cast(field->key.get())->name->c_str()); 201 | else 202 | field->key->transpile(transpiler); 203 | } 204 | else 205 | { 206 | fprintf(transpiler.out, "%zu", ++index); 207 | } 208 | putc(' ', transpiler.out); 209 | field->value->transpile(transpiler); 210 | putc(')', transpiler.out); 211 | } 212 | putc(')', transpiler.out); 213 | } 214 | } 215 | std::unique_ptr> fields; 216 | }; 217 | 218 | // function call expression 219 | struct Call : public Expression { 220 | Call(Expression *f, List *a, Name v) : function(f), arguments(a), colonname(v) { } 221 | virtual void transpile(Transpiler& transpiler) 222 | { 223 | if (colonname) 224 | { 225 | fprintf(transpiler.out, "(let ((self "); 226 | function->transpile(transpiler); 227 | fprintf(transpiler.out, ")) ((index '%s self) () self", colonname->c_str()); 228 | for (auto& argument : *arguments) 229 | { 230 | putc(' ', transpiler.out); 231 | argument->transpile(transpiler); 232 | } 233 | fprintf(transpiler.out, "))"); 234 | } 235 | else 236 | { 237 | putc('(', transpiler.out); 238 | function->transpile(transpiler); 239 | fprintf(transpiler.out, " ()"); 240 | for (auto& argument : *arguments) 241 | { 242 | putc(' ', transpiler.out); 243 | argument->transpile(transpiler); 244 | } 245 | putc(')', transpiler.out); 246 | } 247 | } 248 | std::unique_ptr function; 249 | std::unique_ptr> arguments; 250 | Name colonname; 251 | }; 252 | 253 | // unary operator expression 254 | struct UnaryOp : public Expression { 255 | UnaryOp(const char *s, Expression *e) : op(s), operand(e) { } 256 | virtual void transpile(Transpiler& transpiler) 257 | { 258 | fprintf(transpiler.out, "(" LUA_OPERATOR_FORMAT " ", op); 259 | operand->transpile(transpiler); 260 | putc(')', transpiler.out); 261 | } 262 | const char *op; 263 | std::unique_ptr operand; 264 | }; 265 | 266 | // binary operator expression 267 | struct Op : public Expression { 268 | Op(const char *s, Expression *e1, Expression *e2) : op(s), operand1(e1), operand2(e2) { } 269 | virtual void transpile(Transpiler& transpiler) 270 | { 271 | transpile(transpiler, false); 272 | } 273 | void transpile(Transpiler& transpiler, bool nested) 274 | { 275 | if (!nested) 276 | fprintf(transpiler.out, "(" LUA_OPERATOR_FORMAT " ", op); 277 | if (typeid(*operand1) == typeid(Op) && strcmp(dynamic_cast(operand1.get())->op, op) == 0) 278 | dynamic_cast(operand1.get())->transpile(transpiler, true); 279 | else 280 | operand1->transpile(transpiler); 281 | putc(' ', transpiler.out); 282 | if (typeid(*operand2) == typeid(Op) && strcmp(dynamic_cast(operand2.get())->op, op) == 0) 283 | dynamic_cast(operand2.get())->transpile(transpiler, true); 284 | else 285 | operand2->transpile(transpiler); 286 | if (!nested) 287 | putc(')', transpiler.out); 288 | } 289 | const char *op; 290 | std::unique_ptr operand1, operand2; 291 | }; 292 | 293 | // variable wraps a name as expression 294 | struct Variable : public Expression { 295 | Variable(Name v) : name(v) { } 296 | virtual void transpile(Transpiler& transpiler) 297 | { 298 | #ifdef NAME_LOOKUP 299 | // if unassigned/undefined Lisp variables cause errors: use a Lisp lookup special form 300 | fprintf(transpiler.out, "(lookup " LUA_NAME_FORMAT ")", name->c_str()); 301 | #else 302 | fprintf(transpiler.out, LUA_NAME_FORMAT, name->c_str()); 303 | #endif 304 | } 305 | Name name; 306 | }; 307 | 308 | // var[] index accessor expression 309 | struct Index : public Expression { 310 | Index(Expression *v, Expression *e) : var(v), index(e) { } 311 | virtual void transpile(Transpiler& transpiler) 312 | { 313 | fprintf(transpiler.out, "(index "); 314 | index->transpile(transpiler); 315 | putc(' ', transpiler.out); 316 | var->transpile(transpiler); 317 | putc(')', transpiler.out); 318 | } 319 | std::unique_ptr var, index; 320 | }; 321 | 322 | // var.member accessor expression 323 | struct Member : public Expression { 324 | Member(Expression *v, Name m) : var(v), member(m) { } 325 | virtual void transpile(Transpiler& transpiler) 326 | { 327 | fprintf(transpiler.out, "(index '%s ", member->c_str()); 328 | var->transpile(transpiler); 329 | putc(')', transpiler.out); 330 | } 331 | std::unique_ptr var; 332 | Name member; 333 | }; 334 | 335 | // do end block statement 336 | struct Block : public Statement { 337 | Block() { } 338 | Block(List *b) : block(b) { } 339 | virtual void transpile(Transpiler& transpiler) 340 | { 341 | transpiler.begin_local_scope(); 342 | fprintf(transpiler.out, "(begin"); 343 | for (auto& statement : *block) 344 | { 345 | transpiler.newline(); 346 | statement->transpile(transpiler); 347 | } 348 | putc(')', transpiler.out); 349 | transpiler.end_local_scope(); 350 | } 351 | std::unique_ptr> block; 352 | }; 353 | 354 | // if statement 355 | struct Condition; 356 | struct If : public Statement { 357 | If(List *c) : conditions(c) { } 358 | virtual void transpile(Transpiler& transpiler) 359 | { 360 | fprintf(transpiler.out, "(cond"); 361 | for (auto& condition : *conditions) 362 | condition->transpile(transpiler); 363 | putc(')', transpiler.out); 364 | } 365 | std::unique_ptr> conditions; 366 | }; 367 | 368 | // while do statement 369 | struct While : public Statement { 370 | While(Expression *c, List *b) : condition(c), block(b) { } 371 | virtual void transpile(Transpiler& transpiler) 372 | { 373 | transpiler.begin_local_scope(); 374 | fprintf(transpiler.out, "(block @loop@ (while "); 375 | condition->transpile(transpiler); 376 | for (auto& statement : *block) 377 | { 378 | transpiler.newline(); 379 | statement->transpile(transpiler); 380 | } 381 | fprintf(transpiler.out, "))"); 382 | transpiler.end_local_scope(); 383 | } 384 | std::unique_ptr condition; 385 | std::unique_ptr> block; 386 | }; 387 | 388 | // repeat until statement 389 | struct Until : public Statement { 390 | Until(List *b, Expression *c) : block(b), condition(c) { } 391 | virtual void transpile(Transpiler& transpiler) 392 | { 393 | transpiler.begin_local_scope(); 394 | fprintf(transpiler.out, "(block @loop@ (until "); 395 | condition->transpile(transpiler); 396 | for (auto& statement : *block) 397 | { 398 | transpiler.newline(); 399 | statement->transpile(transpiler); 400 | } 401 | fprintf(transpiler.out, "))"); 402 | transpiler.end_local_scope(); 403 | } 404 | std::unique_ptr> block; 405 | std::unique_ptr condition; 406 | }; 407 | 408 | // for = do statement 409 | struct Range; 410 | struct ForCounter : public Statement { 411 | ForCounter(Name v, Range *r, List *b) : name(v), range(r), block(b) { } 412 | virtual void transpile(Transpiler& transpiler) 413 | { 414 | transpiler.begin_local_scope(); 415 | fprintf(transpiler.out, "(block @loop@ (do ((" LUA_NAME_FORMAT " ", name->c_str()); 416 | range->start->transpile(transpiler); 417 | fprintf(transpiler.out, " (+ " LUA_NAME_FORMAT " ", name->c_str()); 418 | if (range->step) 419 | range->step->transpile(transpiler); 420 | else 421 | putc('1', transpiler.out); 422 | fprintf(transpiler.out, ")) ((> " LUA_NAME_FORMAT " ", name->c_str()); 423 | range->end->transpile(transpiler); 424 | fprintf(transpiler.out, "))"); 425 | for (auto& statement : *block) 426 | { 427 | transpiler.newline(); 428 | statement->transpile(transpiler); 429 | } 430 | fprintf(transpiler.out, ")))"); 431 | transpiler.end_local_scope(); 432 | } 433 | Name name; 434 | std::unique_ptr range; 435 | std::unique_ptr> block; 436 | }; 437 | 438 | // for in do statement 439 | struct ForIterator : public Statement { 440 | ForIterator(NameList *v, List *e, List *b) : namelist(v), iterators(e), block(b) { } 441 | virtual void transpile(Transpiler& transpiler) 442 | { 443 | if (iterators->size() == 1) 444 | { 445 | fprintf(transpiler.out, "("); 446 | iterators->front()->transpile(transpiler); 447 | fprintf(transpiler.out, " "); 448 | } 449 | else 450 | { 451 | fprintf(transpiler.out, "(let ((yield "); 452 | } 453 | if (namelist->size() == 1) 454 | { 455 | fprintf(transpiler.out, "(lambda (" LUA_NAME_FORMAT ")", namelist->front()->c_str()); 456 | } 457 | else 458 | { 459 | int i = 1, c = '('; 460 | fprintf(transpiler.out, "(lambda (ret) (let "); 461 | for (auto& name : *namelist) 462 | { 463 | fprintf(transpiler.out, "%c(" LUA_NAME_FORMAT " (nth %d ret))", c, name->c_str(), i); 464 | ++i; 465 | c = ' '; 466 | } 467 | fprintf(transpiler.out, ")"); 468 | } 469 | transpiler.begin_local_scope(); 470 | for (auto& statement : *block) 471 | { 472 | transpiler.newline(); 473 | statement->transpile(transpiler); 474 | } 475 | transpiler.end_local_scope(); 476 | if (namelist->size() != 1) 477 | fprintf(transpiler.out, ")"); 478 | fprintf(transpiler.out, "))"); 479 | if (iterators->size() != 1) 480 | { 481 | fprintf(transpiler.out, ")"); 482 | for (auto& iterator : *iterators) 483 | { 484 | transpiler.newline(); 485 | fprintf(transpiler.out, "("); 486 | iterator->transpile(transpiler); 487 | fprintf(transpiler.out, " yield)"); 488 | } 489 | fprintf(transpiler.out, ")"); 490 | } 491 | } 492 | std::unique_ptr namelist; 493 | std::unique_ptr> iterators; 494 | std::unique_ptr> block; 495 | }; 496 | 497 | // function statement 498 | struct FunctionName; 499 | struct Function : public Statement { 500 | Function(FunctionName *f, Lambda *l) : functionname(f), lambda(l) { } 501 | virtual void transpile(Transpiler& transpiler) 502 | { 503 | if (functionname->namelist->size() == 1) 504 | { 505 | fprintf(transpiler.out, "(define " LUA_NAME_FORMAT " ", functionname->namelist->front()->c_str()); 506 | lambda->transpile(transpiler); 507 | putc(')', transpiler.out); 508 | } 509 | else 510 | { 511 | fprintf(transpiler.out, "(assign ("); 512 | NameList::reverse_iterator iter = functionname->namelist->rbegin(); 513 | while (iter != functionname->namelist->rend()) 514 | { 515 | Name name = *iter++; 516 | if (iter == functionname->namelist->rend()) 517 | fprintf(transpiler.out, LUA_NAME_FORMAT, name->c_str()); 518 | else 519 | fprintf(transpiler.out, "(index '%s ", name->c_str()); 520 | } 521 | for (size_t i = 0; i < functionname->namelist->size(); ++i) 522 | putc(')', transpiler.out); 523 | fprintf(transpiler.out, " ("); 524 | lambda->transpile(transpiler, functionname->self); 525 | fprintf(transpiler.out, "))"); 526 | } 527 | } 528 | std::unique_ptr functionname; 529 | std::unique_ptr lambda; 530 | }; 531 | 532 | // goto statement 533 | struct Goto : public Statement { 534 | Goto(Name v) : label(v) { } 535 | Name label; 536 | }; 537 | 538 | // :: label :: statement 539 | struct Label : public Statement { 540 | Label(Name v) : label(v) { } 541 | Name label; 542 | }; 543 | 544 | // multi-way assignment statement 545 | struct Assign : public Statement { 546 | Assign(List *l, List *r) : lhs(l), rhs(r) { } 547 | virtual void transpile(Transpiler& transpiler) 548 | { 549 | fprintf(transpiler.out, "(assign "); 550 | int c = '('; 551 | for (auto& expression : *lhs) 552 | { 553 | putc(c, transpiler.out); 554 | expression->transpile(transpiler); 555 | c = ' '; 556 | } 557 | fprintf(transpiler.out, ") "); 558 | c = '('; 559 | for (auto& expression : *rhs) 560 | { 561 | putc(c, transpiler.out); 562 | expression->transpile(transpiler); 563 | c = ' '; 564 | } 565 | fprintf(transpiler.out, "))"); 566 | } 567 | std::unique_ptr> lhs, rhs; 568 | }; 569 | 570 | // function call statement, wraps a function call expression 571 | struct FunctionCall : public Statement { 572 | FunctionCall(Call *c) : call(c) { } 573 | virtual void transpile(Transpiler& transpiler) 574 | { 575 | call->transpile(transpiler); 576 | } 577 | std::unique_ptr call; 578 | }; 579 | 580 | // local binding statement 581 | struct Local : public Statement { 582 | Local(NameList *v, List *e) : namelist(v), init(e) { } 583 | virtual void transpile(Transpiler& transpiler) 584 | { 585 | fprintf(transpiler.out, "(let "); 586 | if (namelist->size() == init->size() || init->empty()) 587 | { 588 | putc('(', transpiler.out); 589 | transpiler.indent++; 590 | NameList::iterator i = namelist->begin(); 591 | List::iterator j = init->begin(); 592 | while (i != namelist->end()) 593 | { 594 | transpiler.newline(); 595 | fprintf(transpiler.out, "(" LUA_NAME_FORMAT, (*i)->c_str()); 596 | if (j != init->end()) 597 | { 598 | putc(' ', transpiler.out); 599 | (*j)->transpile(transpiler); 600 | ++j; 601 | } 602 | putc(')', transpiler.out); 603 | ++i; 604 | } 605 | transpiler.indent--; 606 | putc(')', transpiler.out); 607 | } 608 | else 609 | { 610 | int c = '('; 611 | for (auto& name : *namelist) 612 | { 613 | fprintf(transpiler.out, "%c(" LUA_NAME_FORMAT ")", c, name->c_str()); 614 | c = ' '; 615 | } 616 | putc(')', transpiler.out); 617 | if (!init->empty()) 618 | { 619 | transpiler.newline(); 620 | fprintf(transpiler.out, "(assign "); 621 | c = '('; 622 | for (auto& name : *namelist) 623 | { 624 | fprintf(transpiler.out, "%c" LUA_NAME_FORMAT, c, name->c_str()); 625 | c = ' '; 626 | } 627 | fprintf(transpiler.out, ") "); 628 | c = '('; 629 | for (auto& expression : *init) 630 | { 631 | putc(c, transpiler.out); 632 | expression->transpile(transpiler); 633 | c = ' '; 634 | } 635 | fprintf(transpiler.out, "))"); 636 | } 637 | } 638 | transpiler.locals++; 639 | } 640 | std::unique_ptr namelist; 641 | std::unique_ptr> init; 642 | }; 643 | 644 | // local function binding statement 645 | struct LocalFunction : public Statement { 646 | LocalFunction(Name v, Lambda *l) : name(v), lambda(l) { } 647 | virtual void transpile(Transpiler& transpiler) 648 | { 649 | fprintf(transpiler.out, "(letrec ((" LUA_NAME_FORMAT " ", name->c_str()); 650 | lambda->transpile(transpiler); 651 | fprintf(transpiler.out, "))"); 652 | transpiler.locals++; 653 | } 654 | Name name; 655 | std::unique_ptr lambda; 656 | }; 657 | 658 | // break statement 659 | struct Break : public Statement { 660 | virtual void transpile(Transpiler& transpiler) 661 | { 662 | fprintf(transpiler.out, "(return-from @loop@)"); 663 | } 664 | }; 665 | 666 | // return statement 667 | struct Return : public Statement { 668 | Return(List *l) : list(l) { } 669 | virtual void transpile(Transpiler& transpiler) 670 | { 671 | fprintf(transpiler.out, "(let ((ret"); 672 | if (!list->empty()) 673 | { 674 | putc(' ', transpiler.out); 675 | if (list->size() == 1) 676 | { 677 | list->front()->transpile(transpiler); 678 | } 679 | else 680 | { 681 | fprintf(transpiler.out, "(list"); 682 | for (auto& expression : *list) 683 | { 684 | putc(' ', transpiler.out); 685 | expression->transpile(transpiler); 686 | } 687 | putc(')', transpiler.out); 688 | } 689 | } 690 | fprintf(transpiler.out, "))"); 691 | transpiler.newline(); 692 | fprintf(transpiler.out, "(if (and yield (not (null ret))) (yield ret) (return-from @func@ ret)))"); 693 | } 694 | std::unique_ptr> list; 695 | }; 696 | 697 | // if/elsif then, with optional test expression or NULL 698 | struct Condition : public AbstractSyntaxTree { 699 | Condition(Expression *t, List *b) : test(t), block(b) { } 700 | virtual void transpile(Transpiler& transpiler) 701 | { 702 | transpiler.begin_local_scope(); 703 | transpiler.newline(); 704 | fprintf(transpiler.out, "("); 705 | test->transpile(transpiler); 706 | transpiler.indent++; 707 | for (auto& statement : *block) 708 | { 709 | transpiler.newline(); 710 | statement->transpile(transpiler); 711 | } 712 | transpiler.indent--; 713 | putc(')', transpiler.out); 714 | transpiler.end_local_scope(); 715 | } 716 | std::unique_ptr test; 717 | std::unique_ptr> block; 718 | }; 719 | 720 | // dotted name of a function with optional last colon name (flagged as self) 721 | struct FunctionName : public AbstractSyntaxTree { 722 | FunctionName(NameList *f, bool s) : namelist(f), self(s) { } 723 | std::unique_ptr namelist; 724 | bool self; 725 | }; 726 | 727 | // parameters of a function with optional ... (dots is true) 728 | struct Parameters : public AbstractSyntaxTree { 729 | Parameters(NameList *v, bool d) : namelist(v), dots(d) { } 730 | virtual void transpile(Transpiler& transpiler) 731 | { 732 | transpile(transpiler, false); 733 | } 734 | void transpile(Transpiler& transpiler, bool self) 735 | { 736 | fprintf(transpiler.out, " (yield"); 737 | if (self) 738 | fprintf(transpiler.out, " " LUA_NAME_FORMAT, "self"); 739 | for (auto& name : *namelist) 740 | { 741 | if (typeid(name) == typeid(Dots)) 742 | fprintf(transpiler.out, " . ..."); 743 | else 744 | fprintf(transpiler.out, " " LUA_NAME_FORMAT, name->c_str()); 745 | } 746 | putc(')', transpiler.out); 747 | } 748 | std::unique_ptr namelist; 749 | bool dots; 750 | }; 751 | 752 | // for counter range 753 | struct Range : public AbstractSyntaxTree { 754 | Range(Expression *e1, Expression *e2, Expression *e3) : start(e1), end(e2), step(e3) { } 755 | std::unique_ptr start, end, step; 756 | }; 757 | 758 | // table field with optional key or NULL and value 759 | struct Field : AbstractSyntaxTree { 760 | Field(Expression *k, Expression *v) : key(k), value(v) { } 761 | std::unique_ptr key, value; 762 | }; 763 | 764 | Transpiler(const std::string& s) : name(s), chunk(NULL), locals(0), indent(0), out(NULL) { } 765 | 766 | ~Transpiler() 767 | { 768 | if (chunk) 769 | delete chunk; 770 | } 771 | 772 | void begin_local_scope() 773 | { 774 | scope.push(locals); 775 | ++indent; 776 | } 777 | 778 | void end_local_scope() 779 | { 780 | size_t restore_locals = scope.top(); 781 | scope.pop(); 782 | for (; locals > restore_locals; --locals) 783 | putc(')', out); 784 | --indent; 785 | } 786 | 787 | void newline() 788 | { 789 | fprintf(out, "\n%*s", 2*indent, ""); 790 | } 791 | 792 | bool save() 793 | { 794 | if (name == "-") 795 | out = stdout; 796 | else 797 | out = fopen(name.append(".lisp").c_str(), "w"); 798 | 799 | if (!out) 800 | return false; 801 | 802 | fprintf(out, "; %s", name.c_str()); 803 | for (auto& statement : *chunk) 804 | { 805 | newline(); 806 | statement->transpile(*this); 807 | } 808 | for (; locals; --locals) 809 | putc(')', out); 810 | putc('\n', out); 811 | 812 | if (out != stdout) 813 | fclose(out); 814 | out = NULL; 815 | 816 | return true; 817 | } 818 | 819 | std::string name; 820 | List *chunk; 821 | size_t locals; 822 | int indent; 823 | std::stack scope; 824 | FILE *out; 825 | 826 | }; 827 | 828 | #endif 829 | -------------------------------------------------------------------------------- /lua.l: -------------------------------------------------------------------------------- 1 | // Lua 5.3 RE/flex scanner by Robert van Engelen 2 | 3 | %top{ 4 | #include 5 | #include 6 | #include "LuaParser.hpp" // generated by bison from lua.y 7 | #include "location.hpp" // generated by bison %locations 8 | } 9 | 10 | // lexer syntax: enable free-space mode regular expressions for clarity 11 | %option freespace 12 | 13 | // lexer optimization: generate fast scanner in direct code 14 | %option fast 15 | 16 | // lexer class: yy::LuaScanner 17 | %option namespace=yy 18 | %option lexer=LuaScanner 19 | 20 | // lexer output files 21 | %option outfile=LuaScanner.cpp 22 | %option header-file=LuaScanner.hpp 23 | 24 | // lexer errors: throw an exception in the scanner's default rule 25 | %option exception="yy::LuaParser::syntax_error(location(), \"Unknown token.\")" 26 | 27 | // parser integration: output code for the bison complete with locations parser 28 | %option bison-complete 29 | %option bison-locations 30 | %option bison-cc-namespace=yy 31 | %option bison-cc-parser=LuaParser 32 | 33 | // LuaScanner class 34 | %class{ 35 | 36 | public: 37 | 38 | // lookup/insert a symbol in the "symbol table" 39 | Name symbol(const char *str) 40 | { 41 | return &*symbols.insert(str).first; 42 | } 43 | 44 | private: 45 | 46 | struct Keyword 47 | { 48 | const char *name; 49 | int token; 50 | }; 51 | 52 | // translate keyword to the corresponding token or return 0 if not a keyword 53 | int keyword_token(const char *str) 54 | { 55 | static const Keyword keywords[] = 56 | { 57 | { "and", LuaParser::token::TOKEN_AND }, 58 | { "break", LuaParser::token::TOKEN_BREAK }, 59 | { "do", LuaParser::token::TOKEN_DO }, 60 | { "else", LuaParser::token::TOKEN_ELSE }, 61 | { "elseif", LuaParser::token::TOKEN_ELSEIF }, 62 | { "end", LuaParser::token::TOKEN_END }, 63 | { "false", LuaParser::token::TOKEN_FALSE }, 64 | { "for", LuaParser::token::TOKEN_FOR }, 65 | { "function", LuaParser::token::TOKEN_FUNCTION }, 66 | { "goto", LuaParser::token::TOKEN_GOTO }, 67 | { "if", LuaParser::token::TOKEN_IF }, 68 | { "in", LuaParser::token::TOKEN_IN }, 69 | { "local", LuaParser::token::TOKEN_LOCAL }, 70 | { "nil", LuaParser::token::TOKEN_NIL }, 71 | { "not", LuaParser::token::TOKEN_NOT }, 72 | { "or", LuaParser::token::TOKEN_OR }, 73 | { "repeat", LuaParser::token::TOKEN_REPEAT }, 74 | { "return", LuaParser::token::TOKEN_RETURN }, 75 | { "then", LuaParser::token::TOKEN_THEN }, 76 | { "true", LuaParser::token::TOKEN_TRUE }, 77 | { "until", LuaParser::token::TOKEN_UNTIL }, 78 | { "while", LuaParser::token::TOKEN_WHILE }, 79 | { NULL, 0 } 80 | }; 81 | 82 | for (const Keyword *keyword = keywords; keyword->name != NULL; ++keyword) 83 | if (strcmp(keyword->name, str) == 0) 84 | return keyword->token; 85 | 86 | return 0; 87 | } 88 | 89 | // lookup/insert matched text() in the "symbol table" matching an {name} 90 | Name symbol() 91 | { 92 | return symbol(text()); 93 | } 94 | 95 | // lookup/insert matched text() int the "symbol table" matching a quoted {string} 96 | StringLiteral string() 97 | { 98 | return symbols.insert(translate_escapes()).first->c_str(); 99 | } 100 | 101 | // translate \a, \b, \t, \n, \v, \f, \r, \\, \', \", \xXX, and \u{xxx} in matched text() matching string 102 | std::string translate_escapes() 103 | { 104 | std::string t; 105 | const char *s = matcher().begin() + 1; 106 | const char *e = matcher().end() - 1; 107 | while (s < e) 108 | { 109 | if (*s == '\\' && s + 1 < e) 110 | { 111 | static const char *escapes = "abtnvfr"; 112 | const char *esc = strchr(escapes, *++s); 113 | if (esc != NULL) 114 | { 115 | t.push_back(esc - escapes + '\a'); 116 | ++s; 117 | } 118 | else if (isdigit(*s)) 119 | { 120 | int n = 0, k = 3; 121 | do 122 | n = 10*n + *s++ - '0'; 123 | while (s < e && --k && isdigit(*s)); 124 | t.push_back(n); 125 | } 126 | else if (*s == 'x' && s + 2 < e) 127 | { 128 | int n1 = s[1] - (s[1] >= 'a' ? 'a'-10 : s[1] >= 'A' ? 'A'-10 : '0'); 129 | if (n1 > 9) 130 | n1 -= 7; 131 | int n2 = s[2] - (s[2] >= 'a' ? 'a'-10 : s[2] >= 'A' ? 'A'-10 : '0'); 132 | if (n2 > 9) 133 | n2 -= 7; 134 | t.push_back(n1 << 4 | n2); 135 | s += 3; 136 | } 137 | else if (*s == 'u' && s + 1 < e && s[1] == '{') 138 | { 139 | char buf[8], *u = buf; 140 | int n = 0; 141 | while (++s < e && isdigit(*s)) 142 | n = 16*n + *s - (*s >= 'a' ? 'a'-10 : *s >= 'A' ? 'A'-10 : '0'); 143 | if (s < e && *s == '}') 144 | ++s; 145 | size_t k = reflex::utf8(n, u); 146 | while (k--) 147 | t.push_back(*u++); 148 | } 149 | else 150 | { 151 | t.push_back(*s++); 152 | } 153 | } 154 | else 155 | { 156 | t.push_back(*s++); 157 | } 158 | } 159 | return t; 160 | } 161 | 162 | // return token of matched text() matching {identifier} 163 | LuaParser::symbol_type token_Name() 164 | { 165 | int token = keyword_token(text()); 166 | return token ? LuaParser::symbol_type(token, location()) : LuaParser::make_NAME(symbol(), location()); 167 | } 168 | 169 | // return token of matched text() matching {integer} 170 | LuaParser::symbol_type token_IntegerLiteral() 171 | { 172 | return LuaParser::make_INTEGER(strtol(text(), NULL, 0), location()); 173 | } 174 | 175 | // return token of matched text() matching {float} 176 | LuaParser::symbol_type token_FloatLiteral() 177 | { 178 | return LuaParser::make_FLOAT(strtod(text(), NULL), location()); 179 | } 180 | 181 | // return token of matched text() matching {string} 182 | LuaParser::symbol_type token_StringLiteral() 183 | { 184 | return LuaParser::make_STRING(string(), location()); 185 | } 186 | 187 | // a symbol table 188 | std::set symbols; 189 | 190 | // bracket matching 191 | size_t bracketlen; 192 | 193 | // long string 194 | std::string longstring; 195 | } 196 | 197 | // LuaScanner class constructor 198 | %init{ 199 | } 200 | 201 | %x LONGCOMMENT LONGSTRING 202 | 203 | digit [0-9] 204 | alpha [a-zA-Z_] 205 | name {alpha} ( {alpha} | {digit} )* 206 | integer {digit}+ | 0 [xX] [0-9a-fA-F]+ 207 | exp [eE] [-+]? {digit}+ 208 | float {digit}+ \. {digit}* {exp}? 209 | string \" ( \\. | [^\\"\n] )* \" | \' ( \\. | [^\\'\n] )* \' 210 | longbracket \[=*\[ 211 | 212 | %% 213 | 214 | [[:space:]]+ // skip white space 215 | "--" {longbracket} { bracketlen = size()-2; start(LONGCOMMENT); } 216 | "--".* // ignore inline comment 217 | {name} { return token_Name(); } 218 | {integer} { return token_IntegerLiteral(); } 219 | {float} { return token_FloatLiteral(); } 220 | {string} { return token_StringLiteral(); } 221 | {longbracket} { bracketlen = size(); longstring.clear(); start(LONGSTRING); } 222 | "==" { return LuaParser::symbol_type(LuaParser::token::TOKEN_EQU, location()); } 223 | "~=" { return LuaParser::symbol_type(LuaParser::token::TOKEN_NEQ, location()); } 224 | "<=" { return LuaParser::symbol_type(LuaParser::token::TOKEN_LTE, location()); } 225 | ">=" { return LuaParser::symbol_type(LuaParser::token::TOKEN_GTE, location()); } 226 | "<<" { return LuaParser::symbol_type(LuaParser::token::TOKEN_SHL, location()); } 227 | ">>" { return LuaParser::symbol_type(LuaParser::token::TOKEN_SHR, location()); } 228 | "//" { return LuaParser::symbol_type(LuaParser::token::TOKEN_DIV, location()); } 229 | ".." { return LuaParser::symbol_type(LuaParser::token::TOKEN_CAT, location()); } 230 | "..." { return LuaParser::symbol_type(LuaParser::token::TOKEN_DOTS, location()); } 231 | "::" { return LuaParser::symbol_type(LuaParser::token::TOKEN_COLS, location()); } 232 | [#%&()*+,\-./:;<=>\[\]^{|}~] { return LuaParser::symbol_type(chr(), location()); } 233 | <> { return LuaParser::make_EOF(location()); } 234 | 235 | { 236 | \]=*\] { if (size() == bracketlen) start(INITIAL); } 237 | . | \n // ignore long comment 238 | <> { yy::Parser::syntax_error(location(), "long comment not closed"); } 239 | } 240 | 241 | { 242 | \]=*\] { if (size() == bracketlen) 243 | { 244 | start(INITIAL); 245 | return LuaParser::make_STRING(longstring.c_str(), location()); 246 | } 247 | matcher().less(1); 248 | longstring.push_back(chr()); 249 | } 250 | . | \n { longstring.push_back(chr()); } 251 | <> { yy::Parser::syntax_error(location(), "long string not closed"); } 252 | } 253 | 254 | %% 255 | 256 | -------------------------------------------------------------------------------- /lua.txt: -------------------------------------------------------------------------------- 1 | Lua 5.3 grammar for Bison 3.2 with reduce/reduce conflict eliminated by expanding prefixexp - Dr. Robert A. van Engelen 2 | 3 | %token 4 | AND "and" 5 | BREAK "break" 6 | DO "do" 7 | ELSE "else" 8 | ELSEIF "elseif" 9 | END "end" 10 | FALSE "false" 11 | FOR "for" 12 | FUNCTION "function" 13 | IF "if" 14 | IN "in" 15 | LOCAL "local" 16 | NIL "nil" 17 | NOT "not" 18 | OR "or" 19 | REPEAT "repeat" 20 | RETURN "return" 21 | THEN "then" 22 | TRUE "true" 23 | UNTIL "until" 24 | WHILE "while" 25 | EQU "==" 26 | NEQ "~=" 27 | LTE "<=" 28 | GTE ">=" 29 | CAT ".." 30 | SHL "<<" 31 | SHR ">>" 32 | DIV "//" 33 | DOTS "..." 34 | ; 35 | 36 | %token '#' '%' '&' '(' ')' '*' '+' ',' '-' '.' '/' ':' ';' '<' '=' '>' '[' ']' '^' '{' '|' '}' '~' 37 | 38 | %left OR 39 | %left AND 40 | %left EQU NEQ LTE '<' GTE '>' 41 | %right CAT 42 | %left '|' 43 | %left '~' 44 | %left '&' 45 | %left SHL SHR 46 | %left '+' '-' 47 | %left '*' '/' '%' DIV 48 | %right NOT '#' 49 | %right '^' 50 | 51 | // we expect four shift-reduce conflicts due to Lua's optional semicolon, must always shift on '(' 52 | %expect 4 53 | 54 | %% 55 | 56 | chunk : block 57 | 58 | semi : ';' 59 | | 60 | 61 | block : scope statlist 62 | | scope statlist laststat semi 63 | 64 | ublock : block UNTIL exp 65 | 66 | scope : 67 | | scope statlist binding semi 68 | 69 | statlist : 70 | | statlist stat semi 71 | 72 | stat : DO block END 73 | | WHILE exp DO block END 74 | | repetition DO block END 75 | | REPEAT ublock 76 | | IF conds END 77 | | FUNCTION funcname funcbody 78 | | setlist '=' explist1 79 | | funccall 80 | 81 | repetition : FOR NAME '=' explist23 82 | | FOR namelist IN explist1 83 | 84 | conds : condlist 85 | | condlist ELSE block 86 | 87 | condlist : cond 88 | | condlist ELSEIF cond 89 | 90 | cond : exp THEN block 91 | 92 | laststat : BREAK 93 | | RETURN 94 | | RETURN explist1 95 | 96 | binding : LOCAL namelist 97 | | LOCAL namelist '=' explist1 98 | | LOCAL FUNCTION NAME funcbody 99 | 100 | funcname : dottedname 101 | | dottedname ':' NAME 102 | 103 | dottedname : NAME 104 | | dottedname '.' NAME 105 | 106 | namelist : NAME 107 | | namelist ',' NAME 108 | 109 | explist1 : exp 110 | | explist1 ',' exp 111 | 112 | explist23 : exp ',' exp 113 | | exp ',' exp ',' exp 114 | 115 | exp : NIL 116 | | TRUE 117 | | FALSE 118 | | NUMBER 119 | | STRING 120 | | DOTS 121 | | function 122 | | var 123 | | funccall 124 | | tableconstr 125 | | NOT exp 126 | | '#' exp 127 | | '-' exp %prec NOT 128 | | '~' exp %prec NOT 129 | | exp OR exp 130 | | exp AND exp 131 | | exp '<' exp 132 | | exp LTE exp 133 | | exp '>' exp 134 | | exp GTE exp 135 | | exp EQU exp 136 | | exp NEQ exp 137 | | exp '|' exp 138 | | exp '~' exp 139 | | exp '&' exp 140 | | exp SHL exp 141 | | exp SHR exp 142 | | exp CAT exp 143 | | exp '+' exp 144 | | exp '-' exp 145 | | exp '*' exp 146 | | exp '/' exp 147 | | exp DIV exp 148 | | exp '%' exp 149 | | exp '^' exp 150 | | '(' exp ')' 151 | 152 | setlist : var 153 | | setlist ',' var 154 | 155 | var : NAME 156 | | var '[' exp ']' 157 | | var '.' NAME 158 | | funccall '[' exp ']' 159 | | funccall '.' NAME 160 | | '(' exp ')' '[' exp ']' 161 | | '(' exp ')' '.' NAME 162 | 163 | funccall : var args 164 | | var ':' NAME args 165 | | funccall args 166 | | funccall ':' NAME args 167 | | '(' exp ')' args 168 | | '(' exp ')' ':' NAME args 169 | 170 | args : '(' ')' 171 | | '(' explist1 ')' 172 | | tableconstr 173 | | STRING 174 | 175 | function : FUNCTION funcbody 176 | 177 | funcbody : params block END 178 | 179 | params : '(' parlist ')' 180 | 181 | parlist : 182 | | namelist 183 | | DOTS 184 | | namelist ',' DOTS 185 | 186 | tableconstr : '{' '}' 187 | | '{' fieldlist '}' 188 | | '{' fieldlist ',' '}' 189 | | '{' fieldlist ';' '}' 190 | 191 | fieldlist : field 192 | | fieldlist ',' field 193 | | fieldlist ';' field 194 | 195 | field : exp 196 | | NAME '=' exp 197 | | '[' exp ']' '=' exp 198 | 199 | %% 200 | -------------------------------------------------------------------------------- /lua.y: -------------------------------------------------------------------------------- 1 | // Lua 5.3 Bison parser and transpiler by Robert van Engelen 2 | // Eliminated reduce/reduce conflict by expanding the prefixexp nonterminal 3 | // Expanded the repetition nonterminal 4 | // Renamed long nonterminal names functiondef->function, functioncall->funccall, tableconstructor->table 5 | // Requires lua.l, lua.y, lua.hpp 6 | 7 | %require "3.2" 8 | %skeleton "lalr1.cc" 9 | %language "c++" 10 | 11 | // parser class: yy::LuaParser 12 | %define api.namespace {yy} 13 | %define api.parser.class {LuaParser} 14 | 15 | // use C++ variants and generate yy::LuaParser::make_ constructors 16 | %define api.value.type variant 17 | %define api.token.constructor 18 | 19 | // verbose error messages, reported with yy::LuaParser::error() 20 | %define parse.error verbose 21 | 22 | // generate LuaParser.hpp 23 | %defines 24 | 25 | // generate LuaParser.cpp 26 | %output "LuaParser.cpp" 27 | 28 | // enable bison locations and generate location.hpp 29 | %locations 30 | %define api.location.file "location.hpp" 31 | 32 | // pass LuaScanner lexer, Transpiler transpiler, and error objects to yy::LuaParser::parse(), to use in semantic actions 33 | %parse-param {yy::LuaScanner& lexer} 34 | %parse-param {Transpiler& transpiler} 35 | %parse-param {size_t& errors} 36 | 37 | // generate yy::LuaParser::token::TOKEN_ token constants 38 | %define api.token.prefix {TOKEN_} 39 | 40 | // the code to include with LuaParser.hpp 41 | %code requires { 42 | #include "lua.hpp" 43 | namespace yy { 44 | class LuaScanner; 45 | } 46 | } 47 | 48 | // the code to include with LuaParser.cpp 49 | %code { 50 | #include "LuaScanner.hpp" 51 | // within yy::LuaParser::parse() we should invoke lexer.lex() as yylex() 52 | #undef yylex 53 | #define yylex lexer.lex 54 | } 55 | 56 | // tokens with semantic values 57 | %token NAME "name"; 58 | %token INTEGER "integer constant"; 59 | %token FLOAT "floating point constant"; 60 | %token STRING "string literal"; 61 | %token EOF 0 "end of file"; 62 | 63 | %token 64 | AND "and" 65 | BREAK "break" 66 | DO "do" 67 | ELSE "else" 68 | ELSEIF "elseif" 69 | END "end" 70 | FALSE "false" 71 | FOR "for" 72 | FUNCTION "function" 73 | GOTO "goto" 74 | IF "if" 75 | IN "in" 76 | LOCAL "local" 77 | NIL "nil" 78 | NOT "not" 79 | OR "or" 80 | REPEAT "repeat" 81 | RETURN "return" 82 | THEN "then" 83 | TRUE "true" 84 | UNTIL "until" 85 | WHILE "while" 86 | EQU "==" 87 | NEQ "~=" 88 | LTE "<=" 89 | GTE ">=" 90 | CAT ".." 91 | SHL "<<" 92 | SHR ">>" 93 | DIV "//" 94 | DOTS "..." 95 | COLS "::" 96 | ; 97 | 98 | %token '#' '%' '&' '(' ')' '*' '+' ',' '-' '.' '/' ':' ';' '<' '=' '>' '[' ']' '^' '{' '|' '}' '~' 99 | 100 | // operator precedence and associativity 101 | %left OR 102 | %left AND 103 | %left EQU NEQ LTE '<' GTE '>' 104 | %left '|' 105 | %left '~' 106 | %left '&' 107 | %left SHL SHR 108 | %right CAT 109 | %left '+' '-' 110 | %left '*' '/' DIV '%' 111 | %right NOT '#' 112 | %right '^' 113 | 114 | // we expect four shift-reduce conflicts due to Lua's optional semicolon, correct to shift on '(' 115 | %expect 4 116 | 117 | %type *> block scope statlist 118 | %type stat binding laststat 119 | %type *> explist1 setlist args 120 | %type exp var 121 | %type *> conds condlist 122 | %type cond 123 | %type function funcbody 124 | %type params parlist 125 | %type funccall 126 | %type explist23 127 | %type funcname 128 | %type dottedname namelist 129 | %type label 130 | %type table 131 | %type *> fieldlist 132 | %type field 133 | 134 | %% 135 | 136 | chunk : block { transpiler.chunk = $1; } 137 | 138 | semi : ';' 139 | | 140 | 141 | block : scope statlist { $$ = $1->concat($2); } 142 | | scope statlist laststat semi { $$ = $1->concat($2)->add($3); } 143 | 144 | scope : { $$ = new Transpiler::List(); } 145 | | scope statlist binding semi { $$ = $1->concat($2)->add($3); } 146 | 147 | statlist : { $$ = new Transpiler::List(); } 148 | | statlist stat semi { $$ = $1->add($2); } 149 | 150 | stat : DO block END { $$ = new Transpiler::Block($2); } 151 | | IF conds END { $$ = new Transpiler::If($2); } 152 | | WHILE exp DO block END { $$ = new Transpiler::While($2, $4); } 153 | | REPEAT block UNTIL exp { $$ = new Transpiler::Until($2, $4); } 154 | | FOR NAME '=' explist23 DO block END { $$ = new Transpiler::ForCounter($2, $4, $6); } 155 | | FOR namelist IN explist1 DO block END { $$ = new Transpiler::ForIterator($2, $4, $6); } 156 | | FUNCTION funcname funcbody { $$ = new Transpiler::Function($2, $3); } 157 | | GOTO NAME { $$ = new Transpiler::Goto($2); } 158 | | label { $$ = new Transpiler::Label($1); } 159 | | setlist '=' explist1 { $$ = new Transpiler::Assign($1, $3); } 160 | | funccall { $$ = new Transpiler::FunctionCall($1); } 161 | | error DO { yyerrok; } 162 | | error IF { yyerrok; } 163 | | error WHILE { yyerrok; } 164 | | error FOR { yyerrok; } 165 | | error REPEAT { yyerrok; } 166 | | error FUNCTION { yyerrok; } 167 | | error GOTO { yyerrok; } 168 | | error COLS { yyerrok; } 169 | | error LOCAL { yyerrok; } 170 | | error BREAK { yyerrok; } 171 | | error RETURN { yyerrok; } 172 | | error END { yyerrok; } 173 | | error ';' { yyerrok; } 174 | 175 | conds : condlist { $$ = $1; } 176 | | condlist ELSE block { $$ = $1->add(new Transpiler::Condition(new Transpiler::True(), $3)); } 177 | 178 | condlist : cond { $$ = new Transpiler::List($1); } 179 | | condlist ELSEIF cond { $$ = $1->add($3); } 180 | 181 | cond : exp THEN block { $$ = new Transpiler::Condition($1, $3); } 182 | 183 | laststat : BREAK { $$ = new Transpiler::Break(); } 184 | | RETURN { $$ = new Transpiler::Return(new Transpiler::List()); } 185 | | RETURN explist1 { $$ = new Transpiler::Return($2); } 186 | 187 | label : COLS NAME COLS { $$ = $2; } 188 | 189 | binding : LOCAL namelist { $$ = new Transpiler::Local($2, new Transpiler::List()); } 190 | | LOCAL namelist '=' explist1 { $$ = new Transpiler::Local($2, $4); } 191 | | LOCAL FUNCTION NAME funcbody { $$ = new Transpiler::LocalFunction($3, $4); } 192 | 193 | funcname : dottedname { $$ = new Transpiler::FunctionName($1, false); } 194 | | dottedname ':' NAME { $$ = new Transpiler::FunctionName($1->add($3), true); } 195 | 196 | dottedname : NAME { $$ = new Transpiler::NameList($1); } 197 | | dottedname '.' NAME { $$ = $1->add($3); } 198 | 199 | namelist : NAME { $$ = new Transpiler::NameList($1); } 200 | | namelist ',' NAME { $$ = $1->add($3); } 201 | 202 | explist1 : exp { $$ = new Transpiler::List($1); } 203 | | explist1 ',' exp { $$ = $1->add($3); } 204 | 205 | explist23 : exp ',' exp { $$ = new Transpiler::Range($1, $3, NULL); } 206 | | exp ',' exp ',' exp { $$ = new Transpiler::Range($1, $3, $5); } 207 | 208 | exp : NIL { $$ = new Transpiler::Nil(); } 209 | | TRUE { $$ = new Transpiler::True(); } 210 | | FALSE { $$ = new Transpiler::False(); } 211 | | INTEGER { $$ = new Transpiler::Integer($1); } 212 | | FLOAT { $$ = new Transpiler::Float($1); } 213 | | STRING { $$ = new Transpiler::String($1); } 214 | | DOTS { $$ = new Transpiler::Dots(); } 215 | | function { $$ = $1; } 216 | | table { $$ = $1; } 217 | | var { $$ = $1; } 218 | | funccall { $$ = $1; } 219 | | NOT exp { $$ = new Transpiler::UnaryOp("not", $2); } 220 | | '#' exp { $$ = new Transpiler::UnaryOp("#", $2); } 221 | | '-' exp %prec NOT { $$ = new Transpiler::UnaryOp("-", $2); } 222 | | '~' exp %prec NOT { $$ = new Transpiler::UnaryOp("~", $2); } 223 | | exp OR exp { $$ = new Transpiler::Op("or", $1, $3); } 224 | | exp AND exp { $$ = new Transpiler::Op("and", $1, $3); } 225 | | exp '<' exp { $$ = new Transpiler::Op("<", $1, $3); } 226 | | exp LTE exp { $$ = new Transpiler::Op("<=", $1, $3); } 227 | | exp '>' exp { $$ = new Transpiler::Op(">", $1, $3); } 228 | | exp GTE exp { $$ = new Transpiler::Op(">=", $1, $3); } 229 | | exp EQU exp { $$ = new Transpiler::Op("==", $1, $3); } 230 | | exp NEQ exp { $$ = new Transpiler::Op("~=", $1, $3); } 231 | | exp '|' exp { $$ = new Transpiler::Op("|", $1, $3); } 232 | | exp '~' exp { $$ = new Transpiler::Op("~", $1, $3); } 233 | | exp '&' exp { $$ = new Transpiler::Op("&", $1, $3); } 234 | | exp SHL exp { $$ = new Transpiler::Op("<<", $1, $3); } 235 | | exp SHR exp { $$ = new Transpiler::Op(">>", $1, $3); } 236 | | exp CAT exp { $$ = new Transpiler::Op("..", $1, $3); } 237 | | exp '+' exp { $$ = new Transpiler::Op("+", $1, $3); } 238 | | exp '-' exp { $$ = new Transpiler::Op("-", $1, $3); } 239 | | exp '*' exp { $$ = new Transpiler::Op("*", $1, $3); } 240 | | exp '/' exp { $$ = new Transpiler::Op("/", $1, $3); } 241 | | exp DIV exp { $$ = new Transpiler::Op("//", $1, $3); } 242 | | exp '%' exp { $$ = new Transpiler::Op("%", $1, $3); } 243 | | exp '^' exp { $$ = new Transpiler::Op("^", $1, $3); } 244 | | '(' exp ')' { $$ = $2; } 245 | 246 | setlist : var { $$ = new Transpiler::List($1); } 247 | | setlist ',' var { $$ = $1->add($3); } 248 | 249 | var : NAME { $$ = new Transpiler::Variable($1); } 250 | | var '[' exp ']' { $$ = new Transpiler::Index($1, $3); } 251 | | var '.' NAME { $$ = new Transpiler::Member($1, $3); } 252 | | funccall '[' exp ']' { $$ = new Transpiler::Index($1, $3); } 253 | | funccall '.' NAME { $$ = new Transpiler::Member($1, $3); } 254 | | '(' exp ')' '[' exp ']' { $$ = new Transpiler::Index($2, $5); } 255 | | '(' exp ')' '.' NAME { $$ = new Transpiler::Member($2, $5); } 256 | 257 | funccall : var args { $$ = new Transpiler::Call($1, $2, NULL); } 258 | | var ':' NAME args { $$ = new Transpiler::Call($1, $4, $3); } 259 | | funccall args { $$ = new Transpiler::Call($1, $2, NULL); } 260 | | funccall ':' NAME args { $$ = new Transpiler::Call($1, $4, $3); } 261 | | '(' exp ')' args { $$ = new Transpiler::Call($2, $4, NULL); } 262 | | '(' exp ')' ':' NAME args { $$ = new Transpiler::Call($2, $6, $5); } 263 | 264 | args : '(' ')' { $$ = new Transpiler::List(); } 265 | | '(' explist1 ')' { $$ = $2; } 266 | | table { $$ = new Transpiler::List($1); } 267 | | STRING { $$ = new Transpiler::List(new Transpiler::String($1)); } 268 | 269 | function : FUNCTION funcbody { $$ = $2; } 270 | 271 | funcbody : params block END { $$ = new Transpiler::Lambda($1, $2); } 272 | 273 | params : '(' parlist ')' { $$ = $2; } 274 | 275 | parlist : { $$ = new Transpiler::Parameters(new Transpiler::NameList(), false); } 276 | | namelist { $$ = new Transpiler::Parameters($1, false); } 277 | | DOTS { $$ = new Transpiler::Parameters(new Transpiler::NameList(), true); } 278 | | namelist ',' DOTS { $$ = new Transpiler::Parameters($1, true); } 279 | 280 | table : '{' '}' { $$ = new Transpiler::Table(new Transpiler::List()); } 281 | | '{' fieldlist '}' { $$ = new Transpiler::Table($2); } 282 | | '{' fieldlist ',' '}' { $$ = new Transpiler::Table($2); } 283 | | '{' fieldlist ';' '}' { $$ = new Transpiler::Table($2); } 284 | 285 | fieldlist : field { $$ = new Transpiler::List($1); } 286 | | fieldlist ',' field { $$ = $1->add($3); } 287 | | fieldlist ';' field { $$ = $1->add($3); } 288 | 289 | field : exp { $$ = new Transpiler::Field(NULL, $1); } 290 | | NAME '=' exp { $$ = new Transpiler::Field(new Transpiler::Variable($1), $3); } 291 | | '[' exp ']' '=' exp { $$ = new Transpiler::Field($2, $5); } 292 | 293 | %% 294 | 295 | int main(int argc, char **argv) 296 | { 297 | FILE *file; 298 | 299 | // open the specified source code file for reading 300 | if (argc <= 1 || (file = fopen(argv[1], "r")) == NULL) 301 | { 302 | std::cerr << "Cannot open file for reading\n"; 303 | exit(EXIT_FAILURE); 304 | } 305 | 306 | // construct a lexer taking input from the specified file encoded in UTF-8/16/32 307 | yy::LuaScanner lexer(file); 308 | 309 | // with bison-complete and bison-locations we can set the filename to display with syntax errors 310 | lexer.filename = argv[1]; 311 | 312 | // basename of the filename without path and extension suffix 313 | const char *s = strrchr(argv[1], '/'); 314 | if (s == NULL) 315 | s = argv[1]; 316 | else 317 | ++s; 318 | const char *e = strrchr(s, '.'); 319 | if (e == NULL) 320 | e = s + strlen(s); 321 | std::string name(s, e - s); 322 | 323 | Transpiler transpiler(name); 324 | 325 | // keep track of the number of errors reported with yy:LuaParser::error() 326 | size_t errors = 0; 327 | 328 | // construct a parser, pass the lexer, transpiler and errors to use in the semantic actions 329 | yy::LuaParser parser(lexer, transpiler, errors); 330 | 331 | // parse and transpile 332 | if (parser.parse() || errors > 0) 333 | { 334 | std::cerr << "Compilation errors\n"; 335 | exit(EXIT_FAILURE); 336 | } 337 | 338 | if (file != stdin) 339 | fclose(file); 340 | 341 | // save the transpilation 342 | if (transpiler.save()) 343 | printf("Saved %s.lisp\n", name.c_str()); 344 | else 345 | printf("Cannot save\n"); 346 | 347 | exit(EXIT_SUCCESS); 348 | } 349 | 350 | // display error and location in the source code 351 | void yy::LuaParser::error(const location& loc, const std::string& msg) 352 | { 353 | ++errors; 354 | 355 | std::cerr << loc << ": " << msg << std::endl; 356 | if (loc.begin.line == loc.end.line && loc.begin.line == static_cast(lexer.lineno())) 357 | { 358 | // the error is on the current line and spans only one line 359 | std::cerr << lexer.matcher().line() << std::endl; 360 | for (int i = 0; i < loc.begin.column; ++i) 361 | std::cerr << " "; 362 | for (int i = loc.begin.column; i <= loc.end.column; ++i) 363 | std::cerr << "~"; 364 | std::cerr << std::endl; 365 | } 366 | else 367 | { 368 | // the error is not on the current line or spans multiple lines 369 | FILE *file = lexer.in().file(); // the current file being scanned 370 | if (file != NULL) 371 | { 372 | yy::LuaScanner::Matcher *m = lexer.new_matcher(file); // new matcher 373 | lexer.push_matcher(m); // save the current matcher 374 | off_t pos = ftell(file); // save current position in the file 375 | fseek(file, 0, SEEK_SET); // go to the start of the file 376 | for (int i = 1; i < loc.begin.line; ++i) 377 | m->skip('\n'); // skip to the next line 378 | for (int i = loc.begin.line; i <= loc.end.line; ++i) 379 | { 380 | std::cerr << m->line() << std::endl; // display offending line 381 | m->skip('\n'); // next line 382 | } 383 | fseek(file, pos, SEEK_SET); // restore position in the file to continue scanning 384 | lexer.pop_matcher(); // restore matcher 385 | } 386 | } 387 | if (lexer.size() == 0) // if token is unknown (no match) 388 | lexer.matcher().winput(); // skip character 389 | } 390 | -------------------------------------------------------------------------------- /lua2lisp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Robert-van-Engelen/lua-to-lisp/3007beda57e3328f0230702bb7909f3a359fdf34/lua2lisp.png -------------------------------------------------------------------------------- /luademo.lisp: -------------------------------------------------------------------------------- 1 | ; luademo.lisp 2 | (define lua.allwords (lambda (yield) (block @func@ 3 | (let ( 4 | (lua.line ((index 'read lua.io) ()))) 5 | (let ( 6 | (lua.pos 1)) 7 | (let ((ret (lambda (yield) (block @func@ 8 | (block @loop@ (while lua.line 9 | (let ((lua.s) (lua.e)) 10 | (assign (lua.s lua.e) (((index 'find lua.string) () lua.line "%w+" lua.pos))) 11 | (cond 12 | (lua.s 13 | (assign (lua.pos) ((+ lua.e 1))) 14 | (let ((ret ((index 'sub lua.string) () lua.line lua.s lua.e))) 15 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret)))) 16 | (#t 17 | (assign (lua.line) (((index 'read lua.io) ()))) 18 | (assign (lua.pos) (1))))))) 19 | (let ((ret ())) 20 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret))))))) 21 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret)))))))) 22 | (define lua.prefix (lambda (yield lua.w1 lua.w2) (block @func@ 23 | (let ((ret (.. lua.w1 " " lua.w2))) 24 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret)))))) 25 | (let ( 26 | (lua.statetab)) 27 | (define lua.insert (lambda (yield lua.index lua.value) (block @func@ 28 | (cond 29 | ((not (index lua.index lua.statetab)) 30 | (assign ((index lua.index lua.statetab)) ((list (cons 'lua.n 0)))))) 31 | ((index 'insert lua.table) () (index lua.index lua.statetab) lua.value)))) 32 | (let ( 33 | (lua.N 2)) 34 | (let ( 35 | (lua.MAXGEN 10000)) 36 | (let ( 37 | (lua.NOWORD "\x0a")) 38 | (assign (lua.statetab) (())) 39 | (let ( 40 | (lua.w1 lua.NOWORD) 41 | (lua.w2 lua.NOWORD)) 42 | ((lua.allwords ()) (lambda (lua.w) 43 | (lua.insert () (lua.prefix () lua.w1 lua.w2) lua.w) 44 | (assign (lua.w1) (lua.w2)) 45 | (assign (lua.w2) (lua.w)))) 46 | (lua.insert () (lua.prefix () lua.w1 lua.w2) lua.NOWORD) 47 | (assign (lua.w1) (lua.NOWORD)) 48 | (assign (lua.w2) (lua.NOWORD)) 49 | (block @loop@ (do ((lua.i 1 (+ lua.i 1)) ((> lua.i lua.MAXGEN)) 50 | (let ( 51 | (lua.list (index (lua.prefix () lua.w1 lua.w2) lua.statetab))) 52 | (let ( 53 | (lua.r ((index 'random lua.math) () ((index 'getn lua.table) () lua.list)))) 54 | (let ( 55 | (lua.nextword (index lua.r lua.list))) 56 | (cond 57 | ((== lua.nextword lua.NOWORD) 58 | (let ((ret)) 59 | (if (and yield (not (null ret))) (yield ret) (return-from @func@ ret))))) 60 | ((index 'write lua.io) () lua.nextword " ") 61 | (assign (lua.w1) (lua.w2)) 62 | (assign (lua.w2) (lua.nextword))))))))))))) 63 | -------------------------------------------------------------------------------- /luademo.lua: -------------------------------------------------------------------------------- 1 | -- Markov Chain Program in Lua 2 | 3 | function allwords () 4 | local line = io.read() -- current line 5 | local pos = 1 -- current position in the line 6 | return function () -- iterator function 7 | while line do -- repeat while there are lines 8 | local s, e = string.find(line, "%w+", pos) 9 | if s then -- found a word? 10 | pos = e + 1 -- update next position 11 | return string.sub(line, s, e) -- return the word 12 | else 13 | line = io.read() -- word not found; try next line 14 | pos = 1 -- restart from first position 15 | end 16 | end 17 | return nil -- no more lines: end of traversal 18 | end 19 | end 20 | 21 | function prefix (w1, w2) 22 | return w1 .. ' ' .. w2 23 | end 24 | 25 | local statetab 26 | 27 | function insert (index, value) 28 | if not statetab[index] then 29 | statetab[index] = {n=0} 30 | end 31 | table.insert(statetab[index], value) 32 | end 33 | 34 | local N = 2 35 | local MAXGEN = 10000 36 | local NOWORD = "\n" 37 | 38 | -- build table 39 | statetab = {} 40 | local w1, w2 = NOWORD, NOWORD 41 | for w in allwords() do 42 | insert(prefix(w1, w2), w) 43 | w1 = w2; w2 = w; 44 | end 45 | insert(prefix(w1, w2), NOWORD) 46 | -- generate text 47 | w1 = NOWORD; w2 = NOWORD -- reinitialize 48 | for i=1,MAXGEN do 49 | local list = statetab[prefix(w1, w2)] 50 | -- choose a random item from list 51 | local r = math.random(table.getn(list)) 52 | local nextword = list[r] 53 | if nextword == NOWORD then return end 54 | io.write(nextword, " ") 55 | w1 = w2; w2 = nextword 56 | end 57 | -------------------------------------------------------------------------------- /support.lisp: -------------------------------------------------------------------------------- 1 | ; Lua-compatible operator names (suggested), with operator names changed with lua.hpp LUA_OPERATOR_FORMAT "L%s" 2 | (defmacro alias (to fn) 3 | `(setf (fdefinition ',to) #',fn)) 4 | (alias L+ +) 5 | (alias L- -) 6 | (alias L* *) 7 | (alias L/ /) 8 | (defun L// (x y) (car (truncate (/ x y)))) 9 | (alias L% mod) 10 | (alias L^ expt) 11 | (alias L| logior) 12 | (alias L~ logxor) 13 | (alias L& logand) 14 | (alias L.. concatenate) 15 | (alias L# length) 16 | (alias Lor or) 17 | (alias Land and) 18 | (alias Lnot not) 19 | (defun L== (x y) (if (and (stringp x) (stringp y)) (string= x y) (eq x y))) 20 | (defun L~= (x y) (not (L== x y))) 21 | (defun L< (x y) (if (and (stringp x) (stringp y)) (string< x y) (< x y))) 22 | (defun L> (x y) (if (and (stringp x) (stringp y)) (string> x y) (> x y))) 23 | (defun L<= (x y) (if (and (stringp x) (stringp y)) (string<= x y) (<= x y))) 24 | (defun L>= (x y) (if (and (stringp x) (stringp y)) (string>= x y) (>= x y))) 25 | 26 | ; table indexing (suggested) 27 | (defun index (field table) 28 | (when table 29 | (if (or (symbolp field) (stringp field)) 30 | (if (string= field (car (car table))) 31 | (cdr (car table)) 32 | (index field (cdr table))) 33 | (if (equal field (car (car table))) 34 | (cdr (car table)) 35 | (index field (cdr table)))))) 36 | 37 | ; assign (suggested) 38 | (defmacro assign (lhs rhs) 39 | (list 'let (list (list 'evrhs (list 'mapcar (list 'quote 'eval) rhs))) 40 | ; TODO traverse lhs to assign evrhs list of evaluated rhs 41 | 'evrhs 42 | ) 43 | ) 44 | 45 | ; lookup variable, return nil when undefined (suggested), lua.hpp enable macro NAME_LOOKUP 46 | (defmacro lookup (var) 47 | (list 'ignore-errors var)) 48 | 49 | ; begin (suggested if begin is not a primitive, but progn is) 50 | (defmacro begin body) 51 | (cons 'progn body)) 52 | 53 | ; while (suggested if while is not a primitive, but loop and progn are) 54 | (defmacro while (x . body) 55 | (list 'loop 'while x 'do (cons 'progn body))) 56 | 57 | ; until (suggested if while is not a primitive, but loop and progn are) 58 | (defmacro until (x . body) 59 | (list 'loop 'until x 'do (cons 'progn body))) 60 | --------------------------------------------------------------------------------