diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml new file mode 100644 index 00000000..b0324230 --- /dev/null +++ b/.github/workflows/binaries.yml @@ -0,0 +1,254 @@ +name: "binaries" + +on: [push] + +jobs: + generate-headers: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + + - uses: leafo/gh-actions-lua@master + with: + luaVersion: "5.1" + + - uses: leafo/gh-actions-luarocks@master + + - name: Install dependencies + run: | + luarocks install argparse + luarocks make + + - name: Generate moonscript.h + run: | + bin/splat.moon -l moonscript moonscript moon > moonscript.lua + xxd -i moonscript.lua > bin/binaries/moonscript.h + + - name: Generate moon.h + run: | + awk 'FNR>1' bin/moon > moon.lua + xxd -i moon.lua > bin/binaries/moon.h + + - name: Generate argparse.h + run: | + luarocks install argparse --tree=lua_modules + bin/splat.moon --strip-prefix -l argparse $(find lua_modules/share/lua -name "argparse.lua" -exec dirname {} \; | head -1) > bin/binaries/argparse.lua + xxd -i -n argparse_lua bin/binaries/argparse.lua > bin/binaries/argparse.h + + - name: Generate moonc.h + run: | + awk 'FNR>1' bin/moonc > moonc.lua + xxd -i moonc.lua > bin/binaries/moonc.h + + - name: Upload headers + uses: actions/upload-artifact@v6 + with: + name: generated-headers + path: bin/binaries/*.h + + linux: + runs-on: ubuntu-latest + needs: generate-headers + + strategy: + matrix: + lua_version: ["5.1.5"] + + steps: + - uses: actions/checkout@master + + - name: Set version suffix + run: | + if [[ "${{ github.ref_type }}" == "tag" ]]; then + echo "VERSION_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV + else + echo "VERSION_SUFFIX=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + fi + + - name: Download headers + uses: actions/download-artifact@v4 + with: + name: generated-headers + path: bin/binaries/ + + - name: Show GCC + run: gcc -v + + - name: Setup Lua + run: | + curl -L -O https://www.lua.org/ftp/lua-${{ matrix.lua_version }}.tar.gz + tar -xzf lua-${{ matrix.lua_version }}.tar.gz + cd lua-${{ matrix.lua_version }}/src && make liblua.a MYCFLAGS=-DLUA_USE_POSIX + + - name: Get LPeg + run: | + curl -L -o lpeg.tar.gz https://www.inf.puc-rio.br/~roberto/lpeg/lpeg-1.0.2.tar.gz + tar -xzf lpeg.tar.gz + + - name: Get Luafilesystem + run: | + curl -L -o luafilesystem.tar.gz https://github.com/keplerproject/luafilesystem/archive/v1_8_0.tar.gz + tar -xzf luafilesystem.tar.gz + + - name: Build + run: | + mkdir -p dist + gcc -static -o dist/moon \ + -Ilua-${{ matrix.lua_version }}/src/ \ + -Ilpeg-1.0.2/ \ + -Ibin/binaries/ \ + bin/binaries/moon.c \ + bin/binaries/moonscript.c \ + lpeg-1.0.2/lpvm.c \ + lpeg-1.0.2/lpcap.c \ + lpeg-1.0.2/lptree.c \ + lpeg-1.0.2/lpcode.c \ + lpeg-1.0.2/lpprint.c \ + lua-${{ matrix.lua_version }}/src/liblua.a \ + -lm -ldl + gcc -static -o dist/moonc \ + -Ilua-${{ matrix.lua_version }}/src/ \ + -Ilpeg-1.0.2/ \ + -Ibin/binaries/ \ + bin/binaries/moonc.c \ + bin/binaries/moonscript.c \ + lpeg-1.0.2/lpvm.c \ + lpeg-1.0.2/lpcap.c \ + lpeg-1.0.2/lptree.c \ + lpeg-1.0.2/lpcode.c \ + lpeg-1.0.2/lpprint.c \ + luafilesystem-1_8_0/src/lfs.c \ + lua-${{ matrix.lua_version }}/src/liblua.a \ + -lm -ldl + + - name: Test run + run: | + dist/moon -h + dist/moon -e 'print "hello world"' + dist/moonc -h + + - name: Upload artifact + uses: actions/upload-artifact@v6 + with: + name: moonscript-${{ env.VERSION_SUFFIX }}-linux-lua${{ matrix.lua_version }} + path: dist/ + + - name: Package for release + if: github.ref_type == 'tag' + run: | + cd dist + tar -czvf ../moonscript-${{ env.VERSION_SUFFIX }}-linux-x86_64.tar.gz * + + - name: Upload to release + if: github.ref_type == 'tag' + uses: softprops/action-gh-release@v2 + with: + files: moonscript-${{ env.VERSION_SUFFIX }}-linux-x86_64.tar.gz + + windows: + runs-on: windows-latest + needs: generate-headers + + strategy: + matrix: + lua_version: ["5.1.5"] + + defaults: + run: + shell: msys2 {0} + + steps: + - uses: actions/checkout@master + + - name: Download headers + uses: actions/download-artifact@v4 + with: + name: generated-headers + path: bin/binaries/ + + - uses: msys2/setup-msys2@v2 + with: + install: gcc make curl zip + + - name: Set version suffix + shell: bash + run: | + if [[ "${{ github.ref_type }}" == "tag" ]]; then + echo "VERSION_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV + else + echo "VERSION_SUFFIX=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + fi + + - name: Show GCC + run: gcc -v + + - name: Setup Lua + run: | + curl -L -O https://www.lua.org/ftp/lua-${{ matrix.lua_version }}.tar.gz + tar -xzf lua-${{ matrix.lua_version }}.tar.gz + cd lua-${{ matrix.lua_version }}/src && make liblua.a + + - name: Get LPeg + run: | + curl -L -o lpeg.tar.gz https://www.inf.puc-rio.br/~roberto/lpeg/lpeg-1.0.2.tar.gz + tar -xzf lpeg.tar.gz + + - name: Get Luafilesystem + run: | + curl -L -o luafilesystem.tar.gz https://github.com/keplerproject/luafilesystem/archive/v1_8_0.tar.gz + tar -xzf luafilesystem.tar.gz + + - name: Build + run: | + mkdir -p dist + gcc -static -o dist/moon.exe \ + -Ilua-${{ matrix.lua_version }}/src/ \ + -Ilpeg-1.0.2/ \ + -Ibin/binaries/ \ + bin/binaries/moon.c \ + bin/binaries/moonscript.c \ + lpeg-1.0.2/lpvm.c \ + lpeg-1.0.2/lpcap.c \ + lpeg-1.0.2/lptree.c \ + lpeg-1.0.2/lpcode.c \ + lpeg-1.0.2/lpprint.c \ + lua-${{ matrix.lua_version }}/src/liblua.a \ + -lm + gcc -static -o dist/moonc.exe \ + -Ilua-${{ matrix.lua_version }}/src/ \ + -Ilpeg-1.0.2/ \ + -Ibin/binaries/ \ + bin/binaries/moonc.c \ + bin/binaries/moonscript.c \ + lpeg-1.0.2/lpvm.c \ + lpeg-1.0.2/lpcap.c \ + lpeg-1.0.2/lptree.c \ + lpeg-1.0.2/lpcode.c \ + lpeg-1.0.2/lpprint.c \ + luafilesystem-1_8_0/src/lfs.c \ + lua-${{ matrix.lua_version }}/src/liblua.a \ + -lm + + - name: Test run + run: | + dist/moon.exe -h + dist/moon.exe -e 'print "hello world"' + dist/moonc.exe -h + + - name: Upload artifact + uses: actions/upload-artifact@v6 + with: + name: moonscript-${{ env.VERSION_SUFFIX }}-windows-lua${{ matrix.lua_version }} + path: dist/ + + - name: Package for release + if: github.ref_type == 'tag' + run: | + cd dist + zip ../moonscript-${{ env.VERSION_SUFFIX }}-windows-x86_64.zip * + + - name: Upload to release + if: github.ref_type == 'tag' + uses: softprops/action-gh-release@v2 + with: + files: moonscript-${{ env.VERSION_SUFFIX }}-windows-x86_64.zip diff --git a/.github/workflows/spec.yml b/.github/workflows/spec.yml new file mode 100644 index 00000000..91707d8c --- /dev/null +++ b/.github/workflows/spec.yml @@ -0,0 +1,31 @@ +name: "spec" + +on: [push, pull_request] + +jobs: + test: + strategy: + fail-fast: false + matrix: + luaVersion: ["5.1", "5.2", "5.3", "5.4", "luajit", "luajit-openresty"] + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@master + + - uses: leafo/gh-actions-lua@master + with: + luaVersion: ${{ matrix.luaVersion }} + + - uses: leafo/gh-actions-luarocks@master + + - name: build + run: | + luarocks install busted + luarocks install loadkit + luarocks make + + - name: test + run: | + busted -o utfTerminal diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..dd9c43dc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,578 @@ + + +# MoonScript v0.5.0 (2016-9-25) + +## Syntax updates + +### Function calls + +Function calls with parentheses can now have free whitespace around the +arguments. Additionally, a line break may be used in place of a comma: + +```moonscript +my_func( + "first arg" + => + print "some func" + + "third arg", "fourth arg" +) +``` + +### Function argument definitions + +Just like the function all update, function argument definitions have no +whitespace restrictions between arguments, and line breaks can be used to +separate arguments: + + +```moonscript +some_func = ( + name + type + action="print" +) => + print name, type, action +``` + +## Additions + +* `elseif` can be used part of an `unless` block (nymphium) +* `unless` conditional expression can contain an assignment like an `if` statement (#251) +* Lua 5.3 bitwise operator support (nymphium) (Kawahara Satoru) +* Makefile is Lua version agnostic (nymphium) +* Lint flag can be used with `moonc` watch mode (ChickenNuggers) +* Lint exits with status 1 if there was a problem detected (ChickenNuggers) +* Compiler can be used with lulpeg + +## Bug Fixes + +* Slice boundaries can be full expressions (#233) +* Destructure works when used as loop variable in comprehension (#236) +* Proper name local hoisting works for classes again (#287) +* Quoted table key literals can now be parsed when table declaration is in single line (#286) +* Fix an issue where `else` could get attached to wrong `if` statement (#276) +* Loop variables will no longer overwrite variables of the same name in the same scope (egonSchiele) +* A file being deleted will not crash polling watch mode (ChickenNuggers) +* The compiler will not try to compile a directory ending in `.moon` (Gskartwii) +* alt_getopt import works with modern version (Jon Allen) +* Code coverage not being able to find file from chunk name + + +# MoonScript v0.4.0 (2015-12-06) + +## Changes to `super` + +`super` now looks up the parent method via the class reference, instead of a +(fixed) closure to the parent class. + +Given the following code: + +```moonscript +class MyThing extends OtherThing + the_method: => + super! +``` + +In the past `super` would compile to something like this: + +```lua +_parent_0.the_method(self) +``` + +Where `_parent_0` was an internal local variable that contains a reference to +the parent class. Because the reference to parent is an internal local +variable, you could never swap out the parent unless resorting to the debug +library. + +This version will compile to: + +```lua +_class_0.__parent.__base.the_method(self) +``` + +Where `_class_0` is an internal local variable that contains the current class (`MyThing`). + +Another difference is that the instance method is looked up on `__base` instead +of the class. The old variation would trigger the metamethod for looking up on +the instance, but a class method of the same name could conflict, take +precedence, and be retuned instead. By referencing `__base` directly we avoid +this issue. + +### Super on class methods + +`super` can now be used on class methods. It works exactly as you would expect. + +```moonscript +class MyThing extends OtherThing + @static_method: => + print super! +``` + +Calling `super` will compile to: + +```moonscript +_class_0.__parent.static_method(self) +``` + +### Improved scoping for super + +The scoping of super is more intelligent. You can warp your methods in other +code and `super` will still generate correctly. For example, syntax like this +will now work as expected: + +```moonscript +class Sub extends Base + value: if debugging + => super! + 100 + else + => super! + 10 + + other_value: some_decorator { + the_func: => + super! + } +``` + +`super` will refer to the lexically closest class declaration to find the name +of the method it should call on the parent. + +## Bug Fixes + +* Nested `with` blocks used incorrect ref (#214 by @geomaster) +* Lua quote string literals had wrong precedence (#200 by @nonchip) +* Returning from `with` block would generate two `return` statements (#208) +* Including `return` or `break` in a `continue` wrapped block would generate invalid code (#215 #190 #183) + +## Other + +* Refactor transformer out into multiple files +* `moon` command line script rewritten in MoonScript +* `moonscript.parse.build_grammar` function for getting new instance of parser grammar +* Chain AST updated to be simpler + +# MoonScript v0.3.2 (2015-6-01) + +## Bug Fixes + +* `package.moonpath` geneator does not use paths that don't end in `lua` + +# MoonScript v0.3.1 (2015-3-07) + +## Bug Fixes + +* Fixed a bug where an error from a previous compile would prevent the compiler from running again + +# MoonScript v0.3.0 (2015-2-28) + +## New Features + +* New [unused assignment linter](http://moonscript.org/reference/command_line.html#unused_variable_assigns) finds assignments that are never referenced after being defined. + +## Parsing Updates + +Whitespace parsing has been relaxed in a handful of locations: + +* You can put unrestricted whitespace/newlines after operator in a binary operator before writing the right hand side. The following are now valid: + +```moonscript +x = really_long_function! + + 2304 + +big_math = 123 / + 12 - + 43 * 17 + + +bool_exp = nice_shirt and cool_shoes or + skateboard and shades +``` + +* You can put unrestricted whitespace/newlines immediately after an opening parenthesis, and immediately before closing parenthesis. The following are now valid: + +```moonscript +hello = 100 + ( + var * 0.23 +) - 15 + + +funcall( + "height", "age", "weight" +) + + +takes_two_functions (-> + print "hello" +), -> + print "world" +``` + +* You can put unrestricted whitespace/newlines immediately after a `:` when defining a table literal. The following is now valid: + +```moonscript +x = { + hello: + call_a_function "football", "hut" +} +``` + +## Code Generation + +* Single value `import`/`destructure` compiles directly into single assignment + +## Bug Fixes + +* Some `moonc` command line flags were being ignored +* Linter would not report global reference when inside self assign in table +* Fixed an issue where parser would crash in Lpeg 0.12 when compiling hundreds of times per process + +## Misc + +* MoonScript parser now written in MoonScript + +# MoonScript v0.2.6 (2014-6-18) + +## Bug Fixes + +* Fixes to posmap generation for multi-line mappings and variable declarations +* Prefix file name with `@` when loading code so stack traces tread it as file +* Fix bug where `moonc` couldn't work with absolute paths +* Improve target file path generation for `moonc` + +# MoonScript v0.2.5 (2014-3-5) + +## New Things + +* New [code coverage tool](http://moonscript.org/reference/#code_coverage) built into `moonc` +* New [linting tool](http://moonscript.org/reference/#linter) built into `moonc`, identifies global variable references that don't pass whitelist +* Numbers can have `LL` and `ULL` suffixes for LuaJIT + +## Bug Fixes + +* Error messages from `moonc` are written to standard error +* Moonloader correctly throws error when moon file can't be parsed, instead of skipping the module +* Line number rewriting is no longer incorrectly offset due to multiline strings + +## Code Generation + +Bound functions will avoid creating an anonymous function unless necessary. + +```moonscript +x = hello\world +``` + +**Before:** + +```lua +local x = (function() + local _base_0 = hello + local _fn_0 = _base_0.world + return function(...) + return _fn_0(_base_0, ...) + end +end)() +``` + +**After:** + +```lua +local x +do + local _base_0 = hello + local _fn_0 = _base_0.world + x = function(...) + return _fn_0(_base_0, ...) + end +end +``` + +Explicit return statement now avoids creating anonymous function for statements +where return can be cascaded into the body. + +```moon +-> + if test1 + return [x for x in *y] + + if test2 + return if true + "yes" + else + "no" + + false +``` + + +**Before:** + +```lua +local _ +_ = function() + if test1 then + return (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = y + for _index_0 = 1, #_list_0 do + local x = _list_0[_index_0] + _accum_0[_len_0] = x + _len_0 = _len_0 + 1 + end + return _accum_0 + end)() + end + if test2 then + return (function() + if true then + return "yes" + else + return "no" + end + end)() + end + return false +end +``` + +**After:** + +```lua +local _ +_ = function() + if test1 then + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = y + for _index_0 = 1, #_list_0 do + local x = _list_0[_index_0] + _accum_0[_len_0] = x + _len_0 = _len_0 + 1 + end + return _accum_0 + end + if test2 then + if true then + return "yes" + else + return "no" + end + end + return false +end +``` + + + +# MoonScript v0.2.4 (2013-07-02) + +## Changes + +* The way the subtraction operator works has changed. There was always a little confusion as to the rules regarding whitespace around it and it was recommended to always add whitespace around the operator when doing subtraction. Not anymore. Hopefully it now [works how you would expect](http://moonscript.org/reference/#considerations). (`a-b` compiles to `a - b` and not `a(-b)` anymore). +* The `moon` library is no longer sets a global variable and instead returns the module. Your code should now be: + +```moonscript +moon = require "moon" +``` + +* Generated code will reuse local variables when appropriate. Local variables are guaranteed to not have side effects when being accessed as opposed to expressions and global variables. MoonScript will now take advantage of this and reuse those variable without creating and copying to a temporary name. +* Reduced the creation of anonymous functions that are called immediately. + MoonScript uses this technique to convert a series of statements into a single expression. It's inefficient because it allocates a new function object and has to do a function call. It also obfuscates stack traces. MoonScript will flatten these functions into the current scope in a lot of situations now. +* Reduced the amount of code generated for classes. Parent class code it left out if there is no parent. + +## New Things + +* You can now put line breaks inside of string literals. It will be replaced with `\n` in the generated code. + +```moonscript +x = "hello +world" +``` + +* Added `moonscript.base` module. It's a way of including the `moonscript` module without automatically installing the moonloader. +* You are free to use any whitespace around the name list in an import statement. It has the same rules as an array table, meaning you can delimit names with line breaks. + +```moonscript +import a, b + c, d from z +``` + +* Added significantly better tests. Previously the testing suite would only verify that code compiled to an expected string. Now there are unit tests that execute the code as well. This will make it easier to change the generated output while still guaranteeing the semantics are the same. + +## Bug Fixes + +* `b` is not longer treated as self assign in `{ a : b }` +* load functions will return `nil` instead of throwing error, as described in documentation +* fixed an issue with `moon.mixin` where it did not work as described + + +# MoonScript v0.2.3-2 (2013-01-29) + +Fixed bug with moonloader not loading anything + +# MoonScript v0.2.3 (2013-01-24) + +## Changes + +* For loops when used as expressions will no longer discard nil values when accumulating into an array table. **This is a backwards incompatible change**. Instead you should use the `continue` keyword to filter out iterations you don't want to keep. [Read more here](https://github.com/leafo/moonscript/issues/66). +* The `moonscript` module no longer sets a global value for `moonscript` and instead returns it. You should update your code: + +```moonscript +moonscript = require "moonscript" +``` + +## New Things + +* Lua 5.2 Support. The compiler can now run in either Lua 5.2 and 5.1 +* A switch `when` clause [can take multiple values](http://moonscript.org/reference/#switch), comma separated. +* Added [destructuring assignment](http://moonscript.org/reference/#destructuring_assignment). +* Added `local *` (and `local ^`) for [hoisting variable declarations](http://moonscript.org/reference/#local_statement) in the current scope +* List comprehensions and line decorators now support numeric loop syntax + +## Bug Fixes + +* Numbers that start with a dot, like `.03`, are correctly parsed +* Fixed typo in `fold` library function +* Fix declaration hoisting inside of class body, works the same as `local *` now + +## Other Stuff + +MoonScript has [made its way into GitHub](https://github.com/github/linguist/pull/246). `.moon` files should start to be recognized in the near future. + + +# MoonScript v0.2.2 (2012-11-03) + +## Changes + +* Compiled [files will now implicitly return](http://moonscript.org/reference/#implicit_returns_on_files) their last statement. Be careful, this might change what `require` returns. + +## New Things + +### The Language + +* Added [`continue` keyword](http://moonscript.org/reference/#continue) for skipping the current iteration in a loop. +* Added [string interpolation](http://moonscript.org/reference/#string_interpolation). +* Added [`do` expression and block](http://moonscript.org/reference/#do). +* Added `unless` as a block and line decorator. Is the inverse of `if`. +* Assignment can be used in an [`if` statement's expression](http://moonscript.org/reference/#with_assignment). +* Added `or=` and `and=` operators. +* `@@` can be prefixed in front of a name to access that name within `self.__class` +* `@` and `@@` can be [used as values](http://moonscript.org/reference/#_and__values) to reference `self` and `self.__class`. +* In class declarations it's possible to [assign to the class object](http://moonscript.org/reference/#class_variables) instead of the instance metatable by prefixing the key with `@`. +* Class methods can access [locals defined within the body](http://moonscript.org/reference/#class_declaration_statements) of the class declaration. +* Super classes are [notified when they are extended](http://moonscript.org/reference/#inheritance) from with an `__inherited` callback. +* Classes can now [implicitly return and be expressions](http://moonscript.org/reference/#anonymous_classes). +* `local` keyword returns, can be used for forward declaration or shadowing a variable. +* String literals can be used as keys in [table literals](http://moonscript.org/reference/#table_literals). +* Call methods on string literals without wrapping in parentheses: `"hello"\upper!` +* Table comprehensions can return a single value that is unpacked into the key and value. +* The expression in a [`with` statement can now be an assignment](http://moonscript.org/reference/#with_statement), to give a name to the expression that is being operated on. + + +### The API + +* The `load` functions can take an [optional last argument of options](http://moonscript.org/reference/#load_functions). + +### The Tools + +* The [online compiler](http://moonscript.org/compiler/) now runs through a web service instead of emscripten, should work reliably on any computer now. +* [Windows binaries](http://moonscript.org/bin/) have been updated. + +## Bug Fixes + +* Significantly improved the [line number rewriter](http://moonscript.org/reference/#error_rewriting). It should now accurately report all line numbers. +* Generic `for` loops correctly parse for multiple values as defined in Lua. +* Update expressions don't fail with certain combinations of precedence. +* All statements/expressions are allowed in a class body, not just some. +* `x = "hello" if something` will extract the declaration of `x` if it's not in scope yet. Preventing an impossible to access variable from being created. +* varargs, `...`, correctly bubble up through automatically generated anonymous functions. +* Compiler doesn't crash if you try to assign something that isn't assignable. +* Numerous other small fixes. See [commit log](https://github.com/leafo/moonscript/commits/master). + + +# MoonScript v0.2.0 (2011-12-12) + +## Changes + +* `,` is used instead of `:` for delimiting table slice parts. +* Class objects store the metatable of their instances in `__base`. `__base` is also used in inheritance when chaining metatables. + +## New Things + +### The Language + +* Added [key-value table comprehensions][4]. +* Added a [`switch` statement][7]. +* The body of a class can contain arbitrary expressions in addition to assigning properties. `self` in this scope refers to the class itself. +* Class objects themselves support accessing the properties of the superclass they extend (like instances). +* Class objects store their name as a string in the `__name` property. +* [Enhanced the `super` keyword][8] in instance methods. +* Bound methods can be created for an object by using `object\function_name` as a value. Called [function stubs][6]. +* Added `export *` statement to export all assigned names following the statement. +* Added `export ^` statement to export all assigning names that begin with a capital letter following the statement. +* `export` can be used before any assignment or class declaration to export just that assignment (or class declaration). +* Argument lists can be broken up over several lines with trailing comma. +* `:hello` is short hand for `hello: hello` inside of table literal. +* Added `..=` for string concatenation. +* `table.insert` no longer used to build accumlated values in comprehensions. + +### The API + +* Added `loadfile`, `loadstring`, and `dofile` functions to `moonscript` module to load/run MoonScript code. +* Added `to_lua` function to `moonscript` module to convert a MoonScript code string to Lua string. + +### The Tools + +* Created [prebuilt MoonScript Windows executables][2]. +* Wrote a [Textmate/Sublime Text bundle][9]. +* Wrote a [SciTE syntax highlighter with scintillua][10]. +* Created a [SciTE package for Windows][11] that has everything configured. +* Created an [online compiler and snippet site][12] using [emscripten][13]. +* Watch mode works on all platforms now. Uses polling if `inotify` is not + available. + +### Standard Library + +I'm now including a small set of useful functions in a single module called `moon`: + +```moonscript +require "moon" +``` + +Documentation is [available here][3]. + +## Bug Fixes + +* Windows line endings don't break the parser. +* Fixed issues when using `...` within comprehensions when the compiled code uses an intermediate function in the output. +* Names whose first characters happen to be a keyword don't break parser. +* Return statement can have no arguments +* argument names prefixed with `@` in function definitions work outside of classes work with default values. +* Fixed parse issues with the shorthand values within a `with` block. +* Numerous other small fixes. See [commit log][5]. + +## Other Stuff + +Since the first release, I've written one other project in MoonScript (other than the compiler). It's a static site generator called [sitegen][15]. It's what I now use to generate all of my project pages and this blog. + +# MoonScript 0.1.0 (2011-08-12) + +Initial release + + + [1]: http://moonscript.org + [2]: http://moonscript.org/bin/ + [3]: http://moonscript.org/reference/standard_lib.html + [4]: http://moonscript.org/reference/#table_comprehensions + [5]: https://github.com/leafo/moonscript/commits/master + [6]: http://moonscript.org/reference/#function_stubs + [7]: http://moonscript.org/reference/#switch + [8]: http://moonscript.org/reference/#super + [9]: https://github.com/leafo/moonscript-tmbundle + [10]: https://github.com/leafo/moonscript/tree/master/extra/scintillua + [11]: http://moonscript.org/scite/ + [12]: http://moonscript.org/compiler/ + [13]: http://emscripten.org + [14]: http://twitter.com/moonscript + [15]: http://leafo.net/sitegen/ + diff --git a/Makefile b/Makefile index 30b30229..c3c0868b 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,122 @@ +LUA ?= lua5.1 +LUA_VERSION = $(shell $(LUA) -e 'print(_VERSION:match("%d%.%d"))') +LUAROCKS = luarocks --lua-version=$(LUA_VERSION) +LUA_PATH_MAKE = $(shell $(LUAROCKS) path --lr-path);./?.lua;./?/init.lua +LUA_CPATH_MAKE = $(shell $(LUAROCKS) path --lr-cpath);./?.so -test:: - busted -p "_spec.moon$$" +LUA_SRC_VERSION ?= 5.1.5 +LPEG_VERSION ?= 1.0.2 +LFS_VERSION ?= 1_8_0 -local: - luarocks make --local moonscript-dev-1.rockspec +.PHONY: test local build watch lint count show test_binary -global: - sudo luarocks make moonscript-dev-1.rockspec +build: + LUA_PATH='$(LUA_PATH_MAKE)' LUA_CPATH='$(LUA_CPATH_MAKE)' $(LUA) bin/moonc moon/ moonscript/ + echo "#!/usr/bin/env lua" > bin/moon + $(LUA) bin/moonc -p bin/moon.moon >> bin/moon + echo "-- vim: set filetype=lua:" >> bin/moon -compile: - bin/moonc moon/ moonscript/ - -compile_global: +# This will rebuild MoonScript from the (hopefully working) system installation of moonc +build_from_system: moonc moon/ moonscript/ + echo "#!/usr/bin/env lua" > bin/moon + moonc -p bin/moon.moon >> bin/moon + echo "-- vim: set filetype=lua:" >> bin/moon + +show: + # LUA $(LUA) + # LUA_VERSION $(LUA_VERSION) + # LUAROCKS $(LUAROCKS) + # LUA_PATH_MAKE $(LUA_PATH_MAKE) + # LUA_CPATH_MAKE $(LUA_CPATH_MAKE) + +test: build + busted + +build_test_outputs: build + BUILD=1 busted spec/lang_spec.moon + +local: build + LUA_PATH='$(LUA_PATH_MAKE)' LUA_CPATH='$(LUA_CPATH_MAKE)' $(LUAROCKS) make --local moonscript-dev-1.rockspec watch: moonc moon/ moonscript/ && moonc -w moon/ moonscript/ + +lint: + moonc -l moonscript moon bin + +count: + wc -l $$(git ls-files | grep 'moon$$') | sort -n | tail + +# Binary build targets for local verification (Linux only) +lua_modules: + luarocks install argparse --tree=lua_modules + +lua-$(LUA_SRC_VERSION)/src/liblua.a: + curl -L -O https://www.lua.org/ftp/lua-$(LUA_SRC_VERSION).tar.gz + tar -xzf lua-$(LUA_SRC_VERSION).tar.gz + cd lua-$(LUA_SRC_VERSION)/src && make liblua.a MYCFLAGS=-DLUA_USE_POSIX + +lpeg-$(LPEG_VERSION)/lptree.c: + curl -L -o lpeg.tar.gz https://www.inf.puc-rio.br/~roberto/lpeg/lpeg-$(LPEG_VERSION).tar.gz + tar -xzf lpeg.tar.gz + +luafilesystem-$(LFS_VERSION)/src/lfs.c: + curl -L -o luafilesystem.tar.gz https://github.com/keplerproject/luafilesystem/archive/v$(LFS_VERSION).tar.gz + tar -xzf luafilesystem.tar.gz + +bin/binaries/moonscript.h: moonscript/*.lua moon/*.lua + bin/splat.moon -l moonscript moonscript moon > moonscript.lua + xxd -i moonscript.lua > $@ + rm moonscript.lua + +bin/binaries/moon.h: bin/moon + awk 'FNR>1' bin/moon > moon.lua + xxd -i moon.lua > $@ + rm moon.lua + +bin/binaries/argparse.h: lua_modules + bin/splat.moon --strip-prefix -l argparse $$(find lua_modules/share/lua -name "argparse.lua" -exec dirname {} \; | head -1) > bin/binaries/argparse.lua + xxd -i -n argparse_lua bin/binaries/argparse.lua > $@ + +bin/binaries/moonc.h: bin/moonc + awk 'FNR>1' bin/moonc > moonc.lua + xxd -i moonc.lua > $@ + rm moonc.lua + +dist/moon: lua-$(LUA_SRC_VERSION)/src/liblua.a lpeg-$(LPEG_VERSION)/lptree.c bin/binaries/moonscript.h bin/binaries/moon.h bin/binaries/argparse.h bin/binaries/moon.c bin/binaries/moonscript.c + mkdir -p dist + gcc -static -o dist/moon \ + -Ilua-$(LUA_SRC_VERSION)/src/ \ + -Ilpeg-$(LPEG_VERSION)/ \ + -Ibin/binaries/ \ + bin/binaries/moon.c \ + bin/binaries/moonscript.c \ + lpeg-$(LPEG_VERSION)/lpvm.c \ + lpeg-$(LPEG_VERSION)/lpcap.c \ + lpeg-$(LPEG_VERSION)/lptree.c \ + lpeg-$(LPEG_VERSION)/lpcode.c \ + lpeg-$(LPEG_VERSION)/lpprint.c \ + lua-$(LUA_SRC_VERSION)/src/liblua.a \ + -lm -ldl + +dist/moonc: lua-$(LUA_SRC_VERSION)/src/liblua.a lpeg-$(LPEG_VERSION)/lptree.c luafilesystem-$(LFS_VERSION)/src/lfs.c bin/binaries/moonscript.h bin/binaries/moonc.h bin/binaries/argparse.h bin/binaries/moonc.c bin/binaries/moonscript.c + mkdir -p dist + gcc -static -o dist/moonc \ + -Ilua-$(LUA_SRC_VERSION)/src/ \ + -Ilpeg-$(LPEG_VERSION)/ \ + -Ibin/binaries/ \ + bin/binaries/moonc.c \ + bin/binaries/moonscript.c \ + lpeg-$(LPEG_VERSION)/lpvm.c \ + lpeg-$(LPEG_VERSION)/lpcap.c \ + lpeg-$(LPEG_VERSION)/lptree.c \ + lpeg-$(LPEG_VERSION)/lpcode.c \ + lpeg-$(LPEG_VERSION)/lpprint.c \ + luafilesystem-$(LFS_VERSION)/src/lfs.c \ + lua-$(LUA_SRC_VERSION)/src/liblua.a \ + -lm -ldl + +test_binary: dist/moon + dist/moon diff --git a/README.md b/README.md index abfd3d39..b5bb5f92 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,45 @@ # MoonScript +[![MoonScript](https://leafo.net/dump/sailormoonscript.png)](https://moonscript.org) + + +[![spec](https://github.com/leafo/moonscript/workflows/spec/badge.svg)](https://github.com/leafo/moonscript/actions?query=workflow%3Aspec) [![Build status](https://ci.appveyor.com/api/projects/status/f5prpi4wvytul290/branch/binaries?svg=true)](https://ci.appveyor.com/project/leafo/moonscript/branch/binaries) + + +[![](https://leafo.net/dump/twitch-banner.svg)](https://www.twitch.tv/moonscript) + MoonScript is a programmer friendly language that compiles into -[Lua](http://www.lua.org/). It gives you the power of the fastest scripting -language combined with a rich set of features. It runs on Lua 5.1 and 5.2. +[Lua](https://www.lua.org/). It gives you the power of the fastest scripting +language combined with a rich set of features. It runs on Lua 5.1 and above, +including alternative runtimes like LuaJIT. + +See . + +Online demo/compiler at . + +## Join Our Community + +We have a Discord for those interested in MoonScript and related projects. You can join us here: -See . +## Contributing -Online demo/compiler at . +MoonScript is a self-hosted compiler, meaning it's written in MoonScript itself. When contributing, please follow the following guidelines: + +1. Edit `.moon` files, never modify the alongside `.lua` files directly +2. After making changes to `.moon` files, run the compiler to regenerate the corresponding `.lua` files +3. Both `.moon` and `.lua` files are included in the repository to ensure that: + - Users can install and use MoonScript without having to compile it themselves + - The compiler bootstrapping process works consistently + +It's helpful to have a separate installation of MoonScript should you break +something and you need to re-build the MoonScript with a working version of +MoonScript. You can check out the repo in another directory, or install it +using LuaRocks to have a separate working version. ## Running Tests -Tests are written in MoonScript and use [Busted](http://olivinelabs.com/busted/). -In order to run the tests you must have MoonScript installed. +Tests are written in MoonScript and use [Busted](https://olivinelabs.com/busted/). +In order to run the tests you must have MoonScript and [Loadkit](https://github.com/leafo/loadkit) installed. To run tests, execute from the root directory: @@ -19,9 +47,27 @@ To run tests, execute from the root directory: busted ``` +Writing specs is a bit more complicated. Check out [the spec writing guide](spec/README.md). + +## Binaries + +Precompiled versions of MoonScript are provided for Windows. You can find them +in the [GitHub releases page](https://github.com/leafo/moonscript/releases). +(Scroll down to the `win32-` tags. + +The build code can be found in the [`binaries` +branch](https://github.com/leafo/moonscript/tree/binaries) + +## Editor Support + +* [Vim](https://github.com/leafo/moonscript-vim) +* [Textadept](https://github.com/leafo/moonscript-textadept) +* [Sublime/Textmate](https://github.com/leafo/moonscript-tmbundle) +* [Emacs](https://github.com/k2052/moonscript-mode) + ## License (MIT) -Copyright (C) 2013 by Leaf Corcoran +Copyright (C) 2025 by Leaf Corcoran Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -39,4 +85,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/bin/binaries/moon.c b/bin/binaries/moon.c new file mode 100644 index 00000000..f668d436 --- /dev/null +++ b/bin/binaries/moon.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +#include "moon.h" // the CLI script + +// from moonscript.c +extern int luaopen_moonscript(lua_State *l); + +int main(int argc, char **argv) { + lua_State *l = luaL_newstate(); + luaL_openlibs(l); + + // Load moonscript (this also loads lpeg) + luaopen_moonscript(l); + lua_pop(l, 1); + + // Set up arg table + lua_newtable(l); + lua_pushstring(l, "moon"); + lua_rawseti(l, -2, -1); + for (int i = 0; i < argc; i++) { + lua_pushstring(l, argv[i]); + lua_rawseti(l, -2, i); + } + lua_setglobal(l, "arg"); + + // Load and execute the moon CLI script + if (luaL_loadbuffer(l, (const char *)moon_lua, moon_lua_len, "moon") != 0) { + fprintf(stderr, "Failed to load moon: %s\n", lua_tostring(l, -1)); + return 1; + } + if (lua_pcall(l, 0, 0, 0) != 0) { + fprintf(stderr, "Error: %s\n", lua_tostring(l, -1)); + return 1; + } + + lua_close(l); + return 0; +} diff --git a/bin/binaries/moonc.c b/bin/binaries/moonc.c new file mode 100644 index 00000000..9cd9a67b --- /dev/null +++ b/bin/binaries/moonc.c @@ -0,0 +1,55 @@ +#include +#include +#include +#include + +#include "moonc.h" // the CLI script + +// from moonscript.c +extern int luaopen_moonscript(lua_State *l); + +// from lfs.c +extern int luaopen_lfs(lua_State *l); + +int main(int argc, char **argv) { + lua_State *l = luaL_newstate(); + luaL_openlibs(l); + + // Load moonscript (this also loads lpeg and argparse) + luaopen_moonscript(l); + lua_pop(l, 1); + + // Load luafilesystem and register it in package.loaded + int nresults = luaopen_lfs(l); + if (nresults > 0) { + lua_getglobal(l, "package"); + lua_getfield(l, -1, "loaded"); + lua_pushvalue(l, -3); // push lfs table + lua_setfield(l, -2, "lfs"); + lua_pop(l, 2); // pop loaded, package + } + lua_pop(l, nresults); + + // Set up arg table + lua_newtable(l); + lua_pushstring(l, "moonc"); + lua_rawseti(l, -2, -1); + for (int i = 0; i < argc; i++) { + lua_pushstring(l, argv[i]); + lua_rawseti(l, -2, i); + } + lua_setglobal(l, "arg"); + + // Load and execute the moonc CLI script + if (luaL_loadbuffer(l, (const char *)moonc_lua, moonc_lua_len, "moonc") != 0) { + fprintf(stderr, "Failed to load moonc: %s\n", lua_tostring(l, -1)); + return 1; + } + if (lua_pcall(l, 0, 0, 0) != 0) { + fprintf(stderr, "Error: %s\n", lua_tostring(l, -1)); + return 1; + } + + lua_close(l); + return 0; +} diff --git a/bin/binaries/moonscript.c b/bin/binaries/moonscript.c new file mode 100644 index 00000000..606242dd --- /dev/null +++ b/bin/binaries/moonscript.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include + +#include "moonscript.h" +#include "argparse.h" + +// put whatever is on top of stack into package.loaded under name if something +// is not already there +void setloaded(lua_State* l, const char* name) { + int top = lua_gettop(l); + lua_getglobal(l, "package"); + lua_getfield(l, -1, "loaded"); + lua_getfield(l, -1, name); + if (lua_isnil(l, -1)) { + lua_pop(l, 1); + lua_pushvalue(l, top); + lua_setfield(l, -2, name); + } + + lua_settop(l, top); +} + +extern int luaopen_lpeg(lua_State *l); + +LUALIB_API int luaopen_moonscript(lua_State *l) { + luaopen_lpeg(l); + setloaded(l, "lpeg"); + + // Load argparse (splat output sets up package.preload) + if (luaL_loadbuffer(l, (const char *)argparse_lua, argparse_lua_len, "argparse.lua") == 0) { + lua_call(l, 0, 0); + } + + if (luaL_loadbuffer(l, (const char *)moonscript_lua, moonscript_lua_len, "moonscript.lua") == 0) { + lua_call(l, 0, 1); + return 1; + } + return 0; +} diff --git a/bin/moon b/bin/moon index b6ef27fe..759cae8a 100755 --- a/bin/moon +++ b/bin/moon @@ -1,116 +1,143 @@ #!/usr/bin/env lua - -require "alt_getopt" -local moonscript = require "moonscript.base" - -local util = require "moonscript.util" -local errors = require "moonscript.errors" - +local argparse = require("argparse") +local moonscript = require("moonscript.base") +local util = require("moonscript.util") +local errors = require("moonscript.errors") local unpack = util.unpack - --- moonloader and repl -local opts, ind = alt_getopt.get_opts(arg, "cvhd", { version = "v", help = "h" }) - -local help = [=[Usage: %s [options] [script [args]] - - -h Print this message - -d Disable stack trace rewriting - -c Collect and print code coverage - -v Print version -]=] - -local function print_err(...) - local msg = table.concat({...}, "\t") - io.stderr:write(msg .. "\n") +local argparser = argparse()({ + name = "moon" +}) +argparser:argument("script"):args("?") +argparser:argument("args"):args("*") +argparser:flag("--coverage -c", "Collect and print code coverage") +argparser:flag("-d", "Disable stack trace rewriting") +argparser:option("--execute -e", "Execute MoonScript code string") +argparser:flag("--version -v", "Print version information") +local base = 0 +local _list_0 = arg +for _index_0 = 1, #_list_0 do + local flag = _list_0[_index_0] + base = base + 1 + if flag:sub(1, 1) ~= "-" then + break + end end - -local function print_help(err) - if err then print("Error: "..err) end - print(help:format(arg[0])) - os.exit() -end - -if opts.h then print_help() end - -if opts.v then - local v = require "moonscript.version" - v.print_version() - os.exit() -end - -local script_fname = arg[ind] -if not script_fname then - print_help("repl not yet supported") - return -end - -local new_arg = { - [-1] = arg[0], - [0] = arg[ind], - select(ind + 1, unpack(arg)) +local args = { + unpack(arg, 1, base) } - -local moonscript_chunk, lua_parse_error -local passed, err = pcall(function() - moonscript_chunk, lua_parse_error = moonscript.loadfile(script_fname, { implicitly_return_root = false }) -end) - -if not passed then - print_err(err) - os.exit(1) -end - -if not moonscript_chunk then - if lua_parse_error then - print_err(lua_parse_error) - else - print_err("Can't find file: " .. script_fname) - end - os.exit(1) -end - -util.getfenv(moonscript_chunk).arg = new_arg - -local function run_chunk() - moonscript.insert_loader() - moonscript_chunk(unpack(new_arg)) - moonscript.remove_loader() +local opts = argparser:parse(args) +local print_err +print_err = function(...) + local msg = table.concat((function(...) + local _accum_0 = { } + local _len_0 = 1 + local _list_1 = { + ... + } + for _index_0 = 1, #_list_1 do + local v = _list_1[_index_0] + _accum_0[_len_0] = tostring(v) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(...), "\t") + return io.stderr:write(msg .. "\n") end - -if not opts.d then - local err, trace - local cov - - if opts.c then - local coverage = require "moonscript.cmd.coverage" - cov = coverage.CodeCoverage() - cov:start() - end - - xpcall(run_chunk, function(_err) - err = _err - trace = debug.traceback("", 2) - end) - - if err then - local truncated = errors.truncate_traceback(util.trim(trace)) - local rewritten = errors.rewrite_traceback(truncated, err) - - if rewritten then - print_err(rewritten) - else - -- faield to rewrite, show original - print_err(table.concat({ - err, - util.trim(trace) - }, "\n")) - end - else - if cov then - cov:stop() - cov:print_results() - end - end -else - run_chunk() +local run +run = function() + if opts.version then + require("moonscript.version").print_version() + os.exit() + end + args = { + unpack(arg, base + 1) + } + args[-1] = arg[0] + local moonscript_chunk, lua_parse_error + if opts.execute then + args[0] = "-e" + local passed, err = pcall(function() + moonscript_chunk, lua_parse_error = moonscript.loadstring(opts.execute, "=(command line)", { + implicitly_return_root = false + }) + end) + if not (passed) then + print_err(err) + os.exit(1) + end + if not (moonscript_chunk) then + if lua_parse_error then + print_err(lua_parse_error) + else + print_err("Failed to compile: " .. tostring(opts.execute)) + end + os.exit(1) + end + else + local script_fname = opts.script + if not (script_fname) then + print_err("Usage: moon [options] script [args]") + print_err("Use 'moon --help' for more information.") + os.exit(1) + end + args[0] = script_fname + local passed, err = pcall(function() + moonscript_chunk, lua_parse_error = moonscript.loadfile(script_fname, { + implicitly_return_root = false + }) + end) + if not (passed) then + print_err(err) + os.exit(1) + end + if not (moonscript_chunk) then + if lua_parse_error then + print_err(lua_parse_error) + else + print_err("Can't file file: " .. tostring(script_fname)) + end + os.exit(1) + end + end + util.getfenv(moonscript_chunk).arg = args + local run_chunk + run_chunk = function() + moonscript.insert_loader() + moonscript_chunk(unpack(args)) + return moonscript.remove_loader() + end + if opts.d then + return run_chunk() + end + local err, trace, cov + if opts.coverage then + print("starting coverage") + local coverage = require("moonscript.cmd.coverage") + cov = coverage.CodeCoverage() + cov:start() + end + xpcall(run_chunk, function(_err) + err = _err + trace = debug.traceback("", 2) + end) + if err then + local truncated = errors.truncate_traceback(util.trim(trace)) + local rewritten = errors.rewrite_traceback(truncated, err) + if rewritten then + print_err(rewritten) + else + print_err(table.concat({ + err, + util.trim(trace) + }, "\n")) + end + return os.exit(1) + else + if cov then + cov:stop() + return cov:print_results() + end + end end +return run() +-- vim: set filetype=lua: diff --git a/bin/moon-tags b/bin/moon-tags new file mode 100755 index 00000000..5f657d55 --- /dev/null +++ b/bin/moon-tags @@ -0,0 +1,202 @@ +#!/usr/bin/env moon + +HEADER = [[ +!_TAG_FILE_FORMAT 2 /extended format/ +!_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/ +!_TAG_PROGRAM_AUTHOR leaf corcoran /leafot@gmail.com/ +!_TAG_PROGRAM_NAME MoonTags // +!_TAG_PROGRAM_URL https://github.com/leafo/moonscript /GitHub repository/ +!_TAG_PROGRAM_VERSION 0.0.1 // +]] + +-- see `ctags --list-kinds` for examples of kinds +-- see `ctags --list-fields` + +argparse = require "argparse" + +parser = argparse "moon-tags", "Generate ctags style tags file for MoonScript files" +parser\argument("files", "MoonScript files to generate tags for")\args "+" +parser\flag "--include-line", "Include line number field for each tag" +parser\flag "--lapis", "Support extracting lapis routes" +parser\flag "--no-header", "Don't print the header" + +args = parser\parse [v for _, v in ipairs _G.arg] + +TAGS = {} -- the final output of tags + +literals = require "moonscript.parse.literals" +import Indent, simple_string from require "moonscript.parse.util" + +import P, S, C, Cc, Cg, Cb, Ct, Cs, V from require "lpeg" + +-- consome the rest of the file +until_end = (1 - literals.Stop)^0 +whitespace = S"\t " -- not including newline +ignore_line = Ct until_end -- tag it for empty line + +-- NOTE: we disable interpolation parsing since we don't have full grammar +SingleString = simple_string "'", false +DoubleString = simple_string '"', false +String = SingleString + DoubleString + +-- we have to do this double Ct to capture both the full line and the grouped captures +Type = (name) -> Cg Cc(name), "type" +Line = (type_name, p) -> Ct C Ct Cg(Indent, "indent") * p * Type type_name + +method = P { P"=>" + P(1 - literals.Stop) * V(1) } +func = P { P"->" + P"=>" + P(1 - literals.Stop) * V(1) } + +self_prefix = Cg(P("@") * Cc(true), "self") + +-- this matches end-of-file return table convention for module files to figure +-- out what names are exported +export_list = Ct P"{" * P { + P"}" + ((P":" * literals.Name) + (P(1) - P"}")) * V(1) +} + +eof_exports = P { export_list * S(" \t\r\n")^0 * P(-1) + P(1) * V(1) } + +-- convert a parsed string to the value the string represents +StringVal = C(String) / (str) -> loadstring("return " .. str)() + +class_line = Line "class", P"class" * whitespace^1 * Cg(literals.Name, "tag") * until_end +class_property = Line "property", self_prefix^-1 * Cg(literals.Name, "tag") * P":" * whitespace^0 * Cg(String, "value")^0 * until_end +class_method = Line("method", P("@")^-1 * Cg(literals.Name, "tag") * P":" * method) * until_end +function_def = Line("function", Cg(literals.Name, "tag") * whitespace^0 * P"=" * func) * until_end +lapis_route = Line "lapis-route", P"[" * Cg(literals.Name + StringVal, "tag") * P":" * whitespace^0 * Cg(String, "route") * whitespace^0 * P("]:") * until_end + +line_types = class_line + class_method + class_property + function_def + +if args.lapis + line_types += lapis_route + +parse_lines = Ct P { + (line_types + ignore_line) * (P(-1) + literals.Break * V(1)) +} + +escape_tagaddress = (line_text) -> + replacements = S([[\/.$^]]) / [[\%0]]+ P("\t") / [[\t]] + P("\r") / [[\r]] + P("\n") / [[\n]] + Cs((replacements + 1)^0)\match line_text + +import types from require "tableshape" + +class_field = types.partial { + "self": true + tag: types.string\tag "name" + value: types.partial { + "string" + types.string + types.string\tag "value" -- TODO: will need to un-escape this + } +} + +for fname in *args.files + file = assert io.open fname + contents = assert file\read "*a" + exports = {e, true for e in *eof_exports\match(contents) or {}} + + lines = assert parse_lines\match contents + + class_stack = {} + + push_class = (cls) -> + assert cls.type == "class", "not a class match" + -- remove classes that are longer in scope due to indentation + for i=#class_stack,1,-1 + top = class_stack[i] + + if cls.indent <= top.indent + table.remove class_stack, i + else + break + + table.insert class_stack, cls + + -- find the class this property is associated with based on change in indent + -- the expeted indent is written to `step` on the first proprety + find_class = (property) -> + for i=#class_stack,1,-1 + top = class_stack[i] + step = property.indent - top.indent + + if step > 0 + if top.step == nil + top.step = step + + if step == top.step + return top + + for line_no, line in ipairs lines + continue unless next line + + {line_text, properties} = line + + fields = {"language:moon"} + if args.include_line + table.insert fields, 1, "line:#{line_no}" + + switch properties.type + when "lapis-route" + if cls = find_class properties + prefix = if cls.fields + cls.fields.name + + table.insert TAGS, { + "#{prefix or ""}#{properties.tag}" + fname + "/^#{escape_tagaddress line_text}/;\"" + "f" + table.concat fields, " " + } + + when "property" + -- this is necessary to register the correct indent level for the class + cls = find_class properties + + -- record the fields into the class object so they can be referenced by + -- other tags. Note this is code-order dependent + if cls and args.lapis + if field = class_field properties + cls.fields or= {} + cls.fields[field.name] = field.value + + when "function" + if exports[properties.tag] and properties.indent == 0 + table.insert TAGS, { + properties.tag + fname + -- note we don't use $ here + "/^#{escape_tagaddress line_text}/;\"" + "f" + table.concat fields, " " + } + + when "method" + if cls = find_class properties + table.insert fields, "class:#{cls.tag}" + + table.insert TAGS, { + properties.tag + fname + -- note we don't use $ here + "/^#{escape_tagaddress line_text}/;\"" + "f" + table.concat fields, " " + } + when "class" + push_class properties + + table.insert TAGS, { + properties.tag + fname + "/^#{escape_tagaddress line_text}$/;\"" + "c" + table.concat fields, " " + } + +unless args.no_header + print HEADER + +tag_lines = [table.concat(t, "\t") for t in *TAGS] +table.sort tag_lines +print table.concat tag_lines, "\n" diff --git a/bin/moon.moon b/bin/moon.moon new file mode 100644 index 00000000..fcb8de89 --- /dev/null +++ b/bin/moon.moon @@ -0,0 +1,124 @@ +argparse = require "argparse" + +moonscript = require "moonscript.base" +util = require "moonscript.util" +errors = require "moonscript.errors" + +unpack = util.unpack + +argparser = argparse! name: "moon" + +argparser\argument("script")\args "?" +argparser\argument("args")\args "*" +argparser\flag "--coverage -c", "Collect and print code coverage" +argparser\flag "-d", "Disable stack trace rewriting" +argparser\option "--execute -e", "Execute MoonScript code string" +argparser\flag "--version -v", "Print version information" + +base = 0 +for flag in *arg + base += 1 + break if flag\sub(1, 1) != "-" +args = {unpack arg, 1, base} +opts = argparser\parse args + +print_err = (...) -> + msg = table.concat [tostring v for v in *{...}], "\t" + io.stderr\write msg .. "\n" + +run = -> + + if opts.version + require("moonscript.version").print_version! + os.exit! + + args = {unpack arg, base + 1} + args[-1] = arg[0] + + local moonscript_chunk, lua_parse_error + + if opts.execute + args[0] = "-e" + + passed, err = pcall -> + moonscript_chunk, lua_parse_error = moonscript.loadstring opts.execute, "=(command line)", { + implicitly_return_root: false + } + + unless passed + print_err err + os.exit 1 + + unless moonscript_chunk + if lua_parse_error + print_err lua_parse_error + else + print_err "Failed to compile: #{opts.execute}" + os.exit 1 + else + script_fname = opts.script + + unless script_fname + print_err "Usage: moon [options] script [args]" + print_err "Use 'moon --help' for more information." + os.exit 1 + + args[0] = script_fname + + passed, err = pcall -> + moonscript_chunk, lua_parse_error = moonscript.loadfile script_fname, { + implicitly_return_root: false + } + + unless passed + print_err err + os.exit 1 + + unless moonscript_chunk + if lua_parse_error + print_err lua_parse_error + else + print_err "Can't file file: #{script_fname}" + os.exit 1 + + util.getfenv(moonscript_chunk).arg = args + + run_chunk = -> + moonscript.insert_loader! + moonscript_chunk unpack args + moonscript.remove_loader! + + if opts.d + return run_chunk! + + local err, trace, cov + + if opts.coverage + print "starting coverage" + coverage = require "moonscript.cmd.coverage" + cov = coverage.CodeCoverage! + cov\start! + + xpcall run_chunk, (_err) -> + err = _err + trace = debug.traceback "", 2 + + if err + truncated = errors.truncate_traceback util.trim trace + rewritten = errors.rewrite_traceback truncated, err + + if rewritten + print_err rewritten + else + -- failed to rewrite, show original + print_err table.concat { + err, + util.trim trace + }, "\n" + os.exit 1 + else + if cov + cov\stop! + cov\print_results! + +run! diff --git a/bin/moonc b/bin/moonc old mode 100755 new mode 100644 index 7ce00f37..44f412dd --- a/bin/moonc +++ b/bin/moonc @@ -1,410 +1,240 @@ #!/usr/bin/env lua -local parse = require "moonscript.parse" -local compile = require "moonscript.compile" -local util = require "moonscript.util" - -local dump_tree = require"moonscript.dump".tree - -local alt_getopt = require "alt_getopt" +local argparse = require "argparse" local lfs = require "lfs" -local opts, ind = alt_getopt.get_opts(arg, "lvhwt:pTXb", { - print = "p", tree = "T", version = "v", help = "h", lint = "l" -}) - -local read_stdin = arg[1] == "--" - -local polling_rate = 1.0 +local parser = argparse() -local help = [[Usage: %s [options] files... +parser:flag("-l --lint", "Perform a lint on the file instead of compiling") - -h Print this message - -w Watch file/directory - -t path Specify where to place compiled files - -p Write output to standard out - -T Write parse tree instead of code (to stdout) - -X Write line rewrite map instead of code (to stdout) - -l Perform lint on the file instead of compiling - -b Dump parse and compile time (doesn't write output) - -v Print version - - -- Read from standard in, print to standard out - (Must be first and only argument) -]] - -if opts.v then - local v = require "moonscript.version" - v.print_version() - os.exit() -end - -function print_help(err) - local help_msg = help:format(arg[0]) - - if err then - io.stderr:write("Error: ".. err .. "\n") - io.stderr:write(help_msg .. "\n") - os.exit(1) - else - print(help_msg) - os.exit(0) - end -end +parser:flag("-v --version", "Print version") +parser:flag("-w --watch", "Watch file/directory for updates") +parser:option("--transform", "Transform syntax tree with module") -function mkdir(path) - local chunks = util.split(path, "/") - local accum +parser:mutex( + parser:option("-t --output-to", "Specify where to place compiled files"), + parser:option("-o", "Write output to file"), + parser:flag("-p", "Write output to standard output"), + parser:flag("-T", "Write parse tree instead of code (to stdout)"), + parser:flag("-b", "Write parse and compile time instead of code(to stdout)"), + parser:flag("-X", "Write line rewrite map instead of code (to stdout)") +) - for _, dir in ipairs(chunks) do - accum = accum and accum.."/"..dir or dir - lfs.mkdir(accum) - end +parser:flag("-", + "Read from standard in, print to standard out (Must be only argument)") - return lfs.attributes(path, "mode") -end - -function normalize(path) - return path:match("(.-)/*$").."/" -end - -function get_dir(fname) - return fname:match("^(.-)[^/]*$") -end - --- convert .moon to .lua -function convert_path(path) - return (path:gsub("%.moon$", ".lua")) -end - -function log_msg(...) - if not opts.p then - io.stderr:write(table.concat({...}, " ") .. "\n") - end -end +local read_stdin = arg[1] == "-" -- luacheck: ignore 113 -local gettime = nil -if opts.b then - pcall(function() - require "socket" - gettime = socket.gettime - end) - - function format_time(time) - return ("%.3fms"):format(time*1000) - end - if not gettime then - print_help"LuaSocket needed for benchmark" - end +if not read_stdin then + parser:argument("file/directory"):args("+") else - gettime = function() return 0 end + if arg[2] ~= nil then + io.stderr:write("- must be the only argument\n") + os.exit(1) + end end -function write_file(fname, code) - if opts.p then - if code ~= "" then print(code) end - else - mkdir(get_dir(fname)) - local out_f = io.open(fname, "w") - if not out_f then - return nil, "Failed to write output: "..fname - end - - out_f:write(code.."\n") - out_f:close() - end - return true -end +local opts = read_stdin and {} or parser:parse() -function compile_file(text, fname) - local parse_time = gettime() - local tree, err = parse.string(text) - parse_time = gettime() - parse_time - - if not tree then - return nil, err - end - - if opts.T then - opts.p = true - dump_tree(tree) - return "" - else - local compile_time = gettime() - local code, posmap_or_err, err_pos = compile.tree(tree) - compile_time = gettime() - compile_time - - if not code then - return nil, compile.format_error(posmap_or_err, err_pos, text) - end - - if opts.X then - opts.p = true - print("Pos", "Lua", ">>", "Moon") - print(util.debug_posmap(posmap_or_err, text, code)) - return "" - end - - if opts.b then - opts.p = true - return table.concat({ - fname, - "Parse time \t" .. format_time(parse_time), - "Compile time\t" .. format_time(compile_time), - "" - }, "\n") - end - - return code - end +if opts.version then + local v = require "moonscript.version" + v.print_version() + os.exit() end -function compile_and_write(from, to) - local f = io.open(from) - if not f then - return nil, "Can't find file" - end - local text = f:read("*a") - - local code, err = compile_file(text, from) - if not code then - return nil, err - end - - return write_file(to, code) +function log_msg(...) + if not opts.p then + io.stderr:write(table.concat({...}, " ") .. "\n") + end end -function scan_directory(root, collected) - root = normalize(root) - collected = collected or {} - - for fname in lfs.dir(root) do - if not fname:match("^%.") then - local full_path = root..fname +local moonc = require("moonscript.cmd.moonc") +local util = require "moonscript.util" +local normalize_dir = moonc.normalize_dir +local compile_and_write = moonc.compile_and_write +local path_to_target = moonc.path_to_target - if lfs.attributes(full_path, "mode") == "directory" then - scan_directory(full_path, collected) - end +local function scan_directory(root, collected) + root = normalize_dir(root) + collected = collected or {} - if fname:match("%.moon$") then - table.insert(collected, full_path) - end - end - end + for fname in lfs.dir(root) do + if not fname:match("^%.") then + local full_path = root..fname - return collected -end + if lfs.attributes(full_path, "mode") == "directory" then + scan_directory(full_path, collected) + elseif fname:match("%.moon$") then + table.insert(collected, full_path) + end + end + end -function append(a, b) - for _, v in ipairs(b) do - table.insert(a, v) - end + return collected end -function remove_dups(tbl) - local hash = {} - local final = {} +local function remove_dups(tbl, key_fn) + local hash = {} + local final = {} - for _, v in ipairs(tbl) do - if not hash[v] then - table.insert(final, v) - hash[v] = true - end - end + for _, v in ipairs(tbl) do + local dup_key = key_fn and key_fn(v) or v + if not hash[dup_key] then + table.insert(final, v) + hash[dup_key] = true + end + end - return final + return final end -function get_files(fname, files) - files = files or {} +-- creates tuples of input and target +local function get_files(fname, files) + files = files or {} - if lfs.attributes(fname, "mode") == "directory" then - append(files, scan_directory(fname)) - else - table.insert(files, "./"..fname) - end + if lfs.attributes(fname, "mode") == "directory" then + for _, sub_fname in ipairs(scan_directory(fname)) do + table.insert(files, { + sub_fname, + path_to_target(sub_fname, opts.output_to, fname) + }) + end + else + table.insert(files, { + fname, + path_to_target(fname, opts.output_to) + }) + end - return files + return files end -if opts.h then print_help() end - if read_stdin then - local text = io.stdin:read("*a") - local tree, err = parse.string(text) - if not tree then error(err) end - local code, err, pos = compile.tree(tree) + local parse = require "moonscript.parse" + local compile = require "moonscript.compile" - if not code then - error(compile.format_error(err, pos, text)) - end + local text = io.stdin:read("*a") + local tree, err = parse.string(text) - print(code) - os.exit() -end + if not tree then error(err) end + local code, err, pos = compile.tree(tree) -local inputs = {} -for i = ind, #arg do - table.insert(inputs, arg[i]) -end - -if #inputs == 0 then - print_help("No files specified") -end + if not code then + error(compile.format_error(err, pos, text)) + end -local target_dir = "." -if opts.t then - if mkdir(opts.t) ~= "directory" then - print_help("Invalid target dir") - end - target_dir = opts.t + print(code) + os.exit() end -target_dir = target_dir.."/" +local inputs = opts["file/directory"] local files = {} for _, input in ipairs(inputs) do - get_files(input, files) -end - -files = remove_dups(files) - -function get_sleep_func() - local sleep - if not pcall(function() - require "socket" - sleep = socket.sleep - end) then - -- This is set by moonc.c in windows binaries - sleep = require("moonscript")._sleep - end - if not sleep then - error("Missing sleep function; install LuaSocket") - end - return sleep + get_files(input, files) end - -function plural(count, word) - if count ~= 1 then - word = word .. "s" - end - return table.concat({count, word}, " ") -end +files = remove_dups(files, function(f) + return f[2] +end) -- returns an iterator that returns files that have been updated -function create_watcher(files) - local msg = "Starting watch loop (Ctrl-C to exit)" - - local inotify - pcall(function() - inotify = require "inotify" - end) - - if inotify then - local dirs = {} - for _, fname in ipairs(files) do - table.insert(dirs, get_dir(fname)) - end - dirs = remove_dups(dirs) - - return coroutine.wrap(function() - io.stderr:write(("%s with inotify [%s]"):format(msg, plural(#dirs, "dir")) .. "\n") - - local wd_table = {} - local handle = inotify.init() - for _, dir in ipairs(dirs) do - local wd = handle:addwatch(dir, inotify.IN_CLOSE_WRITE) - wd_table[wd] = dir - end - - while true do - local events = handle:read() - if events then - for _, ev in ipairs(events) do - local fname = wd_table[ev.wd]..ev.name - if fname:match("%.moon$") then - coroutine.yield(fname) - end - end - else - break - end - end - end) - else - -- poll the filesystem instead - local sleep = get_sleep_func() - return coroutine.wrap(function() - io.stderr:write(("%s with polling [%s]"):format(msg, plural(#files, "file")) .. "\n") - - local mod_time = {} - while true do - for _, file in ipairs(files) do - local time = lfs.attributes(file, "modification") - if not mod_time[file] then - mod_time[file] = time - else - if time ~= mod_time[file] then - if time > mod_time[file] then - coroutine.yield(file) - mod_time[file] = time - end - end - end - end - sleep(polling_rate) - end - end) - end -end - -if opts.w then - local watcher = create_watcher(files) - -- catches interrupt error for ctl-c - local protected = function() - local status, file = pcall(watcher) - if status then - return file - elseif file ~= "interrupted!" then - error(file) - end - end - - for fname in protected do - local target = target_dir..convert_path(fname) - local success, err = compile_and_write(fname, target) - if not success then - io.stderr:write(table.concat({ - "", - "Error: " .. fname, - err, - "\n", - }, "\n")) - else - log_msg("Built:", fname, "->", target) - end - end - - io.stderr:write("\nQuitting...\n") -elseif opts.l then - for _, fname in pairs(files) do - lint = require "moonscript.cmd.lint" - local res = lint.lint_file(fname) - if res then - io.stderr:write(res .. "\n\n") - end - end +local function create_watcher(files) + local watchers = require("moonscript.cmd.watchers") + + if watchers.InotifyWacher:available() then + return watchers.InotifyWacher(files):each_update() + end + + return watchers.SleepWatcher(files):each_update() +end + +if opts.watch then + -- build function to check for lint or compile in watch + local handle_file + if opts.lint then + local lint = require "moonscript.cmd.lint" + handle_file = lint.lint_file + else + handle_file = compile_and_write + end + + local watcher = create_watcher(files) + -- catches interrupt error for ctl-c + local protected = function() + local status, file = true, watcher() + if status then + return file + elseif file ~= "interrupted!" then + error(file) + end + end + + for fname in protected do + local target = path_to_target(fname, opts.t) + + if opts.o then + target = opts.o + end + + local success, err = handle_file(fname, target) + if opts.lint then + if success then + io.stderr:write(success .. "\n\n") + elseif err then + io.stderr:write(fname .. "\n" .. err .. "\n\n") + end + elseif not success then + io.stderr:write(table.concat({ + "", + "Error: " .. fname, + err, + "\n", + }, "\n")) + elseif success == "build" then + log_msg("Built", fname, "->", target) + end + end + + io.stderr:write("\nQuitting...\n") +elseif opts.lint then + local has_linted_with_error; + local lint = require "moonscript.cmd.lint" + for _, tuple in pairs(files) do + local fname = tuple[1] + local res, err = lint.lint_file(fname) + if res then + has_linted_with_error = true + io.stderr:write(res .. "\n\n") + elseif err then + has_linted_with_error = true + io.stderr:write(fname .. "\n" .. err.. "\n\n") + end + end + if has_linted_with_error then + os.exit(1) + end else - for _, fname in ipairs(files) do - local success, err = compile_and_write(fname, target_dir..convert_path(fname)) - if not success then - io.stderr:write(fname .. "\t" .. err .. "\n") - os.exit(1) - else - log_msg("Built", fname) - end - end + for _, tuple in ipairs(files) do + local fname, target = util.unpack(tuple) + if opts.o then + target = opts.o + end + + local success, err = compile_and_write(fname, target, { + print = opts.p, + fname = fname, + benchmark = opts.b, + show_posmap = opts.X, + show_parse_tree = opts.T, + transform_module = opts.transform + }) + + if not success then + io.stderr:write(fname .. "\t" .. err .. "\n") + os.exit(1) + end + end end diff --git a/bin/splat.moon b/bin/splat.moon index 506a54ae..d6127042 100755 --- a/bin/splat.moon +++ b/bin/splat.moon @@ -1,26 +1,23 @@ #!/usr/bin/env moon +argparse = require "argparse" --- concatenate a collection of lua modules into one +-- TODO: it would be cool if you could just point this at a luarocks tree, pass a list of top level module names, and it figures it out for you. +-- Perhaps even merge the header generation into here as well to avoid using xxd -require "lfs" -require "alt_getopt" - -import insert, concat from table -import dump, split from require "moonscript.util" - -opts, ind = alt_getopt.get_opts arg, "l:", { - load: "l" -} +normalize = (path) -> + path\match("(.-)/*$").."/" -if not arg[ind] - print "usage: splat [-l module_names] directory [directories...]" - os.exit! +parser = argparse "splat.moon", "Concatenate a collection of Lua modules into a single file" +parser\option("--load -l", "Module names that will be load on require")\count "*" +parser\flag("--strip-prefix -s", "Strip directory prefix from module names") -dirs = [a for a in *arg[ind,]] +parser\argument("directories", "Directories to scan for Lua modules")\args "+" -normalize = (path) -> - path\match("(.-)/*$").."/" +args = parser\parse [v for _, v in ipairs _G.arg] +dirs = args.directories +strip_prefix = args.strip_prefix +lfs = require "lfs" scan_directory = (root, patt, collected={}) -> root = normalize root for fname in lfs.dir root @@ -31,23 +28,24 @@ scan_directory = (root, patt, collected={}) -> scan_directory full_path, patt, collected else if full_path\match patt - insert collected, full_path + table.insert collected, full_path collected -path_to_module_name = (path) -> +path_to_module_name = (path, prefix) -> + if prefix and path\sub(1, #prefix) == prefix + path = path\sub(#prefix + 1) (path\match("(.-)%.lua")\gsub("/", ".")) each_line = (text) -> - import yield from coroutine coroutine.wrap -> start = 1 while true pos, after = text\find "\n", start, true break if not pos - yield text\sub start, pos - 1 + coroutine.yield text\sub start, pos - 1 start = after + 1 - yield text\sub start, #text + coroutine.yield text\sub start, #text nil write_module = (name, text) -> @@ -59,8 +57,9 @@ write_module = (name, text) -> modules = {} for dir in *dirs files = scan_directory dir, "%.lua$" + prefix = strip_prefix and normalize(dir) or nil chunks = for path in *files - module_name = path_to_module_name path + module_name = path_to_module_name path, prefix content = io.open(path)\read"*a" modules[module_name] = true {module_name, content} @@ -73,8 +72,7 @@ for dir in *dirs name = base write_module name, content -if opts.l - for module_name in *split opts.l, "," - if modules[module_name] - print ([[package.preload["%s"]()]])\format module_name +for module_name in *args.load + if modules[module_name] + print ([[package.preload["%s"]()]])\format module_name diff --git a/bin/util/file_to_header.lua b/bin/util/file_to_header.lua new file mode 100644 index 00000000..71378367 --- /dev/null +++ b/bin/util/file_to_header.lua @@ -0,0 +1,42 @@ +-- this script is used to convert a source input file into a C header to embed +-- that file as a string. Works the same as xxd -i + +local input = ... + +local function read_file(file_path) + local file = assert(io.open(file_path, "rb")) + local content = file:read("*a") + file:close() + return content +end + +local function generate_c_header(input_file) + local function byte_to_hex(byte) + return string.format("0x%02x", byte:byte()) + end + + local function sanitize_name(name) + return (name:gsub("[^%w_]", "_")) + end + + local data = read_file(input_file) + local name = sanitize_name(input_file) + local header = {} + + table.insert(header, string.format("unsigned char %s[] = {", name)) + for i = 1, #data do + if i % 16 == 1 then + table.insert(header, "\n ") + end + table.insert(header, byte_to_hex(data:sub(i, i))) + if i ~= #data then + table.insert(header, ", ") + end + end + table.insert(header, "\n};\n") + table.insert(header, string.format("unsigned int %s_len = %d;\n", name, #data)) + + return table.concat(header) +end + +print(generate_c_header(input)) diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 00000000..56425264 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,132 @@ +{ + target: "reference/api" + template: "reference" + title: "Compiler API" + short_name: "api" +} + +# MoonScript Compiler API + +## Autocompiling with the `moonscript` Module + +After installing MoonScript, you can include the `moonscript` module to make +any Lua script MoonScript aware. + +```lua +require "moonscript" +``` + +After `moonscript` is required, Lua's package loader is updated to search for +`.moon` files on any subsequent calls to `require`. The search path for `.moon` +files is based on the current `package.path` value in Lua when `moonscript` is +required. Any search paths in `package.path` ending in `.lua` are copied, +rewritten to end in `.moon`, and then inserted in `package.moonpath`. + +The `moonloader` is the function that is responsible for searching +`package.moonpath` for a file available to be included. It is inserted in the +second position of the `package.loaders` table. This means that a matching `.moon` file +will be loaded over a matching `.lua` file that has the same base name. + +For more information on Lua's `package.loaders` see [Lua Reference Manual +— +package.loaders](http://www.lua.org/manual/5.1/manual.html#pdf-package.loaders) + +The `moonloader`, when finding a valid path to a `.moon` file, will parse and +compile the file in memory. The code is then turned into a function using the +built in `load` function, which is run as the module. + +If you are executing MoonScript code with the included `moon` command line tool +then it is not required to include this module before including any other +MoonScript modules. + +## `moonscript.base` Module + +```moononly +moonscript = require "moonscript.base" +``` + +This module contains an assortment of functions for loading and compiling +MoonScript code from within Lua. + +The module provides `load`, `loadfile`, `loadstring` functions, which are +analogous to the similarly named Lua functions. The major difference is that +they load MoonScript code instead of Lua code. + + +```moononly +moonscript = require "moonscript.base" + +fn = moonscript.loadstring 'print "hi!"' +fn! +``` + +All of these functions can take an optional last argument, a table of options. +The only option right now is `implicitly_return_root`. Setting this to `false` +makes it so the file does not implicitly return its last statement. + + +```moononly +moonscript = require "moonscript.base" + +fn = moonscript.loadstring "10" +print fn! -- prints "10" + +fn = moonscript.loadstring "10", implicitly_return_root: false +print fn! -- prints nothing +``` + +One more useful function is provided: `to_lua`. This function takes a string of +MoonScript code and returns the compiled Lua result along with the line mapping +table. If there are any errors then `nil` and the error message are returned. + + +```moononly +import to_lua from require "moonscript.base" + +lua_code, line_table = to_lua [[ +x = 124 +print "hello world #{x}" +]] +``` + +Similar to the `load*` functions from above, `to_lua` can take an optional +final argument of a table of options. + +The second return value of `to_lua` is useful if you want to perform line +number reversal. It's a table where the key is a Lua line number and the value +is a character offset from the original MoonScript source. + +## Programmatically Compiling + +If you need finer grained control over the compilation process you can use the +raw parse and compile modules. + +Parsing converts a string of MoonScript into an abstract syntax tree. Compiling +converts an abstract syntax tree into a Lua code string. + +Knowledge of this API may be useful for creating tools to aid the generation of +Lua code from MoonScript code. For example, you could build a macro system by +analyzing and manipulating the abstract syntax tree. Be warned though, the +format of the abstract syntax tree is undocumented and may change in the +future. + +Here is a quick example of how you would compile a MoonScript string to a Lua +String (This is effectively the same as the `to_lua` function described above): + +```moononly +parse = require "moonscript.parse" +compile = require "moonscript.compile" + +moon_code = [[(-> print "hello world")!]] + +tree, err = parse.string moon_code +unless tree + error "Parse error: " .. err + +lua_code, err, pos = compile.tree tree +unless lua_code + error compile.format_error err, pos, moon_code + +-- our code is ready +print lua_code +``` diff --git a/docs/command_line.md b/docs/command_line.md new file mode 100644 index 00000000..f0e5d4b2 --- /dev/null +++ b/docs/command_line.md @@ -0,0 +1,309 @@ +{ + target: "reference/command_line" + template: "reference" + title: "Command Line Tools" + short_name: "command_line" +} + +# Command Line Tools + +Two tools are installed with MoonScript, `moon` and `moonc`. + +`moonc` is for compiling MoonScript code to Lua. +`moon` is for running MoonScript code directly. + +## `moon` + +`moon` can be used to run MoonScript files directly from the command line, +without needing a separate compile step. All MoonScript files are compiled in +memory as they are executed. + +```bash +$ moon my_script.moon +``` + +Any MoonScript files that are required will also be compiled on demand as they +are loaded. + +When an error occurs during runtime, the stack trace is rewritten to give line +numbers from the original `.moon` file. + +If you want to disable [error rewriting](#error_rewriting), you can pass the +`-d` flag. A full list of flags can be seen by passing the `-h` or `--help` +flag. + +### Error Rewriting + +Runtime errors are given special attention when running code using the `moon` +command line tool. Because code is written in MoonScript but executed as Lua, +errors that happen during runtime report Lua line numbers. This can make +debugging less than ideal. + +In order to solve this problem MoonScript builds up a table of line number +mappings, allowing the runtime to calculate what line of MoonScript generated +the line of Lua that triggered the error. + +Consider the following file with a bug (note the invalid `z` variable): + +```moon +add_numbers = (x,y) -> x + z -- 1 +print add_numbers 10,0 -- 2 +``` + +The following error is generated: + + moon: scrap.moon:1(3): attempt to perform arithmetic on global 'z' (a nil value) + stack traceback: + scrap.moon:1(3): in function 'add_numbers' + scrap.moon:2(5): in main chunk + + +Notice how next to the file name there are two numbers. The first number is the +rewritten line number. The number in the parentheses is the original Lua line +number. + +The error in this example is being reported on line 1 of the `moon` file, which +corresponds to line 3 of the generated Lua code. The entire stack trace is rewritten in +addition to the error message. + +### Code Coverage + +`moon` lets you run a MoonScript file while keeping track of which lines +are executed with the `-c` flag. + +For example, consider the following `.moon` file: + +```moononly +-- test.moon +first = -> + print "hello" + +second = -> + print "world" + +first! +``` + +We can execute and get a glance of which lines ran: + +```bash +$ moon -c test.moon +``` + +The following output is produced: + + ------| @cool.moon + 1| -- test.moon + * 2| first = -> + * 3| print "hello" + 4| + * 5| second = -> + 6| print "world" + 7| + * 8| first! + 9| + +The star next to the line means that it was executed. Blank lines are not +considered when running so by default they don't get marked as executed. + +## `moonc` + +`moonc` is used for transforming MoonScript files into Lua files. +It takes a list of files, compiles them all, and creates the associated `.lua` +files in the same directories. + +```bash +$ moonc my_script1.moon my_script2.moon ... +``` + +You can control where the compiled files are put using the `-t` flag, followed +by a directory. + +`moonc` can also take a directory as an argument, and it will recursively scan +for all MoonScript files and compile them. + +`moonc` can write to standard out by passing the `-p` flag. + +The `-w` flag can be used to enable watch mode. `moonc` will stay running, and +watch for changes to the input files. If any of them change then they will be +compiled automatically. + +A full list of flags can be seen by passing the `-h` or `--help` flag. + +### Syntax Transformer + +A syntax transformer is a function that manipulates MoonScript code before +compiling to Lua. It operates on the parsed AST (Abstract Syntax Tree). It can +be used to implement macros, change syntax, optimize code, among other things. +You specify the name of a Lua module. This module must return a single function +(or callable object) that takes AST as an argument, and returns the new AST. + +```bash +moonc --transform my_transfomer my_script.moon +``` + +The transform can fail by returning `nil` and an error message. MoonScript AST +is currently undocumented, so you'll have to experiment by printing out the AST +to see how to make changes. MoonScript AST is made up of standard Lua tables +that are nested. + +### Linter + +`moonc` contains a [lint][1] tool for statically detecting potential problems +with code. The linter has two tests: detects accessed global variables, +detect unused declared variables. If the linter detects any issues with a file, +the program will exit with a status of `1`. + +You can execute the linter with the `-l` flag. When the linting flag is +provided only linting takes place and no compiled code is generated. + +The linter is compatible with the watch mode (see above) for automatic linting. + +```bash +moonc -l file1.moon file2.moon +``` + +Like when compiling, you can also pass a directory as a command line argument +to recursively process all the `.moon` files. + +#### Global Variable Checking + +It's considered good practice to avoid using global variables and create local +variables for all the values referenced. A good case for not using global +variables is that you can analyize the code ahead of time without the need to +execute it to find references to undeclared variables. + +MoonScript makes it difficult to declare global variables by forcing you to be +explicit with the `export` keyword, so it's a good candidate for doing this +kind of linting. + +Consider the following program with a typo: (`my_number` is spelled wrong as +`my_nmuber` in the function) + +```moononly +-- lint_example.moon +my_number = 1234 + +some_function = -> + -- a contrived example with a small chance to pass + if math.random() < 0.01 + my_nmuber + 10 + +some_function! +``` + +Although there is a bug in this code, it rarely happens during execution. It's +more likely to be missed during development and cause problems in the future. + +Running the linter immediately identifies the problem: + +```bash +$ moonc -l lint_example.moon +``` + +Outputs: + + ./lint_example.moon + + line 7: accessing global `my_nmuber` + ================================== + > my_nmuber + 10 + +#### Global Variable Whitelist + +In most circumstances it's impossible to avoid using some global variables. For +example, to access any of the built in modules or functions you typically +access them globally. + +For this reason a global variable whitelist is used. It's a list of global +variables that are allowed to be used. A default whitelist is provided that +contains all of Lua's built in functions and modules. + +You can create your own entires in the whitelist as well. For example, the +testing framework [Busted](http://olivinelabs.com/busted) uses a collection of +global functions (like `describe`, `before_each`, `setup`) to make writing +tests easy. + +It would be nice if we could allow all of those global functions to be called +for `.moon` files located in the `spec/` directory. We can do that by creating +a `lint_config` file. + +`lint_config` is a regular MoonScript or Lua file that provides configuration +for the linter. One of those settings is `whitelist_globals`. + +To create a configuration for Busted we might do something like this: + +```moononly +-- lint_config.moon +{ + whitelist_globals: { + ["spec/"]: { + "it", "describe", "setup", "teardown", + "before_each", "after_each", "pending" + } + } +} +``` + +Compile the file: + +```bash +$ moonc lint_config.moon +``` + +Then run the linter on your entire project: + +```bash +$ moonc -l . +``` + +The whitelisted global references in `spec/` will no longer raise notices. + +The `whitelist_globals` property of the `lint_config` is a table where the keys +are Lua patterns that match file names, and the values are an array of globals +that are allowed. + +Multiple patterns in `whitelist_globals` can match a single file, the union of +the allowed globals will be used when linting that file. + +#### Unused Variable Assigns + +Sometimes when debugging, refactoring, or just developing, you might leave +behind stray assignments that aren't actually necessary for the execution of +your code. It's good practice to clean them up to avoid any potential confusion +they might cause. + +The unused assignment detector keeps track of any variables that are assigned, +and if they aren't accessed in within their available scope, they are reported +as an error. + +Given the following code: + +```moononly +a, b = 1, 2 +print "hello", a +``` + +The linter will identify the problem: + + ./lint_example.moon + + line 1: assigned but unused `b` + =============================== + > a, b = 1, 2 + + +Sometimes you need a name to assign to even though you know it will never be +accessed. The linter will treat `_` as a special name that's allowed to be +written to but never accessed: + +The following code would not produce any lint errors: + +```moononly +item = {123, "shoe", "brown", 123} +_, name, _, count = unpack item +print name, count +``` + + [1]: http://en.wikipedia.org/wiki/Lint_(software) + diff --git a/docs/reference.md b/docs/reference.md index aab65d09..b95e8faf 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -1,8 +1,10 @@ - target: reference/index - template: reference - title: MoonScript v0.2.4 - Language Guide - short_name: lang --- +{ + target: "reference/index" + template: "reference" + title: "Language Guide" + short_name: "lang" +} + MoonScript is a programming language that compiles to [Lua](http://www.lua.org). This guide expects the reader to have basic familiarity with Lua. For each code snippet below, the MoonScript is on the @@ -24,8 +26,8 @@ MoonScript is a whitespace sensitive language. This means that instead of using `do` and `end` (or `{` and `}`) to delimit sections of code we use line-breaks and indentation. -This means that how you indent you code is important. Luckily MoonScript -doesn't care how you do it but it's important to be consistent. +This means that how you indent your code is important. Luckily MoonScript +doesn't care how you do it but only requires that you be consistent. An indent must be at least 1 space or 1 tab, but you can use as many as you like. All the code snippets on this page will use two spaces. @@ -53,9 +55,9 @@ variable, or shadow an existing one. ## Update Assignment -`+=`, `-=`, `/=`, `*=`, `%=`, `..=`, `or=`, `and=` operators have been added -for updating and assigning at the same time. They are aliases for their -expanded equivalents. +`+=`, `-=`, `/=`, `*=`, `%=`, `..=`, `or=`, `and=`, `&=`, `|=`, `>>=`, and +`<<=` operators have been added for updating and assigning at the same time. +They are aliases for their expanded equivalents. ```moon x = 0 @@ -66,6 +68,12 @@ s ..= "world" b = false b and= true or false + +p = 50 +p &= 5 +p |= 3 +p >>= 3 +p <<= 3 ``` ## Comments @@ -90,7 +98,7 @@ without an escape sequence: ```moon some_string = "Here is a string - that has a line break in it." + that has a line break in it." ``` ## Function Literals @@ -187,7 +195,7 @@ func = (num) => @value + num It is possible to provide default values for the arguments of a function. An argument is determined to be empty if its value is `nil`. Any `nil` arguments -that have a default value will be replace before the body of the function is run. +that have a default value will be replaced before the body of the function is run. ```moon my_function = (name="something", height=100) -> @@ -556,7 +564,7 @@ for item in *items do print item for j = 1,10,3 do print j ``` -A for loop can also be used an expression. The last statement in the body of +A for loop can also be used as an expression. The last statement in the body of the for loop is coerced into an expression and appended to an accumulating array table. @@ -644,7 +652,7 @@ have_coins = false if have_coins then print "Got coins" else print "No coins" ``` -Because if statements can be used as expressions, this can able be written as: +Because if statements can be used as expressions, this can also be written as: ```moon have_coins = false @@ -1232,7 +1240,20 @@ my_module = import \add from my_module -print add 22 -- equivalent to calling my_module\get 22 +print add 22 -- equivalent to calling my_module\add 22 +``` + +When handing multiple imports you can substitute the comma with a newline and +any amount of whitespace. When working with a lot of imports you might write +something like this: + +```moon +import + assert_csrf + assert_timezone + not_found + require_login + from require "helpers" ``` ## With Statement @@ -1521,201 +1542,30 @@ By default, a file will also implicitly return like a function. This is useful for writing modules, where you can put your module's table as the last statement in the file so it is returned when loaded with `require`. - ### Writing Modules Lua 5.2 has removed the `module` function for creating modules. It is recommended to return a table instead when defining a module. -The `with` statement along with implicit return on a file provides a convenient -way to do this: - +We can cleanly define modules by using the shorthand hash table key/value +syntax: ```moonret --- my_library.moon -with _M = {} - .SOME_CONSTANT = 100 - - .some_function = -> print .SOME_CONSTANT - -``` - -# MoonScript API - -## `moonscript` Module - -Upon installing MoonScript, a `moonscript` module is made available. The best -use of this module is making your Lua's require function MoonScript aware. - -```lua -require "moonscript" -``` - -After `moonscript` is required, Lua's package loader is updated to search for -`.moon` files on any subsequent calls to `require`. The search path for `.moon` -files is based on the current `package.path` value in Lua when `moonscript` is -required. Any search paths in `package.path` ending in `.lua` are copied, -rewritten to end in `.moon`, and then inserted in `package.moonpath`. -The `moonloader` is the function that is responsible for searching -`package.moonpath` for a file available to be included. It is inserted in the -second position of the `package.loaders` table. This means that a matching `.moon` file -will be loaded over a matching `.lua` file that has the same base name. +MY_CONSTANT = "hello" -For more information on Lua's `package.loaders` see [Lua Reference Manual -— -package.loaders](http://www.lua.org/manual/5.1/manual.html#pdf-package.loaders) +my_function = -> print "the function" +my_second_function = -> print "another function" -The `moonloader`, when finding a valid path to a `.moon` file, will parse and -compile the file in memory. The code is then turned into a function using the -built in `load` function, which is run as the module. - -### Load Functions - -MoonScript provides `moonscript.load`, `moonscript.loadfile`, -`mooonscript.loadstring`, which are analogous to Lua's `load`, `loadfile`, and -`loadstring`. - -The MoonScript functions work the same as their counterparts, except they deal -with MoonScript code instead of Lua Code. - - -```moononly -moonscript = require "moonscript" - -fn = moonscript.loadstring 'print "hi!"' -fn! +{ :my_function, :my_second_function, :MY_CONSTANT} ``` -All of these functions can take an optional last argument, a table of options. -The only option right now is `implicitly_return_root`. Setting this to `false` -makes it so the file does not implicitly return its last statement. - - -```moononly -moonscript = require "moonscript" - -fn = moonscript.loadstring "10" -print fn! -- prints "10" - -fn = moonscript.loadstring "10", implicitly_return_root: false -print fn! -- prints nothing -``` - -## Error Rewriting - -Runtime errors are given special attention when running code using the `moon` -binary. Because we start off as MoonScript, but run code as Lua, errors that -happen during runtime report their line numbers as they are in the compiled -file. This can make debugging particularly difficult. - -Consider the following file with a bug (note the invalid `z` variable): - -```moon -add_numbers = (x,y) -> x + z -- 1 -print add_numbers 10,0 -- 2 -``` - -The following error is generated: - - moon: scrap.moon:1(3): attempt to perform arithmetic on global 'z' (a nil value) - stack traceback: - scrap.moon:1(3): in function 'add_numbers' - scrap.moon:2(5): in main chunk - - -Notice how next to the file name there are two numbers. The first number is the -rewritten line number. The number in the parentheses is the original Lua line -number. - -The error in this example is being reported on line 1 of the `moon` file, which -corresponds to line 3 of the generated Lua code. The entire stack trace is rewritten in -addition to the error message. - -## Programmatically Compiling - -The MoonScript module also contains methods for parsing MoonScript text into an -abstract syntax tree, and compiling an instance of a tree into Lua source code. - -Knowledge of this API may be useful for creating tools to aid the generation of -Lua code from MoonScript code. - -Here is a quick example of how you would compile a MoonScript string to a Lua -String: - -```moononly -parse = require "moonscript.parse" -compile = require "moonscript.compile" - -moon_code = [[(-> print "hello world")!]] - -tree, err = parse.string moon_code -if not tree - error "Parse error: " .. err - -lua_code, err, pos = compile.tree tree -if not lua_code - error compile.format_error err, pos, moon_code - --- our code is ready -print lua_code -``` - -# Command Line Use - -Two tools are installed with MoonScript, `moon` and `moonc`. - -`moonc` is for compiling MoonScript code to Lua. -`moon` is for running MoonsScript code directly. - -## `moon` - -`moon` can be used to run MoonsScript files directly from the command line, -without needing a separate compile step. All MoonsScript files are compiled in -memory as they are run. - -```bash -$ moon my_script.moon -``` - -Any MoonScript files that are required will also be compiled and run -automatically. - -When an error occurs during runtime, the stack trace is rewritten to give line -numbers from the original `.moon` file. - -If you want to disable [error rewriting](#error_rewriting), you can pass the -`-d` flag. A full list of flags can be seen by passing the `-h` or `--help` -flag. - - -## `moonc` - -`moonc` is used for transforming MoonsScript files into Lua files. -It takes a list of files, compiles them all, and creates the associated `.lua` -files in the same directories. - -```bash -$ moonc my_script1.moon my_script2.moon ... -``` - -You can control where the compiled files are put using the `-t` flag, followed -by a directory. - -`moonc` can also take a directory as an argument, and it will recursively scan -for all MoonScript files and compile them. - -`moonc` can write to standard out by passing the `-p` flag. - -The `-w` flag can be used to enable watch mode. `moonc` will stay running, and -watch for changes to the input files. If any of them change then they will be -compiled automatically. - -A full list of flags can be seen by passing the `-h` or `--help` flag. +If you need to forward declare your values so you can access them regardless of +their written order you can add `local *` to the top of your file. # License (MIT) - Copyright (C) 2013 by Leaf Corcoran + Copyright (C) 2020 by Leaf Corcoran Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -1736,4 +1586,3 @@ A full list of flags can be seen by passing the `-h` or `--help` flag. THE SOFTWARE. - diff --git a/docs/standard_lib.md b/docs/standard_lib.md index 95c2a552..3a86a7b8 100644 --- a/docs/standard_lib.md +++ b/docs/standard_lib.md @@ -1,8 +1,9 @@ - target: reference/standard_lib - template: reference - title: MoonScript v0.2.4 - Standard Library - short_name: stdlib --- +{ + target: "reference/standard_lib" + template: "reference" + title: "Standard Library" + short_name: "stdlib" +} The MoonScript installation comes with a small kernel of functions that can be used to perform various common things. @@ -11,7 +12,7 @@ The entire library is currently contained in a single object. We can bring this `moon` object into scope by requiring `"moon"`. ```moon -require "moon" +moon = require "moon" -- `moon.p` is the debug printer moon.p { hello: "world" } ``` diff --git a/extra/scintillua/README.md b/extra/scintillua/README.md deleted file mode 100644 index ffeb9800..00000000 --- a/extra/scintillua/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# MoonScript for [scintillua][1] - -MoonScript syntax file for [SciTE][2] written in Lua for [scintillua][1]. - -## Windows Binary - -Windows users can get a all-included package ready for MoonScript Development: - - - -If you already have a ScITE installation, or are on another platform, follow -the directions below. - -## Installation - -Install SciTE, then [install scintillua][1]. - -Put `moonscript.properties` in in your ScITE installation folder or user -properties folder. - -Copy the entire contents of the `lexers` folder in this repository into your -scintillua `lexers` folder. - -In your `lexers` folder edit `lpeg.properties`, add to the end: - - file.patterns.moonscript=*.moon - lexer.$(file.patterns.moonscript)=lpeg_moonscript - -Optionally, enable the Moon theme, find `lexer.peg.color.theme` in the same -file and change it to: - - lexer.lpeg.color.theme=moon - - [1]: http://foicica.com/scintillua/ "scintillua" - [2]: http://www.scintilla.org/SciTE.html "SciTE" - diff --git a/extra/scintillua/lexers/moonscript.lua b/extra/scintillua/lexers/moonscript.lua deleted file mode 100644 index 7a9e54b8..00000000 --- a/extra/scintillua/lexers/moonscript.lua +++ /dev/null @@ -1,124 +0,0 @@ --- Copyright 2006-2011 Mitchell mitchellcaladbolg.net. See LICENSE. --- Moonscript lexer by leaf corcoran - -local l = lexer -local token, word_match = l.token, l.word_match -local P, S, R = lpeg.P, lpeg.S, lpeg.R - -local M = { _NAME = 'moonscript' } - --- Whitespace. -local ws = token(l.WHITESPACE, l.space^1) - -local longstring = #('[[' + ('[' * P('=')^0 * '[')) -local longstring = longstring * P(function(input, index) - local level = input:match('^%[(=*)%[', index) - if level then - local _, stop = input:find(']'..level..']', index, true) - return stop and stop + 1 or #input + 1 - end -end) - --- Comments. -local line_comment = '--' * l.nonnewline^0 -local block_comment = '--' * longstring -local comment = token(l.COMMENT, block_comment + line_comment) - --- Strings. -local sq_str = l.delimited_range("'", '\\', true) -local dq_str = l.delimited_range('"', '\\', true) -local string = token(l.STRING, sq_str + dq_str + longstring) - --- Numbers. -local number = token(l.NUMBER, l.float + l.integer) - --- Keywords. -local keyword = token(l.KEYWORD, word_match { - 'return', 'break', 'for', 'while', - 'if', 'else', 'elseif', 'then', 'export', - 'import', 'from', 'with', 'in', 'and', - 'or', 'not', 'class', 'extends', 'super', 'do', - 'using', 'switch', 'when', -}) - -local special = token("special", word_match { "true", "false", "nil" }) - --- Functions. -local builtin = token(l.FUNCTION, word_match({ - "_G","_VERSION","assert","collectgarbage","dofile","error","getfenv","getmetatable","ipairs","load", - "loadfile","loadstring","module","next","pairs","pcall","print","rawequal","rawget","rawset","require", - "select","setfenv","setmetatable","tonumber","tostring","type","unpack","xpcall", - - "coroutine.create","coroutine.resume","coroutine.running","coroutine.status","coroutine.wrap","coroutine.yield", - - "debug.debug","debug.getfenv","debug.gethook","debug.getinfo","debug.getlocal","debug.getmetatable", - "debug.getregistry","debug.getupvalue","debug.setfenv","debug.sethook","debug.setlocal","debug.setmetatable", - "debug.setupvalue","debug.traceback", - - "io.close","io.flush","io.input","io.lines","io.open","io.output","io.popen","io.read","io.stderr","io.stdin", - "io.stdout","io.tmpfile","io.type","io.write", - - "math.abs","math.acos","math.asin","math.atan","math.atan2","math.ceil","math.cos","math.cosh","math.deg", - "math.exp","math.floor","math.fmod","math.frexp","math.huge","math.ldexp","math.log","math.log10","math.max", - "math.min","math.modf","math.pi","math.pow","math.rad","math.random","math.randomseed","math.sin","math.sinh", - "math.sqrt","math.tan","math.tanh", - - "os.clock","os.date","os.difftime","os.execute","os.exit","os.getenv","os.remove","os.rename","os.setlocale", - "os.time","os.tmpname", - - "package.cpath","package.loaded","package.loaders","package.loadlib","package.path","package.preload", - "package.seeall", - - "string.byte","string.char","string.dump","string.find","string.format","string.gmatch","string.gsub", - "string.len","string.lower","string.match","string.rep","string.reverse","string.sub","string.upper", - - "table.concat","table.insert","table.maxn","table.remove","table.sort" -}, "%.")) - --- Identifiers. -local identifier = token(l.IDENTIFIER, l.word) - -local fndef = token("fndef", P"->" + P"=>") -local err = token(l.ERROR, word_match { "function", "end" }) - --- Operators. -local symbol = token("symbol", S("(){}[]")) -local operator = token(l.OPERATOR, '~=' + S('+-*!\\/%^#=<>;:,.')) - --- self ref -local self_var = token("self_ref", "@" * l.word + "self") - -local proper_ident = token("proper_ident", R("AZ") * l.word) - -local tbl_key = token("tbl_key", l.word * ":" + ":" * l.word ) - -M._rules = { - { 'whitespace', ws }, - { 'error', err }, - { 'self', self_var }, - { 'special', special }, - { 'keyword', keyword }, - { 'builtin', builtin }, - { 'identifier', proper_ident + tbl_key + identifier }, - { 'comment', comment }, - { 'number', number }, - { 'string', string }, - { 'fndef', fndef }, - { 'symbol', symbol }, - { 'operator', operator }, - { 'any_char', l.any_char }, -} - -local style_special = { fore = l.colors.light_blue } -local style_fndef = { fore = l.colors.green } - -M._tokenstyles = { - { 'self_ref', style_special }, - { 'proper_ident', l.style_class }, - { 'fndef', style_fndef }, - { 'symbol', style_fndef }, - { 'special', style_special }, - { 'tbl_key', { fore = l.colors.red } }, -} - -return M diff --git a/extra/scintillua/lexers/themes/moon.lua b/extra/scintillua/lexers/themes/moon.lua deleted file mode 100644 index db8d7b26..00000000 --- a/extra/scintillua/lexers/themes/moon.lua +++ /dev/null @@ -1,61 +0,0 @@ --- Copyright 2006-2011 Mitchell mitchellcaladbolg.net. See LICENSE. --- moon lexer theme for Scintillua. - -module('lexer', package.seeall) - -colors = { - green = color('9F', 'FF', '98'), -- - blue = color('94', '95', 'FF'), -- - light_blue = color('98', 'D9', 'FF'), -- - red = color('FF', '98', '98'), -- - bright_red = color("F9", "26", "32"), -- - yellow = color('FF', 'E8', '98'), -- - teal = color('4D', '99', '99'), - white = color('FF', 'FF', 'FF'), -- - black = color('2E', '2E', '2E'), -- - grey = color('92', '92', '92'), -- - purple = color('CB', '98', 'FF'), -- - orange = color('FF', '92', '00'), -- - pink = color("ED", "4E", "78"), -- -} - -style_nothing = style { } -style_char = style { fore = colors.red, bold = true } -style_class = style { fore = colors.light_blue, bold = true } -style_comment = style { fore = colors.grey, } -style_constant = style { fore = colors.teal, bold = true } -style_definition = style { fore = colors.red, bold = true } -style_error = style { fore = colors.white, back = colors.bright_red, bold = true} -style_function = style { fore = colors.orange, bold = true } -style_keyword = style { fore = colors.purple, bold = true } -style_number = style { fore = colors.blue } -style_operator = style { fore = colors.red, bold = true } -style_string = style { fore = colors.yellow, bold = true } -style_preproc = style { fore = colors.light_blue } -style_tag = style { fore = colors.teal, bold = true } -style_type = style { fore = colors.green } -style_variable = style { fore = colors.white, italic = true } -style_embedded = style_tag..{ back = color('44', '44', '44') } -style_identifier = style_nothing - --- Default styles. -local font_face = '!Bitstream Vera Sans Mono' -local font_size = 12 -if WIN32 then - font_face = not GTK and 'Courier New' or '!Courier New' -elseif OSX then - font_face = '!Monaco' - font_size = 12 -end -style_default = style{ - font = font_face, - size = font_size, - fore = colors.white, - back = colors.black -} -style_line_number = style { fore = colors.black, back = colors.grey } -style_bracelight = style { fore = color('66', '99', 'FF'), bold = true } -style_bracebad = style { fore = color('FF', '66', '99'), bold = true } -style_controlchar = style_nothing -style_indentguide = style { fore = colors.grey, back = colors.white } -style_calltip = style { fore = colors.white, back = color('44', '44', '44') } diff --git a/extra/scintillua/moonscript.properties b/extra/scintillua/moonscript.properties deleted file mode 100644 index feff9333..00000000 --- a/extra/scintillua/moonscript.properties +++ /dev/null @@ -1,11 +0,0 @@ - -file.patterns.moon=*.moon -shbang.moon=moon -filter.moon=MoonScript (moon)|$(file.patterns.moon)| - -command.compile.*.moon=moonc "$(FileNameExt)" -command.go.*.moon=moon "$(FileNameExt)" - -tabsize=2 -indent.size=2 -use.tabs=0 \ No newline at end of file diff --git a/moon.lua b/moon.lua deleted file mode 100644 index 6b1536fd..00000000 --- a/moon.lua +++ /dev/null @@ -1 +0,0 @@ -return require "moon.init" diff --git a/moon/init.lua b/moon/init.lua index 0ca497c6..52ead55d 100644 --- a/moon/init.lua +++ b/moon/init.lua @@ -1,12 +1,18 @@ -local util = require("moonscript.util") local lua = { debug = debug, type = type } -local dump, p, is_object, type, debug, run_with_scope, bind_methods, defaultbl, extend, copy, mixin, mixin_object, mixin_table, fold -dump = util.dump -p = function(...) - return print(dump(...)) +local getfenv, setfenv, dump +do + local _obj_0 = require("moonscript.util") + getfenv, setfenv, dump = _obj_0.getfenv, _obj_0.setfenv, _obj_0.dump +end +local p, is_object, type, debug, run_with_scope, bind_methods, defaultbl, extend, copy, mixin, mixin_object, mixin_table, fold +p = function(o, ...) + print(dump(o)) + if select("#", ...) > 0 then + return p(...) + end end is_object = function(value) return lua.type(value) == "table" and value.__class diff --git a/moon/init.moon b/moon/init.moon index ef07bbaf..57325eea 100644 --- a/moon/init.moon +++ b/moon/init.moon @@ -1,13 +1,13 @@ -util = require "moonscript.util" lua = { :debug, :type } +import getfenv, setfenv, dump from require "moonscript.util" local * -dump = util.dump - -p = (...) -> - print dump ... +p = (o, ...) -> + print dump o + if select("#", ...) > 0 + p ... is_object = (value) -> -- is a moonscript object lua.type(value) == "table" and value.__class diff --git a/moonscript-dev-1.rockspec b/moonscript-dev-1.rockspec index 7fd5ef17..b094bfaf 100644 --- a/moonscript-dev-1.rockspec +++ b/moonscript-dev-1.rockspec @@ -7,6 +7,7 @@ source = { description = { summary = "A programmer friendly language that compiles to Lua", + detailed = "A programmer friendly language that compiles to Lua", homepage = "http://moonscript.org", maintainer = "Leaf Corcoran ", license = "MIT" @@ -15,7 +16,7 @@ description = { dependencies = { "lua >= 5.1", "lpeg >= 0.10, ~= 0.11", - "alt-getopt >= 0.7", + "argparse >= 0.7", "luafilesystem >= 1.5" } @@ -28,6 +29,8 @@ build = { ["moonscript.base"] = "moonscript/base.lua", ["moonscript.cmd.coverage"] = "moonscript/cmd/coverage.lua", ["moonscript.cmd.lint"] = "moonscript/cmd/lint.lua", + ["moonscript.cmd.moonc"] = "moonscript/cmd/moonc.lua", + ["moonscript.cmd.watchers"] = "moonscript/cmd/watchers.lua", ["moonscript.compile"] = "moonscript/compile.lua", ["moonscript.compile.statement"] = "moonscript/compile/statement.lua", ["moonscript.compile.value"] = "moonscript/compile/value.lua", @@ -36,9 +39,19 @@ build = { ["moonscript.errors"] = "moonscript/errors.lua", ["moonscript.line_tables"] = "moonscript/line_tables.lua", ["moonscript.parse"] = "moonscript/parse.lua", + ["moonscript.parse.env"] = "moonscript/parse/env.lua", + ["moonscript.parse.literals"] = "moonscript/parse/literals.lua", + ["moonscript.parse.util"] = "moonscript/parse/util.lua", ["moonscript.transform"] = "moonscript/transform.lua", + ["moonscript.transform.accumulator"] = "moonscript/transform/accumulator.lua", + ["moonscript.transform.class"] = "moonscript/transform/class.lua", + ["moonscript.transform.comprehension"] = "moonscript/transform/comprehension.lua", ["moonscript.transform.destructure"] = "moonscript/transform/destructure.lua", ["moonscript.transform.names"] = "moonscript/transform/names.lua", + ["moonscript.transform.statement"] = "moonscript/transform/statement.lua", + ["moonscript.transform.statements"] = "moonscript/transform/statements.lua", + ["moonscript.transform.transformer"] = "moonscript/transform/transformer.lua", + ["moonscript.transform.value"] = "moonscript/transform/value.lua", ["moonscript.types"] = "moonscript/types.lua", ["moonscript.util"] = "moonscript/util.lua", ["moonscript.version"] = "moonscript/version.lua", diff --git a/moonscript.lua b/moonscript.lua deleted file mode 100644 index dd876be9..00000000 --- a/moonscript.lua +++ /dev/null @@ -1 +0,0 @@ -return require "moonscript.init" diff --git a/moonscript/base.lua b/moonscript/base.lua index d3f83c22..6bc0d49e 100644 --- a/moonscript/base.lua +++ b/moonscript/base.lua @@ -18,14 +18,32 @@ local dirsep, line_tables, create_moonpath, to_lua, moon_loader, loadstring, loa dirsep = "/" line_tables = require("moonscript.line_tables") create_moonpath = function(package_path) - local paths = split(package_path, ";") - for i, path in ipairs(paths) do - local p = path:match("^(.-)%.lua$") - if p then - paths[i] = p .. ".moon" + local moonpaths + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = split(package_path, ";") + for _index_0 = 1, #_list_0 do + local _continue_0 = false + repeat + local path = _list_0[_index_0] + local prefix = path:match("^(.-)%.lua$") + if not (prefix) then + _continue_0 = true + break + end + local _value_0 = prefix .. ".moon" + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end end + moonpaths = _accum_0 end - return concat(paths, ";") + return concat(moonpaths, ";") end to_lua = function(text, options) if options == nil then @@ -33,7 +51,7 @@ to_lua = function(text, options) end if "string" ~= type(text) then local t = type(text) - return nil, "expecting string (got " .. t .. ")", 2 + return nil, "expecting string (got " .. t .. ")" end local tree, err = parse.string(text) if not tree then @@ -41,16 +59,14 @@ to_lua = function(text, options) end local code, ltable, pos = compile.tree(tree, options) if not code then - return nil, compile.format_error(ltable, pos, text), 2 + return nil, compile.format_error(ltable, pos, text) end return code, ltable end moon_loader = function(name) local name_path = name:gsub("%.", dirsep) local file, file_path - local _list_0 = split(package.moonpath, ";") - for _index_0 = 1, #_list_0 do - local path = _list_0[_index_0] + for path in package.moonpath:gmatch("[^;]+") do file_path = path:gsub("?", name_path) file = io.open(file_path) if file then @@ -60,10 +76,13 @@ moon_loader = function(name) if file then local text = file:read("*a") file:close() - return loadstring(text, file_path) - else - return nil, "Could not find moon file" + local res, err = loadstring(text, "@" .. tostring(file_path)) + if not res then + error(file_path .. ": " .. err) + end + return res end + return nil, "Could not find moon file" end loadstring = function(...) local options, str, chunk_name, mode, env = get_options(...) @@ -87,7 +106,7 @@ loadfile = function(fname, ...) end local text = assert(file:read("*a")) file:close() - return loadstring(text, fname, ...) + return loadstring(text, "@" .. tostring(fname), ...) end dofile = function(...) local f = assert(loadfile(...)) @@ -125,10 +144,10 @@ return { insert_loader = insert_loader, remove_loader = remove_loader, to_lua = to_lua, - moon_chunk = moon_chunk, moon_loader = moon_loader, dirsep = dirsep, dofile = dofile, loadfile = loadfile, - loadstring = loadstring + loadstring = loadstring, + create_moonpath = create_moonpath } diff --git a/moonscript/base.moon b/moonscript/base.moon index 4e3e9315..3a4502d4 100644 --- a/moonscript/base.moon +++ b/moonscript/base.moon @@ -13,16 +13,16 @@ line_tables = require "moonscript.line_tables" -- create moon path package from lua package path create_moonpath = (package_path) -> - paths = split package_path, ";" - for i, path in ipairs paths - p = path\match "^(.-)%.lua$" - if p then paths[i] = p..".moon" - concat paths, ";" + moonpaths = for path in *split package_path, ";" + prefix = path\match "^(.-)%.lua$" + continue unless prefix + prefix .. ".moon" + concat moonpaths, ";" to_lua = (text, options={}) -> if "string" != type text t = type text - return nil, "expecting string (got ".. t ..")", 2 + return nil, "expecting string (got ".. t ..")" tree, err = parse.string text if not tree @@ -30,7 +30,7 @@ to_lua = (text, options={}) -> code, ltable, pos = compile.tree tree, options if not code - return nil, compile.format_error(ltable, pos, text), 2 + return nil, compile.format_error(ltable, pos, text) code, ltable @@ -38,7 +38,7 @@ moon_loader = (name) -> name_path = name\gsub "%.", dirsep local file, file_path - for path in *split package.moonpath, ";" + for path in package.moonpath\gmatch "[^;]+" file_path = path\gsub "?", name_path file = io.open file_path break if file @@ -46,9 +46,13 @@ moon_loader = (name) -> if file text = file\read "*a" file\close! - loadstring text, file_path - else - nil, "Could not find moon file" + res, err = loadstring text, "@#{file_path}" + if not res + error file_path .. ": " .. err + + return res + + return nil, "Could not find moon file" loadstring = (...) -> @@ -68,7 +72,7 @@ loadfile = (fname, ...) -> return nil, err unless file text = assert file\read "*a" file\close! - loadstring text, fname, ... + loadstring text, "@#{fname}", ... -- throws errros dofile = (...) -> @@ -98,7 +102,7 @@ remove_loader = -> { _NAME: "moonscript" - :insert_loader, :remove_loader, :to_lua, :moon_chunk, :moon_loader, :dirsep, - :dofile, :loadfile, :loadstring + :insert_loader, :remove_loader, :to_lua, :moon_loader, :dirsep, + :dofile, :loadfile, :loadstring, :create_moonpath } diff --git a/moonscript/cmd/args.lua b/moonscript/cmd/args.lua new file mode 100644 index 00000000..68b06fdc --- /dev/null +++ b/moonscript/cmd/args.lua @@ -0,0 +1,69 @@ +local unpack +unpack = require("moonscript.util").unpack +local parse_spec +parse_spec = function(spec) + local flags, words + if type(spec) == "table" then + flags, words = unpack(spec), spec + else + flags, words = spec, { } + end + assert("no flags for arguments") + local out = { } + for part in flags:gmatch("%w:?") do + if part:match(":$") then + out[part:sub(1, 1)] = { + value = true + } + else + out[part] = { } + end + end + return out +end +local parse_arguments +parse_arguments = function(spec, args) + spec = parse_spec(spec) + local out = { } + local remaining = { } + local last_flag = nil + for _index_0 = 1, #args do + local _continue_0 = false + repeat + local arg = args[_index_0] + local group = { } + if last_flag then + out[last_flag] = arg + _continue_0 = true + break + end + do + local flag = arg:match("-(%w+)") + if flag then + do + local short_name = spec[flag] + if short_name then + out[short_name] = true + else + for char in flag:gmatch(".") do + out[char] = true + end + end + end + _continue_0 = true + break + end + end + table.insert(remaining, arg) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return out, remaining +end +return { + parse_arguments = parse_arguments, + parse_spec = parse_spec +} diff --git a/moonscript/cmd/args.moon b/moonscript/cmd/args.moon new file mode 100644 index 00000000..ac65c0e3 --- /dev/null +++ b/moonscript/cmd/args.moon @@ -0,0 +1,47 @@ +import unpack from require "moonscript.util" +parse_spec = (spec) -> + flags, words = if type(spec) == "table" + unpack(spec), spec + else + spec, {} + + assert "no flags for arguments" + + out = {} + for part in flags\gmatch "%w:?" + if part\match ":$" + out[part\sub 1,1] = { value: true } + else + out[part] = {} + + out + +parse_arguments = (spec, args) -> + spec = parse_spec spec + + out = {} + + remaining = {} + last_flag = nil + + for arg in *args + group = {} + if last_flag + out[last_flag] = arg + continue + + if flag = arg\match "-(%w+)" + if short_name = spec[flag] + out[short_name] = true + else + for char in flag\gmatch "." + out[char] = true + continue + + table.insert remaining, arg + + out, remaining + + + +{ :parse_arguments, :parse_spec } diff --git a/moonscript/cmd/coverage.lua b/moonscript/cmd/coverage.lua index 8c585e83..43456301 100644 --- a/moonscript/cmd/coverage.lua +++ b/moonscript/cmd/coverage.lua @@ -42,6 +42,7 @@ position_to_lines = function(file_content, positions) end local format_file format_file = function(fname, positions) + fname = fname:gsub("^@", "") local file = assert(io.open(fname)) local content = file:read("*a") file:close() @@ -58,6 +59,7 @@ format_file = function(fname, positions) end local CodeCoverage do + local _class_0 local _base_0 = { reset = function(self) self.line_counts = create_counter() @@ -80,7 +82,8 @@ do process_line = function(self, _, line_no) local debug_data = debug.getinfo(2, "S") local source = debug_data.source - self.line_counts[source][line_no] = self.line_counts[source][line_no] + 1 + local _update_0, _update_1 = source, line_no + self.line_counts[_update_0][_update_1] = self.line_counts[_update_0][_update_1] + 1 end, format_results = function(self) local line_table = require("moonscript.line_tables") @@ -101,7 +104,8 @@ do _continue_1 = true break end - positions[file][position] = positions[file][position] + count + local _update_0, _update_1 = file, position + positions[_update_0][_update_1] = positions[_update_0][_update_1] + count _continue_1 = true until true if not _continue_1 then @@ -120,7 +124,7 @@ do end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self) return self:reset() end, diff --git a/moonscript/cmd/coverage.moon b/moonscript/cmd/coverage.moon index dd1d1fa0..281c12aa 100644 --- a/moonscript/cmd/coverage.moon +++ b/moonscript/cmd/coverage.moon @@ -23,6 +23,9 @@ position_to_lines = (file_content, positions) -> lines format_file = (fname, positions) -> + -- sources have @ in front of file names + fname = fname\gsub "^@", "" + file = assert io.open fname content = file\read "*a" file\close! diff --git a/moonscript/cmd/lint.lua b/moonscript/cmd/lint.lua index b47842bf..68053ca5 100644 --- a/moonscript/cmd/lint.lua +++ b/moonscript/cmd/lint.lua @@ -1,93 +1,205 @@ local insert -do - local _obj_0 = table - insert = _obj_0.insert -end +insert = table.insert local Set -do - local _obj_0 = require("moonscript.data") - Set = _obj_0.Set -end +Set = require("moonscript.data").Set local Block -do - local _obj_0 = require("moonscript.compile") - Block = _obj_0.Block -end -local whitelist_globals = Set({ - 'loadstring', - 'select', +Block = require("moonscript.compile").Block +local mtype +mtype = require("moonscript.util").moon.type +local default_whitelist = Set({ + '_G', '_VERSION', - 'pcall', - 'package', + 'assert', + 'bit32', + 'collectgarbage', + 'coroutine', + 'debug', + 'dofile', 'error', - 'rawget', - 'pairs', - 'xpcall', - 'rawlen', + 'getfenv', + 'getmetatable', 'io', - 'loadfile', 'ipairs', - 'table', - 'require', - 'os', + 'load', + 'loadfile', + 'loadstring', + 'math', 'module', - 'debug', - 'type', - 'getmetatable', + 'next', + 'os', + 'package', + 'pairs', + 'pcall', + 'print', 'rawequal', - 'dofile', - 'unpack', - 'math', - 'load', - 'bit32', - 'string', + 'rawget', + 'rawlen', 'rawset', - 'tostring', - 'print', - 'assert', - '_G', - 'next', + 'require', + 'select', + 'setfenv', 'setmetatable', + 'string', + 'table', 'tonumber', - 'collectgarbage', - 'coroutine' + 'tostring', + 'type', + 'unpack', + 'xpcall', + "nil", + "true", + "false" }) local LinterBlock do + local _class_0 local _parent_0 = Block local _base_0 = { + lint_mark_used = function(self, name) + if self.lint_unused_names and self.lint_unused_names[name] then + self.lint_unused_names[name] = false + return + end + if self.parent then + return self.parent:lint_mark_used(name) + end + end, + lint_check_unused = function(self) + if not (self.lint_unused_names and next(self.lint_unused_names)) then + return + end + local names_by_position = { } + for name, pos in pairs(self.lint_unused_names) do + local _continue_0 = false + repeat + if not (pos) then + _continue_0 = true + break + end + local _update_0 = pos + names_by_position[_update_0] = names_by_position[_update_0] or { } + insert(names_by_position[pos], name) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + local tuples + do + local _accum_0 = { } + local _len_0 = 1 + for pos, names in pairs(names_by_position) do + _accum_0[_len_0] = { + pos, + names + } + _len_0 = _len_0 + 1 + end + tuples = _accum_0 + end + table.sort(tuples, function(a, b) + return a[1] < b[1] + end) + for _index_0 = 1, #tuples do + local _des_0 = tuples[_index_0] + local pos, names + pos, names = _des_0[1], _des_0[2] + insert(self:get_root_block().lint_errors, { + "assigned but unused " .. tostring(table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_1 = 1, #names do + local n = names[_index_1] + _accum_0[_len_0] = "`" .. tostring(n) .. "`" + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), ", ")), + pos + }) + end + end, + render = function(self, ...) + self:lint_check_unused() + return _class_0.__parent.__base.render(self, ...) + end, block = function(self, ...) do - local _with_0 = _parent_0.block(self, ...) + local _with_0 = _class_0.__parent.__base.block(self, ...) + _with_0.block = self.block + _with_0.render = self.render + _with_0.get_root_block = self.get_root_block + _with_0.lint_check_unused = self.lint_check_unused + _with_0.lint_mark_used = self.lint_mark_used _with_0.value_compilers = self.value_compilers + _with_0.statement_compilers = self.statement_compilers return _with_0 end end } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ - __init = function(self, lint_errors, ...) - if lint_errors == nil then - lint_errors = { } + _class_0 = setmetatable({ + __init = function(self, whitelist_globals, ...) + if whitelist_globals == nil then + whitelist_globals = default_whitelist + end + _class_0.__parent.__init(self, ...) + self.get_root_block = function() + return self end - self.lint_errors = lint_errors - _parent_0.__init(self, ...) + self.lint_errors = { } local vc = self.value_compilers self.value_compilers = setmetatable({ - raw_value = function(block, name) - if name:match("^[%w_]+$") and not block:has_name(name) and not whitelist_globals[name] then - local stm = block.current_stms[block.current_stm_i] + ref = function(block, val) + local name = val[2] + if not (block:has_name(name) or whitelist_globals[name] or name:match("%.")) then insert(self.lint_errors, { - "accessing global " .. tostring(name), - stm[-1] + "accessing global `" .. tostring(name) .. "`", + val[-1] }) end - return vc.raw_value(block, name) + block:lint_mark_used(name) + return vc.ref(block, val) end }, { __index = vc }) + local sc = self.statement_compilers + self.statement_compilers = setmetatable({ + assign = function(block, node) + local names = node[2] + for _index_0 = 1, #names do + local _continue_0 = false + repeat + local name = names[_index_0] + if type(name) == "table" and name[1] == "temp_name" then + _continue_0 = true + break + end + local real_name, is_local = block:extract_assign_name(name) + if not (is_local or real_name and not block:has_name(real_name, true)) then + _continue_0 = true + break + end + if real_name == "_" then + _continue_0 = true + break + end + block.lint_unused_names = block.lint_unused_names or { } + block.lint_unused_names[real_name] = node[-1] or 0 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return sc.assign(block, node) + end + }, { + __index = sc + }) end, __base = _base_0, __name = "LinterBlock", @@ -96,7 +208,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -153,8 +268,35 @@ format_lint = function(errors, code, header) end return table.concat(formatted, "\n\n") end +local whitelist_for_file +do + local lint_config + whitelist_for_file = function(fname) + if not (lint_config) then + lint_config = { } + pcall(function() + lint_config = require("lint_config") + end) + end + if not (lint_config.whitelist_globals) then + return default_whitelist + end + local final_list = { } + for pattern, list in pairs(lint_config.whitelist_globals) do + if fname:match(pattern) then + for _index_0 = 1, #list do + local item = list[_index_0] + insert(final_list, item) + end + end + end + return setmetatable(Set(final_list), { + __index = default_whitelist + }) + end +end local lint_code -lint_code = function(code, name) +lint_code = function(code, name, whitelist_globals) if name == nil then name = "string input" end @@ -163,8 +305,9 @@ lint_code = function(code, name) if not (tree) then return nil, err end - local scope = LinterBlock() + local scope = LinterBlock(whitelist_globals) scope:stms(tree) + scope:lint_check_unused() return format_lint(scope.lint_errors, code, name) end local lint_file @@ -173,7 +316,7 @@ lint_file = function(fname) if not (f) then return nil, err end - return lint_code(f:read("*a"), fname) + return lint_code(f:read("*a"), fname, whitelist_for_file(fname)) end return { lint_code = lint_code, diff --git a/moonscript/cmd/lint.moon b/moonscript/cmd/lint.moon index b0fcd58e..103c25ed 100644 --- a/moonscript/cmd/lint.moon +++ b/moonscript/cmd/lint.moon @@ -3,68 +3,139 @@ import insert from table import Set from require "moonscript.data" import Block from require "moonscript.compile" +{type: mtype} = require("moonscript.util").moon + -- globals allowed to be referenced -whitelist_globals = Set { - 'loadstring' - 'select' +default_whitelist = Set { + '_G' '_VERSION' - 'pcall' - 'package' + 'assert' + 'bit32' + 'collectgarbage' + 'coroutine' + 'debug' + 'dofile' 'error' - 'rawget' - 'pairs' - 'xpcall' - 'rawlen' + 'getfenv' + 'getmetatable' 'io' - 'loadfile' 'ipairs' - 'table' - 'require' - 'os' + 'load' + 'loadfile' + 'loadstring' + 'math' 'module' - 'debug' - 'type' - 'getmetatable' + 'next' + 'os' + 'package' + 'pairs' + 'pcall' + 'print' 'rawequal' - 'dofile' - 'unpack' - 'math' - 'load' - 'bit32' - 'string' + 'rawget' + 'rawlen' 'rawset' - 'tostring' - 'print' - 'assert' - '_G' - 'next' + 'require' + 'select' + 'setfenv' 'setmetatable' + 'string' + 'table' 'tonumber' - 'collectgarbage' - 'coroutine' + 'tostring' + 'type' + 'unpack' + 'xpcall' + + "nil" + "true" + "false" } class LinterBlock extends Block - new: (@lint_errors={}, ...) => + new: (whitelist_globals=default_whitelist, ...) => super ... + @get_root_block = -> @ + + @lint_errors = {} vc = @value_compilers @value_compilers = setmetatable { - raw_value: (block, name) -> - - if name\match("^[%w_]+$") and not block\has_name(name) and not whitelist_globals[name] - stm = block.current_stms[block.current_stm_i] + ref: (block, val) -> + name = val[2] + unless block\has_name(name) or whitelist_globals[name] or name\match "%." insert @lint_errors, { - "accessing global #{name}" - stm[-1] + "accessing global `#{name}`" + val[-1] } - vc.raw_value block, name + block\lint_mark_used name + vc.ref block, val }, __index: vc + sc = @statement_compilers + @statement_compilers = setmetatable { + assign: (block, node) -> + names = node[2] + -- extract the names to be declared + for name in *names + -- don't include autogenerated names + if type(name) == "table" and name[1] == "temp_name" + continue + + real_name, is_local = block\extract_assign_name name + -- already defined in some other scope + unless is_local or real_name and not block\has_name real_name, true + continue + + continue if real_name == "_" + + block.lint_unused_names or= {} + block.lint_unused_names[real_name] = node[-1] or 0 + + sc.assign block, node + }, __index: sc + + lint_mark_used: (name) => + if @lint_unused_names and @lint_unused_names[name] + @lint_unused_names[name] = false + return + + if @parent + @parent\lint_mark_used name + + lint_check_unused: => + return unless @lint_unused_names and next @lint_unused_names + + names_by_position = {} + for name, pos in pairs @lint_unused_names + continue unless pos + names_by_position[pos] or= {} + insert names_by_position[pos], name + + tuples = [{pos, names} for pos,names in pairs names_by_position] + table.sort tuples, (a,b) -> a[1] < b[1] + + for {pos, names} in *tuples + insert @get_root_block!.lint_errors, { + "assigned but unused #{table.concat ["`#{n}`" for n in *names], ", "}" + pos + } + + render: (...) => + @lint_check_unused! + super ... + block: (...) => + with super ... + .block = @block + .render = @render + .get_root_block = @get_root_block + .lint_check_unused = @lint_check_unused + .lint_mark_used = @lint_mark_used .value_compilers = @value_compilers + .statement_compilers = @statement_compilers format_lint = (errors, code, header) -> return unless next errors @@ -90,19 +161,44 @@ format_lint = (errors, code, header) -> table.concat formatted, "\n\n" -lint_code = (code, name="string input") -> +-- { +-- whitelist_globals: { +-- ["some_file_pattern"]: { +-- "some_var", "another_var" +-- } +-- } +-- } +whitelist_for_file = do + local lint_config + (fname) -> + unless lint_config + lint_config = {} + pcall -> lint_config = require "lint_config" + + return default_whitelist unless lint_config.whitelist_globals + final_list = {} + for pattern, list in pairs lint_config.whitelist_globals + if fname\match(pattern) + for item in *list + insert final_list, item + + setmetatable Set(final_list), __index: default_whitelist + +lint_code = (code, name="string input", whitelist_globals) -> parse = require "moonscript.parse" tree, err = parse.string code return nil, err unless tree - scope = LinterBlock! + scope = LinterBlock whitelist_globals scope\stms tree + scope\lint_check_unused! + format_lint scope.lint_errors, code, name lint_file = (fname) -> f, err = io.open fname return nil, err unless f - lint_code f\read("*a"), fname + lint_code f\read("*a"), fname, whitelist_for_file fname { :lint_code, :lint_file } diff --git a/moonscript/cmd/moonc.lua b/moonscript/cmd/moonc.lua new file mode 100644 index 00000000..e7618ef8 --- /dev/null +++ b/moonscript/cmd/moonc.lua @@ -0,0 +1,199 @@ +local lfs = require("lfs") +local split +split = require("moonscript.util").split +local dirsep, dirsep_chars, mkdir, normalize_dir, parse_dir, parse_file, convert_path, format_time, gettime, compile_file_text, write_file, compile_and_write, is_abs_path, path_to_target +dirsep = package.config:sub(1, 1) +if dirsep == "\\" then + dirsep_chars = "\\/" +else + dirsep_chars = dirsep +end +mkdir = function(path) + local chunks = split(path, dirsep) + local accum + for _index_0 = 1, #chunks do + local dir = chunks[_index_0] + accum = accum and tostring(accum) .. tostring(dirsep) .. tostring(dir) or dir + lfs.mkdir(accum) + end + return lfs.attributes(path, "mode") +end +normalize_dir = function(path) + return path:match("^(.-)[" .. tostring(dirsep_chars) .. "]*$") .. dirsep +end +parse_dir = function(path) + return (path:match("^(.-)[^" .. tostring(dirsep_chars) .. "]*$")) +end +parse_file = function(path) + return (path:match("^.-([^" .. tostring(dirsep_chars) .. "]*)$")) +end +convert_path = function(path) + local new_path = path:gsub("%.moon$", ".lua") + if new_path == path then + new_path = path .. ".lua" + end + return new_path +end +format_time = function(time) + return ("%.3fms"):format(time * 1000) +end +do + local socket + gettime = function() + if socket == nil then + pcall(function() + socket = require("socket") + end) + if not (socket) then + socket = false + end + end + if socket then + return socket.gettime() + else + return nil, "LuaSocket needed for benchmark" + end + end +end +compile_file_text = function(text, opts) + if opts == nil then + opts = { } + end + local parse = require("moonscript.parse") + local compile = require("moonscript.compile") + local parse_time + if opts.benchmark then + parse_time = assert(gettime()) + end + local tree, err = parse.string(text) + if not (tree) then + return nil, err + end + if parse_time then + parse_time = gettime() - parse_time + end + if opts.show_parse_tree then + local dump = require("moonscript.dump") + print(dump.tree(tree)) + return true + end + local compile_time + if opts.benchmark then + compile_time = gettime() + end + do + local mod = opts.transform_module + if mod then + local file = assert(loadfile(mod)) + local fn = assert(file()) + tree = assert(fn(tree)) + end + end + local code, posmap_or_err, err_pos = compile.tree(tree) + if not (code) then + return nil, compile.format_error(posmap_or_err, err_pos, text) + end + if compile_time then + compile_time = gettime() - compile_time + end + if opts.show_posmap then + local debug_posmap + debug_posmap = require("moonscript.util").debug_posmap + print("Pos", "Lua", ">>", "Moon") + print(debug_posmap(posmap_or_err, text, code)) + return true + end + if opts.benchmark then + print(table.concat({ + opts.fname or "stdin", + "Parse time \t" .. format_time(parse_time), + "Compile time\t" .. format_time(compile_time), + "" + }, "\n")) + return true + end + return code +end +write_file = function(fname, code) + mkdir(parse_dir(fname)) + local f, err = io.open(fname, "w") + if not (f) then + return nil, err + end + assert(f:write(code)) + assert(f:write("\n")) + f:close() + return "build" +end +compile_and_write = function(src, dest, opts) + if opts == nil then + opts = { } + end + local f = io.open(src) + if not (f) then + return nil, "Can't find file" + end + local text = assert(f:read("*a")) + f:close() + local code, err = compile_file_text(text, opts) + if not code then + return nil, err + end + if code == true then + return true + end + if opts.print then + print(code) + return true + end + return write_file(dest, code) +end +is_abs_path = function(path) + local first = path:sub(1, 1) + if dirsep == "\\" then + return first == "/" or first == "\\" or path:sub(2, 1) == ":" + else + return first == dirsep + end +end +path_to_target = function(path, target_dir, base_dir) + if target_dir == nil then + target_dir = nil + end + if base_dir == nil then + base_dir = nil + end + local target = convert_path(path) + if target_dir then + target_dir = normalize_dir(target_dir) + end + if base_dir and target_dir then + local head = base_dir:match("^(.-)[^" .. tostring(dirsep_chars) .. "]*[" .. tostring(dirsep_chars) .. "]?$") + if head then + local start, stop = target:find(head, 1, true) + if start == 1 then + target = target:sub(stop + 1) + end + end + end + if target_dir then + if is_abs_path(target) then + target = parse_file(target) + end + target = target_dir .. target + end + return target +end +return { + dirsep = dirsep, + mkdir = mkdir, + normalize_dir = normalize_dir, + parse_dir = parse_dir, + parse_file = parse_file, + convert_path = convert_path, + gettime = gettime, + format_time = format_time, + path_to_target = path_to_target, + compile_file_text = compile_file_text, + compile_and_write = compile_and_write +} diff --git a/moonscript/cmd/moonc.moon b/moonscript/cmd/moonc.moon new file mode 100644 index 00000000..3e05bb07 --- /dev/null +++ b/moonscript/cmd/moonc.moon @@ -0,0 +1,197 @@ +-- assorted utilities for moonc command line tool + +lfs = require "lfs" + +import split from require "moonscript.util" + +local * + +dirsep = package.config\sub 1,1 +dirsep_chars = if dirsep == "\\" + "\\/" -- windows +else + dirsep + +-- similar to mkdir -p +mkdir = (path) -> + chunks = split path, dirsep + + local accum + for dir in *chunks + accum = accum and "#{accum}#{dirsep}#{dir}" or dir + lfs.mkdir accum + + lfs.attributes path, "mode" + +-- strips excess / and ensures path ends with / +normalize_dir = (path) -> + path\match("^(.-)[#{dirsep_chars}]*$") .. dirsep + +-- parse the directory out of a path +parse_dir = (path) -> + (path\match "^(.-)[^#{dirsep_chars}]*$") + +-- parse the filename out of a path +parse_file = (path) -> + (path\match "^.-([^#{dirsep_chars}]*)$") + +-- converts .moon to a .lua path for calcuating compile target +convert_path = (path) -> + new_path = path\gsub "%.moon$", ".lua" + if new_path == path + new_path = path .. ".lua" + new_path + +format_time = (time) -> + "%.3fms"\format time*1000 + +gettime = do + local socket + -> + if socket == nil + pcall -> + socket = require "socket" + + unless socket + socket = false + + if socket + socket.gettime() + else + nil, "LuaSocket needed for benchmark" + +-- compiles file to lua, returns lua code +-- returns nil, error on error +-- returns true if some option handled the output instead +compile_file_text = (text, opts={}) -> + parse = require "moonscript.parse" + compile = require "moonscript.compile" + + parse_time = if opts.benchmark + assert gettime! + + tree, err = parse.string text + return nil, err unless tree + + if parse_time + parse_time = gettime! - parse_time + + if opts.show_parse_tree + dump = require "moonscript.dump" + print dump.tree tree + return true + + compile_time = if opts.benchmark + gettime! + + if mod = opts.transform_module + file = assert loadfile mod + fn = assert file! + tree = assert fn tree + + code, posmap_or_err, err_pos = compile.tree tree + + unless code + return nil, compile.format_error posmap_or_err, err_pos, text + + if compile_time + compile_time = gettime() - compile_time + + if opts.show_posmap + import debug_posmap from require "moonscript.util" + print "Pos", "Lua", ">>", "Moon" + print debug_posmap posmap_or_err, text, code + return true + + if opts.benchmark + print table.concat { + opts.fname or "stdin", + "Parse time \t" .. format_time(parse_time), + "Compile time\t" .. format_time(compile_time), + "" + }, "\n" + return true + + code + +write_file = (fname, code) -> + mkdir parse_dir fname + f, err = io.open fname, "w" + unless f + return nil, err + + assert f\write code + assert f\write "\n" + f\close! + "build" + +compile_and_write = (src, dest, opts={}) -> + f = io.open src + unless f + return nil, "Can't find file" + + text = assert f\read("*a") + f\close! + + code, err = compile_file_text text, opts + + if not code + return nil, err + + if code == true + return true + + if opts.print + print code + return true + + write_file dest, code + +is_abs_path = (path) -> + first = path\sub 1, 1 + if dirsep == "\\" + first == "/" or first == "\\" or path\sub(2,1) == ":" + else + first == dirsep + + +-- calcuate where a path should be compiled to +-- target_dir: the directory to place the file (optional, from -t flag) +-- base_dir: the directory where the file came from when globbing recursively +path_to_target = (path, target_dir=nil, base_dir=nil) -> + target = convert_path path + + if target_dir + target_dir = normalize_dir target_dir + + if base_dir and target_dir + -- one directory back + head = base_dir\match("^(.-)[^#{dirsep_chars}]*[#{dirsep_chars}]?$") + + if head + start, stop = target\find head, 1, true + if start == 1 + target = target\sub(stop + 1) + + if target_dir + if is_abs_path target + target = parse_file target + + target = target_dir .. target + + target + +{ + :dirsep + :mkdir + :normalize_dir + :parse_dir + :parse_file + :convert_path + :gettime + :format_time + :path_to_target + + :compile_file_text + :compile_and_write +} diff --git a/moonscript/cmd/watchers.lua b/moonscript/cmd/watchers.lua new file mode 100644 index 00000000..7e266b8d --- /dev/null +++ b/moonscript/cmd/watchers.lua @@ -0,0 +1,268 @@ +local remove_dupes +remove_dupes = function(list, key_fn) + local seen = { } + return (function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #list do + local _continue_0 = false + repeat + local item = list[_index_0] + local key + if key_fn then + key = key_fn(item) + else + key = item + end + if seen[key] then + _continue_0 = true + break + end + seen[key] = true + local _value_0 = item + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return _accum_0 + end)() +end +local plural +plural = function(count, word) + return tostring(count) .. " " .. tostring(word) .. tostring(count == 1 and "" or "s") +end +local Watcher +do + local _class_0 + local _base_0 = { + start_msg = "Starting watch loop (Ctrl-C to exit)", + print_start = function(self, mode, misc) + return io.stderr:write(tostring(self.start_msg) .. " with " .. tostring(mode) .. " [" .. tostring(misc) .. "]\n") + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, file_list) + self.file_list = file_list + end, + __base = _base_0, + __name = "Watcher" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Watcher = _class_0 +end +local InotifyWacher +do + local _class_0 + local _parent_0 = Watcher + local _base_0 = { + get_dirs = function(self) + local parse_dir + parse_dir = require("moonscript.cmd.moonc").parse_dir + local dirs + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = self.file_list + for _index_0 = 1, #_list_0 do + local _des_0 = _list_0[_index_0] + local file_path + file_path = _des_0[1] + local dir = parse_dir(file_path) + if dir == "" then + dir = "./" + end + local _value_0 = dir + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + dirs = _accum_0 + end + return remove_dupes(dirs) + end, + each_update = function(self) + return coroutine.wrap(function() + local dirs = self:get_dirs() + self:print_start("inotify", plural(#dirs, "dir")) + local wd_table = { } + local inotify = require("inotify") + local handle = inotify.init() + for _index_0 = 1, #dirs do + local dir = dirs[_index_0] + local wd = handle:addwatch(dir, inotify.IN_CLOSE_WRITE, inotify.IN_MOVED_TO) + wd_table[wd] = dir + end + while true do + local events = handle:read() + if not (events) then + break + end + for _index_0 = 1, #events do + local _continue_0 = false + repeat + local ev = events[_index_0] + local fname = ev.name + if not (fname:match("%.moon$")) then + _continue_0 = true + break + end + local dir = wd_table[ev.wd] + if dir ~= "./" then + fname = dir .. fname + end + coroutine.yield(fname) + _continue_0 = true + until true + if not _continue_0 then + break + end + end + end + end) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "InotifyWacher", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.available = function(self) + return pcall(function() + return require("inotify") + end) + end + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + InotifyWacher = _class_0 +end +local SleepWatcher +do + local _class_0 + local _parent_0 = Watcher + local _base_0 = { + polling_rate = 1.0, + get_sleep_func = function(self) + local sleep + pcall(function() + sleep = require("socket").sleep + end) + sleep = sleep or require("moonscript")._sleep + if not (sleep) then + error("Missing sleep function; install LuaSocket") + end + return sleep + end, + each_update = function(self) + return coroutine.wrap(function() + local lfs = require("lfs") + local sleep = self:get_sleep_func() + self:print_start("polling", plural(#self.file_list, "files")) + local mod_time = { } + while true do + local _list_0 = self.file_list + for _index_0 = 1, #_list_0 do + local _continue_0 = false + repeat + local _des_0 = _list_0[_index_0] + local file + file = _des_0[1] + local time = lfs.attributes(file, "modification") + if not (time) then + mod_time[file] = nil + _continue_0 = true + break + end + if not (mod_time[file]) then + mod_time[file] = time + _continue_0 = true + break + end + if time > mod_time[file] then + mod_time[file] = time + coroutine.yield(file) + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + sleep(self.polling_rate) + end + end) + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "SleepWatcher", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + SleepWatcher = _class_0 +end +return { + Watcher = Watcher, + SleepWatcher = SleepWatcher, + InotifyWacher = InotifyWacher +} diff --git a/moonscript/cmd/watchers.moon b/moonscript/cmd/watchers.moon new file mode 100644 index 00000000..c147fc5e --- /dev/null +++ b/moonscript/cmd/watchers.moon @@ -0,0 +1,103 @@ +remove_dupes = (list, key_fn) -> + seen = {} + return for item in *list + key = if key_fn then key_fn item else item + continue if seen[key] + seen[key] = true + item + +plural = (count, word) -> + "#{count} #{word}#{count == 1 and "" or "s"}" + +-- files is a list of tuples, {source, target} +class Watcher + start_msg: "Starting watch loop (Ctrl-C to exit)" + new: (@file_list) => + + print_start: (mode, misc) => + io.stderr\write "#{@start_msg} with #{mode} [#{misc}]\n" + +class InotifyWacher extends Watcher + @available: => + pcall -> require "inotify" + + get_dirs: => + import parse_dir from require "moonscript.cmd.moonc" + dirs = for {file_path} in *@file_list + dir = parse_dir file_path + dir = "./" if dir == "" + dir + + remove_dupes dirs + + -- creates an iterator that yields a file every time it's updated + -- TODO: detect when new files are added to directories + each_update: => + coroutine.wrap -> + dirs = @get_dirs! + + @print_start "inotify", plural #dirs, "dir" + + wd_table = {} + + inotify = require "inotify" + handle = inotify.init! + + for dir in *dirs + wd = handle\addwatch dir, inotify.IN_CLOSE_WRITE, inotify.IN_MOVED_TO + wd_table[wd] = dir + + while true + events = handle\read! + break unless events -- error? + + for ev in *events + fname = ev.name + continue unless fname\match "%.moon$" + dir = wd_table[ev.wd] + fname = dir .. fname if dir != "./" + + -- TODO: check to make sure the file was in the original set + coroutine.yield fname + +class SleepWatcher extends Watcher + polling_rate: 1.0 + + -- the windows mooonscript binaries provide their own sleep function + get_sleep_func: => + local sleep + + pcall -> + sleep = require("socket").sleep + + -- TODO: this is also loading moonloader, which isn't intentional + sleep or= require("moonscript")._sleep + error "Missing sleep function; install LuaSocket" unless sleep + sleep + + each_update: => + coroutine.wrap -> + lfs = require "lfs" + sleep = @get_sleep_func! + + @print_start "polling", plural #@file_list, "files" + mod_time = {} + + while true + for {file} in *@file_list + time = lfs.attributes file, "modification" + unless time -- file no longer exists + mod_time[file] = nil + continue + + unless mod_time[file] -- file time scanned + mod_time[file] = time + continue + + if time > mod_time[file] + mod_time[file] = time + coroutine.yield file + + sleep @polling_rate + +{:Watcher, :SleepWatcher, :InotifyWacher} diff --git a/moonscript/compile.lua b/moonscript/compile.lua index 2b8e1630..66f4df59 100644 --- a/moonscript/compile.lua +++ b/moonscript/compile.lua @@ -7,25 +7,14 @@ do NameProxy, LocalName = _obj_0.NameProxy, _obj_0.LocalName end local Set -do - local _obj_0 = require("moonscript.data") - Set = _obj_0.Set -end -local ntype, has_value +Set = require("moonscript.data").Set +local ntype, value_can_be_statement do local _obj_0 = require("moonscript.types") - ntype, has_value = _obj_0.ntype, _obj_0.has_value -end -local statement_compilers -do - local _obj_0 = require("moonscript.compile.statement") - statement_compilers = _obj_0.statement_compilers -end -local value_compilers -do - local _obj_0 = require("moonscript.compile.value") - value_compilers = _obj_0.value_compilers + ntype, value_can_be_statement = _obj_0.ntype, _obj_0.value_can_be_statement end +local statement_compilers = require("moonscript.compile.statement") +local value_compilers = require("moonscript.compile.value") local concat, insert do local _obj_0 = table @@ -37,6 +26,7 @@ local mtype = util.moon.type local indent_char = " " local Line, DelayedLine, Lines, Block, RootBlock do + local _class_0 local _base_0 = { mark_pos = function(self, pos, line) if line == nil then @@ -70,6 +60,10 @@ do if "string" == _exp_0 or DelayedLine == _exp_0 then line_no = line_no + 1 out[line_no] = posmap[i] + for _ in l:gmatch("\n") do + line_no = line_no + 1 + end + out[line_no] = posmap[i] elseif Lines == _exp_0 then local _ _, line_no = l:flatten_posmap(line_no, out) @@ -100,13 +94,11 @@ do end insert(buffer, l) if "string" == type(self[i + 1]) then - local lc = l:sub(-1) - if (lc == ")" or lc == "]") and self[i + 1]:sub(1, 1) == "(" then + if l:sub(-1) ~= ',' and l:sub(-3) ~= 'end' and self[i + 1]:sub(1, 1) == "(" then insert(buffer, ";") end end insert(buffer, "\n") - local last = l elseif Lines == _exp_0 then l:flatten(indent and indent .. indent_char or indent_char, buffer) else @@ -135,7 +127,7 @@ do end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self) self.posmap = { } end, @@ -153,40 +145,33 @@ do Lines = _class_0 end do + local _class_0 local _base_0 = { pos = nil, - _append_single = function(self, item) - if Line == mtype(item) then - if not (self.pos) then - self.pos = item.pos - end - for _index_0 = 1, #item do - local value = item[_index_0] - self:_append_single(value) - end - else - insert(self, item) - end - return nil - end, append_list = function(self, items, delim) for i = 1, #items do - self:_append_single(items[i]) + self:append(items[i]) if i < #items then insert(self, delim) end end return nil end, - append = function(self, ...) - local _list_0 = { - ... - } - for _index_0 = 1, #_list_0 do - local item = _list_0[_index_0] - self:_append_single(item) + append = function(self, first, ...) + if Line == mtype(first) then + if not (self.pos) then + self.pos = first.pos + end + for _index_0 = 1, #first do + local value = first[_index_0] + self:append(value) + end + else + insert(self, first) + end + if ... then + return self:append(...) end - return nil end, render = function(self, buffer) local current = { } @@ -214,7 +199,7 @@ do insert(current, chunk) end end - if #current > 0 then + if current[1] then add_current() end return buffer @@ -224,7 +209,7 @@ do end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Line" @@ -240,6 +225,7 @@ do Line = _class_0 end do + local _class_0 local _base_0 = { prepare = function() end, render = function(self) @@ -248,7 +234,7 @@ do end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, fn) self.prepare = fn end, @@ -266,12 +252,14 @@ do DelayedLine = _class_0 end do + local _class_0 local _base_0 = { header = "do", footer = "end", export_all = false, export_proper = false, value_compilers = value_compilers, + statement_compilers = statement_compilers, __tostring = function(self) local h if "string" == type(self.header) then @@ -304,6 +292,22 @@ do end end end, + extract_assign_name = function(self, node) + local is_local = false + local real_name + local _exp_0 = mtype(node) + if LocalName == _exp_0 then + is_local = true + real_name = node:get_name(self) + elseif NameProxy == _exp_0 then + real_name = node:get_name(self) + elseif "table" == _exp_0 then + real_name = node[1] == "ref" and node[2] + elseif "string" == _exp_0 then + real_name = node + end + return real_name, is_local + end, declare = function(self, names) local undeclared do @@ -313,19 +317,7 @@ do local _continue_0 = false repeat local name = names[_index_0] - local is_local = false - local real_name - local _exp_0 = mtype(name) - if LocalName == _exp_0 then - is_local = true - real_name = name:get_name(self) - elseif NameProxy == _exp_0 then - real_name = name:get_name(self) - elseif "table" == _exp_0 then - real_name = name[1] == "ref" and name[2] - elseif "string" == _exp_0 then - real_name = name - end + local real_name, is_local = self:extract_assign_name(name) if not (is_local or real_name and not self:has_name(real_name, true)) then _continue_0 = true break @@ -428,8 +420,14 @@ do }) return name end, - add = function(self, item) - self._lines:add(item) + add = function(self, item, pos) + do + local _with_0 = self._lines + _with_0:add(item) + if pos then + _with_0:mark_pos(pos) + end + end return item end, render = function(self, buffer) @@ -440,7 +438,8 @@ do self.next:render(buffer) else if #self._lines == 0 and "string" == type(buffer[#buffer]) then - buffer[#buffer] = buffer[#buffer] .. (" " .. (unpack(Lines():add(self.footer)))) + local _update_0 = #buffer + buffer[_update_0] = buffer[_update_0] .. (" " .. (unpack(Lines():add(self.footer)))) else buffer:add(self._lines) buffer:add(self.footer) @@ -460,7 +459,7 @@ do end end, is_stm = function(self, node) - return statement_compilers[ntype(node)] ~= nil + return self.statement_compilers[ntype(node)] ~= nil end, is_value = function(self, node) local t = ntype(node) @@ -482,8 +481,12 @@ do action = node[1] end local fn = self.value_compilers[action] - if not fn then - error("Failed to compile value: " .. dump.value(node)) + if not (fn) then + error({ + "compile-error", + "Failed to find value compiler for: " .. dump.value(node), + node[-1] + }) end local out = fn(self, node, ...) if type(node) == "table" and node[-1] then @@ -522,11 +525,13 @@ do node = self.transform.statement(node) local result do - local fn = statement_compilers[ntype(node)] + local fn = self.statement_compilers[ntype(node)] if fn then result = fn(self, node, ...) else - if has_value(node) then + if value_can_be_statement(node) then + result = self:value(node) + else result = self:stm({ "assign", { @@ -536,8 +541,6 @@ do node } }) - else - result = self:value(node) end end end @@ -574,7 +577,7 @@ do end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, parent, header, footer) self.parent, self.header, self.footer = parent, header, footer self._lines = Lines() @@ -614,6 +617,7 @@ do Block = _class_0 end do + local _class_0 local _parent_0 = Block local _base_0 = { __tostring = function(self) @@ -635,11 +639,11 @@ do } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, options) self.options = options self.root = self - return _parent_0.__init(self) + return _class_0.__parent.__init(self) end, __base = _base_0, __name = "RootBlock", @@ -648,7 +652,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -667,13 +674,17 @@ do end local format_error format_error = function(msg, pos, file_str) - local line = pos_to_line(file_str, pos) - local line_str - line_str, line = get_closest_line(file_str, line) - line_str = line_str or "" + local line_message + if pos then + local line = pos_to_line(file_str, pos) + local line_str + line_str, line = get_closest_line(file_str, line) + line_str = line_str or "" + line_message = (" [%d] >> %s"):format(line, trim(line_str)) + end return concat({ "Compile error: " .. msg, - (" [%d] >> %s"):format(line, trim(line_str)) + line_message }, "\n") end local value @@ -697,27 +708,26 @@ tree = function(tree, options) return scope:root_stms(tree) end) local success, err = coroutine.resume(runner) - if not success then - local error_msg + if not (success) then + local error_msg, error_pos if type(err) == "table" then - local error_type = err[1] - if error_type == "user-error" then - error_msg = err[2] + local _exp_0 = err[1] + if "user-error" == _exp_0 or "compile-error" == _exp_0 then + error_msg, error_pos = unpack(err, 2) else - error_msg = error("Unknown error thrown", util.dump(error_msg)) + error_msg, error_pos = error("Unknown error thrown", util.dump(error_msg)) end else - error_msg = concat({ + error_msg, error_pos = concat({ err, debug.traceback(runner) }, "\n") end - return nil, error_msg, scope.last_pos - else - local lua_code = scope:render() - local posmap = scope._lines:flatten_posmap() - return lua_code, posmap + return nil, error_msg, error_pos or scope.last_pos end + local lua_code = scope:render() + local posmap = scope._lines:flatten_posmap() + return lua_code, posmap end do local data = require("moonscript.data") diff --git a/moonscript/compile.moon b/moonscript/compile.moon index 812ad74e..716c2c4e 100644 --- a/moonscript/compile.moon +++ b/moonscript/compile.moon @@ -5,10 +5,10 @@ transform = require "moonscript.transform" import NameProxy, LocalName from require "moonscript.transform.names" import Set from require "moonscript.data" -import ntype, has_value from require "moonscript.types" +import ntype, value_can_be_statement from require "moonscript.types" -import statement_compilers from require "moonscript.compile.statement" -import value_compilers from require "moonscript.compile.value" +statement_compilers = require "moonscript.compile.statement" +value_compilers = require "moonscript.compile.value" import concat, insert from table import pos_to_line, get_closest_line, trim, unpack from util @@ -45,6 +45,9 @@ class Lines when "string", DelayedLine line_no += 1 out[line_no] = posmap[i] + + line_no += 1 for _ in l\gmatch"\n" + out[line_no] = posmap[i] when Lines _, line_no = l\flatten_posmap line_no, out else @@ -68,12 +71,10 @@ class Lines -- insert breaks between ambiguous statements if "string" == type @[i + 1] - lc = l\sub(-1) - if (lc == ")" or lc == "]") and @[i + 1]\sub(1,1) == "(" + if l\sub(-1)!=',' and l\sub(-3)!='end' and @[i + 1]\sub(1,1) == "(" insert buffer, ";" insert buffer, "\n" - last = l when Lines l\flatten indent and indent .. indent_char or indent_char, buffer else @@ -96,24 +97,22 @@ class Lines class Line pos: nil - _append_single: (item) => - if Line == mtype item - -- print "appending line to line", item.pos, item - @pos = item.pos unless @pos -- bubble pos if there isn't one - @_append_single value for value in *item - else - insert self, item - nil - append_list: (items, delim) => for i = 1,#items - @_append_single items[i] + @append items[i] if i < #items then insert self, delim nil - append: (...) => - @_append_single item for item in *{...} - nil + append: (first, ...) => + if Line == mtype first + -- print "appending line to line", first.pos, first + @pos = first.pos unless @pos -- bubble pos if there isn't one + @append value for value in *first + else + insert self, first + + if ... + @append ... -- todo: try to remove concats from here render: (buffer) => @@ -136,7 +135,7 @@ class Line else insert current, chunk - if #current > 0 + if current[1] add_current! buffer @@ -162,6 +161,7 @@ class Block export_proper: false value_compilers: value_compilers + statement_compilers: statement_compilers __tostring: => h = if "string" == type @header @@ -211,22 +211,28 @@ class Block if fn = @_listeners[name] fn self, ... + extract_assign_name: (node) => + is_local = false + real_name = switch mtype node + when LocalName + is_local = true + node\get_name self + when NameProxy + node\get_name self + when "table" + node[1] == "ref" and node[2] + when "string" + -- TOOD: some legacy transfomers might use string for ref + node + + real_name, is_local + declare: (names) => undeclared = for name in *names - is_local = false - real_name = switch mtype name - when LocalName - is_local = true - name\get_name self - when NameProxy then name\get_name self - when "table" - name[1] == "ref" and name[2] - when "string" - -- TODO: don't use string literal as ref - name - + real_name, is_local = @extract_assign_name name continue unless is_local or real_name and not @has_name real_name, true - -- put exported names so they can be assigned to in deeper scope + -- this also puts exported names so they can be assigned a new value in + -- deeper scope @put_name real_name continue if @name_exported real_name real_name @@ -288,9 +294,11 @@ class Block @stm {"assign", {name}, {value}} name - -- add a line object - add: (item) => - @_lines\add item + -- add something to the line buffer + add: (item, pos) => + with @_lines + \add item + \mark_pos pos if pos item -- todo: pass in buffer as argument @@ -320,7 +328,7 @@ class Block \append ... is_stm: (node) => - statement_compilers[ntype node] != nil + @statement_compilers[ntype node] != nil is_value: (node) => t = ntype node @@ -341,7 +349,12 @@ class Block node[1] fn = @value_compilers[action] - error "Failed to compile value: "..dump.value node if not fn + unless fn + error { + "compile-error" + "Failed to find value compiler for: " .. dump.value node + node[-1] + } out = fn self, node, ... @@ -362,14 +375,14 @@ class Block return if not node -- skip blank statements node = @transform.statement node - result = if fn = statement_compilers[ntype(node)] - fn self, node, ... + result = if fn = @statement_compilers[ntype(node)] + fn @, node, ... else - -- coerce value into statement - if has_value node - @stm {"assign", {"_"}, {node}} - else + if value_can_be_statement node @value node + else + -- coerce value into statement + @stm {"assign", {"_"}, {node}} if result if type(node) == "table" and type(result) == "table" and node[-1] @@ -392,6 +405,8 @@ class Block nil + -- takes the existing set of lines and replaces them with the result of + -- calling fn on them splice: (fn) => lines = {"lines", @_lines} @_lines = Lines! @@ -416,12 +431,15 @@ class RootBlock extends Block table.concat buffer format_error = (msg, pos, file_str) -> - line = pos_to_line file_str, pos - line_str, line = get_closest_line file_str, line - line_str = line_str or "" + line_message = if pos + line = pos_to_line file_str, pos + line_str, line = get_closest_line file_str, line + line_str = line_str or "" + (" [%d] >> %s")\format line, trim line_str + concat { "Compile error: "..msg - (" [%d] >> %s")\format line, trim line_str + line_message }, "\n" value = (value) -> @@ -440,21 +458,23 @@ tree = (tree, options={}) -> scope\root_stms tree success, err = coroutine.resume runner - if not success - error_msg = if type(err) == "table" - error_type = err[1] - if error_type == "user-error" - err[2] - else - error "Unknown error thrown", util.dump error_msg + + unless success + error_msg, error_pos = if type(err) == "table" + switch err[1] + when "user-error", "compile-error" + unpack err, 2 + else + -- unknown error, bubble it + error "Unknown error thrown", util.dump error_msg else concat {err, debug.traceback runner}, "\n" - nil, error_msg, scope.last_pos - else - lua_code = scope\render! - posmap = scope._lines\flatten_posmap! - lua_code, posmap + return nil, error_msg, error_pos or scope.last_pos + + lua_code = scope\render! + posmap = scope._lines\flatten_posmap! + lua_code, posmap -- mmmm with data = require "moonscript.data" diff --git a/moonscript/compile/statement.lua b/moonscript/compile/statement.lua index 65cc2d5e..7f1c9063 100644 --- a/moonscript/compile/statement.lua +++ b/moonscript/compile/statement.lua @@ -1,18 +1,13 @@ -local util = require("moonscript.util") -local data = require("moonscript.data") -local reversed, unpack -reversed, unpack = util.reversed, util.unpack local ntype -do - local _obj_0 = require("moonscript.types") - ntype = _obj_0.ntype -end +ntype = require("moonscript.types").ntype local concat, insert do local _obj_0 = table concat, insert = _obj_0.concat, _obj_0.insert end -local statement_compilers = { +local unpack +unpack = require("moonscript.util").unpack +return { raw = function(self, node) return self:add(node[2]) end, @@ -62,7 +57,7 @@ local statement_compilers = { end end, assign = function(self, node) - local _, names, values = unpack(node) + local names, values = unpack(node, 2) local undeclared = self:declare(names) local declare = "local " .. concat(undeclared, ", ") local has_fndef = false @@ -79,7 +74,7 @@ local statement_compilers = { _with_0:append(declare) else if #undeclared > 0 then - self:add(declare) + self:add(declare, node[-1]) end _with_0:append_list((function() local _accum_0 = { } @@ -141,7 +136,7 @@ local statement_compilers = { current = next end for _index_0 = 4, #node do - cond = node[_index_0] + local cond = node[_index_0] add_clause(cond) end return root @@ -155,7 +150,7 @@ local statement_compilers = { end end, ["while"] = function(self, node) - local _, cond, block = unpack(node) + local cond, block = unpack(node, 2) do local _with_0 = self:block(self:line("while ", self:value(cond), " do")) _with_0:stms(block) @@ -163,7 +158,7 @@ local statement_compilers = { end end, ["for"] = function(self, node) - local _, name, bounds, block = unpack(node) + local name, bounds, block = unpack(node, 2) local loop = self:line("for ", self:name(name), " = ", self:value({ "explist", unpack(bounds) @@ -178,7 +173,7 @@ local statement_compilers = { end end, foreach = function(self, node) - local _, names, exps, block = unpack(node) + local names, exps, block = unpack(node, 2) local loop do local _with_0 = self:line() @@ -215,7 +210,7 @@ local statement_compilers = { end end, export = function(self, node) - local _, names = unpack(node) + local names = unpack(node, 2) if type(names) == "string" then if names == "*" then self.export_all = true @@ -243,6 +238,3 @@ local statement_compilers = { end, noop = function(self) end } -return { - statement_compilers = statement_compilers -} diff --git a/moonscript/compile/statement.moon b/moonscript/compile/statement.moon index cf89e144..25f53fd0 100644 --- a/moonscript/compile/statement.moon +++ b/moonscript/compile/statement.moon @@ -1,12 +1,10 @@ -util = require "moonscript.util" -data = require "moonscript.data" - -import reversed, unpack from util import ntype from require "moonscript.types" import concat, insert from table -statement_compilers = +import unpack from require "moonscript.util" + +{ raw: (node) => @add node[2] lines: (node) => @@ -28,10 +26,10 @@ statement_compilers = \append_list [@name name for name in *names], ", " assign: (node) => - _, names, values = unpack node + names, values = unpack node, 2 undeclared = @declare names - declare = "local "..concat(undeclared, ", ") + declare = "local " .. concat(undeclared, ", ") has_fndef = false i = 1 @@ -44,7 +42,7 @@ statement_compilers = if #undeclared == #names and not has_fndef \append declare else - @add declare if #undeclared > 0 + @add declare, node[-1] if #undeclared > 0 \append_list [@value name for name in *names], ", " \append " = " @@ -85,12 +83,12 @@ statement_compilers = \stms block while: (node) => - _, cond, block = unpack node + cond, block = unpack node, 2 with @block @line "while ", @value(cond), " do" \stms block for: (node) => - _, name, bounds, block = unpack node + name, bounds, block = unpack node, 2 loop = @line "for ", @name(name), " = ", @value({"explist", unpack bounds}), " do" with @block loop \declare {name} @@ -99,7 +97,7 @@ statement_compilers = -- for x in y ... -- {"foreach", {names...}, {exp...}, body} foreach: (node) => - _, names, exps, block = unpack node + names, exps, block = unpack node, 2 loop = with @line! \append "for " @@ -114,7 +112,7 @@ statement_compilers = \stms block export: (node) => - _, names = unpack node + names = unpack node, 2 if type(names) == "string" if names == "*" @export_all = true @@ -136,6 +134,4 @@ statement_compilers = \stms node[2] noop: => -- nothing! - - -{ :statement_compilers } +} diff --git a/moonscript/compile/value.lua b/moonscript/compile/value.lua index 0a9acd80..ca6e5041 100644 --- a/moonscript/compile/value.lua +++ b/moonscript/compile/value.lua @@ -1,15 +1,9 @@ local util = require("moonscript.util") local data = require("moonscript.data") local ntype -do - local _obj_0 = require("moonscript.types") - ntype = _obj_0.ntype -end +ntype = require("moonscript.types").ntype local user_error -do - local _obj_0 = require("moonscript.errors") - user_error = _obj_0.user_error -end +user_error = require("moonscript.errors").user_error local concat, insert do local _obj_0 = table @@ -22,7 +16,17 @@ local string_chars = { ["\r"] = "\\r", ["\n"] = "\\n" } -local value_compilers = { +return { + scoped = function(self, node) + local _, before, value, after + _, before, value, after = node[1], node[2], node[3], node[4] + _ = before and before:call(self) + do + local _with_0 = self:value(value) + _ = after and after:call(self) + return _with_0 + end + end, exp = function(self, node) local _comp _comp = function(i, value) @@ -67,7 +71,7 @@ local value_compilers = { return self:line("(", self:value(node[2]), ")") end, string = function(self, node) - local _, delim, inner = unpack(node) + local delim, inner = unpack(node, 2) local end_delim = delim:gsub("%[", "]") if delim == "'" or delim == '"' then inner = inner:gsub("[\r\n]", string_chars) @@ -77,11 +81,13 @@ local value_compilers = { chain = function(self, node) local callee = node[2] local callee_type = ntype(callee) - if callee == -1 then + local item_offset = 3 + if callee_type == "dot" or callee_type == "colon" or callee_type == "index" then callee = self:get("scope_var") - if not callee then + if not (callee) then user_error("Short-dot syntax must be called within a with block") end + item_offset = 2 end if callee_type == "ref" and callee[2] == "super" or callee == "super" then do @@ -101,7 +107,7 @@ local value_compilers = { elseif t == "dot" then return ".", tostring(arg) elseif t == "colon" then - return ":", arg, chain_item(node[3]) + return ":", tostring(arg) elseif t == "colon_stub" then return user_error("Uncalled colon stub") else @@ -118,7 +124,7 @@ local value_compilers = { local actions do local _with_0 = self:line() - for _index_0 = 3, #node do + for _index_0 = item_offset, #node do local action = node[_index_0] _with_0:append(chain_item(action)) end @@ -127,7 +133,7 @@ local value_compilers = { return self:line(callee_value, actions) end, fndef = function(self, node) - local _, args, whitelist, arrow, block = unpack(node) + local args, whitelist, arrow, block = unpack(node, 2) local default_args = { } local self_args = { } local arg_names @@ -232,7 +238,7 @@ local value_compilers = { end end, table = function(self, node) - local _, items = unpack(node) + local items = unpack(node, 2) do local _with_0 = self:block("{", "}") local format_line @@ -252,9 +258,7 @@ local value_compilers = { else assign = self:line("[", _with_0:value(key), "]") end - _with_0:set("current_block", key) local out = self:line(assign, " = ", _with_0:value(value)) - _with_0:set("current_block", nil) return out else return self:line(_with_0:value(tuple[1])) @@ -282,6 +286,9 @@ local value_compilers = { number = function(self, node) return node[2] end, + bitnot = function(self, node) + return self:line("~", self:value(node[2])) + end, length = function(self, node) return self:line("#", self:value(node[2])) end, @@ -289,16 +296,52 @@ local value_compilers = { return self:line("not ", self:value(node[2])) end, self = function(self, node) - return "self." .. self:name(node[2]) + local field_name = self:name(node[2]) + if data.lua_keywords[field_name] then + return self:value({ + "chain", + "self", + { + "index", + { + "string", + '"', + field_name + } + } + }) + else + return "self." .. tostring(field_name) + end end, self_class = function(self, node) - return "self.__class." .. self:name(node[2]) + local field_name = self:name(node[2]) + if data.lua_keywords[field_name] then + return self:value({ + "chain", + "self", + { + "dot", + "__class" + }, + { + "index", + { + "string", + '"', + field_name + } + } + }) + else + return "self.__class." .. tostring(field_name) + end end, self_colon = function(self, node) - return "self:" .. self:name(node[2]) + return "self:" .. tostring(self:name(node[2])) end, self_class_colon = function(self, node) - return "self.__class:" .. self:name(node[2]) + return "self.__class:" .. tostring(self:name(node[2])) end, ref = function(self, value) do @@ -316,6 +359,3 @@ local value_compilers = { return tostring(value) end } -return { - value_compilers = value_compilers -} diff --git a/moonscript/compile/value.moon b/moonscript/compile/value.moon index 27aa9f2f..1bfa754b 100644 --- a/moonscript/compile/value.moon +++ b/moonscript/compile/value.moon @@ -14,7 +14,13 @@ string_chars = { "\n": "\\n" } -value_compilers = +{ + scoped: (node) => + {_, before, value, after} = node + before and before\call @ + with @value value + after and after\call @ + -- list of values separated by binary operators exp: (node) => _comp = (i, value) -> @@ -34,7 +40,7 @@ value_compilers = @line "(", @value(node[2]), ")" string: (node) => - _, delim, inner = unpack node + delim, inner = unpack node, 2 end_delim = delim\gsub "%[", "]" if delim == "'" or delim == '"' inner = inner\gsub "[\r\n]", string_chars @@ -44,10 +50,13 @@ value_compilers = chain: (node) => callee = node[2] callee_type = ntype callee + item_offset = 3 - if callee == -1 + if callee_type == "dot" or callee_type == "colon" or callee_type == "index" callee = @get "scope_var" - if not callee then user_error "Short-dot syntax must be called within a with block" + unless callee + user_error "Short-dot syntax must be called within a with block" + item_offset = 2 -- TODO: don't use string literals as ref if callee_type == "ref" and callee[2] == "super" or callee == "super" @@ -64,7 +73,7 @@ value_compilers = elseif t == "dot" ".", tostring arg elseif t == "colon" - ":", arg, chain_item(node[3]) + ":", tostring arg elseif t == "colon_stub" user_error "Uncalled colon stub" else @@ -77,12 +86,12 @@ value_compilers = callee_value = @line "(", callee_value, ")" if ntype(callee) == "exp" actions = with @line! - \append chain_item action for action in *node[3,] + \append chain_item action for action in *node[item_offset,] @line callee_value, actions fndef: (node) => - _, args, whitelist, arrow, block = unpack node + args, whitelist, arrow, block = unpack node, 2 default_args = {} self_args = {} @@ -128,7 +137,7 @@ value_compilers = .header = "function("..concat(arg_names, ", ")..")" table: (node) => - _, items = unpack node + items = unpack node, 2 with @block "{", "}" format_line = (tuple) -> if #tuple == 2 @@ -143,9 +152,7 @@ value_compilers = else @line "[", \value(key), "]" - \set "current_block", key out = @line assign, " = ", \value(value) - \set "current_block", nil out else @line \value tuple[1] @@ -166,6 +173,9 @@ value_compilers = number: (node) => node[2] + bitnot: (node) => + @line "~", @value node[2] + length: (node) => @line "#", @value node[2] @@ -173,16 +183,29 @@ value_compilers = @line "not ", @value node[2] self: (node) => - "self."..@name node[2] + field_name = @name node[2] + if data.lua_keywords[field_name] + @value {"chain", "self", {"index", { + "string", '"', field_name + }}} + else + "self.#{field_name}" self_class: (node) => - "self.__class."..@name node[2] + field_name = @name node[2] + + if data.lua_keywords[field_name] + @value {"chain", "self", {"dot", "__class"}, {"index", { + "string", '"', field_name + }}} + else + "self.__class.#{field_name}" self_colon: (node) => - "self:"..@name node[2] + "self:#{@name node[2]}" self_class_colon: (node) => - "self.__class:"..@name node[2] + "self.__class:#{@name node[2]}" -- a variable reference ref: (value) => @@ -197,5 +220,4 @@ value_compilers = @send "varargs" tostring value - -{ :value_compilers } +} diff --git a/moonscript/data.lua b/moonscript/data.lua index 69176969..f33f708d 100644 --- a/moonscript/data.lua +++ b/moonscript/data.lua @@ -5,15 +5,16 @@ do end local Set Set = function(items) - local self = { } + local _tbl_0 = { } for _index_0 = 1, #items do - local key = items[_index_0] - self[key] = true + local k = items[_index_0] + _tbl_0[k] = true end - return self + return _tbl_0 end local Stack do + local _class_0 local _base_0 = { __tostring = function(self) return "" @@ -21,24 +22,22 @@ do pop = function(self) return remove(self) end, - push = function(self, value) + push = function(self, value, ...) insert(self, value) - return value + if ... then + return self:push(...) + else + return value + end end, top = function(self) return self[#self] end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, ...) - local _list_0 = { - ... - } - for _index_0 = 1, #_list_0 do - local v = _list_0[_index_0] - self:push(v) - end + self:push(...) return nil end, __base = _base_0, diff --git a/moonscript/data.moon b/moonscript/data.moon index a9fbf328..4bb9b4fb 100644 --- a/moonscript/data.moon +++ b/moonscript/data.moon @@ -2,30 +2,30 @@ import concat, remove, insert from table -Set = (items) -> - self = {} - self[key] = true for key in *items - self +Set = (items) -> {k,true for k in *items} class Stack __tostring: => "" new: (...) => - @push v for v in *{...} + @push ... nil pop: => - remove self + remove @ - push: (value) => - insert self, value - value + push: (value, ...) => + insert @, value + if ... + @push ... + else + value top: => self[#self] -lua_keywords = Set{ +lua_keywords = Set { 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', 'function', 'if', 'in', 'local', 'nil', 'not', 'or', diff --git a/moonscript/dump.lua b/moonscript/dump.lua index 8b69ec93..26cdd7a3 100644 --- a/moonscript/dump.lua +++ b/moonscript/dump.lua @@ -29,11 +29,16 @@ value = function(op) end local tree tree = function(block) - local _list_0 = block - for _index_0 = 1, #_list_0 do - value = _list_0[_index_0] - print(flat_value(value)) - end + return table.concat((function() + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #block do + local value = block[_index_0] + _accum_0[_len_0] = flat_value(value) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(), "\n") end return { value = value, diff --git a/moonscript/dump.moon b/moonscript/dump.moon index a6373daa..6fbeb375 100644 --- a/moonscript/dump.moon +++ b/moonscript/dump.moon @@ -12,7 +12,7 @@ value = (op) -> flat_value op tree = (block) -> - print flat_value value for value in *block + table.concat [flat_value value for value in *block], "\n" { :value, :tree } diff --git a/moonscript/errors.lua b/moonscript/errors.lua index f8d3254a..d85c3162 100644 --- a/moonscript/errors.lua +++ b/moonscript/errors.lua @@ -18,7 +18,7 @@ local lookup_line lookup_line = function(fname, pos, cache) if not cache[fname] then do - local _with_0 = io.open(fname) + local _with_0 = assert(io.open(fname)) cache[fname] = _with_0:read("*a") _with_0:close() end @@ -78,8 +78,8 @@ rewrite_traceback = function(text, err) local cache = { } local rewrite_single rewrite_single = function(trace) - local fname, line, msg = trace:match('^%[string "(.-)"]:(%d+): (.*)$') - local tbl = line_tables[fname] + local fname, line, msg = trace:match('^(.-):(%d+): (.*)$') + local tbl = line_tables["@" .. tostring(fname)] if fname and tbl then return concat({ fname, diff --git a/moonscript/errors.moon b/moonscript/errors.moon index a5bd94e8..b04c5671 100644 --- a/moonscript/errors.moon +++ b/moonscript/errors.moon @@ -12,7 +12,7 @@ user_error = (...) -> -- find the line number of `pos` chars into fname lookup_line = (fname, pos, cache) -> if not cache[fname] - with io.open fname + with assert io.open(fname) cache[fname] = \read "*a" \close! pos_to_line cache[fname], pos @@ -56,8 +56,8 @@ rewrite_traceback = (text, err) -> cache = {} -- loaded file cache rewrite_single = (trace) -> - fname, line, msg = trace\match '^%[string "(.-)"]:(%d+): (.*)$' - tbl = line_tables[fname] + fname, line, msg = trace\match '^(.-):(%d+): (.*)$' + tbl = line_tables["@#{fname}"] if fname and tbl concat { fname, ":" diff --git a/moonscript/parse.lua b/moonscript/parse.lua index 0a28f58b..da6731e7 100644 --- a/moonscript/parse.lua +++ b/moonscript/parse.lua @@ -1,658 +1,245 @@ - -local util = require"moonscript.util" - -local lpeg = require"lpeg" - local debug_grammar = false - -local data = require"moonscript.data" -local types = require"moonscript.types" - -local ntype = types.ntype - -local dump = util.dump -local trim = util.trim - -local getfenv = util.getfenv -local setfenv = util.setfenv -local unpack = util.unpack - -local Stack = data.Stack - -local function count_indent(str) - local sum = 0 - for v in str:gmatch("[\t ]") do - if v == ' ' then sum = sum + 1 end - if v == '\t' then sum = sum + 4 end - end - return sum -end - -local R, S, V, P = lpeg.R, lpeg.S, lpeg.V, lpeg.P -local C, Ct, Cmt, Cg, Cb, Cc = lpeg.C, lpeg.Ct, lpeg.Cmt, lpeg.Cg, lpeg.Cb, lpeg.Cc - +local lpeg = require("lpeg") lpeg.setmaxstack(10000) - -local White = S" \t\r\n"^0 -local _Space = S" \t"^0 -local Break = P"\r"^-1 * P"\n" -local Stop = Break + -1 -local Indent = C(S"\t "^0) / count_indent - -local Comment = P"--" * (1 - S"\r\n")^0 * #Stop -local Space = _Space * Comment^-1 -local SomeSpace = S" \t"^1 * Comment^-1 - -local SpaceBreak = Space * Break -local EmptyLine = SpaceBreak - -local AlphaNum = R("az", "AZ", "09", "__") - -local _Name = C(R("az", "AZ", "__") * AlphaNum^0) -local Name = Space * _Name - -local Num = P"0x" * R("09", "af", "AF")^1 + - ( - R"09"^1 * (P"." * R"09"^1)^-1 + - P"." * R"09"^1 - ) * (S"eE" * P"-"^-1 * R"09"^1)^-1 - -Num = Space * (Num / function(value) return {"number", value} end) - -local FactorOp = Space * C(S"+-") -local TermOp = Space * C(S"*/%^") - -local Shebang = P"#!" * P(1 - Stop)^0 - --- can't have P(false) because it causes preceding patterns not to run -local Cut = P(function() return false end) - -local function ensure(patt, finally) - return patt * finally + finally * Cut -end - --- auto declare Proper variables with lpeg.V -local function wrap_env(fn) - local env = getfenv(fn) - local wrap_name = V - - if debug_grammar then - local indent = 0 - local indent_char = " " - - local function iprint(...) - local args = {...} - for i=1,#args do - args[i] = tostring(args[i]) - end - - io.stdout:write(indent_char:rep(indent) .. table.concat(args, ", ") .. "\n") - end - - wrap_name = function(name) - local v = V(name) - v = Cmt("", function() - iprint("* " .. name) - indent = indent + 1 - return true - end) * Cmt(v, function(str, pos, ...) - iprint(name, true) - indent = indent - 1 - return true, ... - end) + Cmt("", function() - iprint(name, false) - indent = indent - 1 - return false - end) - return v - end - end - - return setfenv(fn, setmetatable({}, { - __index = function(self, name) - local value = env[name] - if value ~= nil then return value end - - if name:match"^[A-Z][A-Za-z0-9]*$" then - local v = wrap_name(name) - rawset(self, name, v) - return v - end - error("unknown variable referenced: "..name) - end - })) -end - -local function extract_line(str, start_pos) - str = str:sub(start_pos) - m = str:match"^(.-)\n" - if m then return m end - return str:match"^.-$" -end - -local function mark(name) - return function(...) - return {name, ...} - end -end - -local function insert_pos(pos, value) - if type(value) == "table" then - value[-1] = pos - end - return value -end - -local function pos(patt) - return (lpeg.Cp() * patt) / insert_pos -end - -local function got(what) - return Cmt("", function(str, pos, ...) - local cap = {...} - print("++ got "..what, "["..extract_line(str, pos).."]") - return true - end) -end - -local function flatten(tbl) - if #tbl == 1 then - return tbl[1] - end - return tbl -end - -local function flatten_or_mark(name) - return function(tbl) - if #tbl == 1 then return tbl[1] end - table.insert(tbl, 1, name) - return tbl - end -end - --- makes sure the last item in a chain is an index -local _chain_assignable = { index = true, dot = true, slice = true } - -local function is_assignable(node) - if node == "..." then - return false - end - - local t = ntype(node) - return t == "ref" or t == "self" or t == "value" or t == "self_class" or - t == "chain" and _chain_assignable[ntype(node[#node])] or - t == "table" -end - -local function check_assignable(str, pos, value) - if is_assignable(value) then - return true, value - end - return false -end - -local flatten_explist = flatten_or_mark"explist" -local function format_assign(lhs_exps, assign) - if not assign then - return flatten_explist(lhs_exps) - end - - for _, assign_exp in ipairs(lhs_exps) do - if not is_assignable(assign_exp) then - error {assign_exp, "left hand expression is not assignable"} - end - end - - local t = ntype(assign) - if t == "assign" then - return {"assign", lhs_exps, unpack(assign, 2)} - elseif t == "update" then - return {"update", lhs_exps[1], unpack(assign, 2)} - end - - error "unknown assign expression" -end - --- the if statement only takes a single lhs, so we wrap in table to git to --- "assign" tuple format -local function format_single_assign(lhs, assign) - if assign then - return format_assign({lhs}, assign) - end - return lhs -end - -local function sym(chars) - return Space * chars -end - -local function symx(chars) - return chars -end - -local function simple_string(delim, allow_interpolation) - local inner = P('\\'..delim) + "\\\\" + (1 - P(delim)) - if allow_interpolation then - inter = symx"#{" * V"Exp" * sym"}" - inner = (C((inner - inter)^1) + inter / mark"interpolate")^0 - else - inner = C(inner^0) - end - - return C(symx(delim)) * - inner * sym(delim) / mark"string" -end - -local function wrap_func_arg(value) - return {"call", {value}} -end - --- DOCME -local function flatten_func(callee, args) - if #args == 0 then return callee end - - args = {"call", args} - if ntype(callee) == "chain" then - -- check for colon stub that needs arguments - if ntype(callee[#callee]) == "colon_stub" then - local stub = callee[#callee] - stub[1] = "colon" - table.insert(stub, args) - else - table.insert(callee, args) - end - - return callee - end - - return {"chain", callee, args} -end - -local function flatten_string_chain(str, chain, args) - if not chain then return str end - return flatten_func({"chain", str, unpack(chain)}, args) -end - --- transforms a statement that has a line decorator -local function wrap_decorator(stm, dec) - if not dec then return stm end - return { "decorated", stm, dec } -end - --- wrap if statement if there is a conditional decorator -local function wrap_if(stm, cond) - if cond then - local pass, fail = unpack(cond) - if fail then fail = {"else", {fail}} end - return {"if", cond[2], {stm}, fail} - end - return stm -end - -local function check_lua_string(str, pos, right, left) - return #left == #right -end - --- :name in table literal -local function self_assign(name) - return {{"key_literal", name}, name} -end - local err_msg = "Failed to parse:%s\n [%d] >> %s" - -local build_grammar = wrap_env(function() - local _indent = Stack(0) -- current indent - local _do_stack = Stack(0) - - local last_pos = 0 -- used to know where to report error - local function check_indent(str, pos, indent) - last_pos = pos - return _indent:top() == indent - end - - local function advance_indent(str, pos, indent) - local top = _indent:top() - if top ~= -1 and indent > _indent:top() then - _indent:push(indent) - return true - end - end - - local function push_indent(str, pos, indent) - _indent:push(indent) - return true - end - - local function pop_indent(str, pos) - if not _indent:pop() then error("unexpected outdent") end - return true - end - - - local function check_do(str, pos, do_node) - local top = _do_stack:top() - if top == nil or top then - return true, do_node - end - return false - end - - local function disable_do(str_pos) - _do_stack:push(false) - return true - end - - local function enable_do(str_pos) - _do_stack:push(true) - return true - end - - local function pop_do(str, pos) - if nil == _do_stack:pop() then error("unexpected do pop") end - return true - end - - local DisableDo = Cmt("", disable_do) - local EnableDo = Cmt("", enable_do) - local PopDo = Cmt("", pop_do) - - local keywords = {} - local function key(chars) - keywords[chars] = true - return Space * chars * -AlphaNum - end - - local function op(word) - local patt = Space * C(word) - if word:match("^%w*$") then - keywords[word] = true - patt = patt * -AlphaNum - end - return patt - end - - -- make sure name is not a keyword - local Name = Cmt(Name, function(str, pos, name) - if keywords[name] then return false end - return true - end) / trim - - local SelfName = Space * "@" * ( - "@" * (_Name / mark"self_class" + Cc"self.__class") + - _Name / mark"self" + Cc"self") - - local KeyName = SelfName + Space * _Name / mark"key_literal" - local VarArg = Space * P"..." / trim - - local g = lpeg.P{ - File, - File = Shebang^-1 * (Block + Ct""), - Block = Ct(Line * (Break^1 * Line)^0), - CheckIndent = Cmt(Indent, check_indent), -- validates line is in correct indent - Line = (CheckIndent * Statement + Space * #Stop), - - Statement = pos( - Import + While + With + For + ForEach + Switch + Return + - Local + Export + BreakLoop + - Ct(ExpList) * (Update + Assign)^-1 / format_assign - ) * Space * (( - -- statement decorators - key"if" * Exp * (key"else" * Exp)^-1 * Space / mark"if" + - key"unless" * Exp / mark"unless" + - CompInner / mark"comprehension" - ) * Space)^-1 / wrap_decorator, - - Body = Space^-1 * Break * EmptyLine^0 * InBlock + Ct(Statement), -- either a statement, or an indented block - - Advance = #Cmt(Indent, advance_indent), -- Advances the indent, gives back whitespace for CheckIndent - PushIndent = Cmt(Indent, push_indent), - PreventIndent = Cmt(Cc(-1), push_indent), - PopIndent = Cmt("", pop_indent), - InBlock = Advance * Block * PopIndent, - - Local = key"local" * ((op"*" + op"^") / mark"declare_glob" + Ct(NameList) / mark"declare_with_shadows"), - - Import = key"import" * Ct(ImportNameList) * SpaceBreak^0 * key"from" * Exp / mark"import", - ImportName = (sym"\\" * Ct(Cc"colon_stub" * Name) + Name), - ImportNameList = SpaceBreak^0 * ImportName * ((SpaceBreak^1 + sym"," * SpaceBreak^0) * ImportName)^0, - - BreakLoop = Ct(key"break"/trim) + Ct(key"continue"/trim), - - Return = key"return" * (ExpListLow/mark"explist" + C"") / mark"return", - - WithExp = Ct(ExpList) * Assign^-1 / format_assign, - With = key"with" * DisableDo * ensure(WithExp, PopDo) * key"do"^-1 * Body / mark"with", - - Switch = key"switch" * DisableDo * ensure(Exp, PopDo) * key"do"^-1 * Space^-1 * Break * SwitchBlock / mark"switch", - - SwitchBlock = EmptyLine^0 * Advance * Ct(SwitchCase * (Break^1 * SwitchCase)^0 * (Break^1 * SwitchElse)^-1) * PopIndent, - SwitchCase = key"when" * Ct(ExpList) * key"then"^-1 * Body / mark"case", - SwitchElse = key"else" * Body / mark"else", - - IfCond = Exp * Assign^-1 / format_single_assign, - - If = key"if" * IfCond * key"then"^-1 * Body * - ((Break * CheckIndent)^-1 * EmptyLine^0 * key"elseif" * pos(IfCond) * key"then"^-1 * Body / mark"elseif")^0 * - ((Break * CheckIndent)^-1 * EmptyLine^0 * key"else" * Body / mark"else")^-1 / mark"if", - - Unless = key"unless" * IfCond * key"then"^-1 * Body * - ((Break * CheckIndent)^-1 * EmptyLine^0 * key"else" * Body / mark"else")^-1 / mark"unless", - - While = key"while" * DisableDo * ensure(Exp, PopDo) * key"do"^-1 * Body / mark"while", - - For = key"for" * DisableDo * ensure(Name * sym"=" * Ct(Exp * sym"," * Exp * (sym"," * Exp)^-1), PopDo) * - key"do"^-1 * Body / mark"for", - - ForEach = key"for" * Ct(AssignableNameList) * key"in" * DisableDo * ensure(Ct(sym"*" * Exp / mark"unpack" + ExpList), PopDo) * key"do"^-1 * Body / mark"foreach", - - Do = key"do" * Body / mark"do", - - Comprehension = sym"[" * Exp * CompInner * sym"]" / mark"comprehension", - - TblComprehension = sym"{" * Ct(Exp * (sym"," * Exp)^-1) * CompInner * sym"}" / mark"tblcomprehension", - - CompInner = Ct((CompForEach + CompFor) * CompClause^0), - CompForEach = key"for" * Ct(NameList) * key"in" * (sym"*" * Exp / mark"unpack" + Exp) / mark"foreach", - CompFor = key "for" * Name * sym"=" * Ct(Exp * sym"," * Exp * (sym"," * Exp)^-1) / mark"for", - CompClause = CompFor + CompForEach + key"when" * Exp / mark"when", - - Assign = sym"=" * (Ct(With + If + Switch) + Ct(TableBlock + ExpListLow)) / mark"assign", - Update = ((sym"..=" + sym"+=" + sym"-=" + sym"*=" + sym"/=" + sym"%=" + sym"or=" + sym"and=") / trim) * Exp / mark"update", - - -- we can ignore precedence for now - OtherOps = op"or" + op"and" + op"<=" + op">=" + op"~=" + op"!=" + op"==" + op".." + op"<" + op">", - - Assignable = Cmt(DotChain + Chain, check_assignable) + Name + SelfName, - - Exp = Ct(Value * ((OtherOps + FactorOp + TermOp) * Value)^0) / flatten_or_mark"exp", - - -- Exp = Ct(Factor * (OtherOps * Factor)^0) / flatten_or_mark"exp", - -- Factor = Ct(Term * (FactorOp * Term)^0) / flatten_or_mark"exp", - -- Term = Ct(Value * (TermOp * Value)^0) / flatten_or_mark"exp", - - SimpleValue = - If + Unless + - Switch + - With + - ClassDecl + - ForEach + For + While + - Cmt(Do, check_do) + - sym"-" * -SomeSpace * Exp / mark"minus" + - sym"#" * Exp / mark"length" + - key"not" * Exp / mark"not" + - TblComprehension + - TableLit + - Comprehension + - FunLit + - Num, - - ChainValue = -- a function call or an object access - StringChain + - ((Chain + DotChain + Callable) * Ct(InvokeArgs^-1)) / flatten_func, - - Value = pos( - SimpleValue + - Ct(KeyValueList) / mark"table" + - ChainValue), - - SliceValue = SimpleValue + ChainValue, - - StringChain = String * - (Ct((ColonCall + ColonSuffix) * ChainTail^-1) * Ct(InvokeArgs^-1))^-1 / flatten_string_chain, - - String = Space * DoubleString + Space * SingleString + LuaString, - SingleString = simple_string("'"), - DoubleString = simple_string('"', true), - - LuaString = Cg(LuaStringOpen, "string_open") * Cb"string_open" * Break^-1 * - C((1 - Cmt(C(LuaStringClose) * Cb"string_open", check_lua_string))^0) * - LuaStringClose / mark"string", - - LuaStringOpen = sym"[" * P"="^0 * "[" / trim, - LuaStringClose = "]" * P"="^0 * "]", - - Callable = Name / mark"ref" + SelfName + VarArg + Parens / mark"parens", - Parens = sym"(" * Exp * sym")", - - FnArgs = symx"(" * Ct(ExpList^-1) * sym")" + sym"!" * -P"=" * Ct"", - - ChainTail = ChainItem^1 * ColonSuffix^-1 + ColonSuffix, - - -- a list of funcalls and indexes on a callable - Chain = Callable * ChainTail / mark"chain", - - -- shorthand dot call for use in with statement - DotChain = - (sym"." * Cc(-1) * (_Name / mark"dot") * ChainTail^-1) / mark"chain" + - (sym"\\" * Cc(-1) * ( - (_Name * Invoke / mark"colon") * ChainTail^-1 + - (_Name / mark"colon_stub") - )) / mark"chain", - - ChainItem = - Invoke + - Slice + - symx"[" * Exp/mark"index" * sym"]" + - symx"." * _Name/mark"dot" + - ColonCall, - - Slice = symx"[" * (SliceValue + Cc(1)) * sym"," * (SliceValue + Cc"") * - (sym"," * SliceValue)^-1 *sym"]" / mark"slice", - - ColonCall = symx"\\" * (_Name * Invoke) / mark"colon", - ColonSuffix = symx"\\" * _Name / mark"colon_stub", - - Invoke = FnArgs/mark"call" + - SingleString / wrap_func_arg + - DoubleString / wrap_func_arg, - - TableValue = KeyValue + Ct(Exp), - - TableLit = sym"{" * Ct( - TableValueList^-1 * sym","^-1 * - (SpaceBreak * TableLitLine * (sym","^-1 * SpaceBreak * TableLitLine)^0 * sym","^-1)^-1 - ) * White * sym"}" / mark"table", - - TableValueList = TableValue * (sym"," * TableValue)^0, - TableLitLine = PushIndent * ((TableValueList * PopIndent) + (PopIndent * Cut)) + Space, - - -- the unbounded table - TableBlockInner = Ct(KeyValueLine * (SpaceBreak^1 * KeyValueLine)^0), - TableBlock = SpaceBreak^1 * Advance * ensure(TableBlockInner, PopIndent) / mark"table", - - ClassDecl = key"class" * -P":" * (Assignable + Cc(nil)) * (key"extends" * PreventIndent * ensure(Exp, PopIndent) + C"")^-1 * (ClassBlock + Ct("")) / mark"class", - - ClassBlock = SpaceBreak^1 * Advance * - Ct(ClassLine * (SpaceBreak^1 * ClassLine)^0) * PopIndent, - ClassLine = CheckIndent * (( - KeyValueList / mark"props" + - Statement / mark"stm" + - Exp / mark"stm" - ) * sym","^-1), - - Export = key"export" * ( - Cc"class" * ClassDecl + - op"*" + op"^" + - Ct(NameList) * (sym"=" * Ct(ExpListLow))^-1) / mark"export", - - KeyValue = (sym":" * -SomeSpace * Name) / self_assign + Ct((KeyName + sym"[" * Exp * sym"]" + DoubleString + SingleString) * symx":" * (Exp + TableBlock)), - KeyValueList = KeyValue * (sym"," * KeyValue)^0, - KeyValueLine = CheckIndent * KeyValueList * sym","^-1, - - FnArgsDef = sym"(" * Ct(FnArgDefList^-1) * - (key"using" * Ct(NameList + Space * "nil") + Ct"") * - sym")" + Ct"" * Ct"", - - FnArgDefList = FnArgDef * (sym"," * FnArgDef)^0 * (sym"," * Ct(VarArg))^0 + Ct(VarArg), - FnArgDef = Ct((Name + SelfName) * (sym"=" * Exp)^-1), - - FunLit = FnArgsDef * - (sym"->" * Cc"slim" + sym"=>" * Cc"fat") * - (Body + Ct"") / mark"fndef", - - NameList = Name * (sym"," * Name)^0, - NameOrDestructure = Name + TableLit, - AssignableNameList = NameOrDestructure * (sym"," * NameOrDestructure)^0, - - ExpList = Exp * (sym"," * Exp)^0, - ExpListLow = Exp * ((sym"," + sym";") * Exp)^0, - - InvokeArgs = -P"-" * (ExpList * (sym"," * (TableBlock + SpaceBreak * Advance * ArgBlock * TableBlock^-1) + TableBlock)^-1 + TableBlock), - ArgBlock = ArgLine * (sym"," * SpaceBreak * ArgLine)^0 * PopIndent, - ArgLine = CheckIndent * ExpList - } - - return { - _g = White * g * White * -1, - match = function(self, str, ...) - - local pos_to_line = function(pos) - return util.pos_to_line(str, pos) - end - - local get_line = function(num) - return util.get_line(str, num) - end - - local tree - local pass, err = pcall(function(...) - tree = self._g:match(str, ...) - end, ...) - - -- regular error, let it bubble up - if type(err) == "string" then - error(err) - end - - if not tree then - local pos = last_pos - local msg - - if err then - local node - node, msg = unpack(err) - msg = msg and " " .. msg - pos = node[-1] - end - - local line_no = pos_to_line(pos) - local line_str = get_line(line_no) or "" - - return nil, err_msg:format(msg or "", line_no, trim(line_str)) - end - return tree - end - } +local Stack +Stack = require("moonscript.data").Stack +local trim, pos_to_line, get_line +do + local _obj_0 = require("moonscript.util") + trim, pos_to_line, get_line = _obj_0.trim, _obj_0.pos_to_line, _obj_0.get_line +end +local unpack +unpack = require("moonscript.util").unpack +local wrap_env +wrap_env = require("moonscript.parse.env").wrap_env +local R, S, V, P, C, Ct, Cmt, Cg, Cb, Cc +R, S, V, P, C, Ct, Cmt, Cg, Cb, Cc = lpeg.R, lpeg.S, lpeg.V, lpeg.P, lpeg.C, lpeg.Ct, lpeg.Cmt, lpeg.Cg, lpeg.Cb, lpeg.Cc +local White, Break, Stop, Comment, Space, SomeSpace, SpaceBreak, EmptyLine, AlphaNum, Num, Shebang, L, _Name +do + local _obj_0 = require("moonscript.parse.literals") + White, Break, Stop, Comment, Space, SomeSpace, SpaceBreak, EmptyLine, AlphaNum, Num, Shebang, L, _Name = _obj_0.White, _obj_0.Break, _obj_0.Stop, _obj_0.Comment, _obj_0.Space, _obj_0.SomeSpace, _obj_0.SpaceBreak, _obj_0.EmptyLine, _obj_0.AlphaNum, _obj_0.Num, _obj_0.Shebang, _obj_0.L, _obj_0.Name +end +local SpaceName = Space * _Name +Num = Space * (Num / function(v) + return { + "number", + v + } +end) +local Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, wrap_decorator, check_lua_string, self_assign, got +do + local _obj_0 = require("moonscript.parse.util") + Indent, Cut, ensure, extract_line, mark, pos, flatten_or_mark, is_assignable, check_assignable, format_assign, format_single_assign, sym, symx, simple_string, wrap_func_arg, join_chain, wrap_decorator, check_lua_string, self_assign, got = _obj_0.Indent, _obj_0.Cut, _obj_0.ensure, _obj_0.extract_line, _obj_0.mark, _obj_0.pos, _obj_0.flatten_or_mark, _obj_0.is_assignable, _obj_0.check_assignable, _obj_0.format_assign, _obj_0.format_single_assign, _obj_0.sym, _obj_0.symx, _obj_0.simple_string, _obj_0.wrap_func_arg, _obj_0.join_chain, _obj_0.wrap_decorator, _obj_0.check_lua_string, _obj_0.self_assign, _obj_0.got +end +local build_grammar = wrap_env(debug_grammar, function(root) + local _indent = Stack(0) + local _do_stack = Stack(0) + local state = { + last_pos = 0 + } + local check_indent + check_indent = function(str, pos, indent) + state.last_pos = pos + return _indent:top() == indent + end + local advance_indent + advance_indent = function(str, pos, indent) + local top = _indent:top() + if top ~= -1 and indent > top then + _indent:push(indent) + return true + end + end + local push_indent + push_indent = function(str, pos, indent) + _indent:push(indent) + return true + end + local pop_indent + pop_indent = function() + assert(_indent:pop(), "unexpected outdent") + return true + end + local check_do + check_do = function(str, pos, do_node) + local top = _do_stack:top() + if top == nil or top then + return true, do_node + end + return false + end + local disable_do + disable_do = function() + _do_stack:push(false) + return true + end + local pop_do + pop_do = function() + assert(_do_stack:pop() ~= nil, "unexpected do pop") + return true + end + local DisableDo = Cmt("", disable_do) + local PopDo = Cmt("", pop_do) + local keywords = { } + local key + key = function(chars) + keywords[chars] = true + return Space * chars * -AlphaNum + end + local op + op = function(chars) + local patt = Space * C(chars) + if chars:match("^%w*$") then + keywords[chars] = true + patt = patt * -AlphaNum + end + return patt + end + local Name = Cmt(SpaceName, function(str, pos, name) + if keywords[name] then + return false + end + return true + end) / trim + local SelfName = Space * "@" * ("@" * (_Name / mark("self_class") + Cc("self.__class")) + _Name / mark("self") + Cc("self")) + local KeyName = SelfName + Space * _Name / mark("key_literal") + local VarArg = Space * P("...") / trim + local g = P({ + root or File, + File = Shebang ^ -1 * (Block + Ct("")), + Block = Ct(Line * (Break ^ 1 * Line) ^ 0), + CheckIndent = Cmt(Indent, check_indent), + Line = (CheckIndent * Statement + Space * L(Stop)), + Statement = pos(Import + While + With + For + ForEach + Switch + Return + Local + Export + BreakLoop + Ct(ExpList) * (Update + Assign) ^ -1 / format_assign) * Space * ((key("if") * Exp * (key("else") * Exp) ^ -1 * Space / mark("if") + key("unless") * Exp / mark("unless") + CompInner / mark("comprehension")) * Space) ^ -1 / wrap_decorator, + Body = Space ^ -1 * Break * EmptyLine ^ 0 * InBlock + Ct(Statement), + Advance = L(Cmt(Indent, advance_indent)), + PushIndent = Cmt(Indent, push_indent), + PreventIndent = Cmt(Cc(-1), push_indent), + PopIndent = Cmt("", pop_indent), + InBlock = Advance * Block * PopIndent, + Local = key("local") * ((op("*") + op("^")) / mark("declare_glob") + Ct(NameList) / mark("declare_with_shadows")), + Import = key("import") * Ct(ImportNameList) * SpaceBreak ^ 0 * key("from") * Exp / mark("import"), + ImportName = (sym("\\") * Ct(Cc("colon") * Name) + Name), + ImportNameList = SpaceBreak ^ 0 * ImportName * ((SpaceBreak ^ 1 + sym(",") * SpaceBreak ^ 0) * ImportName) ^ 0, + BreakLoop = Ct(key("break") / trim) + Ct(key("continue") / trim), + Return = key("return") * (ExpListLow / mark("explist") + C("")) / mark("return"), + WithExp = Ct(ExpList) * Assign ^ -1 / format_assign, + With = key("with") * DisableDo * ensure(WithExp, PopDo) * key("do") ^ -1 * Body / mark("with"), + Switch = key("switch") * DisableDo * ensure(Exp, PopDo) * key("do") ^ -1 * Space ^ -1 * Break * SwitchBlock / mark("switch"), + SwitchBlock = EmptyLine ^ 0 * Advance * Ct(SwitchCase * (Break ^ 1 * SwitchCase) ^ 0 * (Break ^ 1 * SwitchElse) ^ -1) * PopIndent, + SwitchCase = key("when") * Ct(ExpList) * key("then") ^ -1 * Body / mark("case"), + SwitchElse = key("else") * Body / mark("else"), + IfCond = Exp * Assign ^ -1 / format_single_assign, + IfElse = (Break * EmptyLine ^ 0 * CheckIndent) ^ -1 * key("else") * Body / mark("else"), + IfElseIf = (Break * EmptyLine ^ 0 * CheckIndent) ^ -1 * key("elseif") * pos(IfCond) * key("then") ^ -1 * Body / mark("elseif"), + If = key("if") * IfCond * key("then") ^ -1 * Body * IfElseIf ^ 0 * IfElse ^ -1 / mark("if"), + Unless = key("unless") * IfCond * key("then") ^ -1 * Body * IfElseIf ^ 0 * IfElse ^ -1 / mark("unless"), + While = key("while") * DisableDo * ensure(Exp, PopDo) * key("do") ^ -1 * Body / mark("while"), + For = key("for") * DisableDo * ensure(Name * sym("=") * Ct(Exp * sym(",") * Exp * (sym(",") * Exp) ^ -1), PopDo) * key("do") ^ -1 * Body / mark("for"), + ForEach = key("for") * Ct(AssignableNameList) * key("in") * DisableDo * ensure(Ct(sym("*") * Exp / mark("unpack") + ExpList), PopDo) * key("do") ^ -1 * Body / mark("foreach"), + Do = key("do") * Body / mark("do"), + Comprehension = sym("[") * Exp * CompInner * sym("]") / mark("comprehension"), + TblComprehension = sym("{") * Ct(Exp * (sym(",") * Exp) ^ -1) * CompInner * sym("}") / mark("tblcomprehension"), + CompInner = Ct((CompForEach + CompFor) * CompClause ^ 0), + CompForEach = key("for") * Ct(AssignableNameList) * key("in") * (sym("*") * Exp / mark("unpack") + Exp) / mark("foreach"), + CompFor = key("for" * Name * sym("=") * Ct(Exp * sym(",") * Exp * (sym(",") * Exp) ^ -1) / mark("for")), + CompClause = CompFor + CompForEach + key("when") * Exp / mark("when"), + Assign = sym("=") * (Ct(With + If + Switch) + Ct(TableBlock + ExpListLow)) / mark("assign"), + Update = ((sym("..=") + sym("+=") + sym("-=") + sym("*=") + sym("/=") + sym("%=") + sym("or=") + sym("and=") + sym("&=") + sym("|=") + sym(">>=") + sym("<<=")) / trim) * Exp / mark("update"), + CharOperators = Space * C(S("+-*/%^><|&")), + WordOperators = op("or") + op("and") + op("<=") + op(">=") + op("~=") + op("!=") + op("==") + op("..") + op("<<") + op(">>") + op("//"), + BinaryOperator = (WordOperators + CharOperators) * SpaceBreak ^ 0, + Assignable = Cmt(Chain, check_assignable) + Name + SelfName, + Exp = Ct(Value * (BinaryOperator * Value) ^ 0) / flatten_or_mark("exp"), + SimpleValue = If + Unless + Switch + With + ClassDecl + ForEach + For + While + Cmt(Do, check_do) + sym("-") * -SomeSpace * Exp / mark("minus") + sym("#") * Exp / mark("length") + sym("~") * Exp / mark("bitnot") + key("not") * Exp / mark("not") + TblComprehension + TableLit + Comprehension + FunLit + Num, + ChainValue = (Chain + Callable) * Ct(InvokeArgs ^ -1) / join_chain, + Value = pos(SimpleValue + Ct(KeyValueList) / mark("table") + ChainValue + String), + SliceValue = Exp, + String = Space * DoubleString + Space * SingleString + LuaString, + SingleString = simple_string("'"), + DoubleString = simple_string('"', true), + LuaString = Cg(LuaStringOpen, "string_open") * Cb("string_open") * Break ^ -1 * C((1 - Cmt(C(LuaStringClose) * Cb("string_open"), check_lua_string)) ^ 0) * LuaStringClose / mark("string"), + LuaStringOpen = sym("[") * P("=") ^ 0 * "[" / trim, + LuaStringClose = "]" * P("=") ^ 0 * "]", + Callable = pos(Name / mark("ref")) + SelfName + VarArg + Parens / mark("parens"), + Parens = sym("(") * SpaceBreak ^ 0 * Exp * SpaceBreak ^ 0 * sym(")"), + FnArgs = symx("(") * SpaceBreak ^ 0 * Ct(FnArgsExpList ^ -1) * SpaceBreak ^ 0 * sym(")") + sym("!") * -P("=") * Ct(""), + FnArgsExpList = Exp * ((Break + sym(",")) * White * Exp) ^ 0, + Chain = (Callable + String + -S(".\\")) * ChainItems / mark("chain") + Space * (DotChainItem * ChainItems ^ -1 + ColonChain) / mark("chain"), + ChainItems = ChainItem ^ 1 * ColonChain ^ -1 + ColonChain, + ChainItem = Invoke + DotChainItem + Slice + symx("[") * Exp / mark("index") * sym("]"), + DotChainItem = symx(".") * _Name / mark("dot"), + ColonChainItem = symx("\\") * _Name / mark("colon"), + ColonChain = ColonChainItem * (Invoke * ChainItems ^ -1) ^ -1, + Slice = symx("[") * (SliceValue + Cc(1)) * sym(",") * (SliceValue + Cc("")) * (sym(",") * SliceValue) ^ -1 * sym("]") / mark("slice"), + Invoke = FnArgs / mark("call") + SingleString / wrap_func_arg + DoubleString / wrap_func_arg + L(P("[")) * LuaString / wrap_func_arg, + TableValue = KeyValue + Ct(Exp), + TableLit = sym("{") * Ct(TableValueList ^ -1 * sym(",") ^ -1 * (SpaceBreak * TableLitLine * (sym(",") ^ -1 * SpaceBreak * TableLitLine) ^ 0 * sym(",") ^ -1) ^ -1) * White * sym("}") / mark("table"), + TableValueList = TableValue * (sym(",") * TableValue) ^ 0, + TableLitLine = PushIndent * ((TableValueList * PopIndent) + (PopIndent * Cut)) + Space, + TableBlockInner = Ct(KeyValueLine * (SpaceBreak ^ 1 * KeyValueLine) ^ 0), + TableBlock = SpaceBreak ^ 1 * Advance * ensure(TableBlockInner, PopIndent) / mark("table"), + ClassDecl = key("class") * -P(":") * (Assignable + Cc(nil)) * (key("extends") * PreventIndent * ensure(Exp, PopIndent) + C("")) ^ -1 * (ClassBlock + Ct("")) / mark("class"), + ClassBlock = SpaceBreak ^ 1 * Advance * Ct(ClassLine * (SpaceBreak ^ 1 * ClassLine) ^ 0) * PopIndent, + ClassLine = CheckIndent * ((KeyValueList / mark("props") + Statement / mark("stm") + Exp / mark("stm")) * sym(",") ^ -1), + Export = key("export") * (Cc("class") * ClassDecl + op("*") + op("^") + Ct(NameList) * (sym("=") * Ct(ExpListLow)) ^ -1) / mark("export"), + KeyValue = (sym(":") * -SomeSpace * Name * lpeg.Cp()) / self_assign + Ct((KeyName + sym("[") * Exp * sym("]") + Space * DoubleString + Space * SingleString) * symx(":") * (Exp + TableBlock + SpaceBreak ^ 1 * Exp)), + KeyValueList = KeyValue * (sym(",") * KeyValue) ^ 0, + KeyValueLine = CheckIndent * KeyValueList * sym(",") ^ -1, + FnArgsDef = sym("(") * White * Ct(FnArgDefList ^ -1) * (key("using") * Ct(NameList + Space * "nil") + Ct("")) * White * sym(")") + Ct("") * Ct(""), + FnArgDefList = FnArgDef * ((sym(",") + Break) * White * FnArgDef) ^ 0 * ((sym(",") + Break) * White * Ct(VarArg)) ^ 0 + Ct(VarArg), + FnArgDef = Ct((Name + SelfName) * (sym("=") * Exp) ^ -1), + FunLit = FnArgsDef * (sym("->") * Cc("slim") + sym("=>") * Cc("fat")) * (Body + Ct("")) / mark("fndef"), + NameList = Name * (sym(",") * Name) ^ 0, + NameOrDestructure = Name + TableLit, + AssignableNameList = NameOrDestructure * (sym(",") * NameOrDestructure) ^ 0, + ExpList = Exp * (sym(",") * Exp) ^ 0, + ExpListLow = Exp * ((sym(",") + sym(";")) * Exp) ^ 0, + InvokeArgs = -P("-") * (ExpList * (sym(",") * (TableBlock + SpaceBreak * Advance * ArgBlock * TableBlock ^ -1) + TableBlock) ^ -1 + TableBlock), + ArgBlock = ArgLine * (sym(",") * SpaceBreak * ArgLine) ^ 0 * PopIndent, + ArgLine = CheckIndent * ExpList + }) + return g, state end) - +local file_parser +file_parser = function() + local g, state = build_grammar() + local file_grammar = White * g * White * -1 + return { + match = function(self, str) + local tree + local _, err = xpcall((function() + tree = file_grammar:match(str) + end), function(err) + return debug.traceback(err, 2) + end) + if type(err) == "string" then + return nil, err + end + if not (tree) then + local msg + local err_pos = state.last_pos + if err then + local node + node, msg = unpack(err) + if msg then + msg = " " .. msg + end + err_pos = node[-1] + end + local line_no = pos_to_line(str, err_pos) + local line_str = get_line(str, line_no) or "" + return nil, err_msg:format(msg or "", line_no, trim(line_str)) + end + return tree + end + } +end return { - extract_line = extract_line, - - -- parse a string - -- returns tree, or nil and error message - string = function (str) - local g = build_grammar() - return g:match(str) - end + extract_line = extract_line, + build_grammar = build_grammar, + string = function(str) + return file_parser():match(str) + end } - diff --git a/moonscript/parse.moon b/moonscript/parse.moon new file mode 100644 index 00000000..7fd5de6f --- /dev/null +++ b/moonscript/parse.moon @@ -0,0 +1,361 @@ +debug_grammar = false +lpeg = require "lpeg" + +lpeg.setmaxstack 10000 -- whoa + +err_msg = "Failed to parse:%s\n [%d] >> %s" + +import Stack from require "moonscript.data" +import trim, pos_to_line, get_line from require "moonscript.util" +import unpack from require "moonscript.util" +import wrap_env from require "moonscript.parse.env" + +{ + :R, :S, :V, :P, :C, :Ct, :Cmt, :Cg, :Cb, :Cc +} = lpeg + +{ + :White, :Break, :Stop, :Comment, :Space, :SomeSpace, :SpaceBreak, :EmptyLine, + :AlphaNum, :Num, :Shebang, :L + Name: _Name +} = require "moonscript.parse.literals" + +SpaceName = Space * _Name +Num = Space * (Num / (v) -> {"number", v}) + +{ + :Indent, :Cut, :ensure, :extract_line, :mark, :pos, :flatten_or_mark, + :is_assignable, :check_assignable, :format_assign, :format_single_assign, + :sym, :symx, :simple_string, :wrap_func_arg, :join_chain, + :wrap_decorator, :check_lua_string, :self_assign, :got + +} = require "moonscript.parse.util" + + +build_grammar = wrap_env debug_grammar, (root) -> + _indent = Stack 0 + _do_stack = Stack 0 + + state = { + -- last pos we saw, used to report error location + last_pos: 0 + } + + check_indent = (str, pos, indent) -> + state.last_pos = pos + _indent\top! == indent + + advance_indent = (str, pos, indent) -> + top = _indent\top! + if top != -1 and indent > top + _indent\push indent + true + + push_indent = (str, pos, indent) -> + _indent\push indent + true + + pop_indent = -> + assert _indent\pop!, "unexpected outdent" + true + + check_do = (str, pos, do_node) -> + top = _do_stack\top! + if top == nil or top + return true, do_node + false + + disable_do = -> + _do_stack\push false + true + + pop_do = -> + assert _do_stack\pop! != nil, "unexpected do pop" + true + + DisableDo = Cmt "", disable_do + PopDo = Cmt "", pop_do + + keywords = {} + key = (chars) -> + keywords[chars] = true + Space * chars * -AlphaNum + + op = (chars) -> + patt = Space * C chars + -- it's a word, treat like keyword + if chars\match "^%w*$" + keywords[chars] = true + patt *= -AlphaNum + + patt + + Name = Cmt(SpaceName, (str, pos, name) -> + return false if keywords[name] + true + ) / trim + + SelfName = Space * "@" * ( + "@" * (_Name / mark"self_class" + Cc"self.__class") + + _Name / mark"self" + + Cc"self" -- @ by itself + ) + + KeyName = SelfName + Space * _Name / mark"key_literal" + VarArg = Space * P"..." / trim + + g = P { + root or File + File: Shebang^-1 * (Block + Ct"") + Block: Ct(Line * (Break^1 * Line)^0) + CheckIndent: Cmt(Indent, check_indent), -- validates line is in correct indent + Line: (CheckIndent * Statement + Space * L(Stop)) + + Statement: pos( + Import + While + With + For + ForEach + Switch + Return + + Local + Export + BreakLoop + + Ct(ExpList) * (Update + Assign)^-1 / format_assign + ) * Space * (( + -- statement decorators + key"if" * Exp * (key"else" * Exp)^-1 * Space / mark"if" + + key"unless" * Exp / mark"unless" + + CompInner / mark"comprehension" + ) * Space)^-1 / wrap_decorator + + Body: Space^-1 * Break * EmptyLine^0 * InBlock + Ct(Statement) -- either a statement, or an indented block + + Advance: L Cmt(Indent, advance_indent) -- Advances the indent, gives back whitespace for CheckIndent + PushIndent: Cmt(Indent, push_indent) + PreventIndent: Cmt(Cc(-1), push_indent) + PopIndent: Cmt("", pop_indent) + InBlock: Advance * Block * PopIndent + + Local: key"local" * ((op"*" + op"^") / mark"declare_glob" + Ct(NameList) / mark"declare_with_shadows") + + Import: key"import" * Ct(ImportNameList) * SpaceBreak^0 * key"from" * Exp / mark"import" + ImportName: (sym"\\" * Ct(Cc"colon" * Name) + Name) + ImportNameList: SpaceBreak^0 * ImportName * ((SpaceBreak^1 + sym"," * SpaceBreak^0) * ImportName)^0 + + BreakLoop: Ct(key"break"/trim) + Ct(key"continue"/trim) + + Return: key"return" * (ExpListLow/mark"explist" + C"") / mark"return" + + WithExp: Ct(ExpList) * Assign^-1 / format_assign + With: key"with" * DisableDo * ensure(WithExp, PopDo) * key"do"^-1 * Body / mark"with" + + Switch: key"switch" * DisableDo * ensure(Exp, PopDo) * key"do"^-1 * Space^-1 * Break * SwitchBlock / mark"switch" + + SwitchBlock: EmptyLine^0 * Advance * Ct(SwitchCase * (Break^1 * SwitchCase)^0 * (Break^1 * SwitchElse)^-1) * PopIndent + SwitchCase: key"when" * Ct(ExpList) * key"then"^-1 * Body / mark"case" + SwitchElse: key"else" * Body / mark"else" + + IfCond: Exp * Assign^-1 / format_single_assign + + IfElse: (Break * EmptyLine^0 * CheckIndent)^-1 * key"else" * Body / mark"else" + IfElseIf: (Break * EmptyLine^0 * CheckIndent)^-1 * key"elseif" * pos(IfCond) * key"then"^-1 * Body / mark"elseif" + + If: key"if" * IfCond * key"then"^-1 * Body * IfElseIf^0 * IfElse^-1 / mark"if" + Unless: key"unless" * IfCond * key"then"^-1 * Body * IfElseIf^0 * IfElse^-1 / mark"unless" + + While: key"while" * DisableDo * ensure(Exp, PopDo) * key"do"^-1 * Body / mark"while" + + For: key"for" * DisableDo * ensure(Name * sym"=" * Ct(Exp * sym"," * Exp * (sym"," * Exp)^-1), PopDo) * + key"do"^-1 * Body / mark"for" + + ForEach: key"for" * Ct(AssignableNameList) * key"in" * DisableDo * ensure(Ct(sym"*" * Exp / mark"unpack" + ExpList), PopDo) * key"do"^-1 * Body / mark"foreach" + + Do: key"do" * Body / mark"do" + + Comprehension: sym"[" * Exp * CompInner * sym"]" / mark"comprehension" + + TblComprehension: sym"{" * Ct(Exp * (sym"," * Exp)^-1) * CompInner * sym"}" / mark"tblcomprehension" + + CompInner: Ct((CompForEach + CompFor) * CompClause^0) + CompForEach: key"for" * Ct(AssignableNameList) * key"in" * (sym"*" * Exp / mark"unpack" + Exp) / mark"foreach" + CompFor: key "for" * Name * sym"=" * Ct(Exp * sym"," * Exp * (sym"," * Exp)^-1) / mark"for" + CompClause: CompFor + CompForEach + key"when" * Exp / mark"when" + + Assign: sym"=" * (Ct(With + If + Switch) + Ct(TableBlock + ExpListLow)) / mark"assign" + Update: ((sym"..=" + sym"+=" + sym"-=" + sym"*=" + sym"/=" + sym"%=" + sym"or=" + sym"and=" + sym"&=" + sym"|=" + sym">>=" + sym"<<=") / trim) * Exp / mark"update" + + CharOperators: Space * C(S"+-*/%^><|&") + WordOperators: op"or" + op"and" + op"<=" + op">=" + op"~=" + op"!=" + op"==" + op".." + op"<<" + op">>" + op"//" + BinaryOperator: (WordOperators + CharOperators) * SpaceBreak^0 + + Assignable: Cmt(Chain, check_assignable) + Name + SelfName + Exp: Ct(Value * (BinaryOperator * Value)^0) / flatten_or_mark"exp" + + SimpleValue: + If + Unless + + Switch + + With + + ClassDecl + + ForEach + For + While + + Cmt(Do, check_do) + + sym"-" * -SomeSpace * Exp / mark"minus" + + sym"#" * Exp / mark"length" + + sym"~" * Exp / mark"bitnot" + + key"not" * Exp / mark"not" + + TblComprehension + + TableLit + + Comprehension + + FunLit + + Num + + -- a function call or an object access + ChainValue: (Chain + Callable) * Ct(InvokeArgs^-1) / join_chain + + Value: pos( + SimpleValue + + Ct(KeyValueList) / mark"table" + + ChainValue + + String) + + SliceValue: Exp + + String: Space * DoubleString + Space * SingleString + LuaString + SingleString: simple_string("'") + DoubleString: simple_string('"', true) + + LuaString: Cg(LuaStringOpen, "string_open") * Cb"string_open" * Break^-1 * + C((1 - Cmt(C(LuaStringClose) * Cb"string_open", check_lua_string))^0) * + LuaStringClose / mark"string" + + LuaStringOpen: sym"[" * P"="^0 * "[" / trim + LuaStringClose: "]" * P"="^0 * "]" + + Callable: pos(Name / mark"ref") + SelfName + VarArg + Parens / mark"parens" + Parens: sym"(" * SpaceBreak^0 * Exp * SpaceBreak^0 * sym")" + + FnArgs: symx"(" * SpaceBreak^0 * Ct(FnArgsExpList^-1) * SpaceBreak^0 * sym")" + sym"!" * -P"=" * Ct"" + FnArgsExpList: Exp * ((Break + sym",") * White * Exp)^0 + + Chain: (Callable + String + -S".\\") * ChainItems / mark"chain" + + Space * (DotChainItem * ChainItems^-1 + ColonChain) / mark"chain" + + ChainItems: ChainItem^1 * ColonChain^-1 + ColonChain + + ChainItem: + Invoke + + DotChainItem + + Slice + + symx"[" * Exp/mark"index" * sym"]" + + DotChainItem: symx"." * _Name/mark"dot" + ColonChainItem: symx"\\" * _Name / mark"colon" + ColonChain: ColonChainItem * (Invoke * ChainItems^-1)^-1 + + Slice: symx"[" * (SliceValue + Cc(1)) * sym"," * (SliceValue + Cc"") * + (sym"," * SliceValue)^-1 *sym"]" / mark"slice" + + Invoke: FnArgs / mark"call" + + SingleString / wrap_func_arg + + DoubleString / wrap_func_arg + + L(P"[") * LuaString / wrap_func_arg + + TableValue: KeyValue + Ct(Exp) + + TableLit: sym"{" * Ct( + TableValueList^-1 * sym","^-1 * + (SpaceBreak * TableLitLine * (sym","^-1 * SpaceBreak * TableLitLine)^0 * sym","^-1)^-1 + ) * White * sym"}" / mark"table" + + TableValueList: TableValue * (sym"," * TableValue)^0 + TableLitLine: PushIndent * ((TableValueList * PopIndent) + (PopIndent * Cut)) + Space + + -- the unbounded table + TableBlockInner: Ct(KeyValueLine * (SpaceBreak^1 * KeyValueLine)^0) + TableBlock: SpaceBreak^1 * Advance * ensure(TableBlockInner, PopIndent) / mark"table" + + ClassDecl: key"class" * -P":" * (Assignable + Cc(nil)) * (key"extends" * PreventIndent * ensure(Exp, PopIndent) + C"")^-1 * (ClassBlock + Ct("")) / mark"class" + + ClassBlock: SpaceBreak^1 * Advance * + Ct(ClassLine * (SpaceBreak^1 * ClassLine)^0) * PopIndent + ClassLine: CheckIndent * (( + KeyValueList / mark"props" + + Statement / mark"stm" + + Exp / mark"stm" + ) * sym","^-1) + + Export: key"export" * ( + Cc"class" * ClassDecl + + op"*" + op"^" + + Ct(NameList) * (sym"=" * Ct(ExpListLow))^-1) / mark"export" + + KeyValue: (sym":" * -SomeSpace * Name * lpeg.Cp!) / self_assign + + Ct( + (KeyName + sym"[" * Exp * sym"]" +Space * DoubleString + Space * SingleString) * + symx":" * + (Exp + TableBlock + SpaceBreak^1 * Exp) + ) + + KeyValueList: KeyValue * (sym"," * KeyValue)^0 + KeyValueLine: CheckIndent * KeyValueList * sym","^-1 + + FnArgsDef: sym"(" * White * Ct(FnArgDefList^-1) * + (key"using" * Ct(NameList + Space * "nil") + Ct"") * + White * sym")" + Ct"" * Ct"" + + FnArgDefList: FnArgDef * ((sym"," + Break) * White * FnArgDef)^0 * ((sym"," + Break) * White * Ct(VarArg))^0 + Ct(VarArg) + FnArgDef: Ct((Name + SelfName) * (sym"=" * Exp)^-1) + + FunLit: FnArgsDef * + (sym"->" * Cc"slim" + sym"=>" * Cc"fat") * + (Body + Ct"") / mark"fndef" + + NameList: Name * (sym"," * Name)^0 + NameOrDestructure: Name + TableLit + AssignableNameList: NameOrDestructure * (sym"," * NameOrDestructure)^0 + + ExpList: Exp * (sym"," * Exp)^0 + ExpListLow: Exp * ((sym"," + sym";") * Exp)^0 + + -- open args + InvokeArgs: -P"-" * (ExpList * (sym"," * (TableBlock + SpaceBreak * Advance * ArgBlock * TableBlock^-1) + TableBlock)^-1 + TableBlock) + ArgBlock: ArgLine * (sym"," * SpaceBreak * ArgLine)^0 * PopIndent + ArgLine: CheckIndent * ExpList + } + + g, state + +file_parser = -> + g, state = build_grammar! + file_grammar = White * g * White * -1 + + { + match: (str) => + local tree + _, err = xpcall (-> + tree = file_grammar\match str + ), (err) -> + debug.traceback err, 2 + + -- regular error, let it bubble up + if type(err) == "string" + return nil, err + + unless tree + local msg + err_pos = state.last_pos + + if err + node, msg = unpack err + msg = " " .. msg if msg + err_pos = node[-1] + + line_no = pos_to_line str, err_pos + line_str = get_line(str, line_no) or "" + return nil, err_msg\format msg or "", line_no, trim line_str + + tree + } + +{ + :extract_line + :build_grammar + + -- parse a string as a file + -- returns tree, or nil and error message + string: (str) -> file_parser!\match str +} + diff --git a/moonscript/parse/env.lua b/moonscript/parse/env.lua new file mode 100644 index 00000000..f805211b --- /dev/null +++ b/moonscript/parse/env.lua @@ -0,0 +1,70 @@ +local getfenv, setfenv +do + local _obj_0 = require("moonscript.util") + getfenv, setfenv = _obj_0.getfenv, _obj_0.setfenv +end +local wrap_env +wrap_env = function(debug, fn) + local V, Cmt + do + local _obj_0 = require("lpeg") + V, Cmt = _obj_0.V, _obj_0.Cmt + end + local env = getfenv(fn) + local wrap_name = V + if debug then + local indent = 0 + local indent_char = " " + local iprint + iprint = function(...) + local args = table.concat((function(...) + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = { + ... + } + for _index_0 = 1, #_list_0 do + local a = _list_0[_index_0] + _accum_0[_len_0] = tostring(a) + _len_0 = _len_0 + 1 + end + return _accum_0 + end)(...), ", ") + return io.stderr:write(tostring(indent_char:rep(indent)) .. tostring(args) .. "\n") + end + wrap_name = function(name) + local v = V(name) + v = Cmt("", function(str, pos) + local rest = str:sub(pos, -1):match("^([^\n]*)") + iprint("* " .. tostring(name) .. " (" .. tostring(rest) .. ")") + indent = indent + 1 + return true + end) * Cmt(v, function(str, pos, ...) + iprint(name, true) + indent = indent - 1 + return true, ... + end) + Cmt("", function() + iprint(name, false) + indent = indent - 1 + return false + end) + return v + end + end + return setfenv(fn, setmetatable({ }, { + __index = function(self, name) + local value = env[name] + if value ~= nil then + return value + end + if name:match("^[A-Z][A-Za-z0-9]*$") then + local v = wrap_name(name) + return v + end + return error("unknown variable referenced: " .. tostring(name)) + end + })) +end +return { + wrap_env = wrap_env +} diff --git a/moonscript/parse/env.moon b/moonscript/parse/env.moon new file mode 100644 index 00000000..ea71c3df --- /dev/null +++ b/moonscript/parse/env.moon @@ -0,0 +1,51 @@ + +import getfenv, setfenv from require "moonscript.util" + +-- all undefined Proper globals are automaticlly converted into lpeg.V +wrap_env = (debug, fn) -> + import V, Cmt from require "lpeg" + + env = getfenv fn + wrap_name = V + + if debug + indent = 0 + indent_char = " " + + iprint = (...) -> + args = table.concat [tostring a for a in *{...}], ", " + io.stderr\write "#{indent_char\rep(indent)}#{args}\n" + + wrap_name = (name) -> + v = V name + v = Cmt("", (str, pos) -> + rest = str\sub(pos, -1)\match "^([^\n]*)" + + iprint "* #{name} (#{rest})" + indent += 1 + true + ) * Cmt(v, (str, pos, ...) -> + iprint name, true + indent -= 1 + true, ... + ) + Cmt("", -> + iprint name, false + indent -= 1 + false + ) + + v + + setfenv fn, setmetatable {}, { + __index: (name) => + value = env[name] + return value if value != nil + + if name\match"^[A-Z][A-Za-z0-9]*$" + v = wrap_name name + return v + + error "unknown variable referenced: #{name}" + } + +{ :wrap_env } diff --git a/moonscript/parse/literals.lua b/moonscript/parse/literals.lua new file mode 100644 index 00000000..1ad5aeb5 --- /dev/null +++ b/moonscript/parse/literals.lua @@ -0,0 +1,39 @@ +local safe_module +safe_module = require("moonscript.util").safe_module +local S, P, R, C +do + local _obj_0 = require("lpeg") + S, P, R, C = _obj_0.S, _obj_0.P, _obj_0.R, _obj_0.C +end +local lpeg = require("lpeg") +local L = lpeg.luversion and lpeg.L or function(v) + return #v +end +local White = S(" \t\r\n") ^ 0 +local plain_space = S(" \t") ^ 0 +local Break = P("\r") ^ -1 * P("\n") +local Stop = Break + -1 +local Comment = P("--") * (1 - S("\r\n")) ^ 0 * L(Stop) +local Space = plain_space * Comment ^ -1 +local SomeSpace = S(" \t") ^ 1 * Comment ^ -1 +local SpaceBreak = Space * Break +local EmptyLine = SpaceBreak +local AlphaNum = R("az", "AZ", "09", "__") +local Name = C(R("az", "AZ", "__") * AlphaNum ^ 0) +local Num = P("0x") * R("09", "af", "AF") ^ 1 * (S("uU") ^ -1 * S("lL") ^ 2) ^ -1 + R("09") ^ 1 * (S("uU") ^ -1 * S("lL") ^ 2) + (R("09") ^ 1 * (P(".") * R("09") ^ 1) ^ -1 + P(".") * R("09") ^ 1) * (S("eE") * P("-") ^ -1 * R("09") ^ 1) ^ -1 +local Shebang = P("#!") * P(1 - Stop) ^ 0 +return safe_module("moonscript.parse.literals", { + L = L, + White = White, + Break = Break, + Stop = Stop, + Comment = Comment, + Space = Space, + SomeSpace = SomeSpace, + SpaceBreak = SpaceBreak, + EmptyLine = EmptyLine, + AlphaNum = AlphaNum, + Name = Name, + Num = Num, + Shebang = Shebang +}) diff --git a/moonscript/parse/literals.moon b/moonscript/parse/literals.moon new file mode 100644 index 00000000..5a4980f3 --- /dev/null +++ b/moonscript/parse/literals.moon @@ -0,0 +1,38 @@ +-- non-recursive parsers +import safe_module from require "moonscript.util" +import S, P, R, C from require "lpeg" + +lpeg = require "lpeg" +L = lpeg.luversion and lpeg.L or (v) -> #v + +White = S" \t\r\n"^0 +plain_space = S" \t"^0 + +Break = P"\r"^-1 * P"\n" +Stop = Break + -1 + +Comment = P"--" * (1 - S"\r\n")^0 * L(Stop) +Space = plain_space * Comment^-1 +SomeSpace = S" \t"^1 * Comment^-1 + +SpaceBreak = Space * Break +EmptyLine = SpaceBreak + +AlphaNum = R "az", "AZ", "09", "__" + +Name = C R("az", "AZ", "__") * AlphaNum^0 + +Num = P"0x" * R("09", "af", "AF")^1 * (S"uU"^-1 * S"lL"^2)^-1 + + R"09"^1 * (S"uU"^-1 * S"lL"^2) + + ( + R"09"^1 * (P"." * R"09"^1)^-1 + + P"." * R"09"^1 + ) * (S"eE" * P"-"^-1 * R"09"^1)^-1 + +Shebang = P"#!" * P(1 - Stop)^0 + +safe_module "moonscript.parse.literals", { + :L + :White, :Break, :Stop, :Comment, :Space, :SomeSpace, :SpaceBreak, :EmptyLine, + :AlphaNum, :Name, :Num, :Shebang +} diff --git a/moonscript/parse/util.lua b/moonscript/parse/util.lua new file mode 100644 index 00000000..3f02c8d5 --- /dev/null +++ b/moonscript/parse/util.lua @@ -0,0 +1,307 @@ +local unpack +unpack = require("moonscript.util").unpack +local P, C, S, Cp, Cmt, V +do + local _obj_0 = require("lpeg") + P, C, S, Cp, Cmt, V = _obj_0.P, _obj_0.C, _obj_0.S, _obj_0.Cp, _obj_0.Cmt, _obj_0.V +end +local ntype +ntype = require("moonscript.types").ntype +local Space +Space = require("moonscript.parse.literals").Space +local Indent = C(S("\t ") ^ 0) / function(str) + do + local sum = 0 + for v in str:gmatch("[\t ]") do + local _exp_0 = v + if " " == _exp_0 then + sum = sum + 1 + elseif "\t" == _exp_0 then + sum = sum + 4 + end + end + return sum + end +end +local Cut = P(function() + return false +end) +local ensure +ensure = function(patt, finally) + return patt * finally + finally * Cut +end +local extract_line +extract_line = function(str, start_pos) + str = str:sub(start_pos) + do + local m = str:match("^(.-)\n") + if m then + return m + end + end + return str:match("^.-$") +end +local show_line_position +show_line_position = function(str, pos, context) + if context == nil then + context = true + end + local lines = { + { } + } + for c in str:gmatch(".") do + local _update_0 = #lines + lines[_update_0] = lines[_update_0] or { } + table.insert(lines[#lines], c) + if c == "\n" then + lines[#lines + 1] = { } + end + end + for i, line in ipairs(lines) do + lines[i] = table.concat(line) + end + local out + local remaining = pos - 1 + for k, line in ipairs(lines) do + if remaining < #line then + local left = line:sub(1, remaining) + local right = line:sub(remaining + 1) + out = { + tostring(left) .. "â—‰" .. tostring(right) + } + if context then + do + local before = lines[k - 1] + if before then + table.insert(out, 1, before) + end + end + do + local after = lines[k + 1] + if after then + table.insert(out, after) + end + end + end + break + else + remaining = remaining - #line + end + end + if not (out) then + return "-" + end + out = table.concat(out) + return (out:gsub("\n*$", "")) +end +local mark +mark = function(name) + return function(...) + return { + name, + ... + } + end +end +local pos +pos = function(patt) + return (Cp() * patt) / function(pos, value) + if type(value) == "table" then + value[-1] = pos + end + return value + end +end +local got +got = function(what, context) + if context == nil then + context = true + end + return Cmt("", function(str, pos) + print("++ got " .. tostring(what), "[" .. tostring(show_line_position(str, pos, context)) .. "]") + return true + end) +end +local flatten_or_mark +flatten_or_mark = function(name) + return function(tbl) + if #tbl == 1 then + return tbl[1] + end + table.insert(tbl, 1, name) + return tbl + end +end +local is_assignable +do + local chain_assignable = { + index = true, + dot = true, + slice = true + } + is_assignable = function(node) + if node == "..." then + return false + end + local _exp_0 = ntype(node) + if "ref" == _exp_0 or "self" == _exp_0 or "value" == _exp_0 or "self_class" == _exp_0 or "table" == _exp_0 then + return true + elseif "chain" == _exp_0 then + return chain_assignable[ntype(node[#node])] + else + return false + end + end +end +local check_assignable +check_assignable = function(str, pos, value) + if is_assignable(value) then + return true, value + else + return false + end +end +local format_assign +do + local flatten_explist = flatten_or_mark("explist") + format_assign = function(lhs_exps, assign) + if not (assign) then + return flatten_explist(lhs_exps) + end + for _index_0 = 1, #lhs_exps do + local assign_exp = lhs_exps[_index_0] + if not (is_assignable(assign_exp)) then + error({ + assign_exp, + "left hand expression is not assignable" + }) + end + end + local t = ntype(assign) + local _exp_0 = t + if "assign" == _exp_0 then + return { + "assign", + lhs_exps, + unpack(assign, 2) + } + elseif "update" == _exp_0 then + return { + "update", + lhs_exps[1], + unpack(assign, 2) + } + else + return error("unknown assign expression: " .. tostring(t)) + end + end +end +local format_single_assign +format_single_assign = function(lhs, assign) + if assign then + return format_assign({ + lhs + }, assign) + else + return lhs + end +end +local sym +sym = function(chars) + return Space * chars +end +local symx +symx = function(chars) + return chars +end +local simple_string +simple_string = function(delim, allow_interpolation) + local inner = P("\\" .. tostring(delim)) + "\\\\" + (1 - P(delim)) + if allow_interpolation then + local interp = symx('#{') * V("Exp") * sym('}') + inner = (C((inner - interp) ^ 1) + interp / mark("interpolate")) ^ 0 + else + inner = C(inner ^ 0) + end + return C(symx(delim)) * inner * sym(delim) / mark("string") +end +local wrap_func_arg +wrap_func_arg = function(value) + return { + "call", + { + value + } + } +end +local join_chain +join_chain = function(callee, args) + if #args == 0 then + return callee + end + args = { + "call", + args + } + if ntype(callee) == "chain" then + table.insert(callee, args) + return callee + end + return { + "chain", + callee, + args + } +end +local wrap_decorator +wrap_decorator = function(stm, dec) + if not (dec) then + return stm + end + return { + "decorated", + stm, + dec + } +end +local check_lua_string +check_lua_string = function(str, pos, right, left) + return #left == #right +end +local self_assign +self_assign = function(name, pos) + return { + { + "key_literal", + name + }, + { + "ref", + name, + [-1] = pos + } + } +end +return { + Indent = Indent, + Cut = Cut, + ensure = ensure, + extract_line = extract_line, + mark = mark, + pos = pos, + flatten_or_mark = flatten_or_mark, + is_assignable = is_assignable, + check_assignable = check_assignable, + format_assign = format_assign, + format_single_assign = format_single_assign, + sym = sym, + symx = symx, + simple_string = simple_string, + wrap_func_arg = wrap_func_arg, + join_chain = join_chain, + wrap_decorator = wrap_decorator, + check_lua_string = check_lua_string, + self_assign = self_assign, + got = got, + show_line_position = show_line_position +} diff --git a/moonscript/parse/util.moon b/moonscript/parse/util.moon new file mode 100644 index 00000000..17adf77a --- /dev/null +++ b/moonscript/parse/util.moon @@ -0,0 +1,197 @@ + +import unpack from require "moonscript.util" +import P, C, S, Cp, Cmt, V from require "lpeg" +import ntype from require "moonscript.types" +import Space from require "moonscript.parse.literals" + +-- captures an indentation, returns indent depth +Indent = C(S"\t "^0) / (str) -> + with sum = 0 + for v in str\gmatch "[\t ]" + switch v + when " " + sum += 1 + when "\t" + sum += 4 + + +-- causes pattern in progress to be rejected +-- can't have P(false) because it causes preceding patterns not to run +Cut = P -> false + +-- ensures finally runs regardless of whether pattern fails or passes +ensure = (patt, finally) -> + patt * finally + finally * Cut + +-- take rest of line from pos out of str +extract_line = (str, start_pos) -> + str = str\sub start_pos + if m = str\match "^(.-)\n" + return m + + str\match "^.-$" + +-- print the line with a token showing the position +show_line_position = (str, pos, context=true) -> + lines = { {} } + for c in str\gmatch "." + lines[#lines] or= {} + table.insert lines[#lines], c + if c == "\n" + lines[#lines + 1] = {} + + for i, line in ipairs lines + lines[i] = table.concat line + + local out + + remaining = pos - 1 + for k, line in ipairs lines + if remaining < #line + left = line\sub 1, remaining + right = line\sub remaining + 1 + out = { + "#{left}â—‰#{right}" + } + + if context + if before = lines[k - 1] + table.insert out, 1, before + + if after = lines[k + 1] + table.insert out, after + + break + else + remaining -= #line + + + return "-" unless out + + out = table.concat out + (out\gsub "\n*$", "") + +-- used to identify a capture with a label +mark = (name) -> + (...) -> {name, ...} + +-- wraps pattern to capture pos into node +-- pos is the character offset from the buffer where the node was parsed from. +-- Used to generate error messages +pos = (patt) -> + (Cp! * patt) / (pos, value) -> + if type(value) == "table" + value[-1] = pos + value + +-- generates a debug pattern that always succeeds and prints out where we are +-- in the buffer with a label +got = (what, context=true) -> + Cmt "", (str, pos) -> + print "++ got #{what}", "[#{show_line_position str, pos, context}]" + true + +-- converts 1 element array to its value, otherwise marks it +flatten_or_mark = (name) -> + (tbl) -> + return tbl[1] if #tbl == 1 + table.insert tbl, 1, name + tbl + +-- determines if node is able to be on left side of assignment +is_assignable = do + chain_assignable = { index: true, dot: true, slice: true } + + (node) -> + return false if node == "..." + switch ntype node + when "ref", "self", "value", "self_class", "table" + true + when "chain" + chain_assignable[ntype node[#node]] + else + false + +check_assignable = (str, pos, value) -> + if is_assignable value + true, value + else + false + +-- joins the two parts of an assign parse into a single node +format_assign = do + flatten_explist = flatten_or_mark "explist" + + (lhs_exps, assign) -> + unless assign + return flatten_explist lhs_exps + + for assign_exp in *lhs_exps + unless is_assignable assign_exp + error {assign_exp, "left hand expression is not assignable"} + + t = ntype assign + switch t + when "assign" + {"assign", lhs_exps, unpack assign, 2} + when "update" + {"update", lhs_exps[1], unpack assign, 2} + else + error "unknown assign expression: #{t}" + +-- helper for if statement, which only has single lhs +format_single_assign = (lhs, assign) -> + if assign + format_assign {lhs}, assign + else + lhs + + +-- a symbol +sym = (chars) -> Space * chars +-- a symbol that doesn't accept whitespace before it +symx = (chars) -> chars + +-- a constructor for quote delimited strings +simple_string = (delim, allow_interpolation) -> + inner = P("\\#{delim}") + "\\\\" + (1 - P delim) + + inner = if allow_interpolation + interp = symx'#{' * V"Exp" * sym'}' + (C((inner - interp)^1) + interp / mark"interpolate")^0 + else + C inner^0 + + C(symx(delim)) * inner * sym(delim) / mark"string" + +-- wraps a single value in format needed to be passed as function arguments +wrap_func_arg = (value) -> {"call", {value}} + +-- chains are parsed in two captures, the chain and then the open arguments +-- if there are open arguments, then append them to the end of the chain as a call +join_chain = (callee, args) -> + return callee if #args == 0 + args = {"call", args} + + if ntype(callee) == "chain" + table.insert callee, args + return callee + + {"chain", callee, args} + +-- constructor for decorator node +wrap_decorator = (stm, dec) -> + return stm unless dec + {"decorated", stm, dec} + +check_lua_string = (str, pos, right, left) -> + #left == #right + +-- constructor for :name self assignments in table literals +self_assign = (name, pos) -> + {{"key_literal", name}, {"ref", name, [-1]: pos}} + +{ :Indent, :Cut, :ensure, :extract_line, :mark, :pos, :flatten_or_mark, + :is_assignable, :check_assignable, :format_assign, :format_single_assign, + :sym, :symx, :simple_string, :wrap_func_arg, :join_chain, :wrap_decorator, + :check_lua_string, :self_assign, :got, :show_line_position } diff --git a/moonscript/transform.lua b/moonscript/transform.lua index 449e983a..c95f14d3 100644 --- a/moonscript/transform.lua +++ b/moonscript/transform.lua @@ -1,1620 +1,4 @@ -local types = require("moonscript.types") -local util = require("moonscript.util") -local data = require("moonscript.data") -local reversed, unpack -reversed, unpack = util.reversed, util.unpack -local ntype, mtype, build, smart_node, is_slice, value_is_singular -ntype, mtype, build, smart_node, is_slice, value_is_singular = types.ntype, types.mtype, types.build, types.smart_node, types.is_slice, types.value_is_singular -local insert -do - local _obj_0 = table - insert = _obj_0.insert -end -local NameProxy, LocalName -do - local _obj_0 = require("moonscript.transform.names") - NameProxy, LocalName = _obj_0.NameProxy, _obj_0.LocalName -end -local destructure = require("moonscript.transform.destructure") -local NOOP = { - "noop" -} -local Run, apply_to_last, is_singular, extract_declarations, expand_elseif_assign, constructor_name, with_continue_listener, Transformer, construct_comprehension, Statement, Accumulator, default_accumulator, implicitly_return, Value -do - local _base_0 = { - call = function(self, state) - return self.fn(state) - end - } - _base_0.__index = _base_0 - local _class_0 = setmetatable({ - __init = function(self, fn) - self.fn = fn - self[1] = "run" - end, - __base = _base_0, - __name = "Run" - }, { - __index = _base_0, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - Run = _class_0 -end -apply_to_last = function(stms, fn) - local last_exp_id = 0 - for i = #stms, 1, -1 do - local stm = stms[i] - if stm and mtype(stm) ~= Run then - last_exp_id = i - break - end - end - return (function() - local _accum_0 = { } - local _len_0 = 1 - for i, stm in ipairs(stms) do - if i == last_exp_id then - _accum_0[_len_0] = { - "transform", - stm, - fn - } - else - _accum_0[_len_0] = stm - end - _len_0 = _len_0 + 1 - end - return _accum_0 - end)() -end -is_singular = function(body) - if #body ~= 1 then - return false - end - if "group" == ntype(body) then - return is_singular(body[2]) - else - return body[1] - end -end -extract_declarations = function(self, body, start, out) - if body == nil then - body = self.current_stms - end - if start == nil then - start = self.current_stm_i + 1 - end - if out == nil then - out = { } - end - for i = start, #body do - local _continue_0 = false - repeat - local stm = body[i] - if stm == nil then - _continue_0 = true - break - end - stm = self.transform.statement(stm) - body[i] = stm - local _exp_0 = stm[1] - if "assign" == _exp_0 or "declare" == _exp_0 then - local _list_0 = stm[2] - for _index_0 = 1, #_list_0 do - local name = _list_0[_index_0] - if ntype(name) == "ref" then - insert(out, name) - elseif type(name) == "string" then - insert(out, name) - end - end - elseif "group" == _exp_0 then - extract_declarations(self, stm[2], 1, out) - end - _continue_0 = true - until true - if not _continue_0 then - break - end - end - return out -end -expand_elseif_assign = function(ifstm) - for i = 4, #ifstm do - local case = ifstm[i] - if ntype(case) == "elseif" and ntype(case[2]) == "assign" then - local split = { - unpack(ifstm, 1, i - 1) - } - insert(split, { - "else", - { - { - "if", - case[2], - case[3], - unpack(ifstm, i + 1) - } - } - }) - return split - end - end - return ifstm -end -constructor_name = "new" -with_continue_listener = function(body) - local continue_name = nil - return { - Run(function(self) - return self:listen("continue", function() - if not (continue_name) then - continue_name = NameProxy("continue") - self:put_name(continue_name) - end - return continue_name - end) - end), - build.group(body), - Run(function(self) - if not (continue_name) then - return - end - self:put_name(continue_name, nil) - return self:splice(function(lines) - return { - { - "assign", - { - continue_name - }, - { - "false" - } - }, - { - "repeat", - "true", - { - lines, - { - "assign", - { - continue_name - }, - { - "true" - } - } - } - }, - { - "if", - { - "not", - continue_name - }, - { - { - "break" - } - } - } - } - end) - end) - } -end -do - local _base_0 = { - transform_once = function(self, scope, node, ...) - if self.seen_nodes[node] then - return node - end - self.seen_nodes[node] = true - local transformer = self.transformers[ntype(node)] - if transformer then - return transformer(scope, node, ...) or node - else - return node - end - end, - transform = function(self, scope, node, ...) - if self.seen_nodes[node] then - return node - end - self.seen_nodes[node] = true - while true do - local transformer = self.transformers[ntype(node)] - local res - if transformer then - res = transformer(scope, node, ...) or node - else - res = node - end - if res == node then - return node - end - node = res - end - return node - end, - bind = function(self, scope) - return function(...) - return self:transform(scope, ...) - end - end, - __call = function(self, ...) - return self:transform(...) - end, - can_transform = function(self, node) - return self.transformers[ntype(node)] ~= nil - end - } - _base_0.__index = _base_0 - local _class_0 = setmetatable({ - __init = function(self, transformers) - self.transformers = transformers - self.seen_nodes = setmetatable({ }, { - __mode = "k" - }) - end, - __base = _base_0, - __name = "Transformer" - }, { - __index = _base_0, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - Transformer = _class_0 -end -construct_comprehension = function(inner, clauses) - local current_stms = inner - for _, clause in reversed(clauses) do - local t = clause[1] - local _exp_0 = t - if "for" == _exp_0 then - local name, bounds - _, name, bounds = clause[1], clause[2], clause[3] - current_stms = { - "for", - name, - bounds, - current_stms - } - elseif "foreach" == _exp_0 then - local names, iter - _, names, iter = clause[1], clause[2], clause[3] - current_stms = { - "foreach", - names, - { - iter - }, - current_stms - } - elseif "when" == _exp_0 then - local cond - _, cond = clause[1], clause[2] - current_stms = { - "if", - cond, - current_stms - } - else - current_stms = error("Unknown comprehension clause: " .. t) - end - current_stms = { - current_stms - } - end - return current_stms[1] -end -Statement = Transformer({ - transform = function(self, tuple) - local _, node, fn - _, node, fn = tuple[1], tuple[2], tuple[3] - return fn(node) - end, - root_stms = function(self, body) - return apply_to_last(body, implicitly_return(self)) - end, - ["return"] = function(self, node) - node[2] = Value:transform_once(self, node[2]) - if "block_exp" == ntype(node[2]) then - local block_exp = node[2] - local block_body = block_exp[2] - local idx = #block_body - node[2] = block_body[idx] - block_body[idx] = node - return build.group(block_body) - end - return node - end, - declare_glob = function(self, node) - local names = extract_declarations(self) - if node[2] == "^" then - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #names do - local _continue_0 = false - repeat - local name = names[_index_0] - if not (name[2]:match("^%u")) then - _continue_0 = true - break - end - local _value_0 = name - _accum_0[_len_0] = _value_0 - _len_0 = _len_0 + 1 - _continue_0 = true - until true - if not _continue_0 then - break - end - end - names = _accum_0 - end - end - return { - "declare", - names - } - end, - assign = function(self, node) - local names, values = unpack(node, 2) - local num_values = #values - local num_names = #values - if num_names == 1 and num_values == 1 then - local first_value = values[1] - local first_name = names[1] - local _exp_0 = ntype(first_value) - if "block_exp" == _exp_0 then - local block_body = first_value[2] - local idx = #block_body - block_body[idx] = build.assign_one(first_name, block_body[idx]) - return build.group({ - { - "declare", - { - first_name - } - }, - { - "do", - block_body - } - }) - elseif "comprehension" == _exp_0 or "tblcomprehension" == _exp_0 or "foreach" == _exp_0 or "for" == _exp_0 or "while" == _exp_0 then - return build.assign_one(first_name, Value:transform_once(self, first_value)) - end - end - local transformed - if num_values == 1 then - local value = values[1] - local t = ntype(value) - if t == "decorated" then - value = self.transform.statement(value) - t = ntype(value) - end - if types.cascading[t] then - local ret - ret = function(stm) - if types.is_value(stm) then - return { - "assign", - names, - { - stm - } - } - else - return stm - end - end - transformed = build.group({ - { - "declare", - names - }, - self.transform.statement(value, ret, node) - }) - end - end - node = transformed or node - if destructure.has_destructure(names) then - return destructure.split_assign(self, node) - end - return node - end, - continue = function(self, node) - local continue_name = self:send("continue") - if not (continue_name) then - error("continue must be inside of a loop") - end - return build.group({ - build.assign_one(continue_name, "true"), - { - "break" - } - }) - end, - export = function(self, node) - if #node > 2 then - if node[2] == "class" then - local cls = smart_node(node[3]) - return build.group({ - { - "export", - { - cls.name - } - }, - cls - }) - else - return build.group({ - { - "export", - node[2] - }, - build.assign({ - names = node[2], - values = node[3] - }) - }) - end - else - return nil - end - end, - update = function(self, node) - local _, name, op, exp = unpack(node) - local op_final = op:match("^(.+)=$") - if not op_final then - error("Unknown op: " .. op) - end - if not (value_is_singular(exp)) then - exp = { - "parens", - exp - } - end - return build.assign_one(name, { - "exp", - name, - op_final, - exp - }) - end, - import = function(self, node) - local _, names, source = unpack(node) - local table_values - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #names do - local name = names[_index_0] - local dest_val - if ntype(name) == "colon_stub" then - dest_val = name[2] - else - dest_val = name - end - local _value_0 = { - { - "key_literal", - name - }, - dest_val - } - _accum_0[_len_0] = _value_0 - _len_0 = _len_0 + 1 - end - table_values = _accum_0 - end - local dest = { - "table", - table_values - } - return { - "assign", - { - dest - }, - { - source - }, - [-1] = node[-1] - } - end, - comprehension = function(self, node, action) - local _, exp, clauses = unpack(node) - action = action or function(exp) - return { - exp - } - end - return construct_comprehension(action(exp), clauses) - end, - ["do"] = function(self, node, ret) - if ret then - node[2] = apply_to_last(node[2], ret) - end - return node - end, - decorated = function(self, node) - local stm, dec = unpack(node, 2) - local wrapped - local _exp_0 = dec[1] - if "if" == _exp_0 then - local cond, fail = unpack(dec, 2) - if fail then - fail = { - "else", - { - fail - } - } - end - wrapped = { - "if", - cond, - { - stm - }, - fail - } - elseif "unless" == _exp_0 then - wrapped = { - "unless", - dec[2], - { - stm - } - } - elseif "comprehension" == _exp_0 then - wrapped = { - "comprehension", - stm, - dec[2] - } - else - wrapped = error("Unknown decorator " .. dec[1]) - end - if ntype(stm) == "assign" then - wrapped = build.group({ - build.declare({ - names = (function() - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = stm[2] - for _index_0 = 1, #_list_0 do - local name = _list_0[_index_0] - if ntype(name) == "ref" then - _accum_0[_len_0] = name - _len_0 = _len_0 + 1 - end - end - return _accum_0 - end)() - }), - wrapped - }) - end - return wrapped - end, - unless = function(self, node) - return { - "if", - { - "not", - { - "parens", - node[2] - } - }, - unpack(node, 3) - } - end, - ["if"] = function(self, node, ret) - if ntype(node[2]) == "assign" then - local _, assign, body = unpack(node) - if destructure.has_destructure(assign[2]) then - local name = NameProxy("des") - body = { - destructure.build_assign(self, assign[2][1], name), - build.group(node[3]) - } - return build["do"]({ - build.assign_one(name, assign[3][1]), - { - "if", - name, - body, - unpack(node, 4) - } - }) - else - local name = assign[2][1] - return build["do"]({ - assign, - { - "if", - name, - unpack(node, 3) - } - }) - end - end - node = expand_elseif_assign(node) - if ret then - smart_node(node) - node['then'] = apply_to_last(node['then'], ret) - for i = 4, #node do - local case = node[i] - local body_idx = #node[i] - case[body_idx] = apply_to_last(case[body_idx], ret) - end - end - return node - end, - with = function(self, node, ret) - local exp, block = unpack(node, 2) - local copy_scope = true - local scope_name, named_assign - if ntype(exp) == "assign" then - local names, values = unpack(exp, 2) - local first_name = names[1] - if ntype(first_name) == "ref" then - scope_name = first_name - named_assign = exp - exp = values[1] - copy_scope = false - else - scope_name = NameProxy("with") - exp = values[1] - values[1] = scope_name - named_assign = { - "assign", - names, - values - } - end - elseif self:is_local(exp) then - scope_name = exp - copy_scope = false - end - scope_name = scope_name or NameProxy("with") - return build["do"]({ - Run(function(self) - return self:set("scope_var", scope_name) - end), - copy_scope and build.assign_one(scope_name, exp) or NOOP, - named_assign or NOOP, - build.group(block), - (function() - if ret then - return ret(scope_name) - end - end)() - }) - end, - foreach = function(self, node, _) - smart_node(node) - local source = unpack(node.iter) - local destructures = { } - do - local _accum_0 = { } - local _len_0 = 1 - for i, name in ipairs(node.names) do - if ntype(name) == "table" then - do - local proxy = NameProxy("des") - insert(destructures, destructure.build_assign(self, name, proxy)) - _accum_0[_len_0] = proxy - end - else - _accum_0[_len_0] = name - end - _len_0 = _len_0 + 1 - end - node.names = _accum_0 - end - if next(destructures) then - insert(destructures, build.group(node.body)) - node.body = destructures - end - if ntype(source) == "unpack" then - local list = source[2] - local index_name = NameProxy("index") - local list_name = self:is_local(list) and list or NameProxy("list") - local slice_var = nil - local bounds - if is_slice(list) then - local slice = list[#list] - table.remove(list) - table.remove(slice, 1) - if self:is_local(list) then - list_name = list - end - if slice[2] and slice[2] ~= "" then - local max_tmp_name = NameProxy("max") - slice_var = build.assign_one(max_tmp_name, slice[2]) - slice[2] = { - "exp", - max_tmp_name, - "<", - 0, - "and", - { - "length", - list_name - }, - "+", - max_tmp_name, - "or", - max_tmp_name - } - else - slice[2] = { - "length", - list_name - } - end - bounds = slice - else - bounds = { - 1, - { - "length", - list_name - } - } - end - return build.group({ - list_name ~= list and build.assign_one(list_name, list) or NOOP, - slice_var or NOOP, - build["for"]({ - name = index_name, - bounds = bounds, - body = { - { - "assign", - node.names, - { - NameProxy.index(list_name, index_name) - } - }, - build.group(node.body) - } - }) - }) - end - node.body = with_continue_listener(node.body) - end, - ["while"] = function(self, node) - smart_node(node) - node.body = with_continue_listener(node.body) - end, - ["for"] = function(self, node) - smart_node(node) - node.body = with_continue_listener(node.body) - end, - switch = function(self, node, ret) - local _, exp, conds = unpack(node) - local exp_name = NameProxy("exp") - local convert_cond - convert_cond = function(cond) - local t, case_exps, body = unpack(cond) - local out = { } - insert(out, t == "case" and "elseif" or "else") - if t ~= "else" then - local cond_exp = { } - for i, case in ipairs(case_exps) do - if i == 1 then - insert(cond_exp, "exp") - else - insert(cond_exp, "or") - end - if not (value_is_singular(case)) then - case = { - "parens", - case - } - end - insert(cond_exp, { - "exp", - case, - "==", - exp_name - }) - end - insert(out, cond_exp) - else - body = case_exps - end - if ret then - body = apply_to_last(body, ret) - end - insert(out, body) - return out - end - local first = true - local if_stm = { - "if" - } - for _index_0 = 1, #conds do - local cond = conds[_index_0] - local if_cond = convert_cond(cond) - if first then - first = false - insert(if_stm, if_cond[2]) - insert(if_stm, if_cond[3]) - else - insert(if_stm, if_cond) - end - end - return build.group({ - build.assign_one(exp_name, exp), - if_stm - }) - end, - class = function(self, node, ret, parent_assign) - local _, name, parent_val, body = unpack(node) - if parent_val == "" then - parent_val = nil - end - local statements = { } - local properties = { } - for _index_0 = 1, #body do - local item = body[_index_0] - local _exp_0 = item[1] - if "stm" == _exp_0 then - insert(statements, item[2]) - elseif "props" == _exp_0 then - for _index_1 = 2, #item do - local tuple = item[_index_1] - if ntype(tuple[1]) == "self" then - insert(statements, build.assign_one(unpack(tuple))) - else - insert(properties, tuple) - end - end - end - end - local constructor - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #properties do - local _continue_0 = false - repeat - local tuple = properties[_index_0] - local key = tuple[1] - local _value_0 - if key[1] == "key_literal" and key[2] == constructor_name then - constructor = tuple[2] - _continue_0 = true - break - else - _value_0 = tuple - end - _accum_0[_len_0] = _value_0 - _len_0 = _len_0 + 1 - _continue_0 = true - until true - if not _continue_0 then - break - end - end - properties = _accum_0 - end - local parent_cls_name = NameProxy("parent") - local base_name = NameProxy("base") - local self_name = NameProxy("self") - local cls_name = NameProxy("class") - if not (constructor) then - if parent_val then - constructor = build.fndef({ - args = { - { - "..." - } - }, - arrow = "fat", - body = { - build.chain({ - base = "super", - { - "call", - { - "..." - } - } - }) - } - }) - else - constructor = build.fndef() - end - end - local real_name = name or parent_assign and parent_assign[2][1] - local _exp_0 = ntype(real_name) - if "chain" == _exp_0 then - local last = real_name[#real_name] - local _exp_1 = ntype(last) - if "dot" == _exp_1 then - real_name = { - "string", - '"', - last[2] - } - elseif "index" == _exp_1 then - real_name = last[2] - else - real_name = "nil" - end - elseif "nil" == _exp_0 then - real_name = "nil" - else - local name_t = type(real_name) - local flattened_name - if name_t == "string" then - flattened_name = real_name - elseif name_t == "table" and real_name[1] == "ref" then - flattened_name = real_name[2] - else - flattened_name = error("don't know how to extract name from " .. tostring(name_t)) - end - real_name = { - "string", - '"', - flattened_name - } - end - local cls = build.table({ - { - "__init", - constructor - }, - { - "__base", - base_name - }, - { - "__name", - real_name - }, - parent_val and { - "__parent", - parent_cls_name - } or nil - }) - local class_index - if parent_val then - local class_lookup = build["if"]({ - cond = { - "exp", - { - "ref", - "val" - }, - "==", - "nil" - }, - ["then"] = { - parent_cls_name:index("name") - } - }) - insert(class_lookup, { - "else", - { - "val" - } - }) - class_index = build.fndef({ - args = { - { - "cls" - }, - { - "name" - } - }, - body = { - build.assign_one(LocalName("val"), build.chain({ - base = "rawget", - { - "call", - { - base_name, - { - "ref", - "name" - } - } - } - })), - class_lookup - } - }) - else - class_index = base_name - end - local cls_mt = build.table({ - { - "__index", - class_index - }, - { - "__call", - build.fndef({ - args = { - { - "cls" - }, - { - "..." - } - }, - body = { - build.assign_one(self_name, build.chain({ - base = "setmetatable", - { - "call", - { - "{}", - base_name - } - } - })), - build.chain({ - base = "cls.__init", - { - "call", - { - self_name, - "..." - } - } - }), - self_name - } - }) - } - }) - cls = build.chain({ - base = "setmetatable", - { - "call", - { - cls, - cls_mt - } - } - }) - local value = nil - do - local out_body = { - Run(function(self) - if name then - self:put_name(name) - end - return self:set("super", function(block, chain) - if chain then - local slice - do - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 3, #chain do - local item = chain[_index_0] - _accum_0[_len_0] = item - _len_0 = _len_0 + 1 - end - slice = _accum_0 - end - local new_chain = { - "chain", - parent_cls_name - } - local head = slice[1] - if head == nil then - return parent_cls_name - end - local _exp_1 = head[1] - if "call" == _exp_1 then - local calling_name = block:get("current_block") - slice[1] = { - "call", - { - "self", - unpack(head[2]) - } - } - if ntype(calling_name) == "key_literal" then - insert(new_chain, { - "dot", - calling_name[2] - }) - else - insert(new_chain, { - "index", - calling_name - }) - end - elseif "colon" == _exp_1 then - local call = head[3] - insert(new_chain, { - "dot", - head[2] - }) - slice[1] = { - "call", - { - "self", - unpack(call[2]) - } - } - end - for _index_0 = 1, #slice do - local item = slice[_index_0] - insert(new_chain, item) - end - return new_chain - else - return parent_cls_name - end - end) - end), - { - "declare_glob", - "*" - }, - parent_val and build.assign_one(parent_cls_name, parent_val) or NOOP, - build.assign_one(base_name, { - "table", - properties - }), - build.assign_one(base_name:chain("__index"), base_name), - parent_val and build.chain({ - base = "setmetatable", - { - "call", - { - base_name, - build.chain({ - base = parent_cls_name, - { - "dot", - "__base" - } - }) - } - } - }) or NOOP, - build.assign_one(cls_name, cls), - build.assign_one(base_name:chain("__class"), cls_name), - build.group((function() - if #statements > 0 then - return { - build.assign_one(LocalName("self"), cls_name), - build.group(statements) - } - end - end)()), - parent_val and build["if"]({ - cond = { - "exp", - parent_cls_name:chain("__inherited") - }, - ["then"] = { - parent_cls_name:chain("__inherited", { - "call", - { - parent_cls_name, - cls_name - } - }) - } - }) or NOOP, - build.group((function() - if name then - return { - build.assign_one(name, cls_name) - } - end - end)()), - (function() - if ret then - return ret(cls_name) - end - end)() - } - value = build.group({ - build.group((function() - if ntype(name) == "value" then - return { - build.declare({ - names = { - name - } - }) - } - end - end)()), - build["do"](out_body) - }) - end - return value - end -}) -do - local _base_0 = { - body_idx = { - ["for"] = 4, - ["while"] = 3, - foreach = 4 - }, - convert = function(self, node) - local index = self.body_idx[ntype(node)] - node[index] = self:mutate_body(node[index]) - return self:wrap(node) - end, - wrap = function(self, node, group_type) - if group_type == nil then - group_type = "block_exp" - end - return build[group_type]({ - build.assign_one(self.accum_name, build.table()), - build.assign_one(self.len_name, 1), - node, - group_type == "block_exp" and self.accum_name or NOOP - }) - end, - mutate_body = function(self, body) - local single_stm = is_singular(body) - local val - if single_stm and types.is_value(single_stm) then - body = { } - val = single_stm - else - body = apply_to_last(body, function(n) - if types.is_value(n) then - return build.assign_one(self.value_name, n) - else - return build.group({ - { - "declare", - { - self.value_name - } - }, - n - }) - end - end) - val = self.value_name - end - local update = { - build.assign_one(NameProxy.index(self.accum_name, self.len_name), val), - { - "update", - self.len_name, - "+=", - 1 - } - } - insert(body, build.group(update)) - return body - end - } - _base_0.__index = _base_0 - local _class_0 = setmetatable({ - __init = function(self, accum_name) - self.accum_name = NameProxy("accum") - self.value_name = NameProxy("value") - self.len_name = NameProxy("len") - end, - __base = _base_0, - __name = "Accumulator" - }, { - __index = _base_0, - __call = function(cls, ...) - local _self_0 = setmetatable({}, _base_0) - cls.__init(_self_0, ...) - return _self_0 - end - }) - _base_0.__class = _class_0 - Accumulator = _class_0 -end -default_accumulator = function(self, node) - return Accumulator():convert(node) -end -implicitly_return = function(scope) - local is_top = true - local fn - fn = function(stm) - local t = ntype(stm) - if t == "decorated" then - stm = scope.transform.statement(stm) - t = ntype(stm) - end - if types.cascading[t] then - is_top = false - return scope.transform.statement(stm, fn) - elseif types.manual_return[t] or not types.is_value(stm) then - if is_top and t == "return" and stm[2] == "" then - return NOOP - else - return stm - end - else - if t == "comprehension" and not types.comprehension_has_value(stm) then - return stm - else - return { - "return", - stm - } - end - end - end - return fn -end -Value = Transformer({ - ["for"] = default_accumulator, - ["while"] = default_accumulator, - foreach = default_accumulator, - ["do"] = function(self, node) - return build.block_exp(node[2]) - end, - decorated = function(self, node) - return self.transform.statement(node) - end, - class = function(self, node) - return build.block_exp({ - node - }) - end, - string = function(self, node) - local delim = node[2] - local convert_part - convert_part = function(part) - if type(part) == "string" or part == nil then - return { - "string", - delim, - part or "" - } - else - return build.chain({ - base = "tostring", - { - "call", - { - part[2] - } - } - }) - end - end - if #node <= 3 then - return (function() - if type(node[3]) == "string" then - return node - else - return convert_part(node[3]) - end - end)() - end - local e = { - "exp", - convert_part(node[3]) - } - for i = 4, #node do - insert(e, "..") - insert(e, convert_part(node[i])) - end - return e - end, - comprehension = function(self, node) - local a = Accumulator() - node = self.transform.statement(node, function(exp) - return a:mutate_body({ - exp - }) - end) - return a:wrap(node) - end, - tblcomprehension = function(self, node) - local _, explist, clauses = unpack(node) - local key_exp, value_exp = unpack(explist) - local accum = NameProxy("tbl") - local inner - if value_exp then - local dest = build.chain({ - base = accum, - { - "index", - key_exp - } - }) - inner = { - build.assign_one(dest, value_exp) - } - else - local key_name, val_name = NameProxy("key"), NameProxy("val") - local dest = build.chain({ - base = accum, - { - "index", - key_name - } - }) - inner = { - build.assign({ - names = { - key_name, - val_name - }, - values = { - key_exp - } - }), - build.assign_one(dest, val_name) - } - end - return build.block_exp({ - build.assign_one(accum, build.table()), - construct_comprehension(inner, clauses), - accum - }) - end, - fndef = function(self, node) - smart_node(node) - node.body = apply_to_last(node.body, implicitly_return(self)) - node.body = { - Run(function(self) - return self:listen("varargs", function() end) - end), - unpack(node.body) - } - return node - end, - ["if"] = function(self, node) - return build.block_exp({ - node - }) - end, - unless = function(self, node) - return build.block_exp({ - node - }) - end, - with = function(self, node) - return build.block_exp({ - node - }) - end, - switch = function(self, node) - return build.block_exp({ - node - }) - end, - chain = function(self, node) - local stub = node[#node] - for i = 3, #node do - local part = node[i] - if ntype(part) == "dot" and data.lua_keywords[part[2]] then - node[i] = { - "index", - { - "string", - '"', - part[2] - } - } - end - end - if ntype(node[2]) == "string" then - node[2] = { - "parens", - node[2] - } - elseif type(stub) == "table" and stub[1] == "colon_stub" then - table.remove(node, #node) - local base_name = NameProxy("base") - local fn_name = NameProxy("fn") - local is_super = ntype(node[2]) == "ref" and node[2][2] == "super" - return self.transform.value(build.block_exp({ - build.assign({ - names = { - base_name - }, - values = { - node - } - }), - build.assign({ - names = { - fn_name - }, - values = { - build.chain({ - base = base_name, - { - "dot", - stub[2] - } - }) - } - }), - build.fndef({ - args = { - { - "..." - } - }, - body = { - build.chain({ - base = fn_name, - { - "call", - { - is_super and "self" or base_name, - "..." - } - } - }) - } - }) - })) - end - end, - block_exp = function(self, node) - local _, body = unpack(node) - local fn = nil - local arg_list = { } - fn = smart_node(build.fndef({ - body = { - Run(function(self) - return self:listen("varargs", function() - insert(arg_list, "...") - insert(fn.args, { - "..." - }) - return self:unlisten("varargs") - end) - end), - unpack(body) - } - })) - return build.chain({ - base = { - "parens", - fn - }, - { - "call", - arg_list - } - }) - end -}) return { - Statement = Statement, - Value = Value, - Run = Run + Statement = require("moonscript.transform.statement"), + Value = require("moonscript.transform.value") } diff --git a/moonscript/transform.moon b/moonscript/transform.moon index 9175d417..29a93ad2 100644 --- a/moonscript/transform.moon +++ b/moonscript/transform.moon @@ -1,967 +1,5 @@ -types = require "moonscript.types" -util = require "moonscript.util" -data = require "moonscript.data" - -import reversed, unpack from util -import ntype, mtype, build, smart_node, is_slice, value_is_singular from types -import insert from table -import NameProxy, LocalName from require "moonscript.transform.names" - -destructure = require "moonscript.transform.destructure" -NOOP = {"noop"} - -local * - -class Run - new: (@fn) => - self[1] = "run" - - call: (state) => - self.fn state - --- transform the last stm is a list of stms --- will puke on group -apply_to_last = (stms, fn) -> - -- find last (real) exp - last_exp_id = 0 - for i = #stms, 1, -1 - stm = stms[i] - if stm and mtype(stm) != Run - last_exp_id = i - break - - return for i, stm in ipairs stms - if i == last_exp_id - {"transform", stm, fn} - else - stm - --- is a body a sindle expression/statement -is_singular = (body) -> - return false if #body != 1 - if "group" == ntype body - is_singular body[2] - else - body[1] - --- this mutates body searching for assigns -extract_declarations = (body=@current_stms, start=@current_stm_i + 1, out={}) => - for i=start,#body - stm = body[i] - continue if stm == nil - stm = @transform.statement stm - body[i] = stm - switch stm[1] - when "assign", "declare" - for name in *stm[2] - if ntype(name) == "ref" - insert out, name - elseif type(name) == "string" - -- TODO: don't use string literal as ref - insert out, name - when "group" - extract_declarations @, stm[2], 1, out - out - -expand_elseif_assign = (ifstm) -> - for i = 4, #ifstm - case = ifstm[i] - if ntype(case) == "elseif" and ntype(case[2]) == "assign" - split = { unpack ifstm, 1, i - 1 } - insert split, { - "else", { - {"if", case[2], case[3], unpack ifstm, i + 1} - } - } - return split - - ifstm - -constructor_name = "new" - -with_continue_listener = (body) -> - continue_name = nil - { - Run => - @listen "continue", -> - unless continue_name - continue_name = NameProxy"continue" - @put_name continue_name - continue_name - - build.group body - - Run => - return unless continue_name - @put_name continue_name, nil - @splice (lines) -> { - {"assign", {continue_name}, {"false"}} - {"repeat", "true", { - lines - {"assign", {continue_name}, {"true"}} - }} - {"if", {"not", continue_name}, { - {"break"} - }} - } - } - - -class Transformer - new: (@transformers) => - @seen_nodes = setmetatable {}, __mode: "k" - - transform_once: (scope, node, ...) => - return node if @seen_nodes[node] - @seen_nodes[node] = true - - transformer = @transformers[ntype node] - if transformer - transformer(scope, node, ...) or node - else - node - - transform: (scope, node, ...) => - return node if @seen_nodes[node] - @seen_nodes[node] = true - while true - transformer = @transformers[ntype node] - res = if transformer - transformer(scope, node, ...) or node - else - node - return node if res == node - node = res - node - - bind: (scope) => - (...) -> @transform scope, ... - - __call: (...) => @transform ... - - can_transform: (node) => - @transformers[ntype node] != nil - -construct_comprehension = (inner, clauses) -> - current_stms = inner - for _, clause in reversed clauses - t = clause[1] - current_stms = switch t - when "for" - {_, name, bounds} = clause - {"for", name, bounds, current_stms} - when "foreach" - {_, names, iter} = clause - {"foreach", names, {iter}, current_stms} - when "when" - {_, cond} = clause - {"if", cond, current_stms} - else - error "Unknown comprehension clause: "..t - - current_stms = {current_stms} - - current_stms[1] - -Statement = Transformer { - transform: (tuple) => - {_, node, fn} = tuple - fn node - - root_stms: (body) => - apply_to_last body, implicitly_return @ - - return: (node) => - node[2] = Value\transform_once @, node[2] - - if "block_exp" == ntype node[2] - block_exp = node[2] - block_body = block_exp[2] - - idx = #block_body - node[2] = block_body[idx] - block_body[idx] = node - return build.group block_body - - node - - declare_glob: (node) => - names = extract_declarations @ - - if node[2] == "^" - names = for name in *names - continue unless name[2]\match "^%u" - name - - {"declare", names} - - assign: (node) => - names, values = unpack node, 2 - - num_values = #values - num_names = #values - - -- special code simplifications for single assigns - if num_names == 1 and num_values == 1 - first_value = values[1] - first_name = names[1] - - switch ntype first_value - when "block_exp" - block_body = first_value[2] - idx = #block_body - block_body[idx] = build.assign_one first_name, block_body[idx] - - return build.group { - {"declare", {first_name}} - {"do", block_body} - } - - when "comprehension", "tblcomprehension", "foreach", "for", "while" - return build.assign_one first_name, Value\transform_once @, first_value - - -- bubble cascading assigns - transformed = if num_values == 1 - value = values[1] - t = ntype value - - if t == "decorated" - value = @transform.statement value - t = ntype value - - if types.cascading[t] - ret = (stm) -> - if types.is_value stm - {"assign", names, {stm}} - else - stm - - build.group { - {"declare", names} - @transform.statement value, ret, node - } - - node = transformed or node - - if destructure.has_destructure names - return destructure.split_assign @, node - - node - - continue: (node) => - continue_name = @send "continue" - error "continue must be inside of a loop" unless continue_name - build.group { - build.assign_one continue_name, "true" - {"break"} - } - - export: (node) => - -- assign values if they are included - if #node > 2 - if node[2] == "class" - cls = smart_node node[3] - build.group { - {"export", {cls.name}} - cls - } - else - -- pull out vawlues and assign them after the export - build.group { - { "export", node[2] } - build.assign { - names: node[2] - values: node[3] - } - } - else - nil - - update: (node) => - _, name, op, exp = unpack node - op_final = op\match "^(.+)=$" - error "Unknown op: "..op if not op_final - exp = {"parens", exp} unless value_is_singular exp - build.assign_one name, {"exp", name, op_final, exp} - - import: (node) => - _, names, source = unpack node - table_values = for name in *names - dest_val = if ntype(name) == "colon_stub" - name[2] - else - name - - {{"key_literal", name}, dest_val} - - dest = { "table", table_values } - { "assign", {dest}, {source}, [-1]: node[-1] } - - comprehension: (node, action) => - _, exp, clauses = unpack node - - action = action or (exp) -> {exp} - construct_comprehension action(exp), clauses - - do: (node, ret) => - node[2] = apply_to_last node[2], ret if ret - node - - decorated: (node) => - stm, dec = unpack node, 2 - - wrapped = switch dec[1] - when "if" - cond, fail = unpack dec, 2 - fail = { "else", { fail } } if fail - { "if", cond, { stm }, fail } - when "unless" - { "unless", dec[2], { stm } } - when "comprehension" - { "comprehension", stm, dec[2] } - else - error "Unknown decorator " .. dec[1] - - if ntype(stm) == "assign" - wrapped = build.group { - build.declare names: [name for name in *stm[2] when ntype(name) == "ref"] - wrapped - } - - wrapped - - unless: (node) => - { "if", {"not", {"parens", node[2]}}, unpack node, 3 } - - if: (node, ret) => - -- expand assign in cond - if ntype(node[2]) == "assign" - _, assign, body = unpack node - if destructure.has_destructure assign[2] - name = NameProxy "des" - - body = { - destructure.build_assign @, assign[2][1], name - build.group node[3] - } - - return build.do { - build.assign_one name, assign[3][1] - {"if", name, body, unpack node, 4} - } - else - name = assign[2][1] - return build["do"] { - assign - {"if", name, unpack node, 3} - } - - node = expand_elseif_assign node - - -- apply cascading return decorator - if ret - smart_node node - -- mutate all the bodies - node['then'] = apply_to_last node['then'], ret - for i = 4, #node - case = node[i] - body_idx = #node[i] - case[body_idx] = apply_to_last case[body_idx], ret - - node - - with: (node, ret) => - exp, block = unpack node, 2 - - copy_scope = true - local scope_name, named_assign - - - if ntype(exp) == "assign" - names, values = unpack exp, 2 - first_name = names[1] - - if ntype(first_name) == "ref" - scope_name = first_name - named_assign = exp - exp = values[1] - copy_scope = false - else - scope_name = NameProxy "with" - exp = values[1] - values[1] = scope_name - named_assign = {"assign", names, values} - - elseif @is_local exp - scope_name = exp - copy_scope = false - - scope_name or= NameProxy "with" - - build.do { - Run => @set "scope_var", scope_name - copy_scope and build.assign_one(scope_name, exp) or NOOP - named_assign or NOOP - build.group block - - if ret - ret scope_name - } - - foreach: (node, _) => - smart_node node - source = unpack node.iter - - destructures = {} - node.names = for i, name in ipairs node.names - if ntype(name) == "table" - with proxy = NameProxy "des" - insert destructures, destructure.build_assign @, name, proxy - else - name - - if next destructures - insert destructures, build.group node.body - node.body = destructures - - if ntype(source) == "unpack" - list = source[2] - - index_name = NameProxy "index" - - list_name = @is_local(list) and list or NameProxy "list" - - slice_var = nil - bounds = if is_slice list - slice = list[#list] - table.remove list - table.remove slice, 1 - - list_name = list if @is_local list - - slice[2] = if slice[2] and slice[2] != "" - max_tmp_name = NameProxy "max" - slice_var = build.assign_one max_tmp_name, slice[2] - {"exp", max_tmp_name, "<", 0 - "and", {"length", list_name}, "+", max_tmp_name - "or", max_tmp_name } - else - {"length", list_name} - - slice - else - {1, {"length", list_name}} - - return build.group { - list_name != list and build.assign_one(list_name, list) or NOOP - slice_var or NOOP - build["for"] { - name: index_name - bounds: bounds - body: { - {"assign", node.names, { NameProxy.index list_name, index_name }} - build.group node.body - } - } - } - - node.body = with_continue_listener node.body - - while: (node) => - smart_node node - node.body = with_continue_listener node.body - - for: (node) => - smart_node node - node.body = with_continue_listener node.body - - switch: (node, ret) => - _, exp, conds = unpack node - exp_name = NameProxy "exp" - - -- convert switch conds into if statment conds - convert_cond = (cond) -> - t, case_exps, body = unpack cond - out = {} - insert out, t == "case" and "elseif" or "else" - if t != "else" - cond_exp = {} - for i, case in ipairs case_exps - if i == 1 - insert cond_exp, "exp" - else - insert cond_exp, "or" - - case = {"parens", case} unless value_is_singular case - insert cond_exp, {"exp", case, "==", exp_name} - - insert out, cond_exp - else - body = case_exps - - if ret - body = apply_to_last body, ret - - insert out, body - - out - - first = true - if_stm = {"if"} - for cond in *conds - if_cond = convert_cond cond - if first - first = false - insert if_stm, if_cond[2] - insert if_stm, if_cond[3] - else - insert if_stm, if_cond - - build.group { - build.assign_one exp_name, exp - if_stm - } - - class: (node, ret, parent_assign) => - _, name, parent_val, body = unpack node - parent_val = nil if parent_val == "" - - -- split apart properties and statements - statements = {} - properties = {} - for item in *body - switch item[1] - when "stm" - insert statements, item[2] - when "props" - for tuple in *item[2,] - if ntype(tuple[1]) == "self" - insert statements, build.assign_one unpack tuple - else - insert properties, tuple - - -- find constructor - local constructor - properties = for tuple in *properties - key = tuple[1] - if key[1] == "key_literal" and key[2] == constructor_name - constructor = tuple[2] - continue - else - tuple - - parent_cls_name = NameProxy "parent" - base_name = NameProxy "base" - self_name = NameProxy "self" - cls_name = NameProxy "class" - - unless constructor - constructor = if parent_val - build.fndef { - args: {{"..."}} - arrow: "fat" - body: { - build.chain { base: "super", {"call", {"..."}} } - } - } - else - build.fndef! - - real_name = name or parent_assign and parent_assign[2][1] - real_name = switch ntype real_name - when "chain" - last = real_name[#real_name] - switch ntype last - when "dot" - {"string", '"', last[2]} - when "index" - last[2] - else - "nil" - when "nil" - "nil" - else - name_t = type real_name - -- TODO: don't use string literal as ref - flattened_name = if name_t == "string" - real_name - elseif name_t == "table" and real_name[1] == "ref" - real_name[2] - else - error "don't know how to extract name from #{name_t}" - - {"string", '"', flattened_name} - - cls = build.table { - {"__init", constructor} - {"__base", base_name} - {"__name", real_name} -- "quote the string" - parent_val and {"__parent", parent_cls_name} or nil - } - - -- looking up a name in the class object - class_index = if parent_val - class_lookup = build["if"] { - cond: { "exp", {"ref", "val"}, "==", "nil" } - then: { - parent_cls_name\index"name" - } - } - insert class_lookup, {"else", {"val"}} - - build.fndef { - args: {{"cls"}, {"name"}} - body: { - build.assign_one LocalName"val", build.chain { - base: "rawget", {"call", {base_name, {"ref", "name"}}} - } - class_lookup - } - } - else - base_name - - cls_mt = build.table { - {"__index", class_index} - {"__call", build.fndef { - args: {{"cls"}, {"..."}} - body: { - build.assign_one self_name, build.chain { - base: "setmetatable" - {"call", {"{}", base_name}} - } - build.chain { - base: "cls.__init" - {"call", {self_name, "..."}} - } - self_name - } - }} - } - - cls = build.chain { - base: "setmetatable" - {"call", {cls, cls_mt}} - } - - value = nil - with build - out_body = { - Run => - -- make sure we don't assign the class to a local inside the do - @put_name name if name - - @set "super", (block, chain) -> - if chain - slice = [item for item in *chain[3,]] - new_chain = {"chain", parent_cls_name} - - head = slice[1] - - if head == nil - return parent_cls_name - - switch head[1] - -- calling super, inject calling name and self into chain - when "call" - calling_name = block\get"current_block" - slice[1] = {"call", {"self", unpack head[2]}} - - if ntype(calling_name) == "key_literal" - insert new_chain, {"dot", calling_name[2]} - else - insert new_chain, {"index", calling_name} - - -- colon call on super, replace class with self as first arg - when "colon" - call = head[3] - insert new_chain, {"dot", head[2]} - slice[1] = { "call", { "self", unpack call[2] } } - - insert new_chain, item for item in *slice - - new_chain - else - parent_cls_name - - {"declare_glob", "*"} - - parent_val and .assign_one(parent_cls_name, parent_val) or NOOP - - .assign_one base_name, {"table", properties} - .assign_one base_name\chain"__index", base_name - - parent_val and .chain({ - base: "setmetatable" - {"call", { - base_name, - .chain { base: parent_cls_name, {"dot", "__base"}} - }} - }) or NOOP - - .assign_one cls_name, cls - .assign_one base_name\chain"__class", cls_name - - .group if #statements > 0 then { - .assign_one LocalName"self", cls_name - .group statements - } - - -- run the inherited callback - parent_val and .if({ - cond: {"exp", parent_cls_name\chain "__inherited" } - then: { - parent_cls_name\chain "__inherited", {"call", { - parent_cls_name, cls_name - }} - } - }) or NOOP - - .group if name then { - .assign_one name, cls_name - } - - if ret - ret cls_name - } - - value = .group { - .group if ntype(name) == "value" then { - .declare names: {name} - } - - .do out_body - } - - value -} - -class Accumulator - body_idx: { for: 4, while: 3, foreach: 4 } - - new: (accum_name) => - @accum_name = NameProxy "accum" - @value_name = NameProxy "value" - @len_name = NameProxy "len" - - -- wraps node and mutates body - convert: (node) => - index = @body_idx[ntype node] - node[index] = @mutate_body node[index] - @wrap node - - -- wrap the node into a block_exp - wrap: (node, group_type="block_exp") => - build[group_type] { - build.assign_one @accum_name, build.table! - build.assign_one @len_name, 1 - node - group_type == "block_exp" and @accum_name or NOOP - } - - -- mutates the body of a loop construct to save last value into accumulator - mutate_body: (body) => - -- shortcut to write simpler code if body is a single expression - single_stm = is_singular body - val = if single_stm and types.is_value single_stm - body = {} - single_stm - else - body = apply_to_last body, (n) -> - if types.is_value n - build.assign_one @value_name, n - else - -- just ignore it - build.group { - {"declare", {@value_name}} - n - } - @value_name - - update = { - build.assign_one NameProxy.index(@accum_name, @len_name), val - {"update", @len_name, "+=", 1} - } - - insert body, build.group update - body - -default_accumulator = (node) => - Accumulator!\convert node - -implicitly_return = (scope) -> - is_top = true - fn = (stm) -> - t = ntype stm - - -- expand decorated - if t == "decorated" - stm = scope.transform.statement stm - t = ntype stm - - if types.cascading[t] - is_top = false - scope.transform.statement stm, fn - elseif types.manual_return[t] or not types.is_value stm - -- remove blank return statement - if is_top and t == "return" and stm[2] == "" - NOOP - else - stm - else - if t == "comprehension" and not types.comprehension_has_value stm - stm - else - {"return", stm} - - fn - -Value = Transformer { - for: default_accumulator - while: default_accumulator - foreach: default_accumulator - - do: (node) => - build.block_exp node[2] - - decorated: (node) => - @transform.statement node - - class: (node) => - build.block_exp { node } - - string: (node) => - delim = node[2] - - convert_part = (part) -> - if type(part) == "string" or part == nil - {"string", delim, part or ""} - else - build.chain { base: "tostring", {"call", {part[2]}} } - - -- reduced to single item - if #node <= 3 - return if type(node[3]) == "string" - node - else - convert_part node[3] - - e = {"exp", convert_part node[3]} - - for i=4, #node - insert e, ".." - insert e, convert_part node[i] - e - - comprehension: (node) => - a = Accumulator! - node = @transform.statement node, (exp) -> - a\mutate_body {exp} - a\wrap node - - tblcomprehension: (node) => - _, explist, clauses = unpack node - key_exp, value_exp = unpack explist - - accum = NameProxy "tbl" - - inner = if value_exp - dest = build.chain { base: accum, {"index", key_exp} } - { build.assign_one dest, value_exp } - else - -- If we only have single expression then - -- unpack the result into key and value - key_name, val_name = NameProxy"key", NameProxy"val" - dest = build.chain { base: accum, {"index", key_name} } - { - build.assign names: {key_name, val_name}, values: {key_exp} - build.assign_one dest, val_name - } - - build.block_exp { - build.assign_one accum, build.table! - construct_comprehension inner, clauses - accum - } - - fndef: (node) => - smart_node node - node.body = apply_to_last node.body, implicitly_return self - node.body = { - Run => @listen "varargs", -> -- capture event - unpack node.body - } - - node - - if: (node) => build.block_exp { node } - unless: (node) =>build.block_exp { node } - with: (node) => build.block_exp { node } - switch: (node) => - build.block_exp { node } - - -- pull out colon chain - chain: (node) => - stub = node[#node] - - -- escape lua keywords used in dot accessors - for i=3,#node - part = node[i] - if ntype(part) == "dot" and data.lua_keywords[part[2]] - node[i] = { "index", {"string", '"', part[2]} } - - if ntype(node[2]) == "string" - -- add parens if callee is raw string - node[2] = {"parens", node[2] } - elseif type(stub) == "table" and stub[1] == "colon_stub" - -- convert colon stub into code - table.remove node, #node - - base_name = NameProxy "base" - fn_name = NameProxy "fn" - - is_super = ntype(node[2]) == "ref" and node[2][2] == "super" - @transform.value build.block_exp { - build.assign { - names: {base_name} - values: {node} - } - - build.assign { - names: {fn_name} - values: { - build.chain { base: base_name, {"dot", stub[2]} } - } - } - - build.fndef { - args: {{"..."}} - body: { - build.chain { - base: fn_name, {"call", {is_super and "self" or base_name, "..."}} - } - } - } - } - - block_exp: (node) => - _, body = unpack node - - fn = nil - arg_list = {} - - fn = smart_node build.fndef body: { - Run => - @listen "varargs", -> - insert arg_list, "..." - insert fn.args, {"..."} - @unlisten "varargs" - - unpack body - } - - build.chain { base: {"parens", fn}, {"call", arg_list} } +{ + Statement: require "moonscript.transform.statement" + Value: require "moonscript.transform.value" } - -{ :Statement, :Value, :Run } diff --git a/moonscript/transform/accumulator.lua b/moonscript/transform/accumulator.lua new file mode 100644 index 00000000..539c65fd --- /dev/null +++ b/moonscript/transform/accumulator.lua @@ -0,0 +1,110 @@ +local types = require("moonscript.types") +local build, ntype, NOOP +build, ntype, NOOP = types.build, types.ntype, types.NOOP +local NameProxy +NameProxy = require("moonscript.transform.names").NameProxy +local insert +insert = table.insert +local is_singular +is_singular = function(body) + if #body ~= 1 then + return false + end + if "group" == ntype(body) then + return is_singular(body[2]) + else + return body[1] + end +end +local transform_last_stm +transform_last_stm = require("moonscript.transform.statements").transform_last_stm +local Accumulator +do + local _class_0 + local _base_0 = { + body_idx = { + ["for"] = 4, + ["while"] = 3, + foreach = 4 + }, + convert = function(self, node) + local index = self.body_idx[ntype(node)] + node[index] = self:mutate_body(node[index]) + return self:wrap(node) + end, + wrap = function(self, node, group_type) + if group_type == nil then + group_type = "block_exp" + end + return build[group_type]({ + build.assign_one(self.accum_name, build.table()), + build.assign_one(self.len_name, 1), + node, + group_type == "block_exp" and self.accum_name or NOOP + }) + end, + mutate_body = function(self, body) + local single_stm = is_singular(body) + local val + if single_stm and types.is_value(single_stm) then + body = { } + val = single_stm + else + body = transform_last_stm(body, function(n) + if types.is_value(n) then + return build.assign_one(self.value_name, n) + else + return build.group({ + { + "declare", + { + self.value_name + } + }, + n + }) + end + end) + val = self.value_name + end + local update = { + build.assign_one(NameProxy.index(self.accum_name, self.len_name), val), + { + "update", + self.len_name, + "+=", + 1 + } + } + insert(body, build.group(update)) + return body + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, accum_name) + self.accum_name = NameProxy("accum") + self.value_name = NameProxy("value") + self.len_name = NameProxy("len") + end, + __base = _base_0, + __name = "Accumulator" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Accumulator = _class_0 +end +local default_accumulator +default_accumulator = function(self, node) + return Accumulator():convert(node) +end +return { + Accumulator = Accumulator, + default_accumulator = default_accumulator +} diff --git a/moonscript/transform/accumulator.moon b/moonscript/transform/accumulator.moon new file mode 100644 index 00000000..62ff015c --- /dev/null +++ b/moonscript/transform/accumulator.moon @@ -0,0 +1,71 @@ +types = require "moonscript.types" + +import build, ntype, NOOP from types +import NameProxy from require "moonscript.transform.names" + +import insert from table + +-- is a body a single expression/statement +is_singular = (body) -> + return false if #body != 1 + if "group" == ntype body + is_singular body[2] + else + body[1] + +import transform_last_stm from require "moonscript.transform.statements" + +class Accumulator + body_idx: { for: 4, while: 3, foreach: 4 } + + new: (accum_name) => + @accum_name = NameProxy "accum" + @value_name = NameProxy "value" + @len_name = NameProxy "len" + + -- wraps node and mutates body + convert: (node) => + index = @body_idx[ntype node] + node[index] = @mutate_body node[index] + @wrap node + + -- wrap the node into a block_exp + wrap: (node, group_type="block_exp") => + build[group_type] { + build.assign_one @accum_name, build.table! + build.assign_one @len_name, 1 + node + group_type == "block_exp" and @accum_name or NOOP + } + + -- mutates the body of a loop construct to save last value into accumulator + mutate_body: (body) => + -- shortcut to write simpler code if body is a single expression + single_stm = is_singular body + val = if single_stm and types.is_value single_stm + body = {} + single_stm + else + body = transform_last_stm body, (n) -> + if types.is_value n + build.assign_one @value_name, n + else + -- just ignore it + build.group { + {"declare", {@value_name}} + n + } + @value_name + + update = { + build.assign_one NameProxy.index(@accum_name, @len_name), val + {"update", @len_name, "+=", 1} + } + + insert body, build.group update + body + +default_accumulator = (node) => + Accumulator!\convert node + +{ :Accumulator, :default_accumulator } diff --git a/moonscript/transform/class.lua b/moonscript/transform/class.lua new file mode 100644 index 00000000..74f93c59 --- /dev/null +++ b/moonscript/transform/class.lua @@ -0,0 +1,486 @@ +local NameProxy, LocalName +do + local _obj_0 = require("moonscript.transform.names") + NameProxy, LocalName = _obj_0.NameProxy, _obj_0.LocalName +end +local Run +Run = require("moonscript.transform.statements").Run +local CONSTRUCTOR_NAME = "new" +local insert +insert = table.insert +local build, ntype, NOOP +do + local _obj_0 = require("moonscript.types") + build, ntype, NOOP = _obj_0.build, _obj_0.ntype, _obj_0.NOOP +end +local unpack +unpack = require("moonscript.util").unpack +local transform_super +transform_super = function(cls_name, on_base, block, chain) + if on_base == nil then + on_base = true + end + local relative_parent = { + "chain", + cls_name, + { + "dot", + "__parent" + } + } + if not (chain) then + return relative_parent + end + local chain_tail = { + unpack(chain, 3) + } + local head = chain_tail[1] + if head == nil then + return relative_parent + end + local new_chain = relative_parent + local _exp_0 = head[1] + if "call" == _exp_0 then + if on_base then + insert(new_chain, { + "dot", + "__base" + }) + end + local calling_name = block:get("current_method") + assert(calling_name, "missing calling name") + chain_tail[1] = { + "call", + { + "self", + unpack(head[2]) + } + } + if ntype(calling_name) == "key_literal" then + insert(new_chain, { + "dot", + calling_name[2] + }) + else + insert(new_chain, { + "index", + calling_name + }) + end + elseif "colon" == _exp_0 then + local call = chain_tail[2] + if call and call[1] == "call" then + chain_tail[1] = { + "dot", + head[2] + } + chain_tail[2] = { + "call", + { + "self", + unpack(call[2]) + } + } + end + end + for _index_0 = 1, #chain_tail do + local item = chain_tail[_index_0] + insert(new_chain, item) + end + return new_chain +end +local super_scope +super_scope = function(value, t, key) + local prev_method + return { + "scoped", + Run(function(self) + prev_method = self:get("current_method") + self:set("current_method", key) + return self:set("super", t) + end), + value, + Run(function(self) + return self:set("current_method", prev_method) + end) + } +end +return function(self, node, ret, parent_assign) + local name, parent_val, body = unpack(node, 2) + if parent_val == "" then + parent_val = nil + end + local parent_cls_name = NameProxy("parent") + local base_name = NameProxy("base") + local self_name = NameProxy("self") + local cls_name = NameProxy("class") + local cls_instance_super + cls_instance_super = function(...) + return transform_super(cls_name, true, ...) + end + local cls_super + cls_super = function(...) + return transform_super(cls_name, false, ...) + end + local statements = { } + local properties = { } + for _index_0 = 1, #body do + local item = body[_index_0] + local _exp_0 = item[1] + if "stm" == _exp_0 then + insert(statements, item[2]) + elseif "props" == _exp_0 then + for _index_1 = 2, #item do + local tuple = item[_index_1] + if ntype(tuple[1]) == "self" then + local k, v + k, v = tuple[1], tuple[2] + v = super_scope(v, cls_super, { + "key_literal", + k[2] + }) + insert(statements, build.assign_one(k, v)) + else + insert(properties, tuple) + end + end + end + end + local constructor + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #properties do + local _continue_0 = false + repeat + local tuple = properties[_index_0] + local key = tuple[1] + local _value_0 + if key[1] == "key_literal" and key[2] == CONSTRUCTOR_NAME then + constructor = tuple[2] + _continue_0 = true + break + else + local val + key, val = tuple[1], tuple[2] + _value_0 = { + key, + super_scope(val, cls_instance_super, key) + } + end + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + properties = _accum_0 + end + if not (constructor) then + if parent_val then + constructor = build.fndef({ + args = { + { + "..." + } + }, + arrow = "fat", + body = { + build.chain({ + base = "super", + { + "call", + { + "..." + } + } + }) + } + }) + else + constructor = build.fndef() + end + end + local real_name = name or parent_assign and parent_assign[2][1] + local _exp_0 = ntype(real_name) + if "chain" == _exp_0 then + local last = real_name[#real_name] + local _exp_1 = ntype(last) + if "dot" == _exp_1 then + real_name = { + "string", + '"', + last[2] + } + elseif "index" == _exp_1 then + real_name = last[2] + else + real_name = "nil" + end + elseif "nil" == _exp_0 then + real_name = "nil" + else + local name_t = type(real_name) + local flattened_name + if name_t == "string" then + flattened_name = real_name + elseif name_t == "table" and real_name[1] == "ref" then + flattened_name = real_name[2] + else + flattened_name = error("don't know how to extract name from " .. tostring(name_t)) + end + real_name = { + "string", + '"', + flattened_name + } + end + local cls = build.table({ + { + "__init", + super_scope(constructor, cls_super, { + "key_literal", + "__init" + }) + }, + { + "__base", + base_name + }, + { + "__name", + real_name + }, + parent_val and { + "__parent", + parent_cls_name + } or nil + }) + local class_index + if parent_val then + local class_lookup = build["if"]({ + cond = { + "exp", + { + "ref", + "val" + }, + "==", + "nil" + }, + ["then"] = { + build.assign_one(LocalName("parent"), build.chain({ + base = "rawget", + { + "call", + { + { + "ref", + "cls" + }, + { + "string", + '"', + "__parent" + } + } + } + })), + build["if"]({ + cond = LocalName("parent"), + ["then"] = { + build.chain({ + base = LocalName("parent"), + { + "index", + "name" + } + }) + } + }) + } + }) + insert(class_lookup, { + "else", + { + "val" + } + }) + class_index = build.fndef({ + args = { + { + "cls" + }, + { + "name" + } + }, + body = { + build.assign_one(LocalName("val"), build.chain({ + base = "rawget", + { + "call", + { + base_name, + { + "ref", + "name" + } + } + } + })), + class_lookup + } + }) + else + class_index = base_name + end + local cls_mt = build.table({ + { + "__index", + class_index + }, + { + "__call", + build.fndef({ + args = { + { + "cls" + }, + { + "..." + } + }, + body = { + build.assign_one(self_name, build.chain({ + base = "setmetatable", + { + "call", + { + "{}", + base_name + } + } + })), + build.chain({ + base = "cls.__init", + { + "call", + { + self_name, + "..." + } + } + }), + self_name + } + }) + } + }) + cls = build.chain({ + base = "setmetatable", + { + "call", + { + cls, + cls_mt + } + } + }) + local value = nil + do + local out_body = { + Run(function(self) + if name then + return self:put_name(name) + end + end), + { + "declare", + { + cls_name + } + }, + { + "declare_glob", + "*" + }, + parent_val and build.assign_one(parent_cls_name, parent_val) or NOOP, + build.assign_one(base_name, { + "table", + properties + }), + build.assign_one(base_name:chain("__index"), base_name), + parent_val and build.chain({ + base = "setmetatable", + { + "call", + { + base_name, + build.chain({ + base = parent_cls_name, + { + "dot", + "__base" + } + }) + } + } + }) or NOOP, + build.assign_one(cls_name, cls), + build.assign_one(base_name:chain("__class"), cls_name), + build.group((function() + if #statements > 0 then + return { + build.assign_one(LocalName("self"), cls_name), + build.group(statements) + } + end + end)()), + parent_val and build["if"]({ + cond = { + "exp", + parent_cls_name:chain("__inherited") + }, + ["then"] = { + parent_cls_name:chain("__inherited", { + "call", + { + parent_cls_name, + cls_name + } + }) + } + }) or NOOP, + build.group((function() + if name then + return { + build.assign_one(name, cls_name) + } + end + end)()), + (function() + if ret then + return ret(cls_name) + end + end)() + } + value = build.group({ + build.group((function() + if ntype(name) == "value" then + return { + build.declare({ + names = { + name + } + }) + } + end + end)()), + build["do"](out_body) + }) + end + return value +end diff --git a/moonscript/transform/class.moon b/moonscript/transform/class.moon new file mode 100644 index 00000000..7451375b --- /dev/null +++ b/moonscript/transform/class.moon @@ -0,0 +1,285 @@ +import NameProxy, LocalName from require "moonscript.transform.names" +import Run from require "moonscript.transform.statements" + +CONSTRUCTOR_NAME = "new" + +import insert from table +import build, ntype, NOOP from require "moonscript.types" +import unpack from require "moonscript.util" + +transform_super = (cls_name, on_base=true, block, chain) -> + relative_parent = { + "chain", + cls_name + {"dot", "__parent"} + } + + return relative_parent unless chain + + chain_tail = { unpack chain, 3 } + head = chain_tail[1] + + if head == nil + return relative_parent + + new_chain = relative_parent + + switch head[1] + -- calling super, inject calling name and self into chain + when "call" + if on_base + insert new_chain, {"dot", "__base"} + + calling_name = block\get "current_method" + assert calling_name, "missing calling name" + chain_tail[1] = {"call", {"self", unpack head[2]}} + + if ntype(calling_name) == "key_literal" + insert new_chain, {"dot", calling_name[2]} + else + insert new_chain, {"index", calling_name} + + -- colon call on super, replace class with self as first arg + when "colon" + call = chain_tail[2] + -- calling chain tail + if call and call[1] == "call" + chain_tail[1] = { + "dot" + head[2] + } + + chain_tail[2] = { + "call" + { + "self" + unpack call[2] + } + } + + insert new_chain, item for item in *chain_tail + new_chain + + +super_scope = (value, t, key) -> + local prev_method + + { + "scoped", + Run => + prev_method = @get "current_method" + @set "current_method", key + @set "super", t + value + Run => + @set "current_method", prev_method + } + +(node, ret, parent_assign) => + name, parent_val, body = unpack node, 2 + parent_val = nil if parent_val == "" + + parent_cls_name = NameProxy "parent" + base_name = NameProxy "base" + self_name = NameProxy "self" + cls_name = NameProxy "class" + + -- super call on instance + cls_instance_super = (...) -> transform_super cls_name, true, ... + + -- super call on parent class + cls_super = (...) -> transform_super cls_name, false, ... + + -- split apart properties and statements + statements = {} + properties = {} + for item in *body + switch item[1] + when "stm" + insert statements, item[2] + when "props" + for tuple in *item[2,] + if ntype(tuple[1]) == "self" + {k,v} = tuple + v = super_scope v, cls_super, {"key_literal", k[2]} + insert statements, build.assign_one k, v + else + insert properties, tuple + + -- find constructor + local constructor + properties = for tuple in *properties + key = tuple[1] + if key[1] == "key_literal" and key[2] == CONSTRUCTOR_NAME + constructor = tuple[2] + continue + else + {key, val} = tuple + {key, super_scope val, cls_instance_super, key} + + + unless constructor + constructor = if parent_val + build.fndef { + args: {{"..."}} + arrow: "fat" + body: { + build.chain { base: "super", {"call", {"..."}} } + } + } + else + build.fndef! + + real_name = name or parent_assign and parent_assign[2][1] + real_name = switch ntype real_name + when "chain" + last = real_name[#real_name] + switch ntype last + when "dot" + {"string", '"', last[2]} + when "index" + last[2] + else + "nil" + when "nil" + "nil" + else + name_t = type real_name + -- TODO: don't use string literal as ref + flattened_name = if name_t == "string" + real_name + elseif name_t == "table" and real_name[1] == "ref" + real_name[2] + else + error "don't know how to extract name from #{name_t}" + + {"string", '"', flattened_name} + + cls = build.table { + {"__init", super_scope constructor, cls_super, {"key_literal", "__init"}} + {"__base", base_name} + {"__name", real_name} -- "quote the string" + parent_val and {"__parent", parent_cls_name} or nil + } + + -- looking up a name in the class object + class_index = if parent_val + class_lookup = build["if"] { + cond: { "exp", {"ref", "val"}, "==", "nil" } + then: { + build.assign_one LocalName"parent", build.chain { + base: "rawget" + { + "call", { + {"ref", "cls"} + {"string", '"', "__parent"} + } + } + } + + build.if { + cond: LocalName "parent" + then: { + build.chain { + base: LocalName "parent" + {"index", "name"} + } + } + } + } + } + insert class_lookup, {"else", {"val"}} + + build.fndef { + args: {{"cls"}, {"name"}} + body: { + build.assign_one LocalName"val", build.chain { + base: "rawget", {"call", {base_name, {"ref", "name"}}} + } + class_lookup + } + } + else + base_name + + cls_mt = build.table { + {"__index", class_index} + {"__call", build.fndef { + args: {{"cls"}, {"..."}} + body: { + build.assign_one self_name, build.chain { + base: "setmetatable" + {"call", {"{}", base_name}} + } + build.chain { + base: "cls.__init" + {"call", {self_name, "..."}} + } + self_name + } + }} + } + + cls = build.chain { + base: "setmetatable" + {"call", {cls, cls_mt}} + } + + value = nil + with build + out_body = { + Run => + -- make sure we don't assign the class to a local inside the do + @put_name name if name + + {"declare", { cls_name }} + {"declare_glob", "*"} + + parent_val and .assign_one(parent_cls_name, parent_val) or NOOP + + .assign_one base_name, {"table", properties} + .assign_one base_name\chain"__index", base_name + + parent_val and .chain({ + base: "setmetatable" + {"call", { + base_name, + .chain { base: parent_cls_name, {"dot", "__base"}} + }} + }) or NOOP + + .assign_one cls_name, cls + .assign_one base_name\chain"__class", cls_name + + .group if #statements > 0 then { + .assign_one LocalName"self", cls_name + .group statements + } + + -- run the inherited callback + parent_val and .if({ + cond: {"exp", parent_cls_name\chain "__inherited" } + then: { + parent_cls_name\chain "__inherited", {"call", { + parent_cls_name, cls_name + }} + } + }) or NOOP + + .group if name then { + .assign_one name, cls_name + } + + if ret + ret cls_name + } + + value = .group { + .group if ntype(name) == "value" then { + .declare names: {name} + } + + .do out_body + } + + value diff --git a/moonscript/transform/comprehension.lua b/moonscript/transform/comprehension.lua new file mode 100644 index 00000000..da33892d --- /dev/null +++ b/moonscript/transform/comprehension.lua @@ -0,0 +1,54 @@ +local is_value +is_value = require("moonscript.types").is_value +local construct_comprehension +construct_comprehension = function(inner, clauses) + local current_stms = inner + for i = #clauses, 1, -1 do + local clause = clauses[i] + local t = clause[1] + local _exp_0 = t + if "for" == _exp_0 then + local _, name, bounds + _, name, bounds = clause[1], clause[2], clause[3] + current_stms = { + "for", + name, + bounds, + current_stms + } + elseif "foreach" == _exp_0 then + local _, names, iter + _, names, iter = clause[1], clause[2], clause[3] + current_stms = { + "foreach", + names, + { + iter + }, + current_stms + } + elseif "when" == _exp_0 then + local _, cond + _, cond = clause[1], clause[2] + current_stms = { + "if", + cond, + current_stms + } + else + current_stms = error("Unknown comprehension clause: " .. t) + end + current_stms = { + current_stms + } + end + return current_stms[1] +end +local comprehension_has_value +comprehension_has_value = function(comp) + return is_value(comp[2]) +end +return { + construct_comprehension = construct_comprehension, + comprehension_has_value = comprehension_has_value +} diff --git a/moonscript/transform/comprehension.moon b/moonscript/transform/comprehension.moon new file mode 100644 index 00000000..a181a891 --- /dev/null +++ b/moonscript/transform/comprehension.moon @@ -0,0 +1,30 @@ + +import is_value from require "moonscript.types" + +construct_comprehension = (inner, clauses) -> + current_stms = inner + for i=#clauses,1,-1 + clause = clauses[i] + t = clause[1] + + current_stms = switch t + when "for" + {_, name, bounds} = clause + {"for", name, bounds, current_stms} + when "foreach" + {_, names, iter} = clause + {"foreach", names, {iter}, current_stms} + when "when" + {_, cond} = clause + {"if", cond, current_stms} + else + error "Unknown comprehension clause: "..t + + current_stms = {current_stms} + + current_stms[1] + +comprehension_has_value = (comp) -> + is_value comp[2] + +{:construct_comprehension, :comprehension_has_value} diff --git a/moonscript/transform/destructure.lua b/moonscript/transform/destructure.lua index cb450711..1e370b7d 100644 --- a/moonscript/transform/destructure.lua +++ b/moonscript/transform/destructure.lua @@ -4,26 +4,13 @@ do ntype, mtype, build = _obj_0.ntype, _obj_0.mtype, _obj_0.build end local NameProxy -do - local _obj_0 = require("moonscript.transform.names") - NameProxy = _obj_0.NameProxy -end +NameProxy = require("moonscript.transform.names").NameProxy local insert -do - local _obj_0 = table - insert = _obj_0.insert -end +insert = table.insert local unpack -do - local _obj_0 = require("moonscript.util") - unpack = _obj_0.unpack -end +unpack = require("moonscript.util").unpack local user_error -do - local _obj_0 = require("moonscript.errors") - user_error = _obj_0.user_error -end -local util = require("moonscript.util") +user_error = require("moonscript.errors").user_error local join join = function(...) do @@ -81,7 +68,7 @@ extract_assign_names = function(name, accum, prefix) local s if ntype(key) == "key_literal" then local key_name = key[2] - if ntype(key_name) == "colon_stub" then + if ntype(key_name) == "colon" then s = key_name else s = { @@ -116,6 +103,7 @@ extract_assign_names = function(name, accum, prefix) end local build_assign build_assign = function(scope, destruct_literal, receiver) + assert(receiver, "attempting to build destructure assign with no receiver") local extracted_names = extract_assign_names(destruct_literal) local names = { } local values = { } @@ -125,7 +113,7 @@ build_assign = function(scope, destruct_literal, receiver) values } local obj - if scope:is_local(receiver) then + if scope:is_local(receiver) or #extracted_names == 1 then obj = receiver else do @@ -144,7 +132,13 @@ build_assign = function(scope, destruct_literal, receiver) for _index_0 = 1, #extracted_names do local tuple = extracted_names[_index_0] insert(names, tuple[1]) - insert(values, NameProxy.chain(obj, unpack(tuple[2]))) + local chain + if obj then + chain = NameProxy.chain(obj, unpack(tuple[2])) + else + chain = "nil" + end + insert(values, chain) end return build.group({ { @@ -235,5 +229,6 @@ end return { has_destructure = has_destructure, split_assign = split_assign, - build_assign = build_assign + build_assign = build_assign, + extract_assign_names = extract_assign_names } diff --git a/moonscript/transform/destructure.moon b/moonscript/transform/destructure.moon index 274e6ed3..b0413b2d 100644 --- a/moonscript/transform/destructure.moon +++ b/moonscript/transform/destructure.moon @@ -6,8 +6,6 @@ import unpack from require "moonscript.util" import user_error from require "moonscript.errors" -util = require "moonscript.util" - join = (...) -> with out = {} i = 1 @@ -22,6 +20,7 @@ has_destructure = (names) -> false extract_assign_names = (name, accum={}, prefix={}) -> + i = 1 for tuple in *name[2] value, suffix = if #tuple == 1 @@ -30,9 +29,10 @@ extract_assign_names = (name, accum={}, prefix={}) -> tuple[1], s else key = tuple[1] + s = if ntype(key) == "key_literal" key_name = key[2] - if ntype(key_name) == "colon_stub" + if ntype(key_name) == "colon" key_name else {"dot", key_name} @@ -54,6 +54,8 @@ extract_assign_names = (name, accum={}, prefix={}) -> accum build_assign = (scope, destruct_literal, receiver) -> + assert receiver, "attempting to build destructure assign with no receiver" + extracted_names = extract_assign_names destruct_literal names = {} @@ -61,7 +63,7 @@ build_assign = (scope, destruct_literal, receiver) -> inner = {"assign", names, values} - obj = if scope\is_local receiver + obj = if scope\is_local(receiver) or #extracted_names == 1 receiver else with obj = NameProxy "obj" @@ -72,7 +74,11 @@ build_assign = (scope, destruct_literal, receiver) -> for tuple in *extracted_names insert names, tuple[1] - insert values, NameProxy.chain obj, unpack tuple[2] + chain = if obj + NameProxy.chain obj, unpack tuple[2] + else + "nil" + insert values, chain build.group { {"declare", names} @@ -121,4 +127,4 @@ split_assign = (scope, assign) -> build.group g -{ :has_destructure, :split_assign, :build_assign } +{ :has_destructure, :split_assign, :build_assign, :extract_assign_names } diff --git a/moonscript/transform/names.lua b/moonscript/transform/names.lua index abc0d5b9..f6af4c46 100644 --- a/moonscript/transform/names.lua +++ b/moonscript/transform/names.lua @@ -1,22 +1,17 @@ local build -do - local _obj_0 = require("moonscript.types") - build = _obj_0.build -end +build = require("moonscript.types").build local unpack -do - local _obj_0 = require("moonscript.util") - unpack = _obj_0.unpack -end +unpack = require("moonscript.util").unpack local LocalName do + local _class_0 local _base_0 = { get_name = function(self) return self.name end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, name) self.name = name self[1] = "temp_name" @@ -36,6 +31,7 @@ do end local NameProxy do + local _class_0 local _base_0 = { get_name = function(self, scope, dont_put) if dont_put == nil then @@ -47,31 +43,21 @@ do return self.name end, chain = function(self, ...) - local items - do - local _accum_0 = { } - local _len_0 = 1 - local _list_0 = { - ... - } - for _index_0 = 1, #_list_0 do - local i = _list_0[_index_0] - if type(i) == "string" then - _accum_0[_len_0] = { - "dot", - i - } - else - _accum_0[_len_0] = i - end - _len_0 = _len_0 + 1 + local items = { + base = self, + ... + } + for k, v in ipairs(items) do + if type(v) == "string" then + items[k] = { + "dot", + v + } + else + items[k] = v end - items = _accum_0 end - return build.chain({ - base = self, - unpack(items) - }) + return build.chain(items) end, index = function(self, key) if type(key) == "string" then @@ -97,7 +83,7 @@ do end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, prefix) self.prefix = prefix self[1] = "temp_name" @@ -115,7 +101,18 @@ do _base_0.__class = _class_0 NameProxy = _class_0 end +local is_name_proxy +is_name_proxy = function(v) + if not (type(v) == "table") then + return false + end + local _exp_0 = v.__class + if LocalName == _exp_0 or NameProxy == _exp_0 then + return true + end +end return { NameProxy = NameProxy, - LocalName = LocalName + LocalName = LocalName, + is_name_proxy = is_name_proxy } diff --git a/moonscript/transform/names.moon b/moonscript/transform/names.moon index daf17ed9..4af6aa0d 100644 --- a/moonscript/transform/names.moon +++ b/moonscript/transform/names.moon @@ -18,16 +18,14 @@ class NameProxy @name chain: (...) => - items = for i in *{...} - if type(i) == "string" - {"dot", i} + items = { base: @, ... } + for k,v in ipairs items + items[k] = if type(v) == "string" + {"dot", v} else - i + v - build.chain { - base: self - unpack items - } + build.chain items index: (key) => if type(key) == "string" @@ -43,5 +41,11 @@ class NameProxy else ("name")\format @prefix +is_name_proxy = (v) -> + return false unless type(v) == "table" + + switch v.__class + when LocalName, NameProxy + true -{ :NameProxy, :LocalName } +{ :NameProxy, :LocalName, :is_name_proxy } diff --git a/moonscript/transform/statement.lua b/moonscript/transform/statement.lua new file mode 100644 index 00000000..edeab697 --- /dev/null +++ b/moonscript/transform/statement.lua @@ -0,0 +1,847 @@ +local Transformer +Transformer = require("moonscript.transform.transformer").Transformer +local NameProxy, LocalName, is_name_proxy +do + local _obj_0 = require("moonscript.transform.names") + NameProxy, LocalName, is_name_proxy = _obj_0.NameProxy, _obj_0.LocalName, _obj_0.is_name_proxy +end +local Run, transform_last_stm, implicitly_return, last_stm +do + local _obj_0 = require("moonscript.transform.statements") + Run, transform_last_stm, implicitly_return, last_stm = _obj_0.Run, _obj_0.transform_last_stm, _obj_0.implicitly_return, _obj_0.last_stm +end +local types = require("moonscript.types") +local build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP +build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP = types.build, types.ntype, types.is_value, types.smart_node, types.value_is_singular, types.is_slice, types.NOOP +local insert +insert = table.insert +local destructure = require("moonscript.transform.destructure") +local construct_comprehension +construct_comprehension = require("moonscript.transform.comprehension").construct_comprehension +local unpack +unpack = require("moonscript.util").unpack +local with_continue_listener +with_continue_listener = function(body) + local continue_name = nil + return { + Run(function(self) + return self:listen("continue", function() + if not (continue_name) then + continue_name = NameProxy("continue") + self:put_name(continue_name) + end + return continue_name + end) + end), + build.group(body), + Run(function(self) + if not (continue_name) then + return + end + local last = last_stm(body) + local enclose_lines = types.terminating[last and ntype(last)] + self:put_name(continue_name, nil) + return self:splice(function(lines) + if enclose_lines then + lines = { + "do", + { + lines + } + } + end + return { + { + "assign", + { + continue_name + }, + { + "false" + } + }, + { + "repeat", + "true", + { + lines, + { + "assign", + { + continue_name + }, + { + "true" + } + } + } + }, + { + "if", + { + "not", + continue_name + }, + { + { + "break" + } + } + } + } + end) + end) + } +end +local extract_declarations +extract_declarations = function(self, body, start, out) + if body == nil then + body = self.current_stms + end + if start == nil then + start = self.current_stm_i + 1 + end + if out == nil then + out = { } + end + for i = start, #body do + local _continue_0 = false + repeat + local stm = body[i] + if stm == nil then + _continue_0 = true + break + end + stm = self.transform.statement(stm) + body[i] = stm + local _exp_0 = stm[1] + if "assign" == _exp_0 or "declare" == _exp_0 then + local _list_0 = stm[2] + for _index_0 = 1, #_list_0 do + local name = _list_0[_index_0] + if ntype(name) == "ref" then + insert(out, name) + elseif type(name) == "string" then + insert(out, name) + end + end + elseif "group" == _exp_0 then + extract_declarations(self, stm[2], 1, out) + end + _continue_0 = true + until true + if not _continue_0 then + break + end + end + return out +end +local expand_elseif_assign +expand_elseif_assign = function(ifstm) + for i = 4, #ifstm do + local case = ifstm[i] + if ntype(case) == "elseif" and ntype(case[2]) == "assign" then + local split = { + unpack(ifstm, 1, i - 1) + } + insert(split, { + "else", + { + { + "if", + case[2], + case[3], + unpack(ifstm, i + 1) + } + } + }) + return split + end + end + return ifstm +end +return Transformer({ + transform = function(self, tuple) + local _, node, fn + _, node, fn = tuple[1], tuple[2], tuple[3] + return fn(node) + end, + root_stms = function(self, body) + return transform_last_stm(body, implicitly_return(self)) + end, + ["return"] = function(self, node) + local ret_val = node[2] + local ret_val_type = ntype(ret_val) + if ret_val_type == "explist" and #ret_val == 2 then + ret_val = ret_val[2] + ret_val_type = ntype(ret_val) + end + if types.cascading[ret_val_type] then + return implicitly_return(self)(ret_val) + end + if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension" then + local Value = require("moonscript.transform.value") + ret_val = Value:transform_once(self, ret_val) + if ntype(ret_val) == "block_exp" then + return build.group(transform_last_stm(ret_val[2], function(stm) + return { + "return", + stm + } + end)) + end + end + node[2] = ret_val + return node + end, + declare_glob = function(self, node) + local names = extract_declarations(self) + if node[2] == "^" then + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local _continue_0 = false + repeat + local name = names[_index_0] + local str_name + if ntype(name) == "ref" then + str_name = name[2] + else + str_name = name + end + if not (str_name:match("^%u")) then + _continue_0 = true + break + end + local _value_0 = name + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + _continue_0 = true + until true + if not _continue_0 then + break + end + end + names = _accum_0 + end + end + return { + "declare", + names + } + end, + assign = function(self, node) + local names, values = unpack(node, 2) + local num_values = #values + local num_names = #values + if num_names == 1 and num_values == 1 then + local first_value = values[1] + local first_name = names[1] + local first_type = ntype(first_value) + if first_type == "chain" then + local Value = require("moonscript.transform.value") + first_value = Value:transform_once(self, first_value) + first_type = ntype(first_value) + end + local _exp_0 = ntype(first_value) + if "block_exp" == _exp_0 then + local block_body = first_value[2] + local idx = #block_body + block_body[idx] = build.assign_one(first_name, block_body[idx]) + return build.group({ + { + "declare", + { + first_name + } + }, + { + "do", + block_body + } + }) + elseif "comprehension" == _exp_0 or "tblcomprehension" == _exp_0 or "foreach" == _exp_0 or "for" == _exp_0 or "while" == _exp_0 then + local Value = require("moonscript.transform.value") + return build.assign_one(first_name, Value:transform_once(self, first_value)) + else + values[1] = first_value + end + end + local transformed + if num_values == 1 then + local value = values[1] + local t = ntype(value) + if t == "decorated" then + value = self.transform.statement(value) + t = ntype(value) + end + if types.cascading[t] then + local ret + ret = function(stm) + if is_value(stm) then + return { + "assign", + names, + { + stm + } + } + else + return stm + end + end + transformed = build.group({ + { + "declare", + names + }, + self.transform.statement(value, ret, node) + }) + end + end + node = transformed or node + if destructure.has_destructure(names) then + return destructure.split_assign(self, node) + end + return node + end, + continue = function(self, node) + local continue_name = self:send("continue") + if not (continue_name) then + error("continue must be inside of a loop") + end + return build.group({ + build.assign_one(continue_name, "true"), + { + "break" + } + }) + end, + export = function(self, node) + if #node > 2 then + if node[2] == "class" then + local cls = smart_node(node[3]) + return build.group({ + { + "export", + { + cls.name + } + }, + cls + }) + else + return build.group({ + { + "export", + node[2] + }, + build.assign({ + names = node[2], + values = node[3] + }) + }) + end + else + return nil + end + end, + update = function(self, node) + local name, op, exp = unpack(node, 2) + local op_final = op:match("^(.+)=$") + if not op_final then + error("Unknown op: " .. op) + end + local lifted + if ntype(name) == "chain" then + lifted = { } + local new_chain + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 3, #name do + local part = name[_index_0] + if ntype(part) == "index" then + local proxy = NameProxy("update") + table.insert(lifted, { + proxy, + part[2] + }) + _accum_0[_len_0] = { + "index", + proxy + } + else + _accum_0[_len_0] = part + end + _len_0 = _len_0 + 1 + end + new_chain = _accum_0 + end + if next(lifted) then + name = { + name[1], + name[2], + unpack(new_chain) + } + end + end + if not (value_is_singular(exp)) then + exp = { + "parens", + exp + } + end + local out = build.assign_one(name, { + "exp", + name, + op_final, + exp + }) + if lifted and next(lifted) then + local names + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #lifted do + local l = lifted[_index_0] + _accum_0[_len_0] = l[1] + _len_0 = _len_0 + 1 + end + names = _accum_0 + end + local values + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #lifted do + local l = lifted[_index_0] + _accum_0[_len_0] = l[2] + _len_0 = _len_0 + 1 + end + values = _accum_0 + end + out = build.group({ + { + "assign", + names, + values + }, + out + }) + end + return out + end, + import = function(self, node) + local names, source = unpack(node, 2) + local table_values + do + local _accum_0 = { } + local _len_0 = 1 + for _index_0 = 1, #names do + local name = names[_index_0] + local dest_name + if ntype(name) == "colon" then + dest_name = name[2] + else + dest_name = name + end + local _value_0 = { + { + "key_literal", + name + }, + dest_name + } + _accum_0[_len_0] = _value_0 + _len_0 = _len_0 + 1 + end + table_values = _accum_0 + end + local dest = { + "table", + table_values + } + return { + "assign", + { + dest + }, + { + source + }, + [-1] = node[-1] + } + end, + comprehension = function(self, node, action) + local exp, clauses = unpack(node, 2) + action = action or function(exp) + return { + exp + } + end + return construct_comprehension(action(exp), clauses) + end, + ["do"] = function(self, node, ret) + if ret then + node[2] = transform_last_stm(node[2], ret) + end + return node + end, + decorated = function(self, node) + local stm, dec = unpack(node, 2) + local wrapped + local _exp_0 = dec[1] + if "if" == _exp_0 then + local cond, fail = unpack(dec, 2) + if fail then + fail = { + "else", + { + fail + } + } + end + wrapped = { + "if", + cond, + { + stm + }, + fail + } + elseif "unless" == _exp_0 then + wrapped = { + "unless", + dec[2], + { + stm + } + } + elseif "comprehension" == _exp_0 then + wrapped = { + "comprehension", + stm, + dec[2] + } + else + wrapped = error("Unknown decorator " .. dec[1]) + end + if ntype(stm) == "assign" then + wrapped = build.group({ + build.declare({ + names = (function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = stm[2] + for _index_0 = 1, #_list_0 do + local name = _list_0[_index_0] + if ntype(name) == "ref" then + _accum_0[_len_0] = name + _len_0 = _len_0 + 1 + end + end + return _accum_0 + end)() + }), + wrapped + }) + end + return wrapped + end, + unless = function(self, node) + local clause = node[2] + if ntype(clause) == "assign" then + if destructure.has_destructure(clause[2]) then + error("destructure not allowed in unless assignment") + end + return build["do"]({ + clause, + { + "if", + { + "not", + clause[2][1] + }, + unpack(node, 3) + } + }) + else + return { + "if", + { + "not", + { + "parens", + clause + } + }, + unpack(node, 3) + } + end + end, + ["if"] = function(self, node, ret) + if ntype(node[2]) == "assign" then + local assign, body = unpack(node, 2) + if destructure.has_destructure(assign[2]) then + local name = NameProxy("des") + body = { + destructure.build_assign(self, assign[2][1], name), + build.group(node[3]) + } + return build["do"]({ + build.assign_one(name, assign[3][1]), + { + "if", + name, + body, + unpack(node, 4) + } + }) + else + local name = assign[2][1] + return build["do"]({ + assign, + { + "if", + name, + unpack(node, 3) + } + }) + end + end + node = expand_elseif_assign(node) + if ret then + smart_node(node) + node['then'] = transform_last_stm(node['then'], ret) + for i = 4, #node do + local case = node[i] + local body_idx = #node[i] + case[body_idx] = transform_last_stm(case[body_idx], ret) + end + end + return node + end, + with = function(self, node, ret) + local exp, block = unpack(node, 2) + local copy_scope = true + local scope_name, named_assign + do + local last = last_stm(block) + if last then + if types.terminating[ntype(last)] then + ret = false + end + end + end + if ntype(exp) == "assign" then + local names, values = unpack(exp, 2) + local first_name = names[1] + if ntype(first_name) == "ref" then + scope_name = first_name + named_assign = exp + exp = values[1] + copy_scope = false + else + scope_name = NameProxy("with") + exp = values[1] + values[1] = scope_name + named_assign = { + "assign", + names, + values + } + end + elseif self:is_local(exp) then + scope_name = exp + copy_scope = false + end + scope_name = scope_name or NameProxy("with") + local out = build["do"]({ + copy_scope and build.assign_one(scope_name, exp) or NOOP, + named_assign or NOOP, + Run(function(self) + return self:set("scope_var", scope_name) + end), + unpack(block) + }) + if ret then + table.insert(out[2], ret(scope_name)) + end + return out + end, + foreach = function(self, node, _) + smart_node(node) + local source = unpack(node.iter) + local destructures = { } + do + local _accum_0 = { } + local _len_0 = 1 + for i, name in ipairs(node.names) do + if ntype(name) == "table" then + do + local proxy = NameProxy("des") + insert(destructures, destructure.build_assign(self, name, proxy)) + _accum_0[_len_0] = proxy + end + else + _accum_0[_len_0] = name + end + _len_0 = _len_0 + 1 + end + node.names = _accum_0 + end + if next(destructures) then + insert(destructures, build.group(node.body)) + node.body = destructures + end + if ntype(source) == "unpack" then + local list = source[2] + local index_name = NameProxy("index") + local list_name = self:is_local(list) and list or NameProxy("list") + local slice_var = nil + local bounds + if is_slice(list) then + local slice = list[#list] + table.remove(list) + table.remove(slice, 1) + if self:is_local(list) then + list_name = list + end + if slice[2] and slice[2] ~= "" then + local max_tmp_name = NameProxy("max") + slice_var = build.assign_one(max_tmp_name, slice[2]) + slice[2] = { + "exp", + max_tmp_name, + "<", + 0, + "and", + { + "length", + list_name + }, + "+", + max_tmp_name, + "or", + max_tmp_name + } + else + slice[2] = { + "length", + list_name + } + end + bounds = slice + else + bounds = { + 1, + { + "length", + list_name + } + } + end + local names + do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = node.names + for _index_0 = 1, #_list_0 do + local n = _list_0[_index_0] + _accum_0[_len_0] = is_name_proxy(n) and n or LocalName(n) or n + _len_0 = _len_0 + 1 + end + names = _accum_0 + end + return build.group({ + list_name ~= list and build.assign_one(list_name, list) or NOOP, + slice_var or NOOP, + build["for"]({ + name = index_name, + bounds = bounds, + body = { + { + "assign", + names, + { + NameProxy.index(list_name, index_name) + } + }, + build.group(node.body) + } + }) + }) + end + node.body = with_continue_listener(node.body) + end, + ["while"] = function(self, node) + smart_node(node) + node.body = with_continue_listener(node.body) + end, + ["for"] = function(self, node) + smart_node(node) + node.body = with_continue_listener(node.body) + end, + switch = function(self, node, ret) + local exp, conds = unpack(node, 2) + local exp_name = NameProxy("exp") + local convert_cond + convert_cond = function(cond) + local t, case_exps, body = unpack(cond) + local out = { } + insert(out, t == "case" and "elseif" or "else") + if t ~= "else" then + local cond_exp = { } + for i, case in ipairs(case_exps) do + if i == 1 then + insert(cond_exp, "exp") + else + insert(cond_exp, "or") + end + if not (value_is_singular(case)) then + case = { + "parens", + case + } + end + insert(cond_exp, { + "exp", + case, + "==", + exp_name + }) + end + insert(out, cond_exp) + else + body = case_exps + end + if ret then + body = transform_last_stm(body, ret) + end + insert(out, body) + return out + end + local first = true + local if_stm = { + "if" + } + for _index_0 = 1, #conds do + local cond = conds[_index_0] + local if_cond = convert_cond(cond) + if first then + first = false + insert(if_stm, if_cond[2]) + insert(if_stm, if_cond[3]) + else + insert(if_stm, if_cond) + end + end + return build.group({ + build.assign_one(exp_name, exp), + if_stm + }) + end, + class = require("moonscript.transform.class") +}) diff --git a/moonscript/transform/statement.moon b/moonscript/transform/statement.moon new file mode 100644 index 00000000..2e4b8e83 --- /dev/null +++ b/moonscript/transform/statement.moon @@ -0,0 +1,522 @@ +import Transformer from require "moonscript.transform.transformer" + +import NameProxy, LocalName, is_name_proxy from require "moonscript.transform.names" + +import Run, transform_last_stm, implicitly_return, last_stm + from require "moonscript.transform.statements" + +types = require "moonscript.types" + +import build, ntype, is_value, smart_node, value_is_singular, is_slice, NOOP + from types + +import insert from table + +destructure = require "moonscript.transform.destructure" +import construct_comprehension from require "moonscript.transform.comprehension" + +import unpack from require "moonscript.util" + +with_continue_listener = (body) -> + continue_name = nil + + { + Run => + @listen "continue", -> + unless continue_name + continue_name = NameProxy"continue" + @put_name continue_name + continue_name + + build.group body + + Run => + return unless continue_name + last = last_stm body + enclose_lines = types.terminating[last and ntype(last)] + + @put_name continue_name, nil + @splice (lines) -> + lines = {"do", {lines}} if enclose_lines + + { + {"assign", {continue_name}, {"false"}} + {"repeat", "true", { + lines + {"assign", {continue_name}, {"true"}} + }} + {"if", {"not", continue_name}, { + {"break"} + }} + } + } + + +-- this mutates body searching for assigns +extract_declarations = (body=@current_stms, start=@current_stm_i + 1, out={}) => + for i=start,#body + stm = body[i] + continue if stm == nil + stm = @transform.statement stm + body[i] = stm + switch stm[1] + when "assign", "declare" + for name in *stm[2] + if ntype(name) == "ref" + insert out, name + elseif type(name) == "string" + -- TODO: don't use string literal as ref + insert out, name + when "group" + extract_declarations @, stm[2], 1, out + out + +expand_elseif_assign = (ifstm) -> + for i = 4, #ifstm + case = ifstm[i] + if ntype(case) == "elseif" and ntype(case[2]) == "assign" + split = { unpack ifstm, 1, i - 1 } + insert split, { + "else", { + {"if", case[2], case[3], unpack ifstm, i + 1} + } + } + return split + + ifstm + + +Transformer { + transform: (tuple) => + {_, node, fn} = tuple + fn node + + root_stms: (body) => + transform_last_stm body, implicitly_return @ + + return: (node) => + ret_val = node[2] + ret_val_type = ntype ret_val + + if ret_val_type == "explist" and #ret_val == 2 + ret_val = ret_val[2] + ret_val_type = ntype ret_val + + if types.cascading[ret_val_type] + return implicitly_return(@) ret_val + + -- flatten things that create block exp + if ret_val_type == "chain" or ret_val_type == "comprehension" or ret_val_type == "tblcomprehension" + -- TODO: clean this up + Value = require "moonscript.transform.value" + ret_val = Value\transform_once @, ret_val + if ntype(ret_val) == "block_exp" + return build.group transform_last_stm ret_val[2], (stm)-> + {"return", stm} + + node[2] = ret_val + node + + declare_glob: (node) => + names = extract_declarations @ + + if node[2] == "^" + names = for name in *names + str_name = if ntype(name) == "ref" + name[2] + else + name + + continue unless str_name\match "^%u" + name + + {"declare", names} + + assign: (node) => + names, values = unpack node, 2 + + num_values = #values + num_names = #values + + -- special code simplifications for single assigns + if num_names == 1 and num_values == 1 + first_value = values[1] + first_name = names[1] + first_type = ntype first_value + + -- reduce colon stub chain to block exp + if first_type == "chain" + -- TODO: clean this up + Value = require "moonscript.transform.value" + first_value = Value\transform_once @, first_value + first_type = ntype first_value + + switch ntype first_value + when "block_exp" + block_body = first_value[2] + idx = #block_body + block_body[idx] = build.assign_one first_name, block_body[idx] + + return build.group { + {"declare", {first_name}} + {"do", block_body} + } + + when "comprehension", "tblcomprehension", "foreach", "for", "while" + -- TODO: clean this up + Value = require "moonscript.transform.value" + return build.assign_one first_name, Value\transform_once @, first_value + else + values[1] = first_value + + -- bubble cascading assigns + transformed = if num_values == 1 + value = values[1] + t = ntype value + + if t == "decorated" + value = @transform.statement value + t = ntype value + + if types.cascading[t] + ret = (stm) -> + if is_value stm + {"assign", names, {stm}} + else + stm + + build.group { + {"declare", names} + @transform.statement value, ret, node + } + + node = transformed or node + + if destructure.has_destructure names + return destructure.split_assign @, node + + node + + continue: (node) => + continue_name = @send "continue" + error "continue must be inside of a loop" unless continue_name + build.group { + build.assign_one continue_name, "true" + {"break"} + } + + export: (node) => + -- assign values if they are included + if #node > 2 + if node[2] == "class" + cls = smart_node node[3] + build.group { + {"export", {cls.name}} + cls + } + else + -- pull out vawlues and assign them after the export + build.group { + { "export", node[2] } + build.assign { + names: node[2] + values: node[3] + } + } + else + nil + + update: (node) => + name, op, exp = unpack node, 2 + op_final = op\match "^(.+)=$" + + error "Unknown op: "..op if not op_final + + local lifted + + if ntype(name) == "chain" + lifted = {} + new_chain = for part in *name[3,] + if ntype(part) == "index" + proxy = NameProxy "update" + table.insert lifted, { proxy, part[2] } + { "index", proxy } + else + part + + if next lifted + name = {name[1], name[2], unpack new_chain} + + exp = {"parens", exp} unless value_is_singular exp + out = build.assign_one name, {"exp", name, op_final, exp} + + if lifted and next lifted + names = [l[1] for l in *lifted] + values = [l[2] for l in *lifted] + + out = build.group { + {"assign", names, values} + out + } + + out + + import: (node) => + names, source = unpack node, 2 + table_values = for name in *names + dest_name = if ntype(name) == "colon" + name[2] + else + name + + {{"key_literal", name}, dest_name} + + dest = { "table", table_values } + { "assign", {dest}, {source}, [-1]: node[-1] } + + comprehension: (node, action) => + exp, clauses = unpack node, 2 + + action = action or (exp) -> {exp} + construct_comprehension action(exp), clauses + + do: (node, ret) => + node[2] = transform_last_stm node[2], ret if ret + node + + decorated: (node) => + stm, dec = unpack node, 2 + + wrapped = switch dec[1] + when "if" + cond, fail = unpack dec, 2 + fail = { "else", { fail } } if fail + { "if", cond, { stm }, fail } + when "unless" + { "unless", dec[2], { stm } } + when "comprehension" + { "comprehension", stm, dec[2] } + else + error "Unknown decorator " .. dec[1] + + if ntype(stm) == "assign" + wrapped = build.group { + build.declare names: [name for name in *stm[2] when ntype(name) == "ref"] + wrapped + } + + wrapped + + unless: (node) => + clause = node[2] + + if ntype(clause) == "assign" + if destructure.has_destructure clause[2] + error "destructure not allowed in unless assignment" + + build.do { + clause + { "if", {"not", clause[2][1]}, unpack node, 3 } + } + + else + { "if", {"not", {"parens", clause}}, unpack node, 3 } + + if: (node, ret) => + -- expand assign in cond + if ntype(node[2]) == "assign" + assign, body = unpack node, 2 + if destructure.has_destructure assign[2] + name = NameProxy "des" + + body = { + destructure.build_assign @, assign[2][1], name + build.group node[3] + } + + return build.do { + build.assign_one name, assign[3][1] + {"if", name, body, unpack node, 4} + } + else + name = assign[2][1] + return build.do { + assign + {"if", name, unpack node, 3} + } + + node = expand_elseif_assign node + + -- apply cascading return decorator + if ret + smart_node node + -- mutate all the bodies + node['then'] = transform_last_stm node['then'], ret + for i = 4, #node + case = node[i] + body_idx = #node[i] + case[body_idx] = transform_last_stm case[body_idx], ret + + node + + with: (node, ret) => + exp, block = unpack node, 2 + + copy_scope = true + local scope_name, named_assign + + if last = last_stm block + ret = false if types.terminating[ntype(last)] + + if ntype(exp) == "assign" + names, values = unpack exp, 2 + first_name = names[1] + + if ntype(first_name) == "ref" + scope_name = first_name + named_assign = exp + exp = values[1] + copy_scope = false + else + scope_name = NameProxy "with" + exp = values[1] + values[1] = scope_name + named_assign = {"assign", names, values} + + elseif @is_local exp + scope_name = exp + copy_scope = false + + scope_name or= NameProxy "with" + + out = build.do { + copy_scope and build.assign_one(scope_name, exp) or NOOP + named_assign or NOOP + Run => @set "scope_var", scope_name + unpack block + } + + if ret + table.insert out[2], ret scope_name + + out + + foreach: (node, _) => + smart_node node + source = unpack node.iter + + destructures = {} + node.names = for i, name in ipairs node.names + if ntype(name) == "table" + with proxy = NameProxy "des" + insert destructures, destructure.build_assign @, name, proxy + else + name + + if next destructures + insert destructures, build.group node.body + node.body = destructures + + if ntype(source) == "unpack" + list = source[2] + + index_name = NameProxy "index" + + list_name = @is_local(list) and list or NameProxy "list" + + slice_var = nil + bounds = if is_slice list + slice = list[#list] + table.remove list + table.remove slice, 1 + + list_name = list if @is_local list + + slice[2] = if slice[2] and slice[2] != "" + max_tmp_name = NameProxy "max" + slice_var = build.assign_one max_tmp_name, slice[2] + {"exp", max_tmp_name, "<", 0 + "and", {"length", list_name}, "+", max_tmp_name + "or", max_tmp_name } + else + {"length", list_name} + + slice + else + {1, {"length", list_name}} + + names = [is_name_proxy(n) and n or LocalName(n) or n for n in *node.names] + + return build.group { + list_name != list and build.assign_one(list_name, list) or NOOP + slice_var or NOOP + build["for"] { + name: index_name + bounds: bounds + body: { + {"assign", names, { NameProxy.index list_name, index_name }} + build.group node.body + } + } + } + + node.body = with_continue_listener node.body + + while: (node) => + smart_node node + node.body = with_continue_listener node.body + + for: (node) => + smart_node node + node.body = with_continue_listener node.body + + switch: (node, ret) => + exp, conds = unpack node, 2 + exp_name = NameProxy "exp" + + -- convert switch conds into if statment conds + convert_cond = (cond) -> + t, case_exps, body = unpack cond + out = {} + insert out, t == "case" and "elseif" or "else" + if t != "else" + cond_exp = {} + for i, case in ipairs case_exps + if i == 1 + insert cond_exp, "exp" + else + insert cond_exp, "or" + + case = {"parens", case} unless value_is_singular case + insert cond_exp, {"exp", case, "==", exp_name} + + insert out, cond_exp + else + body = case_exps + + if ret + body = transform_last_stm body, ret + + insert out, body + + out + + first = true + if_stm = {"if"} + for cond in *conds + if_cond = convert_cond cond + if first + first = false + insert if_stm, if_cond[2] + insert if_stm, if_cond[3] + else + insert if_stm, if_cond + + build.group { + build.assign_one exp_name, exp + if_stm + } + + class: require "moonscript.transform.class" + +} diff --git a/moonscript/transform/statements.lua b/moonscript/transform/statements.lua new file mode 100644 index 00000000..6a49b77e --- /dev/null +++ b/moonscript/transform/statements.lua @@ -0,0 +1,115 @@ +local types = require("moonscript.types") +local ntype, mtype, is_value, NOOP +ntype, mtype, is_value, NOOP = types.ntype, types.mtype, types.is_value, types.NOOP +local comprehension_has_value +comprehension_has_value = require("moonscript.transform.comprehension").comprehension_has_value +local Run +do + local _class_0 + local _base_0 = { + call = function(self, state) + return self.fn(state) + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, fn) + self.fn = fn + self[1] = "run" + end, + __base = _base_0, + __name = "Run" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Run = _class_0 +end +local last_stm +last_stm = function(stms) + local last_exp_id = 0 + for i = #stms, 1, -1 do + local stm = stms[i] + if stm and mtype(stm) ~= Run then + if ntype(stm) == "group" then + return last_stm(stm[2]) + end + last_exp_id = i + break + end + end + return stms[last_exp_id], last_exp_id, stms +end +local transform_last_stm +transform_last_stm = function(stms, fn) + local _, last_idx, _stms = last_stm(stms) + if _stms ~= stms then + error("cannot transform last node in group") + end + return (function() + local _accum_0 = { } + local _len_0 = 1 + for i, stm in ipairs(stms) do + if i == last_idx then + _accum_0[_len_0] = { + "transform", + stm, + fn + } + else + _accum_0[_len_0] = stm + end + _len_0 = _len_0 + 1 + end + return _accum_0 + end)() +end +local chain_is_stub +chain_is_stub = function(chain) + local stub = chain[#chain] + return stub and ntype(stub) == "colon" +end +local implicitly_return +implicitly_return = function(scope) + local is_top = true + local fn + fn = function(stm) + local t = ntype(stm) + if t == "decorated" then + stm = scope.transform.statement(stm) + t = ntype(stm) + end + if types.cascading[t] then + is_top = false + return scope.transform.statement(stm, fn) + elseif types.manual_return[t] or not is_value(stm) then + if is_top and t == "return" and stm[2] == "" then + return NOOP + else + return stm + end + else + if t == "comprehension" and not comprehension_has_value(stm) then + return stm + else + return { + "return", + stm + } + end + end + end + return fn +end +return { + Run = Run, + last_stm = last_stm, + transform_last_stm = transform_last_stm, + chain_is_stub = chain_is_stub, + implicitly_return = implicitly_return +} diff --git a/moonscript/transform/statements.moon b/moonscript/transform/statements.moon new file mode 100644 index 00000000..164e6b58 --- /dev/null +++ b/moonscript/transform/statements.moon @@ -0,0 +1,78 @@ + +types = require "moonscript.types" +import ntype, mtype, is_value, NOOP from types + +import comprehension_has_value from require "moonscript.transform.comprehension" + +-- A Run is a special statement node that lets a function run and mutate the +-- state of the compiler +class Run + new: (@fn) => + @[1] = "run" + + call: (state) => + @.fn state + +-- extract the last statment from an array of statements +-- is group aware +-- returns: the last statement, the index, the table it was fetched from +last_stm = (stms) -> + last_exp_id = 0 + for i = #stms, 1, -1 + stm = stms[i] + if stm and mtype(stm) != Run + if ntype(stm) == "group" + return last_stm stm[2] + + last_exp_id = i + break + + stms[last_exp_id], last_exp_id, stms + +-- transform the last stm is a list of stms +-- will puke on group +transform_last_stm = (stms, fn) -> + _, last_idx, _stms = last_stm stms + + if _stms != stms + error "cannot transform last node in group" + + return for i, stm in ipairs stms + if i == last_idx + {"transform", stm, fn} + else + stm + +chain_is_stub = (chain) -> + stub = chain[#chain] + stub and ntype(stub) == "colon" + +implicitly_return = (scope) -> + is_top = true + fn = (stm) -> + t = ntype stm + + -- expand decorated + if t == "decorated" + stm = scope.transform.statement stm + t = ntype stm + + if types.cascading[t] + is_top = false + scope.transform.statement stm, fn + elseif types.manual_return[t] or not is_value stm + -- remove blank return statement + if is_top and t == "return" and stm[2] == "" + NOOP + else + stm + else + if t == "comprehension" and not comprehension_has_value stm + stm + else + {"return", stm} + + fn + +{:Run, :last_stm, :transform_last_stm, :chain_is_stub, :implicitly_return } + diff --git a/moonscript/transform/transformer.lua b/moonscript/transform/transformer.lua new file mode 100644 index 00000000..4ea16959 --- /dev/null +++ b/moonscript/transform/transformer.lua @@ -0,0 +1,74 @@ +local ntype +ntype = require("moonscript.types").ntype +local Transformer +do + local _class_0 + local _base_0 = { + transform_once = function(self, scope, node, ...) + if self.seen_nodes[node] then + return node + end + self.seen_nodes[node] = true + local transformer = self.transformers[ntype(node)] + if transformer then + return transformer(scope, node, ...) or node + else + return node + end + end, + transform = function(self, scope, node, ...) + if self.seen_nodes[node] then + return node + end + self.seen_nodes[node] = true + while true do + local transformer = self.transformers[ntype(node)] + local res + if transformer then + res = transformer(scope, node, ...) or node + else + res = node + end + if res == node then + return node + end + node = res + end + return node + end, + bind = function(self, scope) + return function(...) + return self:transform(scope, ...) + end + end, + __call = function(self, ...) + return self:transform(...) + end, + can_transform = function(self, node) + return self.transformers[ntype(node)] ~= nil + end + } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function(self, transformers) + self.transformers = transformers + self.seen_nodes = setmetatable({ }, { + __mode = "k" + }) + end, + __base = _base_0, + __name = "Transformer" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + Transformer = _class_0 +end +return { + Transformer = Transformer +} diff --git a/moonscript/transform/transformer.moon b/moonscript/transform/transformer.moon new file mode 100644 index 00000000..8a40c3c7 --- /dev/null +++ b/moonscript/transform/transformer.moon @@ -0,0 +1,42 @@ +import ntype from require "moonscript.types" + +class Transformer + new: (@transformers) => + @seen_nodes = setmetatable {}, __mode: "k" + + transform_once: (scope, node, ...) => + return node if @seen_nodes[node] + @seen_nodes[node] = true + + transformer = @transformers[ntype node] + if transformer + transformer(scope, node, ...) or node + else + node + + transform: (scope, node, ...) => + return node if @seen_nodes[node] + + @seen_nodes[node] = true + while true + transformer = @transformers[ntype node] + res = if transformer + transformer(scope, node, ...) or node + else + node + + return node if res == node + node = res + + node + + bind: (scope) => + (...) -> @transform scope, ... + + __call: (...) => @transform ... + + can_transform: (node) => + @transformers[ntype node] != nil + + +{ :Transformer } diff --git a/moonscript/transform/value.lua b/moonscript/transform/value.lua new file mode 100644 index 00000000..33aaef99 --- /dev/null +++ b/moonscript/transform/value.lua @@ -0,0 +1,265 @@ +local Transformer +Transformer = require("moonscript.transform.transformer").Transformer +local build, ntype, smart_node +do + local _obj_0 = require("moonscript.types") + build, ntype, smart_node = _obj_0.build, _obj_0.ntype, _obj_0.smart_node +end +local NameProxy +NameProxy = require("moonscript.transform.names").NameProxy +local Accumulator, default_accumulator +do + local _obj_0 = require("moonscript.transform.accumulator") + Accumulator, default_accumulator = _obj_0.Accumulator, _obj_0.default_accumulator +end +local lua_keywords +lua_keywords = require("moonscript.data").lua_keywords +local Run, transform_last_stm, implicitly_return, chain_is_stub +do + local _obj_0 = require("moonscript.transform.statements") + Run, transform_last_stm, implicitly_return, chain_is_stub = _obj_0.Run, _obj_0.transform_last_stm, _obj_0.implicitly_return, _obj_0.chain_is_stub +end +local construct_comprehension +construct_comprehension = require("moonscript.transform.comprehension").construct_comprehension +local insert +insert = table.insert +local unpack +unpack = require("moonscript.util").unpack +return Transformer({ + ["for"] = default_accumulator, + ["while"] = default_accumulator, + foreach = default_accumulator, + ["do"] = function(self, node) + return build.block_exp(node[2]) + end, + decorated = function(self, node) + return self.transform.statement(node) + end, + class = function(self, node) + return build.block_exp({ + node + }) + end, + string = function(self, node) + local delim = node[2] + local convert_part + convert_part = function(part) + if type(part) == "string" or part == nil then + return { + "string", + delim, + part or "" + } + else + return build.chain({ + base = "tostring", + { + "call", + { + part[2] + } + } + }) + end + end + if #node <= 3 then + if type(node[3]) == "string" then + return node + else + return convert_part(node[3]) + end + end + local e = { + "exp", + convert_part(node[3]) + } + for i = 4, #node do + insert(e, "..") + insert(e, convert_part(node[i])) + end + return e + end, + comprehension = function(self, node) + local a = Accumulator() + node = self.transform.statement(node, function(exp) + return a:mutate_body({ + exp + }) + end) + return a:wrap(node) + end, + tblcomprehension = function(self, node) + local explist, clauses = unpack(node, 2) + local key_exp, value_exp = unpack(explist) + local accum = NameProxy("tbl") + local inner + if value_exp then + local dest = build.chain({ + base = accum, + { + "index", + key_exp + } + }) + inner = { + build.assign_one(dest, value_exp) + } + else + local key_name, val_name = NameProxy("key"), NameProxy("val") + local dest = build.chain({ + base = accum, + { + "index", + key_name + } + }) + inner = { + build.assign({ + names = { + key_name, + val_name + }, + values = { + key_exp + } + }), + build.assign_one(dest, val_name) + } + end + return build.block_exp({ + build.assign_one(accum, build.table()), + construct_comprehension(inner, clauses), + accum + }) + end, + fndef = function(self, node) + smart_node(node) + node.body = transform_last_stm(node.body, implicitly_return(self)) + node.body = { + Run(function(self) + return self:listen("varargs", function() end) + end), + unpack(node.body) + } + return node + end, + ["if"] = function(self, node) + return build.block_exp({ + node + }) + end, + unless = function(self, node) + return build.block_exp({ + node + }) + end, + with = function(self, node) + return build.block_exp({ + node + }) + end, + switch = function(self, node) + return build.block_exp({ + node + }) + end, + chain = function(self, node) + for i = 2, #node do + local part = node[i] + if ntype(part) == "dot" and lua_keywords[part[2]] then + node[i] = { + "index", + { + "string", + '"', + part[2] + } + } + end + end + if ntype(node[2]) == "string" then + node[2] = { + "parens", + node[2] + } + end + if chain_is_stub(node) then + local base_name = NameProxy("base") + local fn_name = NameProxy("fn") + local colon = table.remove(node) + local is_super = ntype(node[2]) == "ref" and node[2][2] == "super" + return build.block_exp({ + build.assign({ + names = { + base_name + }, + values = { + node + } + }), + build.assign({ + names = { + fn_name + }, + values = { + build.chain({ + base = base_name, + { + "dot", + colon[2] + } + }) + } + }), + build.fndef({ + args = { + { + "..." + } + }, + body = { + build.chain({ + base = fn_name, + { + "call", + { + is_super and "self" or base_name, + "..." + } + } + }) + } + }) + }) + end + end, + block_exp = function(self, node) + local body = unpack(node, 2) + local fn = nil + local arg_list = { } + fn = smart_node(build.fndef({ + body = { + Run(function(self) + return self:listen("varargs", function() + insert(arg_list, "...") + insert(fn.args, { + "..." + }) + return self:unlisten("varargs") + end) + end), + unpack(body) + } + })) + return build.chain({ + base = { + "parens", + fn + }, + { + "call", + arg_list + } + }) + end +}) diff --git a/moonscript/transform/value.moon b/moonscript/transform/value.moon new file mode 100644 index 00000000..04e16ae2 --- /dev/null +++ b/moonscript/transform/value.moon @@ -0,0 +1,164 @@ +import Transformer from require "moonscript.transform.transformer" +import build, ntype, smart_node from require "moonscript.types" + +import NameProxy from require "moonscript.transform.names" +import Accumulator, default_accumulator from require "moonscript.transform.accumulator" +import lua_keywords from require "moonscript.data" + +import Run, transform_last_stm, implicitly_return, chain_is_stub from require "moonscript.transform.statements" + +import construct_comprehension from require "moonscript.transform.comprehension" + +import insert from table +import unpack from require "moonscript.util" + +Transformer { + for: default_accumulator + while: default_accumulator + foreach: default_accumulator + + do: (node) => + build.block_exp node[2] + + decorated: (node) => + @transform.statement node + + class: (node) => + build.block_exp { node } + + string: (node) => + delim = node[2] + + convert_part = (part) -> + if type(part) == "string" or part == nil + {"string", delim, part or ""} + else + build.chain { base: "tostring", {"call", {part[2]}} } + + -- reduced to single item + if #node <= 3 + return if type(node[3]) == "string" + node + else + convert_part node[3] + + e = {"exp", convert_part node[3]} + + for i=4, #node + insert e, ".." + insert e, convert_part node[i] + e + + comprehension: (node) => + a = Accumulator! + node = @transform.statement node, (exp) -> + a\mutate_body {exp} + a\wrap node + + tblcomprehension: (node) => + explist, clauses = unpack node, 2 + key_exp, value_exp = unpack explist + + accum = NameProxy "tbl" + + inner = if value_exp + dest = build.chain { base: accum, {"index", key_exp} } + { build.assign_one dest, value_exp } + else + -- If we only have single expression then + -- unpack the result into key and value + key_name, val_name = NameProxy"key", NameProxy"val" + dest = build.chain { base: accum, {"index", key_name} } + { + build.assign names: {key_name, val_name}, values: {key_exp} + build.assign_one dest, val_name + } + + build.block_exp { + build.assign_one accum, build.table! + construct_comprehension inner, clauses + accum + } + + fndef: (node) => + smart_node node + node.body = transform_last_stm node.body, implicitly_return self + node.body = { + Run => @listen "varargs", -> -- capture event + unpack node.body + } + + node + + if: (node) => + build.block_exp { node } + + unless: (node) => + build.block_exp { node } + + with: (node) => + build.block_exp { node } + + switch: (node) => + build.block_exp { node } + + -- pull out colon chain + chain: (node) => + -- escape lua keywords used in dot accessors + for i=2,#node + part = node[i] + if ntype(part) == "dot" and lua_keywords[part[2]] + node[i] = { "index", {"string", '"', part[2]} } + + if ntype(node[2]) == "string" + -- add parens if callee is raw string + node[2] = {"parens", node[2] } + + if chain_is_stub node + base_name = NameProxy "base" + fn_name = NameProxy "fn" + colon = table.remove node + + is_super = ntype(node[2]) == "ref" and node[2][2] == "super" + build.block_exp { + build.assign { + names: {base_name} + values: {node} + } + + build.assign { + names: {fn_name} + values: { + build.chain { base: base_name, {"dot", colon[2]} } + } + } + + build.fndef { + args: {{"..."}} + body: { + build.chain { + base: fn_name, {"call", {is_super and "self" or base_name, "..."}} + } + } + } + } + + block_exp: (node) => + body = unpack node, 2 + + fn = nil + arg_list = {} + + fn = smart_node build.fndef body: { + Run => + @listen "varargs", -> + insert arg_list, "..." + insert fn.args, {"..."} + @unlisten "varargs" + + unpack body + } + + build.chain { base: {"parens", fn}, {"call", arg_list} } +} + diff --git a/moonscript/types.lua b/moonscript/types.lua index aae50c91..e8efc409 100644 --- a/moonscript/types.lua +++ b/moonscript/types.lua @@ -1,14 +1,8 @@ local util = require("moonscript.util") local Set -do - local _obj_0 = require("moonscript.data") - Set = _obj_0.Set -end +Set = require("moonscript.data").Set local insert -do - local _obj_0 = table - insert = _obj_0.insert -end +insert = table.insert local unpack unpack = util.unpack local manual_return = Set({ @@ -25,6 +19,10 @@ local cascading = Set({ "class", "do" }) +local terminating = Set({ + "return", + "break" +}) local ntype ntype = function(node) local _exp_0 = type(node) @@ -47,14 +45,12 @@ do return moon_type(val) end end -local has_value -has_value = function(node) - if ntype(node) == "chain" then - local ctype = ntype(node[#node]) - return ctype ~= "call" and ctype ~= "colon" - else - return true +local value_can_be_statement +value_can_be_statement = function(node) + if not (ntype(node) == "chain") then + return false end + return ntype(node[#node]) == "call" end local is_value is_value = function(stm) @@ -62,10 +58,6 @@ is_value = function(stm) local transform = require("moonscript.transform") return compile.Block:is_value(stm) or transform.Value:can_transform(stm) end -local comprehension_has_value -comprehension_has_value = function(comp) - return is_value(comp[2]) -end local value_is_singular value_is_singular = function(node) return type(node) ~= "table" or node[1] ~= "exp" or #node == 2 @@ -312,6 +304,9 @@ local smart_node smart_node = function(node) return setmetatable(node, smart_node_mt[ntype(node)]) end +local NOOP = { + "noop" +} return { ntype = ntype, smart_node = smart_node, @@ -321,7 +316,8 @@ return { manual_return = manual_return, cascading = cascading, value_is_singular = value_is_singular, - comprehension_has_value = comprehension_has_value, - has_value = has_value, - mtype = mtype + value_can_be_statement = value_can_be_statement, + mtype = mtype, + terminating = terminating, + NOOP = NOOP } diff --git a/moonscript/types.moon b/moonscript/types.moon index 9b415231..23f4ed91 100644 --- a/moonscript/types.moon +++ b/moonscript/types.moon @@ -6,12 +6,20 @@ import insert from table import unpack from util -- implicit return does not work on these statements -manual_return = Set{"foreach", "for", "while", "return"} +manual_return = Set { + "foreach", "for", "while", "return" +} -- Assigns and returns are bubbled into their bodies. -- All cascading statement transform functions accept a second arugment that -- is the transformation to apply to the last statement in their body -cascading = Set{ "if", "unless", "with", "switch", "class", "do" } +cascading = Set { + "if", "unless", "with", "switch", "class", "do" +} + +terminating = Set { + "return", "break" +} -- type of node as string ntype = (node) -> @@ -23,6 +31,7 @@ ntype = (node) -> else "value" +-- gets the class of a type if possible mtype = do moon_type = util.moon.type -- lets us check a smart node without throwing an error @@ -31,13 +40,11 @@ mtype = do return "table" if mt and mt.smart_node moon_type val --- does this always return a value -has_value = (node) -> - if ntype(node) == "chain" - ctype = ntype(node[#node]) - ctype != "call" and ctype != "colon" - else - true +-- can this value be compiled in a line by itself +value_can_be_statement = (node) -> + return false unless ntype(node) == "chain" + -- it's a function call + ntype(node[#node]) == "call" is_value = (stm) -> compile = require "moonscript.compile" @@ -45,9 +52,6 @@ is_value = (stm) -> compile.Block\is_value(stm) or transform.Value\can_transform stm -comprehension_has_value = (comp) -> - is_value comp[2] - value_is_singular = (node) -> type(node) != "table" or node[1] != "exp" or #node == 2 @@ -182,9 +186,12 @@ smart_node_mt = setmetatable {}, { smart_node = (node) -> setmetatable node, smart_node_mt[ntype node] +NOOP = {"noop"} + { :ntype, :smart_node, :build, :is_value, :is_slice, :manual_return, - :cascading, :value_is_singular, :comprehension_has_value, :has_value, - :mtype + :cascading, :value_is_singular, + :value_can_be_statement, :mtype, :terminating + :NOOP } diff --git a/moonscript/util.lua b/moonscript/util.lua index dfb43557..83d7e37f 100644 --- a/moonscript/util.lua +++ b/moonscript/util.lua @@ -1,8 +1,5 @@ local concat -do - local _obj_0 = table - concat = _obj_0.concat -end +concat = table.concat local unpack = unpack or table.unpack local type = type local moon = { @@ -63,14 +60,6 @@ get_closest_line = function(str, line_num) return line, line_num end end -local reversed -reversed = function(seq) - return coroutine.wrap(function() - for i = #seq, 1, -1 do - coroutine.yield(i, seq[i]) - end - end) -end local split split = function(str, delim) if str == "" then @@ -113,7 +102,11 @@ dump = function(what) lines = _accum_0 end seen[what] = false - return "{\n" .. concat(lines) .. (" "):rep((depth - 1) * 4) .. "}\n" + local class_name + if type(what.__class) == "table" and type(what.__class.__name) == "string" then + class_name = "<" .. tostring(what.__class.__name) .. ">" + end + return tostring(class_name or "") .. "{\n" .. concat(lines) .. (" "):rep((depth - 1) * 4) .. "}\n" else return tostring(what) .. "\n" end @@ -199,12 +192,19 @@ get_options = function(...) return { }, ... end end +local safe_module +safe_module = function(name, tbl) + return setmetatable(tbl, { + __index = function(self, key) + return error("Attempted to import non-existent `" .. tostring(key) .. "` from " .. tostring(name)) + end + }) +end return { moon = moon, pos_to_line = pos_to_line, get_closest_line = get_closest_line, get_line = get_line, - reversed = reversed, trim = trim, split = split, dump = dump, @@ -212,5 +212,6 @@ return { getfenv = getfenv, setfenv = setfenv, get_options = get_options, - unpack = unpack + unpack = unpack, + safe_module = safe_module } diff --git a/moonscript/util.moon b/moonscript/util.moon index d43d0505..9969ac4a 100644 --- a/moonscript/util.moon +++ b/moonscript/util.moon @@ -48,11 +48,6 @@ get_closest_line = (str, line_num) -> else line, line_num -reversed = (seq) -> - coroutine.wrap -> - for i=#seq,1,-1 - coroutine.yield i, seq[i] - split = (str, delim) -> return {} if str == "" str ..= delim @@ -75,7 +70,10 @@ dump = (what) -> seen[what] = false - "{\n" .. concat(lines) .. (" ")\rep((depth - 1)*4) .. "}\n" + class_name = if type(what.__class) == "table" and type(what.__class.__name) == "string" + "<#{what.__class.__name}>" + + "#{class_name or ""}{\n" .. concat(lines) .. (" ")\rep((depth - 1)*4) .. "}\n" else tostring(what).."\n" @@ -129,8 +127,14 @@ get_options = (...) -> else {}, ... +safe_module = (name, tbl) -> + setmetatable tbl, { + __index: (key) => + error "Attempted to import non-existent `#{key}` from #{name}" + } + { - :moon, :pos_to_line, :get_closest_line, :get_line, :reversed, :trim, :split, - :dump, :debug_posmap, :getfenv, :setfenv, :get_options, :unpack + :moon, :pos_to_line, :get_closest_line, :get_line, :trim, :split, :dump, + :debug_posmap, :getfenv, :setfenv, :get_options, :unpack, :safe_module } diff --git a/moonscript/version.lua b/moonscript/version.lua index ce19da42..91f885aa 100644 --- a/moonscript/version.lua +++ b/moonscript/version.lua @@ -1,7 +1,7 @@ - -module("moonscript.version", package.seeall) - -version = "0.2.4" -function print_version() - print("MoonScript version "..version) -end +local version = "0.5.0" +return { + version = version, + print_version = function() + return print("MoonScript version " .. tostring(version)) + end +} diff --git a/moonscript/version.moon b/moonscript/version.moon new file mode 100644 index 00000000..68c483e4 --- /dev/null +++ b/moonscript/version.moon @@ -0,0 +1,8 @@ + +version = "0.5.0" + +{ + version: version, + print_version: -> + print "MoonScript version #{version}" +} diff --git a/spec/README.md b/spec/README.md new file mode 100644 index 00000000..3ca43503 --- /dev/null +++ b/spec/README.md @@ -0,0 +1,91 @@ + +# MoonScript spec guide + +## Testing the right code + +Because MoonScript is written in MoonScript, and MoonScript specs are written +in MoonScript, you need to be aware of which copy of MoonScript is actually +executing the specs. + +A system installed version of MoonScript is recommended to run the specs (and +for development). This means that you'll typically have two versions of +MoonScript available in the load path: + +* The system version +* The version in the current directory + +> A system install is recommended because you'll always want a functioning +> version of MoonScript to compile with in case you break your development +> version. + +When developing you want to make ensure the tests are executing your changes in +the current directory, and not testing the system install. + +Busted itself is MoonScript aware, so it means it should have a functional +MoonScript compiler in order to load the `.moon` test files. This should be the +system install. After booting your specs though, you would like to use the +current directory version of MoonScript to the test + +Because by default Busted will have the system install take precedence over the +loaded version, running `require "moonscript.base"` within a test you won't get +the working directory version of the code that you should be testing. + +The `with_dev` spec helper will ensure that any require calls within the spec +that ask for MoonScript modules. `with_dev` calls a setup and teardown that +replaces `_G.require` with a custom version. + +You'll use it like this: + +```moonscript +import with_dev from require "spec.helpers" +describe "moonscript.base", -> + with_dev! + + it "should load code", -> + -- the local version is loaded + moonscript = require "moonscript" + moonscript.load "print 12" +``` + +Note that `with_dev`'s `require` function will not use the MoonLoader, it will +only load the `.lua` files in the working directory directory, not the `moon` +ones. This means you must compile the working directory version of MoonScript +before running the tests. + +There is a make task to conveniently do all of this: + +``` +make test +``` + +## Building syntax tests + +The test suite has a series of *syntax* tests (`spec/lang_spec.moon`) that +consist of a bunch of `.moon` files and their expected output. These files +should capture a large range of syntax that can be verified to have the correct +output when you make changes to the language. + +If you are adding new syntax, or changing the expected output, then these tests +will fail until you rebuild the expected outputs. You can do this by running +the syntax test suite with the `BUILD` environment variable set. + +There is a make task to conveniently do this: + +``` +make build_test_outputs +``` + +## Performance timing + +The syntax specs have performance timing collection built in. To get these +times run the test suite with the `TIME` environment variable set. + +``` +TIME=1 busted spec/lang_spec.moon +``` + +Any changes to the compiler should not introduce any substantial performance +decreases. + + + diff --git a/spec/class_spec.moon b/spec/class_spec.moon index c46b4415..394c59b1 100644 --- a/spec/class_spec.moon +++ b/spec/class_spec.moon @@ -19,8 +19,7 @@ describe "class", -> instance = Thing! assert.same instance\get_color!, "blue" - - it "should have class property", -> + it "should have base properies from class", -> class Thing color: "blue" get_color: => @color @@ -41,50 +40,6 @@ describe "class", -> instance = Thing "color" assert.same instance\get_property!, "green" - it "should call super constructor", -> - class Base - new: (@property) => - - class Thing extends Base - new: (@name) => - super "name" - - instance = Thing "the_thing" - - assert.same instance.property, "name" - assert.same instance.name, "the_thing" - - - it "should call super method", -> - class Base - _count: 111 - counter: => @_count - - class Thing extends Base - counter: => "%08d"\format super! - - instance = Thing! - assert.same instance\counter!, "00000111" - - it "should get super class", -> - class Base - class Thing extends Base - get_super: => super - - instance = Thing! - assert.is_true instance\get_super! == Base - - it "should get a bound method from super", -> - class Base - count: 1 - get_count: => @count - - class Thing extends Base - get_count: => "this is wrong" - get_method: => super\get_count - - instance = Thing! - assert.same instance\get_method!!, 1 it "should have class properties", -> class Base @@ -102,7 +57,6 @@ describe "class", -> Thing = class assert.same Thing.__name, "Thing" - it "should not expose class properties on instance", -> class Thing @height: 10 @@ -135,3 +89,209 @@ describe "class", -> instance = Thing! instance\go! + it "should have class properies take precedence over base properties", -> + class Thing + @prop: "hello" + prop: "world" + + assert.same "hello", Thing.prop + + describe "super", -> + it "should call super constructor", -> + class Base + new: (@property) => + + class Thing extends Base + new: (@name) => + super "name" + + instance = Thing "the_thing" + + assert.same instance.property, "name" + assert.same instance.name, "the_thing" + + it "should call super method", -> + class Base + _count: 111 + counter: => @_count + + class Thing extends Base + counter: => "%08d"\format super! + + instance = Thing! + assert.same instance\counter!, "00000111" + + it "should call other method from super", -> + class Base + _count: 111 + counter: => + @_count + + class Thing extends Base + other_method: => super\counter! + + instance = Thing! + assert.same instance\other_method!, 111 + + it "should get super class", -> + class Base + class Thing extends Base + get_super: => super + + instance = Thing! + assert.is_true instance\get_super! == Base + + it "should get a bound method from super", -> + class Base + count: 1 + get_count: => @count + + class Thing extends Base + get_count: => "this is wrong" + get_method: => super\get_count + + instance = Thing! + assert.same instance\get_method!!, 1 + + it "class properties take precedence in super class over base", -> + class Thing + @prop: "hello" + prop: "world" + + class OtherThing extends Thing + + assert.same "hello", OtherThing.prop + + it "gets value from base in super class", -> + class Thing + prop: "world" + + class OtherThing extends Thing + assert.same "world", OtherThing.prop + + it "should let parent be replaced on class", -> + class A + @prop: "yeah" + cool: => 1234 + plain: => "a" + + class B + @prop: "okay" + cool: => 9999 + plain: => "b" + + class Thing extends A + cool: => + super! + 1 + + get_super: => + super + + instance = Thing! + + assert.same "a", instance\plain! + assert.same 1235, instance\cool! + assert A == instance\get_super!, "expected super to be B" + + Thing.__parent = B + setmetatable Thing.__base, B.__base + + assert.same "b", instance\plain! + assert.same 10000, instance\cool! + assert B == instance\get_super!, "expected super to be B" + + it "should resolve many levels of super", -> + class One + a: => + 1 + + class Two extends One + a: => + super! + 2 + + class Three extends Two + a: => + super! + 3 + + i = Three! + + assert.same 6, i\a! + + + it "should resolve many levels of super with a gap", -> + class One + a: => + 1 + + class Two extends One + + class Three extends Two + a: => + super! + 3 + + class Four extends Three + a: => + super! + 4 + + i = Four! + + assert.same 8, i\a! + + + it "should call correct class/instance super methods", -> + class Base + doit: => + "instance" + + @doit: => + "class" + + class One extends Base + doit: => super! + @doit: => super! + + assert.same "instance", One!\doit! + assert.same "class", One\doit! + + + it "should resolve many levels of super on class methods", -> + class One + @a: => + 1 + + class Two extends One + + class Three extends Two + @a: => + super! + 3 + + class Four extends Three + @a: => + super! + 4 + + assert.same 8, Four\a! + + it "super should still work when method wrapped", -> + add_some = (opts) -> + => opts.amount + opts[1] @ + + class Base + value: => 1 + + class Sub extends Base + value: add_some { + amount: 12 + => + super! + 100 + } + + class OtherSub extends Base + value: if true + => 5 + super! + else + => 2 + super! + + assert.same 1 + 100 + 12, Sub!\value! + assert.same 6, OtherSub!\value! + + diff --git a/spec/cmd_spec.moon b/spec/cmd_spec.moon new file mode 100644 index 00000000..039973d5 --- /dev/null +++ b/spec/cmd_spec.moon @@ -0,0 +1,130 @@ + +import with_dev from require "spec.helpers" + +-- TODO: add specs for windows equivalents + +describe "moonc", -> + local moonc + + dev_loaded = with_dev -> + moonc = require "moonscript.cmd.moonc" + + same = (fn, a, b) -> + assert.same b, fn a + + it "should normalize dir", -> + same moonc.normalize_dir, "hello/world/", "hello/world/" + same moonc.normalize_dir, "hello/world//", "hello/world/" + same moonc.normalize_dir, "", "/" -- wrong + same moonc.normalize_dir, "hello", "hello/" + + it "should parse dir", -> + same moonc.parse_dir, "/hello/world/file", "/hello/world/" + same moonc.parse_dir, "/hello/world/", "/hello/world/" + same moonc.parse_dir, "world", "" + same moonc.parse_dir, "", "" + + it "should parse file", -> + same moonc.parse_file, "/hello/world/file", "file" + same moonc.parse_file, "/hello/world/", "" + same moonc.parse_file, "world", "world" + same moonc.parse_file, "", "" + + it "convert path", -> + same moonc.convert_path, "test.moon", "test.lua" + same moonc.convert_path, "/hello/file.moon", "/hello/file.lua" + same moonc.convert_path, "/hello/world/file", "/hello/world/file.lua" + + it "calculate target", -> + p = moonc.path_to_target + + assert.same "test.lua", p "test.moon" + assert.same "hello/world.lua", p "hello/world.moon" + assert.same "compiled/test.lua", p "test.moon", "compiled" + + assert.same "/home/leafo/test.lua", p "/home/leafo/test.moon" + assert.same "compiled/test.lua", p "/home/leafo/test.moon", "compiled" + assert.same "/compiled/test.lua", p "/home/leafo/test.moon", "/compiled/" + + assert.same "moonscript/hello.lua", p "moonscript/hello.moon", nil, "moonscript" + assert.same "out/moonscript/hello.lua", p "moonscript/hello.moon", "out", "moonscript" + + assert.same "out/moonscript/package/hello.lua", + p "moonscript/package/hello.moon", "out", "moonscript/" + + assert.same "/out/moonscript/package/hello.lua", + p "/home/leafo/moonscript/package/hello.moon", "/out", "/home/leafo/moonscript" + + it "should compile file text", -> + assert.same { + [[return print('hello')]] + }, { + moonc.compile_file_text "print'hello'", fname: "test.moon" + } + + describe "watcher", -> + describe "inotify watcher", -> + it "gets dirs", -> + import InotifyWacher from require "moonscript.cmd.watchers" + watcher = InotifyWacher { + {"hello.moon", "hello.lua"} + {"cool/no.moon", "cool/no.lua"} + } + + assert.same { + "./" + "cool/" + }, watcher\get_dirs! + + describe "parse args", -> + it "parses spec", -> + import parse_spec from require "moonscript.cmd.args" + spec = parse_spec "lt:o:X" + assert.same { + X: {} + o: {value: true} + t: {value: true} + l: {} + }, spec + + it "parses arguments", -> + import parse_arguments from require "moonscript.cmd.args" + out, res = parse_arguments { + "ga:p" + print: "p" + }, {"hello", "word", "-gap"} + + assert.same { + g: true + a: true + p: true + }, out + + describe "stubbed lfs", -> + local dirs + + before_each -> + dirs = {} + package.loaded.lfs = nil + dev_loaded["moonscript.cmd.moonc"] = nil + + package.loaded.lfs = { + mkdir: (dir) -> table.insert dirs, dir + attributes: -> "directory" + } + + moonc = require "moonscript.cmd.moonc" + + after_each -> + package.loaded.lfs = nil + dev_loaded["moonscript.cmd.moonc"] = nil + moonc = require "moonscript.cmd.moonc" + + it "should make directory", -> + moonc.mkdir "hello/world/directory" + assert.same { + "hello" + "hello/world" + "hello/world/directory" + }, dirs + diff --git a/spec/compiler_spec.moon b/spec/compiler_spec.moon new file mode 100644 index 00000000..696e6bb9 --- /dev/null +++ b/spec/compiler_spec.moon @@ -0,0 +1,183 @@ +import Block from require "moonscript.compile" + +import ref, str from require "spec.factory" + +-- no transform step +class SimpleBlock extends Block + new: (...) => + super ... + @transform = { + value: (...) -> ... + statement: (...) -> ... + } + +value = require "moonscript.compile.value" + +describe "moonscript.compile", -> + compile_node = (node) -> + block = SimpleBlock! + block\add block\value node + lines = block._lines\flatten! + lines[#lines] = nil if lines[#lines] == "\n" + table.concat lines + + -- compiling lua ast + describe "value", -> + for {name, node, expected} in *{ + { + "ref" + -> {"ref", "hello_world"} + "hello_world" + } + + { + "number" + -> {"number", "14"} + "14" + } + + { + "minus" + -> {"minus", ref!} + "-val" + } + + { + "explist" + -> { "explist", ref("a"), ref("b"), ref("c")} + "a, b, c" + } + + { + "exp" + -> {"exp", ref("a"), "+", ref("b"), "!=", ref("c")} + "a + b ~= c" + } + + { + "parens" + -> { "parens", ref! } + "(val)" + } + + { + "string (single quote)" + -> {"string", "'", "Hello\\'s world"} + "'Hello\\'s world'" + } + + { + "string (double quote)" + -> {"string", '"', "Hello's world"} + [["Hello's world"]] + + } + + { + "string (lua)" + -> {"string", '[==[', "Hello's world"} + "[==[Hello's world]==]" + } + + { + "self" + -> {"self", ref!} + "self.val" + } + + { + "self_class" + -> {"self_class", ref!} + "self.__class.val" + } + + { + "self_class_colon" + -> {"self_class_colon", ref!} + "self.__class:val" + } + + { + "not" + -> {"not", ref!} + "not val" + } + + { + "length" + -> {"length", ref!} + "#val" + } + + { + "length" + -> {"length", ref!} + "#val" + } + + { + "bitnot" + -> {"bitnot", ref!} + "~val" + } + + { + "chain (single)" + -> {"chain", ref!} + "val" + } + + { + "chain (dot)" + -> {"chain", ref!, {"dot", "zone"} } + "val.zone" + } + + { + "chain (index)" + -> {"chain", ref!, {"index", ref("x") } } + "val[x]" + } + + + { + "chain (call)" + -> {"chain", ref!, {"call", { ref("arg") }} } + "val(arg)" + } + + { + "chain" + -> { + "chain" + ref! + {"dot", "one"} + {"index", str!} + {"colon", "two"} + {"call", { ref("arg") }} + } + 'val.one["dogzone"]:two(arg)' + } + + { + "chain (self receiver)" + -> { + "chain" + {"self", ref!} + {"call", {ref "arg"} } + } + "self:val(arg)" + } + + { + "fndef (empty)" + -> {"fndef", {}, {}, "slim", {}} + "function() end" + } + + } + it "compiles #{name}", -> + node = node! + assert.same expected, compile_node(node) + + diff --git a/spec/comprehension_spec.moon b/spec/comprehension_spec.moon index 2f07939d..d8823728 100644 --- a/spec/comprehension_spec.moon +++ b/spec/comprehension_spec.moon @@ -1,4 +1,6 @@ +import unpack from require "moonscript.util" + describe "comprehension", -> it "should double every number", -> input = {1,2,3,4,5,6} diff --git a/spec/coverage_output_handler.moon b/spec/coverage_output_handler.moon new file mode 100644 index 00000000..0043e462 --- /dev/null +++ b/spec/coverage_output_handler.moon @@ -0,0 +1,51 @@ + +load_line_table = (chunk_name) -> + import to_lua from require "moonscript.base" + + return unless chunk_name\match "^@" + fname = chunk_name\sub 2 + + file = assert io.open fname + code = file\read "*a" + file\close! + + c, ltable = to_lua code + + return nil, ltable unless c + + line_tables = require "moonscript.line_tables" + line_tables[chunk_name] = ltable + true + +(options) -> + busted = require "busted" + handler = require("busted.outputHandlers.utfTerminal") options + + local spec_name + + coverage = require "moonscript.cmd.coverage" + cov = coverage.CodeCoverage! + + busted.subscribe { "test", "start" }, (context) -> + cov\start! + + busted.subscribe { "test", "end" }, -> + cov\stop! + + busted.subscribe { "suite", "end" }, (context) -> + line_counts = {} + + for chunk_name, counts in pairs cov.line_counts + continue unless chunk_name\match("^@$./") or chunk_name\match "@[^/]" + continue if chunk_name\match "^@spec/" + + if chunk_name\match "%.lua$" + chunk_name = chunk_name\gsub "lua$", "moon" + continue unless load_line_table chunk_name + + line_counts[chunk_name] = counts + + cov.line_counts = line_counts + cov\format_results! + + handler diff --git a/spec/error_rewriting_spec.moon b/spec/error_rewriting_spec.moon index d2f55c98..c3616116 100644 --- a/spec/error_rewriting_spec.moon +++ b/spec/error_rewriting_spec.moon @@ -1,30 +1,71 @@ -moonscript = require "moonscript.base" -errors = require "moonscript.errors" -util = require "moonscript.util" +import unindent, with_dev from require "spec.helpers" -get_rewritten_line_no = (fname) -> - fname = "spec/error_inputs/#{fname}.moon" - chunk = moonscript.loadfile fname +describe "moonscript.errors", -> + local moonscript, errors, util, to_lua - success, err = pcall chunk - error "`#{fname}` is supposed to have runtime error!" if success + -- with_dev -> + moonscript = require "moonscript.base" + errors = require "moonscript.errors" + util = require "moonscript.util" - source = tonumber err\match "]:(%d+)" + {:to_lua} = moonscript - line_table = require("moonscript.line_tables")[fname] - errors.reverse_line_number fname, line_table, source, {} + get_rewritten_line_no = (fname) -> + fname = "spec/error_inputs/#{fname}.moon" + chunk = moonscript.loadfile fname + success, err = pcall chunk + error "`#{fname}` is supposed to have runtime error!" if success --- TODO: check entire stack trace -describe "error rewriting", -> - tests = { - "first": 24 - "second": 16 - "third": 11 - } + source = tonumber err\match "^.-:(%d+):" - for name, expected_no in pairs tests - it "should rewrite line number", -> - assert.same get_rewritten_line_no(name), expected_no + line_table = assert require("moonscript.line_tables")["@#{fname}"], "missing line table" + errors.reverse_line_number fname, line_table, source, {} + describe "error rewriting", -> + tests = { + "first": 24 + "second": 16 + "third": 11 + } + + for name, expected_no in pairs tests + it "should rewrite line number", -> + assert.same get_rewritten_line_no(name), expected_no + + describe "line map", -> + it "should create line table", -> + moon_code = unindent [[ + print "hello world" + if something + print "cats" + ]] + + lua_code, posmap = assert to_lua moon_code + -- print util.debug_posmap(posmap, moon_code, lua_code) + assert.same { 1, 23, 36, 21 }, posmap + + it "should create line table for multiline string", -> + moon_code = unindent [[ + print "one" + x = [==[ + one + two + thre + yes + no + ]==] + print "two" + ]] + + lua_code, posmap = assert to_lua moon_code + -- print util.debug_posmap(posmap, moon_code, lua_code) + assert.same {[1]: 1, [2]: 13, [7]: 13, [8]: 57}, posmap + + describe "error reporting", -> + it "should compile bad code twice", -> + code, err = to_lua "{b=5}" + assert.truthy err + code, err2 = to_lua "{b=5}" + assert.same err, err2 diff --git a/spec/factory.moon b/spec/factory.moon new file mode 100644 index 00000000..a6f3d76c --- /dev/null +++ b/spec/factory.moon @@ -0,0 +1,13 @@ + +-- ast factory + +ref = (name="val") -> + {"ref", name} + +str = (contents="dogzone", delim='"') -> + {"string", delim, contents} + +{ + :ref + :str +} diff --git a/spec/helpers.moon b/spec/helpers.moon new file mode 100644 index 00000000..caf885c2 --- /dev/null +++ b/spec/helpers.moon @@ -0,0 +1,54 @@ + +-- remove front indentation from a multiline string, making it suitable to be +-- parsed +unindent = (str) -> + indent = str\match "^%s+" + return str unless indent + (str\gsub("\n#{indent}", "\n")\gsub("%s+$", "")\gsub "^%s+", "") + +in_dev = false + +-- this will ensure any moonscript modules included come from the local +-- directory +with_dev = (fn) -> + error "already in dev mode" if in_dev + + -- a package loader that only looks in currect directory + import make_loader from require "loadkit" + loader = make_loader "lua", nil, "./?.lua" + + import setup, teardown from require "busted" + + old_require = _G.require + dev_cache = {} + + setup -> + _G.require = (mod) -> + mod = switch mod + when "moonscript" + "moonscript.init" + when "moon" + "moon.init" + + return dev_cache[mod] if dev_cache[mod] + + testable = mod\match("moonscript%.") or mod == "moonscript" or + mod\match("moon%.") or mod == "moon" + + if testable + fname = assert loader(mod), "failed to find module: #{mod}" + dev_cache[mod] = assert(loadfile fname)! + return dev_cache[mod] + + old_require mod + + if fn + fn! + + teardown -> + _G.require = old_require + in_dev = false + + dev_cache + +{ :unindent, :with_dev } diff --git a/spec/inputs/ambiguous.moon b/spec/inputs/ambiguous.moon new file mode 100644 index 00000000..66109c11 --- /dev/null +++ b/spec/inputs/ambiguous.moon @@ -0,0 +1,8 @@ +a = 'b' +c = d +(a b) c d +import c from d +(a b) c d +(c d) a b +a, b = c, d +(d a) c diff --git a/spec/inputs/ambiguous_tables.moon b/spec/inputs/ambiguous_tables.moon new file mode 100644 index 00000000..c2c7ea8c --- /dev/null +++ b/spec/inputs/ambiguous_tables.moon @@ -0,0 +1,6 @@ +x = { +hello +(one) +(two) +three() +} diff --git a/spec/inputs/class.moon b/spec/inputs/class.moon index 1d27e5e5..9a980557 100644 --- a/spec/inputs/class.moon +++ b/spec/inputs/class.moon @@ -77,6 +77,7 @@ class CoolSuper super\yeah"world".okay hi, hi, hi something.super super.super.super.super + super\hello nil @@ -175,4 +176,38 @@ class Something class X new: hi + +-- + +class Cool extends Thing + dang: => + { + hello: -> super! + world: -> super.one + } + +-- + +class Whack extends Thing + dang: do_something => + super! + +--- + +class Wowha extends Thing + @butt: -> + super! + super.hello + super\hello! + super\hello + + + @zone: cool { + -> + super! + super.hello + super\hello! + super\hello + } + nil diff --git a/spec/inputs/comprehension.moon b/spec/inputs/comprehension.moon index 345d5b70..1609d79e 100644 --- a/spec/inputs/comprehension.moon +++ b/spec/inputs/comprehension.moon @@ -30,4 +30,23 @@ dd = [y for i=1,10 when cool for thing in y when x > 3 when c + 3] {"hello", "world" for i=1,10} +-- + +j = [a for {a,b,c} in things] +k = [a for {a,b,c} in *things] +i = [hello for {:hello, :world} in *things] + +hj = {a,c for {a,b,c} in things} +hk = {a,c for {a,b,c} in *things} +hi = {hello,world for {:hello, :world} in *things} + +ok(a,b,c) for {a,b,c} in things + +-- + +[item for item in *items[1 + 2,3+4]] +[item for item in *items[hello! * 4, 2 - thing[4]]] + + + nil diff --git a/spec/inputs/cond.moon b/spec/inputs/cond.moon index 89097216..18e42b91 100644 --- a/spec/inputs/cond.moon +++ b/spec/inputs/cond.moon @@ -147,6 +147,14 @@ print "hello" unless value dddd = {1,2,3} unless value + +-- + +do + j = 100 + unless j = hi! + error "not j!" + ---------------- a = 12 @@ -154,3 +162,29 @@ a,c,b = "cool" if something +--- + +j = if 1 + if 2 + 3 +else 6 + + +m = if 1 + + + + if 2 + + + 3 + + +else 6 + + + +nil + + + diff --git a/spec/inputs/funcs.moon b/spec/inputs/funcs.moon index 8c17439b..08a29b64 100644 --- a/spec/inputs/funcs.moon +++ b/spec/inputs/funcs.moon @@ -59,3 +59,100 @@ k -> if yes then return else return -> real_name if something +-- + +d( + -> + print "hello world" + 10 +) + + + +d( + 1,2,3 + 4 + 5 + 6 + + if something + print "okay" + 10 + + 10,20 +) + + +f( + + )( + + )( + what + )(-> + print "srue" + 123) + +-- + +x = (a, + b) -> + print "what" + + +y = (a="hi", + b=23) -> + print "what" + +z = ( + a="hi", + b=23) -> + print "what" + + +j = (f,g,m, + a="hi", + b=23 +) -> + print "what" + + +y = (a="hi", + b=23, + ...) -> + print "what" + + +y = (a="hi", + b=23, + ... +) -> + print "what" + +-- + +args = (a + b) -> + print "what" + + +args = (a="hi" + b=23) -> + print "what" + +args = ( + a="hi" + b=23) -> + print "what" + + +args = (f,g,m + a="hi" + b=23 +) -> + print "what" + + + + +nil diff --git a/spec/inputs/lists.moon b/spec/inputs/lists.moon index 91e559e2..c1191857 100644 --- a/spec/inputs/lists.moon +++ b/spec/inputs/lists.moon @@ -69,8 +69,4 @@ print thing for thing in *test -> a = b for row in *rows --- testing implicit return --> x for x in *things --> [x for x in *things] - diff --git a/spec/inputs/literals.moon b/spec/inputs/literals.moon index 7f58b6ee..c3a24a6f 100644 --- a/spec/inputs/literals.moon +++ b/spec/inputs/literals.moon @@ -15,6 +15,14 @@ .2323e-1 .2323e13434 + +1LL +1ULL +9332LL +9332 +0x2aLL +0x2aULL + [[ hello world ]] [=[ hello world ]=] diff --git a/spec/inputs/local.moon b/spec/inputs/local.moon index d4a616ca..fec78b18 100644 --- a/spec/inputs/local.moon +++ b/spec/inputs/local.moon @@ -75,6 +75,17 @@ do d = 2323 +do + local ^ + lowercase = 5 + Uppercase = 3 + + class One + Five = 6 + + class Two + class No + do local * -- this generates a nil value in the body diff --git a/spec/inputs/loops.moon b/spec/inputs/loops.moon index fbdc6380..a704e562 100644 --- a/spec/inputs/loops.moon +++ b/spec/inputs/loops.moon @@ -114,6 +114,15 @@ for x=1,10 for y = 2,12 continue if y % 3 == 0 + +while true + continue if false + break + +while true + continue if false + return 22 + -- do diff --git a/spec/inputs/operators.moon b/spec/inputs/operators.moon new file mode 100644 index 00000000..142ef622 --- /dev/null +++ b/spec/inputs/operators.moon @@ -0,0 +1,72 @@ + +-- binary ops +x = 1 + 3 + +y = 1 + + 3 + +z = 1 + + 3 + + 4 + +-- + +k = b and c and + g + + +h = thing and + -> + print "hello world" + +-- TODO: should fail, indent still set to previous line so it thinks body is +-- indented +i = thing or + -> + print "hello world" + +p = thing and + -> +print "hello world" + +s = thing or + -> and 234 + + +-- +u = { + color: 1 and 2 and + 3 + 4 + 4 +} + +v = { + color: 1 and + -> + "yeah" + "great" + oksy: 3 ^ +2 +} + +-- parens + +nno = ( + yeah + 2 ) + +nn = ( + yeah + 2 +) + +n = hello( + b +) -> + +hello a, + ( + yeah + + 2 + ) - + okay + diff --git a/spec/inputs/return.moon b/spec/inputs/return.moon new file mode 100644 index 00000000..61d3dcad --- /dev/null +++ b/spec/inputs/return.moon @@ -0,0 +1,55 @@ +-- testing `return` propagation + +-> x for x in *things +-> [x for x in *things] + + +-- doesn't make sense on purpose +do + return x for x in *things + +do + return [x for x in *things] + +do + return {x,y for x,y in *things} + +-> + if a + if a + a + else + b + elseif b + if a + a + else + b + else + if a + a + else + b + + +do + return if a + if a + a + else + b + elseif b + if a + a + else + b + else + if a + a + else + b + +-> a\b +do a\b + + diff --git a/spec/inputs/syntax.moon b/spec/inputs/syntax.moon index 09a5dabd..b5b92a84 100644 --- a/spec/inputs/syntax.moon +++ b/spec/inputs/syntax.moon @@ -66,6 +66,18 @@ something 'else', "ya" something'else' something"else" +something[[hey]] * 2 +something[======[hey]======] * 2 + + +something'else', 2 +something"else", 2 +something[[else]], 2 + +something 'else', 2 +something "else", 2 +something [[else]], 2 + here(we)"go"[12123] -- this runs @@ -146,6 +158,13 @@ hello ..= "world" @@something += 10 @something += 10 +@@then += 10 +@then += 10 + +a["hello"] += 10 +a["hello#{tostring ff}"] += 10 +a[four].x += 10 + x = 0 (if ntype(v) == "fndef" then x += 1) for v in *values @@ -231,6 +250,8 @@ another hello, one, a += 3 - 5 a *= 3 + 5 a *= 3 +a >>= 3 +a <<= 3 a /= func "cool" --- diff --git a/spec/inputs/tables.moon b/spec/inputs/tables.moon index e918556f..2bf66d70 100644 --- a/spec/inputs/tables.moon +++ b/spec/inputs/tables.moon @@ -102,3 +102,60 @@ xam = { } +kam = { + hello: 12 + goodcheese: + "mmm" + + yeah: + 12 + 232 + + lets: + keepit going: true, + okay: "yeah" + + more: + { + 1, [x for x=1,10] + } + + [{"one", "two"}]: + one_thing => +} + +-- TODO: both of these have undesirable output +keepit going: true, + okay: "yeah", + workd: "okay" + +thing what: + "great", no: + "more" + okay: 123 + + +-- +thing what: + "great", no: + "more" +okay: 123 -- a anon table + + +-- + +k = { "hello": "world" } +k = { 'hello': 'world' } +k = { "hello": 'world', "hat": "zat" } + +please "hello": "world" +k = "hello": "world", "one": "zone" + +f = "one", "two": three, "four" +f = "two": three, "four" +f = { "one", "two": three, "four" } + + +j = "one", "two": three, "four": five, 6, 7 + + +nil diff --git a/spec/inputs/unless_else.moon b/spec/inputs/unless_else.moon new file mode 100644 index 00000000..fe96c0bd --- /dev/null +++ b/spec/inputs/unless_else.moon @@ -0,0 +1,5 @@ +if a + unless b + print "hi" + elseif c + print "not hi" diff --git a/spec/inputs/whitespace.moon b/spec/inputs/whitespace.moon index a8242228..4a2ff1fa 100644 --- a/spec/inputs/whitespace.moon +++ b/spec/inputs/whitespace.moon @@ -81,3 +81,22 @@ if hello 1,2,3, print "hello" +-- + +a( + one, two, three +) + +b( + one, + two, + three +) + + +c(one, two, + three, four) + +-- + +nil diff --git a/spec/inputs/with.moon b/spec/inputs/with.moon index 54aa5091..ae3c8c05 100644 --- a/spec/inputs/with.moon +++ b/spec/inputs/with.moon @@ -93,6 +93,26 @@ do with k.j = "jo" print \upper! +do + with a + print .b + -- nested `with`s should change the scope correctly + with .c + print .d +do + with a + -- nested `with`s with assignments should change the scope correctly + with .b = 2 + print .c +do + -> + with hi + return .a, .b + +do + with dad + .if "yes" + y = .end.of.function diff --git a/spec/lang_spec.moon b/spec/lang_spec.moon index 7fd1a3f8..b0fa1bab 100644 --- a/spec/lang_spec.moon +++ b/spec/lang_spec.moon @@ -1,12 +1,10 @@ lfs = require "lfs" -parse = require "moonscript.parse" -compile = require "moonscript.compile" -util = require "moonscript.util" +import with_dev from require "spec.helpers" pattern = ... -import unpack from util +unpack = table.unpack or unpack options = { in_dir: "spec/inputs", @@ -20,7 +18,7 @@ options = { tool: "git diff --no-index --color" --color-words" filter: (str) -> -- strip the first four lines - table.concat [line for line in *util.split(str, "\n")[5,]], "\n" + table.concat [l for l in *([line for line in str\gmatch("[^\n]+")])[5,]], "\n" } } @@ -81,21 +79,22 @@ input_fname = (base) -> output_fname = (base) -> options.out_dir .. "/" .. base .. options.output_ext +inputs = for file in lfs.dir options.in_dir + with match = file\match options.input_pattern + continue unless match + +table.sort inputs + describe "input tests", -> - inputs = for file in lfs.dir options.in_dir - with match = file\match options.input_pattern - continue unless match + local parse, compile - table.sort inputs + with_dev -> + parse = require "moonscript.parse" + compile = require "moonscript.compile" for name in *inputs input = input_fname name - fn = if pattern and not input\match pattern - pending - else - it - - fn input .. " #input", -> + it input .. " #input", -> file_str = read_all input_fname name parse_time, tree, err = benchmark -> parse.string file_str diff --git a/spec/moon_spec.moon b/spec/moon_spec.moon index 145b5ee1..773e53c0 100644 --- a/spec/moon_spec.moon +++ b/spec/moon_spec.moon @@ -1,8 +1,13 @@ -- test moon library -moon = require "moon" +import with_dev from require "spec.helpers" describe "moon", -> + local moon + + with_dev -> + moon = require "moon" + it "should determine correct type", -> class Test diff --git a/spec/moonscript_spec.moon b/spec/moonscript_spec.moon new file mode 100644 index 00000000..b7e5271b --- /dev/null +++ b/spec/moonscript_spec.moon @@ -0,0 +1,12 @@ +-- moonscript module + +import with_dev from require "spec.helpers" + +describe "moonscript.base", -> + with_dev! + + it "should create moonpath", -> + path = ";./?.lua;/usr/share/lua/5.1/?.lua;/usr/share/lua/5.1/?/init.lua;/usr/lib/lua/5.1/?.luac;/home/leafo/.luarocks/lua/5.1/?.lua" + import create_moonpath from require "moonscript.base" + assert.same "./?.moon;/usr/share/lua/5.1/?.moon;/usr/share/lua/5.1/?/init.moon;/home/leafo/.luarocks/lua/5.1/?.moon", create_moonpath(path) + diff --git a/spec/outputs/ambiguous.lua b/spec/outputs/ambiguous.lua new file mode 100644 index 00000000..2330da40 --- /dev/null +++ b/spec/outputs/ambiguous.lua @@ -0,0 +1,9 @@ +local a = 'b' +local c = d; +(a(b))(c(d)) +c = d.c; +(a(b))(c(d)); +(c(d))(a(b)) +local b +a, b = c, d +return (d(a))(c) \ No newline at end of file diff --git a/spec/outputs/ambiguous_tables.lua b/spec/outputs/ambiguous_tables.lua new file mode 100644 index 00000000..8af0aaab --- /dev/null +++ b/spec/outputs/ambiguous_tables.lua @@ -0,0 +1,6 @@ +local x = { + hello, + (one), + (two), + three() +} \ No newline at end of file diff --git a/spec/outputs/bubbling.lua b/spec/outputs/bubbling.lua index 247ff1c2..036d5fb7 100644 --- a/spec/outputs/bubbling.lua +++ b/spec/outputs/bubbling.lua @@ -78,7 +78,7 @@ do ... } for _index_0 = 1, #_list_0 do - x = _list_0[_index_0] + local x = _list_0[_index_0] _accum_0[_len_0] = x _len_0 = _len_0 + 1 end diff --git a/spec/outputs/class.lua b/spec/outputs/class.lua index 1e3a3015..018760a0 100644 --- a/spec/outputs/class.lua +++ b/spec/outputs/class.lua @@ -1,5 +1,6 @@ local Hello do + local _class_0 local _base_0 = { hello = function(self) return print(self.test, self.world) @@ -9,7 +10,7 @@ do end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, test, world) self.test, self.world = test, world return print("creating object..") @@ -32,13 +33,14 @@ x:hello() print(x) local Simple do + local _class_0 local _base_0 = { cool = function(self) return print("cool") end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Simple" @@ -55,11 +57,12 @@ do end local Yikes do + local _class_0 local _parent_0 = Simple local _base_0 = { } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self) return print("created hello") end, @@ -70,7 +73,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -91,13 +97,14 @@ x = Yikes() x:cool() local Hi do + local _class_0 local _base_0 = { cool = function(self, num) return print("num", num) end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, arg) return print("init arg", arg) end, @@ -115,17 +122,18 @@ do Hi = _class_0 end do + local _class_0 local _parent_0 = Hi local _base_0 = { cool = function(self) - return _parent_0.cool(self, 120302) + return _class_0.__parent.__base.cool(self, 120302) end } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self) - return _parent_0.__init(self, "man") + return _class_0.__parent.__init(self, "man") end, __base = _base_0, __name = "Simple", @@ -134,7 +142,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -156,11 +167,12 @@ x:cool() print(x.__class == Simple) local Okay do + local _class_0 local _base_0 = { something = 20323 } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Okay" @@ -177,19 +189,20 @@ do end local Biggie do + local _class_0 local _parent_0 = Okay local _base_0 = { something = function(self) - _parent_0.something(self, 1, 2, 3, 4) - _parent_0.something(another_self, 1, 2, 3, 4) - return assert(_parent_0 == Okay) + _class_0.__parent.__base.something(self, 1, 2, 3, 4) + _class_0.__parent.something(another_self, 1, 2, 3, 4) + return assert(_class_0.__parent == Okay) end } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, ...) - return _parent_0.__init(self, ...) + return _class_0.__parent.__init(self, ...) end, __base = _base_0, __name = "Biggie", @@ -198,7 +211,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -217,13 +233,14 @@ do end local Yeah do + local _class_0 local _base_0 = { okay = function(self) - return _parent_0.something(self, 1, 2, 3, 4) + return _class_0.__parent.something(self, 1, 2, 3, 4) end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Yeah" @@ -240,13 +257,14 @@ do end local What do + local _class_0 local _base_0 = { something = function(self) return print("val:", self.val) end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "What" @@ -262,24 +280,23 @@ do What = _class_0 end do + local _class_0 local _parent_0 = What local _base_0 = { val = 2323, something = function(self) - return (function() - local _base_1 = _parent_0 - local _fn_0 = _base_1.something - return function(...) - return _fn_0(self, ...) - end - end)() + local _base_1 = _class_0.__parent + local _fn_0 = _base_1.something + return function(...) + return _fn_0(self, ...) + end end } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, ...) - return _parent_0.__init(self, ...) + return _class_0.__parent.__init(self, ...) end, __base = _base_0, __name = "Hello", @@ -288,7 +305,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -313,19 +333,27 @@ do end local CoolSuper do + local _class_0 local _base_0 = { hi = function(self) - _parent_0.hi(self, 1, 2, 3, 4)(1, 2, 3, 4) - _parent_0.something(1, 2, 3, 4) - local _ = _parent_0.something(1, 2, 3, 4).world - _parent_0.yeah(self, "world").okay(hi, hi, hi) + _class_0.__parent.__base.hi(self, 1, 2, 3, 4)(1, 2, 3, 4) + _class_0.__parent.something(1, 2, 3, 4) + local _ = _class_0.__parent.something(1, 2, 3, 4).world + _class_0.__parent.yeah(self, "world").okay(hi, hi, hi) _ = something.super - _ = _parent_0.super.super.super + _ = _class_0.__parent.super.super.super + do + local _base_1 = _class_0.__parent + local _fn_0 = _base_1.hello + _ = function(...) + return _fn_0(self, ...) + end + end return nil end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "CoolSuper" @@ -351,12 +379,13 @@ xx = function(hello, world, cool) end local ClassMan do + local _class_0 local _base_0 = { blue = function(self) end, green = function(self) end } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "ClassMan" @@ -386,10 +415,11 @@ self.hello(2, 3, 4) local _ = hello[self].world local Whacko do + local _class_0 local hello local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Whacko" @@ -419,9 +449,10 @@ local yyy yyy = function() local Cool do + local _class_0 local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Cool" @@ -441,9 +472,10 @@ yyy = function() end end do + local _class_0 local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "D" @@ -461,9 +493,10 @@ do a.b.c.D = _class_0 end do + local _class_0 local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "hello" @@ -481,13 +514,14 @@ do a.b["hello"] = _class_0 end do + local _class_0 local _parent_0 = Hello.World local _base_0 = { } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, ...) - return _parent_0.__init(self, ...) + return _class_0.__parent.__init(self, ...) end, __base = _base_0, __name = "Something", @@ -496,7 +530,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -519,9 +556,10 @@ do end local a do + local _class_0 local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "a" @@ -539,9 +577,10 @@ end local b local Something do + local _class_0 local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Something" @@ -559,13 +598,14 @@ do end local c do + local _class_0 local _parent_0 = Hello local _base_0 = { } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, ...) - return _parent_0.__init(self, ...) + return _class_0.__parent.__init(self, ...) end, __base = _base_0, __name = "Something", @@ -574,7 +614,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -594,13 +637,14 @@ do end local d do + local _class_0 local _parent_0 = World local _base_0 = { } _base_0.__index = _base_0 setmetatable(_base_0, _parent_0.__base) - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self, ...) - return _parent_0.__init(self, ...) + return _class_0.__parent.__init(self, ...) end, __base = _base_0, __name = "d", @@ -609,7 +653,10 @@ do __index = function(cls, name) local val = rawget(_base_0, name) if val == nil then - return _parent_0[name] + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end else return val end @@ -629,9 +676,10 @@ end print(((function() local WhatsUp do + local _class_0 local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "WhatsUp" @@ -649,9 +697,10 @@ print(((function() end end)()).__name) do + local _class_0 local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Something" @@ -669,10 +718,11 @@ do Something = _class_0 end do + local _class_0 local val, insert local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function(self) return print(insert, val) end, @@ -689,16 +739,14 @@ do _base_0.__class = _class_0 local self = _class_0 val = 23 - do - local _obj_0 = table - insert = _obj_0.insert - end + insert = table.insert Something = _class_0 end do + local _class_0 local _base_0 = { } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = hi, __base = _base_0, __name = "X" @@ -713,4 +761,153 @@ do _base_0.__class = _class_0 X = _class_0 end +do + local _class_0 + local _parent_0 = Thing + local _base_0 = { + dang = function(self) + return { + hello = function() + return _class_0.__parent.__base.dang(self) + end, + world = function() + return _class_0.__parent.one + end + } + end + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "Cool", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + Cool = _class_0 +end +do + local _class_0 + local _parent_0 = Thing + local _base_0 = { + dang = do_something(function(self) + return _class_0.__parent.__base.dang(self) + end) + } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "Whack", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + Whack = _class_0 +end +do + local _class_0 + local _parent_0 = Thing + local _base_0 = { } + _base_0.__index = _base_0 + setmetatable(_base_0, _parent_0.__base) + _class_0 = setmetatable({ + __init = function(self, ...) + return _class_0.__parent.__init(self, ...) + end, + __base = _base_0, + __name = "Wowha", + __parent = _parent_0 + }, { + __index = function(cls, name) + local val = rawget(_base_0, name) + if val == nil then + local parent = rawget(cls, "__parent") + if parent then + return parent[name] + end + else + return val + end + end, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + self.butt = function() + _class_0.__parent.butt(self) + _ = _class_0.__parent.hello + _class_0.__parent.hello(self) + local _base_1 = _class_0.__parent + local _fn_0 = _base_1.hello + return function(...) + return _fn_0(self, ...) + end + end + self.zone = cool({ + function() + _class_0.__parent.zone(self) + _ = _class_0.__parent.hello + _class_0.__parent.hello(self) + local _base_1 = _class_0.__parent + local _fn_0 = _base_1.hello + return function(...) + return _fn_0(self, ...) + end + end + }) + if _parent_0.__inherited then + _parent_0.__inherited(_parent_0, _class_0) + end + Wowha = _class_0 +end return nil \ No newline at end of file diff --git a/spec/outputs/comprehension.lua b/spec/outputs/comprehension.lua index 7512c5f3..15a93890 100644 --- a/spec/outputs/comprehension.lua +++ b/spec/outputs/comprehension.lua @@ -41,7 +41,7 @@ do local _tbl_0 = { } local _list_0 = yes for _index_0 = 1, #_list_0 do - x = _list_0[_index_0] + local x = _list_0[_index_0] local _key_0, _val_0 = unpack(x) _tbl_0[_key_0] = _val_0 end @@ -68,7 +68,7 @@ do } } for _index_0 = 1, #_list_0 do - x = _list_0[_index_0] + local x = _list_0[_index_0] local _key_0, _val_0 = unpack((function() local _accum_0 = { } local _len_0 = 1 @@ -168,4 +168,93 @@ do end _ = _tbl_0 end +local j +do + local _accum_0 = { } + local _len_0 = 1 + for _des_0 in things do + local a, b, c + a, b, c = _des_0[1], _des_0[2], _des_0[3] + _accum_0[_len_0] = a + _len_0 = _len_0 + 1 + end + j = _accum_0 +end +local k +do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = things + for _index_0 = 1, #_list_0 do + local _des_0 = _list_0[_index_0] + local a, b, c + a, b, c = _des_0[1], _des_0[2], _des_0[3] + _accum_0[_len_0] = a + _len_0 = _len_0 + 1 + end + k = _accum_0 +end +local i +do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = things + for _index_0 = 1, #_list_0 do + local _des_0 = _list_0[_index_0] + local hello, world + hello, world = _des_0.hello, _des_0.world + _accum_0[_len_0] = hello + _len_0 = _len_0 + 1 + end + i = _accum_0 +end +local hj +do + local _tbl_0 = { } + for _des_0 in things do + local a, b, c + a, b, c = _des_0[1], _des_0[2], _des_0[3] + _tbl_0[a] = c + end + hj = _tbl_0 +end +local hk +do + local _tbl_0 = { } + local _list_0 = things + for _index_0 = 1, #_list_0 do + local _des_0 = _list_0[_index_0] + local a, b, c + a, b, c = _des_0[1], _des_0[2], _des_0[3] + _tbl_0[a] = c + end + hk = _tbl_0 +end +local hi +do + local _tbl_0 = { } + local _list_0 = things + for _index_0 = 1, #_list_0 do + local _des_0 = _list_0[_index_0] + local hello, world + hello, world = _des_0.hello, _des_0.world + _tbl_0[hello] = world + end + hi = _tbl_0 +end +for _des_0 in things do + local a, b, c + a, b, c = _des_0[1], _des_0[2], _des_0[3] + ok(a, b, c) +end +local _max_0 = 3 + 4 +for _index_0 = 1 + 2, _max_0 < 0 and #items + _max_0 or _max_0 do + local item = items[_index_0] + _ = item +end +local _max_1 = 2 - thing[4] +for _index_0 = hello() * 4, _max_1 < 0 and #items + _max_1 or _max_1 do + local item = items[_index_0] + _ = item +end return nil \ No newline at end of file diff --git a/spec/outputs/cond.lua b/spec/outputs/cond.lua index 036f5469..35e7ac56 100644 --- a/spec/outputs/cond.lua +++ b/spec/outputs/cond.lua @@ -246,8 +246,34 @@ if not (value) then 3 } end +do + local j = 100 + do + j = hi() + if not j then + error("not j!") + end + end +end local a = 12 local c, b if something then a, c, b = "cool" -end \ No newline at end of file +end +local j +if 1 then + if 2 then + j = 3 + end +else + j = 6 +end +local m +if 1 then + if 2 then + m = 3 + end +else + m = 6 +end +return nil \ No newline at end of file diff --git a/spec/outputs/destructure.lua b/spec/outputs/destructure.lua index 28315438..8254e3c9 100644 --- a/spec/outputs/destructure.lua +++ b/spec/outputs/destructure.lua @@ -26,27 +26,15 @@ do local _obj_0 = yeah a, b, c, d = _obj_0.a, _obj_0.b, _obj_0.c, _obj_0.d end - do - local _obj_0 = one - a = _obj_0[1] - end + a = one[1] local _ = two - do - local _obj_0 = one - b = _obj_0[1] - end + b = one[1] c = nil - do - local _obj_0 = one - d = _obj_0[1] - end + d = one[1] local e = two local x = one local y - do - local _obj_0 = two - y = _obj_0[1] - end + y = two[1] local xx, yy = 1, 2 do local _obj_0 = { @@ -77,18 +65,12 @@ do name, street, city = futurists.poet.name, futurists.poet.address[1], futurists.poet.address[2] end do - do - local _obj_0 = x - self.world = _obj_0[1] - end + self.world = x[1] do local _obj_0 = x a.b, c.y, func().z = _obj_0[1], _obj_0[2], _obj_0[3] end - do - local _obj_0 = x - self.world = _obj_0.world - end + self.world = x.world end do local thing = { diff --git a/spec/outputs/export.lua b/spec/outputs/export.lua index 72ebadea..08fb4ce8 100644 --- a/spec/outputs/export.lua +++ b/spec/outputs/export.lua @@ -4,11 +4,12 @@ do end do do + local _class_0 local _base_0 = { umm = "cool" } _base_0.__index = _base_0 - local _class_0 = setmetatable({ + _class_0 = setmetatable({ __init = function() end, __base = _base_0, __name = "Something" diff --git a/spec/outputs/funcs.lua b/spec/outputs/funcs.lua index 35dee928..f5e2cf3c 100644 --- a/spec/outputs/funcs.lua +++ b/spec/outputs/funcs.lua @@ -108,8 +108,103 @@ k(function() return end end) -return function() +_ = function() if something then return real_name end -end \ No newline at end of file +end +d(function() + return print("hello world") +end, 10) +d(1, 2, 3, 4, 5, 6, (function() + if something then + print("okay") + return 10 + end +end)(), 10, 20) +f()()(what)(function() + return print("srue") +end, 123) +x = function(a, b) + return print("what") +end +local y +y = function(a, b) + if a == nil then + a = "hi" + end + if b == nil then + b = 23 + end + return print("what") +end +local z +z = function(a, b) + if a == nil then + a = "hi" + end + if b == nil then + b = 23 + end + return print("what") +end +local j +j = function(f, g, m, a, b) + if a == nil then + a = "hi" + end + if b == nil then + b = 23 + end + return print("what") +end +y = function(a, b, ...) + if a == nil then + a = "hi" + end + if b == nil then + b = 23 + end + return print("what") +end +y = function(a, b, ...) + if a == nil then + a = "hi" + end + if b == nil then + b = 23 + end + return print("what") +end +local args +args = function(a, b) + return print("what") +end +args = function(a, b) + if a == nil then + a = "hi" + end + if b == nil then + b = 23 + end + return print("what") +end +args = function(a, b) + if a == nil then + a = "hi" + end + if b == nil then + b = 23 + end + return print("what") +end +args = function(f, g, m, a, b) + if a == nil then + a = "hi" + end + if b == nil then + b = 23 + end + return print("what") +end +return nil \ No newline at end of file diff --git a/spec/outputs/import.lua b/spec/outputs/import.lua index f7467f4f..4c6520eb 100644 --- a/spec/outputs/import.lua +++ b/spec/outputs/import.lua @@ -1,8 +1,5 @@ local hello -do - local _obj_0 = yeah - hello = _obj_0.hello -end +hello = yeah.hello local world do local _obj_0 = table["cool"] @@ -34,10 +31,7 @@ local yumm a, yumm = 3434, "hello" local _table_0 = 232 local something -do - local _obj_0 = a(table) - something = _obj_0.something -end +something = a(table).something if indent then local okay, well do diff --git a/spec/outputs/lists.lua b/spec/outputs/lists.lua index 81aaa5b9..abb167eb 100644 --- a/spec/outputs/lists.lua +++ b/spec/outputs/lists.lua @@ -161,14 +161,14 @@ do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #items do - x = items[_index_0] + local x = items[_index_0] _accum_0[_len_0] = x * 2 _len_0 = _len_0 + 1 end double = _accum_0 end for _index_0 = 1, #double do - x = double[_index_0] + local x = double[_index_0] print(x) end local cut @@ -176,7 +176,7 @@ do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #items do - x = items[_index_0] + local x = items[_index_0] if x > 3 then _accum_0[_len_0] = x _len_0 = _len_0 + 1 @@ -189,7 +189,7 @@ do local _accum_0 = { } local _len_0 = 1 for _index_0 = 1, #items do - x = items[_index_0] + local x = items[_index_0] for _index_1 = 1, #items do local y = items[_index_1] _accum_0[_len_0] = x + y @@ -254,27 +254,10 @@ for _index_0 = 1, #test do local thing = test[_index_0] print(thing) end -local _ -_ = function() +return function() local _list_0 = rows for _index_0 = 1, #_list_0 do local row = _list_0[_index_0] a = b end -end -_ = function() - for _index_0 = 1, #things do - x = things[_index_0] - _ = x - end -end -return function() - local _accum_0 = { } - local _len_0 = 1 - for _index_0 = 1, #things do - x = things[_index_0] - _accum_0[_len_0] = x - _len_0 = _len_0 + 1 - end - return _accum_0 end \ No newline at end of file diff --git a/spec/outputs/literals.lua b/spec/outputs/literals.lua index b49c57a8..d8fc7e2b 100644 --- a/spec/outputs/literals.lua +++ b/spec/outputs/literals.lua @@ -10,6 +10,12 @@ _ = 0xABCDEF _ = .2323 _ = .2323e-1 _ = .2323e13434 +_ = 1LL +_ = 1ULL +_ = 9332LL +_ = 9332 +_ = 0x2aLL +_ = 0x2aULL _ = [[ hello world ]] _ = [=[ hello world ]=] _ = [====[ hello world ]====] diff --git a/spec/outputs/local.lua b/spec/outputs/local.lua index db19697a..3ee8623e 100644 --- a/spec/outputs/local.lua +++ b/spec/outputs/local.lua @@ -70,6 +70,73 @@ do d = 200 d = 2323 end +do + local Uppercase, One, Two + local lowercase = 5 + Uppercase = 3 + do + local _class_0 + local Five + local _base_0 = { } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function() end, + __base = _base_0, + __name = "One" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + Five = 6 + One = _class_0 + end + do + local _class_0 + local No + local _base_0 = { } + _base_0.__index = _base_0 + _class_0 = setmetatable({ + __init = function() end, + __base = _base_0, + __name = "Two" + }, { + __index = _base_0, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_0) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_0.__class = _class_0 + local self = _class_0 + do + local _class_1 + local _base_1 = { } + _base_1.__index = _base_1 + _class_1 = setmetatable({ + __init = function() end, + __base = _base_1, + __name = "No" + }, { + __index = _base_1, + __call = function(cls, ...) + local _self_0 = setmetatable({}, _base_1) + cls.__init(_self_0, ...) + return _self_0 + end + }) + _base_1.__class = _class_1 + No = _class_1 + end + Two = _class_0 + end +end do local _list_0 = { } for _index_0 = 1, #_list_0 do diff --git a/spec/outputs/loops.lua b/spec/outputs/loops.lua index dcba0bc1..57fe40be 100644 --- a/spec/outputs/loops.lua +++ b/spec/outputs/loops.lua @@ -62,7 +62,7 @@ do end x = function() for _index_0 = 1, #hello do - x = hello[_index_0] + local x = hello[_index_0] local _ = y end end @@ -260,6 +260,38 @@ for x = 1, 10 do break end end +while true do + local _continue_0 = false + repeat + do + if false then + _continue_0 = true + break + end + break + end + _continue_0 = true + until true + if not _continue_0 then + break + end +end +while true do + local _continue_0 = false + repeat + do + if false then + _continue_0 = true + break + end + return 22 + end + _continue_0 = true + until true + if not _continue_0 then + break + end +end do local xxx = { 1, diff --git a/spec/outputs/operators.lua b/spec/outputs/operators.lua new file mode 100644 index 00000000..9829ef57 --- /dev/null +++ b/spec/outputs/operators.lua @@ -0,0 +1,29 @@ +local x = 1 + 3 +local y = 1 + 3 +local z = 1 + 3 + 4 +local k = b and c and g +local h = thing and function() + return print("hello world") +end +local i = thing or function() + return print("hello world") +end +local p = thing and function() end +print("hello world") +local s = thing or function() end and 234 +local u = { + color = 1 and 2 and 3, + 4, + 4 +} +local v = { + color = 1 and function() + return "yeah" + end, + "great", + oksy = 3 ^ 2 +} +local nno = (yeah + 2) +local nn = (yeah + 2) +local n = hello(b)(function() end) +return hello(a, (yeah + 2) - okay) \ No newline at end of file diff --git a/spec/outputs/return.lua b/spec/outputs/return.lua new file mode 100644 index 00000000..4eeaca20 --- /dev/null +++ b/spec/outputs/return.lua @@ -0,0 +1,102 @@ +local _ +_ = function() + local _list_0 = things + for _index_0 = 1, #_list_0 do + local x = _list_0[_index_0] + _ = x + end +end +_ = function() + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = things + for _index_0 = 1, #_list_0 do + local x = _list_0[_index_0] + _accum_0[_len_0] = x + _len_0 = _len_0 + 1 + end + return _accum_0 +end +do + local _list_0 = things + for _index_0 = 1, #_list_0 do + local x = _list_0[_index_0] + return x + end +end +do + local _accum_0 = { } + local _len_0 = 1 + local _list_0 = things + for _index_0 = 1, #_list_0 do + local x = _list_0[_index_0] + _accum_0[_len_0] = x + _len_0 = _len_0 + 1 + end + return _accum_0 +end +do + local _tbl_0 = { } + local _list_0 = things + for _index_0 = 1, #_list_0 do + local x, y = _list_0[_index_0] + _tbl_0[x] = y + end + return _tbl_0 +end +_ = function() + if a then + if a then + return a + else + return b + end + elseif b then + if a then + return a + else + return b + end + else + if a then + return a + else + return b + end + end +end +do + if a then + if a then + return a + else + return b + end + elseif b then + if a then + return a + else + return b + end + else + if a then + return a + else + return b + end + end +end +_ = function() + local _base_0 = a + local _fn_0 = _base_0.b + return function(...) + return _fn_0(_base_0, ...) + end +end +do + local _base_0 = a + local _fn_0 = _base_0.b + return function(...) + return _fn_0(_base_0, ...) + end +end \ No newline at end of file diff --git a/spec/outputs/string.lua b/spec/outputs/string.lua index 2d751d81..a928c5d3 100644 --- a/spec/outputs/string.lua +++ b/spec/outputs/string.lua @@ -26,7 +26,7 @@ local f = [[hello #{world} world]] a = 'hello #{hello} hello' b = '#{hello} hello' c = 'hello #{hello}' -local _ = "hello" +local _ = "hello"; ("hello"):format(1); ("hello"):format(1, 2, 3); ("hello"):format(1, 2, 3)(1, 2, 3); diff --git a/spec/outputs/stub.lua b/spec/outputs/stub.lua index 1f02d09c..4ceac088 100644 --- a/spec/outputs/stub.lua +++ b/spec/outputs/stub.lua @@ -4,19 +4,20 @@ local x = { return print(self.val) end } -local fn = (function() +local fn +do local _base_0 = x local _fn_0 = _base_0.val - return function(...) + fn = function(...) return _fn_0(_base_0, ...) end -end)() +end print(fn()) print(x:val()) -x = (function(...) +do local _base_0 = hello(...) local _fn_0 = _base_0.world - return function(...) + x = function(...) return _fn_0(_base_0, ...) end -end)(...) \ No newline at end of file +end \ No newline at end of file diff --git a/spec/outputs/syntax.lua b/spec/outputs/syntax.lua index 72d3dab6..8667f348 100644 --- a/spec/outputs/syntax.lua +++ b/spec/outputs/syntax.lua @@ -53,6 +53,14 @@ end) something('else', "ya") something('else') something("else") +_ = something([[hey]]) * 2 +_ = something([======[hey]======]) * 2 +_ = something('else'), 2 +_ = something("else"), 2 +_ = something([[else]]), 2 +something('else', 2) +something("else", 2) +something([[else]], 2) _ = here(we)("go")[12123] local something = { test = 12323, @@ -154,6 +162,14 @@ local m = m % 2 local hello = hello .. "world" self.__class.something = self.__class.something + 10 self.something = self.something + 10 +self.__class["then"] = self.__class["then"] + 10 +self["then"] = self["then"] + 10 +local _update_0 = "hello" +a[_update_0] = a[_update_0] + 10 +local _update_1 = "hello" .. tostring(tostring(ff)) +a[_update_1] = a[_update_1] + 10 +local _update_2 = four +a[_update_2].x = a[_update_2].x + 10 x = 0 local _list_0 = values for _index_0 = 1, #_list_0 do @@ -178,7 +194,7 @@ _ = 5 + what(wack) what(whack + 5) _ = 5 - what(wack) what(whack - 5) -x = hello - world - something +x = hello - world - something; (function(something) if something == nil then do @@ -233,6 +249,8 @@ another(hello, one, two, three, four, { a = a + (3 - 5) a = a * (3 + 5) a = a * 3 +a = a >> 3 +a = a << 3 a = a / func("cool") x["then"] = "hello" x["while"]["true"] = "hello" diff --git a/spec/outputs/tables.lua b/spec/outputs/tables.lua index 7eabec1c..b6a4f587 100644 --- a/spec/outputs/tables.lua +++ b/spec/outputs/tables.lua @@ -125,4 +125,82 @@ local xam = { hello = 1234, ["hello"] = 12354, ["hello"] = 12354 -} \ No newline at end of file +} +local kam = { + hello = 12, + goodcheese = "mmm", + yeah = 12 + 232, + lets = keepit({ + going = true + }, { + okay = "yeah" + }), + more = { + 1, + (function() + local _accum_0 = { } + local _len_0 = 1 + for x = 1, 10 do + _accum_0[_len_0] = x + _len_0 = _len_0 + 1 + end + return _accum_0 + end)() + }, + [{ + "one", + "two" + }] = one_thing(function(self) end) +} +keepit({ + going = true +}, { + okay = "yeah", + workd = "okay" +}) +thing({ + what = "great", + no = "more" +}, { + okay = 123 +}) +thing({ + what = "great", + no = "more" +}) +local _ = { + okay = 123 +} +local k = { + ["hello"] = "world" +} +k = { + ['hello'] = 'world' +} +k = { + ["hello"] = 'world', + ["hat"] = "zat" +} +please({ + ["hello"] = "world" +}) +k = { + ["hello"] = "world", + ["one"] = "zone" +} +local f = "one", { + ["two"] = three +}, "four" +f = { + ["two"] = three +}, "four" +f = { + "one", + ["two"] = three, + "four" +} +local j = "one", { + ["two"] = three, + ["four"] = five +}, 6, 7 +return nil \ No newline at end of file diff --git a/spec/outputs/unless_else.lua b/spec/outputs/unless_else.lua new file mode 100644 index 00000000..f05e7517 --- /dev/null +++ b/spec/outputs/unless_else.lua @@ -0,0 +1,7 @@ +if a then + if not (b) then + return print("hi") + elseif c then + return print("not hi") + end +end \ No newline at end of file diff --git a/spec/outputs/whitespace.lua b/spec/outputs/whitespace.lua index ce2d7920..c112a055 100644 --- a/spec/outputs/whitespace.lua +++ b/spec/outputs/whitespace.lua @@ -72,5 +72,9 @@ if hello(1, 2, 3, world, world) then print("hello") end if hello(1, 2, 3, world, world) then - return print("hello") -end \ No newline at end of file + print("hello") +end +a(one, two, three) +b(one, two, three) +c(one, two, three, four) +return nil \ No newline at end of file diff --git a/spec/outputs/with.lua b/spec/outputs/with.lua index d1d104a0..3b2eec3b 100644 --- a/spec/outputs/with.lua +++ b/spec/outputs/with.lua @@ -131,6 +131,42 @@ do local _with_0 = "jo" k.j = _with_0 print(_with_0:upper()) + end +end +do + do + local _with_0 = a + print(_with_0.b) + do + local _with_1 = _with_0.c + print(_with_1.d) + end + end +end +do + do + local _with_0 = a + do + local _with_1 = 2 + _with_0.b = _with_1 + print(_with_1.c) + end + end +end +do + local _ + _ = function() + do + local _with_0 = hi + return _with_0.a, _with_0.b + end + end +end +do + do + local _with_0 = dad + _with_0["if"]("yes") + local y = _with_0["end"].of["function"] return _with_0 end end \ No newline at end of file diff --git a/spec/transform_spec.moon b/spec/transform_spec.moon new file mode 100644 index 00000000..9bb06a92 --- /dev/null +++ b/spec/transform_spec.moon @@ -0,0 +1,257 @@ + +import with_dev from require "spec.helpers" + + +describe "moonscript.transform.destructure", -> + local extract_assign_names, split_assign, Block + + with_dev -> + { :extract_assign_names, :split_assign } = require "moonscript.transform.destructure" + {:Block} = require "moonscript.compile" + + describe "split_assign #fff", -> + -- {:hello} = world + it "simple assignment", -> + node = { + "assign" + { + { "table", { + {{"key_literal", "hello"}, {"ref", "hello"}} + } + } + } + { + {"ref", "world"} + } + } + + out = split_assign Block!, node + + assert.same { "group", { + { "group", { + { "declare", { {"ref", "hello"} } } + { "assign", { {"ref", "hello"} }, { {"chain", {"ref", "world"}, {"dot", "hello"}} } } + }} + }}, out + + -- {:a, :b} = world! + -- a complex value should never be repeated to avoid double execution + it "complex value", -> + node = { + "assign" + { + { "table", { + {{"key_literal", "a"}, {"ref", "a"}} + {{"key_literal", "b"}, {"ref", "b"}} + } + } + } + { + {"chain", {"ref", "world"}, {"call", {}}} + } + } + + out = split_assign Block!, node + + -- the temp name the result is stored into + tmp = {"temp_name", prefix: "obj"} + + assert.same { "group", { + { "group", { + { "declare", { {"ref", "a"}, {"ref", "b"} } } + + { "do", { + {"assign", { tmp }, { {"chain", {"ref", "world"}, {"call", {}}} } } + {"assign", { {"ref", "a"}, {"ref", "b"} }, { {"chain", tmp, {"dot", "a"}}, {"chain", tmp, {"dot", "b"}} } } + }} + }} + }}, out + + -- a, {:hello} = one, two + it "multiple assigns", -> + node = { + "assign" + { + {"ref", "a"} + { "table", { + {{"key_literal", "hello"}, {"ref", "hello"}} + } + } + } + { + {"ref", "one"} + {"ref", "two"} + } + } + + out = split_assign Block!, node + + assert.same { "group", { + {"assign", { {"ref", "a"} }, { {"ref", "one"} }} + + { "group", { + { "declare", { {"ref", "hello"} } } + { "assign", { {"ref", "hello"} }, { {"chain", {"ref", "two"}, {"dot", "hello"}} } } + }} + }}, out + + -- {:hello}, a = one, two + it "multiple assigns swapped", -> + node = { + "assign" + { + { "table", { + {{"key_literal", "hello"}, {"ref", "hello"}} + } + } + {"ref", "a"} + } + { + {"ref", "one"} + {"ref", "two"} + } + } + + out = split_assign Block!, node + + assert.same { "group", { + { "group", { + { "declare", { {"ref", "hello"} } } + { "assign", { {"ref", "hello"} }, { {"chain", {"ref", "one"}, {"dot", "hello"}} } } + }} + + {"assign", { {"ref", "a"} }, { {"ref", "two"} }} + }}, out + + + it "extracts names from table destructure", -> + des = { + "table" + { + {{"key_literal", "hi"}, {"ref", "hi"}} + {{"key_literal", "world"}, {"ref", "world"}} + } + } + + assert.same { + { + {"ref", "hi"} -- target + { + {"dot", "hi"} + } -- chain suffix + } + + { + {"ref", "world"} + { + {"dot", "world"} + } + } + + }, extract_assign_names des + + it "extracts names from array destructure", -> + des = { + "table" + { + {{"ref", "hi"}} + } + } + + assert.same { + { + {"ref", "hi"} + { + {"index", {"number", 1}} + } + } + }, extract_assign_names des + +describe "moonscript.transform.statements", -> + local last_stm, transform_last_stm, Run + + with_dev -> + { :last_stm, :transform_last_stm, :Run } = require "moonscript.transform.statements" + + describe "last_stm", -> + it "gets last statement from empty list", -> + assert.same nil, (last_stm {}) + + it "gets last statement", -> + stms = { + {"ref", "butt_world"} + {"ref", "hello_world"} + } + + stm, idx, t = last_stm stms + assert stms[2] == stm + assert.same 2, idx + assert stms == t + + it "gets last statement ignoring run", -> + stms = { + {"ref", "butt_world"} + {"ref", "hello_world"} + Run => print "hi" + } + + stm, idx, t = last_stm stms + assert stms[2] == stm + assert.same 2, idx + assert stms == t + + it "gets last from within group", -> + stms = { + {"ref", "butt_world"} + {"group", { + {"ref", "hello_world"} + {"ref", "cool_world"} + }} + } + + last = stms[2][2][2] + + stm, idx, t = last_stm stms + assert stm == last, "should get last" + assert.same 2, idx + assert t == stms[2][2], "should get correct table" + + describe "transform_last_stm", -> + + it "transforms empty stms", -> + before = {} + after = transform_last_stm before, (n) -> {"wrapped", n} + + assert.same before, after + assert before != after + + it "transforms stms", -> + before = { + {"ref", "butt_world"} + {"ref", "hello_world"} + } + + transformer = (n) -> n + after = transform_last_stm before, transformer + + assert.same { + {"ref", "butt_world"} + {"transform", {"ref", "hello_world"}, transformer} + }, after + + it "transforms empty stms ignoring runs", -> + before = { + {"ref", "butt_world"} + {"ref", "hello_world"} + Run => print "hi" + } + + transformer = (n) -> n + after = transform_last_stm before, transformer + + assert.same { + {"ref", "butt_world"} + {"transform", {"ref", "hello_world"}, transformer} + before[3] + }, after + diff --git a/thoughts b/thoughts index 735f6684..dfd6b69d 100644 --- a/thoughts +++ b/thoughts @@ -3,16 +3,10 @@ # # --- local * and local ^ - --- seems like running in moon messes up require order - - don't reuse _, put a local on it, so we don't keep around trash -- swithc X with Func -- or= and= - - error with stray comma at end of line * multiline comments @@ -20,22 +14,18 @@ * combine for and if line decorators -* export later? nah - - x = 232 - export x - - * allow return anywhere in block -* any/every keywords for comprehensions? (what about iterators) +-- all function literals have a string that is their function definition -* let array items in table be defined without {} when indented (no, too similar to arguments) +-- super should work here: --- for searching? for returning to accumulator early? -x = for thing in *things - if is_important thing - break thing +thing = Thing! +thing.method = -> + super 1,2,3 --- all function literals have a string that is their function definition +-- goes to +thing.method = function(self) do + self.__class:method(1,2,3) +end